Produce Nested XML using muenchian method in XLST

2019-06-24 01:48发布

问题:

I am trying to produce a nested XML document from a flat XML document as below. After searching here and other resources available on the net it seems that that the muenchain method could be the solution however I am struggling applying it to my situation.

The XML I need to translate is of form :

<i>
  <item id="1" name="one" sub_id="10" sub_name="s1" detail_id="t1" detail_name="aaaa"/>
  <item id="1" name="one" sub_id="10" sub_name="s1" detail_id="t2" detail_name="bbb"/>
  <item id="1" name="one" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ccc"/>
  <item id="1" name="one" sub_id="20" sub_name="s2" detail_id="t2" detail_name="ddd"/>
  <item id="2" name="two" sub_id="10" sub_name="s1" detail_id="t1" detail_name="eee"/>
  <item id="2" name="two" sub_id="10" sub_name="s1" detail_id="t2" detail_name="fff"/>
  <item id="2" name="two" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ggg"/>
  <item id="2" name="two" sub_id="20" sub_name="s2" detail_id="t2" detail_name="hhh"/>
  <item id="3" name="three" />
  <item id="4" name="four" sub_id="10" sub_name="s1" detail_id="t1" detail_name="mmm"/>
  <item id="4" name="four" sub_id="10" sub_name="s1" detail_id="t2" detail_name="nnn"/>
  <item id="4" name="four" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ooo"/>
  <item id="4" name="four" sub_id="20" sub_name="s2" detail_id="t2" detail_name="ppp"/>
</i>

I would like to tranform this to XML in the following form:

  <i>
    <item id="1" name="one" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
        <detail detail_id="t1" detail_name="aaaa"/>
        <detail detail_id="t2" detail_name="bbb"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
        <detail detail_id="t1" detail_name="ccc"/>
        <detail detail_id="t2" detail_name="ddd"/>
      </sub_item>
    </item>
    <item id="2" name="two" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
        <detail detail_id="t1" detail_name="eee"/>
        <detail detail_id="t2" detail_name="fff"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
        <detail detail_id="t1" detail_name="ggg"/>
        <detail detail_id="t2" detail_name="hhh"/>
      </sub_item>
    </item>
    <item id="3" name="three" sub_items="false"/>
    <item id="4" name="four" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
        <detail detail_id="t1" detail_name="mmm"/>
        <detail detail_id="t2" detail_name="nnn"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
        <detail detail_id="t1" detail_name="ooo"/>
        <detail detail_id="t2" detail_name="ppp"/>
      </sub_item>
    </item>
  </i>

From my research I have the following XLST to perform the transform. I use the key method on the item id attribute. This doesn't group the data correctly repeating everything for the given item id at each level - which makes sense given the key. So my problem is how do I go about selecting the nodes required to output each nest level do I need to use another key statement?

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="indexes" match="i/item" use="@id"/>
  <xsl:template match="i">
    <i>
    <xsl:for-each select="item[count(. | key('indexes',@id)[1]) = 1]"  >
      <xsl:sort select="@id"/>
      <item>
        <xsl:attribute name="id">
          <xsl:value-of select="@id"/>
        </xsl:attribute>
        <xsl:attribute name="name">
          <xsl:value-of select="@name"/>
        </xsl:attribute>
        <xsl:attribute name="hasRows">
          <xsl:value-of select="@id"/>
        </xsl:attribute>
        <xsl:for-each select="key('indexes',@id)">
          <subitem>
            <xsl:attribute name ="sub_id">
              <xsl:value-of select="@sub_id"/>
            </xsl:attribute>
            <xsl:attribute name ="sub_name">
              <xsl:value-of select="@sub_name"/>
            </xsl:attribute>
            <xsl:for-each select="key('indexes',@id)">
              <segment>
                <xsl:attribute name ="detail_id">
                  <xsl:value-of select="@detail_id"/>
                </xsl:attribute>
                <xsl:attribute name ="detail_name">
                  <xsl:value-of select="@detail_name"/>
                </xsl:attribute>
              </segment>
            </xsl:for-each>
          </subitem>
        </xsl:for-each>
      </item>
    </xsl:for-each>
    </i>
  </xsl:template>
</xsl:stylesheet>

Also is it possible to populate the *sub_items* attribute with true when subitems/details for an item exist and false when they don't?

Lastly to improve XSLT understanding/skills can anyone recommend good learning resources?

回答1:

Notice this XSLT 1.0 transform with use of:

  • AVT (attribute value templates) to simplify the attributes definition)
  • one main key, used to group first level item
  • One second compound key to perform sublevel groupings
  • extensive use of xsl:apply-templates with modes, versus xsl:for-each

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="kSubs" match="item" use="concat(@id,'|',@sub_id)"/> 
    <xsl:key name="kItems" match="item" use="@id"/> 
    
    <xsl:template match="i">
        <i>
            <xsl:apply-templates select="item[
                generate-id()
                    = generate-id(key('kItems',@id)[1])]"/>
        </i>
    </xsl:template>
    
    <xsl:template match="item">
        <item id="{@id}" name="{@name}" sub_items="{boolean(@sub_id)}">
            <xsl:apply-templates select=".|following-sibling::item[
                (generate-id()
                        = generate-id(key('kSubs',concat(@id,'|',@sub_id))[1]))
                and
                (./@id 
                        = current()/@id)
                ]" mode="sub_id"/>
        </item>
    </xsl:template>
    
    <xsl:template match="item[@sub_id]" mode="sub_id">
        <sub_item sub_id="{@sub_id}" sub_name="{@sub_name}">
            <xsl:apply-templates select="key('kSubs',concat(@id,'|',@sub_id))" 
             mode="detail"/>
        </sub_item>
    </xsl:template>
    
    <xsl:template match="item" mode="detail">
        <detail detail_id="{@detail_id}" detail_name="{@detail_name}"/>
    </xsl:template>
    
    </xsl:stylesheet>
    

Applied on the input shown in your question, produces:

<i>
   <item id="1" name="one" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
         <detail detail_id="t1" detail_name="aaaa"/>
         <detail detail_id="t2" detail_name="bbb"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
         <detail detail_id="t1" detail_name="ccc"/>
         <detail detail_id="t2" detail_name="ddd"/>
      </sub_item>
   </item>
   <item id="2" name="two" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
         <detail detail_id="t1" detail_name="eee"/>
         <detail detail_id="t2" detail_name="fff"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
         <detail detail_id="t1" detail_name="ggg"/>
         <detail detail_id="t2" detail_name="hhh"/>
      </sub_item>
   </item>
   <item id="3" name="three" sub_items="false"/>
   <item id="4" name="four" sub_items="true">
      <sub_item sub_id="10" sub_name="s1">
         <detail detail_id="t1" detail_name="mmm"/>
         <detail detail_id="t2" detail_name="nnn"/>
      </sub_item>
      <sub_item sub_id="20" sub_name="s2">
         <detail detail_id="t1" detail_name="ooo"/>
         <detail detail_id="t2" detail_name="ppp"/>
      </sub_item>
   </item>
</i>


回答2:

The Muenchian method is tricky for nested grouping. The first level of grouping is easy to manage, but for the others you have to index elements with compound keys composed with previous ids.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes" />
    <xsl:key name="items" match="item" use="@id" />
    <xsl:key name="sub-items" match="item" use="concat(@id, '|', @sub_id)" />
    <xsl:template match="i">
      <i>
        <xsl:for-each select="item[generate-id() = generate-id(key('items', @id)[1])]">
          <xsl:copy>
           <xsl:copy-of select="@id|@name" />
           <xsl:choose>
             <xsl:when test="count(key('items', @id)) &gt; 1">
           <xsl:attribute name="sub_items">true</xsl:attribute>
           <xsl:for-each select="key('items', @id)[generate-id() = generate-id(key('sub-items', concat(@id, '|', @sub_id))[1])]">
             <sub_item>
            <xsl:copy-of select="@sub_id|@sub_name" />
            <xsl:for-each select="key('sub-items', concat(@id, '|', @sub_id))">
              <detail>
                <xsl:copy-of select="@detail_id|@detail_name" />
              </detail>
             </xsl:for-each>
             </sub_item>
           </xsl:for-each>
         </xsl:when>
          <xsl:otherwise>
            <xsl:attribute name="sub_items">false</xsl:attribute>
          </xsl:otherwise>
        </xsl:choose> 
          </xsl:copy>   
        </xsl:for-each> 
      </i>
    </xsl:template>
</xsl:stylesheet>


标签: xslt