3

我想创建一个可以转换 XML 的 XSLT,以便在输出 XML(来自 XSLT)中排除 XSD 中未定义的所有元素和属性。

假设你有这个 XSD。

<xs:element name="parent">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="keptElement1" />
            <xs:element name="keptElement2" />
        </xs:sequence>

        <xs:attribute name="keptAttribute1" />
        <xs:attribute name="keptAttribute2" />
    </complexType>
</xsd:element>

你有这个输入 XML

<parent keptAttribute1="kept" 
    keptAttribute2="kept" 
    notKeptAttribute3="not kept" 
    notKeptAttribute4="not kept">

    <notKeptElement0>not kept</notKeptElement0>
    <keptElement1>kept</keptElement1>
    <keptElement2>kept</keptElement2>
    <notKeptElement3>not kept</notKeptElement3>
</parent>

然后我想让输出 Xml 看起来像这样。

<parent keptAttribute1="kept" 
    keptAttribute2="kept">

    <keptElement1>kept</keptElement1>
    <keptElement2>kept</keptElement2>
</parent>

我可以通过指定元素来做到这一点,但这大约是我的 xslt 技能所能达到的。我通常对所有元素和所有属性执行此操作时遇到问题。

4

2 回答 2

6

这里有两个挑战:(1) 识别模式中声明的元素名称和属性集,以及本地声明的适当上下文信息,以及 (2) 编写 XSLT 以保留与这些名称或名称匹配的元素和属性 - 并且 -上下文。

还有第三个问题,即明确指定“在 XSD 模式中定义(或未定义)的元素和属性”的含义。出于讨论的目的,我假设您的意思是元素和属性,这些元素和属性可以绑定到模式中的元素或属性声明,在验证集 (a) 植根于输入文档树中的任意点和 (b) 以 a 开头顶级元素声明或属性声明。这个假设意味着几件事。(a) 本地元素声明只会匹配上下文中的东西——在你的例子中,keptElement1并且keptElement2只有当它们是parent,否则不是。(b) 不能保证输入中的元素实际上会绑定到所讨论的元素声明:如果它们的祖先之一在本地无效,那么在 XSD 1.0 和 1.1 中,事情都会变得非常复杂。(c) 我们不允许从命名类型定义开始验证;我们可以,但听起来好像您不感兴趣。 (d) 我们不允许从本地元素或属性声明开始验证。

有了这些明确的假设,我们就可以转向您的问题。

第一个任务要求您列出 (a) 架构中具有顶级声明的所有元素和属性,以及 (b) 可从它们访问的所有元素和属性。对于顶级声明,我们只需要记录对象(元素或属性)的种类和扩展名称。对于本地对象,我们需要对象的种类和顶级元素声明的完整路径。对于您的示例架构,列表 (a) 包括

  • 元素{}父

(我使用的是用大括号中的命名空间名称编写扩展名称的约定;有些人称之为克拉克表示法,为詹姆斯克拉克。)

清单 (b) 包括

  • 元素 {}parent/{}keptElement1
  • 元素 {}parent/{}keptElement2
  • 属性 {}parent/{}keptAttribute1
  • 属性 {}parent/{}keptAttribute2

在更复杂的模式中,当您完成生成此列表的过程时,将会有一定数量的簿记。

您的第二个任务是编写一个 XSLT 样式表,将元素和属性保留在列表中并删除其余部分。(我在这里假设当你删除一个元素时,你也会删除它的所有内容;你的问题是关于元素,而不是标签。)

对于列表中的每个元素,使用列表中给出的上下文编写适当的身份转换:

<xsl:template match="parent">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

您可以为每个元素编写单独的模板,也可以将多个元素写入匹配模式:

<xsl:template match="parent
                    | parent/keptElement1 
                    | parent/keptElement2">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
</xsl:template>

对于列表中的每个属性,执行相同的操作:

<xsl:template match="parent/@keptAttribute1">
  <xsl:copy/>
</xsl:template>

覆盖元素和属性的默认模板,以抑制所有其他元素和属性:

<xsl:template match="*|@*"/>

[或者,正如 DrMacro 所建议的,您可以在 XSLT 中编写一个函数或命名模板来查阅您在任务 1 中生成的列表,而不是将其写入具有显式匹配模式的重复模板中。根据您的背景,您可能会发现这种方法更容易或更难理解样式表的作用。]

于 2013-02-12T16:53:29.330 回答
5

这不能通过通用 XSLT 处理来完成,因为 XSLT 引擎不了解 XSD。

这留下了几个选择:

  1. 使用 XSLT 直接处理 XSD 文档以确定哪些元素类型是实际声明的和未实际声明的,然后在转换中使用该信息。例如,如果一个元素位于不受 XSD 架构管理的命名空间中,那么您就知道它没有定义,或者如果元素的命名空间由 xs:any 元素指定,并且具有“宽松”验证,那么您就知道它是未声明。

  2. 使用 Saxon 的商业版本,它提供 XSD 解析和验证,并提供对 XSD 处理添加到元素的附加属性的访问。有关详细信息,请参阅撒克逊文档。

Apache xerces 项目包含一个 Java 中的 XSD 解析器,可用于处理复杂的 XSD 以执行您需要的任何操作,例如构建元素类型或名称空间的列表,这些元素类型或名称空间受或不受给定模式管理。因此,如果您的模式是相对静态的,那么预处理模式以构建一个简单的数据文件可能是最有效的,您的 XSLT 可以在处理文档时使用该文件。

您没有说是否可以使用 XSLT 2,但如果可以,一般的解决方案是定义一个函数来确定是否声明了给定的元素或属性,然后将该函数用作标准身份转换的一部分。使用 XSLT 1,您可以使用命名模板获得相同的效果。

例如:

<xsl:function name="local:isGoverned" as="xs:boolean">
   <xsl:param name="context" as="node()"/>
   <xsl:variable name="isGoverned" as="xs:boolean">
   <!-- Do whatever you do to determine governedness,
        whether this is to look at your collected data
        or use Saxon-provide info or whatever.
    -->
  </xsl:variable>
  <xsl:sequence select="$isGoverned"/>
</xsl:function>

然后在你的身份转换中:

<xsl:template match="*">
  <xsl:copy>
    <xsl:apply-templates 
      select="
         @*[local:isGoverned(.)], 
         (*[local:isGoverned(.)] | 
          node())"
    />
  </xsl:copy>
</xsl:copy>

<xsl:template match="@* | text() | comment() | processing-instruction()">
  <xsl:sequence select="."/>
</xsl:template>

这将产生仅通过由 XSD 管理的那些元素和属性的效果,但是您可以弄清楚。

艾略特

于 2013-02-12T15:54:44.740 回答