Implementing Key Value Concept in XSLT

2019-02-06 13:04发布

问题:

I am working on XSLT, where I need to implement something as follows. My Source XML sample looks like this.

<?xml version="1.0" encoding="ISO-8859-1"?>
    <catalog>
        <cd>
            <title>A</title>  
            <title>B</title>
            <title>C</title>  
        </cd>
    </catalog>

Consider there is some key value pair list is there.

    Key         Value
    A           Algebra
    B           Biology
    C           Chemistry
    D           Data Analysis
    ---         ---

    ----        ---

I need to write an xslt such that for every occurance of key 'A', need to replace with appropriate value.

I also need to mention the list of Key value pairs in the same XSLT. Sample Output:

<Data>
    <Subject>Algebra</Subject>
    <Subject>Biology</Subject>
    <Subject>Chemistry</Subject>
 </Data>

Can any one help me out how to do it.

Thank you.

回答1:

I. Simple XSLT 1.0 Solution

This transformation:

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

 <my:codes>
   <code key="A" value="Algebra"/>
   <code key="B" value="Biology"/>
   <code key="C" value="Chemistry"/>
   <code key="D" value="Data Analysis"/>
 </my:codes>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match=
  "title/text()[. = document('')/*/my:codes/*/@key]">

  <xsl:value-of select=
   "document('')/*/my:codes/*[@key=current()]/@value"/>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<catalog>
    <cd>
        <title>A</title>
        <title>B</title>
        <title>C</title>
    </cd>
</catalog>

produces the wanted, correct result:

<catalog>
   <cd>
      <title>Algebra</title>
      <title>Biology</title>
      <title>Chemistry</title>
   </cd>
</catalog>

Explanation:

This is the standard way of including inline XML node as a global element (child element of xsl:stylesheet) that belongs to a (non-empty) namespace, different than the xsl namespace.


II. More efficient XSLT 1.0 solution, using keys:

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

     <my:codes>
       <code key="A" value="Algebra"/>
       <code key="B" value="Biology"/>
       <code key="C" value="Chemistry"/>
       <code key="D" value="Data Analysis"/>
     </my:codes>

     <xsl:key name="kCodeByName" match="code" use="@key"/>

     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>

     <xsl:template match=
      "title/text()[. = document('')/*/my:codes/*/@key]">

      <xsl:variable name="vCur" select="."/>

      <xsl:for-each select="document('')">
          <xsl:value-of select=
           "key('kCodeByName', $vCur)/@value"/>
      </xsl:for-each>
     </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the same XML document (above), the same correct, wanted result is produced:

<catalog>
   <cd>
      <title value="Algebra"/>
      <title value="Biology"/>
      <title value="Chemistry"/>
   </cd>
</catalog>

Explanation:

Accessing data via the key() function is typically sub-linear -- often O(1) and is extremely faster than linear search (which is important if the number of nodes to be searched is big).

Accessing a node of one document via an index (xsl:key) while processing a node of another document is possible if the document containing the node to be looked-up is the current document. To access nodes from the other document, its root (or node of interest need to be saved and referenced off a variable.)


III. XSLT 2.0 solution:

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

 <xsl:variable name="vCodes">
  <codes>
   <code key="A" value="Algebra"/>
   <code key="B" value="Biology"/>
   <code key="C" value="Chemistry"/>
   <code key="D" value="Data Analysis"/>
  </codes>
 </xsl:variable>

 <xsl:key name="kCodeByName" match="code" use="string(@key)"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match=
  "title/text()[key('kCodeByName', ., $vCodes)]">

  <xsl:sequence select=
   "key('kCodeByName', ., $vCodes)/@value"/>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the same XML document (above), the same correct, wanted result is produced:

<catalog>
   <cd>
      <title value="Algebra"/>
      <title value="Biology"/>
      <title value="Chemistry"/>
   </cd>
</catalog>

Explanation:

Almost the same as the efficient XSLT 1.0 solution, but:

  1. In XSLT 2.0 a template match pattern can contain a variable reference.

  2. In XSLT 2.0 there is no need for acrobatic tricks manipulating the current and the indexed documents -- the 3rd argument of the key() function is to specify the tree whose index to use.



标签: xslt xslt-1.0