優化

2013-05-06 80 views
2

輸入I有:優化

我與產生RSS以下形式饋送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. 如果缺少任何字段的信息,應該輸出一個空白元素。請注意,在我期望的輸出中,第二個<Event>元素中的空子元素爲<Field2>
  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>元素並將它們放在底部。我認爲這很容易彌補,但我所有的解決方案都顯得很愚蠢,因此不包括在內。 :)

Ready,Set,Go!

我將不勝感激您的幫助與更優雅的解決方案(或者,至少,解決方案,修復問題#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> 

該解決方案增加了一個額外的層:它允許我很好地更改字段名稱(通過每個<name>元素上的oldNamenewName屬性)。

感謝所有回答!

+0

是名總是從字面上'Field1'通過'Field5',或做他們需要從數據派生? – Borodin 2013-05-06 03:52:47

+1

我建議編寫並使用一個簡單的單線程擴展函數,將其字符串參數解析爲XML並返回生成的XmlDocument。這對C#來說是微不足道的。 – 2013-05-06 04:12:51

+0

@DimitreNovatchev:使用XSLT的' – Borodin 2013-05-06 05:13:29

回答

4

您可能對此解決方案感興趣。我已經使用了字段名稱Field1,但是Field5,並且因爲您有權訪問node-set,我已經將這些名稱添加到可方便修改的變量中。

的代碼處理所述description文本通過在它以兩個叮咬,以提取用於每個字段名稱的值。第一次通過選擇字段名稱後面的文本並在文本div之前創建$rough。這會給出類似於:&lt;/b&gt; Value 1&lt;/(或:</b> Value 1</)的內容。在&gt;之後和&lt;之前,Value 1之後的下一個改進取$rough中的所有內容。通過在xsl:value-of元素中使用normalize-space來從該最終值中修剪空格。

如果在目標字符串中找不到分隔符字符串,XSLT本身會通過從substring-before返回空字符串來處理缺少的Field2(或任何字段)。

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

謝謝,@Borodin。我能夠將您的想法作爲解決方案的基礎。感謝你的幫助。 – ABach 2013-05-06 13:29:53

0

她是基於非常好 「提取」 模板形式@Borodin遞歸解決方案。 由於小的優點,這也可以在沒有節點集()的情況下工作。

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

感謝您的解決方案,@ hr_117。不幸的是,我不能假定字段將通過模式命名(例如「字段」後跟數字增量)。我意識到這在原來的問題上是誤導性的;我很抱歉。 – ABach 2013-05-06 13:43:20

0

這裏有一些 「如果」 的解決方案。
如果描述的內容始終是「格式良好的XML」(因爲它是在你的例子),並
如果你能做到兩個分離的通行證(二XSLT處理器調用)。

通過1:生成描述內容(簡單明瞭)的臨時xml文件,內容爲disable-output-escaping="yes"

<?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>