6

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

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

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

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


让我举个例子吧。

我有一个这样的 XML 文档:

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

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

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

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

了解 XSOM 是什么很重要——它是一个 java API,代表 XML Schema 中包含的信息,而不是我的实例文档中包含的信息。

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

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

4

4 回答 4

4

我被同样的问题困扰了大约两周。终于我得到了突破。这可以使用 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();
  }
}
于 2011-11-26T14:10:15.420 回答
3

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

<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>

这可以通过重新排序<foo>and<bar>或通过重新排序<dee>and来符合模式<dum>。似乎没有任何理由偏爱一个。

于 2009-09-16T22:16:58.990 回答
2

您的问题转化为:您有一个与架构不匹配的 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 节点。

于 2009-09-16T21:10:58.410 回答
1

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

我会给你一个 C#-syntax 解决方案,因为这是我日夜编码的,它非常接近 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 个或多个元素时,您可以继续处理源节点直到没有更多匹配项,然后移动架构步行器从那里开始。

于 2009-09-16T22:21:54.110 回答