2009-12-26 43 views
2

由於可用的XPath引擎實現的性質,給定多個XPath表達式以及我嘗試過的所有事情都失敗,所以我正在尋找一種可將XML文件壓縮成表的通用算法。使用多個XPath表達式平展XML

給定一個XML:

<A Name="NameA"> 
<B Name="NameB1"> 
    <C Name="NameC1"/> 
    <C Name="NameC2"/> 
    <C Name="NameC3"/> 
</B> 
<B Name="NameB2"> 
    <C Name="NameC4"/> 
    <C Name="NameC5"/> 
    <C Name="NameC6"/> 
</B> 

和下面的XPath表達式作爲輸入:

/A/@Name 
/A/B/@Name 
/A/B/C/@Name 

輸出應該在以下形式的表格:

NAMEA NameB1 NameC1

NAMEA NameB1 NameC2

NAMEA NameB1 NameC3

NAMEA NameB2 NameC4

NAMEA NameB2 NameC5

NAMEA NameB2 NameC6

我試圖讓這個表可用的Java XML包,如javax.xml.xpath,jdom等..無濟於事。

這似乎是

XPath.evaluate("/A/B/C/@Name", doc, XPathConstants.NODESET); 

代碼將返回不能走過了「分離」節點。

我已經嘗試了許多XPath評估節點上的遞歸方式無濟於事。還想到DOM樹的DFS遍歷,但所有XPath評估程序似乎都返回分離的節點,其中node.getParent()將始終返回「null」。

「多XPath表達式感知」算法的任何想法,可以跟蹤嵌套的XPath表達式?

我有一種感覺,這是可能容易使用XSLT,但我的XSLT技能是非常生疏...

+0

忘了提的是,XML,也不是XPath的是靜態的。 該表達式在沒有XML的預先知識的情況下給出。 – yarinbenado

回答

0

編輯同樣的事情,但使用XPath:

 XPathFactory f = XPathFactory.newInstance(); 
     XPath xPath = f.newXPath(); 
     NodeList list = (NodeList) xPath.evaluate("//*[* and not(*/*)]/*", new InputSource(stream), XPathConstants.NODESET); 

     for (int i = 0; i < list.getLength(); i++) { 
      Node n = list.item(i); 
      Stack<Node> s = new Stack<Node>(); 

      while (n != null) { 
       s.push(n); 
       n = n.getParentNode(); 
      } 

      s.pop(); //this is document root, we don't need it 

      while (s.size() > 0) { 
       NamedNodeMap map = s.pop().getAttributes(); 

       for (int j = 0; j < map.getLength(); j++) { 
        Node node = map.item(j); 
        System.out.print(node.getNodeName() + ": " + node.getTextContent() + " "); 
       } 
      } 

      System.out.println(""); 
     } 

可以使用常規的DOM功能。它不如XPath好,但通用,並可用於任何XML文件。

如果我理解你的權利,那麼這段代碼就可以了:

String xml = "<A Name=\"NameA\">\n" + 
      "<B Name=\"NameB1\">\n" + 
      "  <C Name=\"NameC1\"> </C>\n" + 
      "  <C Name=\"NameC2\"/>\n" + 
      "  <C Name=\"NameC3\"/>\n" + 
      "</B>\n" + 
      "<B Name=\"NameB2\">\n" + 
      "  <C Name=\"NameC4\"/>\n" + 
      "  <C Name=\"NameC5\"/>\n" + 
      "  <C Name=\"NameC6\"/>\n" + 
      "</B></A>"; 
    try { 
     DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 
     Document doc = builder.parse(new ByteArrayInputStream(xml.getBytes())); 

     Queue<Node> q = new LinkedList<Node>(); 

     q.add(doc.getFirstChild()); 
     //start BFS 
     while (q.size() > 0) { 
      Node n = q.poll(); 
      NodeList childNodes = n.getChildNodes(); 
      //add all children of current node 
      int elemNodes = 0; 
      for (int i = 0; i < childNodes.getLength(); i++) { 
       Node node = childNodes.item(i); 
       if (node.getNodeType() == Node.ELEMENT_NODE) { 
        elemNodes++; 
        q.add(node); 
       } 
      } 
      //if node has no children, print its path 
      if (elemNodes == 0) { 
       Stack<Node> s = new Stack<Node>(); 

       while (n != null) { 
        s.push(n); 
        n = n.getParentNode(); 
       } 

       s.pop(); //this is document root, we don't need it 

       while (s.size() > 0) 
        System.out.print(s.pop().getAttributes().getNamedItem("Name").getTextContent() + " "); 

       System.out.println(""); 
      } 
     } 
    } catch (ParserConfigurationException e) { 
     e.printStackTrace(); 
    } catch (SAXException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
+0

偉大的迴應Piligrim!這是一個不錯的方法來簡單遍歷所有的DOM樹:) 但我的問題是,我會處理的XML,可能有這樣的表達: /A/@名稱 /A/B/@ AnotherName /A/B/C/D/E/@ ADifferentName 所以「名稱」常量將不起作用:( – yarinbenado

+0

名稱不是常量,您可以獲取節點的任何屬性 –

+0

好的,我已更改處理任何屬性的代碼。 –

0

我希望你能與XSLT2做到這一點。 (如果你僅限於XSLT1,那麼我不確定)。 有關教程,請參閱http://www.xml.com/pub/a/2003/11/05/tr.html。你可以有多個分組指令,他們都採用XPath。我無法立即爲您的問題提供代碼,但如果您閱讀了教程,我認爲它的地圖非常好。

3

這XSLT:

<?xml version="1.0"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output indent="yes" /> 

    <xsl:template match="/"> 
    <table> 
<!--Based upon your comments, it sounds as if you don't know what the structure of the XML you will be dealing with is(element nesting or attribute names). 
     That makes it a little bit difficult.  
     Based upon the example XML you gave the following for-each will work:--> 
     <xsl:for-each select="//C"> <!--You could also use "/A/B/C" --> 
     <tr> 
<!--This looks up the node tree and creates a column for the current element, as well as for each of it's parents, using the first Attribute as the value.--> 
      <xsl:for-each select="ancestor-or-self::*"> 
      <td><xsl:value-of select="@*[1]"/></td> 
      </xsl:for-each> 
     </tr> 
     </xsl:for-each> 
    </table> 
    </xsl:template> 

</xsl:stylesheet> 

作品爲XML提供,併產生下列:

<?xml version="1.0" encoding="UTF-16"?> 
<table> 
<tr> 
<td>NameA</td> 
<td>NameB1</td> 
<td>NameC1</td> 
</tr> 
<tr> 
<td>NameA</td> 
<td>NameB1</td> 
<td>NameC2</td> 
</tr> 
<tr> 
<td>NameA</td> 
<td>NameB1</td> 
<td>NameC3</td> 
</tr> 
<tr> 
<td>NameA</td> 
<td>NameB2</td> 
<td>NameC4</td> 
</tr> 
<tr> 
<td>NameA</td> 
<td>NameB2</td> 
<td>NameC5</td> 
</tr> 
<tr> 
<td>NameA</td> 
<td>NameB2</td> 
<td>NameC6</td> 
</tr> 
</table>