问题描述
|
我需要确保XML文档始终包含以下节点:
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
典型的XML输入文件如下所示(组节点应始终位于类别和摘要之前):
<story>
<group>
<section>section-content</section>
<head>head-content</head>
<body>body-content</body>
<extra>extra-content</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
但是不能保证group元素或其任何子元素将存在。
因此,给出一个XML文档:
<story>
<category>some text</category>
<summary>some text</summary>
</story>
输出应如下所示:
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
XSLT不应修改现有内容,例如。
<story>
<group>
<section>existing text</section>
<extra>existing text</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
应转换为:
<story>
<group>
<section>existing text</section>
<head>0002</head>
<body>0003</body>
<extra>existing text</extra>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
以Tomalak的答案为基础,我想到了这一点:
<xsl:stylesheet version=\"1.0\"
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"
xmlns:subst=\"http://tempuri.com/mysubst\"
>
<xsl:strip-space elements=\"*\"/>
<xsl:output method=\"xml\" encoding=\"utf-8\" indent=\"yes\"/>
<!-- These defaults (elements and contents) can be modified at any time -->
<subst:defaults>
<subst:element name=\"group\">
<subst:section>0001</subst:section>
<subst:head>0002</subst:head>
<subst:body>0002</subst:body>
</subst:element>
</subst:defaults>
<!-- this makes the above available as a variable -->
<xsl:variable name=\"defaults\" select=\"document(\'\')/*/subst:defaults\" />
<xsl:template match=\"@*|node()\">
<xsl:copy>
<xsl:apply-templates select=\"@*|node()\"/>
</xsl:copy>
</xsl:template>
<xsl:template match=\"story[not(group)]\">
<xsl:copy>
<xsl:apply-templates select=\"@*\"/>
<xsl:element name=\"group\"></xsl:element>
<xsl:apply-templates select=\"node()\"/>
</xsl:copy>
</xsl:template>
<xsl:template match=\"group\">
<xsl:copy>
<xsl:copy-of select=\"@*|*\"/>
<xsl:call-template name=\"create-defaults\" />
</xsl:copy>
</xsl:template>
<!-- Insert the defaults-->
<xsl:template name=\"create-defaults\">
<xsl:variable name=\"this\" select=\".\" />
<xsl:for-each select=\"$defaults/subst:element[@name = name($this)]/*\">
<xsl:if test=\"not($this/*[name() = local-name(current())])\">
<xsl:apply-templates select=\".\" />
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- Remove the namespaces -->
<xsl:template match=\"*\">
<xsl:element name=\"{local-name()}\">
<xsl:apply-templates select=\"@*|node()\"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
如果group元素已经存在,这似乎可行,但是如果group元素不存在,我想不通如何使它工作。
解决方法
此XSLT 1.0转换
<xsl:stylesheet version=\"1.0\"
xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"
xmlns:subst=\"http://tempuri.com/mysubst\"
exclude-result-prefixes=\"subst\"
>
<xsl:strip-space elements=\"*\"/>
<xsl:output method=\"xml\" encoding=\"utf-8\" indent=\"yes\"/>
<subst:defaults>
<subst:element name=\"group\">
<subst:element name=\"section\">0001</subst:element>
<subst:element name=\"head\">0002</subst:element>
<subst:element name=\"body\">0003</subst:element>
</subst:element>
</subst:defaults>
<xsl:variable name=\"subst\" select=\"document(\'\')/*/subst:defaults/subst:element\" />
<xsl:template match=\"@* | node()\">
<xsl:copy>
<xsl:apply-templates select=\"@* | node()\"/>
</xsl:copy>
</xsl:template>
<!-- subst:element outputs a new element with the given @name -->
<xsl:template match=\"subst:element\">
<xsl:element name=\"{@name}\">
<!-- this would also copy any additional \"default\" attribute! -->
<xsl:apply-templates select=\"@*[not(name() = \'name\')] | node()\"/>
</xsl:element>
</xsl:template>
<!-- only stories without any group get \"special\" treatment -->
<xsl:template match=\"story[not(group)]\">
<xsl:copy>
<xsl:apply-templates select=\"@*\" />
<xsl:apply-templates select=\"$subst[@name = \'group\']\" mode=\"copy-or-default\" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<!-- a group first outputs all \"default\" children,then any extra children -->
<xsl:template match=\"group\">
<xsl:variable name=\"defaults\" select=\"$subst[@name = \'group\']/*\" />
<xsl:copy>
<xsl:apply-templates select=\"@*\" />
<xsl:apply-templates select=\"$defaults\" mode=\"copy-or-default\">
<xsl:with-param name=\"parent\" select=\".\" />
</xsl:apply-templates>
<!-- any elements that don\'t have a default AND any non-element children -->
<xsl:apply-templates select=\"
*[not(name() = $defaults/@name)] | node()[not(self::*)]
\" />
</xsl:copy>
</xsl:template>
<!-- in \"copy-or-default\" mode,this checks the context parent
and either copies the element from there or uses the default -->
<xsl:template match=\"subst:element\" mode=\"copy-or-default\">
<xsl:param name=\"parent\" />
<xsl:choose>
<xsl:when test=\"$parent and $parent/*[name() = current()/@name]\">
<xsl:apply-templates select=\"$parent/*[name() = current()/@name]\" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select=\".\" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
应用于此输入时
<x>
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
<story>
<group>
<body>0004</body>
<!-- a comment! -->
<foo>blah</foo>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
<story>
<category>some text</category>
<summary>some text</summary>
</story>
</x>
产生:
<x>
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0004</body>
<!-- a comment! -->
<foo>blah</foo>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
<story>
<group>
<section>0001</section>
<head>0002</head>
<body>0003</body>
</group>
<category>some text</category>
<summary>some text</summary>
</story>
</x>