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.

XML

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.

ANSI C

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);
  
}

PHP

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";
}

?>

Perl – my preciousss

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";
}

Java

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);
    }

  }
}

Object Pascal (Delphi)

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.

Záver

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:

  1. 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

  2. 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().

  3. 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?

  4. 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.

  5. 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

  6. Juraj - 25.06.2009 2009 09:39:00

    Super prispevok, dik

Pridať komentár