Parsovanie XML v príkladoch
XML (Extensible Markup Language) je vyznačovací jazyk, ktorý je medzi vývojármi obľúbený najmä vďaka jeho prehľadnosti a jednoduchosti. Najčastejšie sa používa na ukladanie konfigurácie programov a v jednoduchších aplikáciách dokáže poskytnúť rozumnú alternatívu k relačným databázovým systémom. Tvorí však aj základ protokolu SOAP a mnohých ďalších technológií ako napríklad AJAX či RSS. Procesu získavania údajov z XML štruktúr sa hovorí “parsovanie” a pre väčšinu programovacích jazykov existuje tzv. XML parser, čiže nástroj na čítanie XML štruktúr. V článku nájdete ukážky použitia XML parserov v jazykoch ANSI C, PHP, Perl, Java a Object Pascal.
Ak ste s jazykom XML ešte nepracovali, odporúčam vám preštudovať aspoň krátky tutoriál na webe w3schools.com. Oboznámite sa v ňom okrem iného so základnými pravidlami, ktorými by ste sa mali pri tvorbe XML štruktúr riadiť.
V ďalšej časti článku budem predpokladať, že súbor “tcpip.xml” obsahuje nasledovnú XML štruktúru:
<?xml version="1.0" encoding="utf-8" ?>
<model_tcpip>
<linkova_vrstva>
<protokol nazov="Ethernet" />
</linkova_vrstva>
<sietova_vrstva>
<protokol nazov="IP">Internet Protocol</protokol>
<protokol nazov="ICMP">Internet Control Message Protocol</protokol>
<protokol nazov="IGMP">Internet Group Management Protocol</protokol>
</sietova_vrstva>
<transportna_vrstva>
<protokol nazov="TCP">Transmission Control Protocol</protokol>
<protokol nazov="UDP">User Datagram Protocol</protokol>
</transportna_vrstva>
<aplikacna_vrstva>
<protokol sluzba="FTP" popis="File Transport Protocol" />
<protokol sluzba="SSH" popis="Secure SHell" />
<protokol sluzba="HTTP" popis="HyperText Transport Protocol" />
</aplikacna_vrstva>
</model_tcpip>
Vopred upozorňujem, že nasledujúce ukážky kódov obsahujú kvôli zvýšeniu prehľadnosti len minimálnu kontrolu chýb.
V príklade je použitá knižnica libxml2. Program sa kompiluje príkazom:
# gcc -Wall -O2 `xml2-config --cflags --libs` xmlparser.c -o xmlparser
Podstatnú časť programu už tradične zaberajú funkcie uvoľňujúce pamäť :)
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
int main(void) {
char *subor = "tcpip.xml";
xmlDoc *doc = NULL;
xmlNode *root_element = NULL;
xmlNode *current_node = NULL;
xmlNode *protokol = NULL;
xmlChar *atribut = NULL;
xmlChar *obsah = NULL;
LIBXML_TEST_VERSION
doc = xmlReadFile(subor, NULL, 0);
if (doc == NULL) {
printf("Parsovanie suboru %s neuspesne.n", subor);
return(1);
}
root_element = xmlDocGetRootElement(doc);
if (root_element == NULL) {
printf("Chyba pri parsovani XML suboru.n");
return(1);
}
for (current_node = root_element->children; current_node != NULL; current_node = current_node->next) {
if (current_node->type == XML_ELEMENT_NODE && !xmlStrcmp(current_node->name, (const xmlChar *) "linkova_vrstva")) {
printf("Linkova vrstvan");
for (protokol = current_node->children; protokol != NULL; protokol = protokol->next) {
atribut = xmlGetProp(protokol, (const xmlChar *) "nazov");
if (atribut) {
printf("t%sn",atribut);
}
xmlFree(atribut);
}
xmlFreeNode(protokol);
}
if (current_node->type == XML_ELEMENT_NODE && !xmlStrcmp(current_node->name, (const xmlChar *) "sietova_vrstva")) {
printf("Sietova vrstvan");
for (protokol = current_node->children; protokol != NULL; protokol = protokol->next) {
atribut = xmlGetProp(protokol, (const xmlChar *) "nazov");
obsah = xmlNodeGetContent(protokol);
if (atribut) {
printf("t%s - %sn", atribut, obsah);
}
xmlFree(obsah);
xmlFree(atribut);
}
xmlFreeNode(protokol);
}
if (current_node->type == XML_ELEMENT_NODE && !xmlStrcmp(current_node->name, (const xmlChar *) "transportna_vrstva")) {
printf("Transportna vrstvan");
for (protokol = current_node->children; protokol != NULL; protokol = protokol->next) {
atribut = xmlGetProp(protokol, (const xmlChar *) "nazov");
obsah = xmlNodeGetContent(protokol);
if (atribut) {
printf("t%s - %sn", atribut, obsah);
}
xmlFree(obsah);
xmlFree(atribut);
}
xmlFreeNode(protokol);
}
if (current_node->type == XML_ELEMENT_NODE && !xmlStrcmp(current_node->name, (const xmlChar *) "aplikacna_vrstva")) {
printf("Aplikacna vrstvan");
for (protokol = current_node->children; protokol != NULL; protokol = protokol->next) {
atribut = xmlGetProp(protokol, (const xmlChar *) "sluzba");
if (atribut) {
printf("t%s", atribut);
}
xmlFree(atribut);
atribut = xmlGetProp(protokol, (const xmlChar *) "popis");
if (atribut) {
printf(" - %sn", atribut);
}
xmlFree(atribut);
}
xmlFreeNode(protokol);
}
}
xmlFreeNode(current_node);
xmlFreeNode(root_element);
xmlFreeDoc(doc);
xmlCleanupParser();
return(0);
}
V príklade je použitý modul SimpleXML, ktorý je súčasťou PHP5. Myslím, že po porovnaní kódu s ostatnými príkladmi je každému hneď jasné, prečo je PHP tak populárne.
<?php
$subor = file_get_contents("./tcpip.xml");
$xml = new SimpleXMLElement($subor);
echo "nLinkova vrstvan";
foreach ($xml->linkova_vrstva->protokol as $protocol) {
echo "t".$protocol['nazov']."n";
}
echo "nSietova vrstvan";
foreach ($xml->sietova_vrstva->protokol as $protocol) {
echo "t".$protocol['nazov']." - ".$protocol."n";
}
echo "nTransportna vrstvan";
foreach ($xml->transportna_vrstva->protokol as $protocol) {
echo "t".$protocol['nazov']." - ".$protocol."n";
}
echo "nAplikacna vrstvan";
foreach ($xml->aplikacna_vrstva->protokol as $protocol) {
echo "t".$protocol['sluzba']." - ".$protocol['popis']."n";
}
?>
Osobne preferujem v perle parsovanie pomocou regulárnych výrazov, no v tomto prípade som pre názornosť použil modul XML::Simple. Pochopenie kódu vyžaduje znalosť asociatívnych polí (tzv. hash-ov).
#!/usr/bin/perl
use strict;
use XML::Simple;
my $xml = new XML::Simple;
my $data = $xml->XMLin("tcpip.xml", forcearray => 1);
print "Linkova vrstvan";
foreach my $protokol (@{$data->{linkova_vrstva}->[0]->{protokol}}) {
print "t".$protokol->{nazov}."n";
}
print "Sietova vrstvan";
foreach my $protokol (@{$data->{sietova_vrstva}->[0]->{protokol}}) {
print "t".$protokol->{nazov}." - ".$protokol->{content}."n";
}
print "Transportna vrstvan";
foreach my $protokol (@{$data->{transportna_vrstva}->[0]->{protokol}}) {
print "t".$protokol->{nazov}." - ".$protokol->{content}."n";
}
print "Aplikacna vrstvan";
foreach my $protokol (@{$data->{aplikacna_vrstva}->[0]->{protokol}}) {
print "t".$protokol->{sluzba}." - ".$protokol->{popis}."n";
}
I keď sa snažím, občas sa použitiu javy nedá vyhnúť. Program sa prekladá príkazom:
javac XMLparser.java
Bez Eclipse by sa mi tento príklad pravdepodobne nepodarilo “zlepiť”.
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class XMLparser {
public static void main(String[] args) {
javax.xml.parsers.DocumentBuilderFactory dbf;
javax.xml.parsers.DocumentBuilder parser;
org.w3c.dom.Document document = null;
try {
dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(false);
parser = dbf.newDocumentBuilder();
document = parser.parse("tcpip.xml");
System.out.println("Linkova vrstva");
if (document.getElementsByTagName("linkova_vrstva").getLength() > 0) {
Element app_vrstva = (Element) document.getElementsByTagName("linkova_vrstva").item(0);
NodeList nodes = app_vrstva.getElementsByTagName("protokol");
for (int i = 0; i < nodes.getLength(); i++) {
Element protokol = (Element) nodes.item(i);
System.out.println(" "+protokol.getAttribute("nazov"));
}
}
System.out.println("Sietova vrstva");
if (document.getElementsByTagName("sietova_vrstva").getLength() > 0) {
Element app_vrstva = (Element) document.getElementsByTagName("sietova_vrstva").item(0);
NodeList nodes = app_vrstva.getElementsByTagName("protokol");
for (int i = 0; i < nodes.getLength(); i++) {
Element protokol = (Element) nodes.item(i);
System.out.println(" "+protokol.getAttribute("nazov")+" - "+protokol.getTextContent());
}
}
System.out.println("Transportna vrstva");
if (document.getElementsByTagName("transportna_vrstva").getLength() > 0) {
Element app_vrstva = (Element) document.getElementsByTagName("transportna_vrstva").item(0);
NodeList nodes = app_vrstva.getElementsByTagName("protokol");
for (int i = 0; i < nodes.getLength(); i++) {
Element protokol = (Element) nodes.item(i);
System.out.println(" "+protokol.getAttribute("nazov")+" - "+protokol.getTextContent());
}
}
System.out.println("Aplikacna vrstva");
if (document.getElementsByTagName("aplikacna_vrstva").getLength() > 0) {
Element app_vrstva = (Element) document.getElementsByTagName("aplikacna_vrstva").item(0);
NodeList nodes = app_vrstva.getElementsByTagName("protokol");
for (int i = 0; i < nodes.getLength(); i++) {
Element protokol = (Element) nodes.item(i);
System.out.println(" "+protokol.getAttribute("sluzba")+" - "+protokol.getAttribute("popis"));
}
}
} catch (Exception e) {
System.out.println("Chyba pri parsovani XML suboru - "+e);
}
}
}
Uvoľnenie Turbo Delphi bolo pre mňa udalosťou roka 2006. Borland je opäť v hre.
program xmlparser;
{$APPTYPE CONSOLE}
uses
SysUtils, Activex, XMLDoc, XMLIntf;
var
XMLDocument: IXMLDocument;
XMLroot, XMLvrstva, XMLproto : IXMLNode;
i : integer;
begin
CoInitialize(nil);
XMLDocument := TXMLDocument.Create(nil);
// Pri pouziti VCL komponenty TXMLDocument nie je treba
// pouzit predchadzajuce 2 riadky
try
XMLDocument.XML.LoadFromFile('tcpip.xml');
XMLDocument.Active := True;
XMLroot := XMLDocument.DocumentElement;
writeln('Linkova vrstva');
XMLvrstva := XMLroot.ChildNodes.Nodes['linkova_vrstva'];
if (Assigned(XMLvrstva)) then
begin
for i := 0 to XMLvrstva.ChildNodes.Count - 1 do
begin
XMLproto := XMLvrstva.ChildNodes[i];
writeln(' '+XMLproto.Attributes['nazov']);
end;
end;
writeln('Sietova vrstva');
XMLvrstva := nil;
XMLvrstva := XMLroot.ChildNodes.Nodes['sietova_vrstva'];
if (Assigned(XMLvrstva)) then
begin
for i := 0 to XMLvrstva.ChildNodes.Count - 1 do
begin
XMLproto := XMLvrstva.ChildNodes[i];
writeln(' '+XMLproto.Attributes['nazov']+' - '+XMLproto.NodeValue);
end;
end;
writeln('Transportna vrstva');
XMLvrstva := nil;
XMLvrstva := XMLroot.ChildNodes.Nodes['transportna_vrstva'];
if (Assigned(XMLvrstva)) then
begin
for i := 0 to XMLvrstva.ChildNodes.Count - 1 do
begin
XMLproto := XMLvrstva.ChildNodes[i];
writeln(' '+XMLproto.Attributes['nazov']+' - '+XMLproto.NodeValue);
end;
end;
writeln('Aplikacna vrstva');
XMLvrstva := nil;
XMLvrstva := XMLroot.ChildNodes.Nodes['aplikacna_vrstva'];
if (Assigned(XMLvrstva)) then
begin
for i := 0 to XMLvrstva.ChildNodes.Count - 1 do
begin
XMLproto := XMLvrstva.ChildNodes[i];
writeln(' '+XMLproto.Attributes['sluzba']+' - '+XMLproto.Attributes['popis']);
end;
end;
except
writeln('Pri parsovani XML suboru sa vyskytla chyba.');
end;
XMLDocument.Active := False;
XMLDocument := nil;
readln;
end.
Neostáva mi iné, než odporučiť malú rozcvičku, pretože programovať sa človek učí programovaním. Skúste teda vo svojom obľúbenom jazyku napísať napríklad vlastnú RSS čítačku.

Komentáre k článku:
lenin - 12.12.2007 2007 18:21:08
Zdravim, neviem preco mi vypisuje pri pouziti kodu takuto hlasku??
Fatal error: Call to undefined function: simplexml_load_file() in /var/www/vhosts/len-in.sk/http
Jaroslav Imrich - 12.12.2007 2007 18:47:00
Moznosti je viacero. Mozete mat starsiu verziu interpreta PHP, ktory tuto funkciu este nepozna alebo je PHP skompilovane bez SimpleXML modulu. Pricinu by som hladal v logoch web servera resp. vo vypise funkcie phpinfo().
Martin - 30.12.2007 2007 23:11:46
Zdravim. Co sa tyka parserov pre formaty txt,doc, rtf alebo html mozes dako poradit? Robim program v Delphi pre ukladanie dokumentov do databazy SQL a hodilo by sa mi parsovanie aby som mohol ulozit cisto len text do DB pre fulltextove vyhladavanie. Vedel by si mi s tym dako pomoct?
Jaroslav Imrich - 31.12.2007 2008 14:52:04
Nechapem celkom preco spominas parser na txt a html, pretoze tieto formaty su textove. Co sa tyka doc a rtf tak myslim, ze v Delphi mozes pouzit ActiveX alebo COM komponenty MS Office a s nimi ulozit dokument ako plaintext.
trolo - 17.06.2008 2008 14:48:19
mam problem, mam xml subor
<?xml version=”1.0″ encoding=”ISO-8859-2″?>
a ked ho rozparsujem
XMLproto.Attributes['popis']
tak mi nezobrazuje spravne diakritiku…
nasiel som aj nieco taketo
XMLDocument.Encoding := ‘ISO-8859-2′;
avsak vobec nic sa nestalo… nevie mi niekto poradit?
vdaka
DELPHI 6.0
Juraj - 25.06.2009 2009 09:39:00
Super prispevok, dik
Pridať komentár