标记化XSLT的优化

时间:2013-05-06 00:50:28

标签: xml xslt

我的输入:

我正在使用以下列形式生成RSS Feed的Sharepoint列表:

<?xml version="1.0"?>
<rss>
  <channel>
    <!-- Irrelevant Fields -->
    <item>
      <title type="text">Title</title>
      <description type="html">
        &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field2:&lt;/b&gt; Value 2&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt;
      </description>
    </item>
    <item>
      <title type="text">Title</title>
      <description type="html">
        &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt;
      </description>
    </item>
    <item>
      <title type="text">Title</title>
      <description type="html">
        &lt;div&gt;&lt;b&gt;Field1:&lt;/b&gt; Value 1&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field2:&lt;/b&gt; Value 2&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field3:&lt;/b&gt; Value 3&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field4:&lt;/b&gt; Value 4&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Field5:&lt;/b&gt; Value 5&lt;/div&gt;
      </description>
    </item>
    <!-- More <item> elements -->
  </channel>
</rss>

请注意,<description>元素似乎定义了一组元素。此外,请注意,并非所有<description>元素都包含“Field2”的标记。

我需要的是什么:

我需要以下格式的XML:

<?xml version="1.0"?>
<Events>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2>Value 2</Field2>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2/>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2>Value 2</Field2>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
</Events>

规则(已更新):

  1. 这需要是一个XSLT 1.0解决方案。
  2. xxx:node-set是唯一可用的有效扩展功能;这包括用其他语言编写的扩展函数,例如C#或Javascript。
  3. 如果缺少任何字段的信息,则应输出空白元素。请在我想要的输出中注意第二个<Field2>元素中的空<Event>子项。
  4. 我们不能假设字段名称本身会遵循任何特定的模式;它们也可能是<PeanutButter><Jelly>
  5. 我到目前为止:

    <?xml version="1.0"?>
    <xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:exsl="http://exslt.org/common" 
      exclude-result-prefixes="exsl"
      version="1.0">
      <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="/*">
        <Events>
          <xsl:apply-templates select="*/item"/>
        </Events>
      </xsl:template>
    
      <xsl:template match="item[contains(description, 'Field2')]">
        <Event>
          <xsl:variable name="vElements">
            <xsl:call-template name="tokenize">
              <xsl:with-param name="text" select="description"/>
              <xsl:with-param name="delimiter" select="'&#10;'"/>
            </xsl:call-template>
          </xsl:variable>
    
          <Category>
            <xsl:value-of select="title"/>
          </Category>
          <xsl:apply-templates
            select="exsl:node-set($vElements)/*[normalize-space()]" mode="token"/>
        </Event>
      </xsl:template>
    
      <!-- NOTE HOW THIS TEMPLATE IS NEARLY IDENTICAL TO THE LAST ONE,
           MINUS THE BLANK <Field2>; THAT'S NOT VERY ELEGANT. -->
      <xsl:template match="item[not(contains(description, 'Field2'))]">
        <Event>
          <xsl:variable name="vElements">
            <xsl:call-template name="tokenize">
              <xsl:with-param name="text" select="description"/>
              <xsl:with-param name="delimiter" select="'&#10;'"/>
            </xsl:call-template>
          </xsl:variable>
    
          <Category>
            <xsl:value-of select="title"/>
          </Category>
          <xsl:apply-templates
            select="exsl:node-set($vElements)/*[normalize-space()]" mode="token"/>
          <Field2/>
        </Event>
      </xsl:template>
    
      <xsl:template match="*" mode="token">
        <xsl:element
          name="{substring-after(
                   substring-before(normalize-space(), ':'), 
                   '&lt;div&gt;&lt;b&gt;')}">
          <xsl:value-of
            select="substring-before(
                      substring-after(., ':&lt;/b&gt; '),
                      '&lt;/div&gt;')"/>
        </xsl:element>
      </xsl:template>
    
      <xsl:template name="tokenize">
        <xsl:param name="text"/>
        <xsl:param name="delimiter" select="' '"/>
        <xsl:choose>
          <xsl:when test="contains($text,$delimiter)">
            <xsl:element name="token">
              <xsl:value-of select="substring-before($text,$delimiter)"/>
            </xsl:element>
            <xsl:call-template name="tokenize">
              <xsl:with-param
                name="text"
                select="substring-after($text,$delimiter)"/>
              <xsl:with-param
                name="delimiter"
                select="$delimiter"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:when test="$text">
            <xsl:element name="token">
              <xsl:value-of select="$text"/>
            </xsl:element>
          </xsl:when>
        </xsl:choose>
      </xsl:template>
    </xsl:stylesheet>
    

    ......产生:

    <?xml version="1.0"?>
    <Events>
      <Event>
        <Category>Title</Category>
        <Field1>Value 1</Field1>
        <Field2>Value 2</Field2>
        <Field3>Value 3</Field3>
        <Field4>Value 4</Field4>
        <Field5>Value 5</Field5>
      </Event>
      <Event>
        <Category>Title</Category>
        <Field1>Value 1</Field1>
        <Field3>Value 3</Field3>
        <Field4>Value 4</Field4>
        <Field5>Value 5</Field5>
        <Field2/>
      </Event>
      <Event>
        <Category>Title</Category>
        <Field1>Value 1</Field1>
        <Field2>Value 2</Field2>
        <Field3>Value 3</Field3>
        <Field4>Value 4</Field4>
        <Field5>Value 5</Field5>
      </Event>
    </Events>
    

    我的解决方案有两个主要问题:

    1. 感觉笨重;有重复的代码,似乎有点笨拙。我想可能会发生一些优化?
    2. 请注意,它会以错误的顺序输出空<Field2>个元素,并将它们放在底部。我想,这有点容易补救,但我的所有解决方案看起来都很愚蠢,因此不包括在内。 :)
    3. 准备,设置,开始!

      我将非常感谢您提供更优雅的解决方案(或者至少是解决上述问题#2的解决方案)。谢谢!


      结论

      根据@Borodin在他自己的解决方案中所做的观察,我决定采用以下方法:

      <?xml version="1.0"?>
      <xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:exsl="http://exslt.org/common"
        exclude-result-prefixes="exsl"
        version="1.0">
        <xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
        <xsl:strip-space elements="*"/>
      
        <xsl:variable name="vFieldNames">
          <name oldName="Field1" newName="fieldA" />
          <name oldName="Field2" newName="fieldB" />
          <name oldName="Field3" newName="fieldC" />
          <name oldName="Field4" newName="fieldD" />
          <name oldName="Field5" newName="fieldE" />
        </xsl:variable>
      
        <xsl:template match="/">
          <events>
            <xsl:apply-templates select="*/*/item" />
          </events>
        </xsl:template>
      
        <xsl:template match="item">
          <event>
            <category>
              <xsl:value-of select="title" />
            </category>
            <xsl:apply-templates select="exsl:node-set($vFieldNames)/*">
              <xsl:with-param
                name="pDescriptionText"
                select="current()/description" />
            </xsl:apply-templates>
          </event>
        </xsl:template>
      
        <xsl:template match="name">
           <xsl:param name="pDescriptionText" />
           <xsl:variable
             name="vRough"
             select="substring-before(
                       substring-after($pDescriptionText, @oldName), 
                       'div')"/>
      
           <xsl:variable
             name="vValue"
             select="substring-before(
                       substring-after($vRough, '&gt;'),
                       '&lt;')"/>
           <xsl:element name="{@newName}">
             <xsl:value-of select="normalize-space($vValue)" />
           </xsl:element>
        </xsl:template>
      
      </xsl:stylesheet>
      

      此解决方案添加了一个额外的图层:它允许我很好地更改字段名称(通过每个oldName元素上的newName<name>属性。)

      感谢所有回答的人!

3 个答案:

答案 0 :(得分:4)

您可能对此解决方案感兴趣。我使用了文字字段名Field1虽然Field5,并且由于您可以访问node-set,我已将这些名称添加到可以方便修改的变量中。

代码处理description文本,通过对其中的两个字符串提取每个字段名称的值。第一遍通过选择字段名称后面和文本$rough之前的文本来创建div。这将提供类似:&lt;/b&gt; Value 1&lt;/(或:</b> Value 1</)的内容。下一个细化会在$rough&gt;&lt;之前将Value 1中的所有内容转换为normalize-space。通过在xsl:value-of元素中使用Field2,可以从最终值中修剪空格。

如果在目标字符串中找不到分隔符字符串,则XSLT本身通过从substring-before返回空字符串来处理缺少的<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext" version="1.0"> <xsl:strip-space elements="*"/> <xsl:output method="xml" indent="yes"/> <xsl:variable name="names"> <name>Field1</name> <name>Field2</name> <name>Field3</name> <name>Field4</name> <name>Field5</name> </xsl:variable> <xsl:template match="/"> <Events> <xsl:apply-templates select="rss/channel/item"/> </Events> </xsl:template> <xsl:template match="item"> <xsl:variable name="description" select="description"/> <Event> <Category> <xsl:value-of select="title"/> </Category> <xsl:for-each select="ext:node-set($names)/name"> <xsl:call-template name="extract"> <xsl:with-param name="text" select="$description"/> <xsl:with-param name="field-name" select="."/> </xsl:call-template> <xsl:variable name="field-name" select="."/> </xsl:for-each> </Event> </xsl:template> <xsl:template name="extract"> <xsl:param name="text"/> <xsl:param name="field-name"/> <xsl:variable name="rough" select="substring-before(substring-after($text, $field-name), 'div')"/> <xsl:variable name="value" select="substring-before(substring-after($rough, '&gt;'), '&lt;')"/> <xsl:element name="{$field-name}"> <xsl:value-of select="normalize-space($value)"/> </xsl:element> </xsl:template> </xsl:stylesheet> (或任何字段)。

<?xml version="1.0" encoding="utf-8"?>
<Events>
   <Event>
      <Category>Title</Category>
      <Field1>Value 1</Field1>
      <Field2>Value 2</Field2>
      <Field3>Value 3</Field3>
      <Field4>Value 4</Field4>
      <Field5>Value 5</Field5>
   </Event>
   <Event>
      <Category>Title</Category>
      <Field1>Value 1</Field1>
      <Field2/>
      <Field3>Value 3</Field3>
      <Field4>Value 4</Field4>
      <Field5>Value 5</Field5>
   </Event>
   <Event>
      <Category>Title</Category>
      <Field1>Value 1</Field1>
      <Field2>Value 2</Field2>
      <Field3>Value 3</Field3>
      <Field4>Value 4</Field4>
      <Field5>Value 5</Field5>
   </Event>
</Events>

<强>输出

{{1}}

答案 1 :(得分:0)

她是基于非常好的“提取”模板形式@Borodin的递归解决方案。 利用小优点,这也可以在没有node-set()的情况下工作。

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

    <xsl:template match="/*">
        <Events>
            <xsl:apply-templates select="//item"/>
        </Events>
    </xsl:template>

    <xsl:template match="item">
        <Event>
            <Category>
                <xsl:value-of select="title"/>
            </Category>
            <xsl:call-template name="Field" >
                <xsl:with-param name="fnr" select="'1'" />
                <xsl:with-param name="max_fnr" select="'5'" />
            </xsl:call-template>
        </Event>
    </xsl:template>

    <xsl:template name="Field">
        <xsl:param name="fnr" />
        <xsl:param name="max_fnr" />

        <xsl:call-template name="extract">
            <xsl:with-param name="text" select="."/>
            <xsl:with-param name="field-name" select="concat('Field',$fnr)"/>
        </xsl:call-template>

        <xsl:if test="$fnr &lt; $max_fnr">
            <xsl:call-template name="Field" >
                <xsl:with-param name="fnr" select="$fnr+1" />
                <xsl:with-param name="max_fnr" select="$max_fnr" />
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

    <xsl:template name="extract">
        <xsl:param name="text"/>
        <xsl:param name="field-name"/>
        <xsl:variable name="rough" select="substring-before(substring-after($text, $field-name), 'div')"/>
        <xsl:variable name="value" select="substring-before(substring-after($rough, '&gt;'), '&lt;')"/>
        <xsl:element name="{$field-name}">
            <xsl:value-of select="normalize-space($value)"/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet> 

将生成以下输出:

<Events>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2>Value 2</Field2>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2/>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
  <Event>
    <Category>Title</Category>
    <Field1>Value 1</Field1>
    <Field2>Value 2</Field2>
    <Field3>Value 3</Field3>
    <Field4>Value 4</Field4>
    <Field5>Value 5</Field5>
  </Event>
</Events>

答案 2 :(得分:0)

这是一个带有一些“ifs”的解决方案 如果描述内容始终是“格式良好的XML”(就像在您的示例中那样)和
如果你可以做两个单独的传递(两个xslt处理器调用)。

传递1:为描述内容生成一个disable-output-escaping="yes"的临时xml文件(简单明了)。

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

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

    <xsl:template match="description">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:value-of select="."  disable-output-escaping="yes"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

传递2:从临时xml文件生成预期输出(现在也很简单):

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

    <xsl:template match="/*">
        <Events>
            <xsl:apply-templates select="//item"/>
        </Events>
    </xsl:template>

    <xsl:template match="item">
        <Event>
            <Category>
                <xsl:value-of select="title"/>
            </Category>
            <xsl:call-template name="Field" >
                <xsl:with-param name="fnr" select="'1'" />
                <xsl:with-param name="max_fnr" select="'5'" />
            </xsl:call-template>
        </Event>
    </xsl:template>

    <xsl:template name="Field">
        <xsl:param name="fnr" />
        <xsl:param name="max_fnr" />
        <xsl:element name="Field{$fnr}" >
            <xsl:value-of select="description/div[b[text()=concat('Field', $fnr, ':')]]/text()"/>
        </xsl:element>
        <xsl:if test="$fnr &lt; $max_fnr">
            <xsl:call-template name="Field" >
                <xsl:with-param name="fnr" select="$fnr+1" />
                <xsl:with-param name="max_fnr" select="$max_fnr" />
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>