CodeGym /Corsi /C# SELF /Lavoro avanzato con XML: X...

Lavoro avanzato con XML: XmlDocument e XPath

C# SELF
Livello 48 , Lezione 2
Disponibile

1. Introduzione

Immagina un XML gigantesco che ti manda qualche servizio SOAP o un API bancaria. Contiene centinaia di elementi annidati, strutture non omogenee, e tu devi trovare una sola, unica somma di pagamento del mese scorso. L'approccio con la serializzazione qui non va bene — è troppo ingombrante e non sai in anticipo dove guardare. Serve una "lente" per l'albero XML e la capacità di muoversi rapidamente tra i suoi rami.

Proprio per questi casi in .NET esiste da anni uno strumento potente — la classe XmlDocument, integrata dal linguaggio di query XPath. Oggi imparerai a:

  • Caricare XML in un albero DOM;
  • Muoversi e trovare elementi manualmente e usando XPath;
  • Modificare il contenuto di un documento XML;
  • Aggiungere, rimuovere e modificare nodi;
  • Usare XPath per selezioni complesse.

2. Cos'è XmlDocument?

XmlDocument è una classe nel namespace System.Xml. È l'implementazione del modello DOM (Document Object Model) — la rappresentazione dell'intero file XML come un albero di oggetti in memoria.

Un po' di teoria e analogie

Se XmlSerializer è simile a un set LEGO, quando trasformi un oggetto in pezzi e viceversa, allora XmlDocument è come lavorare con un vero albero: ha una radice, rami (elementi), foglie (nodi di testo), e puoi camminare per quell'albero, aggiungere rami, tagliare foglie e trapiantarle dove vuoi.

Caricamento semplice di un documento XML

Supponiamo di avere questo XML:

<users>
  <user id="1">
    <name>Ol'ga</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>Igor'</name>
    <age>35</age>
  </user>
</users>

Carichiamolo in memoria:

using System.Xml;

string xml = @"
<users>
  <user id='1'>
    <name>Ol'ga</name>
    <age>28</age>
  </user>
  <user id='2'>
    <name>Igor'</name>
    <age>35</age>
  </user>
</users>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // oppure doc.Load("percorso_al_file.xml");

Dopo la chiamata a LoadXml (o Load, se l'XML proviene da file) hai pieno accesso al contenuto del documento.

3. Navigazione nell'albero DOM

Struttura dell'albero DOM

Ogni documento XML dopo il caricamento diventa un albero composto da nodi di vari tipi:

Tipo di nodo Classe in .NET Esempio
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Ol'ga, 28
Comment XmlComment
<!-- commento -->

Per accedere agli elementi necessari dovrai "camminare per l'albero" usando proprietà come ChildNodes, Attributes, ParentNode ecc.

Otteniamo l'elemento radice

XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users

Iterare sugli elementi figli

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — è <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"Utente {id}: {name}, età {age}");
    }
}

IMPORTANTE: L'accesso user["name"] è possibile solo se fra i figli immediati esiste l'elemento <name>.

Accesso ad attributi e testo

var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Ol'ga"

4. Modifica del documento XML

Modificare un valore

Supponiamo che Ol'ga decida improvvisamente di "ringiovanire":

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // ora <age>22</age>

Aggiungere un nuovo utente

XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");

XmlElement name = doc.CreateElement("name");
name.InnerText = "Vasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);

Rimuovere un elemento

Rimuoviamo il secondo utente ("Igor'):

XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
    root.RemoveChild(userToDelete);

Salvare le modifiche

doc.Save("users_updated.xml");
// Oppure doc.OuterXml — per ottenere la stringa XML

5. XPath — linguaggio di ricerca e selezione in XML

Lavorare con l'albero DOM diventa faticoso se devi cercare elementi "per condizione" — per esempio tutti gli utenti con età maggiore di 25 anni. Per questo esiste XPath — un linguaggio per navigare l'albero XML.

Uso base di XPath

Le query XPath si eseguono tramite i metodi SelectSingleNode (restituisce il primo nodo trovato) e SelectNodes (restituisce una collection di nodi).

Esempio: trovare l'utente con id = 1

XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Ol'ga

Esempio: trovare tutti gli utenti con età > 25

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // Stamperà Ol'ga e Igor' (se Igor' non è stato ancora rimosso)
}

XPath — sintassi rapida

Espressione XPath Cosa farà
/users/user
Tutti gli elementi <user> nella radice <users>
user[@id='3']
Utente con id=3
user[age>25]
Utenti con età maggiore di 25
user/name
Tutti gli elementi <name> di tutti gli utenti
user[last()]
Ultimo <user>
user[position()<3]
I primi due <user>

Altri esempi e sintassi: Documentazione su XPath.

Altro esempio: selezione per elementi annidati

Immaginiamo che l'XML sia più complesso:

<library>
  <book>
    <title>Il signore delle mosche</title>
    <author>
      <firstname>Vilim</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>Alla ricerca del tempo perduto</title>
    <author>
      <firstname>Marsel'</firstname>
      <lastname>Proust</lastname>
    </author>
  </book>
</library>
XmlDocument doc = new XmlDocument();
doc.LoadXml(libraryXml);
XmlNodeList authors = doc.SelectNodes("/library/book/author/lastname");
foreach (XmlNode lastName in authors)
    Console.WriteLine(lastName.InnerText);

Cosa verrà stampato:

Golding
Proust

6. XPath: filtraggio, logica, calcoli

Filtri logici

Stampare i nomi di tutti gli utenti la cui età è tra 18 e 30:

// [age>=18 and age<=30]
XmlNodeList youngUsers = root.SelectNodes("user[age>=18 and age<=30]");
foreach (XmlNode u in youngUsers)
    Console.WriteLine(u["name"].InnerText);

Lavorare con gli attributi

Gli attributi si selezionano con @. Troviamo gli utenti il cui id inizia con "1":

XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");

Contare i nodi

Direttamente nel metodo C# SelectNodes non puoi usare la funzione count() per ottenere un numero — il metodo restituisce una collection, non un numero. Ma puoi fare così:

int count = root.SelectNodes("user").Count;

Filtri annidati

XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Golding']");

7. XPath e spazi dei nomi

Se il tuo XML usa spazi dei nomi (xmlns), le cose si complicano un po'! In questi casi usa XmlNamespaceManager:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Algoritmi</title>
  </book>
</catalog>
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("b", "http://books.example.com");

XmlNodeList books = doc.SelectNodes("/b:catalog/b:book", nsmgr);
foreach (XmlNode book in books)
    Console.WriteLine(book.SelectSingleNode("b:title", nsmgr)?.InnerText);

8. Modificare XML con il DOM

Aggiungere nodi "al volo"

Aggiungiamo un nuovo elemento <email> al primo utente:

XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);

Modificare attributi

olga.SetAttribute("id", "99"); // Ora id="99"

Rimuovere nodi usando XPath

XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
    root.RemoveChild(node);

9. Sfumature utili

Ricerca e modifica di massa della struttura

Supponiamo che ti arrivi un grande XML con vari elementi <record type="...">. Devi tenere solo quelli il cui attributo type è "customer", e aggiungere a ciascuno un figlio <status>active</status>.

XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
    XmlElement status = doc.CreateElement("status");
    status.InnerText = "active";
    record.AppendChild(status);
}

Albero dei nodi XML (DOM)


users
├─ user (id="1")
│   ├─ name ("Ol'ga")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("Igor'")
│   └─ age ("35")

Esempio di selezione con XPath

Query XPath Cosa restituirà
/users/user[1]
Primo utente (id="1")
/users/user[last()]
Ultimo utente (id="2")
/users/user[age>30]
Tutti con età > 30 (Igor')
/users/user[@id='2']
Utente con id="2"

Applicazione pratica

  • Integrazioni con API "vecchie". In molte istituzioni pubbliche e banche SOAP/XML domina ancora. La capacità di trovare e modificare rapidamente qualcosa in una grande risposta XML può salvare decine di ore.
  • Migrazione dei dati. Quando si passa da un sistema a un altro spesso serve parsare XML e fare selezioni complesse, trasformazioni e modifiche in massa.
  • Import/export in Excel. In molti prodotti B2B l'input è ancora XML. Qui XPath è un modo rapido per estrarre quello che serve senza costruire un grande modello di dati.

10. Errori, sfumature e trappole tipiche

NullReferenceException: Se provi ad accedere a un elemento inesistente, ad esempio el["something"].InnerText, e quel figlio non esiste, otterrai un errore. Controlla sempre per null.

XPath e annidamento: Ricorda che un'espressione senza lo slash iniziale / cerca tra i discendenti del nodo corrente, mentre con / parte dalla radice del documento. Punti di riferimento diversi possono portare a nessun risultato anche se i dati ci sono.

Lavorare con gli spazi dei nomi: Se non usi XmlNamespaceManager, le query XPath su XML con xmlns restituiranno vuoto.

Modificare durante l'iterazione: Se vuoi rimuovere nodi secondo il risultato di SelectNodes, prima salvali in un array e poi itera — altrimenti la collection cambierà durante l'iterazione e possono sorgere errori.

Differenza tra nodi di testo ed elementi: Tra gli elementi possono esserci nodi di testo — spazi e a-capo che XML considera contenuto. Per selezioni rigorose usa solo XmlElement o filtra per NodeType.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION