# Buch Metadaten aus XML Datei parsen



## MiMa (25. Apr 2019)

Hallo,
ich habe ein kleines Tool geschrieben welches eine ISBN an eine Datenbank sendet und erhalte dafür eine XML Datei mit allen Metadaten über das entsprechende Buch. Mit ein paar Datensätzen lief alles wunderbar. Jedoch ist mir aufgefallen, das manche Metadaten nicht wirklich korrekt sind.
Ich habe heraus gefunden, das ich manch mal mehr Informationen erhalte, als ich eigentlich wollte.
Bei Büchern bei denen alles stimmt, erhalte ich auch korrekt nur einmal die Daten über das Buch.
Bei Büchern mit Abweichungen der Metadaten habe ich festgestellt, das ich manchmal 2 bis dreifache Metadaten erhalte (Print, eBook, usw.)
Da ich mit SAX parse, bleiben dann die Daten des letzten Datensatzes gespeichert.
Da ich vorher nicht weiß wie viele Datensätze in der zurückgegebenen XML erhalte, ist es da überhaupt sinnvoll mit SAX zu parsen?
Danke
Mi


----------



## mihe7 (25. Apr 2019)

MiMa hat gesagt.:


> ist es da überhaupt sinnvoll mit SAX zu parsen?


Warum nicht? SAX bedeutet ja nicht, dass Du vorab wissen musst, wie viele Sätze du erhältst.


----------



## MiMa (26. Apr 2019)

Dann gib mir mal einen Tipp, wie ich denn vorgehen sollte.
Mit XML habe ich nicht viel Erfahrung.
SAX arbeitet ja sequentiell ab und verarbeitet die die Daten sofort.
Was mir einfallen würde wäre ein zusätzliches Objekt zu erstellen das alle Daten der empfangenen XML aufnimmt.
Dieses könnte mann dann verarbeiten.
Aber macht das nicht DOM so?


----------



## mihe7 (26. Apr 2019)

Genau, mit einem DOM-Parser wird aus einem XML-Dokument ein vollständiges, an XML ausgerichtetes, fest definiertes Objektmodell erzeugt. Mit SAX erzeugst Du, was Du willst. 

Während Du also bei DOM Objekte mit Document-/Element-/Node-Schnittstelle bekommst, aus denen Du ggf. Deine Domain-Objekte erzeugst, kannst Du mit SAX sofort die Domain-Objekte erstellen und zwar nur für die Ausschnitte der XML-Datei, die Du tatsächlich brauchst. Kurz: DOM ist im Handling (scheinbar) etwas einfacher, braucht dafür mehr Speicher.

Für eine konkrete Lösung fehlen noch ein paar Infos. Wie ist das XML strukturiert? Und wenn Du mehrere Metadaten für ein Buch erhältst, woher weißt Du, welche die richtigen sind?


----------



## MiMa (26. Apr 2019)

Ja genau das ist der springende Punkt.
Wenn ich in einem XML mit mehreren Datensätze erhalte, weis ich nicht genau welches die richtigen sind.
Es kann der erste, der letzte oder der in der Mitte sein.
Wenn ich eine ISBN sende, erhalte ich ebenfalls ISBNs zurück. Es ist dann der richtige Datensatz, wenn die zurückgelieferte ISBN mit der gesendeten ISBN identisch ist.

Beispiel: Ich sende eine 13 Stellige ISBN 978-3-658-02836-0 (Buch)
und erhalte dann
978-3-658-02835-0 für Buch mit Metadaten
978-3-658-02836-7 für eBook mit weiteren Metadaten

Aktuell speichert mein Tool dann den letzten Datensatz wobei die Metadaten dann alle vom eBook entsprechen.
Ich könnte dann ja prüfen ob die ISBN stimmt und wenn ja, dann parse mit SAX ansonsten nicht, aber die iSBN kommt in der XML Datei nicht zu Anfang sondern erst später.

Ich weiss nicht wie speicherintensiv DOM ist, kenne nur SAX. Aber bei einem 16 bzw 32 GB Arbeitsspeicher sollten keine Probleme auftauchen wenn es sich um 2 bis 4 Datensätze in einer XML Datei handeln.

Warscheinlich sollte ich mich doch mal mit DOM beschäftigen und den Parser ggfs neu programmieren. Mit SAX habe ich Wochen gebraucht, bis der so lief wie er es jetzt macht. Leider hatte ich nicht damit gerechnet, das es auch mehrere Bücher in einer XML Datei zurück gegeben werden könnten. Das war bei meinen 5 Test ISBNs nicht der Fall.


----------



## mihe7 (26. Apr 2019)

MiMa hat gesagt.:


> Ich könnte dann ja prüfen ob die ISBN stimmt und wenn ja, dann parse mit SAX ansonsten nicht, aber die iSBN kommt in der XML Datei nicht zu Anfang sondern erst später.


Wie sieht denn das XML aus? Kannst Du mal ein Beispiel posten (in Code-Tags, also [code=XML]Inhalt der XML-Datei[/code])?



MiMa hat gesagt.:


> Aber bei einem 16 bzw 32 GB Arbeitsspeicher sollten keine Probleme auftauchen wenn es sich um 2 bis 4 Datensätze in einer XML Datei handeln.


LOL, bei 2 bis 4 Datensätze nicht wirklich (wobei es natürlich auf die Größe der Datensätze ankommt). Da würde ich keinen Gedanken an irgendwas verschwenden sondern den für mich einfachsten Weg nehmen.


----------



## MiMa (26. Apr 2019)

Anbei eine Rückgabedatei mit nur einem Eintrag, die ich damals gespeichert hatte.
Am Anfang der XML steht schon die Angabe der Recordeinträge und wenn dort "1" für einen Eintrag steht, ist das unkompliziert da der Code dann wie gewünscht funktioniert.
Die Verwendung von SAX wäre mir am liebsten, da ich schon sehr viel Arbeit darin investiert habe und es schade darum wäre. Aufgrund von mangelndem Wissen in dieser Situation habe ich es dann leider lange vor mir her geschoben. Möchte das Problem aber jetzt endlich beheben.

Meine Aktuelle Situation sieht so aus, das ich mein Programmierprojekt auf das letzte und aktuellste prüfen muss, da ich durch einen Blitzeinschlag meinen iMac aus auch mein umfangreiches NAS Datensystem dabei verloren habe.
Zum Glück oder leider muss ich jetzt die vielen Netbeans Datensicherung durchgehen. 


```
<searchRetrieveResponse xmlns="http://www.loc.gov/zing/srw/">
    <version>1.1</version>
    <numberOfRecords>1</numberOfRecords>
    <records>
        <record>
            <recordSchema>MARC21-xml</recordSchema>
            <recordPacking>xml</recordPacking>
            <recordData>
                <record xmlns="http://www.loc.gov/MARC21/slim" type="Bibliographic">
                    <leader>00000pam a2200000 c 4500</leader>
                    <controlfield tag="001">989219313</controlfield>
                    <controlfield tag="003">DE-101</controlfield>
                    <controlfield tag="005">20171202073101.0</controlfield>
                    <controlfield tag="007">tu</controlfield>
                    <controlfield tag="008">080617s2008 gw ||||| |||| 00||||ger </controlfield>
                    <datafield tag="015" ind1=" " ind2=" ">
                        <subfield code="a">08,A46,0093</subfield>
                        <subfield code="z">08,N27,0049</subfield>
                        <subfield code="2">dnb</subfield>
                    </datafield>
                    <datafield tag="016" ind1="7" ind2=" ">
                        <subfield code="2">DE-101</subfield>
                        <subfield code="a">989219313</subfield>
                    </datafield>
                    <datafield tag="020" ind1=" " ind2=" ">
                        <subfield code="a">9783834805690</subfield>
                        <subfield code="c">kart. : EUR 19.90</subfield>
                        <subfield code="9">978-3-8348-0569-0</subfield>
                    </datafield>
                    <datafield tag="024" ind1="3" ind2=" ">
                        <subfield code="a">9783834805690</subfield>
                    </datafield>
                    <datafield tag="035" ind1=" " ind2=" ">
                        <subfield code="a">(DE-599)DNB989219313</subfield>
                    </datafield>
                    <datafield tag="035" ind1=" " ind2=" ">
                        <subfield code="a">(OCoLC)723982056</subfield>
                    </datafield>
                    <datafield tag="040" ind1=" " ind2=" ">
                        <subfield code="a">1245</subfield>
                        <subfield code="b">ger</subfield>
                        <subfield code="c">DE-101</subfield>
                        <subfield code="d">9999</subfield>
                    </datafield>
                    <datafield tag="041" ind1=" " ind2=" ">
                        <subfield code="a">ger</subfield>
                    </datafield>
                    <datafield tag="044" ind1=" " ind2=" ">
                        <subfield code="c">XA-DE-HE</subfield>
                    </datafield>
                    <datafield tag="082" ind1="0" ind2="4">
                        <subfield code="8">1\u</subfield>
                        <subfield code="a">005.1</subfield>
                        <subfield code="q">DE-101</subfield>
                        <subfield code="2">22/ger</subfield>
                    </datafield>
                    <datafield tag="083" ind1="7" ind2=" ">
                        <subfield code="a">004</subfield>
                        <subfield code="q">DE-101</subfield>
                        <subfield code="2">22sdnb</subfield>
                    </datafield>
                    <datafield tag="085" ind1=" " ind2=" ">
                        <subfield code="8">1\u</subfield>
                        <subfield code="b">005.1</subfield>
                    </datafield>
                    <datafield tag="090" ind1=" " ind2=" ">
                        <subfield code="a">b</subfield>
                    </datafield>
                    <datafield tag="100" ind1="1" ind2=" ">
                        <subfield code="0">(DE-588)136269087</subfield>
                        <subfield code="0">http://d-nb.info/gnd/136269087</subfield>
                        <subfield code="0">(DE-101)136269087</subfield>
                        <subfield code="a">Rimscha, Markus von</subfield>
                        <subfield code="e">Verfasser</subfield>
                        <subfield code="4">aut</subfield>
                    </datafield>
                    <datafield tag="245" ind1="0" ind2="0">
                        <subfield code="a">Algorithmen kompakt und verständlich</subfield>
                        <subfield code="b">Lösungsstrategien am Computer</subfield>
                        <subfield code="c">Markus von Rimscha</subfield>
                    </datafield>
                    <datafield tag="250" ind1=" " ind2=" ">
                        <subfield code="a">1. Aufl.</subfield>
                    </datafield>
                    <datafield tag="259" ind1=" " ind2=" ">
                        <subfield code="a">11</subfield>
                    </datafield>
                    <datafield tag="264" ind1=" " ind2="1">
                        <subfield code="a">Wiesbaden</subfield>
                        <subfield code="b">Vieweg + Teubner</subfield>
                        <subfield code="c">2008</subfield>
                    </datafield>
                    <datafield tag="300" ind1=" " ind2=" ">
                        <subfield code="a">VIII, 144 S.</subfield>
                        <subfield code="b">Ill., graph. Darst.</subfield>
                        <subfield code="c">24 cm</subfield>
                    </datafield>
                    <datafield tag="336" ind1=" " ind2=" ">
                        <subfield code="a">Text</subfield>
                        <subfield code="b">txt</subfield>
                        <subfield code="2">rdacontent</subfield>
                    </datafield>
                    <datafield tag="337" ind1=" " ind2=" ">
                        <subfield code="a">ohne Hilfsmittel zu benutzen</subfield>
                        <subfield code="b">n</subfield>
                        <subfield code="2">rdamedia</subfield>
                    </datafield>
                    <datafield tag="338" ind1=" " ind2=" ">
                        <subfield code="a">Band</subfield>
                        <subfield code="b">nc</subfield>
                        <subfield code="2">rdacarrier</subfield>
                    </datafield>
                    <datafield tag="490" ind1="0" ind2=" ">
                        <subfield code="a">Studium</subfield>
                    </datafield>
                    <datafield tag="490" ind1="0" ind2=" ">
                        <subfield code="a">Online plus</subfield>
                    </datafield>
                    <datafield tag="500" ind1=" " ind2=" ">
                        <subfield code="a">Literaturverz. S. 137 - 141</subfield>
                    </datafield>
                    <datafield tag="650" ind1=" " ind2="7">
                        <subfield code="0">(DE-588)4001183-5</subfield>
                        <subfield code="0">http://d-nb.info/gnd/4001183-5</subfield>
                        <subfield code="0">(DE-101)040011836</subfield>
                        <subfield code="a">Algorithmus</subfield>
                        <subfield code="2">gnd</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Algorithmus</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Rekursiv</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Genetisch</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Heuristisch</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Problemlösungsstrategie</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Künstliche Intelligenz</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-FS)Künstlich</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-PF)BC: Paperback</subfield>
                    </datafield>
                    <datafield tag="653" ind1=" " ind2=" ">
                        <subfield code="a">(VLB-WN)1632: HC/Informatik, EDV/Informatik</subfield>
                    </datafield>
                    <datafield tag="689" ind1="0" ind2="0">
                        <subfield code="0">(DE-588)4001183-5</subfield>
                        <subfield code="0">http://d-nb.info/gnd/4001183-5</subfield>
                        <subfield code="0">(DE-101)040011836</subfield>
                        <subfield code="D">s</subfield>
                        <subfield code="a">Algorithmus</subfield>
                    </datafield>
                    <datafield tag="689" ind1="0" ind2=" ">
                        <subfield code="5">DE-101</subfield>
                        <subfield code="5">DE-101</subfield>
                    </datafield>
                    <datafield tag="850" ind1=" " ind2=" ">
                        <subfield code="a">DE-101a</subfield>
                        <subfield code="a">DE-101b</subfield>
                    </datafield>
                    <datafield tag="856" ind1="4" ind2="2">
                        <subfield code="m">B:DE-101</subfield>
                        <subfield code="q">application/pdf</subfield>
                        <subfield code="u">http://d-nb.info/989219313/04</subfield>
                        <subfield code="3">Inhaltsverzeichnis</subfield>
                    </datafield>
                    <datafield tag="925" ind1="r" ind2=" ">
                        <subfield code="a">ra</subfield>
                    </datafield>
                </record>
            </recordData>
            <recordPosition>1</recordPosition>
        </record>
    </records>
    <nextRecordPosition>2</nextRecordPosition>
    <echoedSearchRetrieveRequest>
        <version>1.1</version>
        <query>NUM=9783834805690</query>
        <xQuery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
        <recordSchema>MARC21-xml</recordSchema>
    </echoedSearchRetrieveRequest>
    <extraResponseData>
        <accountOf xmlns="">SRU</accountOf>
    </extraResponseData>
</searchRetrieveResponse>
```


----------



## mihe7 (26. Apr 2019)

Im Feld 020 a findet sich ja die ISBN. Ich weiß jetzt nicht, was Du wie ausliest, aber im Allgemeinen funktioniert es so, dass Du die Daten aus eines Element-Baums liest und in Objekten zwischenspeicherst. Beim Schließen des record-Elements brauchst Du ja nur zu prüfen, ob die ISBN passt. Dann weißt Du, ob der record der gesuchte ist.

Du kannst auch einfach einen ISBN-String deklarieren, den Du beim Öffnen eines record-Elements auf einen leeren String zurücksetzt. Dann merkst Du Dir in einem Flag, ob Du Dich gerade in einem datafield mit tag 020 befindest, bzw. in einem subfield mit code "a" des datafields mit tag 020. Dort setzt Du dann den ISBN-String. Beim schließen des record-Elements kannst Du dann auf Gleichheit mit der gesuchten ISBN prüfen.


----------



## MiMa (26. Apr 2019)

Aufgrund der Schnittstellendokumentation der Deutschen Nationalbibliothek weiss ich welche Metadaten sich in den entsprechenden tags und codes befinden. Ich prüfe diese und schreibe die Inhalte dann in ein Java Objekt (buchObjekt). 
Wenn die ISBN zum vergleich ansteht, sind bereits Zeilen verarbeitet, geprüft und gespeichert worden.
Meinst du das wenn die ISBN nicht übereinstimmt, dass dann alle bereits gelesen und gespeicherte Metadaten verworfen werden sollen und alle weiteren ignorieren bis der neue Metadaten Record anfängt?

Das würde auch bedeuten wenn beim ersten Record die ISBN stimmt und alle Daten gespeichert wurden müssen alle nachträglichen Records ignoriert werden.


----------



## mihe7 (26. Apr 2019)

MiMa hat gesagt.:


> Meinst du das wenn die ISBN nicht übereinstimmt, dass dann alle bereits gelesen und gespeicherte Metadaten verworfen werden sollen und alle weiteren ignorieren bis der neue Metadaten Record anfängt?


Ja, wobei "speichern" hier nur das Abbilden auf ein Objekt meint. 



MiMa hat gesagt.:


> Ich prüfe diese und schreibe die Inhalte dann in ein Java Objekt (buchObjekt).


Das ist doch optimal.


----------



## MiMa (26. Apr 2019)

Danke für deine Hilfe,
ich werde das Projekt suchen, zurückspeichern und dann versuchen es zu implementieren.


----------



## MiMa (2. Jun 2020)

Ich muss das Thema noch einmal hervor bringen.
Manchmal erhalte ich zu einer ISBN mehrere Werte bzw mehrere Bücher.
Das Bedeutet, das es dann in einer XML mehrere Bücher zurück gegeben werden wie Werte ir IDBN der Printausgabe, des epub eBooks und zum PDF.
Vor dem Parsen erkenne ich es dann wen die Number of Records mehr als 1 enthalten.

```
<numberOfRecords>3</numberOfRecords>
```
Ich kann dann in einer Schleife dann 3 Buchobejkte erzeugen und die Werte dann in das jeweilige Objekt stecken.
Es werden dann die Daten behalten, die zur Datei passen (epub, pdf).

Das größte Problem, welches ich noch nicht wirklich gelöst habe sind die Apmersand Zeichen die den SAX Paser anweisen Werte zu teilen, die aber zusammengehören und damit Datenverlust verursachen.

Es kam schon mal die Idee, das zurückgegeben XML in ein Objekt zu stecken, die gefährlichen Zeichen zu ersetzen und dann zu parsen.
Nur wie realisiere ich das?

Über hilfe würde ich mich sehr freuen.
Danke
Mi


----------



## mihe7 (2. Jun 2020)

MiMa hat gesagt.:


> Das größte Problem, welches ich noch nicht wirklich gelöst habe sind die Apmersand Zeichen die den SAX Paser anweisen Werte zu teilen, die aber zusammengehören und damit Datenverlust verursachen.


Wovon sprichst Du?


----------



## MiMa (2. Jun 2020)

Ach ich habe es hier wiedergefunden.
Kaufmännisches & zerstückelt Datensatz
wirklich verstanden habe ich das aber nicht?!


----------



## mihe7 (2. Jun 2020)

In XML ist & ein reserviertes Zeichen, das daher escaped werden muss. Ein XML-Datei, die z. B. die Zeichenkette "C&A" enthält, ist kein gültiges XML. Richtig wäre z. B. "C&amp;A" oder "C&#038;A" oder "C&#x26;A".


----------



## MiMa (2. Jun 2020)

Das bedeutet ich müsste es vor dem parsen überprüfen und mit replace korrigieren und dann parsen?


----------



## mihe7 (2. Jun 2020)

Die Frage ist: warum hast Du XML-Dateien, die keine sind?!?


----------



## MiMa (2. Jun 2020)

Die XML erhalte ich als Rückmeldung von der einer Datenbank. 
Das ist mir aber auch durch Zufall aufgefallen weil es nicht bei jedem Datensatz auftritt.
Damals musste ich herausfinden warum meine zurückgegeben Daten falsch waren und habe dann heraus gefunden das es auch schon mal vorkommt das es mehr als ein Buch zurückgegeben wird. Es wurde dann immer de zuletzt geparste Datensatz gespeichert.
In diesem Zusammenhang habe ich auch das Problem mit dem Ampersand gefunden.
Aktuell teste ich mit 12 verschiedenen ISBNs und Fehler traten auf nachdem ich mehrere Hundert ISBNs zum testen nahm.
Gibt es Methoden ein zurück gegebenes XML zu prüfen bevor es geparst wird?
Dann müsste ich nur XML untersuchen die nicht gültig sind.


----------



## mihe7 (2. Jun 2020)

Mir ist zwar immer noch nicht klar, warum Du invalides XML aus einer Datenbank erhältst, aber Du kannst z. B. einen FilterInputStream implementieren, der derlei Fehler halbwegs behebt.


----------



## MiMa (2. Jun 2020)

Ist mir auch unklar, aber wenn es so ist muss man Vorkehrungen treffen.


----------



## mihe7 (2. Jun 2020)

MiMa hat gesagt.:


> Ist mir auch unklar, aber wenn es so ist muss man Vorkehrungen treffen.


Mal aus Interesse: wie rufst Du die Daten ab und welches DBMS verwendest Du?


----------



## MiMa (8. Jun 2020)

Die Daten rufe ich von der Deutschen National Bibliothek ab (Algorithmen kompakt und verständlich).
Das ganze wird über einen langen String abgewickelt, der eine URL zur Schnittstelle mit ISBN und Zugangspasswort aufbaut.

```
String sruAbfrage = "https://services.dnb.de/sru/dnb?version=1.1&operation=........
```
Geparst wird dann durch das zurückgegebene XML der marc21 Schnittstelle.

```
...
// Das Parsen wird gestartet
xmlReader.parse(sruAbfrage);
...
```
Im Log sehe ich dann das Zeichen empfangen werden, die den XML Eintrag zerstückeln.
Es werden 100a viermal ausgeführt obwohl es nur ein Eintrag ist.

Wenn ich die URL für die Abfrage im Chrome Browser eingebe, dann erhalte ich genau die gleichen Informationen, wie im Java Programm.

Die gleiche URL im Firefox Browser sieht der Datenabschnitt anders aus

Dann habe ich auf der Seite der Deutschen Nationalbibliothek den Datensatz über den Browser herunter geladen.

Im XML Editor sah der Datensatz dann ganz anders aus.

wie kommt es das es so unterschiedlich ist?
Das Encoding in der Netbeans IDE ist auf UTF-8 gesetzt.
Muss vor der Abfrage des Datensatzes noch irgendetwas definiert werden?

Danke
Mi


----------



## mihe7 (8. Jun 2020)

Naja, das eine ist die XML-kodierte Darstellung, das andere die dekodierte. Ich weiß nicht, was Du da betreibst, aber das XML ist natürlich valide und kann ganz normal verarbeitet werden:


```
import java.net.URL;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

public class Test {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://d-nb.info/989219313/about/marcxml");
        DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = f.newDocumentBuilder();
        Document document = builder.parse(url.openStream());

        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StreamResult result = new StreamResult(System.out);
        DOMSource source = new DOMSource(document);
        transformer.transform(source, result);

        System.out.println("\n\n---------- Autor ---------\n");
        NodeList nl = document.getElementsByTagName("subfield");
        for (int i = 0, n = nl.getLength(); i < n; i++) {
            Element elem = (Element) nl.item(i);
            String value = elem.getAttribute("code");
            if ("a".equals(value)) {
                String text = elem.getTextContent();
                if (text != null && text.startsWith("Rims")) {
                    System.out.println(text);
                }
            }
        }
    }
}
```


----------



## LimDul (8. Jun 2020)

Machst du mit dem String zwischen Abrufen per https und an den Sax-Parser übergeben noch irgendwas damit?


----------



## MiMa (8. Jun 2020)

Nein, dazwischen mache ich nichts.
Sollte ich noch etwas dazwischen machen?


----------



## LimDul (8. Jun 2020)

Ne, dazwischen solltest du auch nichts tun 

- Wie rufst du dass über https ab mit welcher Bibliothek?
- Wie erzeugst du den SAXParser?

Kannst du den Code mal zeigen? Irgendwo muss eine Transformation greifen, die du nicht haben willst.


----------



## mihe7 (8. Jun 2020)

MiMa hat gesagt.:


> Nein, dazwischen mache ich nichts.
> Sollte ich noch etwas dazwischen machen?


Nein. Zeig mal mehr Code. Da darf es keine Fehler geben, weil die von der DNB erzeugten XML valide sind.


----------



## MiMa (8. Jun 2020)

Das ist die Methode zum Abfragen und parsen.

```
public static Buch parseISBN(Buch buch)
            throws ParserConfigurationException, SAXException, IOException {
        LOG.info("Die ISBN wird durch Marc21 über die XML Datenbank der DNB geparst");
        String isbn = buch.getIsbn();
        // Zusammenstellung des Abfrage-Strings für die DNB Datenbank
        String sruAbfrage = "https://services.dnb.de/sru/dnb?version=1.1&operation=searchRetrieve&query=NUM%3D"
                + isbn + "&recordSchema=MARC21-xml&accessToken=" + Einstellungen.getDnbZugangspasswort();
        LOG.info("Suchabfrage: " + sruAbfrage);

        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
        saxFactory.setNamespaceAware(true);

        SAXParser saxParser = saxFactory.newSAXParser();
        XMLReader xmlReader = saxParser.getXMLReader();

        // Der DNBmarc21 ContentHandler wird übergeben
        xmlReader.setContentHandler(new DNBmarc21(buch));

        // Das Parsen wird gestartet
        xmlReader.parse(sruAbfrage);

        // Erstellt einen Handler für DNBmarc21
        DNBmarc21 handler = new DNBmarc21(buch);

        // Gibt das geparste Buch-Objekt zurück
        return handler.buchParser;
    }
```


----------



## mihe7 (8. Jun 2020)

Funktioniert genauso (hier mal mit & im Titel):

```
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class Test {
    public static void main(String[] args) throws Exception {
        String url = "https://d-nb.info/1209389347/about/marcxml";
        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
        saxFactory.setNamespaceAware(true);

        SAXParser saxParser = saxFactory.newSAXParser();
        XMLReader xmlReader = saxParser.getXMLReader();
        xmlReader.setContentHandler(new DefaultHandler() {
            boolean inTag = false;
            StringBuilder b;

            @Override
            public void characters(char[] ch, int start, int length) {
                if (inTag) {
                    b.append(new String(ch, start, length));
                }
            }
            @Override

            public void startElement(String uri, String localName, String qName, Attributes attributes) {
                if (qName.equals("datafield") && "245".equals(attributes.getValue("tag"))) {
                    inTag = true;
                    b = new StringBuilder();
                }
            }
            @Override
            public void endElement(String uri, String localName, String qName) {
                if (inTag && qName.equals("datafield")) {
                    inTag = false;
                    System.out.println(b.toString());
                }
            }
        });

        xmlReader.parse(url);
    }
}
```


----------



## MiMa (8. Jun 2020)

Habe gerade eine Information vom Schnittstellen-Service der DNB erhalten.
Die DNB liefert die Sonderzeichen für die Codierung in UTF-8 decomposed.

Passt sich Java und SAX da an oder müssen irgendwelche Schritte noch dazwischen gemacht werden?
Wenn es da zwei Möglichkeiten gibt, muss man den Parser entsprechend anpassen?


----------



## mihe7 (8. Jun 2020)

MiMa hat gesagt.:


> Die DNB liefert die Sonderzeichen für die Codierung in UTF-8 decomposed.


Das spielt keine Rolle, denn das Encoding ist im Header angegeben: 
`<?xml version="1.0" encoding="UTF-8"?>`


----------



## MiMa (8. Jun 2020)

Und wo liegt jetzt das Problem?


----------



## LimDul (8. Jun 2020)

Zeig mal deinen Contenthandler. Was ich übrigens schräg finde in deinem Code , du übergibst einen ContentHandler an den XML REader - und ein paar Zeilen drunter erstellst du noch einen, wovon du was zurückgibst. Das sieht auf den ersten Blick falsch aus.


----------



## MiMa (8. Jun 2020)

Naja was soll ich sagen, so oft habe ich noch nicht gemacht und den Code habe ich vor ein paar Jahren geschrieben. Jetzt versuche ich halt das Problem noch zu lösen. Ich mache das nicht beruflich und möchte immer etwas neues Lernen und umsetzen.
Für Kritik und Verbesserungsvorschläge bin ich sehr dankbar, zumal ich es auch gerne auf richtige weise lösen möchte.

```
public class DNBmarc21 extends DefaultHandler {

    private String bufferNummer; 
    private String bufferCode; 
    private boolean eintrag = false;
    private int ZahlEintrag;

    // Erstellt ein Parser-Objekt für die Buchabfragen
    Buch buchParser = new Buch();

    public DNBmarc21(Buch buch) {
        this.buchParser = buch;
    }
  
    public static String getValueFromAttribute(Attributes attr, String attributeName) {
            for (int i = 0; i < attr.getLength(); i++) {
            if (attr.getLocalName(i).equals(attributeName)) {
                return attr.getValue(i);
            }
        }
        return null;
    }
  
    public void startDocument() throws SAXException {
        LOG.info("\n\nBeginn der XML-Auswertung\n");
    }

    public void startElement(String namespaceURI, String localName, String qName, Attributes attr)
            throws SAXException {
        if (localName.equals("numberOfRecords")) {
            eintrag = true;
        } else if (localName.equals("datafield")) {
            this.bufferNummer = DNBmarc21.getValueFromAttribute(attr, "tag");
        } else if (localName.equals("subfield")) {
            this.bufferCode = DNBmarc21.getValueFromAttribute(attr, "code");

        }
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException {
        String zeichenkette = new String(ch, start, length);
      
        if (eintrag == true){
            this.ZahlEintrag = Integer.parseInt(zeichenkette);
            eintrag = false;
            LOG.info("Anzahl der Einträge    : " + this.ZahlEintrag);
        } else if ("020".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setEan(zeichenkette);
                LOG.info("EAN                    : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("c".equals(this.bufferCode)) {
                buchParser.setBezugsbedingung(zeichenkette);
                LOG.info("Bezugsbedingung        : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("9".equals(this.bufferCode)) {
                buchParser.setIsbn(zeichenkette);
                LOG.info("ISBN                   : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("041".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setSpracheISO(zeichenkette);
                LOG.info("Sprache ISO            : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("100".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setVerfasser1(zeichenkette);
                LOG.info("Verfasser 1 (FamN, VoN): " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("c".equals(this.bufferCode)) {
                buchParser.setUntertitel(zeichenkette);
                LOG.info("Untertitel              : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("245".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setTitel(zeichenkette);
                LOG.info("Titel                  : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("b".equals(this.bufferCode)) {
                buchParser.setUntertitel(zeichenkette);
                LOG.info("Untertitel             : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("c".equals(this.bufferCode)) {
                buchParser.setVerfasser2(zeichenkette);
                LOG.info("Verfasser 2            : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
                splitteAutoren(zeichenkette);
            }
        } else if ("250".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                zeichenkette = Buch.transformiereAuflage(zeichenkette);
                buchParser.setAuflage(zeichenkette);
                LOG.info("Auflage                : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("264".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setErscheinungsOrt(zeichenkette);
                LOG.info("Erscheinungsort        : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("b".equals(this.bufferCode)) {
                buchParser.setVerlagName(zeichenkette);
                LOG.info("Verlagsname            : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("c".equals(this.bufferCode)) {
                buchParser.setErscheinungsJahr(Integer.parseInt(zeichenkette));
                LOG.info("Erscheinungsjahr       : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("300".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.setUmfang(zeichenkette);
                LOG.info("Umfang                 :" + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("b".equals(this.bufferCode)) {
                buchParser.setIllustration(zeichenkette);
                LOG.info("Illustration           : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("c".equals(this.bufferCode)) {
                buchParser.setMasse(zeichenkette);
                LOG.info("Masse                  : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("490".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.addIsbd(zeichenkette);
                LOG.info("ISBD                   : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("650".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.addSachschlagwort(zeichenkette);
                LOG.info("Sachschlagwort         : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("653".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
            }
        } else if ("689".equals(this.bufferNummer)) {
            if ("a".equals(this.bufferCode)) {
                buchParser.addSchlagwort2(zeichenkette);
                LOG.info("Schlagwort 2           : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            }
        } else if ("856".equals(this.bufferNummer)) {
            if ("u".equals(this.bufferCode)) {
                buchParser.setLink(zeichenkette);
                LOG.info("Link                   : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } else if ("3".equals(this.bufferCode)) {
                buchParser.setLinkText(zeichenkette);
                LOG.info("Link Text              : " + zeichenkette);
                LOG.info("Tag Code               : " + bufferNummer + " " + bufferCode);
            } // if-else
        } // if-else
    }
  
    public void endElement(String namespaceURI, String localName, String qName)
            throws SAXException {
        if (localName.equals("datafield")) {
            this.bufferNummer = null;
        } else if (localName.equals("subfield")) {
            this.bufferCode = null;
        }
    }
  
    public void endDocument()
            throws SAXException {
        LOG.info("\n");
        LOG.info("EAN                    : " + buchParser.getEAN());
        LOG.info("Bezugsart              : " + buchParser.getBezugsbedingung());
        LOG.info("ISBN                   : " + buchParser.getIsbn());
        LOG.info("Sprachecode            : " + buchParser.getSpracheISO());
        LOG.info("Verfasser 1            : " + buchParser.getVerfasser1());
        LOG.info("Verfasser 2            : " + buchParser.getVerfasser2());
        LOG.info("Buchtitel              : " + buchParser.getTitel());
        LOG.info("Titelzusatz            : " + buchParser.getUntertitel());
        LOG.info("Auflage                : " + buchParser.getAuflage());
        LOG.info("Erscheinungsort        : " + buchParser.getErscheinungsOrt());
        LOG.info("Verlagname             : " + buchParser.getVerlagName());
        LOG.info("Erscheinungsjahr       : " + buchParser.getErscheinungsJahr());
        LOG.info("Umfang                 : " + buchParser.getUmfang());
        LOG.info("Illustration           : " + buchParser.getIllustration());
        LOG.info("Maße                   : " + buchParser.getMasse());

        for (int i = 0; i < buchParser.getIsbdZahl(); i++) {
            LOG.info("Die ISBD ist           : " + buchParser.getIsbd(i));
        }

        for (int i = 0; i < buchParser.getSachschlagwortZahl(); i++) {
            LOG.info("Sachschlagwort         : " + buchParser.getSachschlagwort(i));
        }

        for (int i = 0; i < buchParser.getSchlagwort1Zahl(); i++) {
            LOG.info("Schlagwort 1           : " + buchParser.getSchlagwort1(i));
        }
        for (int i = 0; i < buchParser.getSchlagwort2Zahl(); i++) {
            LOG.info("Schlagwort 2           : " + buchParser.getSchlagwort2(i));
        }

        for (Person person : buchParser.getAutorenListe()) {
            LOG.info("Autor Vorname  : " + person.getVorname());
            LOG.info("Autor Nachname : " + person.getNachname());
        }

        LOG.info("Link                   : " + buchParser.getLink());
        LOG.info("Link Text              : " + buchParser.getLinkText());

        LOG.info("\n\nEnde der XML-Auswertung\n");
        LOG.info("Das Dokumentende wurde erreicht");
    } 
}
```


----------



## LimDul (8. Jun 2020)

Dein problem dürfte die Methode https://docs.oracle.com/javase/7/do...tentHandler.html#characters(char[], int, int) sein.



> The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous character data in a single chunk, or *they may split it into several chunks*; however, all of the characters in any single event must come from the same external entity so that the Locator provides useful information.



Du erhälst nicht den gesamten String garantiret in einem Aufruf, sondern an den Sonderzeichen ggf. gesplittet und musst den zusammembauen


----------



## MiMa (8. Jun 2020)

Das bedeutet, das erstmal geprüft werden muss ob Sonderzeichen enthalten sind und wenn ja, dann die Aufsplittungen zusammenbauen?
Ich bekomme ja nicht mal die richtigen Zeichen angezeigt?
Anstatt "&#152;" bekomme ich ein Quadrat angezeigt, wobei ich nicht sagen kann ob das Quadrat decompessed ist und "&#152;" der compressed?
Ich weis nicht mal was ein Browser oder XMLEditor anzeigt? compressed, decompressed?
Zumindest weis ich jetzt keinen Ansatz wie ich das innerhalb des Parsers machen soll?
In der von geladenen XML waren die Sonderzeichen "&#152;von&#156;" enthalten.
Ich habe nichts zu diesen codes finden können und kann demnach nicht sagen ob es sich tatsächlich um Leerzeichen handeln??


----------



## MiMa (8. Jun 2020)

LimDul hat gesagt.:


> Was ich übrigens schräg finde in deinem Code , du übergibst einen ContentHandler an den XML REader - und ein paar Zeilen drunter erstellst du noch einen, wovon du was zurückgibst. Das sieht auf den ersten Blick falsch aus.


Das habe ich mir näher angeschaut und auch den zweiten entfernt.
Danke für den Tipp.


----------



## MiMa (8. Jun 2020)

Ich habe mal foldenden Code in die Character Methode eingebaut

```
if (zeichenkette.contains("&")) {
            System.out.println("Es wurde ein Sonderzeichen erkannt");
        } else if...
```
Und es wurde nicht ausgegeben.
Dann habe ich es mit dem quadratischen Zeichen versucht und dann ging es?
Das ist doch nichts von XML? Wie kommt das denn da rein?


----------



## LimDul (8. Jun 2020)

Du musst nicht auf Sonderzeichen überprüfen - sondern Start & End werte des Methoden aufrufs vergleichen.


----------



## MiMa (8. Jun 2020)

Was genau soll ich denn da prüfen, bzw vergleichen?


----------



## mihe7 (8. Jun 2020)

Schau Dir mein Beispiel aus #29 an. Du musst in characters Zwischenspeichern, bis das Ende des Tags erreicht wurde. Dann kannst Du den String im Ganzen weiterverarbeiten.


----------



## MiMa (8. Jun 2020)

mihe7 hat gesagt.:


> Schau Dir mein Beispiel aus #29 an. Du musst in characters Zwischenspeichern, bis das Ende des Tags erreicht wurde. Dann kannst Du den String im Ganzen weiterverarbeiten.


Danke, das werde ich mir in Ruhe ansehen.
Ich weis jetzt auch was die Codes in der xml der DNB bedeuten, die ich in all den Tabellen nicht finden konnte.
Der Code "&#152;" ist die Anfangsmarkierung für nicht sortierbare Zeichen.
Der Code "&#156;" ist die Endmarkierung für nicht Sortierbare Zeichen.

Mich würde es aber dennoch sehr interessieren warum bei der Datenabfrage diese Quadratischen Kästchen zurück zurück gegeben werden anstatt die Markierungs-Codes?

Danke
Mi


----------



## mihe7 (8. Jun 2020)

MiMa hat gesagt.:


> Mich würde es sehr interessieren warum bei der Datenabfrage diese Quadratischen Kästchen zurück zurück gegeben werden anstatt die Markierungs-Codes?


Das sind Unicode-Zeichen: &#152; ist das Zeichen mit Codepoint 152 und &#156; ist das Zeichen mit Codepoint 156, s. https://www.codetable.net/decimal/152 und https://www.codetable.net/decimal/156 (kennzeichnenderweise heißen die Zeichen "Start of String" und "String Terminator")

Je nach verwendetem Zeichensatz und verwendeter Schriftart kann das Zeichen gar nicht dargestellt werden. Da kommen dann ggf. Phantasiezeichen raus, z. B. weil der verwendete Zeichensatz keine Entsprechung hat oder an der Stelle in der verwendeten Schriftart ein anderes Zeichen (z. B. eine Box) steht.


----------



## MiMa (8. Jun 2020)

Kann man das denn Kontrollieren?
Einen Zeichensatz wählen und in Java und SAX verwenden, der weitestgehend alles darstellen kann um diese Probleme zu vermeiden?


----------



## mihe7 (8. Jun 2020)

MiMa hat gesagt.:


> Kann man das denn Kontrollieren?
> Einen Zeichensatz wählen und in Java und SAX verwenden, der weitestgehend alles darstellen kann um diese Probleme zu vermeiden?


Jein. In Java selbst musst Du zunächst einmal gar nichts machen, weil Java intern Unicode (UTF-16) verwendet. Das Einlesen von Dateien mit einem UTF-8-Encoding wird also immer funktionieren. 

Das Problem ist lediglich die Ausgabe. 

Wenn Du eine Windows-Shell aufmachst, dürfte die CP1252 als Zeichensatz verwenden (1 Byte/Zeichen). Es ist natürlich klar, dass eine Ausgabe beliebiger Unicode-Zeichen dort nicht funktioniert. Schreibst Du in eine Datei, kannst Du das gewünschte Encoding/den Zeichensatz angeben. Wenn Du also eine UTF-8-Datei schreibst und per `type dateiname` in der Windows-Shell ausgibst, wirst Du mit hoher Wahrscheinlichkeit falsche Zeichen (z. B. bei Umlauten) sehen.

Bei der grafischen Ausgabe innerhalb von Java besteht dagegen nur das Problem, dass die gewählte Schriftart die gewünschten Zeichen evtl. nicht enthält. Es gibt verschiedene Schriftarten auch als "Unicode-Font", die eben mehr Zeichen enthält.


----------



## MiMa (9. Jun 2020)

Die Vorgehensweise in Post #29 hat es mir gut verdeutlicht.
Meine Implementation kam ohne den booleschen Parameter "inTag" aus.

Nun verfüge ich über den zusammengesetzten Autorenname.

Allerdings weis ich nicht wie ich die Steuerzeichen (Quadrate) entfernen kann?
Ich kann halt nur etwas manipulieren, wenn ich Kenntnis über das zu manipulierende Element habe.


----------



## mihe7 (9. Jun 2020)

MiMa hat gesagt.:


> Allerdings weis ich nicht wie ich die Steuerzeichen (Quadrate) entfernen kann?


Du könntest z. B: in characters() unerwünschte Zeichen rausfiltern, also z. B. etwa in der Form:

```
static final char START_OF_STRING = '\u0098';
static final char STRING_TERMINATOR = '\u009c';

public void characters(char[] ch, int start, int length) {
    for (int i = 0; i < length; i++) {
        char cur = ch[start+i];
        if (cur != START_OF_STRING && ch != STRING_TERMINATOR) {
            b.append(cur);
        }
    }
}
```


----------



## MiMa (9. Jun 2020)

Ich dachte mir schon das es darauf hinaus läuft.
Es wird in der Schleife die einzelnen Zeichen durchlaufen und wenn es nicht die beiden oben aufgeführten Zeichen sind, wird es am StringBbuilder Objekt angehängt. Ist mir schon klar.
Du hast jetzt die Werte u0098 und u009c angegeben. Wenn man diese nicht kennt, kann man die auch nicht manipulieren.
Um es selbst heraus finden zu lassen, ist es wahrscheinlich zu aufwändig?!?
Vielleicht mit einer Liste, mit möglichen Zeichen die vorkommen könnten?


----------



## mihe7 (9. Jun 2020)

MiMa hat gesagt.:


> Du hast jetzt die Werte u0098 und u009c angegeben. Wenn man diese nicht kennt, kann man die auch nicht manipulieren.
> Um es selbst heraus finden zu lassen, ist es wahrscheinlich zu aufwändig?!?


Die Werte habe ich angegeben, weil die im XML standen: &#152; ist der Unicode Codepoint 152 gemeint, wobei die 152 dezimal ist. In der Java-Schreibweise (\uxxxx) werden Hexadezimalzahlen verwendet. 152 dezimal ist 98 hexadezimal (9*16+8 = 152), daher \u0098.

Du kannst aber, wie Du schon schreibst, auch den umgekehrten Weg gehen und nur bestimmte Zeichen zulassen. Wenn Du beispielsweise nur Buchstaben, Zahlen, Whitespace und z. B. "&" haben willst, änderst Du die Bedingung halt zu `Character.isLetterOrDigit(cur) || Character.isWhitespace(cur) || cur == '&'`
oder wenn Du mehrere Sonderzeichen angeben willst, kannst Du auch einen String mit den Zeichen definieren und dann per indexOf abfragen, ob das aktuelle Zeichen darin enthalten ist:

```
static final String SYMBOLS = "$'%\"/&()[]?.,#-+";
...
if (Character.isLetterOrDigit(cur) || Character.isWhitespace(cur) || SYMBOLS.indexOf(cur) != -1)
```
Möglichkeiten gibt es viele.


----------

