Cómo eliminar atributos duplicados de XML con C #

c# html-agility-pack validation xml

Pregunta

Estoy analizando algunos archivos XML de un proveedor externo y, lamentablemente, no siempre es un XML bien formado, ya que a veces algunos elementos contienen atributos duplicados.

No tengo control sobre la fuente y no sé qué elementos pueden tener atributos duplicados ni conozco los nombres de atributos duplicados de antemano.

Obviamente, cargar el contenido en un objeto XMLDocument genera una XmlException en los atributos duplicados, así que pensé que podría usar un XmlReader para pasar el elemento XML por elemento y tratar con los atributos duplicados cuando llegue al elemento ofensivo.

Sin embargo, la XmlException se XmlException en reader.Read() - antes de que tenga la oportunidad de incluir los atributos del elemento.

Aquí hay un método de muestra para demostrar el problema:

public static void ParseTest()
{
    const string xmlString = 
        @"<?xml version='1.0'?>
        <!-- This is a sample XML document -->
        <Items dupattr=""10"" id=""20"" dupattr=""33"">
            <Item>test with a child element <more/> stuff</Item>
        </Items>";

    var output = new StringBuilder();
    using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
    {
        XmlWriterSettings ws = new XmlWriterSettings();
        ws.Indent = true;
        using (XmlWriter writer = XmlWriter.Create(output, ws))
        {
            while (reader.Read())   /* Exception throw here when Items element encountered */
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        writer.WriteStartElement(reader.Name);
                        if (reader.HasAttributes){ /* CopyNonDuplicateAttributes(); */}
                        break;
                    case XmlNodeType.Text:
                        writer.WriteString(reader.Value);
                        break;
                    case XmlNodeType.XmlDeclaration:
                    case XmlNodeType.ProcessingInstruction:
                        writer.WriteProcessingInstruction(reader.Name, reader.Value);
                        break;
                    case XmlNodeType.Comment:
                        writer.WriteComment(reader.Value);
                        break;
                    case XmlNodeType.EndElement:
                        writer.WriteFullEndElement();
                        break;
                }
            }

        }
    }
    string str = output.ToString();
}

¿Hay otra forma de analizar la entrada y eliminar los atributos duplicados sin tener que usar expresiones regulares y manipulación de cadenas?

Respuesta aceptada

Encontré una solución pensando en el XML como un documento HTML. Luego, utilizando la biblioteca de código abierto Agtation Pack Html , pude obtener XML válido.

El truco era guardar el xml con un encabezado HTML primero.
Así que reemplaza la declaración XML
<?xml version="1.0" encoding="utf-8" ?>
con una declaración HTML como esta:
!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

Una vez que los contenidos se guardan en un archivo, este método devolverá un documento XML válido.

// Requires reference to HtmlAgilityPack
public XmlDocument LoadHtmlAsXml(string url)
{
    var web = new HtmlWeb();

    var m = new MemoryStream();
    var xtw = new XmlTextWriter(m, null);

    // Load the content into the writer
    web.LoadHtmlAsXml(url, xtw);

    // Rewind the memory stream
    m.Position = 0;

    // Create, fill, and return the xml document
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml((new StreamReader(m)).ReadToEnd());
    return xmlDoc;
}

Los nodos de atributos duplicados se eliminan automáticamente con los valores de atributos posteriores que sobrescriben los anteriores.


Respuesta popular

Ok, creo que necesitas atrapar el error:

Entonces deberías poder usar los siguientes métodos:

reader.MoveToFirstAttribute();

y

reader.MoveToNextAttribute()

para obtener las siguientes propiedades:

reader.Value
reader.Name

Esto le permitirá obtener todos los valores de atributo.



Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué