Objavljeno: 30.9.2005 15:00 | Avtor: Gregor Humar | Monitor September 2005

Strukturiranje podatkov XML

XML (eXtensible Mark-up Language - razširljiv jezik za označevanje) se je že precej uveljavil kot jezik za izmenjavo strukturiranih podatkov. Ključ do njegove uporabnosti je zajet že v njegovem imenu - prav razširljivost je namreč bistvo njegove prilagodljivosti.

Razširljivost označevalnega jezika pomeni, da je odgovornost definicije strukture in oznak podatkov prepuščena vsaki izmenjavi posebej. XML torej oznak ne definira že vnaprej, ampak so le-te odvisne od potreb in zahtev konkretne izvedbe. Vse to je nujno za učinkovito izmenjavo podatkov. Pri konkretni izmenjavi je treba definirati strukturo in vsebino dokumentov XML, hkrati pa imeti možnost preverjanja ustreznosti prenesenih podatkov. Večinoma je treba poskrbeti tudi za ustrezno predstavitev vsebin, varnost, dostop le do določenih oznak v dokumentu ... Za vse te zahteve so nam na voljo tehnologije, ki stojijo ob strani standardu XML in brez katerih od samega označevalnega jezika nimamo dosti koristi. Tokrat se bomo lotili le tistega dela procesa analiziranja podatkov XML, ki zajema definiranje strukture in vsebine ter preverjanje podatkov. Dokumente XML preverjajo programi za branje in analiziranje, členitelji XML (XML parser). Najpogosteje se uporabljajo: Xerces-J, Crimson, , Piccolo. Členitelji so še veliko več - so API vmesniki, ki dovoljujejo uporabniškim programom razširitev privzetih funkcionalnosti (členitelji v osnovi le berejo in preverjajo dokumente XML), a to je že druga zgodba.

DTD (Document Type Definition)

DTD je bil kot priporočilo konzorcija w3c predstavljen že skupaj z uvedbo jezika XML konec devetdesetih let. Jezik DTD odlikuje predvsem izjemna enostavnost, žal pa ga le-ta omejuje pri njegovi funkcionalnosti, kot bomo videli kasneje.

Poglejmo si preprost zgled - želimo definirati dokument XML, ki bo imel za svojo glavno oznako <kupec>, le-ta pa naj nosi v sebi enega ali več elementov <izdelek id='sifra izdelka'>. Vsak izdelek naj ima natanko en element <cena>. Ustrezna definicija DTD je naslednja:

<!ELEMENT kupec (izdelek*)>

<!ELEMENT izdelek (cena)>

<!ELEMENT cena (#PCDATA)>

<!ATTLIST izdelek id CDATA #REQUIRED>

Vidimo, da mora biti definiran vsak element posebej - element <izdelek>, ki je naveden v definiciji elementa <kupec>, je definiran posebej v naslednji vrstici. Splošna oblika za definicijo elementa je taka:

<!ELEMENT imeElementa (vsebinaElementa)>

Kaj stoji znotraj oklepajev, je seveda odvisno od vsebine elementa - tu pa imamo več možnosti:

  • element je prazen oziroma nima vsebine: EMPTY
  • element je lahko prazen ali pa vsebuje podelement (nikakor pa ne oboje hkrati): ANY
  • če naj element vsebuje le podelemente, le-te enostavno naštejemo, npr.: (podelement1, podelement2, podelement3). Ob tem lahko uporabimo operatorje I (ali), * (vse), ? (noben ali eden), + (eden ali več). Naveden vrstni red elementov se zahteva tudi v dokumentu XML.
  • element naj ima mešano vsebino, torej podelemente in besedilo: Mixed
  • element vsebuje le besedilo: (#PCDATA)
  • Definicija enega ali več atributov elementa je podobna:

    <!ATTLIST imeElementa

    imePrvegaAtributa vrstaAtributa privzetaVrednost

    imeDrugegaAtributa vrstaAtributa privzetaVrednost

    ...

    imeZadnjegaAtributa vrstaAtributa privzetaVrednost>

    Možnih je več vrst atributov, najpogosteje pa srečamo naslednje:

  • večinoma so atributi vrste CDATA (kar pomeni, da je vrednost atributa kakršnokoli besedilo ali nabor znakov);
  • vrsta NMTOKEN zahteva, da besedilna vrednost ustreza standardu XML1.0;
  • vrsta ID zahteva, da je vrednost atributa unikatna skozi celotni dokument (le-ta atribut ima lahko to vrednost). Vrednost se ne sme začeti s številko - ponavadi so ID oznake oblike x000001, pri čemer je x kakršnakoli črka, ki ji sledi nabor števil, npr. e003453;
  • IDREF omejuje vrednost atributa na eno izmed vrednosti že obstoječih ID atributov v dokumentu.
  • Kot privzeto vrednost pa imamo na voljo:

  • #REQUIRED (atribut je obvezen);
  • #IMPLIED (atribut ni obvezen in nima privzete vrednosti);
  • lahko samo napišemo privzeto vrednost: ''vrednost'' (če atribut nima vrednosti, jo bo členitelj XML nadomestil s privzeto);
  • #FIXED ''vrednost'' (atribut ni obvezen, če pa je prisoten, mora imeti vrednost, ki je tu definirana).
  • Če želimo, da se neka vrednost elementa ne preverja, v dokumentu XML to označimo s sledečo skladnjo:

    <![CDATA[...nečlenjen tekst...]]>

    To je koristno predvsem zato, da lahko v elemente pišemo tista besedila, ki imajo polno skladnje, za katero bi potrebovali entitete oziroma nadomestne znake, saj nočemo, da se razumejo kot oznake XML - gre predvsem za dele kode javascript, oznake html ali jsp ...

    Kratek zgled:

    <script>

    <![CDATA[

    function matchwo(a,b) {

    if (a < b && a < 0)

    then

    { return 1 }

    else

    { return 0 }

    }

    ]]>

    </script>

    Členitelj torej preskoči vse znotraj oznake CDATA.

    Oglejmo si zgled definicije DTD, ki vsebuje večinoma različne možnosti, in ustrezen dokument XML:

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

    <!DOCTYPE Books [

    <!ELEMENT Books (book*)>

    <!ELEMENT book (author+,title,price)>

    <!ATTLIST book category CDATA ''literature''>

    <!ELEMENT author EMPTY>

    <!ATTLIST author

    last CDATA #REQUIRED

    middle CDATA #IMPLIED

    first CDATA #REQUIRED>

    <!ELEMENT title (#PCDATA)>

    <!ELEMENT price (#PCDATA)>

    ]>

    <Books>

    <book category="reference">

    <author last="Nigel" middle="" first="Rees" />

    <title>Sayings of the Century</title>

    <price>8.95</price>

    </book>

    <book category="fiction">

    <author last="Evelyn" middle="" first="Waugh" />

    <title>Sword of Honour</title>

    <price>12.99</price>

    </book>

    </Books>

    Definicija DTD je lahko, kot smo videli, del dokumenta XML. Ponavadi pa je DTD ločen od XML. V tem primeru je druga vrstica dokumenta XML namig členiteljju, kje naj dobi definicijo DTD, s katero naj preveri dokument XML. Splošna oblika je naslednja:

    <!DOCTYPE imeGlavnegaElementa SYSTEM "potDoDokumenta">

    V našem zgledu:

    <!DOCTYPE Books SYSTEM "books.dtd">

    Če je npr. books.dtd dokument v tekoči mapi. Pri tem velja omeniti, da v primeru, da notranja in zunanja definicija definirata isti objekt, velja notranja.

    DTD nudi še nekaj naprednejših možnosti (entitete, notacije ...), ki pa ne odtehtajo pomanjkljivosti. Največji problem je, da nimamo možnosti definicije vrst podatkov (string, float, boolean ...) in da so možnosti glede omejitve števila elementov ali atributov precej borne (ni možno npr. zahtevati, naj element <Books> vsebuje 10 do 15 podelementov <book>. V nasprotju z drugimi tehnologijami XML (XMLSchema, XSLT ...) DTD nima skladnje XML. Vse to je uporabnost DTD definicij omejila do te mere, da bo kmalu le še tehnologija preteklosti, saj jo že večinoma nadomešča precej bogatejša XMLSchema.

    XMLSchema

    XMLSchema odpravlja večino pomanjkljivosti, ki smo jih očitali DTD definicijam. W3c konzorcij jo je kot priporočilo predstavil leta 2001 in v zadnjih nekaj letih je postala izjemno razširjen standard. Sintaktično gledano je shema XML dokument, za katerega pa seveda veljajo pravila jezika XMLSchema, ki si jih bomo podrobneje ogledali.

    Oglejmo si najprej naš prvi zgled iz prejšnjega razdelka. S shemo XML bi ga napisali takole:

    <xsd:schema

    targetNamespace="http://some.nameSpace/URI"

    xmlns="http://some.nameSpace/URI"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <xsd:element name='kupec'>

    <xsd:complexType>

    <xsd:sequence>

    <xsd:element name='izdelek' type="xsd:string"/>

    <xsd:complexType>

    <xsd:sequence>

    <xsd:element name='cena' type="xsd:double"/>

    </xsd:sequence>

    </xsd:complexType>

    </xsd:sequence>

    <xsd:attribute name='id' type='xsd:ID' />

    </xsd:complexType>

    </xsd:element>

    Takoj vidimo, da pisanje definicij ni tako enostavno, kakor z rabo DTD, imamo pa precej več možnosti. Razvoj sheme je bil predvsem usmerjen v močno podporo standardiziranim vrstam podatkov (byte, date, integer, vrste jave in SQL, predvsem pa vrstam, ki ustrezajo zbirkam podatkov).

    Glavni element v vsaki shemi je element <schema>, znotraj katerega so napisane vse druge definicije. Več o njegovih atributih kasneje.

    Splošna skladnja definicije elementa XML je naslednja:

    <xsd:element name="imeElementa" type="vrstaElementa">

    Element je lahko kompleksne ali enostavne vrste. Enostavne vrste imajo le besedilno vrednost (torej nimajo podelementov ali atributov). V standard XMLSchema je vgrajenih kar prek 80 enostavnih vrst. Naštejmo le najpomembnejše:

    string, ID, Integer, positiveInteger, negativeInteger, nonNegativeInteger, nonPositiveInteger, int, unsignedInt, long, unsignedLong, short, unsignedShort, decimal, float, double, boolean, time, dateTime, duration, date, byte,...

    Element priimek enostavne vrste xsd:string bi napisali takole:

    <xsd:element name="priimek" type="xsd:string/>

    Definicijo elementa smo lahko takoj zaprli, ker gre pač za že vgrajeno vrsto. Enostavno vrsto pa lahko definiramo tudi sami - krajevno znotraj definicije elementa ali pa globalno (v tem primeru vrsto poimenujemo in jo lahko uporabimo večkrat). Oglejmo si zgled globalne definicije enostavne vrste in njene uporabe:

    <xsd:simpleType name="kolicinaVrsta">

    <xsd:restriction base="xsd:integer">

    <xsd:minInclusive value="2"/>

    <xsd:maxInclusive value="5"/>

    </xsd:restriction>

    </xsd:simpleType>

    <xsd:element name='kolicina' type="kolicinaVrsta"/>

    Seveda bi to vrsto lahko definirali tudi krajevno, če bi jo potrebovali le enkrat v celotnem dokumentu:

    <xsd:element name='kolicina'>

    <xsd:simpleType>

    <xsd:restriction base="xsd:integer">

    <xsd:minInclusive value="2"/>

    <xsd:maxInclusive value="5"/>

    </xsd:restriction>

    </xsd:simpleType>

    </xsd:element>

    Uporabili smo dodatni oznaki minInclusive in maxInclusive, ki določata najmanjšo in največjo dovoljeno vrednost. Dodatnih oznak, ki jih uporabljamo za razširjanje ali omejevanje, je še kar nekaj, npr.:

  • length, minLength, maxLength - določajo dolžino besedila;
  • enumeration - lahko naštejemo dovoljene vrednosti;
  • maxInclusive, maxExclusive, minInclusive, minExclusive - omejujemo največje in najmanjše vrednosti z mejami intervalov ali brez njih;
  • Zgled za naštevanje dovoljenih besedilnih vrednosti:

    <xsd:element name="barva">

    <xsd:simpleType>

    <xsd:restriction base="xsd:string">

    <xsd:enumeration value="rdeca"/>

    <xsd:enumeration value="zelena"/>

    <xsd:enumeration value="modra"/>

    </xsd:restriction>

    </xsd:simpleType>

    </xsd:element>

    Veljaven element XML bi bil npr. <barva>rdeca</barva>.

    Precej kompleksnejše strukture lahko napravimo s tako imenovanimi kompleksnimi vrstami elementov. Kompleksne vrste elementov imajo podelemente oziroma atribute. Oglejmo si zgled:

    <xsd:element name="kolicina">

    <xsd:complexType>

    <xsd:simpleContent>

    <xsd:extension base="xsd:nonNegativeInteger">

    <xsd:attribute name="dobavljivo"

    type="xsd:boolean

    default="false"/>

    </xsd:extension>

    </xsd:simpleContent>

    </xsd:complexType>

    </xsd:element>

    Definirali smo element kolicina, ki ima boolean atribut dobavljivo. Oznaka <xsd:simpleContent> pomeni, da element nima podelementov, ampak le atribut. V XML jeziku je atribut le del začetne oznake elementa, pravzaprav nekakšna razširitev le-te - od tod oznaka <xsd:extension>, katere atribut base pove, kakšne vrste je besedilo v elementu. Oglejmo si splošno obliko definicije atributa:

    <xsd:attribute

    name='imeAtributa'

    type='enostavnaVrstaAtributa'

    fixed|default='privzetaFiksnaVrednost'

    use='...'/>

    Atributi vsebujejo podatke poljubne enostavne vrste. Določimo lahko privzeto oziroma fiksirano vrednost (v popolnoma enakem smislu kot v DTD jeziku). Pod use določimo, ali je prisotnost atributa obvezna ali ne (required ali optional).

    Če določeno kombinacijo atributov potrebujemo večkrat (torej imajo določeni elementi enake atribute), lahko definiramo skupino atributov in jo pozneje uporabimo:

    <xsd:attributeGroup name="addressInfo">

    <xsd:attribute name="street" type="xsd:string" use="required"/>

    <xsd:attribute name="city" type="xsd:string" use="required"/>

    <xsd:attribute name="state" type="xsd:string" use="required">

    <xsd:attribute name="zip" type="xsd:string" use="required">

    </xsd:attribute>

    <xsd:element name="mailingAddress">

    <xsd:complexType>

    <xsd:attributeGroup ref="addressInfo"/>

    </xsd:complexType>

    </xsd:element>

    Če naj ima element podelemente, oznako <xsd:simpleContent> seveda izpustimo. Znotraj definicije elementa preprosto definiramo nov (pod)element. Če je le-teh več, je treba določiti vrstni red (če seveda želimo le-tega določiti). Pri tem uporabimo takoimenovane kompozitorje:

    Sequence kompozitor določa, da morajo biti vsi definirani podelementi prisotni v enakem vrstnem redu, kakor so definirani:

    <xsd:sequence>

    <xsd:element name='firstName' type='xsd:string'/>

    <xsd:element name='lastName' type='xsd:string'/>

    </xsd:sequence>

    Pri kompozitorju choice naštejemo dovoljene podelemente (prisoten je lahko eden):

    <xsd:choice>

    <xsd:element name='maidenName' type='xsd:string'/>

    <xsd:element name='cityOfBirth' type='xsd:string'/>

    </xsd:choice>

    Kompozitor all pa zahteva, da so vsi navedeni podelementi prisotni, pri čemer pa ni pomemben vrstni red le-teh:

    <xsd:all>

    <xsd:element name='height' type='xsd:float'/>

    <xsd:element name='weight' type='xsd:float'/>

    </xsd:all>

    Velikokrat je koristno definirati skupino elementov, ki se večkrat pojavljajo skupaj:

    <xsd:group name="uporabnik">

    <xsd:sequence>

    <xsd:element name="ime" type="xsd:string"/>

    <xsd:element name="id" type="xsd:integer"/>

    </xsd:sequence>

    </xsd:group>

    To skupino lahko seveda uporabimo v kateri koli kompleksni vrsti, npr:

    <xsd:complexType name="osebaVrsta">

    <xsd:group ref="uporabnik"/>

    <xsd:attribute

    name="registriran"

    type="xsd:boolean"

    use="optional"/>

    </xsd:complexType>

    <xsd:element name="oseba" type="osebaVrsta"/>

    Element XML, ki ustreza taki definiciji, bi bil na primer takle:

    <oseba registriran='true'>

    <ime>Grega</ime>

    <id>123456</id>

    </oseba>

    Izjemno koristno je, da je mogoče kompozitorje gnezditi. Tako lahko na primer znotraj kompozitorja all uporabimo sequence:

    <all>

    <sequence>

    ...definicija elementov...

    </sequence>

    <sequence>

    ...definicija drugih elementov...

    </sequence>

    </all>

    S tem zahtevamo, da se obe skupini elementov pojavljata, pri čemer je pomemben vrstni red znotraj skupine, medtem ko je vrstni red skupin nepomemben.

    Omenimo še dva atributa, ki ju lahko uporabimo pri definiranju različnih struktur (elementov, skupin) - minOccurs in maxOccurs, ki definirata, kolikokrat se lahko določena struktura ponovi. Oglejmo si kratek zgled:

    <xsd:element

    name='izdelek'

    type='xsd:nonNegativeInteger'

    minOccurs='1'

    maxOccurs='10'/>

    Zahtevamo torej vsaj en element <izdelek>, lahko pa jih je največ deset. V primeru, da slučajno izberemo vrednost maxOccurs manjšo od vrednosti minOccurs (kar seveda nima smisla), členitelj upošteva privzeto vrednost 1 za oba atributa (element se v tem primeru pojavi natančno enkrat).

    Dodatnih možnosti pri XML shemi je še precej - pregledali smo le najpomembnejše. Na koncu je treba seveda še povedati členitelju, kje naj najde shemo za določen dokument. To napravimo na naslednji način:

  • Dokument XML postavimo na privzeti prostor imen (default namespace). V našem primeru naj bo glavni element v dokumentu XML element <narocilo>, ki ga postavimo na http://www.ibm.com/WD03/Schemas/XML.
  • <narocilo xmlns="http://www.ibm.com/WD03/Schemas/XML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ibm.com/WD03/Schemas/XML schema.xsd">25</quantity>

  • Vidimo, da je ta privzeti naslov tudi prva vrednost atributa schemaLocation. To ujemanje je obvezno. Druga vrednost tega atributa pa je dejanska pot do datoteke, v kateri je naša shema - v našem primeru je to kar schema.xsd (očitno je ta datoteka v tekoči mapi). Med obema vrednostima atributa je presledek.
  • Ta privzeti naslov pa mora biti tudi v sami shemi - vrednost atributa targetNamespace elementa schema:
  • <schema xmlns="http://www.w3.org/2001/XMLSchema"

    targetNamespace="http://www.ibm.com/WD03/Schemas/XML"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    Skratka, ujemati se morajo vrednosti, ki so v našem primeru napisane odebeljeno. Naslov URL je seveda lahko drugačen - odvisno od tega, kakšno prostorsko ime (namespace) želimo.

    Element <schema> ima še nekaj atributov. Poleg targetNamespace ima ponavadi še definiran privzeti naslov, poleg tega pa je treba definirati tudi predpono xsd, ki smo jo, kot vidimo, uporabljali pri vsaki shema oznaki. Glede na pravila XML to napravimo takole:

    xmlns:xsd="http://www.w3.org/2001/XMLSchema"

    Druge možnosti in oznake si lahko ogledate v dokumentaciji na http://www.w3c.org/XML/Schema.

    Naroči se na redna tedenska ali mesečna obvestila o novih prispevkih na naši spletni strani!

    Komentirajo lahko le prijavljeni uporabniki

     
    • Polja označena z * je potrebno obvezno izpolniti
    • Pošlji