【问题标题】:Using a schema to reorder the elements of an XML document in conformance with the schema使用模式重新排序 XML 文档的元素以符合模式
【发布时间】:2009-09-16 21:03:23
【问题描述】:

假设我有一个 XML 文档(表示为文本、一个 W3C DOM 等等),还有一个 XML Schema。 XML 文档包含架构定义的所有正确元素,但顺序错误。

如何使用架构“重新排序”文档中的元素以符合架构定义的顺序?

我知道这应该是可能的,可能使用XSOM,因为 JAXB XJC 代码生成器使用元素的正确序列化顺序注释其生成的类。

但是,我对 XSOM API 并不熟悉,而且它非常密集,所以我希望你们中的某个人对它有一些经验,并且可以为我指明正确的方向。类似“在这个父元素中允许哪些子元素,以及以什么顺序?”


让我举个例子。

我有一个这样的 XML 文档:

<A>
   <Y/>
   <X/>
</A>

我有一个 XML Schema,它表示 &lt;A&gt; 的内容必须是 &lt;X&gt; 后跟 &lt;Y&gt;。现在很明显,如果我尝试根据架构验证文档,它会失败,因为 &lt;X&gt;&lt;Y&gt; 的顺序错误。但是我提前知道我的文档是“错误的”,所以我还没有使用模式来验证。但是,我确实知道我的文档包含架构定义的所有正确元素,只是顺序错误。

我想做的是以编程方式检查 Schema(可能使用 XSOM - 这是 XML Schema 的对象模型),并询问&lt;A&gt; 的内容应该是什么。 API 将公开“您需要&lt;X&gt; 后跟&lt;Y&gt;”的信息。

所以我使用我的 XML 文档(使用 DOM API)并相应地重新排列,以便现在文档将根据架构进行验证。

了解 XSOM 在这里很重要 - 它是一个 java API,代表 XML Schema 中包含的信息,不是我的实例文档中包含的信息。

我不想从架构生成代码,因为架构在构建时是未知的。此外,XSLT 没有用处,因为元素的正确顺序仅由模式中包含的数据字典决定。

希望现在已经足够明确了。

【问题讨论】:

  • 输入有什么限制?你举了一个相当简单的例子,但显然可以有更复杂的情况,比如选择(序列(选择(...)))。此外,是否事先知道输入文档可以通过重新排序元素来符合模式?如果这不是保证,那么坦率地说,我什至不知道从哪里开始。
  • 是的,我提前知道正确的元素都在那里,但是顺序已经被前面的处理步骤有效地随机化了。您说得对,架构类型定义的潜在复杂性可能令人生畏,这就是为什么我希望 XSOM 能够为我破译并以简单的方式向我展示它的原因。
  • 就我所见,XSOM 并没有真正简化任何事情——它更像是用于 XML Schema 的强类型 DOM。它主要为您提供了使用现成的解析器和类似 AST 的结构的便利,但对您想要的没有任何帮助。因此,无论您处理 XML Schema 的方式如何,解决方案都是通用的。
  • XOM 的功能远不止 XSD 解析器,它理解模式结构的含义。例如,JAXB 的代码生成器使用 XSOM 来确定要生成什么代码。
  • “理解意义”是什么意思?我在这里查看 JavaDoc:xsom.dev.java.net/nonav/javadoc/index.html - 据我所知,它几乎是一对一的映射;例如xs:choicexs:sequence 将映射到 XSModelGroup 的实例。 .NET 有一个类似的 API,但它也有一个有趣的地方,它可以让您逐个节点验证文档(推,而不是拉),然后中途停止并询问它可能遵循的有效粒子的详尽列表在此刻。我在 XSOM 中没有看到这一点,但也许我只是在看错误的地方?如果它在那里会有所帮助。

标签: java xml xsd xsom


【解决方案1】:

我被同样的问题困扰了大约两周。 终于我得到了突破。 这可以使用 JAXB 编组/解组功能来实现。

在 JAXB 编组/解组中,XML 验证是一个可选功能。 所以在创建 Marshaller 和 UnMarshaller 对象时,我们不调用 setSchema(schema) 方法。 省略此步骤可避免 marshal/unmarshal 的 XML 验证功能。

现在,

  1. 如果 XML 中不存在任何符合 XSD 的强制元素,则会被忽略。
  2. 如果 XSD 中不存在的任何标签存在于 XML 中,则不会引发错误,并且它不会出现在编组/解组后获得的新 XML 中。
  3. 如果元素不按顺序排列,则会重新排序。这是由我们在创建 JAXBContext 时传递的 JAXB 生成的 POJO 完成的。
  4. 如果某个元素错位在某个其他标记中,则它会在新 XML 中被省略。编组/解组时不会引发错误。

public class JAXBSequenceUtil {
  public static void main(String[] args) throws JAXBException, IOException {

    String xml = FileUtils.readFileToString(new File(
            "./conf/out/Response_103_1015700001&^&IOF.xml"));

    System.out.println("Before marshalling : \n" + xml);
    String sequencedXml = correctSequence(xml,
            "org.acord.standards.life._2");
    System.out.println("After marshalling : \n" + sequencedXml);
  }

  /**
   * @param xml
   *            - XML string to be corrected for sequence.
   * @param jaxbPackage
   *            - package containing JAXB generated classes using XSD.
   * @return String - xml with corrected sequence
   * @throws JAXBException
   */
  public static String correctSequence(String xml, String jaxbPackage)
        throws JAXBException {
    JAXBContext jaxbContext = JAXBContext.newInstance(jaxbPackage);
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    Object txLifeType = unmarshaller.unmarshal(new InputSource(
            new StringReader(xml)));
    System.out.println(txLifeType);

    StringWriter stringWriter = new StringWriter();
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.marshal(txLifeType, stringWriter);

    return stringWriter.toString();
  }
}

【讨论】:

    【解决方案2】:

    对此我还没有一个好的答案,但我必须指出,那里可能存在歧义。考虑这个架构:

    <xs:element name="root">
      <xs:choice>
        <xs:sequence>
          <xs:element name="foo"/>
          <xs:element name="bar">
            <xs:element name="dee">
            <xs:element name="dum">
          </xs:element>
        </xs:sequence>
        <xs:sequence>
          <xs:element name="bar">
            <xs:element name="dum">
            <xs:element name="dee">
          </xs:element>
          <xs:element name="foo"/>
        </xs:sequence>
      </xs:choice>
    </xs:element>
    

    还有这个输入 XML:

    <root>
      <foo/>
      <bar>
        <dum/>
        <dee/>
      </bar>
    </root>
    

    这可以通过重新排序&lt;foo&gt;&lt;bar&gt; 或通过重新排序&lt;dee&gt;&lt;dum&gt; 来符合架构。似乎没有任何理由偏爱一个。

    【讨论】:

    • 很好,这是一个公平的观点。然而,就我而言,我知道不会出现这样的歧义,因为每个 &lt;bar&gt; 都将具有相同的模式类型,具有相同的子顺序。
    • 好点(+1),但这种结构有多普遍?为什么有人会使用这样的结构?
    【解决方案3】:

    您的问题转化为:您有一个与架构不匹配的 XSM 文件,并且您希望将其转换为有效的文件。

    使用 XSOM,您可以读取 XSD 中的结构并可能分析 XML,但它仍然需要从无效表单到有效表单的额外映射。使用样式表会容易得多,因为您将遍历 XML,使用 XPath 节点以正确的顺序处理元素。对于您希望苹果先于梨的 XML,样式表将首先复制苹果节点 (/Fruit/Apple),然后再复制梨节点。这样,无论旧文件中的顺序如何,它们在新文件中的顺序都是正确的。

    您可以使用 XSOM 执行的操作是读取 XSD 并生成将重新排序数据的样式表。然后使用该样式表转换 XML。一旦 XSOM 为 XSD 生成了样式表,您就可以重复使用该样式表,直到 XSD 被修改或需要另一个 XSD。

    当然,您可以使用 XSOM 以正确的顺序立即复制节点。但是由于这意味着您的代码必须自己遍历所有节点和子节点,因此可能需要一些时间来处理才能完成。样式表会做同样的事情,但转换器将能够更快地处理它。它可以直接处理数据,而 Java 代码必须通过 XMLDocument 属性获取/设置每个节点。


    因此,我将使用 XSOM 为 XSD 生成样式表,该样式表只会逐个节点复制 XML 以便反复使用。样式表只需要在 XSD 更改时重写,并且它的执行速度比 Java API 需要遍历节点本身时更快。样式表不关心顺序,因此它总是以正确的顺序结束。
    为了使其更有趣,您可以跳过 XSOM 并尝试使用读取 XSD 的样式表以从中生成另一个样式表.此生成的样式表将按照样式表中定义的确切顺序复制 XML 节点。会不会很复杂?实际上,样式表需要为每个元素生成模板,并确保以正确的顺序处理该元素中的子元素。

    当我想到这一点时,我想知道以前是否已经这样做过。它将非常通用,并且能够处理几乎所有 XSD/XML。

    让我们看看...使用“//xsd:element/@name”,您将获得架构中的所有元素名称。每个唯一名称都需要翻译成模板。在这些模板中,您需要处理特定元素的子节点,这稍微复杂一些。元素可以有一个参考,您需要遵循该参考。否则,获取它的所有子 xsd:element 节点。

    【讨论】:

    • 好的,很酷,我们现在在同一个页面上 :) 我同意 XSL 转换会比手动在 DOM 中更有效地重新排列我的文档,但最初的问题是使用 XSOM API 找出订单应该保留什么,而不管我使用何种机制来执行重新排序本身。
    • 我突然想知道是否可以使用样式表将 XSD 转换为 XML 复制样式表。将制作一个有趣的跨平台解决方案。如果您已经熟悉 XSD 和 XSLT,那么这可能比学习更多有关 XSOM 的知识更容易。
    • 我不知道,模式可能非常复杂,尤其是我正在使用的那些......扩展类型、替换组,所有这些东西。吓人。
    【解决方案4】:

    基本上,您希望获取根元素,然后从那里递归地查看文档中的子元素和架构中定义的子元素,并使顺序匹配。

    我会给你一个 C# 语法的解决方案,因为这是我日夜编码的,它非常接近 Java。请注意,我将不得不猜测 XSOM,因为我不知道它的 API。我还编造了 XML Dom 方法,因为给你的 C# 方法可能无济于事:)

    // 假设第一个调用是 SortChildrenIntoNewDocument( sourceDom.DocumentElement, targetDom.DocumentElement, schema.RootElement )

    public void SortChildrenIntoNewDocument( XmlElement source, XmlElement target, SchemaElement schemaElement )
    {
        // whatever method you use to ask the XSOM to tell you the correct contents
        SchemaElement[] orderedChildren = schemaElement.GetChildren();
        for( int i = 0; i < orderedChildren.Length; i++ )
        {
            XmlElement sourceChild = source.SelectChildByName( orderedChildren[ i ].Name );
            XmlElement targetChild = target.AddChild( sourceChild )
            // recursive-call
            SortChildrenIntoNewDocument( sourceChild, targetChild, orderedChildren[ i ] );
        }
    }
    

    如果它是一棵深树,我不推荐使用递归方法,在这种情况下,您将不得不创建一些“tree walker”类型的对象。这种方法的优点是您将能够处理更复杂的事情,例如当架构说您可以拥有 0 个或多个元素时,您可以继续处理源节点直到没有更多匹配项,然后移动架构步行器从那里开始。

    【讨论】:

    • 没有那么简单,因为没有getChildren()——据我了解,可能有xs:choicemaxOccurs &gt; 1之类的东西,所以甚至可能没有一个特定的元素作为第 N 个孩子 - 这将是“X 或 Y 或 ...”,基本上是任意长度。
    • 我认为它可能是这样的(考虑到 XSD 的性质),所以我提到的第二个选项是真正可行的唯一方法。如果没有其他人提出解决方案,我可以发布一个如何工作的示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多