XSLT如何根据特定子节点的内容

时间:2015-08-10 21:27:23

标签: xml xslt

我是XSLT的新手。我需要汇总一些xml到pdf2txt.py中给出的PDF文件内容的信息。一些PDF很大(+ 100MB),甚至更大的是它们的xml输出。因此,通过几个xsltproc命令处理输出中的所有内容以便从不需要的内容中修剪xml代码似乎更有效(时间)。除此之外,还有一个带有文本内容的xml节点,我希望将其转换为其父节点的属性。

更具体地说,我有以下输入XML 文件结构:

<?xml version="1.0"?>
<pages>
  <page id="1">
    <text bbox="2831.881,1170.243,3124.184,1192.535">text11</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">sheet</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">P793</text>
  </page>
  <page id="2">
    <text bbox="2831.881,1170.243,3124.184,1192.535">text21</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">sheet:</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">S234</text>
  </page>
</pages>

我想将其转换为(请注意添加的网页属性):

<?xml version="1.0"?>
<pages>
  <page id="1" sheet="P793">
    <text bbox="2831.881,1170.243,3124.184,1192.535">text11</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">sheet</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">P793</text>
  </page>
  <page id="2" sheet="S234">
    <text bbox="2831.881,1170.243,3124.184,1192.535">text21</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">sheet</text>
    <text bbox="3149.641,1291.323,3318.336,1313.615">S234</text>
  </page>
</pages>

按照XSLT: Add Attribute to parent based on child attribute value containing a specific string中的示例,我尝试使用以下 XSL样式表

<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="*"/>
<xsl:preserve-space elements="text"/>

<xsl:template match="/">
 <xsl:apply-templates/>
</xsl:template>

<xsl:template match="page">
   <xsl:apply-templates select="@*"/>
  <xsl:variable name="sheet" select="//text[contains(text(),'sheet')]/following::text[string-length()>3]"/>
  <xsl:attribute name="sheet"><xsl:copy-of select="$sheet" /></xsl:attribute>
   <xsl:apply-templates select="node()"/>
</xsl:template>

<xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

但是,我没有输出。我尝试用文本节点上的for-each循环替换变量技巧以定义新的页面属性,但后来我得到错误我试图在添加子节点后添加属性,我不太了解理解。

是否可以“预见”这样的节点值并使用它向父节点添加属性?怎么样?为什么我的样式表没有提供任何输出?

我的最终目标是同时删除与表单节点及其标签对应的XML文本行,但这看起来比这个前瞻,属性副本更容易解决,我稍后会处理它。

谢谢!

编辑:我简化了我的输入案例和xsl样式表。实际上,我在这里提供的示例有一个输出,但它是一个错误输出:

runtime error: file test.xsl line 18 element copy
Attribute nodes must be added before any child nodes to an element.
runtime error: file test.xsl line 13 element attribute
xsl:attribute: Cannot add attributes to an element if children have been already added to the element.
no result for -

这是一个错误,我还没弄清楚如何处理。谷歌搜索没有帮助。

1 个答案:

答案 0 :(得分:2)

主要问题在于匹配page的模板,您要做的第一件事就是创建一个属性

<xsl:template match="page">
    <xsl:apply-templates select="@*"/>

但您实际上并未先复制page元素,因此它会尝试将属性和子text节点添加到之前创建的元素上;即pages。对于匹配的第二个page元素,它将尝试执行相同的操作,但是因为您无法向已添加子元素的元素添加属性而出现错误。

尝试使用此模板

<xsl:template match="page">
    <xsl:copy>
       <xsl:apply-templates select="@*"/>
        <xsl:variable name="sheet" select="text[contains(text(),'sheet')]/following-sibling::text[string-length()>3]"/>
        <xsl:attribute name="sheet"><xsl:value-of select="$sheet" /></xsl:attribute>
        <xsl:apply-templates select="node()"/>
    </xsl:copy>
</xsl:template>

请注意sheet表达式的更改。以前,您使用//text启动它,它将在文档中的任何位置找到第一个text元素。需要删除//,使其相对于当前page节点。

此外,请注意使用following-sibling而不是following,以便将其自身限制为仅限当前page元素下的兄弟节点。

最后,它是否只是您想要访问的后续兄弟姐妹?如果是这样,您可能需要在表达式

中添加额外条件
<xsl:variable name="sheet" select="text[contains(text(),'sheet')]/following-sibling::text[1][string-length()>3]"/>

或许可以颠倒逻辑,然后用这种方式写

<xsl:variable name="sheet" select="text[string-length()>3][contains(preceding-sibling::text[1],'sheet')]"/>