Re: [xsl] producing three sets of output from one file

Subject: Re: [xsl] producing three sets of output from one file
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Fri, 29 Mar 2002 14:47:47 -0500
Eric,

What appears below is *not* a beginner's solution, but it's an interesting approach to your problem. While less evidently straightforward than Joerg's solution, it has the virtue of a more complete modeling of the problem, making it more flexible and extensible.

You start with a lookup table in your stylesheet:

<my:privileges xmlns:my="mynamespace.com"> <!-- put the table in a local namespace -->
<rights group="C">
<rights group="B">
<rights group="A"/>
</rights>
</rights>
</my:privileges>At 12:23 PM 3/29/2002, you wrote:


Notice that the design of these nodes reflects the relations of the groups: that C can see B's and A's stuff, that B can see A's stuff but not C's, and that A can only see A's stuff.

Next, you create a utility variable just so you can pick any of these nodes out easily:

<xsl:variable name="rightsgroups" select="document('')/*/my:privileges//rights"/>

Okay, that's "advanced": it queries the stylesheet itself with the document('') function, then traverses down via an XPath to collect all the rights elements inside your my:privileges node. (Actually you could do without this variable, rolling it into the next one, but breaking it out makes it a bit easier to understand.)

Next, you want to locate the *subset* of these nodes in which you are actually interested. You do this with a parameter, which you can pass into the stylesheet at run time:

<xsl:param name="thisgroup" select="'A'"/>

(This declaration sets the default to 'A' but it could be the empty string '' if you wanted to default to no permissions, or 'C' for the highest, whatever.)

And then,

<xsl:variable name="theseprivileges"
              select="$rightsgroups[@group=$thisgroup]//rights/@group"/>

This fancy expression means "all the 'group' attribute nodes [@group] on any rights nodes that are descendants or self of the node from $rightsgroups whose @group attribute equals $thisgroup".

The long way of writing this expression would look like:

$rightsgroups[@group=$thisgroup]/descendant-or-self::rights/@group

This way, if your parameter is 'C' you'll get the @group attribute on the C rights node *along with the @group attributes* on its descendants, B and A; but if $thisgroup is 'A', you'll only get the group='A' attribute node (since its 'rights' element has no descendants).

Now, you proceed as Joerg did, except you can use the equality rule for nodesets and strings to collect nodes from your source file that are permitted to each group:

<xsl:apply-templates select="article[@audience = $theseprivileges]"/>

Remember that $theseprivileges is a *collection* of attribute nodes: in your case, if $thisgroup is A, it's a lone @group attribute with value 'A'; but if $thisgroup is C, it's a collection of @group attributes, with values 'A', 'B', 'C'.

Since a string is equal to a nodeset if its value is the value of *any* of the nodes in the set, you can see you'll get your A, B, and C articles when your group is C, but only your A articles when your group is A.

While it's obscure (and if you use this code, *document* it for the poor XSLT beginner who takes over your job when you move up), you can see how powerful and extensible this approach is. All you need to do is extend your lookup table to accommodate for more groups and their relations:

<my:privileges>
  <rights group="admin">
    <rights group="superusers">
      <rights group="daytimers">
         <rights group="guests"/>
         <rights group="gamers"/>
      </rights>
      <rights group="nighttimers">
         <rights="gamers"/>
      </rights>
    </rights>
  </rights>
</my:privileges>

And it works with any strings as group labels, not just one-character strings. Here, the admin group gets all articles, the daytimers get their own stuff plus guests' and gamers' articles, but gamers get only gamers' stuff. Nightimers get their stuff plus gamers'; the appearance of one group in several places is not a problem here.

I know that's more than you asked for, but your problem was interesting enough that I thought it was worth sketching out. While not, as you requested, a beginner's solution, it does demonstrate some things that beginners (or advanced beginners) might like to know about:

1. using a local (or remote) lookup table for very flexible configuration (with the document() function for access)
2. XPath expressions (in your variable declarations etc.) to select *and filter* nodes
3. testing a string against a node set -- true when *any* node contains the string (a very useful feature once you're aware of it)
4. using tree structures to represent more complex relations than simple linear relations (such as the relations in a permissions hierarchy)


I hope that's fun, anyway--

Cheers,
Wendell

> I'm trying produce three sets of output from one set of data, each of
> which are subsets of the preceding.  That is, I have:
>     <article audience="A">stuff here</article>
>     <article audience="B">stuff here</article>
>     <article audience="C">stuff here</article>
>     <article audience="A">stuff here</article>
>     <article audience="A">stuff here</article>
>
> For audience A, I want to include only the A articles; for audience B, I
> want to include both A&B articles, and for audience C, I want to include
> them all. I was preparing to just create three different XSL files and
> then use Instant Saxon to transform with each XSL. However, since the
> output is formatted identically, I suspect there's a much more elegant
> way to do it (perhaps passing some parameter when compiling?), but I'm
> not sure what I would do.
>
> If this is explainable to a beginner, I'd appreciate any help.  (Or if
> this is beyond a beginner's ability, I'd appreciate someone telling me
> that... and I'll do the easy three files as I planned and come back to
> this at some point in the future.)


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================


XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list



Current Thread