xml - 标记化 XSLT 的优化

标签 xml xslt

我的意见:

我正在使用一个 Sharepoint 列表,它以以下形式生成 RSS 源:

<?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> element 似乎定义了一组元素。此外,请注意并非所有 <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>第二个内的 child <Event>元素。
  4. 我们不能假设字段名称本身遵循任何特定模式;他们也可能是<PeanutButter> , <Jelly>等等

到目前为止我所拥有的:

<?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>元素的顺序不正确并将它们放置在底部。我想这很容易解决,但我所有的解决方案看起来都很愚蠢,因此不包括在内。 :)

准备,出发,出发!

非常感谢您提供更优雅的解决方案(或者至少修复上述问题 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> 属性)。

感谢所有回答的人!

最佳答案

您可能对此解决方案感兴趣。我使用了文字字段名称 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元素。

XSLT 本身会处理缺失的 Field2 (或任何字段)通过从 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>

关于xml - 标记化 XSLT 的优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16390657/

相关文章:

java - 如何在java中将新创建的DOM文档文件保存为.xml?

xml - 如何使用内胚包装器来修复这个练习?

java - jaxb可以生成这样的xml模式吗?

java - spring - xsl 转换 dtd 未找到错误

xslt - 编写 XPath 查询以根据属性和内容匹配元素

c++ - Boost C++ xml_oarchive 运行时检查失败 #2 - 变量周围的堆栈已损坏

xml - 从 azure 逻辑应用操作内的 Envolope 请求中提取 SOAP 正文

xml - xslt如何选择当前节点或先前的同级,但不能同时选择两者

xslt - 使用 XSLT 拆分对象集合并重新分布?

javascript - 在 xslt 中使用 javascript?