使用XSLT根据XSD转换XML

时间:2013-02-12 13:18:27

标签: xml xslt xsd

我想创建一个可以转换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技能所达到的目标。对于所有元素和所有属性,我一般都有问题。

2 个答案:

答案 0 :(得分:5)

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

还有第三个问题,即明确指出“XSD架构中定义(或未定义)的元素和属性”的含义。出于讨论的目的,我假设您的意思是元素和属性,它们可以绑定到模式中的元素或属性声明,在验证集(a)中以输入文档树中的任意点为根,以及(b)从顶级元素声明或属性声明。这个假设意味着几件事。 (a)本地元素声明仅匹配上下文中的内容 - 在您的示例中,keptElement1keptElement2仅在parent的子项时才会保留,否则将保留。 (b)无法保证输入中的元素实际上会与所讨论的元素声明绑定:如果其中一个祖先在本地无效,则在XSD 1.0和1.1中都会很快变得复杂。 (c)我们不允许从命名类型定义开始验证;我们可以,但听起来并不像你感兴趣的那样。(d)我们不允许从本地元素或属性声明开始验证。

有了这些假设,我们可以转向你的问题。

第一个任务要求您列出(a)模式中具有顶级声明的所有元素和属性,以及(b)可从中获取的所有元素和属性。对于顶级声明,我们需要记录的是对象的类型(元素或属性)和扩展名称。对于本地对象,我们需要一种对象和顶级元素声明的完整路径。对于您的示例模式,list(a)由

组成
  • element {} parent

(我正在使用在大括号中使用命名空间名称编写扩展名称的约定;对于James Clark,有些人称之为Clark符号。)

列表(b)由

组成
  • element {} parent / {} keepsElement1
  • element {} parent / {} keepsElement2
  • attribute {} parent / {} keepsAttribute1
  • attribute {} parent / {} keepsAttribute2

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

您的第二个任务是编写一个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中生成的列表,而不是将其写入具有显式匹配模式的重复模板中。根据您的背景,您可能会发现这种方法可以更容易或更难理解样式表正在做什么。]

答案 1 :(得分:4)

使用通用XSLT处理无法做到这一点,因为XSLT引擎不了解XSD。

这留下了几个选项:

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

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

  3. 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管理的那些元素和属性,但是你想出来了。

    埃利奥特