2016-06-13 2 views
0

Мне нужна помощь в создании выражения xpath для чтения всех имен узлов, значений узлов и атрибутов в строке xml. Я сделал это:Java, XPath Expression для чтения всех имен узлов, значений узлов и атрибутов

private List<String> listOne = new ArrayList<String>(); 
private List<String> listTwo = new ArrayList<String>(); 

public void read(String xml) { 
    try { 
     // Turn String into a Document 
     Document document = DocumentBuilderFactory.newInstance() 
       .newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes())); 

     // Setup XPath to retrieve all tags and values 
     XPath xPath = XPathFactory.newInstance().newXPath(); 
     NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document, XPathConstants.NODESET); 

     // Iterate through nodes 
     for(int i = 0; i < nodeList.getLength(); i++) { 
      Node node = nodeList.item(i); 
      listOne.add(node.getNodeName()); 
      listTwo.add(node.getNodeValue()); 
      // Another list to hold attributes 
     } 

    } catch(Exception e) { 
     LogHandle.info(e.getMessage()); 
    } 
} 

Я нашел выражение //text()[normalize-space()=''] онлайн; однако это не сработает. Когда я пытаюсь получить имя узла от listOne, это всего лишь #text. Я пробовал //, но это тоже не работает. Если у меня был этот XML:

<Data xmlns="Somenamespace.nsc"> 
    <Test>blah</Test> 
    <Foo>bar</Foo> 
    <Date id="2">12242016</Date> 
    <Phone> 
     <Home>5555555555</Home> 
     <Mobile>5555556789</Mobile> 
    </Phone> 
</Data> 

listOne[0] должен держать Data, listOne[1] должен держать Test, listTwo[1]blah должны держать, и т.д ... Все атрибуты будут сохранены в другом параллельном списке.

Какое выражение должно быть xPath оценить?

Примечание: XML-строка может иметь разные теги, поэтому я не могу ничего жестко кодировать.

Update: Пробовал этот цикл:

NodeList nodeList = (NodeList) xPath.evaluate("//*", document, XPathConstants.NODESET); 

// Iterate through nodes 
for(int i = 0; i < nodeList.getLength(); i++) { 
    Node node = nodeList.item(i); 

    listOne.add(i, node.getNodeName()); 

    // If null then must be text node 
    if(node.getChildNodes() == null) 
     listTwo.add(i, node.getTextContent()); 
} 

Однако, это только получает корневой элемент Data, то просто останавливается.

+1

'текст()' относится к содержимому элемента. В вашем примере XML, 'blah',' bar' и '12242016' являются текстовыми узлами. Итак, 'text()' вероятно, не то, что вы хотите. – VGR

+0

Спасибо! Если 'text()' дает содержимое элемента, будет ли 'node()' давать узлы? – syy

+1

Я думаю, что может понадобиться некоторое разъяснение. В XML «узел» относится к любой возможной части информации в XML-документе, включая текст, комментарии, инструкции по обработке и т. Д., Тогда как «элемент» относится к информации, состоящей из начального тега и соответствующего конечного тега, или одиночный самозакрывающийся тег (''). Вы действительно хотите прочитать каждый узел, или только каждый элемент и его атрибуты? – VGR

ответ

1

//* выберет все узлы элементов, //@* всех узлов атрибутов. Однако узел элемента не имеет значимого значения узла в DOM, поэтому вам нужно будет прочитать getTextContent() вместо getNodeValue.

Как вы, кажется, рассматривать элемент с дочерними элементами, чтобы иметь «нулевое» значение, я думаю, вам нужно проверить, есть ли дочерние элементы:

DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); 
    docBuilderFactory.setNamespaceAware(true); 

    DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); 

    Document doc = docBuilder.parse("sampleInput1.xml"); 

    XPathFactory fact = XPathFactory.newInstance(); 
    XPath xpath = fact.newXPath(); 

    NodeList allElements = (NodeList)xpath.evaluate("//*", doc, XPathConstants.NODESET); 

    ArrayList<String> elementNames = new ArrayList<>(); 
    ArrayList<String> elementValues = new ArrayList<>(); 

    for (int i = 0; i < allElements.getLength(); i++) 
    { 
     Node currentElement = allElements.item(i); 
     elementNames.add(i, currentElement.getLocalName()); 
     elementValues.add(i, xpath.evaluate("*", currentElement, XPathConstants.NODE) != null ? null : currentElement.getTextContent()); 
    } 

    for (int i = 0; i < elementNames.size(); i++) 
    { 
     System.out.println("Name: " + elementNames.get(i) + "; value: " + (elementValues.get(i))); 
    } 

Для ввода образца

<Data xmlns="Somenamespace.nsc"> 
    <Test>blah</Test> 
    <Foo>bar</Foo> 
    <Date id="2">12242016</Date> 
    <Phone> 
     <Home>5555555555</Home> 
     <Mobile>5555556789</Mobile> 
    </Phone> 
</Data> 

выход

Name: Data; value: null 
Name: Test; value: blah 
Name: Foo; value: bar 
Name: Date; value: 12242016 
Name: Phone; value: null 
Name: Home; value: 5555555555 
Name: Mobile; value: 5555556789 
+0

Я сделал '// *' с 'getTextContext()' и смог получить имена и значения тегов. Однако для родительских узлов, таких как «Данные», текст, который он возвращает, - это все, что есть у его детей. Поэтому 'listTwo.get (0)' возвращает 'blah, bar, 12242016'. Я попытался проверить, является ли 'getChildNodes()' не null, а затем не получает текстовое содержимое, а затем цикл просто останавливается. Как сделать это так: listOne (0) 'is' Data', 'listTwo (0)' is 'null',' listOne (1) 'is' Test', 'listTwo (1)' is 'blah'. Я обновлю OP. – syy

+1

'getChildNodes' дает вам' NodeList', а не 'null'. И даже ' бар' имеет дочерний узел, текстовый узел. Также, что вы хотите делать со смешанным контентом, например '

Это bold текст.

'? Вам нужно более подробно объяснить, какие результаты вы хотите. –

+0

О, я вижу сейчас. Что касается вашего примера, у меня не будет такого случая. Он будет строго подобен показанному в OP (добавленному в XML-пример немного больше). Я просто хочу, чтобы 'listOne' удерживал все элементы и' listTwo' для хранения связанного с ними текста. Однако, если у элемента есть дочерние элементы и нет прямого текста, то для этого индекса 'listTwo' должен быть' null', как показано в примере вышеприведенного комментария. – syy

Смежные вопросы