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.

7 thoughts on “Parsovanie XML v príkladoch

  1. 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. 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. 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. 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. 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. neviem ci po troch rokoch je to este aktualne, ale zaujimalo by ma rozparsovanie neznameho suboru, teda bez predpokladanej struktury (stači v DELPHI), a ci je to rychlejsie ako riesenie pomocou transformacii

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *

*

Môžete použiť tieto HTML značky a atribúty: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>