1
votes

I am transforming from xml to xml between two different formats.

My question:
How do i select the a sequence of subfields with the code 'B' that is a following sibling to 'A' but not the not to any other field. If anyone can help me figure out how to "select" the correct nodes

Restrictions:
- XSLT 1.0
- I can't change the order of the content in datafields and due to the amount of different posts. Different rulesets are applied based on previous and following subfields.
- I can't use specific positions(there no guarantee that 2 subfields 'B' will follow 'A' or similar)

Example:
Each post contains datafields with any number of subfields.

XML is similar to this

<datafield tag="1">
      <subfield code="A"></subfield>
      <subfield code="B"></subfield>
      <subfield code="B"></subfield>
      <subfield code="C"></subfield>
      <subfield code="D"></subfield>
      <subfield code="B"></subfield>
      <subfield code="B"></subfield>
      <subfield code="G"></subfield>      
</datafield>

My xslt:

<xsl:template match="datafield[@tag=1]">    
<xsl:choose>    
    <datafield>        
      <xsl:attribute name="tag">new tag</xsl:attribute>                    
      <!-- Do not transfer subfield code='X' -->
      <xsl:for-each select="./subfield[@code != 'X']">           
        <subfield>
          <xsl:choose>
            <xsl:when test="./@code = 'A'">
              <xsl:attribute name="code">New code</xsl:attribute>     
                <!-- Find siblings with the code 'B'.PROBLEM all fields with the code B is selected-->
              <xsl:when test="./following-sibling::subfield[@code='B']"> Apply operation here </xsl:when>
              </xsl:choose>
            </xsl:when>
          </xsl:choose>                
        </subfield>
        </xsl:if>
      </xsl:for-each>
    </datafield>
  </xsl:when>
</xsl:choose>

I'm a pretty new stackoverflow user so if i missed any vital input please tell me and ill add it.

2
I believe you want to use a key here.michael.hor257k

2 Answers

0
votes

The most straightforward way is to do something based on counts - select all the following Bs whose number of preceding non-Bs is the same as the number of my preceding non-Bs plus one (i.e. me)

following-sibling::subfield[@code='B']
       [count(preceding-sibling::subfield[not(@code='B')])
      = count(current()/preceding-sibling::subfield[not(@code='B')]) + 1]

A more efficient way might be to define a key grouping Bs by their nearest preceding non-B

<xsl:key name="BbyNonB" match="subfield[@code='B']"
         use="generate-id(preceding-sibling::subfield[not(@code='B')][1])" />

and then you can extract all the Bs immediately following the current (non-B) subfield with

key('BbyNonB', generate-id())
0
votes

Ian's answer was really great. I am not contesting that at all. But personally, I found it easier to learn XSLT thinking in terms of recursive functions. If that's also the case for you, consider this solution. Again, as a beginner, thinking in terms of functions calls was easier for me. But Ian's answer is obviously much shorter and cooler.

Here's the XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="xml" indent="yes"/>                    
 <xsl:template match="subfield[@code='A']">
  <xsl:for-each select=".">
    <xsl:call-template name="getB">
      <xsl:with-param name="nodes" select="./following-sibling::subfield"/>
    </xsl:call-template>
  </xsl:for-each>
</xsl:template>

<xsl:template name="getB">
<xsl:param name="nodes"/>
  <xsl:choose>
     <xsl:when test="$nodes[1][@code='B']">
        <xsl:copy-of select="$nodes[1]"/>
        <xsl:call-template name="getB">
           <xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
        </xsl:call-template>
     </xsl:when>
     <xsl:otherwise/>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>

For this input:

 <datafield tag="1">
  <subfield code="A"></subfield>
  <subfield code="B"></subfield>
  <subfield code="B"></subfield>
  <subfield code="C"></subfield>
  <subfield code="D"></subfield>
  <subfield code="B"></subfield>
  <subfield code="B"></subfield>
  <subfield code="G"></subfield>      
 </datafield>

You get this output:

 <?xml version="1.0" encoding="utf-8"?>
  <subfield code="B"/>
  <subfield code="B"/>

But if this kind of solutions doesn't help, simply ignore it.