Newsgroup: it.comp.lang.perl
Titolo: Indentare un XML creato con XML::DOM

Ho utilizzato XML::DOM per creare un documento XML, quindi creo un file su
disco con il metodo $document->printToFile ma l'output non mi soddisfa.
Cioe', funziona bene per un parser ma manca di indentazione e newline, e
quindi e' poco human readable.
Non ho trovato documentazione per i metodi come Print ecc., ma mi e'
sembrato di capire che non ci sia modo di cambiare questo comportamento.
Allora ho provato a fare il de-parsing su stringa (con toString), quindi
usando XML::LibXML fargliela ri-parsare (usando parse_string) ed usare il
metodo XML::LibXML::Document->toFile per ottenere un output migliore, ma
anche qui nulla: usando $format=1 ottengo lo stesso risultato che con
$format=0, e quindi non va bene, mentre con $format=2 il risultato è
decisamente più leggibile ma di indentazione nemmeno l'ombra...
A me piacerebbe ottenere in output la classica indentazione rientrata a
seconda dei nodi.
I mie files XML sono piuttosto piccoli quindi fare il parsing due volte e'
accettabile.
Cercando su CPAN ho visto che ci sono certi moduli (come
XML::Filter::Reindent ) che potrebbero funzionare, ma sono basati su SAX e
non ho idea di come utilizzarli!
Tra l'altro, ho anche qualche problema nel gestire le lettere accentate, si
accettano suggerimenti su quale encoding utilizzare (e come), ma è un altro
discorso...
Grazie, Nicola

 e newline, e quindi e' poco human readable.
Prima di tutto, una precisazione. Aggiungere "indentazione" a un documento
XML significa *alterarne* il contenuto, giacchè gli a-capo e gli spazi
aggiunti non fanno parte del documento iniziale. Se questa alterazione sia
o meno rilevante, dipende solo dal tipo di applicazione che dovrà usare il
documento.
Ad esempio, se un blocco XML di questo tipo
 
 <p><o>pippo</o><q><u>U</u></q></p>
viene ri-stampato come 
 <p>
  <o>pippo</o>
  <q>
   <u>U</u>
  </q>
 </p>
I tag <p> e <q> conterranno rispettivamente tre e due nodi di testo
aggiuntivi.
L'applicazione può scegliere di ignorare certi contenuti. Alcuni parser
danno la possibilità di scartare automaticamente certi blocchi di
whitespace, ma per lo standard XML quegli spazi sono comunque
significativi. Se provi con XML::DOM, vedrai che ti ritrovi quei blocchi
di spazi come nodi DOM di tipo TEXT_NODE.
Detto questo, probabilmente esiste già qualcosa che fa al caso tuo (io
conosco pochissimo gli strumenti XML del Perl), ma se non esistesse, è
piuttosto semplice scrivere una funzione che produca il tipo di output che
desideri. Ho scritto questa, giusto per rendere l'idea:
 sub xml_nice_print
 {
	my $node = shift;
	my $fh = shift;
	my $level = shift || 0;
	my $indent = shift || 1;
	if( $node->getNodeType() == ELEMENT_NODE )
	{
		if( $indent ) { print $fh ' ' x $level; }
		print $fh "<", $node->getNodeName(), " ";
 
		my $attr = $node->getAttributes;
		my $n;
		my $len;
		$len = $attr->getLength;
		 # stampiamo gli attributi;
 		 # anche qui si potrebbe "indentare",
		 # ma per ora occupiamoci solo dei children...
		for( my $i =  0; $i < $len; ++$i )
		{	$n = $attr->item($i);	
			$n->printToFileHandle($fh);
		}
		print $fh ">";	
		my $prevtype = ELEMENT_NODE;
		my $children = $node->getChildNodes();
		my $len = $children->getLength();
		 # stampiamo i children...
		for( my $i =  0; $i < $len; ++$i )
		{
			$n = $children->item($i);	
			my $indent_flag = 0;
			if( $prevtype == ELEMENT_NODE && 
				$n->getNodeType() == ELEMENT_NODE )
			{
			 # se è un elemento ed è il primo, oppure è
			 # preceduto da un altro elemento,
			 # vado a capo, e lo stampo con "indentazione"
				print $fh "\n";
				$indent_flag = 1;
			}
			xml_nice_print( $n, $fh, $level+1, $indent_flag );
			$prevtype = $n->getNodeType() ;
		}
		if( $indent && $prevtype == ELEMENT_NODE ) 
		{ 
			print $fh "\n", ' ' x $level; 
		}
		print $fh "</", $node->getNodeName(), ">"; 
	}
	else
	{
		$node->printToFileHandle( $fh );
	}
 };
Da usare così:
   my $xml = new XML::DOM::Parser;
   my $doc = $xml->parse( $data );
   xml_nice_print( $doc->getDocumentElement(), \*STDOUT );
Max

    xml_nice_print( $doc->getDocumentElement(), \*STDOUT );
Ciao, la tua precisazione sull'alterazione all'XML e' verissima, solo che in
questo caso mi interessava di piu' poter avere un bell'output perche'
probabilmente qualcuno lavorera' sui files anche a mano.
Detto questo, la tua sub e' esattamente quello che mi serviva, l'ho ritarata
per le mie esigenze e funziona alla grande!
Ora devo vedere come tirarmi fuori dal DOM gli header dell'XML ecc. e poi e'
tutto a posto.
Grazie ancora, Nicola