Übung: Erstellung einer Document-Type-Definition für XML-Rechnungen

https://bildung.social/@oerinformatik/111980708596828078

https://oer-informatik.de/xml-dtd-uebung

tl/dr; (ca. 20-30 min Bearbeitungszeit): Für eine XML-Rechnung soll Schritt für Schritt eine Document-Type-Definition erstellt werden, die einen relativ großen Funktionsumfang der DTD enthält. Geeignet zur Vertiefung des XML-DTD-Wissens in Kombination mit dem DTD-Artikel. (Zuletzt geändert am 26.02.2024)

Dieser Artikel ist Bestandteil einer Serie zu den XML-Grundlagen:

Sämtliche Aufgaben lassen sich mit einem Tool wie dem Online XML-Validator von truugo.com überprüfen.

Aufgabenstellung der Gesamtrechnung

Rechnungen sollen als XML-Datei exportiert werden. Hierzu liegt ein XML-Beispielauszug vor, für den in kleinen Schritten eine Document-Type-Definition (DTD) erstellt werden soll.

Die Gesamtdatei sieht folgendermaßen aus, wird aber gleich in kleineren Schritten realisiert:

<?xml version='1.0' encoding='UTF-8'?>

<rechnung nr="1234" datum="12.01.2024">
    <absender>
        <adresse>
            <firmenname>Baschira GmbH</firmenname>
            <postfach>Postfach 12344</postfach>
            <plz>10248</plz>
            <ort>Berlin</ort>
            <telefon>0124 / 6567891</telefon>
            <telefon>0234 / 7567894</telefon>
        </adresse>
    </absender>
    <empfaenger>
        <adresse kundennummer="1234">
            <vorname>Max</vorname>
            <nachname>Mustermann</nachname>
            <namenszusatz>3. OG links</namenszusatz>
            <strasse>Hauptstraße</strasse>
            <hausnr>23</hausnr>
            <plz>12345</plz>
            <ort>Berlin</ort>
        </adresse>
    </empfaenger>
    <positionen>
        <position nr="pos1">
            <anzahl>2</anzahl>
            <artikelNr>12345</artikelNr>
            <artikelName>Produkt1</artikelName>
            <einzelpreis>12.50</einzelpreis>
            <betragNetto>25.00</betragNetto>
        </position>

        <position nr="pos2">
            <anzahl>1</anzahl>
            <artikelNr>54321</artikelNr>
            <artikelName>Produkt2</artikelName>
            <einzelpreis>75.00</einzelpreis>
            <betragNetto>75.00</betragNetto>
        </position>
    </positionen>
    <summe>
        <netto>100.00</netto>
        <mwst>19.00</mwst>
        <brutto>119.00</brutto>
    </summe>
    <status>
        <statusmeldung datum="22.02.2024" statusart="unbekannt" />
        <statusmeldung datum="24.02.2024" statusart="vorbereitet" />
    </status>
    <hinweise>
        <notiz>Dies ist eine Korrektur der Rechnung 1234 über <netto>80.00</netto> Euro.</notiz>
        <text>Dies ist eine Korrektur der Rechnung 1234 über 80.00 Euro.</text>
        <weitereadresse>
            Versandadresse
            <adresse>
                <firmenname>Baschira GmbH Filiale Hamburg</firmenname>
                <postfach>Postfach 12344</postfach>
                <plz>20248</plz>
                <ort>Hamburg</ort>
            </adresse>
        </weitereadresse>
        <text>Noch ein Text</text>
    </hinweise>
</rechnung>

Wer das nicht in Einzelschritten lösen will, sondern in einem großen Rutsch, dem seinen noch die Randbedingungen genannt: Die DTD soll dafür sorgen, dass alle Elemente in der Art vorkommen, wie sie im Beispiel genannt sind. Alle Elemente bis auf telefon, namenszusatz und hinweise müssen verpflichtend vorkommen, wenn Sie im Beispiel genannt sind. Adressen können von Privatpersonen (vorname, nachname) oder Firmen (firmenname) angegeben werden. Die Adresse kann als Straße oder Postfach angegeben werden. Eine Rechnung muss mindestens eine Rechnungsposition haben. In hinweise können die drei genannten Elemente beliebig häufig vorkommen, wobei weitereadresse Text oder adresse enthalten darf und notiz beliebigen Inhalt. Die Positionsnummern sollen einzigartig vergeben werden. Aber langsam, Schritt für Schritt…

1. Die Grundstruktur für eine XML-Rechnung

Die Grundstruktur der Rechnung ist folgende. Das Element hinweise soll optional sein, es wird zunächst angenommen, das alle Elemente unterhalb von rechnung ausschließlich parsbaren Text enthalten. Erstelle hierfür eine gültige DTD:

2. Absender ergänzen als Firmenadresse

Das Element absender wird angepasst: hier soll eine Firmenadresse nach folgendem Vorbild enthalten sein. Das Element telefon soll optional sein.

3. Empfänger ergänzen als Privatadresse

Auch für das Element empfänger soll eine Adresse hinterlegt werden. Diese ist aber anders aufgebaut, daher muss das oben definierte Element adresse angepasst werden: Adressen können von Privatpersonen (vorname, nachname) oder Firmen (firmenname) angegeben werden. Die Adresse kann als Straße oder Postfach angegeben werden. Außerdem soll es die Möglichkeit geben, nach dem Firmen- oder Privatnamen einen namenszusatz zu erfassen. Die obigen Firmenadressen sollen weiterhin in der genannten Form unter absender stehen:

4. Rechnungspositionen erfassen

Es sollen die Rechnungspositionen erfasst werden, wobei sichergestellt sein muss, dass mindestens eine Position in jeder Rechnung gespeichert wird:

5. Rechnungssumme

Die Rechnungssumme soll zwingend in der Reihenfolge netto, mwst, brutto erfasst werden:

6. Hinweise

Im Element Hinweise können drei verschiedene Einträge stehen. Es muss ein Unterelement geben, es können aber beliebig viele sein. In einer notiz darf alles stehen (beliebiger Text und Unterelemente), in einem text nur Text und in einer weiterenadresse zunächst auch nur Text.

7. Hinweise

Das Element weitereadresse darf Text enthalten und adresse-Elemente.

Realisierung als mixed content - eine Bestimmung der Reihenfolge ist nicht möglich, #PCDATA muss als erstes genannt werden.

8. Rechnungsnummer und Datum

Für jede Rechnung soll im Root-Element die Rechnungsnummer und das Rechnungsdatum erfasst werden. Welche Gründe sprechen dafür, das als Attribut zu realisieren, was spricht dafür, das als Unterelement zu realisieren?

Für Elemente spricht:

  • später leichter durch weitere Unterelemente erweiterbar

  • mehrere gleichartige Werte speicherbar (z.B. mehrere Datumsangaben oder mehrere Rechnungsnummern)

  • leichter über Baumstruktur erreichbar / einheitlicher als gemischt Attribut/Element

Für Attribute spricht:

  • kompakt in der Schreibweise

  • Reihenfolge muss nicht / kann nicht festgelegt werden

  • mehr Einschränkungen im Rahmen der DTD möglich (Einzigartigkeit, Eingabepflicht)

Es wird die Realisierung über Attribute beschlossen, diese sollen zwingend Werte enthalten. Erstelle die entsprechende DTD:

9. Kunden

Die Kundenummer soll als optionales Attribut erfasst werden, :a sie nur beim Empfänger, nicht aber beim Absender in der adresse auftritt.

10. Status

Für jeder Rechnung soll ein status erfasst werden aus beliebig vielen Statusmeldungen, die jeweils nur Datum und eine Statusart (gültig ist nur: vorbereitet, erstellt, versendet, bezahlt, angemahnt oder unbekannt) enthalten. Elementeinhalt darf statusmeldung nicht haben.

11. Positionsnummer

Für die einzelnen Rechnungspositionen soll jeweils eine zwingend eindeutige ID vergeben werden:

Warum kann das Attribut nr als Attributtyp ID nicht einfach laufende Nummern enthalten?

Der Attributtyp ID schreibt fest, dass der Wert des Attributs dieselben Regeln erfüllen muss wie ein XML-Elementenamen (z.B. keine Zahlen am Beginn). Man muss daher die Werte anpassen, z.B. durch ein führendes “pos”. Wenn der Attributtyp zu CDATA oder NMTOKEN geändert wird, können laufende Nummern genutzt werden. Allerdings entfällt dann die Möglichkeit, zu überprüfen, ob die Werte eindeutig sind.

12. Die Gesamtlösung - das Big Picture

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE rechnung [
<!ELEMENT rechnung (absender, empfaenger, positionen, summe, status, hinweise?)>
  <!ELEMENT empfaenger (adresse)>
  <!ELEMENT absender (adresse)>
    <!ELEMENT adresse (((vorname, nachname)|firmenname), namenszusatz?, ((strasse, hausnr) | postfach), plz, ort, telefon*)> 
      <!ELEMENT vorname (#PCDATA)>
      <!ELEMENT nachname (#PCDATA)>
      <!ELEMENT namenszusatz (#PCDATA)>
      <!ELEMENT firmenname (#PCDATA)>
      <!ELEMENT strasse (#PCDATA)>
      <!ELEMENT hausnr (#PCDATA)>
      <!ELEMENT postfach (#PCDATA)>
      <!ELEMENT plz (#PCDATA)>
      <!ELEMENT ort (#PCDATA)>
      <!ELEMENT telefon (#PCDATA)>      
  <!ELEMENT positionen (position)+>
    <!ELEMENT position (anzahl, artikelNr, artikelName, einzelpreis, betragNetto)>
      <!ELEMENT nr (#PCDATA)>
      <!ELEMENT anzahl (#PCDATA)>
      <!ELEMENT artikelNr (#PCDATA)>
      <!ELEMENT artikelName (#PCDATA)>
      <!ELEMENT einzelpreis (#PCDATA)>
      <!ELEMENT betragNetto (#PCDATA)>
  <!ELEMENT summe (netto, mwst, brutto)>
    <!ELEMENT netto (#PCDATA)>
    <!ELEMENT mwst (#PCDATA)>  
    <!ELEMENT brutto (#PCDATA)>
  <!ELEMENT status (statusmeldung)*>
  <!ELEMENT statusmeldung EMPTY>
  <!ELEMENT hinweise (notiz | text | weitereadresse)+>
    <!ELEMENT notiz ANY>
    <!ELEMENT text (#PCDATA)>
    <!ELEMENT weitereadresse (#PCDATA |adresse)*>
  <!ATTLIST rechnung
   nr CDATA #REQUIRED
   datum CDATA #REQUIRED >
   <!ATTLIST adresse
   kundennummer CDATA #IMPLIED >
   <!ATTLIST position
   nr ID #REQUIRED >
   <!ATTLIST statusmeldung
   datum CDATA #REQUIRED
   statusart (vorbereitet | erstellt | versendet | bezahlt | angemahnt | unbekannt) #REQUIRED>
]>

<rechnung nr="1234" datum="12.01.2024">
    <absender>
        <adresse>
            <firmenname>Baschira GmbH</firmenname>
            <postfach>Postfach 12344</postfach>
            <plz>10248</plz>
            <ort>Berlin</ort>
            <telefon>0124 / 6567891</telefon>
            <telefon>0234 / 7567894</telefon>
        </adresse>
    </absender>
    <empfaenger>
        <adresse kundennummer="1234">
            <vorname>Max</vorname>
            <nachname>Mustermann</nachname>
            <namenszusatz>3. OG links</namenszusatz>
            <strasse>Hauptstraße</strasse>
            <hausnr>23</hausnr>
            <plz>12345</plz>
            <ort>Berlin</ort>
        </adresse>
    </empfaenger>
    <positionen>
        <position nr="pos1">
            <anzahl>2</anzahl>
            <artikelNr>12345</artikelNr>
            <artikelName>Produkt1</artikelName>
            <einzelpreis>12.50</einzelpreis>
            <betragNetto>25.00</betragNetto>
        </position>

        <position nr="pos2">
            <anzahl>1</anzahl>
            <artikelNr>54321</artikelNr>
            <artikelName>Produkt2</artikelName>
            <einzelpreis>75.00</einzelpreis>
            <betragNetto>75.00</betragNetto>
        </position>
    </positionen>
    <summe>
        <netto>100.00</netto>
        <mwst>19.00</mwst>
        <brutto>119.00</brutto>
    </summe>
    <status>
        <statusmeldung datum="22.02.2024" statusart="unbekannt" />
        <statusmeldung datum="24.02.2024" statusart="vorbereitet" />
    </status>
    <hinweise>
        <notiz>Dies ist eine Korrektur der Rechnung 1234 über <netto>80.00</netto> Euro.</notiz>
        <text>Dies ist eine Korrektur der Rechnung 1234 über 80.00 Euro.</text>
        <weitereadresse>
            Versandadresse
            <adresse>
                <firmenname>Baschira GmbH Filiale Hamburg</firmenname>
                <postfach>Postfach 12344</postfach>
                <plz>20248</plz>
                <ort>Hamburg</ort>
            </adresse>
        </weitereadresse>
        <text>Noch ein Text</text>
    </hinweise>
</rechnung>

Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: Übung: Erstellung einer Document-Type-Definition für XML-Rechnungen” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/xml-dtd-uebung veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/datenformate/xml. Stand: 26.02.2024.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: