2012-07-02 25 views
4

考慮我有下面的示例XML文件:如何將XML文件壓縮成一組xpath表達式?

<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'> 
    <article xmlns:ns1='http://predic8.com/material/1/'> 
     <name xmlns:ns1='http://predic8.com/material/1/'>foo</name> 
     <description xmlns:ns1='http://predic8.com/material/1/'>bar</description> 
     <price xmlns:ns1='http://predic8.com/common/1/'> 
     <amount xmlns:ns1='http://predic8.com/common/1/'>00.00</amount> 
     <currency xmlns:ns1='http://predic8.com/common/1/'>USD</currency> 
     </price> 
     <id xmlns:ns1='http://predic8.com/material/1/'>1</id> 
    </article> 
</ns1:create> 

什麼是最好的(最有效)的方式來壓平成一組XPath表達式這一點。 另請注意:我想忽略任何名稱空間和屬性信息。 (如果需要,這也可以作爲預處理步驟完成)。

所以我希望得到的輸出:

/create/article/name 
/create/article/description 
/create/article/price/amount 
/create/article/price/currency 
/create/article/id 

我在Java中實現。

編輯: PS,我可能還需要這的情況下工作,有在文本節點沒有數據,因此,例如,這下應該產生的輸出同上:

<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'> 
    <article xmlns:ns1='http://predic8.com/material/1/'> 
    <name /> 
    <description /> 
    <price xmlns:ns1='http://predic8.com/common/1/'> 
     <amount /> 
     <currency xmlns:ns1='http://predic8.com/common/1/'></currency> 
    </price> 
    <id xmlns:ns1='http://predic8.com/material/1/'></id> 
    </article> 
</ns1:create> 
+0

我試圖希望得到一些比迭代文檔等手動方法更可靠的方法。例如:我們還需要考慮多元素,它可以轉換爲/ field [0]和/字段[1]等。 – Larry

回答

2

你可以用XSLT很容易地做到這一點。看看你的例子,看起來你只想要包含文本的元素的XPath。如果情況並非如此,請告訴我,我可以更新XSLT。

我創建了一個新的輸入示例來顯示它如何處理具有相同名稱的兄弟姐妹。在這種情況下,<article>

XML輸入

<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'> 
    <article xmlns:ns1='http://predic8.com/material/1/'> 
     <name xmlns:ns1='http://predic8.com/material/1/'>foo</name> 
     <description xmlns:ns1='http://predic8.com/material/1/'>bar</description> 
     <price xmlns:ns1='http://predic8.com/common/1/'> 
      <amount xmlns:ns1='http://predic8.com/common/1/'>00.00</amount> 
      <currency xmlns:ns1='http://predic8.com/common/1/'>USD</currency> 
     </price> 
     <id xmlns:ns1='http://predic8.com/material/1/'>1</id> 
    </article> 
    <article xmlns:ns1='http://predic8.com/material/2/'> 
     <name xmlns:ns1='http://predic8.com/material/2/'>some name</name> 
     <description xmlns:ns1='http://predic8.com/material/2/'>some description</description> 
     <price xmlns:ns1='http://predic8.com/common/2/'> 
      <amount xmlns:ns1='http://predic8.com/common/2/'>00.01</amount> 
      <currency xmlns:ns1='http://predic8.com/common/2/'>USD</currency> 
     </price> 
     <id xmlns:ns1='http://predic8.com/material/2/'>2</id> 
    </article> 
</ns1:create> 

XSLT 1.0

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

    <xsl:template match="text()"/> 

    <xsl:template match="*[text()]"> 
     <xsl:call-template name="genPath"/> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:template> 

    <xsl:template name="genPath"> 
     <xsl:param name="prevPath"/> 
     <xsl:variable name="currPath" select="concat('/',local-name(),'[', 
     count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
     <xsl:for-each select="parent::*"> 
      <xsl:call-template name="genPath"> 
       <xsl:with-param name="prevPath" select="$currPath"/> 
      </xsl:call-template> 
     </xsl:for-each> 
     <xsl:if test="not(parent::*)"> 
      <xsl:value-of select="$currPath"/> 
      <xsl:text>&#xA;</xsl:text> 
     </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

輸出

/create[1]/article[1]/name[1] 
/create[1]/article[1]/description[1] 
/create[1]/article[1]/price[1]/amount[1] 
/create[1]/article[1]/price[1]/currency[1] 
/create[1]/article[1]/id[1] 
/create[1]/article[2]/name[1] 
/create[1]/article[2]/description[1] 
/create[1]/article[2]/price[1]/amount[1] 
/create[1]/article[2]/price[1]/currency[1] 
/create[1]/article[2]/id[1] 

UPDATE

對於XSLT把所有元素的工作,只需從match="*[text()]"刪除[text()]謂語。這將輸出每個元素的路徑。如果不希望包含其他元素的元素(如創建,文章和價格)的路徑輸出添加謂詞[not(*)]。下面是一個更新的例子:

新的XML輸入

<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'> 
    <article xmlns:ns1='http://predic8.com/material/1/'> 
     <name /> 
     <description /> 
     <price xmlns:ns1='http://predic8.com/common/1/'> 
      <amount /> 
      <currency xmlns:ns1='http://predic8.com/common/1/'></currency> 
     </price> 
     <id xmlns:ns1='http://predic8.com/material/1/'></id> 
    </article> 
    <article xmlns:ns1='http://predic8.com/material/2/'> 
     <name xmlns:ns1='http://predic8.com/material/2/'>some name</name> 
     <description xmlns:ns1='http://predic8.com/material/2/'>some description</description> 
     <price xmlns:ns1='http://predic8.com/common/2/'> 
      <amount xmlns:ns1='http://predic8.com/common/2/'>00.01</amount> 
      <currency xmlns:ns1='http://predic8.com/common/2/'>USD</currency> 
     </price> 
     <id xmlns:ns1='http://predic8.com/material/2/'>2</id> 
    </article> 
</ns1:create> 

XSLT 1。0

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

    <xsl:template match="text()"/> 

    <xsl:template match="*[not(*)]"> 
     <xsl:call-template name="genPath"/> 
     <xsl:apply-templates select="node()"/> 
    </xsl:template> 

    <xsl:template name="genPath"> 
     <xsl:param name="prevPath"/> 
     <xsl:variable name="currPath" select="concat('/',local-name(),'[', 
      count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
     <xsl:for-each select="parent::*"> 
      <xsl:call-template name="genPath"> 
       <xsl:with-param name="prevPath" select="$currPath"/> 
      </xsl:call-template> 
     </xsl:for-each> 
     <xsl:if test="not(parent::*)"> 
      <xsl:value-of select="$currPath"/> 
      <xsl:text>&#xA;</xsl:text> 
     </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

輸出

/create[1]/article[1]/name[1] 
/create[1]/article[1]/description[1] 
/create[1]/article[1]/price[1]/amount[1] 
/create[1]/article[1]/price[1]/currency[1] 
/create[1]/article[1]/id[1] 
/create[1]/article[2]/name[1] 
/create[1]/article[2]/description[1] 
/create[1]/article[2]/price[1]/amount[1] 
/create[1]/article[2]/price[1]/currency[1] 
/create[1]/article[2]/id[1] 

如果刪除[not(*)]斷言,這是輸出的樣子(路徑是每個元件輸出):

/create[1] 
/create[1]/article[1] 
/create[1]/article[1]/name[1] 
/create[1]/article[1]/description[1] 
/create[1]/article[1]/price[1] 
/create[1]/article[1]/price[1]/amount[1] 
/create[1]/article[1]/price[1]/currency[1] 
/create[1]/article[1]/id[1] 
/create[1]/article[2] 
/create[1]/article[2]/name[1] 
/create[1]/article[2]/description[1] 
/create[1]/article[2]/price[1] 
/create[1]/article[2]/price[1]/amount[1] 
/create[1]/article[2]/price[1]/currency[1] 
/create[1]/article[2]/id[1] 

以下是XSLT的另一個版本,速度大約快65%:

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

    <xsl:template match="text()"/> 

    <xsl:template match="*[not(*)]"> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:value-of select="concat('/',local-name(),'[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/> 
     </xsl:for-each> 
     <xsl:text>&#xA;</xsl:text> 
     <xsl:apply-templates select="node()"/> 
    </xsl:template> 

</xsl:stylesheet> 
+0

非常感謝您的解決方案,但是,您可以考慮在文本節點上沒有數據的情況下嗎?我編輯了我的問題,概述了這樣的例子。日Thnx。 – Larry

+0

@Larry - 請參閱我的更新。如果您有任何其他問題,請告訴我。 –

+0

我也做了一個小改動,以便任何屬性值不會被轉儲到輸出。 –

0

我的建議是使用SAX解析器。 wiki entry for SAXXerces: a SAX parser for java by Apache

在每個開始元素上,將元素的名稱添加到列表的末尾。在每個結束元素上,刪除最後一個列表條目。當你遇到內容,並且你想輸出你的xpath時,可以通過迭代列表來檢索它。