简体   繁体   中英

XSLT grouping based on XML element values

My Input XML File looks like

<test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
      <segment id="ORC"/>
      <segment id="OBR"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
   </test-message>

in My above input XML file element segment with id="ORC" is optional

I want to group My input XML file based on element segment with id="ORC" or element segment with id="OBR"

for above Input XML file I want to have below result when element segment with id="ORC" is present

<message-group>
    <test-message>
          <segment id="MSH"/>
          <segment id="SFT"/>
          <segment id="PID"/>
          <segment id="NTE"/>
          <segment id="NK1"/>
          <segment id="PV1"/>
</test-message>
<test-message>
          <segment id="ORC"/>
          <segment id="OBR"/>
          <segment id="NTE"/>
          <segment id="OBX"/>
          <segment id="NTE"/>
          <segment id="SPM"/>
       </test-message>
</message-group>

for above Input XML file I want to have below result when element segment with id="ORC" is not present

<message-group>
    <test-message>
          <segment id="MSH"/>
          <segment id="SFT"/>
          <segment id="PID"/>
          <segment id="NTE"/>
          <segment id="NK1"/>
          <segment id="PV1"/>
</test-message>
<test-message>
          <segment id="OBR"/>
          <segment id="NTE"/>
          <segment id="OBX"/>
          <segment id="NTE"/>
          <segment id="SPM"/>
       </test-message>
</message-group>

Can I have the XSLT (2.0) template or function to handle the above scenario

Note : I am making use of XSLT 2.0 and saxon parsers

This transformation :

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <message-group>
   <xsl:for-each-group select="*" group-starting-with=
   "segment[@id='ORC'][not(preceding-sibling::segment[1][@id='OBR'])]
  | segment[@id='OBR'][not(preceding-sibling::segment[1][@id='ORC'])]

   ">

      <test-message><xsl:sequence select="current-group()"/></test-message>
   </xsl:for-each-group>
  </message-group>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
      <segment id="ORC"/>
      <segment id="OBR"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
</test-message>

produces the wanted, correct result:

<message-group>
   <test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
   </test-message>
   <test-message>
      <segment id="ORC"/>
      <segment id="OBR"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
   </test-message>
</message-group>

When the same transformation (above) is applied on this XML document ('ORC' is not present):

<test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
      <segment id="OBR"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
</test-message>

again the wanted, correct result is produced :

<message-group>
   <test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
   </test-message>
   <test-message>
      <segment id="OBR"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
   </test-message>
</message-group>

Wwhen the same transformation is applied on this XML document ('OBR' is not present):

<test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
      <segment id="ORC"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
</test-message>

again the wanted, correct result is produced :

<message-group>
   <test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
   </test-message>
   <test-message>
      <segment id="ORC"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
   </test-message>
</message-group>

Finally, when both 'ORC' and 'OBR' are present, but 'OBR' precedes 'ORC' :

<test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
      <segment id="OBR"/>
      <segment id="ORC"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
</test-message>

Again the correct, wanted result is produced :

<message-group>
   <test-message>
      <segment id="MSH"/>
      <segment id="SFT"/>
      <segment id="PID"/>
      <segment id="NTE"/>
      <segment id="NK1"/>
      <segment id="PV1"/>
   </test-message>
   <test-message>
      <segment id="OBR"/>
      <segment id="ORC"/>
      <segment id="NTE"/>
      <segment id="OBX"/>
      <segment id="NTE"/>
      <segment id="SPM"/>
   </test-message>
</message-group>

There are two fairly simple ways that come quickly to mind.

(1) In your template for test-message , include two test-message output elements, each containing an apply-templates instruction. Give apply-templates a parameter to distinguish the first and second calls.

Say, something like (not tested):

<xsl:template match="test-message">
  <test-message>
    <xsl:apply-templates>
      <xsl:with-param name="flag" select="1"/>
    </xsl:apply-templates>
  </test-message>
  <test-message>
    <xsl:apply-templates>
      <xsl:with-param name="flag" select="2"/>
    </xsl:apply-templates>
  </test-message>
</xsl:template>

In the template for segment , write out a copy of the element if (a) $flag = 1 and neither this segment nor any preceding sibling segment has an id of OBR or ORC , or else if (b) $flag = 2 and this segment or some preceding sibling segment has such an id . Something like

<xsl:template match="segment">
  <xsl:param name="flag"/>
  <xsl:if test="(
                  $flag = 1 
                  and not(@id = ('ORC', 'OBR'))
                  and not(preceding-sibling::segment
                         [@id=('ORC','OBR')])
                ) or (
                  $flag = 2 
                  and ((@id = ('ORC', 'OBR'))
                       or preceding-sibling::segment
                         [@id=('ORC','OBR')]
                )">
    <xsl:copy-of select="."/>
 </xsl:if> 

(2) Make the test-message template as above, but add select="./segment[1]" to the two calls to xsl:apply-templates .

Then make the template for segment do its work and then recur on its immediately following sibling. To keep the logic simple, we distinguish several cases: first, $flag=1 and we haven't seen OBR or ORC yet: copy the current element and keep going.

<xsl:template match="segment">
  <xsl:param name="flag"/>
  <xsl:choose>
    <xsl:when test="$flag=1 and not(@id=('OBR', 'ORC'))">
      <xsl:copy-of select="."/>
      <xsl:if test="not(@id=('OBR','ORC'))">
         <xsl:apply-templates select="following-sibling::*[1]">
            <xsl:with-param name="flag" select="$flag"/>
         </xsl:apply-templates>
      </xsl:if>
    </xsl:when>

Second, $flag = 1 and we are now encountering OBR or ORC. Don't copy the current element and don't keep going; the first test-message element is now completed.

    <xsl:when test="$flag=1 and @id=('OBR', 'ORC')">
      <!--* do nothing, stop recursion *-->
    </xsl:when>

Third, $flag = 2 and we haven't yet encountered OBR or ORC. Keep going.

    <xsl:when test="$flag=2 and not(@id=('OBR', 'ORC'))">
      <!--* don't copy yet, keep looking for OBR/ORC *-->
      <xsl:apply-templates select="following-sibling::*[1]">
        <xsl:with-param name="flag" select="$flag"/>
      </xsl:apply-templates>
    </xsl:when>

Fourth, $flag = 2 and we are now encountering OBR or ORC. Copy the current element and keep going; switch the flag to a third value which means we are in the second test-message element and we have seen OBR or ORC:

    <xsl:when test="$flag=2 and @id=('OBR', 'ORC')">
      <xsl:copy-of select="."/>
      <xsl:apply-templates select="following-sibling::*[1]">
        <xsl:with-param name="flag" select="3"/>
      </xsl:apply-templates>
    </xsl:when>

Finally, if $flag = 3 then we just copy the current element and keep going.

    <xsl:when test="$flag=3">
      <xsl:copy-of select="."/>
      <xsl:apply-templates select="following-sibling::*[1]">
        <xsl:with-param name="flag" select="3"/>
      </xsl:apply-templates>
    </xsl:when>
  </xsl:choose>
</xsl:template>

If you're always splitting the sequence into exactly two groups, then I think I would do this:

<xsl:variable name="split" select="segment[@id=('ORC', 'OBR')][1]"/>
<test-message>
  <xsl:copy-of select="$split/preceding-sibling::*"/>  
</test-message>
<test-message>
  <xsl:copy-of select="$split, $split/following-sibling::*"/>  
</test-message>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM