【问题标题】:Creating Valid XML Against Schema with XSLT使用 XSLT 针对 Schema 创建有效的 XML
【发布时间】:2010-07-14 01:40:28
【问题描述】:

我正在根据我的架构中定义的类型对元素列表进行排序。我知道 XSLT 可以针对给定的模式进行验证,但我想做的是在执行之前检查以确保我的操作(在本例中为副本)有效。

简化代码:

传入数据:

<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
    <sch:attr1>val1</sch:attr1>
    <sch:attr2>val2</sch:attr2>
    <sch:attr3>val3</sch:attr3>
    <sch:attr4>val4</sch:attr4>
</sch:foo>

所需的传出数据:

<?xml version="1.0"?>
<sch:fooOut xmlns:sch="http://www.whatever.com/schema">
    <sch:bar>
        <sch:attr1>val1</sch:attr1>
        <sch:attr2>val2</sch:attr2>
    </sch:bar>
    <sch:stuff>
        <sch:attr3>val3</sch:attr3>
        <sch:attr4>val4</sch:attr4>
    </sch:stuff>
</sch:fooOut>

架构文件中的某处:

<complexType name="fooOut">
    <sequence>
        <!-- ... -->
        <element name="bar">
            <complexType>
                <sequence>
                    <element name="attr1" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
                    <element name="attr2" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
                </sequence>
            </complexType>
        </element>
        <element name="stuff">
            <complexType>
                <sequence>
                    <element name="attr3" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
                    <element name="attr4" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
                </sequence>
            </complexType>
        </element>
    </sequence>
</complexType>

(我只是在学习如何使用 .xsd,所以用语言来表达:只有 attr1attr2 可以进入 bar,并且只有 attr3attr4 可以进入stuff)

基本上,在实际情况中,标签太多,无法手动将它们分开。我想知道是否有一种方法可以检查架构中的元素是否适合它们需要分类的任何类型。如果他们属于一个类别,他们应该只属于那个类别。

感谢所有帮助并感谢!

编辑

@Alejandro 的代码适用于上述基本伪代码,但我无法在我的文件中实现它,因为这些文件更复杂。出于这个原因,我添加了一个更复杂的示例:

传入数据

<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
    <sch:nesting>
        <sch:myGroup>
            <sch:mustHaveData>asdf</sch:mustHaveData>

            <sch:attr1>val1</sch:attr1>
            <sch:attr2>val2</sch:attr2>
            <sch:attr3>val3</sch:attr3>
            <sch:attr4>val4</sch:attr4>
        </sch:myGroup>
        <sch:myGroup>
            <sch:mustHaveData>asdf2</sch:mustHaveData>

            <sch:attr1>val5</sch:attr1>
            <sch:attr2>val6</sch:attr2>
            <sch:attr3>val7</sch:attr3>
            <sch:attr4>val8</sch:attr4>
        </sch:myGroup>
    </sch:nesting>
</sch:foo>

所需的传出数据:

<?xml version="1.0"?>
<sch:fooOut xmlns:sch="http://www.whatever.com/schema">
    <sch:anotherGroup>
        <sch:name>foobar</sch:name>
        <sch:bar>
            <sch:attr1>val1</sch:attr1>
            <sch:attr2>val2</sch:attr2>
        </sch:bar>
        <sch:stuff>
            <sch:attr3>val3</sch:attr3>
            <sch:attr4>val4</sch:attr4>
        </sch:stuff>
    </sch:anotherGroup>
    <sch:anotherGroup>
        <sch:name>foobar</sch:name>
        <sch:bar>
            <sch:attr1>val5</sch:attr1>
            <sch:attr2>val6</sch:attr2>
        </sch:bar>
        <sch:stuff>
            <sch:attr3>val7</sch:attr3>
            <sch:attr4>val8</sch:attr4>
        </sch:stuff>
    </sch:anotherGroup>
</sch:fooOut>

架构文件中的某处:(并且比上次更准确)

<complexType name="anotherGroup">
    <sequence>
        <element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
        <element name="bar" type="barListType" minOccurs="0" maxOccurs="1" />
        <element name="stuff" type="stuffListType" minOccurs="0" maxOccurs="1" />
    </sequence>
</complexType>

<!-- in another .xsd -->
<complexType name="barListType">
    <group ref="barGroup" maxOccurs="unbounded" />
</complexType>

<complexType name="stuffListType">
    <group ref="stuffGroup" maxOccurs="unbounded" />
</complexType>

<!-- in yet another .xsd -->
<group name="barGroup">
    <choice>
        <element name="attr1" type="blah1" minOccurs="0" maxOccurs="1" />
        <element name="attr2" type="blah2" minOccurs="0" maxOccurs="1" />
        <!-- etc -->
    </choice>
</group>

<group name ="stuffGroup">
    <choice>
        <element name="attr3" type="blah3" minOccurs="0" maxOccurs="1" />
        <element name="attr4" type="blah4" minOccurs="0" maxOccurs="1" />
        <!-- etc -->
    </choice>
</group>

最后,我的 xsl 文件

    <xsl:output method="xml" encoding="UTF-8" />

    <xsl:param name="schema-name" select="'myXsd.xsd'" />
    <xsl:template match="/">
        <xsl:apply-templates select="document($schema-name)/xs:complexType[@*]" />
        <xsl:apply-templates select="sch:nesting"/>
    </xsl:template>

    <xsl:template match="sch:nesting/xs:element[xs:complexType]">
        <xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
            <xsl:apply-templates />
        </xsl:element>
    </xsl:template>

    <xsl:template match="sch:nesting/xs:element[not(xs:complexType)]">
        <xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
            <xsl:value-of select="/*/sch:*[name()=current()/@name or
                                  substring-after(name(),':')=current()/@name]"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="sch:nesting">
        <xsl:element name="anotherGroup">
            <xsl:element name="name">
                <!-- Whatever -->
            </xsl:element>

            <xsl:apply-templates /> <!-- I want to drop the data here, but this is definitely wrong -->

        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

再次感谢您的帮助!

编辑 2

因此,我忘记对我的数据文件进行一项小改动。除了布局,其他一切都应该是一样的,它是这样嵌套的:

<?xml version="1.0"?>
<sch:foo xmlns:sch="http://www.whatever.com/schema">
    <sch:nesting>
        <sch:myGroup>
            <inner1>
                <sch:mustHaveData>asdf</sch:mustHaveData>

                <sch:attr1>val1</sch:attr1>
            </inner1>
            <inner2>
                <sch:attr2>val2</sch:attr2>
                <sch:attr3>val3</sch:attr3>
                <sch:attr4>val4</sch:attr4>
            </inner2>
        </sch:myGroup>
        <sch:myGroup>
            <inner1>
                <sch:mustHaveData>asdf2</sch:mustHaveData>

                <sch:attr1>val5</sch:attr1>
            </inner1>
            <inner2>
                <sch:attr2>val6</sch:attr2>
                <sch:attr3>val7</sch:attr3>
                <sch:attr4>val8</sch:attr4>
            </inner2>
        </sch:myGroup>
    </sch:nesting>
</sch:foo>

我在这里试图说明的是,组中有子类别,我需要匹配这些子类别中的所有内容。

Alejandro,为了让这件事物有所值(因为您提供了如此出色的帮助),您应该将您的回复放在一个新的答案中,当我尝试并让它发挥作用时,我会投票并接受那个答案。

再次感谢!你真是个救命恩人!

编辑 3

我想出了想要的结果。我改变了说的行

<xsl:copy-of select="*[local-name()=document($schema-name)/*/*

<xsl:copy-of select="*/*[local-name()=document($schema-name)/*/*

这给了我所需的额外部分。

【问题讨论】:

    标签: xml xslt xsd schema xml-validation


    【解决方案1】:

    架构(“schema.xs”):

    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
        <element name="fooOut">
            <complexType name="fooOut">
                <sequence>
                    <element name="bar">
                        <complexType>
                            <sequence>
                                <element name="attr1" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
                                <element name="attr2" type="sch:myBarType" minOccurs="0" maxOccurs="unbounded"/>
                            </sequence>
                        </complexType>
                    </element>
                    <element name="stuff">
                        <complexType>
                            <sequence>
                                <element name="attr3" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
                                <element name="attr4" type="sch:myStuffType" minOccurs="0" maxOccurs="unbounded"/>
                            </sequence>
                        </complexType>
                    </element>
                </sequence>
            </complexType>
        </element>
    </schema>
    

    输入:

    <sch:foo xmlns:sch="http://www.whatever.com/schema">   
        <sch:attr1>val1</sch:attr1>   
        <sch:attr2>val2</sch:attr2>   
        <sch:attr3>val3</sch:attr3>   
        <sch:attr4>val4</sch:attr4>   
    </sch:foo> 
    

    样式表:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:xs="http://www.w3.org/2001/XMLSchema"
     xmlns:sch="http://www.whatever.com/schema">
        <xsl:param name="schema-name" select="'schema.xs'"/>
        <xsl:variable name="input" select="/"/>
        <xsl:template match="/">
            <xsl:apply-templates select="document($schema-name)/node()"/>
        </xsl:template>
        <xsl:template match="xs:element[xs:complexType]">
            <xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
                <xsl:apply-templates/>
            </xsl:element>
        </xsl:template>
        <xsl:template match="xs:element[not(xs:complexType)]">
            <xsl:element name="{@name}" namespace="http://www.whatever.com/schema">
                <xsl:value-of select="$input/*/sch:*[name()=current()/@name or
                                            substring-after(name(),':')=current()/@name]"/>
            </xsl:element>
        </xsl:template>
    </xsl:stylesheet>
    

    结果:

    <?xml version="1.0" encoding="UTF-16"?>
    <fooOut xmlns="http://www.whatever.com/schema">
        <bar>
            <attr1>val1</attr1>
            <attr2>val2</attr2>
        </bar>
        <stuff>
            <attr3>val3</attr3>
            <attr4>val4</attr4>
        </stuff>
    </fooOut>
    

    注意:这是一个 XSLT 1.0 解决方案,但我认为使用 XSLT 2.0 可以做得更好。- 此外,如果架构更合适(元素定义和类型定义),则方法可以与键一起使用。另一种方法(输入驱动而不是模式驱动)尽管更复杂,但也可以完成,但我的时间不多了。

    编辑:现在,假设这些模式

    schemaA.xsd

    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
        <include schemaLocation="schemaB.xsd"/>
        <element name="fooOut" type="fooOut"/>
        <complexType name="fooOut">
            <element name="anotherGroup" type="anotherGroup" minOccurs="0" maxOccurs="unbounded"/>
        </complexType>
        <complexType name="anotherGroup">
            <sequence>
                <element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
                <element name="bar" type="barListType" minOccurs="0" maxOccurs="1" />
                <element name="stuff" type="stuffListType" minOccurs="0" maxOccurs="1" />
            </sequence>
        </complexType>
    </schema>
    

    schemaB.xsd

    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
        <include schemaLocation="schemaC.xsd"/>
        <complexType name="barListType">
            <group ref="barGroup" maxOccurs="unbounded" />
        </complexType>
        <complexType name="stuffListType">
            <group ref="stuffGroup" maxOccurs="unbounded" />
        </complexType>
    </schema>
    

    schemaC.xsd

    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.whatever.com/schema">
        <group name="barGroup">
            <choice>
                <element name="attr1" type="blah1" minOccurs="0" maxOccurs="1" />
                <element name="attr2" type="blah2" minOccurs="0" maxOccurs="1" />
                <!-- etc -->
            </choice>
        </group>
        <group name ="stuffGroup">
            <choice>
                <element name="attr3" type="blah3" minOccurs="0" maxOccurs="1" />
                <element name="attr4" type="blah4" minOccurs="0" maxOccurs="1" />
                <!-- etc -->
            </choice>
        </group>
    </schema>
    

    这个样式表:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xsl:output method="xml" encoding="UTF-8" />
        <xsl:param name="schema-name" select="'schemaA.xsd'" />
        <xsl:variable name="input" select="/*/*/*" />
        <xsl:variable name="root" select="document($schema-name)/*/xs:element[not(..//xs:element/@ref = @name)]" />
        <xsl:variable name="uri" select="$root/../@targetNamespace" />
        <xsl:template match="/" name="root">
            <xsl:param name="schema" select="$root/../*"/>
            <xsl:choose>
                <xsl:when test="$schema[self::xs:include]">
                    <xsl:call-template name="root">
                        <xsl:with-param name="schema" select="$schema[not(self::xs:include)]|document($schema[self::xs:include]/@schemaLocation)/*/*"/>
                    </xsl:call-template>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="$root">
                        <xsl:with-param name="schema" select="$schema"/>
                        <xsl:with-param name="input" select="$input"/>
                    </xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
        <xsl:template match="xs:element">
            <xsl:param name="schema"/>
            <xsl:param name="input"/>
            <xsl:variable name="complex" select="xs:complexType|
                                                 $schema[self::xs:complexType][@name=current()/@type]"/>
            <xsl:element name="{@name|@ref}" namespace="{$uri}">
                <xsl:choose>
                    <xsl:when test="$complex">
                        <xsl:apply-templates select="$complex">
                            <xsl:with-param name="schema" select="$schema"/>
                            <xsl:with-param name="input" select="$input"/>
                        </xsl:apply-templates>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$input[local-name()=current()/@name and
                                      namespace-uri()=$uri]"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:element>
        </xsl:template>
        <xsl:template match="xs:group[@ref]">
            <xsl:param name="schema"/>
            <xsl:param name="input"/>
            <xsl:apply-templates select="$schema[self::xs:group][@name=current()/@ref]">
                <xsl:with-param name="schema" select="$schema"/>
                <xsl:with-param name="input" select="$input"/>
            </xsl:apply-templates>
        </xsl:template>
        <xsl:template match="xs:element[@name='name']" priority="1">
            <xsl:element name="{@name}" namespace="{$uri}">foobar</xsl:element>
        </xsl:template>
        <xsl:template match="xs:element[@maxOccurs='unbounded']">
            <xsl:param name="schema"/>
            <xsl:param name="input"/>
            <xsl:variable name="current" select="."/>
            <xsl:for-each select="$input">
                <xsl:element name="{$current/@name}" namespace="{$uri}">
                    <xsl:apply-templates select="$schema[self::xs:complexType][@name=$current/@type]">
                        <xsl:with-param name="schema" select="$schema"/>
                        <xsl:with-param name="input" select="./*"/>
                    </xsl:apply-templates>
                </xsl:element>
            </xsl:for-each>
        </xsl:template>
        <xsl:template match="*">
            <xsl:param name="schema"/>
            <xsl:param name="input"/>
            <xsl:apply-templates>
                <xsl:with-param name="schema" select="$schema"/>
                <xsl:with-param name="input" select="$input"/>
            </xsl:apply-templates>
        </xsl:template>
    </xsl:stylesheet>
    

    结果:

    <fooOut xmlns="http://www.whatever.com/schema">
        <anotherGroup>
            <name>foobar</name>
            <bar>
                <attr1>val1</attr1>
                <attr2>val2</attr2>
            </bar>
            <stuff>
                <attr3>val3</attr3>
                <attr4>val4</attr4>
            </stuff>
        </anotherGroup>
        <anotherGroup>
            <name>foobar</name>
            <bar>
                <attr1>val5</attr1>
                <attr2>val6</attr2>
            </bar>
            <stuff>
                <attr3>val7</attr3>
                <attr4>val8</attr4>
            </stuff>
        </anotherGroup>
    </fooOut>
    

    注意:这可行,但您的第二个问题(或问题)表明没有通用案例样式表。为什么?因为使用 XSLT,您必须将输入(具有众所周知的模式)绑定到输出(也具有众所周知的模式)。所以这个特定的样式表可以完成这项工作:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sch="http://www.whatever.com/schema">
        <xsl:output method="xml" encoding="UTF-8" />
        <xsl:param name="schema-name" select="'schemaC.xsd'" />
        <xsl:template match="sch:foo">
            <sch:fooOut>
                <xsl:apply-templates/>
            </sch:fooOut>
        </xsl:template>
        <xsl:template match="sch:myGroup">
            <sch:anotherGroup>
                <sch:name>foobar</sch:name>
                <sch:bar>
                    <xsl:copy-of select="*[local-name()=document($schema-name)/*/*[@name='barGroup']//@name]" />
                </sch:bar>
                <sch:stuff>
                    <xsl:copy-of select="*[local-name()=document($schema-name)/*/*[@name='stuffGroup']//@name]" />
                </sch:stuff>
            </sch:anotherGroup>
        </xsl:template>
    </xsl:stylesheet>
    

    结果:

    <sch:fooOut xmlns:sch="http://www.whatever.com/schema">
        <sch:anotherGroup>
            <sch:name>foobar</sch:name>
            <sch:bar>
                <sch:attr1>val1</sch:attr1>
                <sch:attr2>val2</sch:attr2>
            </sch:bar>
            <sch:stuff>
                <sch:attr3>val3</sch:attr3>
                <sch:attr4>val4</sch:attr4>
            </sch:stuff>
        </sch:anotherGroup>
        <sch:anotherGroup>
            <sch:name>foobar</sch:name>
            <sch:bar>
                <sch:attr1>val5</sch:attr1>
                <sch:attr2>val6</sch:attr2>
            </sch:bar>
            <sch:stuff>
                <sch:attr3>val7</sch:attr3>
                <sch:attr4>val8</sch:attr4>
            </sch:stuff>
        </sch:anotherGroup>
    </sch:fooOut>
    

    【讨论】:

    • 该解决方案看起来确实很棘手,如果您稍后可以告诉我,我愿意等待。在样式表中,我会为schema-name 输入什么?我会尝试让这个解决方案发挥作用,但如果使用 XSLT 2.0 更容易,那将是首选。
    • @adam_0: schema-name 参数只是模式文档名称。这很棘手,但不是因为方法(我使用模式驱动转换并使用数据填充)。这很棘手,因为模式不是模块化的。如果它是模块化的,我应该需要一个入口点元素(根元素)并跨类型定义导航模式(使用键会更容易)
    • 您能否澄清一下您所说的“模块化”是什么意思?
    • 虽然我可以让您的示例运行,但在实际情况中实施它时遇到了麻烦。在我的例子中,我需要排序的 XML 元素在输入和输出文档中都是 4 或 5 层。修改您的示例以在这种情况下工作有多困难?除此之外,我认为我可能对自己来说太难了 - 我正在手动生成 XML 文档的其余部分。是否可以制作 XSLT 文档,以便自动创建我手动生成的图层?
    • 很抱歉添加了所有这些 cmets,但我注意到输出中的标签没有命名空间。我的输出肯定需要每个元素标签上的命名空间,否则它将无法正常工作。您将如何添加它?
    【解决方案2】:

    不确定您要在这里寻找什么。 XSLT 将从源文档复制到目标文档,但您告诉它。例如:

    <sch:fooOut>
      <sch:bar>
        <xsl:copy>
          <sch1:attr1>
          <sch1:attr2>
        </xsl:copy>
      </sch:bar>
    </sch:fooOut>
    

    您需要什么样的验证?如果您正在寻找更动态的解决方案,则最好使用 XSLT 以外的其他解决方案。

    【讨论】:

    • 我想说的是我想对一堆元素进行排序,它们要么是一种类型,要么是另一种类型。如果它们是一种类型,它们应该进入输出的一个部分,如果它们是另一种,它们应该进入另一个部分。模式是说明什么去哪里。这更有意义吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-28
    相关资源
    最近更新 更多