2011-12-01 53 views
2

我最近遇到以下股票代碼XML飼料跑:優化XSLT的

<?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. 我的意圖是要善於使用身份變換。 但是,我不確定我的匹配修改是否是正確的 方式來完成我正在做的事情。

總體而言,我正在尋找關於如何改進此問題的專家建議。隨意告訴我,我試圖在不屬於它的設計模式中進行干預。 :)

非常感謝。

回答

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> 
+0

謝謝,@Dimitre。我喜歡你的想法。然而,兩個注意事項:(1)當我運行這個轉換(使用xsltproc)時,我得到一個xsl:元素:有效的名稱''不是有效的QName。有關{@Name}值的錯誤; (2)它看起來像一些領域,仍然有一個價值元素 - 我的意圖是擺脫這些,並有價值直接輸出。 – ABach

+0

@ABach:很好。我沒有注意到這一點,因爲Saxon 6.5.4(XSLT 1.0處理器)有一個bug並且轉換成功。現在我用Saxon 9.1.05(XSLT 2.0處理器)運行它。我會盡快更新我的答案。只需使用:'而不是' @ –

+0

@ABach:更新了我的答案。 –

3

我覺得這裏就是你要找的機制:

<?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元素的變換將不能很好地形成的結果。

+0

感謝您的回覆。問題:爲什麼多個'Instrument'元素會導致非格式良好的結果? – ABach

+0

當您匹配Instruments元素時,您只有應用模板。這意味着對於每個樂器的孩子來說,模板樂器都會被觸發,沒有任何插入元素。您可以添加一個測試(使用xsl:choose)到模板中:if count(*)> 1然後創建一個包含元素並應用模板,否則就是apply-templates。 –