【问题标题】:Optimization of XSLT using Identity Transform使用身份转换优化 XSLT
【发布时间】:2011-12-01 05:55:16
【问题描述】:

我最近遇到了以下股票代码 XML 提要:

<?xml version="1.0" encoding="utf-8"?>
<BloombergOutput>
  <BloombergOutput CreatedUtc="2011-08-11T20:40:50.8851936Z">
    <Instruments>
      <Instrument Symbol="BLL">
        <Fields>
          <Field1 Name="LastPrice">
            <Value>35.550000</Value>
          </Field1>
          <Field2 Name="NetChangeOneDay">
            <Value>+1.550000</Value>
          </Field2>
          <Field3 Name="LastCloseDate">
            <Value>08/11/2011</Value>
          </Field3>
          <Field4 Name="LastClosePrice">
            <Value>35.550000</Value>
          </Field4>
          <Field5 Name="UpdateDate">
            <Value>08/11/2011</Value>
          </Field5>
          <Field6 Name="UpdateTime">
            <Value>16:15:03</Value>
          </Field6>
          <Field7 Name="LongName">
            <Value>Ball Corp</Value>
          </Field7>
          <Field8 Name="Name">
            <Value>BALL CORP</Value>
          </Field8>
          <Field9 Name="PriceSource">
            <Value>US</Value>
          </Field9>
          <Field10 Name="SymbolType">
            <Value>Common Stock</Value>
          </Field10>
        </Fields>
      </Instrument>
    </Instruments>
  </BloombergOutput>
</BloombergOutput>

我想使用 XSLT 将此提要转换为没有不必要的标记嵌套、具有更多描述性元素名称以及截断过长数字以使其小数点后只有两个数字的东西。这是我想出的 XSLT:

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

  <!-- Identity Transform, modified to begin at the Instruments element -->
  <xsl:template match="BloombergOutput/BloombergOutput/Instruments/@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- For each instrument, we grab the Symbol attribute and work on each child element -->
  <xsl:template match="Instrument">
    <Instrument>
      <Symbol><xsl:value-of select="@Symbol" /></Symbol>
      <xsl:apply-templates select="Fields/*" mode="fields" />
    </Instrument>
  </xsl:template>

  <!-- For each child field, we create a newly-named one and give it a value -->
  <xsl:template match="node()" mode="fields">

    <xsl:variable
      name="FieldName"
      select="@Name" />
    <xsl:variable
        name="Value"
        select="Value" />

    <xsl:element name="{$FieldName}">
      <xsl:choose>
        <!-- For these fields, we only want to preserve to spots after the decimal point -->
        <xsl:when test="$FieldName='LastPrice' or $FieldName='NetChangeOneDay' or $FieldName='LastClosePrice'">
          <xsl:value-of select="concat(substring-before($Value, '.'), '.', substring(substring-after($Value, '.'), 1, 2))" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$Value" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

...产生这个输出:

<?xml version="1.0"?>
<BloombergOutput>
  <BloombergOutput>2011-08-11T20:40:50.8851936Z
    <Instruments>
      <Instrument>
        <Symbol>BLL</Symbol>
        <LastPrice>35.55</LastPrice>
        <NetChangeOneDay>+1.55</NetChangeOneDay>
        <LastCloseDate>08/11/2011</LastCloseDate>
        <LastClosePrice>35.55</LastClosePrice>
        <UpdateDate>08/11/2011</UpdateDate>
        <UpdateTime>16:15:03</UpdateTime>
        <LongName>Ball Corp</LongName>
        <Name>BALL CORP</Name>
        <PriceSource>US</PriceSource>
        <SymbolType>Common Stock</SymbolType>
      </Instrument>
    </Instruments>
  </BloombergOutput>
</BloombergOutput>

虽然这几乎是我想要的,但也存在一些问题:

  1. 顶部的附加BloombergOutput元素被保留;此外,它的CreatedUtc 参数以一种相当奇怪的方式保留。我最初的意图是完全删除不必要的BloombergOutput 标签。
  2. 我已成功插入添加的Instrument 标签。 但是,Instruments 被保留,我没有明确表示。 我知道身份转换带来了它,因为我没有 告诉它走开,但如果我想要不同的开场元素怎么办 (比如StockQuote)?
  3. 我的目的是要善于使用身份转换。 但是,我不确定我的匹配修改是否正确 完成我正在做的事情的方法。

总体而言,我正在寻求您关于如何改进这一点的专家建议。随意告诉我,我正在尝试在不属于它的设计模式中进行干扰。 :)

非常感谢。

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    我认为这是您正在寻找的机制:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="no" indent="yes"/>
        <xsl:strip-space elements="*"/>
    
        <!-- Get rid of the BloombergOutput, Instruments elements-->
        <xsl:template match="BloombergOutput|Instruments">
            <xsl:apply-templates/>
        </xsl:template>
        <!-- Identity Transform -->
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    
        <!-- For each instrument, we grab the Symbol attribute and work on each child element -->
        <xsl:template match="Instrument">
            <Instrument>
                <Symbol><xsl:value-of select="@Symbol" /></Symbol>
                <xsl:apply-templates select="Fields/*" />
            </Instrument>
        </xsl:template>
    
        <!-- For each child field, we create a newly-named one and give it a value -->
        <xsl:template match="*[starts-with(name(),'Field')]">
    
            <xsl:variable
                name="FieldName"
                select="@Name" />
            <xsl:variable
                name="Value"
                select="Value" />
    
            <xsl:element name="{$FieldName}">
                <xsl:choose>
                    <!-- For these fields, we only want to preserve to spots after the decimal point -->
                    <xsl:when test="$FieldName='LastPrice' or $FieldName='NetChangeOneDay' or $FieldName='LastClosePrice'">
                        <xsl:value-of select="concat(substring-before($Value, '.'), '.', substring(substring-after($Value, '.'), 1, 2))" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$Value" />
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:element>
        </xsl:template>
    </xsl:stylesheet>
    

    请注意,您不必更改身份模板。这个模板的目标是说:当你不知道该做什么时,就留在已经存在的东西上。

    剩下的,在你的情况下你不需要模式,你只需要:

    1. 对于像 InstrumentsBloombergOutput 这样的元素:继续而不创建任何类型的结构
    2. 对以Field 开头的元素执行特定任务。

    结果是:

    <?xml version="1.0" encoding="utf-8"?>
    <Instrument>
       <Symbol>BLL</Symbol>
       <LastPrice>35.55</LastPrice>
       <NetChangeOneDay>+1.55</NetChangeOneDay>
       <LastCloseDate>08/11/2011</LastCloseDate>
       <LastClosePrice>35.55</LastClosePrice>
       <UpdateDate>08/11/2011</UpdateDate>
       <UpdateTime>16:15:03</UpdateTime>
       <LongName>Ball Corp</LongName>
       <Name>BALL CORP</Name>
       <PriceSource>US</PriceSource>
       <SymbolType>Common Stock</SymbolType>
    </Instrument>
    

    还有一点,如果你有两个Instrument 元素,那么转换的结果就不是很好了。

    【讨论】:

    • 感谢您的回复。问题:为什么多个Instrument 元素会导致格式不正确的结果?
    • 当你匹配 Instruments 元素时,你只有 apply-templates。这意味着对于每个 Instrument 子级,模板 Instrument 将被触发,没有任何 englobing 元素。您可以向模板添加一个测试(使用 xsl:choose):如果 count(*)>1 然后创建一个 englobing 元素并在其中应用模板,否则只需应用模板。
    【解决方案2】:

    好问题,+1。

    这里有一个更简单、更短、更简洁的解决方案(没有变量,没有xsl:choose/xsl:when/xsl:otherwise,没有substring()):

    <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="node()|@*">
             <xsl:copy>
               <xsl:apply-templates select="node()|@*"/>
             </xsl:copy>
         </xsl:template>
    
         <xsl:template match="BloombergOutput | Fields" priority="2">
          <xsl:apply-templates/>
         </xsl:template>
    
         <xsl:template match="*[starts-with(name(),'Field')]">
           <xsl:element name="{@Name}">
             <xsl:apply-templates/>
           </xsl:element>
         </xsl:template>
    
         <xsl:template match="Value">
          <xsl:apply-templates/>
         </xsl:template>
    
         <xsl:template match=
          "*[contains('|LastPrice|LastClosePrice|NetChangeOneDay|',
                      concat('|', @Name, '|')
                      )
            ]
              /Value
            ">
    
            <xsl:value-of select=
              "format-number(translate(.,'+', ''), '##0.00')"/>
         </xsl:template>
    </xsl:stylesheet>
    

    当此转换应用于提供的 XML 文档时

    <BloombergOutput>
      <BloombergOutput CreatedUtc="2011-08-11T20:40:50.8851936Z">
        <Instruments>
          <Instrument Symbol="BLL">
            <Fields>
              <Field1 Name="LastPrice">
                <Value>35.550000</Value>
              </Field1>
              <Field2 Name="NetChangeOneDay">
                <Value>+1.550000</Value>
              </Field2>
              <Field3 Name="LastCloseDate">
                <Value>08/11/2011</Value>
              </Field3>
              <Field4 Name="LastClosePrice">
                <Value>35.550000</Value>
              </Field4>
              <Field5 Name="UpdateDate">
                <Value>08/11/2011</Value>
              </Field5>
              <Field6 Name="UpdateTime">
                <Value>16:15:03</Value>
              </Field6>
              <Field7 Name="LongName">
                <Value>Ball Corp</Value>
              </Field7>
              <Field8 Name="Name">
                <Value>BALL CORP</Value>
              </Field8>
              <Field9 Name="PriceSource">
                <Value>US</Value>
              </Field9>
              <Field10 Name="SymbolType">
                <Value>Common Stock</Value>
              </Field10>
            </Fields>
          </Instrument>
        </Instruments>
      </BloombergOutput>
    </BloombergOutput>
    

    产生想要的正确结果

    <Instruments>
       <Instrument Symbol="BLL">
          <LastPrice>35.55</LastPrice>
          <NetChangeOneDay>1.55</NetChangeOneDay>
          <LastCloseDate>08/11/2011</LastCloseDate>
          <LastClosePrice>35.55</LastClosePrice>
          <UpdateDate>08/11/2011</UpdateDate>
          <UpdateTime>16:15:03</UpdateTime>
          <LongName>Ball Corp</LongName>
          <Name>BALL CORP</Name>
          <PriceSource>US</PriceSource>
          <SymbolType>Common Stock</SymbolType>
       </Instrument>
    </Instruments>
    

    【讨论】:

    • 谢谢,@Dimitre。我喜欢你的想法。但是,有两个注意事项:(1)当我运行这个转换(使用 xsltproc)时,我得到一个 xsl:element:有效名称''不是有效的 QName。关于 {@Name} 值的错误; (2) 看起来对于某些字段,仍然有一个值元素 - 我的目的是摆脱那些并直接输出值。
    • @Abach:很好的收获。我没有注意到这一点,因为 Saxon 6.5.4(XSLT 1.0 处理器)有一个错误并且转换成功。现在我用 Saxon 9.1.05(XSLT 2.0 处理器)运行它。我会尽快更新我的答案。只需使用:`
    • 谢谢 - 这解决了这个问题。最后一个呢(我想删除无关的&lt;value&gt; 元素)?我希望所有字段都遵循&lt;LastPrice&gt; 的格式(而不是&lt;SymbolType&gt;)。感谢您的帮助。
    • @ABach:我现在正在工作——请耐心等待——我会在 5-6 小时后调查此事。
    • @ABach:我的理解是否正确,您想要拥有:&lt;SymbolType&gt;Common Stock&lt;/SymbolType&gt;not&lt;SymbolType&gt;&lt;Value&gt;Common Stock&lt;/Value&gt;&lt;/SymbolType&gt;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多