2009-09-16 108 views
5

重新安排XML文檔的一致性的元素說我有一個XML文檔(表示爲文本,W3C DOM,等等),還一個XML Schema。 XML文檔具有由模式定義的所有正確元素,但順序錯誤。使用模式與模式

如何使用該模式以「重新排序」的元素在文檔中符合由架構定義的排序?

我知道這應該是可能的,可能使用XSOM,因爲JAXB XJC代碼生成器使用正確的元素序列化順序爲其生成的類註釋。

不過,我不熟悉XSOM API,它是相當密集的,所以我希望你們很多人有它一定的經驗,並能指出我在正確的方向。像「在這個父元素中允許哪些子元素,以什麼順序?」


讓我舉個例子。

我有一個這樣的XML文檔:

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

我有一個XML Schema它說的<A>的內容必須是<X>後跟一個<Y>。現在很明顯,如果我試圖根據模式驗證文檔,它會失敗,因爲<X><Y>的順序是錯誤的。但是我知道我的文檔事先是「錯誤的」,所以我現在還沒有使用該模式進行驗證。但是,我知道我的文檔具有模式定義的所有正確元素,只是順序錯誤。

我想要做的就是以編程方式檢查架構(可能使用XSOM--這是XML架構的對象模型),並詢問它應該是什麼內容<A>。 API將公開「你需要一個<X>後跟一個<Y>」的信息。

所以我把我的XML文檔(使用DOM API)和重新排列,並相應地,因此,現在的文件將根據模式驗證。

瞭解XSOM是什麼在這裏很重要 - 它是一個java API,代表包含在XML Schema中的信息,而不是包含在我的實例文檔中的信息。

我不想做的就是生成的模式代碼,因爲該模式是在構建時未知。此外,XSLT是沒有用的,因爲元素的正確排序僅由模式中包含的數據字典確定。

希望這已經夠明確了。

+1

什麼是對輸入的約束?你給出一個相當簡單的例子,但顯然可以有更復雜的情況,如選擇(序列(選擇(...)))。另外,是否事先知道輸入文檔可以通過對元素進行重新排序來符合模式?如果這不是保證,那坦率地說,我甚至不知道哪裏開始。 – 2009-09-16 21:54:55

+0

是的,我事先知道正確的元素都在那裏,但順序已經被先前的處理步驟有效地隨機化。你是對的,因爲模式類型定義的潛在複雜性可能令人望而生畏,這就是爲什麼我希望XSOM能夠解譯我並用簡單的術語代表它。 – skaffman 2009-09-16 21:56:40

+1

就我所見,XSOM並沒有真正簡化任何東西 - 它更像是一個XML Schema的強類型DOM。它主要爲您提供便捷的現成解析器和類AST結構,但無助於您的需求。因此,無論您處理XML Schema的方式如何,解決方案都是通用的。 – 2009-09-16 22:04:53

回答

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節點。

+0

是的,這就是方式去。 – 2009-09-16 21:18:06

+0

好吧,很酷,我們現在都在同一頁上:)我同意XSL轉換會比在DOM中手動調用更有效地重新排列文檔,但是使用XSOM API發現問題的最初問題無論我用於執行重新排序本身的機制,命令*應該保留什麼。 – skaffman 2009-09-16 22:01:30

+0

我突然想知道是否無法使用樣式表將XSD轉換爲XML複製樣式表。會製作一個有趣的跨平臺解決方案。如果您已經熟悉XSD和XSLT,那麼這可能比了解更多關於XSOM更容易。 – 2009-09-16 22:06:08

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>。似乎沒有任何理由相互傾向。

+0

那麼發現,這是一個公平點。然而,就我而言,我知道這種不明確性不會出現,因爲每個「」都具有相同的模式類型,並且具有相同的子級排序。 – skaffman 2009-09-16 22:20:48

+0

好點(+1),但這樣的結構有多普遍?爲什麼有人會使用這樣的建築? – 2009-09-17 08:24:47

1

基本上你想採取根元素,並從那裏遞歸地查看文檔中的子元素和模式中定義的子元素,並使順序匹配。

我給你一個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 ]); 
    } 
} 

如果它要成爲一個深樹,我不會推薦一個遞歸方法,在這種情況下,你將不得不創造一些「樹遍歷」類型的對象。這種方法的優點是你可以處理更復雜的事情,比如模式說你可以有0個或更多的元素,你可以繼續處理源節點,直到沒有更多的匹配,然後移動模式步行者從那裏開始。

+0

它不那麼簡單,因爲沒有'getChildren()' - 據我所知,可能有像'xs:choice'或'maxOccurs> 1'這樣的東西,所以甚至可能沒有一個特定的元素作爲第N個孩子 - 這將是「X或Y或......」,基本上是任意長。 – 2009-09-16 22:26:08

+0

我想這可能就是這樣(鑑於XSD的本質),所以我提到的第二個選項是真正的唯一途徑。如果沒有人提出解決方案,我可以發佈一個如何工作的例子。 – 2009-09-17 20:40:04

3

我在兩週左右遇到同樣的問題。 最後我獲得了突破。 這可以使用JAXB編組/解組功能來實現。

在JAXB元帥/ unmarshal中,XML驗證是一個可選功能。 因此,在創建Marshaller和UnMarshaller對象時,我們不調用setSchema(模式)方法。 省略此步可避免編組/解組的XML驗證功能。

所以,現在,

  1. 如果按照XSD任何強制性因素不存在於XML,它被忽視。
  2. 如果XSD中沒有任何標記存在於XML中,則不會引發任何錯誤,也不會出現在編組/解組後的新XML中。
  3. 如果元素沒有按順序排列,則會重新排序。這是通過JAXB生成的POJO完成的,我們在創建JAXBContext時通過這些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(); 
    } 
}