Saltar al contenido

Tenemos un XML con esta forma, que tiene definido un espacio de nombres para algunas etiquetas (véase el ns4). Aparentemente su lectura con Linq to XML es sencilla, con un simple Descendats o Elements nos dará los resultados que queremos... pero algo va mal... cunden los nervios y no aparecen los resultados, podemos llegar a realizar combinaciones imposibles y no caer que en el XML viene con el espacio de nombre en las etiquetas que hacen referencia a un URL. Este es un ejemplo:

<DatosAdicionales>
     <ns4:Prueba atributo="0">
       <Prueba2 causa="blablabla">
           <Prueba5 descriptivo="dasdasdasd" porcentaje="100.00" importe="0.00" />
       </Prueba2>
     </ns4:Prueba>
     <ns4:Prueba/>
 </DatosAdicionales>

Si nos encontramos con el problema de que Linq to XML no lee al hacer:

 //cargamos el xml en la variable "xml" .....
 var XML1 = (from c in xml.Descendants("DatosAdicionales") select c).FirstOrDefault();
 var XML2= (from c in XML1.Descendants("Prueba") select c).FirstOrDefault(); 
// NULL -&gt; No da resultado¿?¿?¿

Para que consiga leer tenemos que tener definido el espacio de nombre previamente con XNamespace:

XNamespace ns4 = "http://url de definicion....";
var XML2= (from c in XML1.Descendants(ns4 + "Prueba") select c).FirstOrDefault(); // Éxito¡

En este hilo más info: http://stackoverflow.com/questions/2338512/understanding-linq-to-xml-descendants-return-no-results

linqtoxml

Estos días he estado bastante liado ya que el proceso que teníamos de carga de XML realizaba una precarga en memoria del archivo (método Load()), la solución que hay es bastante sencilla con un XMLReader, os pongo un fragmento que seguro se entiende:

using (XmlReader reader = XmlReader.Create(directoryTemp + fichero))
{
 reader.MoveToContent();
 
 int count = 0; // una variable contador XElement nombretabla = null; // un XElement auxiliar
 
 while (!reader.EOF)
 {
 switch (reader.NodeType)
 {
 case XmlNodeType.Element: // se puede filtrar por tipo de XmlNodeType...
 if (count == 0)
 {
 // CUALQUIER CONDICION
 count++;
 reader.Read();
 
 } else if (count == 1)
 {
 nombretabla = new XElement((XName)reader.Name); // esto para crear un XELEMENT con el nombre del Reader actual
 count++;
 reader.Read();
 } else
 {
 if (string.IsNullOrEmpty(nodoReg)) nodoReg = reader.Name; // condicion para omitir anidados..
 
 if (nodoReg == reader.Name)
 {
 count++;
 nombretabla.Add(XElement.ReadFrom(reader) as XElement);
 
 // INSERCION .. cada X lanzo una inserción en BD
 if (count % numInserciones == 0)
 {
 nombrecondicionado.Add(nombretabla);
 docaux.Add(nombrecondicionado);
 
 // VALIDACION ...
 validado = ValidarEsquema(docaux, esquema);
 
 if (validado)
 {
 nreg += cond.Insertar(docaux, delete);
 delete = false;
 
 // vacio contenidos
 nombretabla.Elements().Remove();
 nombrecondicionado.Elements().Remove();
 docaux.Elements().Remove();
 }
 else
 {
 OnReportInformation(string.Format("ERROR fichero{0} no valido.", fichero), ReportLevel.Error);
 EstadoCarga = 2;
 }
 }
 
 }
 
 }
 break;
 
 default :
 reader.Read();
 break;
 
 }
 
 }
 // INSERCION DE LOS ULTIMOS QUE NO ENTRAN EN EL ÚLTIMO WHILE
 nombrecondicionado.Add(nombretabla);
 docaux.Add(nombrecondicionado);
 
 // VALIDACION
 validado = ValidarEsquema(docaux, esquema);
 
 if (validado)
 {
 nreg += cond.Insertar(docaux, delete);
 
 // vacio contenidos
 nombretabla.Elements().Remove();
 nombrecondicionado.Elements().Remove();
 docaux.Elements().Remove();
 }
 else
 {
 OnReportInformation(string.Format("ERROR fichero{0} no valido.", fichero), ReportLevel.Error);
 EstadoCarga = 2;
 }
 
}

La diferencia es sencilla, mientra Descendants() cuenta todos los nodos (anidados incluidos), Elements() solo cuenta los del mismo nivel.

string xml = @"
                <Root>
                    <Item>
                        <id>1</id>
                    </Item>
                    <Item>
                        <id>2</id>
                    </Item>
                    <Item>
                        <id>3</id>
                        <Items>
                            <Item>
                                <id>5</id>
                            </Item>
                            <Item>
                                <id>6</id>
                            </Item>                            
                        </Items>
                    </Item>
                    <Item>
                        <id>4</id>
                    </Item>
                </Root>";
 
            XDocument doc1 = XDocument.Parse(xml);
 
            var q1 = from e in doc1.Root.Descendants("Item")
                     select e;
 
            var q2 = from e in doc1.Root.Elements("Item")
                     select e;
 
            int c1 = q1.Count(); //6
            int c2 = q2.Count(); //4

1

Siempre cargaba los documentos XML del streamReader de esta forma:

XDocument myXmlDocument = XDocument.Load(myReader, LoadOptions.None);

Hasta que hace unos días me salía un error con un documento que decía:

'.', valor hexadecimal 0x00, es un carácter no válido. Línea XXX, posición XX. (casualmente siempre me indicaba la última línea)

Bueno... y como podía ser esto posible... si yo veía el XML correcto, muy bien a ciencia cierta no lo se pero encontré una solución que al menos lo que hace es eliminar todos los caracteres hexadecimales de la cadena de carga de XML.

 

Utilizando esta función en mi caso:

public static string CleanInvalidXmlChars(string text)        {           
// From xml spec valid chars:             
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]                 
// any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.             
string re = @"[^x09x0Ax0Dx20-xD7FFxE000-xFFFDx10000-x10FFFF]";            
return Regex.Replace(text, re, "");        
}

Y cambiando el Load por el Parse:

XDocument myXmlDocument = XDocument.Parse( CleanInvalidXmlChars(myReader.ReadToEnd()), LoadOptions.None);