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
[email protected] <mailto:[email protected]>
(617) 373-5983

Reply via email to