XML XSL stripping/reducing 所需元素的结构

XML XSL stripping/reducing a structure to needed elements

我又来了。我有一个新问题。

我喜欢 strip/reduce 只需要元素的 xml 结构。

为了解释这个问题,我构建了一个简单化的随机结构。

<ROOT>
    <DATA>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4711</VALUE>
        </ALLOC>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4712</VALUE>
        </ALLOC>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4713</VALUE>
        </ALLOC>
    </DATA>
    <SOURCE>
        <CONNECTION>
            <TYPE>SQL</TYPE>
            <VALUE>jdbc</VALUE>
            <CSTRING>jdbc string</CSTRING>
        </CONNECTION>
        <CONNECTION>
            <TYPE>CSV</TYPE>
            <VALUE>CSV</VALUE>
            <CSTRING></CSTRING>
        </CONNECTION>
    </SOURCE>
</ROOT>

所需元素例如:

/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]
/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]

所需的元素语句来自 java xmlassert.equal > xmldiff

现在我必须剥离 xml 结构以获取所需的元素,但保留元素的 xml 结构 (xpath)。

期望的输出是:

<ROOT>
    <DATA>
        <ALLOC>
            <VALUE>4712</VALUE>
        </ALLOC>
    </DATA>
    <SOURCE>
        <CONNECTION>
            <CSTRING>jdbc string</CSTRING>
        </CONNECTION>       
    </SOURCE>
</ROOT>

真正的结构是巨大的(如果你要打印它至少 6x A4 页),复杂并且有多个层次。请求的元素也是动态的。

我花了最后几个小时阅读了很多 fourms 中的线程,尝试了大量不同的 xslt 并阅读了更多线程。

我该怎么做?

在此先感谢您。

据我了解,您需要一个 XSLT,它将采用一系列 XPath 表达式,然后将输入 XML 减少为仅匹配 XPath 表达式及其祖先的那些元素。

您没有说明要使用哪个 XSLT 版本,或者您将使用哪个处理器,因此很难为您提供好的示例代码。相反,我将概述一些我认为您可以选择的选项:

  1. 生成一些像@michael.hor257k 的回答中那样的 XSLT(使用 XSLT?),使用 XPath 语句作为输入,以及 运行 that XSLT根据您的输入。这可能会很好地扩展,但需要大量的初始投资,并且比其他选项更复杂。
  2. 使用 xsl:key 和 key() 函数来定义要保留的元素。记住你想保留所有祖先。
  3. 使用函数、参数或调用模板来评估您正在检查的元素是否具有与您的任何 XPath 列表或其祖先相对应的 XPath 地址。您可能可以使用参数来节省大量处理时间。
  4. 涉及 saxon:parse() 或某些其他自定义函数的内容可能在您的环境中可用,也可能不可用。

TMTOWTDI。无论选择哪种方法,您都可能希望使用 XSLT 2,这样您就可以将 XPath 地址列表视为字符串序列;您可能还想扩展该序列以包括所有祖先 - "/ROOT[1]/DATA[1]/ALLOC[2]" 变为 ("/ROOT[1]/DATA[1]/ALLOC[2]", "/ROOT[1]/DATA[1]", "/ROOT[1]") - 以简化事情。

见鬼,我很无聊,给你做了一个 XSLT 2 实现:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:local="http://example.com/local"
  exclude-result-prefixes="xs local"
  version="2.0">

  <xsl:output indent="yes"/>

  <xsl:param name="XPath" select="('/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]', '/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]')" as="xs:string+"/>

  <xsl:variable name="XPe" as="xs:string+">
    <xsl:for-each select="$XPath">
      <xsl:sequence select="local:ancestorize(.)"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="XPd" as="xs:string+">
    <xsl:sequence select="distinct-values($XPe)"/>
  </xsl:variable>

  <xsl:template match="@*|*">
    <xsl:param name="parentXP" as="xs:string?"/>
    <xsl:variable name="selfXP" as="xs:string">
      <xsl:variable name="seq">
        <xsl:value-of select="$parentXP"/>
        <xsl:text>/</xsl:text>
        <xsl:if test=". is ../@*">
          <!-- this test is a bit untested: you may need a better test to tell if you're looking at an attribute; I leave it as an exercise for you! -->
          <xsl:text>@</xsl:text>
        </xsl:if>
        <!-- I'm assuming no namespaces: if you have namespaces you'll have to build in your prefix here -->
        <xsl:value-of select="local-name()"/>
        <xsl:text>[</xsl:text>
        <xsl:value-of select="1 + count(preceding-sibling::*[name() eq current()/name()])"/>
        <xsl:text>]</xsl:text>
      </xsl:variable>
      <xsl:value-of select="xs:string($seq)"/>
    </xsl:variable>
    <xsl:if test="$selfXP = $XPd">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()">
          <xsl:with-param name="parentXP" select="$selfXP"/>
        </xsl:apply-templates>
      </xsl:copy>
    </xsl:if>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:param name="parentXP"/>
    <xsl:if test="$parentXP = $XPd and normalize-space(.) ne ''">
      <xsl:copy/>
    </xsl:if>
  </xsl:template>

  <xsl:function name="local:ancestorize" as="xs:string+">
    <xsl:param name="XPath" as="xs:string"/>
    <xsl:sequence select="$XPath"/>
    <xsl:if test="count(tokenize($XPath, '/')) gt 1">
      <xsl:sequence select="local:ancestorize(string-join((tokenize($XPath, '/'))[not(position() eq last())], '/'))"/>
    </xsl:if>
  </xsl:function>

</xsl:stylesheet>

How can i do that?

这是一个简短的 XSLT 1.0 通用 解决方案:

<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:param name="pExpressions">
      <e>/ROOT[1]/DATA[1]/ALLOC[2]/VALUE[1]</e>
      <e>/ROOT[1]/SOURCE[1]/CONNECTION[1]/CSTRING[1]</e>
    </xsl:param>        
    <xsl:variable name="vExpressions" 
                  select="document('')/*/xsl:param[@name='pExpressions']/*"/>

    <xsl:template match="*">
      <xsl:variable name="vPath">
        <xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
      </xsl:variable>

      <xsl:copy-of select="self::*[$vExpressions[.=$vPath]]"/>

      <xsl:apply-templates select=
      "self::*[$vExpressions[not(.=$vPath) and starts-with(.,$vPath)]]" mode="process"/>
    </xsl:template>

    <xsl:template match="*" mode="path">
        <xsl:value-of select="concat('/',name())"/>
        <xsl:variable name="vnumPrecSiblings" select=
         "count(preceding-sibling::*[name()=name(current())])"/>
        <xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/>
     </xsl:template>

     <xsl:template match="*" mode="process">
       <xsl:copy>
         <xsl:apply-templates select="*"/>
       </xsl:copy>
     </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时:

<ROOT>
    <DATA>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4711</VALUE>
        </ALLOC>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4712</VALUE>
        </ALLOC>
        <ALLOC>
            <TYPE>Test</TYPE>
            <NAME>something text</NAME>
            <VALUE>4713</VALUE>
        </ALLOC>
    </DATA>
    <SOURCE>
        <CONNECTION>
            <TYPE>SQL</TYPE>
            <VALUE>jdbc</VALUE>
            <CSTRING>jdbc string</CSTRING>
        </CONNECTION>
        <CONNECTION>
            <TYPE>CSV</TYPE>
            <VALUE>CSV</VALUE>
            <CSTRING></CSTRING>
        </CONNECTION>
    </SOURCE>
</ROOT>

产生了想要的、正确的结果:

<ROOT>
   <DATA>
      <ALLOC>
         <VALUE>4712</VALUE>
      </ALLOC>
   </DATA>
   <SOURCE>
      <CONNECTION>
         <CSTRING>jdbc string</CSTRING>
      </CONNECTION>
   </SOURCE>
</ROOT>

解释:

对于 XML 文档中的每个元素,都会生成其 XPath 表达式(以问题中指定的样式)。这个元素是:

  • 完全复制,如果其 XPath 表达式等于作为参数传递的 XPath 表达式之一。
  • 浅复制,如果其 XPath 表达式是一个或多个作为参数传递的 XPath 表达式的字符串前缀
  • 否则忽略(删除)

解的通用性:

输入的 XPath 表达式可以在调用转换时作为 <xsl:param> 传递,或者可以在 XML 文件中,其 URI 作为参数传递给转换。

:

I spent the last hours with reading threads in a lot of fourms, tries with a lot of amount of different xslt's and reading of more threads.

有关为每种类型的节点生成 XPath 表达式的更复杂和优雅的方法,请参阅 this answer