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 版本,或者您将使用哪个处理器,因此很难为您提供好的示例代码。相反,我将概述一些我认为您可以选择的选项:
- 生成一些像@michael.hor257k 的回答中那样的 XSLT(使用 XSLT?),使用 XPath 语句作为输入,以及 运行 that XSLT根据您的输入。这可能会很好地扩展,但需要大量的初始投资,并且比其他选项更复杂。
- 使用 xsl:key 和 key() 函数来定义要保留的元素。记住你想保留所有祖先。
- 使用函数、参数或调用模板来评估您正在检查的元素是否具有与您的任何 XPath 列表或其祖先相对应的 XPath 地址。您可能可以使用参数来节省大量处理时间。
- 涉及 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。
我又来了。我有一个新问题。
我喜欢 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 版本,或者您将使用哪个处理器,因此很难为您提供好的示例代码。相反,我将概述一些我认为您可以选择的选项:
- 生成一些像@michael.hor257k 的回答中那样的 XSLT(使用 XSLT?),使用 XPath 语句作为输入,以及 运行 that XSLT根据您的输入。这可能会很好地扩展,但需要大量的初始投资,并且比其他选项更复杂。
- 使用 xsl:key 和 key() 函数来定义要保留的元素。记住你想保留所有祖先。
- 使用函数、参数或调用模板来评估您正在检查的元素是否具有与您的任何 XPath 列表或其祖先相对应的 XPath 地址。您可能可以使用参数来节省大量处理时间。
- 涉及 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。