Applicare uno schema XML a un documento Word 2007
ULTIM’ORA. Per la gioia di chi adora i modelli preconfezionati annuncio che ne ho preparato uno, che facilita la sperimentazione dal vivo. Convinto però che, PRIMA, è opportuno leggere l’articolo, ne inserisco il link IN FONDO allo stesso.
Una novità interessante e poco nota di Word 2007 è la possibilità di applicare uno schema XML a un documento Word, .docx o .docm o, meglio ancora, a un modello .dotx. La cosa è legata alla struttura XML del nuovo formato aperto di Office 2007, OOXML (Open Office XML), argomento sul quale su questo sito nonché su quello degli sviluppatori OOXML (http://openxmldeveloper.org/forums) ho dato alcuni contributi, con esempi relativi a Excel 2007.
A proposito dei formati open di Word 2007 sarò franco: la liberalità espressa da Microsoft in risposta alla sfida del rivale ODF (Open Document Format di OpenOffice.org e IBM Symphony) è apprezzabile, ma non mi pare che, con Word come con PowerPoint, di per sé apra particolari orizzonti elaborativi. Infatti con OOXML come ODF l’XML è un testo che… descrive un altro testo coi suoi formati, stili e struttura. Ora la struttura in Word di regola è semplicemente composta di sezioni, paragrafi e caratteri e, dal mio punto di vista “estremistico” di fan del DP – data processing, trattamento di DATI signori miei - il nocciolo duro di un documento è il testo “puro”, libero, gli orpelli avendo valore ergonomico ed editoriale. E far “trattare come dati” un testo puro da parte di un programma è impresa ben dura: certo si possono compiere ricerche documentarie anche avanzate (v. Google) ma siamo ben lungi dalle elaborazioni, semplici e automatiche, che si possono compiere su dati strutturati veraci, parlo delle tabelle relazionali nonché dei file XML però organizzati in modo meno generale di quanto non faccia l’OOXML di Word (e con Excel? Dipende da quanto sono sparpagliate le celle sul foglio di lavoro).
Insomma un testo, a meno di futuribili azioni di Intelligenza Artificiale (di là da venire), non può essere compreso da un automa, ma solo da un umano, che ne conosca la lingua, tanto per cominciare e, inoltre, abbia competenza nella materia trattata). In termini aulici, il problema è semantico, non meccanico.
Vi sono però due eccezioni: in una certa misura le tabelle di Word e, per l’appunto, gli schemi XML personali di cui qui ci occupiamo. Questi infatti aggiungono elementi strutturati suscettibili perciò di trattamenti più o meno classici. Il semplice ma tipico esempio che sto per mostrare consente di inserire in un documento .docx (o .docm, .dotx, .dotm). i dati relativi a una fattura in modo che si possano assoggettare a successive elaborazioni.
Il bello è che questa opportunità si può impostare mediante comandi manuali, per cui stupisce un po’ che solo manuali ponderosi (come “Word 2007 oltre ogni limite” – Mondadori Informatica) ne parlino.
Un semplice schema di fattura
La nozione di schema XML è un prerequisito. Ricordo solo che, mentre un XML è un file di dati ossia di contenuti, un file del genere, di estensione XSD (sta per Xml Schema Definition), definisce la struttura organizzativa di un file XML, standard o personalizzato. Entriamo in medias res considerando il seguente schema XSD che useremo nel nostro documento-fattura:
< ?xml version="1.0"?>
< !-- Nome file: SchemaFattura.xsd -->
< xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
< xsd:element name="FATTURA">
< xsd:complexType>
< xsd:sequence>
< xsd:element name="NOME" type="xsd:string"/>
< xsd:element name="COGNOME" type="xsd:string"/>
< xsd:element name="INDIRIZZO" type="xsd:string"/>
< xsd:element name="CITTA" type="xsd:string"/>
< xsd:element name="DESCRIZIONE" type="xsd:string"/>
< xsd:element name="QUANT" type="xsd:positiveInteger"/>
< xsd:element name="PREZZO" type="xsd:decimal"/>
< xsd:element name="IMPONIBILE" type="xsd:decimal"/>
< xsd:element name="IVA" type="xsd:positiveInteger"/>
< xsd:element name="IMPORTO" type="xsd:decimal"/>
< /xsd:sequence>
< /xsd:complexType>
< /xsd:element>
< /xsd:schema>
Nota bene. Per evitare un bug dell’Editor di Shareoffice le parentesi angolari sinistre (segno minore) sono seguite da uno spazio. Questo deve essere eliminato nel file .xsd che altrimenti sarebbe rigettato come non valido. idem per altri file XML.
Tale schema è stato ripreso dal CD di un libro (purtroppo essi scarseggiano nella dotazione che dovrebbe accompagnare Word 2007 almeno nell’ed. italiana), modificandone campi e aggiungendone, ma senza cambiare il namespace http://www.w3.org/2001/XMLSchema altrimenti lo schema verrebbe rifiutato. Quindi lo si copi e incolli nel Blocco Note e lo si salvi in una cartella che andrà destinata a questo e ad altri schemi (senza poi modificarla!) col nome SchemaFattura.xsd.
La struttura del nostro schema è del tipo più semplice, sequenza (come indicato dal nodo xsd:sequence) ossia una serie di campi senza gerarchie. Insomma equivale a una normale tabella “classica”. Accenniamo che applicando tale schema a un file .XLM questo si presenterebbe dotato di tag del tipo seguente:
< NOME>Mario< /NOME>
< COGNOME>Bianchi< /NOME>
. . . . . .
< DESCRIZIONE>Cocomeri< /DESCRIZIONE>
< QUANT>123< /QUANT>
eccetera.
Schema XML in un documento Word
Vediamo invece come si usa uno schema XML in Word. Occorre anzitutto sincerarsi che la barra multifunzione contenga il quadro Sviluppo (provvedere, se occorre, con mosse che diamo per facili, altrimenti ricorrere al solito amico...), quindi in un nuovo documento Word si compiano le operazioni seguenti.
- Scegliere Sviluppo > Struttura > Schema.
- Nella finestra Modelli e aggiunte, pannello Schema XML, dare un clic su Aggiungi schema...
- Navigare nel disco fino a “pescare” il nostro SchemaFattura.xsd.
- Nella susseguente finestra Impostazioni schema digitare http://www.w3.org/2001/XMLSchema nella casella URI e Fattura nella casella Alias.

- Si prepari un layout tipico (v. figura più avanti) comprendente in alto l’intestatario della fatturina con nome, cognome, indirizzo ecc. e, sotto, una tabella con campi intestati Descrizione, Quantità, Prezzo eccetera.
- Si lanci di nuovo Sviluppo > Schema scegliendo il nostro Fattura (attivandone la casellina) e si dia l’OK. Compare sulla destra un pannello, inizialmente vuoto.
- Per renderlo più “eloquente” si selezioni il punto del documento in cui si desidera inserire un campo, diciamo il primo carattere sotto “Spettabile”, il che farà comparire solo FATTURA nella lista in basso. Selezionandola si riceverà la richiesta se applicare lo schema all’intero documento o alla selezione. Conviene scegliere la seconda, che fa apparire i segnaposti FATTURA a circondare i dati che ci interesseranno.
- A questo punto lascio a chi legge il compito di far apparire anche i sospirati NOME, COGNOME ecc. nel pannello e di inserirne le coppie di segnali nei punti opportuni, ottenendo quanto indicato in figura:
http://blog.shareoffice.it/giannigiaccaglini/gallery/image/898.aspx
- Salvare il documento col nome che si vuole, verbi gratia FatturaConSchemaXML.docx.
Tale documento (o, meglio ancora, modello .dotm) presenta così delle coppie di tag entro le quali l’utente può inserire i dati opportuni.
Nota. Il layout rimane piuttosto libero. Soprattutto si rifletta sul fatto che i tag relativi a diverse voci della fattura – DESCRIZIONE, QUANT, PREZZO ecc. – si possono ripetere su più righe della tabella, come di regola accade coi layout delle fatture che ne prevedono una dozzina, che l’utente può riempire più o meno.
In gergo si parla di isola di dati inserita nel documento, che per il resto è un testo puro, più o meno intensamente formattato e dotato di figure e altri orpelli magari multimediali.
Il contenuto OOXML
Se ora eseguiamo le ben note manovre: modifica dell’estensione docx in zip e doppio clic sul file così rinominato – possiamo scoprirne i componenti, o “parti” OOXML. A noi interessa il document.xml posto nella cartella Word, di cui riportiamo la sezione iniziale, segnando con evidenziatore – nella selva di nodi di testo normale e formati, tra cui quelli relativi alla tabella < w:tbl> e derivati - gli elementi < w:custom Xml> che ci interessano, unitamente ai nodi-figli < w:t> che racchiudano i vari dati dell’”isola”.
I molti omissis corrispondono a una selva di particolarità relative alla formattazione che sovrabbondano anche in una semplice letterina come quella esemplificata!
Nota. In particolare si noti la distinzione tra w:r (range, assoggettabile a formattazione) e w:t (testo “vero e proprio”) e, nel document.xml completo, i tag relativi a tabelle griglie e altri ammennicoli formattanti, che a noi NON interesseranno.
< w:customXml w:uri="http://www.w3.org/2001/XMLSchema" w:element="FATTURA">
< w:p w:rsidR="00233072" w:rsidRDefault="00233072" w:rsidP="00A55353">
< w:pPr>
< w:r>
< w:t xml:space="preserve">Spettabile< /w:t>
< /w:r>
< w:ind w:left="708" />
< /w:pPr>
< /w:p>
. . . o m i s s i s . . .
< w:tblStyle w:val="Grigliatabella" />
. . o m i s s i s . . .
< w:tblBorders>
. . . o m i s s i s . . .
< /w:tblBorders>
< w:tblLook w:val="04A0" />
< /w:tblPr>
< w:tblGrid>
. . o m i s s i s . . .
< /w:tblGrid>
< w:tr w:rsidR="00233072" w:rsidTr="00233072">
< w:customXml w:element="NOME">
. . . o m i s s i s . . .
< w:t>Giovanni< /w:t>
. . . o m i s s i s . . .
< /w:customXml>
< w:customXml w:element="COGNOME">
. . . o m i s s i s . . .
< w:t>Rinaldi< /w:t>
. . . o m i s s i s . . .
< /w:customXml>
. . . o m i s s i s . . .
< /w:customXml>
Si osservi infine che l’ultima tag < /w:customXml> è la tag di chiusura del nodo iniziale, quello di attributi w:uri=”http://www.w3.org/2001/XMLSchema” e w:element="FATTURA", insomma la nostra sezione XML dal fantasioso nome FATTURA.
E una macro VBA per esplorarlo
A questo punto propongo una macro esplorativa ai più bravi che si cimentano col VBA dedicato alla gestione di documenti XML in generale e OOXML in particolare. Ripeto che un prerequisito è una certa familiarità con l’XPath (cercare questo tema su MSDN, Microsoft Developer Network e... meditare). Un altro prerequisito è la conoscenza dei file DOM e dei suoi vari oggetti che, in VBA, sono principalmente DOMDocument (il documento DOM, ovviamente), IXMLDOMNodeList (elenco di nodi) e IXMLDOMNode (singolo nodo). Per essi e per le loro proprietà e metodi non posso che invitare a leggere, chi non l’avesse fatto, i miei altri post e articoli su OOXML + VBA (dedicati a Excel: con Word si debbono solo tener presenti le diversità, tra cui il prefisso w: anteposto a nodi ecc., comunque le basi sono identiche).
Nota. Altra cosa che va ripetuta ai più distratti: questi esempi trattano, mediante macro di una cartella Excel o documento Word (al limite, PowerPoint) aperta su RAM dati presenti in file xml residenti sul disco. E si scopre, piacevolmente, che le operazioni sono assai veloci.
Ma ecco i preliminari necessari.
- Estrarre in una cartella di file, diciamo C:\MiaCartFattConSchemaXML i componenti del nostro FatturaConSchemaXML.docx, previa solita modifica dell’estensione in .zip.
- Inserire in C:\MiaCartFattConSchemaXML un file Excel macro enabled, putacaso un GestioneCartFattConSchemaXML.xlsm;
- Passare con Alt+F11 nell’Editor VBA e attivare la libreria Microsoft XML, v5, tramite Strumenti > Riferimenti... con quel che deve seguire;
- In un Modulo1 dell’Editor VBA digitare la macro qui di seguito riportata.
Nota. Ho previsto un file .xlsm, ma andava bene anche un .docm. E tutte le macro riportate funzionerebbero, cambiando ThisWorkbook in ThisDocument, più un’altra piccola modifica nelle due ultime macro (v. più avanti). Informo poi che i file su RAM possono anche essere Word o Excel edizione 2003 (attivando la libreria MS XML v5 che già Office 2003 supporta.
Sub ElencaNodiDocumConSchema()
ChDir ThisWorkbook.Path
'MsgBox Dir("*.xml") 'servita per debug
Dim DocXml As DOMDocument
Dim NodiXml As IXMLDOMNodeList, NodoXml As IXMLDOMNode
Set DocXml = New DOMDocument
DocXml.async = False 'inibisce sincronizzazione DOM/origine
DocXml.Load ("word\document.xml") 'carica il docum. DOM
'Seleziona tutti i nodi-testo, per DEBUG
'Set NodiXml = DocXml.SelectNodes("//w:t")
'Seleziona i nodi relativi allo schema XML Fattura
Dim strQyery As String
strQuery = "//w:customXml//w:customXml"
Set NodiXml = DocXml.SelectNodes(strQuery)
Dim NumNodiTesto As Integer
'NumNodiTesto = NodiXml.Length 'Per DEBUG
'MsgBox "Numero nodi-testo: " & NumNodiTesto 'Per DEBUG
Dim TuttoIlTesto As String
Dim NomeCampo As string, DatoCampo As String
For Each NodoXml In NodiXml
i = i + 1
NomeCampo = NodoXml.Attributes(0).Text
DatoCampo = NodoXml.Text
MsgBox "Nodo N. " & i & ":" & vbLf & _
NomeCampo & " = " & DatoCampo
TuttoIlTesto = TuttoIlTesto & " " & DatoCampo
Next
MsgBox TuttoIlTesto, vbInformation, _
"Numero nodi-testo: " & NodiXml.Length
End Sub
Commenti essenziali. La Sub esordisce col rendere corrente la directory in cui giace il nostro file .xlsm, assieme alle parti della fattura con XML, poi carica il componente document.xml in un nuovo DOMDocument mediante DocXml.Load. Segue il (cruciale!) metodo SelectNodes del DocXml, che con opportuna sgtringa di query filtra tutti e soli i nodi facenti parte dello schema XML incorporato, ossia FATTURA. Questo, corrisponde alla tag più esterna w:custom Xml il cui w:element è = “FATTURA”, per l’appunto, al cui interno si danno diverse w:custom Xml il cui w:element è =”NOME”, “COGNOME” e così via. Pensa e ripensa (ce n’è voluta, confesso) la sospirata stringa è risultata "//w:customXml//w:customXml". Eureka! Funziona! Infatti il ciclo For Each NodoXml... Next emette chiari messaggi del tipo “Nodo N. 1: NOME = Giovanni”; “Nodo N. 2:COGNOME = Rinaldi”; eccetera.
Dimenticavo: il nome del campo, per i nodi interni del nodo FATTURA è il primo e unico attributo (w:element) mentre per FATTURA w:element è il secondo, il primo essendo w:uri. In pratica i nomi dei campi (della sequenza) sono dati dalla proprietà Attributes(0) (l’indice di Attributes parte con zero).
Nota. Se alcuni campi non sono stati riempiti dall’utente, non c’è da sorprendersi se il messaggio sarà del tipo “Nodo N. n =”.
Altre macro per i più o meno esperti
Altro non voglio aggiungere, se non invitare a sperimentare altri esempi di query, che vado a fornire senza commenti. Il primo è dato dalla Sub seguente, in cui il filtro è “//w:t” che prende tutti i testi dei vari nodi, indiscriminatamente. Il confronto con la precedente macro è d’obbligo, direi!
Sub ElencaTestiDocumConSchema()
ChDir ThisWorkbook.Path
Dim DocXml As DOMDocument
Dim NodiXml As IXMLDOMNodeList, NodoXml As IXMLDOMNode
Set DocXml = New DOMDocument
DocXml.async = False 'inibisce sincronizzazione DOM/origine
DocXml.Load ("word\document.xml") 'carica il docum. DOM
'Seleziona tutti i nodi-testo
Set NodiXml = DocXml.SelectNodes("//w:t")
Dim TuttoIlTesto As String
Dim DatoCampo As String
For Each NodoXml In NodiXml
i = i + 1
DatoCampo = NodoXml.Text
MsgBox "Nodo N. " & i & ":" & vbLf & DatoCampo
TuttoIlTesto = TuttoIlTesto & " " & DatoCampo
Next
ActiveCell = TuttoIlTesto ‘Testo completo nella cella attiva
MsgBox TuttoIlTesto, vbInformation, "Numero nodi-testo: " & NodiXml.Length
End Sub
Nota. Come anticipato, se il file contenente questa come la successiva Sub è un documento .docx o docm o persino .doc (Word 2003) in luogo di ActiveCell = TuttoIltesto si userà un’istruzione Selection.text = TuttoIlTesto, magari preceduta da ActiveDocument.Words.Last.Select o simili.
La variante seguente usa invece il filtro //w:element, per cui elenca tutti e soli i nomi dei vari campi.
Sub ElencaTuttiElementiDocumConSchema()
ChDir ThisWorkbook.Path
Dim DocXml As DOMDocument
Dim NodiXml As IXMLDOMNodeList, NodoXml As IXMLDOMNode
Set DocXml = New DOMDocument
DocXml.async = False 'inibisce sincronizzazione DOM/origine
DocXml.Load ("word\document.xml") 'carica il docum. DOM
'Seleziona tutti gli attributi dei nodi dell’XML incorporato
Set NodiXml = DocXml.SelectNodes("//@w:element")
Dim TuttoIlTesto As String
Dim NomeCampo As String
For Each NodoXml In NodiXml
i = i + 1
NomeCampo = NodoXml.Text
MsgBox "Nodo N. " & i & ":" & vbLf & NomeCampo
TuttoIlTesto = TuttoIlTesto & " " & NomeCampo
Next
ActiveCell = TuttoIlTesto
MsgBox TuttoIlTesto, vbInformation, "Numero nodi-testo: " & NodiXml.Length
End Sub
Si noterà che i nomi DESCRIZIONE, QUANT, PREZZO ecc. compaiono tante volte quante sono le voci della tabella “segnate” con tali tag dello schema. Inoltre stavolta compare anche FATTURA, il nome della tag più esterna, relativa all’intero schema applicato.
Nota. Si poteva anche omettere tale tag? Forse sì nel caso di un solo schema inserito, ma se un certo documento ne utilizza più d’uno le rispettive tag sono indispensabili, per evitare confusioni.
Chicca finale: somma di importi
Infine una routine che serve a totalizzare i dati del campo IMPORTO (scelta contingente, per semplicità e concretezza). È preceduta dalla funzione ValoreStringa, che traduce in valori i dati forniti dalla proprietà Text e torna utile anche in casi analoghi del mondo Word, ove (come nel documento d’esempio) non è raro che l’utente anteponga il simbolo dell’euro e utilizzi la virgola e il punto come separatori, mentre la funzione standard Val pretende il contrario e, inoltre, darebbe zero con stringhe come “€ 123” (darebbe 123 con “123 €”, ma tanto vale abolire tutti gli €).
Function ValoreStringa(ByVal StringaNum As String) As Double
'Toglie l’eventuale € e i punti delle migliaia
StringaNum = Replace(StringaNum, "€", "")
StringaNum = Replace(StringaNum, ".", "")
'Sostituisci la virgola col punto
StringaNum = Replace(StringaNum, ",", ".")
ValoreStringa = Val(StringaNum)
End Function
Sub SommaImporti()
ChDir ThisWorkbook.Path
Dim DocXml As DOMDocument
Dim NodiXml As IXMLDOMNodeList, NodoXml As IXMLDOMNode
Set DocXml = New DOMDocument
DocXml.async = False
DocXml.Load ("word\document.xml")
Dim strQuery As String
strQuery = "//w:customXml//w:customXml[@w:element='IMPORTO']"
Set NodiXml = DocXml.SelectNodes(strQuery)
Dim i As Integer, DatoCampo As String, Somma As Double
For Each NodoXml In NodiXml
i = i + 1
DatoCampo = NodoXml.Text
MsgBox "Nodo N. " & i & ":" & vbLf & DatoCampo
Somma = Somma + ValoreStringa(DatoCampo)
Next
MsgBox "Importo totale = " & Somma
End Sub
Quanto a SommaImporti c’è solo da dire che la stringa di query, qui sopra evidenziata a dovere, filtra i soli nodi IMPORTO dello schema, che sono tanti quante sono le voci dell’ordine fatturato.