# XML Reader



## Shoox (21. Sep 2010)

Hallo an alle.

Ich verzweifle gerade an einer XML Reader class. Die class soll (wär jz nicht zu erwarten gewesen) ein xml file auslesen. Die Klasse soll aber als eine Art Helper definiert sein.

Meine Aufgabenstellung lautet:
Lese alle elements und speichere sie wie folgt ab:

[XML]<root>
 <product>
  <id>3</id>
  <lieferadresse>
   <name>bla</name>
  </lieferadresse>
  <rechnungsadresse>
   <name>bla2</name>
  </rechnungsadresse>
 </product>
 <prodcut>
 ...
 </product>
</root>[/XML]

Das ist mal eine vereinfachte Variante von meiner xml Datei. Ich soll nun für jedes product eine HashMap erzeugen in der ich alles reinspeichere. Die Lieferadresse und die Rechnungsadresse sind eigene complexTypes die ich wiederrum in eine HashMap speichern soll also in der ersten HashMap soll ich als key "lieferadresse" rein haun und als value soll ich eine HashMap zurück bekommen wo dann dessen Daten drinstehen.
Attribute sollen auch gesichert werden ...

Ich habs mal (ziemlich ziemlich ziemlich hässlich) soweit:

```
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;

import org.w3c.dom.*;
import org.xml.sax.*;
import test.StringFunctions;

import javax.xml.parsers.*;

/**
 * Diese Klasse liest ein .xml File aus und gibt die gelesenen Elements entweder <br>
 * mit der Methode getList(), getHashMap() oder getListOfHashMaps() zurück. <br>
 * 
 * Best Practice: getListOfHashMaps()
 * @author schmid.k
 */
public class ParseXMLFile {
   private ArrayList<String> list = new ArrayList<String>();
   private static final String trennzeichen = "#";
   private static String startNode;
   private int count = 0;

   /**
    * Liefert das verwendete Trennzeichen zurück welches bei den Methoden getList(), getHashMap()
    * und getListOfHasMaps() verwendet wurde.
    * @return String Trennzeichen
    */
   public static String getTrennzeichen() {
      return trennzeichen;
   }
   
   /**
    * Liefert die Anzahl an eingelesenen startNodes zurück. <br>
    * @return anzahl
    */
   public int getAnzahl() {
      return count;
   }
   
   private void add(String value) {
      list.add(value);
   }

   /**
    * Die Datensätze werden wie folgt geliefert: <br>
    * nodeName/trennzeichen/nodeValue/trennzeichen/attributeName/trennzeichen/attributeValue...
    * @return Erhalt der Datensätze der .xml Datei in einer ArrayList<br>
    */
   public ArrayList<String> getList() {
      return list;
   }

   /**
    * <b>ACHTUNG: Nur anwendbar wenn jeder key einzigartig ist.</b>
    * @return Erhalt der Datensätze der .xml Datei in einer HashMap.
    */
   public HashMap<String,String> getHashMap() {
      HashMap<String,String> hm = new HashMap<String,String>();
      String[] datensatz;
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < list.size(); i++){
         datensatz = list.get(i).toString().split(trennzeichen);
         if (datensatz.length == 1)
            hm.put(datensatz[0], "");
         if (datensatz.length == 2) {
            hm.put(datensatz[0], datensatz[1]);
         } else {
            for (int j = 1; j < datensatz.length; j++) {
               sb.append(datensatz[j]);
               sb.append(trennzeichen);
            }
            hm.put(datensatz[0], sb.toString());
         }
      }
      return hm;
   }
   
   /**
    * Gibt alle Header Informationen in einer ArrayList zurück. <br>
    * nodeName/trennzeichen/nodeValue/trennzeichen/attributeName/trennzeichen/attributeValue...
    * @return Erhalt der Headerdaten der .xml Datei in einer ArrayList.
    */
   public ArrayList<String> getHeader() {
      ArrayList<String> header = new ArrayList<String>();
      boolean check = true;
      for (int i = 0; i<list.size(); i++) {
         String line = list.get(i).toString().trim();
         if(!line.split(trennzeichen)[0].equals(startNode) && check) {
            header.add(line);
         }
         else {
            check = false;
         }
      }
      return header;
   }
   
   /**
    * Gibt alle Header Informationen in einer HashMap zurück.<br>
    * key = nodeName<br>
    * value = nodeValue/trennzeichen/attributeName/trennzeichen/attributeValue...
    * @return Erhalt der Headerdaten der .xml Datei in einer HashMap.
    */
   public HashMap<String, String> getHeaderAsHashMap() {
      ArrayList<String> header = getHeader();
      HashMap<String,String> map = new HashMap<String,String>();
      for (int i = 0; i<header.size(); i++) {
         String line = header.get(i).toString();
         if (line.contains(trennzeichen)) {
            String[] lines = line.split(trennzeichen);
            if (lines.length>2) {
               map.put(lines[0], line.substring(lines[0].length()+1));
            } else {
            map.put(lines[0], lines[1]);
            }
         }
      }
      return map;
   }

   /**
    * <br>
    * <b>getListOfHashMaps()</b><br><br>
    * Erhalt der Datensätze der .xml Datei in einer Liste von HashMaps.<br>
    * Diese Methode wird angewand falls in der .xml Datei gleiche keys mehrfach vorkommen. <br><br>
    * Der Aufbau verhält sich wiefolgt:<br>
    * <b>key</b> = nodeName<br>
    * <b>value</b> = nodeValue<br><br>
    * Falls Attribute im Node vorhanden sind werden diese dem nodeValue angehängt:<br>
    * nodeValue/trennzeichen/attributeName/trennzeichen/attributeValue...<br><br>
    * Falls ein Node keinen Value hat, jedoch Attribute werden durch den key sofort die
    * Attribute aufgerufen:<br>
    * <b>key</b> = nodeName<br>
    * <b>value</b> = attributeName/trennzeichen/attributeValue...<br>
    * @return ArrayList von HashMaps <br>
    */
   public ArrayList<HashMap<String,String>> getListOfHashMaps() {
      ArrayList<String> xmlList = getList();
      ArrayList<HashMap<String,String>> hashlist = new ArrayList<HashMap<String,String>>();
      String key;
      String oldKey = "";
      String value;
      String oldValue = "";
      StringBuffer sb = new StringBuffer();
      boolean check = false;
      boolean checkAttribute = true;
      HashMap<String,String> map = new HashMap<String,String>();
      // for-Schleife bis size()-1 weil hardcoded ein letzter startNode angehängt wurde
      // um eine NullPointerException der letzen if-Abfrage vorzubeugen.
      for (int i = 0; i < xmlList.size() - 1; i++){
         String[] datensatz = xmlList.get(i).toString().split(trennzeichen);
         key = datensatz[0];
         // Abfrage um header Daten zu exludieren
         if (key.equals(startNode) || check){
            check = true;
            if (datensatz.length == 2){
               value = StringFunctions.convertNullString(datensatz[1]);
            }else{
            // Wenn datensatz > 1, dann sind Attribute angehängt
               if (datensatz.length > 1){
                  for (int j = 1; j < datensatz.length; j++){
                     // Abfrage um Attribute mit gleichem key an den alten key anzuhängen
                     if (oldKey.equals(key) && checkAttribute) {
                        checkAttribute = false;
                        oldValue = StringFunctions.convertNullString(map.get(oldKey)).toString().trim();
                        sb.append(oldValue);
                        sb.append(trennzeichen);
                     }
                     sb.append(datensatz[j]);
                     // Wenn die Datensatzlänge dem nächsten Iterator entspricht wird kein Trennzeichen angefügt,
                     // da es sich um den letzten AttributeValue handelt.
                     if ((j+1) != datensatz.length){
                        sb.append(trennzeichen);
                     }
                  }
                  // StringBuffer wird als value gespeichert und ...
                  value = sb.toString();
                  // ... neu initialisiert.
                  sb = new StringBuffer();
               }else{
                  value = "";
               }
            }
            if (!value.isEmpty() || !value.equals("")){
               if (!key.equals(startNode)){
                  map.put(key, value);
                  oldKey = key;
                  oldValue = "";
                  checkAttribute = true;
               }
               // Wenn der nächste iterierte Datensatz dem startNode entspricht, wird die map der list hinzugefügt.
               if ((xmlList.get(i + 1).toString().split(trennzeichen)[0]).equals(startNode)){
                  hashlist.add(map);
                  map = new HashMap<String,String>();
               }
            }
         }
      }
      return hashlist;
   }

   /**
    * Datensätze der .xml Datei werden gespeichert. Zum Erhalt dieser entweder getHashMap, getList oder
    * (vorzugsweise) getListOfHashMaps aufrufen.
    * @param verzeichnis
    * @param fileName
    * @param firstNode   
    */
   public ParseXMLFile(String verzeichnis, String fileName, String firstNode) {
      ParseXMLFile.startNode = firstNode;
      Document doc = parseFile(verzeichnis + fileName);
      Node root = doc.getDocumentElement();
      getElements(root, firstNode);
      // add für getListOfHashMaps
      add(firstNode + trennzeichen + " ");
   }

   /**
    * Datensätze der .xml Datei werden gespeichert. Zum Erhalt dieser entweder getHashMap, getList oder
    * (vorzugsweise) getListOfHashMaps aufrufen.
    * @param file
    * @param firstNode       
    */
   public ParseXMLFile(File f, String firstNode) {
      this(f.getPath(), f.getName(), firstNode);
   }

   private final static String getElementValue(Node elem) {
      Node kid;
      if (elem != null){
         if (elem.hasChildNodes()){
            for (kid = elem.getFirstChild(); kid != null; kid = kid.getNextSibling()){
               if (kid.getNodeType() == Node.TEXT_NODE){
                  return kid.getNodeValue();
               }
            }
         }
      }
      return "";
   }

   //Rekursive Methode zum erhalt aller Nodes aus der .xml Datei
   private void getElements(Node node, String firstNode) {
      StringBuffer sb = new StringBuffer();
      String nodeName = node.getNodeName().trim();
      if (nodeName.equals(firstNode)){
         add(firstNode + trennzeichen);
         count++;
      }else{
         String nodeValue = getElementValue(node).trim();
         if (!nodeValue.equals("")){
            sb.append(nodeName + trennzeichen + nodeValue);
         }else{
            sb.append(nodeName);
         }
         NamedNodeMap attributes = node.getAttributes();
         for (int j = 0; j < attributes.getLength(); j++){
            Node attribute = attributes.item(j);
            // attributes adden
            if (!StringFunctions.convertNullString(attribute.getNodeValue()).equals("")){
               sb.append(trennzeichen);
               sb.append(attribute.getNodeName());
               sb.append(trennzeichen);
               sb.append(attribute.getNodeValue());
            }
         }
         add(sb.toString());
         //sb = new StringBuffer();
      }
      // Rekursion: Schreiben der Childs
      NodeList children = node.getChildNodes();
      for (int j = 0; j < children.getLength(); j++){
         Node child = children.item(j);
         if (child.getNodeType() == Node.ELEMENT_NODE){
            getElements(child, firstNode);
         }
      }
   }

   private Document parseFile(String fileName) {
      DocumentBuilder docBuilder;
      Document doc = null;
      DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
      docBuilderFactory.setIgnoringElementContentWhitespace(true);
      try{
         docBuilder = docBuilderFactory.newDocumentBuilder();
      }catch (ParserConfigurationException e){
         System.out.println("Wrong parser configuration: " + e.getMessage());
         return null;
      }
      File sourceFile = new File(fileName);
      try{
         doc = docBuilder.parse(sourceFile);
      }catch (SAXException e){
         System.out.println("Wrong XML file structure: " + e.getMessage());
         return null;
      }catch (IOException e){
         System.out.println("Could not read source file: " + e.getMessage());
      }
      return doc;
   }
}
```

Wie ihr seht ist dass alles zusammen sehr schlecht programmiert (wollte es irgendwie quick&dirty lösen) vorallem die wichtigste Methode die getListOfHashMaps(). Eigentlich funktioniert das meiste irgendwie, bis auf die complexTypes, da ich keine Abfrage drin hab ob das element komplex ist und somit überschreibe ich die gleichen tagNames von der Lieferadresse und von der Rechnungsadresse ...

So, kann mir jemand helfen da die complexTypes noch irgendwie rein zu bekommen oder kann mir jemand einen guten fertigen Helper empfehlen den ich verwenden könnte? 

Ich muss dieses Chaos irgendwie loswerden ^^
Dank im vorraus

€: update auf 1.6


----------



## Noctarius (21. Sep 2010)

Wieso musst du 1.4 programmieren? Das ist mehr als nur Outdated. Es gibt keinen Support und keine Patches mehr dafür und allgemein ist es eine Unsitte heute noch Software auf dieser Basis zu entwickeln.

Heute würde dich auch jeder komisch anschauen wenn du noch Software anbieten würdest die nur unter Windows95 läuft.


----------



## Shoox (21. Sep 2010)

Kundenwunsch ... kann leider nichts dagegen tun.


----------



## Noctarius (21. Sep 2010)

Würde ich dem Kunden die selbe Frage stellen. Und ich würde ihm vor allem erklären, dass es für Java 1.4 einfach keinen Support mehr gibt und seit Ewigkeiten auch Bugs bekannt sind und es nicht in seinem Sinne sein kann mit diesen zu leben.

Die Frage steht ja im Raum: Jetzt Geld ausgeben für etwas was dann in 2 - 3 Jahren eventuell zwangsweise ausgetauscht werden muss oder einmal richtig machen.
Immerhin ist es nur eine Frage der Zeit wie lange Oracle das 1.4 noch zum Download anbietet.

Meistens ist den Kunden sowas alles gar nicht wirklich klar. Erst auf erneutes explizites Hinweisen würde ich mir das sogar schriftlich vom Kunden absegnen lassen, dass er wider besserer Belehrung trotzdem Java 1.4 einsetzen will und du aus jeder Haftung ausgenommen bist in diesem Bezug.

Oft kommt auch das Argument: Wir haben noch Software die 1.4 braucht. Ja und? Dann packe ich eine 1.5 / 1.6 VM in den Programmfolder mit rein und passe nur für dieses Programm die Pfade an. Auch hier wissen Kunden zu 99% nicht, dass das machbar ist.


----------



## Tomate_Salat (21. Sep 2010)

Shoox hat gesagt.:


> Kundenwunsch ... kann leider nichts dagegen tun.



Da würde ich dem Kunden aber auch entschieden von abraten. Welche Vorteile bringt es bitte, auf einer veralteten Technologie zu entwickeln? Das aktuelle JRE ist kostenfrei, also ein upgrade von 1.4 auf 1.6 ist nicht mit weiteren Ausgaben verbunden.

MFG

Tomate_Salat


----------



## Shoox (21. Sep 2010)

Das weiß ich eh, dieser Kunde wird höchstwahrscheinlich auch in näherer Zukunft auf 1.6 updaten nur jz halt noch nicht. Doch ich stehe nicht in der Position solche Sachen zu entscheiden bzw habe ich gar keinen Kundenkontakt, wenn mein Chef sagt "Bitte mach das in 1.4, der Kunde wills halt so" dann muss ich das so akzeptieren ... leider.
Jedenfalls hab ich meine "wunderschöne" Klasse mit generics ausgestattet um demjenigen der mir helfen will ein bessere Basis zu bieten


----------



## Noctarius (21. Sep 2010)

Ab 1.5 hätte ich dir Lycia angeboten aber das Ding noch 1.4 fähig zu machen find ich absolut widerlich. DU kannst es eventuell mal mit RetroGuard versuchen abwärtskompatibel zu machen aber das wird vermutlich auf Grund der geänderten XML Basis nicht klappen.

Übrigens noch ein absolut entschiedener Grund gegen Java 1.4, die XML Implementierung ist absolut veraltet. Die unterstützt auch die aktuelle XML-Spec nicht. Ein Umbau ist später viel viel viel umständlicher.
Wenn dein Vorgesetzter den Kunden das machen lässt sollte auch der Vorgesetzte ersetzt werden. Neue Entwicklungen, besonders wenn diese in naher Zukunft aktualisiert werden sollen, auf 1.4 zu bauen gehört schwer verboten und die Personen die so etwas zulassen gesteinigt


----------



## Shoox (21. Sep 2010)

Steinigung ist eine gute Idee ^^
Ich werde mal ein paar Takte mit gewissen Leuten reden gehen, wenn alles andere zu kompliziert wäre.

Ok, trotzdem löst sich mein Problem dadurch nicht von Zauberhand auch mit 1.6 nicht. Lycia sieht wie eine richtig gute Sache aus, aber ich denke mir dass ich nicht so ein mächtiges Werkzeug brauche. Auslesen, in lists und hashmaps speichern muss doch auch "kleiner" gehen.


----------



## Tomate_Salat (21. Sep 2010)

ich weis nicht wie das bei deinem chef ist, aber wenn wir unserem Chef sagen: "die und die gewüschnte umsetzung bringt keine Vorteile, sonder nur nachteile z.B.: ..." dann ruft der der netterweise nochmal beim Kunden an und sagt ihm auch ganz genau, dass der Entwickler davon abrät mit der und der Begründung.

Insofern: rede doch nochmal mit deinem Chef.


----------



## Shoox (21. Sep 2010)

Hab grünes Licht für 1.6 bekommen. So, wieder alles zurück auf Anfang ^^


----------



## Tomate_Salat (21. Sep 2010)

Na geht doch^^, herzlichen glückwunsch.


----------



## Noctarius (21. Sep 2010)

Also ich denke schneller als mit Lycia wirst du es fast nicht hinbekommen alle Werte in eine List / Map zu stopfen. Das Problem ist einfach, dass du um das "Durchhangeln" des XML-Tree auch nicht so ohne weiteres herumkommst.

Wenn du ein XML Schema (XSD) hast solltest du eventuell überlegen JAXB oder EMF (wobei das Erste direkt in der JRE mit drin ist) nutzen magst. Dies liest dir die Subelemente auch direkt in Listen ein.

Bei Beiden bekommst du quasi POJOs raus (mit Annotations versehen). Die Klassen passend zum Schema erstellt dir JAXB mit Maven oder Ant Plugin automatisch.


----------



## Shoox (21. Sep 2010)

JAXB hört sich gut an (jup, ich hab ein xsd). Hab zwar noch nie damit gearbeitet, aber die javadoc existiert ja nicht umsonst  Danke mal für den Tipp, werde meine Resultate (und eventuell auftretende Problem) hier dann posten.


----------



## Noctarius (21. Sep 2010)

Schau dir direkt den Klassen-Generator an. Die Dinger von Hand zu machen wenn du ein Schema hast wäre Selbstmord. Außerdem mapped der Generator definitiv die Xml-Types richtig zum Schema ^^

Ich würde die Klassenerstellung auch in den Build-Prozess also Ant oder Maven mit einbinden, damit du sicher immer die aktuellsten Versionen der Klassen hast.


----------

