使用模式按照模式重新排序XML文档的元素

时间:2009-09-16 21:03:24

标签: java xml xsd xsom

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

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

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

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


让我举个例子。

我有一个这样的XML文档:

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

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

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

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

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

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

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

4 个答案:

答案 0 :(得分: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><bar>,或重新排序<dee><dum>,可以使其符合架构。似乎没有任何理由偏爱一个而不是另一个。

答案 1 :(得分:3)

我在两周左右遇到了同样的问题。 最后我获得了突破。 这可以使用JAXB编组/解组功能来实现。

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

现在,

  1. 如果XML中没有XSD中的任何必需元素,则会被忽略。
  2. 如果XML中不存在XSD中没有的任何标记,则不会抛出任何错误,并且在编组/解组后得到的新XML中不会出现错误。
  3. 如果元素不按顺序排列,则重新排序。这是由我们在创建JAXBContext时传递的JAXB生成的POJO完成的。
  4. 如果某个元素在其他标记内部放错位置,则在新XML中将其省略。编组/解组时不会抛出任何错误。

  5. 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 :(得分:2)

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

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

使用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节点。

答案 3 :(得分: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或更多元素时,你可以继续处理源节点,直到没有更多的匹配,然后移动模式walker从那里开始。