# XML einlesen gleichnamige Elemente



## leonbrachwitz (5. Sep 2018)

Moin Moin!
ich habe ein Problem. Ich versuche die .xml der Europäischen Zentralbank einzulesen. Der Aufbau ist folgendermaßen (von mir gekürzt):

```
<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope
   xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01"
   xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
   <gesmes:subject>Reference rates</gesmes:subject>
   <gesmes:Sender>
       <gesmes:name>European Central Bank</gesmes:name>
   </gesmes:Sender>
   <Cube>
       <Cube time="2018-08-06">
           <Cube currency="USD" rate="1.1543" />
           <Cube currency="JPY" rate="128.68" />
           <Cube currency="BGN" rate="1.9558" />
       </Cube>
       <Cube time="2018-08-03">
           <Cube currency="USD" rate="1.1588" />
           <Cube currency="JPY" rate="129.3" />
           <Cube currency="BGN" rate="1.9558" />
       </Cube>
   </Cube>
</gesmes:Envelope>
```

Ich benötige nur die Cube Elemente, welche einen Währungskürzel und den Tageskurs enthalten. Mein Code sieht folgendermaßen aus:
Bei jedem Cube-Element wird ein Objekt Waehrung erstellt. Bei jedem schließenden Cube-Tag, wird das Objekt in eine Liste gepackt. Das Problem: Die letzte Währung (<Cube currency="BGN" rate="1.9558" />) hängt 3 mal in meiner Liste, da ja bei jedem schlißenden Tag das Objekt angehangen wird.
Meine Frage: Wie kann ich das umgehen? Ich möchte jede Währung nur einmal in meiner Liste haben, also in diesem Fall 6stk.

Hier noch der Code:

```
public List<Waehrung> liesXmlEin(String xml) throws XMLStreamException
    {
        if (xml == "")
        {
            throw new IllegalArgumentException();
        }
        else
        {
            InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
            XMLInputFactory factory = XMLInputFactory.newInstance();
            XMLEventReader eventReader = factory.createXMLEventReader(inputStream);

            List<Waehrung> waehrungen = new ArrayList<>();
            Waehrung waehrung = null;
            boolean gueltigeWaehrung = false;

            while (eventReader.hasNext())
            {
                XMLEvent xmlEvent = eventReader.nextEvent();

                if (xmlEvent.isStartElement())
                {
                    StartElement startElement = xmlEvent.asStartElement();

                    if ("Cube".equalsIgnoreCase(startElement.getName().getLocalPart()))
                    {
                        waehrung = new Waehrung();
                        gueltigeWaehrung = false;
                    }

                    Iterator<Attribute> iterator = startElement.getAttributes();

                    while (iterator.hasNext())
                    {
                        Attribute attribute = iterator.next();
                        QName name = attribute.getName();

                        if ("currency".equalsIgnoreCase(name.getLocalPart()))
                        {
                            gueltigeWaehrung = true;
                            waehrung.setWaehrungskuerzel((String.valueOf(attribute.getValue())));
                        }

                        if ("rate".equalsIgnoreCase(name.getLocalPart()))
                        {
                            waehrung.setTageskurs(((new BigDecimal(String.valueOf(attribute.getValue())))));
                        }
                    }

                }
                if (xmlEvent.isEndElement())
                {
                    EndElement endElement = xmlEvent.asEndElement();

                    if ("Cube".equalsIgnoreCase(endElement.getName().getLocalPart())
                            && (gueltigeWaehrung == true))
                    {
                        waehrungen.add(waehrung);
                    }
                }
            }

            return waehrungen;
        }

    }
```


----------



## looparda (5. Sep 2018)

Du brauchst das EndElement nicht. Sobald du auf ein Cube-Element stößt liest du die Attribute und im Anschluss hast du doch dein Währungsobjekt fertig bestückt, um es in die Liste zu packen.
Die einzige Ausnahme ist das äußere cube-Element, welches cube-Elemente enthält. Hier kannst du ein Flag setzen, sobald du das erste cube-Element passiert hast. Nachdem das Flag gesetzt wurde kannst du sicher sein, dass alle folgenden Cube-Elemente  die Attribute enthalten, die du auslesen willst.

```
Iterator<Attribute> iterator = startElement.getAttributes();
while (iterator.hasNext()) {...}
// hier hast du bereits alle Informationen um dein Währungsobjekt in die Liste zu packen
```


----------



## Flown (5. Sep 2018)

Also wenn ich etwas in einem XML suche, dann nur mit XPath:

```
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
    "<gesmes:Envelope\n" +
    "   xmlns:gesmes=\"http://www.gesmes.org/xml/2002-08-01\"\n" +
    "   xmlns=\"http://www.ecb.int/vocabulary/2002-08-01/eurofxref\">\n" +
    "   <gesmes:subject>Reference rates</gesmes:subject>\n" +
    "   <gesmes:Sender>\n" +
    "       <gesmes:name>European Central Bank</gesmes:name>\n" +
    "   </gesmes:Sender>\n" +
    "   <Cube>\n" +
    "       <Cube time=\"2018-08-06\">\n" +
    "           <Cube currency=\"USD\" rate=\"1.1543\" />\n" +
    "           <Cube currency=\"JPY\" rate=\"128.68\" />\n" +
    "           <Cube currency=\"BGN\" rate=\"1.9558\" />\n" +
    "       </Cube>\n" +
    "       <Cube time=\"2018-08-03\">\n" +
    "           <Cube currency=\"USD\" rate=\"1.1588\" />\n" +
    "           <Cube currency=\"JPY\" rate=\"129.3\" />\n" +
    "           <Cube currency=\"BGN\" rate=\"1.9558\" />\n" +
    "       </Cube>\n" +
    "   </Cube>\n" +
    "</gesmes:Envelope>";

try {
  DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
  Document document = documentBuilder.parse(new InputSource(new StringReader(xml)));

  XPathFactory xPathFactory = XPathFactory.newInstance();
  XPath xPath = xPathFactory.newXPath();
  
  NodeList currencies = (NodeList) xPath.evaluate("//Cube[@currency and @rate]", document, XPathConstants.NODESET);
  for (int i = 0; i < currencies.getLength(); i++) {
    Node currency = currencies.item(i);
    System.out.println(xPath.evaluate("./@currency", currency) + ": " + xPath.evaluate("./@rate", currency));
  }
} catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException e) {
  e.printStackTrace();
}
```
Output:

```
USD: 1.1543
JPY: 128.68
BGN: 1.9558
USD: 1.1588
JPY: 129.3
BGN: 1.9558
```


----------



## leonbrachwitz (5. Sep 2018)

Wow. Manchmal sieht man den Wald vor lauter Bäum nicht.
Vielen Dank für die Antwort looparda!


----------



## looparda (5. Sep 2018)

Aber allgemein scheint mir die Library nicht passend zu sein, da du immer nur Zugriff auf das aktuelle Element hast und keine Funktionen wie getParent und getChildren, um hierarchisch parsen zu können.
Beispiel mit jdom2 und durchhangeln bis zu den inneren Cubes:

```
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class XMLRead {

    public static void main(String[] args) throws IOException {
        new XMLRead().run();
    }

    private void run() throws IOException {
        File file = new File(getClass().getResource("waehrung.xml").getFile());
        final List<Waehrung> waehrungen = liesXmlEin(file);
        for ( Waehrung w : waehrungen) {
            System.out.println(w.getWaehrungskuerzel() + ":" + w.getTageskurs());
        }

    }

    public List<Waehrung> liesXmlEin(File xmlFile)
    {
        List<Waehrung> waehrungen = new ArrayList<>();

        try {
            SAXBuilder saxBuilder = new SAXBuilder();
            Document document = saxBuilder.build(xmlFile);
            Element root = document.getRootElement();
            //TODO: besser getChild(cname, namspace)
            final Element cubeContainer = root.getChildren().stream().filter(c -> c.getName().equals("Cube")).findFirst().orElseThrow(IllegalStateException::new);
            final List<Element> cubesWithDate = cubeContainer.getChildren();
            final List<Element> cubesWithData = cubesWithDate.stream().map(c -> c.getChildren()).flatMap(c -> c.stream()).collect(Collectors.toList());
            cubesWithData.stream().forEach(c -> waehrungen.add( new Waehrung(c.getAttribute("currency").getValue(), new BigDecimal(c.getAttribute("rate").getValue()))));
        } catch(JDOMException e) {
            e.printStackTrace();
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }

        return waehrungen;
    }
}
```


```
USD:1.1543
JPY:128.68
BGN:1.9558
USD:1.1588
JPY:129.3
BGN:1.9558
```


----------



## dzim (5. Sep 2018)

Ich finde die Antwort von @Flown jedoch sauberer...


----------

