###1 - Tipos de dados
xmlChar
é um tipo unsigned char
para tratar strings com formato UTF-16.
Neste caso, o arquivo XML deve estar no encoding UTF-8 (char
) ou UTF-16
(unsigned char
). Se a declaração do encode não estiver presente, o xmlChar
automaticamente irá converter o arquivo para UTF-8!
xmlDoc
é uma estrutura que irá armazenar as informações da tree
, por exemplo:
filename, children, last child, parent, next node, previous node, xml version,
encoding, URL, namespace e etc. É uma variedade de informações analisadas e
organizadas no momento em que usado a função "parser" - xmlDocPtr
seria um
ponteiro para essa estrutura:
xmlDocPtr xmlParseFile(const char *filename);
xmlDocPtr xmlReadFile(const char *filename,
const char *encoding,
int options);
######imagem retirada deste link: libXML2, xmlsoft.org
ps: set encoding/options to 0
for null
. Definido em:
Github: libxml2, include/libxml/tree.h#L551
xmlNode
é uma estrutura que contém as informações do node atual, trata-se que
estamos analisando um XML node-by-node e essa função tem um ponteiro chamado
xmlNodePtr
para que consigamos "caminhar" identificando os demais nodes contidos
no arquivo XML.
Os membros da classe de xmlNodePtr
é definido em:
Github: libxml2, include/libxml/tree.h#L487
PS: Ao verificar os membros da "struct" conhecerá os recursos disponíveis para manipular os nodes.
A principio, para criar um analisador (parser) de XML é preciso:
[+] Inserir o arquivo XML.
[+] Detectar o primeiro (root) node.
Caso apenas isso não seja suficiente, e quer buscar informações contidas:
[+] Detectar o primeiro children do node principal. E, asssim, ir avançando até chegar no escopo que está o node a ser manipulado.
[+] Extrair informações.
Obtendo como base as informações acima, o código example1.c
mostra
como criar um simples analisador XML.
###2 - XML PARSING ####MONTANDO UM PARSING SIMPLES
/* filename: example1.c */
#include <stdio.h>
#include <stdlib.h>
#include <libxml/parser.h>
int main(int argc, char *argv[])
{
xmlDocPtr doc; // tipo de dado para tratar o "file.xml"
xmlNodePtr curnode; // tipo de dado para tratar os conteúdos (leia sobre xmlNode)
doc = xmlReadFile(argv[1], 0, 0); // montando uma "tree" para o file.xml ser analisado
if (!argv[1]) // imagine que, argv[1] == file.xml
{
fprintf(stderr, "[!] The filename \"%s\" is empty.\n", argv[1]);
exit(EXIT_FAILURE);
}
curnode = xmlDocGetRootElement(doc); // detecta o primeiro node em file.xml
if (!curnode) // caso não encontre nenhum node em file.xml
{
puts("[!] Make sure that is a correct file.\n");
xmlFreeDoc(doc); // desfaz toda a estrutura criada
return 1;
}
/* Espera-se que o primeiro node em file.xml chama-se "root_node" */
if (xmlStrcmp(curnode->name, (xmlChar *)"root_node"))
{
puts("[!] This node isn`t root node.\n");
xmlFreeDoc(doc); // desfaz toda a estrutura criada
return 1;
}
return 0;
}
O código acima irá apenas analisar o arquivo inserido e criar uma estrutura através
da função xmlReadFile
. Com o arquivo XML inserido, usa-se a função xmlDocGetRootElemet
,
e será detectado o primeiro node do arquivo que foi passado em seu parametro. - Se o
node não for encontrado, um NULL
será retornado.
Na comparação é usado o xmlStrcmp
para validar se o node principal é o mesmo que o
esperado. Então, verifica se curnode->name
é igual à "root_node"
.
####PRINT RESULT OF "PARSING"
O código abaixo irá buscar as informações contidas em <tagname>
, que o mesmo está
dentro do root node (que chamamos de "root_node" no exemplo do
código acima [exemple1.c
]).
Imagine o seguinte cenário: [file.xml]
<root_node>
<tagname>thrash 'til death</tagname>
</root_node>
Deste modo, a função xmlNodeListGetString
retonará a frase: "thrash 'til death"
.
É possível utilizar um loop para que encontre todos os children dentro de <tagname>
que sejam de mesma identificação, por exemplo:
[file2.xml]
<root_node>
<tagname>thrash 'til death</tagname>
<tagname>slayer</tagname>
<tagname>sodom</tagname>
</root_node>
Com o exemplo de "file2.xml", quando aplicado o loop o resultado esperado seria imprimir os
3 childrens <tagname>
.
/* filename: print_parse.c */
void get_children_element(xmlDocPtr doc, xmlNodePtr curnode)
{
/* set "curnode" to point to the first child */
xmlChar *value;
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"tagname"))
{
value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
printf("[tagname]: %s\n", (char *)value);
xmlFree(value);
}
curnode = curnode->next;
} while(0);
return 0;
}
Estes códigos de exemplos não irão funcionar se você não mencionou o node principal e o children node numa mesma função! Isso vai depender em qual escopo a children está.
###3 - Lidando com childrens mais profundos
Aproveitando que estamos num assunto mais técnico, peguei dois arquivos XML, das quais, suas estrutuaras são diferentes, repare o começo de cada um:
####Exemplo Wordpress Plugin RSS XML
<?xml version="1.0" encoding="iso-8859-1"?>
<extension version="2.5" type="plugin" group="wppipes-adapter" method="upgrade">
<name>RSS</name>
<author>wppipes.com</author>
...
</extension>
####Exemplo Exploit-DB RSS XML
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Exploit-DB Updates</title>
<link>https://www.exploit-db.com</link>
...
</channel>
</rss>
PS: A primeira linha pode ignorar, pois nada mais é do que o tipo de encode para o XML.
De acordo com os exemplos acima, o node principal está na linha 2, e a outra, na linha 3 -, respectivamente.
####Lidando com Wordpress Plugin RSS XML
Em primeiro lugar, identificar o <extension>
que é o node principal e pode-se usar do
xmlStrcmp
para buscar a children <author>
ou, de no modo direto, usar a função ``.
A libXML2 define uma macro xmlChildrenNode
que representa o children
- ou seja,
curnode->xmlChildrenNode
é o mesmo que curnode->children
. Declarando
curnode->xmlChildrenNode
, estará buscando o primeiro children do node atual, que
seria o <name>
e usando curnode->next
encontraremos o <author>
. Neste caso, o código
acima pode ser feito em apenas uma função. Veremos:
(Aproveitando o código acima)
/* filename: extension.c */
#include <stdio.h>
/* Mesmo que o modulo parse.c já faz
a inclusão do stdlib, prefiro incluir
pra deixar mais legível. A lib não irá
incluir duas vezes porque ele faz o
teste "HAVE_STDLIB_H". */
#include <stdlib.h>
#include <libxml/parser.h>
void main(int argc, char *argv[])
{
xmlDocPtr doc; // tipo de dado para tratar o "file.xml"
xmlNodePtr curnode; // tipo de dado para tratar os conteúdos (leia sobre xmlNode)
doc = xmlReadFile(argv[1], 0, 0); // montando uma "tree" para o file.xml ser analisado
if (!argv[1]) // imagine que, argv[1] == file.xml
{
fprintf(stderr, "[!] The filename \"%s\" is empty.\n", argv[1]);
exit(EXIT_FAILURE);
}
curnode = xmlDocGetRootElement(doc); // detecta o primeiro node em file.xml
if (!curnode) // caso não encontre nenhum node em file.xml
{
puts("[!] Make sure that is a correct file.\n");
xmlFreeDoc(doc); // desfaz a estrutura criada
exit(EXIT_FAILURE);
}
/* Note: pequena modificação na linha abaixo
Espera-se que o primeiro node em file.xml chama-se "extension" */
if (xmlStrcmp(curnode->name, (xmlChar *)"extension"))
{
puts("[!] This node isn`t root node.\n");
xmlFreeDoc(doc); // desfaz a estrutura criada
exit(EXIT_FAILURE);
}
/* Função incluida para buscar o "author" children */
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"extension"))
{
xmlChar *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
fprintf(stdout, "%s\n", (char *)value); // cast to "char" (utf-8) to avoid bad chars
xmlFree(value);
}
curnode = curnode->next;
}while(curnode);
exit(EXIT_SUCCESS);
}
Esquece a função principal por enquanto, foca na função que irá caminhar pelos nodes e buscar as strings.
Quanto mais interno estiver o "element child" estiver, o trabalho será
ainda maior. Pode ser usado a função xmlDocGetRootElement(xmlDocPtr *)
para buscar o root node e passar como parametro a variável que armazena
o arquivo XML. Um esquema mais óbvio de char no node desejado é ir
buscando sempre o parent node
até chegar no mesmo escopo.
O parente node do elemento title
é o <channel>
. Ao chegar nele, para
caminhar em seu escopo basta jogar uma função para que faça uma comparação
em cada node até encontrar o </channel>
e encerra a busca.
Em prática, para chegar em channel
pode ser feito algo deste jeito:
####Montando um parser para o Exploit-DB RSS XML
void xml_parser(char *file) // parsing com `char *argv[]`
{
xmlDocPtr doc = xmlReadFile(file, 0, 0);
if (!doc)
{
fprintf(stderr, "[!] The file \"%s\".\n", argv[1]);
exit(EXIT_FAILURE);
}
xmlNodePtr curnode = xmlDocGetRootElement(doc);
if (!curnode)
{
fprintf("[!] Make sure that the \"%s\" is an XML\n");
xmlFreeDoc(doc);
exit(EXIT_FAILURE);
}
if (!xmlStrcmp(curnode->name, (const xmlChar *)"rss"))
{
puts("[!] This node isn`t root node.\n");
xmlFreeDoc(doc);
exit(EXIT_FAILURE);
}
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"channel"))
{
get_title(doc, curnode); // função que fará a busca do <title>
}
curnode = curnode->next;
}while(curnode);
xmlFreeDoc(doc);
}
E para buscar o <title>
defina uma função que compare com os node -,
se for um específico pode ser usado o xmlStrncmp
, ou, pode ser usado
xmlStrcmp
. Ambos são similares a função strncmp
e strcmp
da glibc.
void get_title(xmlDocPtr doc, xmlNodePtr curnode)
{
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"title"))
{
xmlDocPtr *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
fprintf(stdout,"%s\n", (char *)value);
xmlFree(value); /* Lembre-se de usar sempre esta função quando
estiver usando xmlNodeListGetString, pois
ela não irá limpar os valores armazenados.
- Não livrando os valores, pode causar Overflow
e se usar xmlFree e xmlFreeDoc, estará livrando
2x seguidas, causando Memory Corruption...
Fique atento! */
}
curnode = curnode->next;
}while(curnode);
}
#####Colocando tudo junto...
#include <stdio.h>
#include <stdlib.h>
#include <libxml/parser.h>
void get_title(xmlDocPtr doc, xmlNodePtr curnode);
void xml_parser(char *file);
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "[!] Usage: %s <file.xml>\n", argv[0]);
exit(EXIT_FAILURE);
}
xml_parser(argv[1]);
return 0;
}
void xml_parser(char *file)
{
xmlDocPtr doc = xmlReadFile(file, 0, 0);
if (!doc)
{
puts("[!] The filename \"%s\" is empty.\n");
exit(EXIT_FAILURE);
}
xmlNodePtr curnode = xmlDocGetRootElement(doc);
if (!curnode)
{
puts("[!] Make suere that the \"%s\" is an XML file.\n");
xmlFreeDoc(doc);
exit(EXIT_FAILURE);
}
if (xmlStrcmp(curnode->name, (const xmlChar *)"rss"))
{
puts("[!] This node isn`t root node.\n");
xmlFreeDoc(doc);
exit(EXIT_FAILURE);
}
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"channel"))
{
get_title(doc, curnode);
}
curnode = curnode->next;
}while(curnode);
xmlFreeDoc(doc);
}
void get_title(xmlDocPtr doc, xmlNodePtr curnode)
{
curnode = curnode->xmlChildrenNode;
do {
if (!xmlStrcmp(curnode->name, (const xmlChar *)"title"))
{
xmlChar *value = xmlNodeListGetString(doc, curnode->xmlChildrenNode, 1);
fprintf(stdout, "%s\n", value);
xmlFree(value);
}
curnode = curnode->next;
}while(curnode);
}
Dropping this as public 2 years later.