开发者

xml xslt transform

I’ve been trying to transform some simple XML into another simple XML using XSLT. I am new to XSLT, so if someone could give me an example I will expand on it.

I have arbitrary XML files: e.g

<element>
 <child_element>
  <grandchild_element>
     only one
  </grandchild_element>
 </child_element>
 <child_element>
  <grandchild_element>
     one
  </grandchild_element>
  <grandchild_element>
     two
  </grandchild_element>
 </child_element>
</element>

From which I want to produce:

<tree>
 <item class="element" id="1">
  <item class="child_element" id="11">
   <item class="grandchild_element" id="111" value="only one"/>
  </item>
  <item class="child_element" 开发者_开发百科id="12">
   <item class="grandchild_element" id="121" value="only one"/>
   <item class="grandchild_element" id="122" value="only one"/>
  </item>
 </item>
</tree>

Thanks!


One of the simplest/shortest solutions (only 3 templates no modes, no axes, no count(), only a single xsl:attribute) that is at the same time most generic (works with any element names):

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

 <xsl:template match="/">
  <tree>
   <xsl:apply-templates/>
  </tree>
 </xsl:template>

 <xsl:template match="*">
  <xsl:variable name="vNumber">
   <xsl:number count="*" level="multiple"/>
  </xsl:variable>

  <item class="{name()}"
        id="{translate($vNumber, '.', '')}">
   <xsl:apply-templates/>
  </item>
 </xsl:template>

 <xsl:template match="text()">
  <xsl:attribute name="value">
   <xsl:value-of select="normalize-space()"/>
  </xsl:attribute>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<element>
 <child_element>
  <grandchild_element>
     only one
  </grandchild_element>
 </child_element>
 <child_element>
  <grandchild_element>
     one
  </grandchild_element>
  <grandchild_element>
     two
  </grandchild_element>
 </child_element>
</element>

the wanted, correct result is produced:

<tree>
   <item class="element" id="1">
      <item class="child_element" id="11">
         <item class="grandchild_element" id="111" value="only one"/>
      </item>
      <item class="child_element" id="12">
         <item class="grandchild_element" id="121" value="one"/>
         <item class="grandchild_element" id="122" value="two"/>
      </item>
   </item>
</tree>


You'd write up a template for each element, like:

<xsl:template match="child_element">

and use the count of the preceding or preceding-sibling elements to get the "id" field:

<xsl:template match="child_element">
    <item>
        <xsl:attribute name="id">
            <xsl:value-of select="concat(count(preceding-sibling::element),count(preceding::child_element)+1)"/>
        </xsl:attribute>
    </item>
</xsl:template>

For the grandchild-id, you'll have to play around with .. and preceding-sibling. I'd however strongly advise you not to use your current counting scheme for id's anyway: Once you have more than 10 nodes at any level, collisions occur.


To get the id for each element, you will need look back at each of the ancestors, and for each level, count the number of preceding-siblings.

<xsl:attribute name="id">
    <xsl:apply-templates select="ancestor-or-self::*" mode="id"/>
</xsl:attribute>

<xsl:template match="*" mode="id">
   <xsl:value-of select="count(preceding-sibling::*) + 1"/>
</xsl:template>

To convert the element name into the class attribute is straight-forward, and done like so:

<xsl:attribute name="class">
   <xsl:value-of select="local-name()"/>
</xsl:attribute>

And to convert text into the value attribute is also fairly simple.

<xsl:template match="text()">
   <xsl:attribute name="value">
      <xsl:value-of select="normalize-space(.)"/>
   </xsl:attribute>
</xsl:template>

The normalize-space here is to remove the line-breaks shown in your sample XML.

Here is the full XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <!-- Match root element -->
   <xsl:template match="/">
      <tree>
         <xsl:apply-templates select="node()"/>
      </tree>
   </xsl:template>

   <!-- Match any element in the XML -->
   <xsl:template match="*">
      <item>
         <xsl:attribute name="id">
            <xsl:apply-templates select="ancestor-or-self::*" mode="id"/>
         </xsl:attribute>
         <xsl:attribute name="class">
            <xsl:value-of select="local-name()"/>
         </xsl:attribute>
         <xsl:apply-templates select="@*|node()"/>
      </item>
   </xsl:template>

   <!-- Used to match ancestors to work out the id -->
   <xsl:template match="*" mode="id">
      <xsl:value-of select="count(preceding-sibling::*) + 1"/>
   </xsl:template>

   <!-- Convert text into the value attribute -->
   <xsl:template match="text()">
      <xsl:attribute name="value">
         <xsl:value-of select="normalize-space(.)"/>
      </xsl:attribute>
   </xsl:template>

   <!-- Copy any existing attributes in the XML -->
   <xsl:template match="@*">
      <xsl:copy/>
   </xsl:template>
</xsl:stylesheet>

When applied on your sample XML, the following is output:

<tree>
   <item id="1" class="element">
      <item id="11" class="child_element">
         <item id="111" class="grandchild_element" value="only one"/>
      </item>
      <item id="12" class="child_element">
         <item id="121" class="grandchild_element" value="one"/>
         <item id="122" class="grandchild_element" value="two"/>
      </item>
   </item>
</tree>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜