Az ERLANG programozási nyelv

XML kezelés: xmerl

Szolgáltatások

Az xmerl XML parser képes XML dokumentumok elemzésére az XML 1.0 szabvány szerint. Alapértelmezésben jól formált elemzést hajt végre (szintaktikus elemzés, jól formáltsági kikötések vizsgálata). Az xmerl egy XML fájl validitásának vizsgálatára is használható (ellenőrzés megadott DTD, és validitási megszorítások alapján).

Áttekintés

Az xmerlnek két ismert hiányossága van:

Az XML dokumentum elemzésével egy olyan rekordot kapsz visszatérési értékül, ami a dokumentum struktúráját reprezentálja. A rekordban a dokumentum adatai is el vannak tárolva. Az xmerl alkalmas a következő feladatok megoldására is:

Adatot kell kinyerned egy XML dokumentumból. Az Erlang nyelven írt szoftvered képes az XML dokumentum adatainak kezelésére, az elemzés által létrehozott adatstruktúra felhasználásával.

Az xmerl felhasználásával az így nyert struktúrát is további feldolgozásnak vethetjük alá. Ha meg szeretnéd változtatni az XML dokumentum formátumát, például HTML-re, szövegre, vagy más XML formátumra, akkor átalakíthatod. Az xmerl támogatja az ilyen átalakításokat.

Tetszőleges adatot szintén átalakíthatunk XML-é, például hogy emberi szemmel olvashatóbb legyen. Ebben az esetben először létre kell hoznunk az xmerl adatstruktúrát a saját adatainkból, majd azt XML-é alakítanunk.

xmerl Felhasználói felület adat struktúra

A sikeres elemzés eredménye egy tuple {DataStructure,M}. Ahol M az XML előállításakor keletkező misc. A DataStructure egy xmlElement rekord, amely mezői közt szerepel többek közt a parents, attributes és a content.

#xmlElement{name=Name,
     ...
     parents=Parents,
     ...
     attributes=Attrs,
     content=Content,
     ...}

Az element neve a name mezőben van. A parents mezőben a szülő elemek nevei vannak. A Parents egy tuple lista ahol az első elem mindig a szülő elem neve. A lista fordított sorrendben van.

Az xmlAttribute rekord egy attribútum nevét és értékét tárolja a name és a value mezőkben. Egy elem minden attribútuma egy xmlAttribute listában van eltárolva az xmlElement attributes mezőjében.

A fő elem content mezeje rekordok egy listája, ami megmutatja a dokumentum struktúráját. Például egy ilyen egyszerű dokumentum esetén:

<?xml version="1.0"?>
<dog>
Grand Danois
</dog>

Ez lesz az elemzés eredménye:

#xmlElement{name = dog,
     ...
     parents = [],
     ...
     attributes = [],
     content = [{xmlText,[{dog,1}],1,[],"\
Grand Danois\
",text}],
     ...
     }

Ahol a fő elem tartalma: [{xmlText,[{dog,1}],1,[],"\ Grand Danois\ ",text}]. A szöveg az xmlText rekordokban fog visszatérni. Általában a dokumentumok ennél összetettebbek, ekkor a fő elem tartalma xmlElement rekordok egymásba ágyazott struktúrája lesz, amiknek szintén lehet összetett tartalmuk. Mindez az XML dokumentum struktúráját tükrözi.

A szintaktikus jele közti whitespace karakterek normalizálva xmlText rekordokban térnek vissza.

Hibák

Egy sikertelen elemzés eredménye egy hiba, ami lehet egy tuple {error,Reason} vagy egy exit: {'EXIT',Reason}. Az XML 1.0 szabvány szerint vannak fatális és nem fatális hiba helyzetek. A többi hibával ellentétben a végzetes hibákat egy megerősítő elemzőnek kell megtalálnia. Mind két kategóriát végzetes hibaként jelenti az xmerl jelenlegi verziója, leggyakrabban exit-ként.

Elemzés

A következő példákhoz "motorcycles.xml" XML dokumentumot fogjuk használni és a neki megfelelő DTD-t a "motorcycles.dtd"-t. A motorcycles.xml így néz ki:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE motorcycles SYSTEM "motorcycles.dtd">
<motorcycles>
 <bike year="2000" color="black">
  <name>
   <manufacturer>Suzuki</manufacturer>
   <brandName>Suzuki VL 1500</brandName>
   <additionalName>Intruder</additionalName>
  </name>
  <engine>V-engine, 2-cylinders, 1500 cc</engine>
  <kind>custom</kind>
  <drive>cardan</drive>
  <accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
 </bike>
 <date>2004.08.25</date>
 <bike year="1983" color="read pearl">
  <name>
   <manufacturer>Yamaha</manufacturer>
   <brandName>XJ 400</brandName>
  </name>
  <engine>4 cylinder, 400 cc</engine>
  <kind>alround</kind>
  <drive>chain</drive>
  <comment>Good shape!</comment>
 </bike>
</motorcycles>

és a motorcycles.dtd így néz ki:

<?xml version="1.0" encoding="utf-8" ?>
<!ELEMENT motorcycles (bike,date?)+ >
<!ELEMENT bike    (name,engine,kind,drive, accessories?,comment?) >
<!ELEMENT name    (manufacturer,brandName,additionalName?) >
<!ELEMENT manufacturer    (#PCDATA)>
<!ELEMENT brandName     (#PCDATA)>
<!ELEMENT additionalName  (#PCDATA)>
<!ELEMENT engine       (#PCDATA)>
<!ELEMENT kind        (#PCDATA)>
<!ELEMENT drive       (#PCDATA)>
<!ELEMENT comment      (#PCDATA)>
<!ELEMENT accessories    (#PCDATA)>
<!-- Date of the format yyyy.mm.dd -->
<!ELEMENT date       (#PCDATA)>
<!ATTLIST bike year NMTOKEN #REQUIRED
        color NMTOKENS #REQUIRED
        condition (useless | bad | serviceable | moderate | good | excellent | new | outstanding) "excellent" >

Ha szeretnéd elemezni a motorcycles.xml XML fájlt akkor futtasd a következőket az Erlang shellben:

3> {ParsResult,Misc}=xmerl_scan:file("motorcycles.xml").
{{xmlElement,motorcycles,
       motorcycles,
       [],
       {xmlNamespace,[],[]},
       [],
       1,
       [],
       [{xmlText,[{motorcycles,1}],1,[],"\
 ",text},
       {xmlElement,bike,
             bike,
             [],
             {xmlNamespace,[],[]},
             [{motorcycles,1}],
             2,
             [{xmlAttribute,year,[],[],[],[]|...},
              {xmlAttribute,color,[],[],[]|...}],
             [{xmlText,[{bike,2},{motorcycles|...}],
                  1,
                  []|...},
              {xmlElement,name,name,[]|...},
              {xmlText,[{...}|...],3|...},
              {xmlElement,engine|...},
              {xmlText|...},
              {...}|...],
             [],
             ".",
             undeclared},
       ...
       ],
       [],
       ".",
       undeclared},
[]}
4>  

Ha az XML dokumentum stringként áll rendelkezésre, akkor használhatod az xmerl_scan:string/1 függvényt. A file/2 és a string/2 is létezik, ahol a második paraméter a parser kapcsolóinak listája. (bővebben: http://www.erlang.org/doc/man/xmerl_scan.html)

Példa: Adatok kinyerése XML-ből

Ebben a példában azt a szituációt vesszük amikor csak egy bizonyos adatot akarunk megvizsgálni az XML fájlban. Például azt, hogy melyik motorkerékpár adatait mikor rögzítették.

Tegyünk egy pillantást a DTD-re és figyeljük meg hogy egy XML dokumentum ami megfelel ennek a DTD-nek kell hogy legyen egy motorcycles eleme (a gyökér elemnek). A motorcycles elemnek kell legyen legalább egy bike eleme. Minden bike elem után lehet egy date elem. A date elem tartalma #PCDATA (Parsed Character DATA), pl. hagyományos szöveg.

Ha sikeresen beolvasol egy XML fájlt a validáció bekapcsolásával, mint itt: xmerl_scan:file('motorcycles.xml',[{validation,true}]) akkor tudod, hogy az XML fájl szabványos és megfelel a DTD-nek.

Így, a megengedett struktúra ismeretével könnyű olyan programot írni ami áttekinti az adat struktúrát és kiválasztja a date nevű xmlElementeket.

Figyeljük meg, hogy minden white space: space, tab vagy sortörés a szintaktikai jelek közt egy xmlText rekordot eredményez.

Példa: XML készítése tetszőleges adatok alapján

Ezt a feladatot többféleképpen is megoldhatjuk. A "brute force" megoldás az, hogy létrehozzuk a megfelelő rekordokat és megadjuk az megfelelő elemek content és attribute mezőit.

Erre a feladatra az xmerl támogatja a "simple-form" formátumot. Betáplálhatjuk az adatainkat a simple-form adatstruktúrába, és megadhatjuk az xmerl:export_simple(Content, Callback, RootAttributes) függvénynek. A tartalom lehet a simple-formn és xmerl rekordok, mint az xmlElement és az xmlText keveréke.

A paraméterek típusai:

Az Element a következők egyike lehet:

A simple-form struktúra a következők egyike lehet: {Tag, Attributes, Content}, {Tag, Content} vagy Tag, ahol:

bővebben: http://www.erlang.org/doc/man/xmerl.html#export_simple-3

Ha szeretnéd a motorcycles.xml-hez hozzáadni egy újállapotú 2003-mas fekete Harley Davidsson 1200 cc Sportster motorbiciklit, akkor így írhatod le simple-form formátummal:

Data =
 {bike,
   [{year,"2003"},{color,"black"},{condition,"new"}],
   [{name,
     [{manufacturer,["Harley Davidsson"]},
     {brandName,["XL1200C"]},
     {additionalName,["Sportster"]}]},
   {engine,
     ["V-engine, 2-cylinders, 1200 cc"]},
   {kind,["custom"]},
   {drive,["belt"]}]}  

Ahhoz hogy hozzáadhassuk ezt az adatot a motorcycles.xml fájlhoz, először be kell olvasnunk a fájlt, majd hozzáadni az adatot a gyökér elem tartalmához.

  {RootEl,Misc}=xmerl_scan:file('motorcycles.xml'),
  #xmlElement{content=Content} = RootEl,
  NewContent=Content++lists:flatten([Data]),
  NewRootEl=RootEl#xmlElement{content=NewContent},

És ezzel meghívjuk az export_simple/2 függvényt:

  {ok,IOF}=file:open('new_motorcycles.xml',[write]),
  Export=xmerl:export_simple([NewRootEl],xmerl_xml),
  io:format(IOF,"~s~n",[lists:flatten(Export)]),

Az eredmény ez lesz:

<?xml version="1.0"?><motorcycles>
 <bike year="2000" color="black">
  <name>
   <manufacturer>Suzuki</manufacturer>
   <brandName>Suzuki VL 1500</brandName>
   <additionalName>Intruder</additionalName>
  </name>
  <engine>V-engine, 2-cylinders, 1500 cc</engine>
  <kind>custom</kind>
  <drive>cardan</drive>
  <accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
 </bike>
 <date>2004.08.25</date>
 <bike year="1983" color="read pearl">
  <name>
   <manufacturer>Yamaha</manufacturer>
   <brandName>XJ 400</brandName>
  </name>
  <engine>4 cylinder, 400 cc</engine>
  <kind>alround</kind>
  <drive>chain</drive>
  <comment>Good shape!</comment>
 </bike>
<bike year="2003" color="black" condition="new"><name><manufacturer>Harley Davidsson</manufacturer><brandName>XL1200C</brandName><additionalName>Sportster</additionalName></name><engine>V-engine, 2-cylinders, 1200 cc</engine><kind>custom</kind><drive>belt</drive></bike></motorcycles>

Ha fontos, hogy az eredeti dokumentuméhoz hasonló tagolást kapjunk, akkor hozzá kell adnunk az #xmlText{} rekordokat a megfelelő space és új sor értékekkel. Szintén fontos lehet, hogy megtartsuk az eredeti fejlécet, ahol a DTD-re hivatkozunk. Ha igen, akkor lehetségünk van egy RootAttribute {prolog,Value} paramétert átadnunk az export_simple/3 függvénynek. A következő példakód javítja ezeket az előző példából:

  Data =
   [#xmlText{value=" "},
    {bike,[{year,"2003"},{color,"black"},{condition,"new"}],
       [#xmlText{value="\
  "},
       {name,[#xmlText{value="\
   "},
           {manufacturer,["Harley Davidsson"]},
           #xmlText{value="\
   "},
           {brandName,["XL1200C"]},
           #xmlText{value="\
   "},
           {additionalName,["Sportster"]},
           #xmlText{value="\
  "}]},
       {engine,["V-engine, 2-cylinders, 1200 cc"]},
       #xmlText{value="\
  "},
       {kind,["custom"]},
       #xmlText{value="\
  "},
       {drive,["belt"]},
       #xmlText{value="\
 "}]},
    #xmlText{value="\
"}],
  ...
  NewContent=Content++lists:flatten([Data]),
  NewRootEl=RootEl#xmlElement{content=NewContent},
  ...
  Prolog = ["<?xml version=\\"1.0\\" encoding=\\"utf-8\\" ?>
<!DOCTYPE motorcycles SYSTEM \\"motorcycles.dtd\\">\
"],
  Export=xmerl:export_simple([NewRootEl],xmerl_xml,[{prolog,Prolog}]),
  ...  

Ez lesz az eredmény:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE motorcycles SYSTEM "motorcycles.dtd">
<motorcycles>
 <bike year="2000" color="black">
  <name>
   <manufacturer>Suzuki</manufacturer>
   <brandName>Suzuki VL 1500</brandName>
   <additionalName>Intruder</additionalName>
  </name>
  <engine>V-engine, 2-cylinders, 1500 cc</engine>
  <kind>custom</kind>
  <drive>cardan</drive>
  <accessories>Sissy bar, luggage carrier,V&H exhaust pipes</accessories>
 </bike>
 <date>2004.08.25</date>
 <bike year="1983" color="read pearl">
  <name>
   <manufacturer>Yamaha</manufacturer>
   <brandName>XJ 400</brandName>
  </name>
  <engine>4 cylinder, 400 cc</engine>
  <kind>alround</kind>
  <drive>chain</drive>
  <comment>Good shape!</comment>
 </bike>
 <bike year="2003" color="black" condition="new">
  <name>
   <manufacturer>Harley Davidsson</manufacturer>
   <brandName>XL1200C</brandName>
   <additionalName>Sportster</additionalName>
  </name><engine>V-engine, 2-cylinders, 1200 cc</engine>
  <kind>custom</kind>
  <drive>belt</drive>
 </bike>
</motorcycles>