Print

Print


Hi Eric,

Whew, this is a tangle! Getting the descendants of a given element is 
easy in XPath, and your nested <xsl:for-each> instructions should do the 
right thing. You could even combine the two instructions with this XPath:

     .//presentation//person

The difficulty you noted is that there's no hierarchy to explicitly 
connect any given person to their affiliation. For each person, you need 
to get the following elements named 'affiliation' that _do not_ occur 
after a following <person>. This is not an easy XPath to write!

When I've run into this problem in the past, I've used a named XSLT 
template which takes the next X number of siblings and tests if they 
meet an inclusion criteria (here, "am I an <affiliation>?" or 
`exists(self::affiliation)` ). Everything after the first non-match is 
thrown out.

However, this is not a method I'd recommend to anyone, because it turns 
out there's a somewhat easier method using <xsl:for-each-group>:

    <xsl:for-each-group select="presentation/*"
    group-starting-with="person">
           <xsl:variable name="personAdjGrp" select="current-group()"/>
           <xsl:variable name="currentPerson"
    select="$personAdjGrp[self::person]"/>
           <xsl:variable name="allAffiliations"
    select="$personAdjGrp[self::affiliation]"/>
           <xsl:variable name="paperTitle"
    select="normalize-space(../paper)"/>
           <xsl:message>
             <xsl:text>Working on </xsl:text>
             <xsl:value-of select="normalize-space($currentPerson)"/>
             <xsl:text> of </xsl:text>
             <xsl:value-of select="$paperTitle"/>
           </xsl:message>

           <xsl:value-of
    select="normalize-space($currentPerson)"/><xsl:text>&#09;</xsl:text>
           <xsl:value-of
    select="string-join($allAffiliations/normalize-space(),
    '|')"/><xsl:text>&#09;</xsl:text>
           <xsl:value-of select="$paperTitle"/><xsl:text>&#10;</xsl:text>
         </xsl:for-each-group>


I found out about this method in Michael Kay's excellent _XSLT 2.0 and 
XPath 2.0_. It forms a group of all sibling elements that occur after 
<person>, but before the next <person>. You can then use the 
`current-group()` function to get the sequence of elements that for sure 
are associated with this particular person, and tests on the self axis 
to narrow _those_ down to only the elements of a certain type. I added 
some special sauce with the `string-join()` function to put a '|' 
between each affiliation for a given person.

Hope this helps!

Best,
Ashley



On 11/16/17 12:42 PM, Eric Lease Morgan wrote:
> How can I use XSLT to find the direct descendants of a given element with a given name?
>
> I want to create a tab-delimited version of an XML file. I have the following XML snippet, and notice how each of the persons in the first presentation have a single affiliation, but the person of the second presentation has two affiliations:
>
>    <session>
>      <presentation>
>        <person>RICHARD G. ANDERSON</person>
>        <affiliation>Lindenwood University</affiliation>
>        <person>AREERAT KICHKHA</person>
>        <affiliation>Lindenwood University</affiliation>
>        <paper>Is Less More?</paper>
>      </presentation>
>      <presentation>
>        <person>BRIAN W. SLOBODA</person>
>        <affiliation>University of Maryland</affiliation>
>        <affiliation>University College</affiliation>
>        <paper>Inflation Policies</paper>
>      </presentation>
>    </session>
>
> I want my resulting tab-delimited file to look like this:
>
>    RICHARD G. ANDERSON  Lindenwood University                      Is Less More?
>    AREERAT KICHKHA      Lindenwood University                      Is Less More?
>    BRIAN W. SLOBODA     University of Maryland|University College  Inflation Policies
>
> I have the following XSLT snippet, but my process of getting affiliations is not nearly correct:
>
>    <xsl:for-each select=".//presentation">
>      <xsl:for-each select=".//person">
>        <xsl:value-of select="normalize-space(.)"/><xsl:text>&#09;</xsl:text>
>        <xsl:value-of select="normalize-space(../affiliation)"/><xsl:text>&#09;</xsl:text>
>        <xsl:value-of select="normalize-space(../paper)"/><xsl:text>&#10;</xsl:text>
>      </xsl:for-each>
>    </xsl:for-each>
>
> Can you offer any suggestions? What sort of XPath expression should I be using to find all of the affiliation elements between person elements? Something with following-sibling?

-- 

Ashley M. Clark
XML Applications Developer
Digital Scholarship Group
Northeastern University Libraries
[log in to unmask] <mailto:[log in to unmask]>
(617) 373-5983