RE: [xsl] Complicated grouping and column question

Subject: RE: [xsl] Complicated grouping and column question
From: "Brook Ellingwood" <belling@xxxxxxx>
Date: Wed, 29 Jun 2005 14:08:59 -0700
I just did this exact same thing. My solution was to find middle child of the
parent elements, and identify it with a key, then put all the
preceding-siblings of that child's parent, plus the parent itself in the left
hand column, and all the following-siblings of that child's parent in the
right hand column. It works pretty well, but since the number of parents is
variable and in the output the spacing between them is greater than the
spacing between children, the lengths of the columns can be pretty uneven
depending on the particular file. I've thought a little bit about trying to
account for the number of parents to correct for this, but it's not a major
priority in my application. Might be in yours, however.

Sample of my XML:

	<rec_gear_sec>
		<rec_gear_title>Official Papers</rec_gear_title>
		<rec_gear>Valid passport</rec_gear>
		<rec_gear>Tourist visa (given upon arrival in New Zealand)</rec_gear>
		<rec_gear>Airline tickets</rec_gear>
	</rec_gear_sec>
	<rec_gear_sec>
		<rec_gear_title>Luggage</rec_gear_title>
		<rec_gear>Duffel bag (wheels and retractable handle are fine), sturdy and
large enough to hold clothing and gear</rec_gear>
		<rec_gear>Daypack, sized to carry your camera, water bottles and extra
clothing</rec_gear>
		<rec_gear>Passport security pouch or belt</rec_gear>
		<rec_gear>Luggage tags and luggage locks</rec_gear>
	</rec_gear_sec>
	<rec_gear_sec>
		<rec_gear_title>Camping Gear</rec_gear_title>
		<rec_gear>Sleeping bag; rated to 30-40B0F, compressible w/stuff sack (if you
prefer to bring your own)</rec_gear>
	</rec_gear_sec>
	<rec_gear_sec>
		<rec_gear_title>City Clothing</rec_gear_title>
		<rec_gear>Lightweight, easily washable items for city wear or when
traveling</rec_gear>
	</rec_gear_sec>
	<rec_gear_sec>
		<rec_gear_title>Clothing</rec_gear_title>
		<rec_gear>T-shirts</rec_gear>
		<rec_gear>Long sleeve shirts</rec_gear>
		<rec_gear>Hiking pants</rec_gear>
		<rec_gear>Hiking shorts</rec_gear>
		<rec_gear>Lightweight fleece or synthetic top</rec_gear>
		<rec_gear>Underwear</rec_gear>
		<rec_gear>Lightweight thermal underwear top and bottom, synthetic or
wool</rec_gear>
		<rec_gear>Hiking and liner socks</rec_gear>
		<rec_gear>Swimsuit</rec_gear>
	</rec_gear_sec>

My XSL (relevant parts):

<xsl:variable name="gearMid"
select="generate-id((//rec_gear_sec/*)[round(count(//rec_gear_sec/*) div
2)])"/>

	<xsl:template name="rec_gear_list">
		<xsl:for-each select="//rec_gear_sec/*[generate-id(.) = $gearMid]">
			<div style="width:275; float:left;padding-right:10px;">
				<xsl:apply-templates
select="(../preceding-sibling::*)[name()='rec_gear_sec']"/>
				<xsl:apply-templates select=".."/>
			</div>
			<div style="width:285; float:right;">
				<xsl:apply-templates
select="(../following-sibling::*)[name()='rec_gear_sec']"/>
			</div>
		</xsl:for-each>
	</xsl:template>

The parent 'rec_gear_sec' elements also have unrelated siblings with different
names, so I had to filter in the predicate by name() value.

Hope that gets you pointed in a worthwhile direction.

-- Brook


-----Original Message-----
From: Will McCutchen [mailto:mccutchen@xxxxxxxxx]
Sent: Wednesday, June 29, 2005 1:56 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: [xsl] Complicated grouping and column question


Hi everyone,

I've got a problem trying to create two columns for an index of data.
I can't for the life of me figure out how to break the data over two columns.

This might be hard for me to explain, so please bear with me.

I've got a large XML file representing a course schedule for the community
college where I work.  Here's a small sample of the format:

<schedule>
    <division name="Business">
        <cluster name="Ethics">
            <course title="..." />
            <course title="..." />
            <course title="..." />
        </cluster>
        <cluster name="Accounting">
            <course title="..." />
        </cluster>
    </division>

    <division name="Arts">
        <cluster name="Painting">
            <course title="..." />
        </cluster>
        <cluster name="Drawing">
            <course title="..." />
        </cluster>
    </division>
</schedule>

(The actual format is a little bit more complicated than that, but this is all
of the structure that's involved in creating the index.)

I've got a stylesheet that creates a nice alphabetical index based on the
schedule XML file.  For the sample above, the index would look like this:

A
	Arts
		Painting
		Drawing
B
	Business
		Accounting
		Ethics

So, in the index, the top level is a letter from A-Z, the second level is a
<division> name and the third level is a <cluster> name.

I'm using XSLT 2.0 and Saxon 8.4.  Here's the important part of the current
stylesheet (which is just outputting a simple HTML unordered list, at the
moment):

<xsl:template name="make-index">
	<ul>
        <xsl:for-each-group select="/schedule/division"
group-by="substring(@name,1,1)">
            <xsl:sort select="current-grouping-key()" />
            <xsl:variable name="current-letter"
select="current-grouping-key()" />

            <li>
                <h2><xsl:value-of select="$current-letter" /></h2>
                <ul>
                    <xsl:apply-templates
select="/schedule/division[substring(@name,1,1) = $current-letter]">
                        <xsl:sort select="@name" />
                    </xsl:apply-templates>
                </ul>
            </li>
        </xsl:for-each-group>
    </ul>
</xsl:template>

<xsl:template match="division">
    <li>
        <xsl:value-of select="@name" />
        <xsl:if test="cluster">
            <ul>
                <xsl:apply-templates select="cluster">
                    <xsl:sort select="@name" />
                </xsl:apply-templates>
            </ul>
        </xsl:if>
    </li>
</xsl:template>

<xsl:template match="cluster">
	<li><xsl:value-of select="@name" /></li>
</xsl:template>


Because I'm using for-each-group to break the divisions up into sections by
their first letters, I just can't figure out how to calculate a good place to
break the output into two columns.  The columns need to break on a letter, so
the "C" section couldn't break at the bottom of one column and continue at the
top of the other.  One other problem is that the index is weighted heavily
towards the front of the alphabet, so the break will probably need to come
around D or E.

If this post made any sense and anyone has any suggestions, I'll be grateful.
If I need to explain myself better, or provide a complete input document, I'd
be glad to do so.

Thanks in advance for your help,


Will.

Current Thread