1

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

我喜欢将 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 页面)、复杂并且有多层。请求的元素也是动态的。

在过去的几个小时里,我花了很多时间阅读线程,尝试使用大量不同的 xslt 并阅读更多线程。

我怎样才能做到这一点?

非常感谢你。

4

2 回答 2

1

我怎样才能做到这一点?

这是一个简短的 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 作为参数传递给转换。

注意

在过去的几个小时里,我花了很多时间阅读线程,尝试使用大量不同的 xslt 并阅读更多线程。

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

于 2016-11-05T04:45:28.807 回答
0

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

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

  1. 在@michael.hor257k 的答案中生成一些XSLT(使用XSLT?),使用XPath 语句作为输入,并在您的输入上运行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>
于 2016-11-03T22:39:14.530 回答