XSLT 如何基于特定子节点的内容向父节点添加属性



我是XSLT的新手。我需要通过pdf2txt.py聚合xml格式的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>

,我想把转换成(注意添加的page属性):

<?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中的示例:根据包含特定字符串的子属性值向父属性添加属性,我尝试使用以下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 -

这是一个错误,我还没有弄清楚如何处理。

主要问题是在匹配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')]"/>

最新更新