MSXML de C++: impresión bonita/sangría de documentos recién creados
pretty-print (5)
Estoy escribiendo archivos XML usando el analizador MSXML, con un contenedor que descargué de aquí: http://www.codeproject.com/KB/XML/JW_CXml.aspx . Funciona muy bien, excepto que cuando creo un nuevo documento a partir del código (para que no se cargue desde el archivo ni lo modifique), el resultado es todo en una gran línea. Me gustaría que los elementos se sangraran muy bien para poder leerlos fácilmente en un editor de texto.
La búsqueda en Google muestra a muchas personas con la misma pregunta, formulada alrededor de 2001 más o menos. Las respuestas usualmente dicen ''aplicar una transformación XSL'' o ''agregar sus propios nodos de espacio en blanco''. Especialmente el último me hace ir% (así que espero que en 2008 haya una manera más fácil de obtener resultados MSXML bonitos. Entonces mi pregunta: ¿está ahí y cómo la uso?
A menos que la biblioteca tenga una opción de formato, la única otra forma es usar XSLT o una impresora bonita externa (creo que htmltidy también puede hacer xml). No parece haber una opción en el proyecto de código lib, pero puede especificar un XSLT hoja de estilos a MSXML.
Aquí hay una versión modificada de la respuesta aceptada que se transformará en la memoria (solo cambia en las últimas líneas pero estoy publicando todo el bloque para la conveniencia de los futuros lectores):
bool CXml::FormatDOMDocument(IXMLDOMDocument *pDoc)
{
// Create the writer
CComPtr <IMXWriter> pMXWriter;
if (FAILED (pMXWriter.CoCreateInstance(__uuidof (MXXMLWriter), NULL, CLSCTX_ALL))) {
return false;
}
CComPtr <ISAXContentHandler> pISAXContentHandler;
if (FAILED (pMXWriter.QueryInterface(&pISAXContentHandler))) {
return false;
}
CComPtr <ISAXErrorHandler> pISAXErrorHandler;
if (FAILED (pMXWriter.QueryInterface (&pISAXErrorHandler))) {
return false;
}
CComPtr <ISAXDTDHandler> pISAXDTDHandler;
if (FAILED (pMXWriter.QueryInterface (&pISAXDTDHandler))) {
return false;
}
if (FAILED (pMXWriter->put_omitXMLDeclaration (VARIANT_FALSE)) ||
FAILED (pMXWriter->put_standalone (VARIANT_TRUE)) ||
FAILED (pMXWriter->put_indent (VARIANT_TRUE)) ||
FAILED (pMXWriter->put_encoding (L"UTF-8")))
{
return false;
}
// Create the SAX reader
CComPtr <ISAXXMLReader> pSAXReader;
if (FAILED(pSAXReader.CoCreateInstance(__uuidof (SAXXMLReader), NULL, CLSCTX_ALL))) {
return false;
}
if (FAILED(pSAXReader->putContentHandler (pISAXContentHandler)) ||
FAILED(pSAXReader->putDTDHandler (pISAXDTDHandler)) ||
FAILED(pSAXReader->putErrorHandler (pISAXErrorHandler)) ||
FAILED(pSAXReader->putProperty (L"http://xml.org/sax/properties/lexical-handler", CComVariant (pMXWriter))) ||
FAILED(pSAXReader->putProperty (L"http://xml.org/sax/properties/declaration-handler", CComVariant (pMXWriter))))
{
return false;
}
// Perform the write
bool success1 = SUCCEEDED(pMXWriter->put_output(CComVariant(pDoc.GetInterfacePtr())));
bool success2 = SUCCEEDED(pSAXReader->parse(CComVariant(pDoc.GetInterfacePtr())));
return success1 && success2;
}
He escrito una secuencia de comandos sed hace un tiempo atrás para la sangría xml básica. Puede usarlo como un penetrador externo si todo lo demás falla (guárdelo en xmlindent.sed y procese su xml con sed -f xmlindent.sed <nombre de archivo> ). Sin embargo, puede necesitar cygwin u otro entorno posix para usarlo.
Aquí está la fuente:
:a
/>/!N;s//n/ /;ta
s/ / /g;s/^ *//;s/ */ /g
/^<!--/{
:e
/-->/!N;s//n//;te
s/-->//n/;D;
}
/^<[?!][^>]*>/{
H;x;s//n//;s/>.*$/>/;p;bb
}
/^<//[^>]*>/{
H;x;s//n//;s/>.*$/>/;s/^ //;p;bb
}
/^<[^>]*//>/{
H;x;s//n//;s/>.*$/>/;p;bb
}
/^<[^>]*[^//]>/{
H;x;s//n//;s/>.*$/>/;p;s/^/ /;bb
}
/</!ba
{
H;x;s//n//;s/ *<.*$//;p;s/[^ ].*$//;x;s/^[^<]*//;ba
}
:b
{
s/[^ ].*$//;x;s/^<[^>]*>//;ba
}
Hrmp, las pestañas parecen estar distorsionadas ... Puede copiar-desperdiciar desde aquí: sangría XML con sed (1)
Prueba esto, lo encontré hace años en la web.
#include <msxml2.h>
bool FormatDOMDocument (IXMLDOMDocument *pDoc, IStream *pStream)
{
// Create the writer
CComPtr <IMXWriter> pMXWriter;
if (FAILED (pMXWriter.CoCreateInstance(__uuidof (MXXMLWriter), NULL, CLSCTX_ALL)))
{
return false;
}
CComPtr <ISAXContentHandler> pISAXContentHandler;
if (FAILED (pMXWriter.QueryInterface(&pISAXContentHandler)))
{
return false;
}
CComPtr <ISAXErrorHandler> pISAXErrorHandler;
if (FAILED (pMXWriter.QueryInterface (&pISAXErrorHandler)))
{
return false;
}
CComPtr <ISAXDTDHandler> pISAXDTDHandler;
if (FAILED (pMXWriter.QueryInterface (&pISAXDTDHandler)))
{
return false;
}
if (FAILED (pMXWriter ->put_omitXMLDeclaration (VARIANT_FALSE)) ||
FAILED (pMXWriter ->put_standalone (VARIANT_TRUE)) ||
FAILED (pMXWriter ->put_indent (VARIANT_TRUE)) ||
FAILED (pMXWriter ->put_encoding (L"UTF-8")))
{
return false;
}
// Create the SAX reader
CComPtr <ISAXXMLReader> pSAXReader;
if (FAILED (pSAXReader.CoCreateInstance (__uuidof (SAXXMLReader), NULL, CLSCTX_ALL)))
{
return false;
}
if (FAILED (pSAXReader ->putContentHandler (pISAXContentHandler)) ||
FAILED (pSAXReader ->putDTDHandler (pISAXDTDHandler)) ||
FAILED (pSAXReader ->putErrorHandler (pISAXErrorHandler)) ||
FAILED (pSAXReader ->putProperty (
L"http://xml.org/sax/properties/lexical-handler", CComVariant (pMXWriter))) ||
FAILED (pSAXReader ->putProperty (
L"http://xml.org/sax/properties/declaration-handler", CComVariant (pMXWriter))))
{
return false;
}
// Perform the write
return
SUCCEEDED (pMXWriter ->put_output (CComVariant (pStream))) &&
SUCCEEDED (pSAXReader ->parse (CComVariant (pDoc)));
}
Incluso mis 2 centavos llegan 7 años después. Creo que la pregunta aún merece una respuesta simple envuelta en unas pocas líneas de código, lo que es posible usando la directiva #import
Visual C ++ y la biblioteca nativa de compatibilidad COM de C ++ (que ofrece punteros inteligentes y error de encapsulado manejo).
Tenga en cuenta que, al igual que la respuesta aceptada, no trata de encajar en la clase CXml
el OP, sino que muestra la idea central. También asumo msxml6
.
Pretty-impresión a cualquier secuencia
void PrettyWriteXmlDocument(MSXML2::IXMLDOMDocument* xmlDoc, IStream* stream)
{
MSXML2::IMXWriterPtr writer(__uuidof(MSXML2::MXXMLWriter60));
writer->encoding = L"utf-8";
writer->indent = _variant_t(true);
writer->standalone = _variant_t(true);
writer->output = stream;
MSXML2::ISAXXMLReaderPtr saxReader(__uuidof(MSXML2::SAXXMLReader60));
saxReader->putContentHandler(MSXML2::ISAXContentHandlerPtr(writer));
saxReader->putProperty(PUSHORT(L"http://xml.org/sax/properties/lexical-handler"), writer.GetInterfacePtr());
saxReader->parse(xmlDoc);
}
Transmisión de archivos
Si necesita escribir una clase de flujo en un archivo, puede escribir el suyo implementando la interfaz IStream
.
Otra solución simple que funciona bien para mí es utilizar la clase Ado Stream:
void PrettySaveXmlDocument(MSXML2::IXMLDOMDocument* xmlDoc, const wchar_t* filePath)
{
ADODB::_StreamPtr stream(__uuidof(ADODB::Stream));
stream->Type = ADODB::adTypeBinary;
stream->Open(vtMissing, ADODB::adModeUnknown, ADODB::adOpenStreamUnspecified, _bstr_t(), _bstr_t());
PrettyWriteXmlDocument(xmlDoc, IStreamPtr(stream));
stream->SaveToFile(filePath, ADODB::adSaveCreateOverWrite);
}
Pegándolo todo
Una función main
simplista muestra esto en acción:
#include <stdlib.h>
#include <objbase.h>
#include <comutil.h>
#include <comdef.h>
#include <comdefsp.h>
#import <msxml6.dll>
#import <msado60.tlb> rename("EOF", "EndOfFile") // requires: /I $(CommonProgramFiles)/System/ado
void PrettyWriteXmlDocument(MSXML2::IXMLDOMDocument* xmlDoc, IStream* stream);
void PrettySaveXmlDocument(MSXML2::IXMLDOMDocument* xmlDoc, const wchar_t* filePath);
int wmain()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
try
{
MSXML2::IXMLDOMDocumentPtr xmlDoc(__uuidof(MSXML2::DOMDocument60));
xmlDoc->appendChild(xmlDoc->createElement(L"root"));
PrettySaveXmlDocument(xmlDoc, L"xmldoc.xml");
}
catch (const _com_error&)
{
}
CoUninitialize();
return EXIT_SUCCESS;
}
// assume definitions of PrettyWriteXmlDocument and PrettySaveXmlDocument go here