10 de mayo de 2015

Operaciones sobre XML desde C#

1. Se utilizara el archivo contactos.xml para las operaciones, primero se filtraran los resultados a mostrar utilizando Linq to xml

2. Luego se mostrara información especifica del valor de un nodo(no todo el nodo, solo un valor dentro de este), se agregara, modificara y eliminara información dentro de un nodo, ya sea un valor o un atributo.

3. Por ultimo se muestra como consultar toda la información de un nodo(no solo un valor o atributo de este) como agregar y como eliminar todo un nodo no solo un valor o atributo de este.

  
    Juan
    juan@gmail.com
    666666666
    911111111
  
  
    Luis
    luis@gmail.com
    666777777
  
  
    María
    maria@gmail.com
    666888888
    91222222
  

Código Linq2 XML para leer Archivo

Existen varias formas en .NET C# para leer un fichero XML, acá expondremos 2 formas distintas aunque si fuera yo quien tuviese que elegir me quedaría con esta primera variante de código para leer un archivo XML.
Esta primera variante usa el método Descendants que existe en ambas clases (XElement y XDocument):
// Código para leer un fichero XML usando LINQ 2 XML
private static void Linq2XmlLeerFicheroXmlConXElement()
{
  XElement xmlContactos = XElement.Load("Contactos.xml");
 
  //Obtener el Nombre de todos los contactos
  var contactosAll =
    from c in xmlContactos.Descendants("Contacto")
    select c.Element("Nombre").Value;
 
  //Obtener todos los contactos cuyo nombre comiencen con L
  var contactosL =
    from c in xmlContactos.Descendants("Contacto")
    where c.Element("Nombre").Value.StartsWith("L")
    select c;
 
  //Obtener todos los contactos que al menos uno de los Telefonos sea de Tipo Personal
  var contactosTelPers =
    from c in xmlContactos.Descendants("Contacto")
    where null != c.Elements("Telefono").Attributes("Tipo").
                  FirstOrDefault(t => t.Value == "Personal")
    select c;
}

Otra variante de código para obtener el mismo resultado:
// Código para leer un fichero XML usando LINQ 2 XML
private static void Linq2XmlLeerFicheroXml()
{
  XDocument docContactos = XDocument.Load("Contactos.xml");
 
  //Obtener el Nombre de todos los contactos
  var contactosAll =
    from c in docContactos.Elements("Contactos").Elements("Contacto")
    select c.Element("Nombre").Value;
 
  //Obtener todos los contactos cuyo nombre comiencen con L
  var contactosL =
    from c in docContactos.Elements("Contactos").Elements("Contacto")
    where c.Element("Nombre").Value.StartsWith("L")
    select c;
 
  //Obtener todos los contactos que al menos uno de los Telefonos sea de Tipo Personal
  var contactosTelPers =
    from c in docContactos.Elements("Contactos").Elements("Contacto")
    where null != c.Elements("Telefono").Attributes("Tipo").
                  FirstOrDefault(t => t.Value == "Personal")
    select c;
}

Adicionar un elemento a nodos existentes con LINQ to XML

Existen varias formas en .NET C# para crear un nuevo elemento en un árbol XML, acá expondremos 2 formas distintas:
XElement.Add: Se describe por si solo, adiciona un nuevo elemento.
XElement.SetElementValue: Modifica, agrega o elimina un elemento.
Como vemos en el contenido del archivo XML de arriba, nuestro segundo contacto (Luis) tiene un elemento EMail asociado, imaginemos que queremos modificar el Email. Veamos las 2 variantes de código para obtener dicho resultado:

// Método XElement.Add
// Código para adicionar un elemento usando Add (LINQ 2 XML)

private static void Linq2XmlAddEmailUsandoAdd()
{
  XElement xmlContactos = XElement.Load("Contactos.xml");
 
  //Obtener el contacto con nombre Luis
  var contactoLuis =
     (from c in xmlContactos.Descendants("Contacto")
      where c.Element("Nombre").Value.ToUpper() == "LUIS"
      select c).FirstOrDefault();
 
  if (contactoLuis != null)
  {
    string strXml = @"luis1@gmail.com";
    contactoLuis.Add(XElement.Parse(strXml));
  }
}

Modificar un elemento de nodos existentes con LINQ to XML

Si usamos el código que hemos expuesto, en realidad no estamos modificando el Email, sino que estamos adicionando un nuevo Email, es decir para Luis tendríamos ahora 2 Mail, el que tenia anteriormente (luis@gmail.com) y el que recién acabamos de adicionar (luis1@gmail.com). Es decir que no logramos el resultado esperado.
Para lograrlo teníamos que haber eliminado el nodo y crearlo nuevamente con el valor correcto. Veamos ahora el código usando el segundo método:

// Método XElement.SetElementValue
// Código para adicionar un elemento usando SetElementValue (LINQ 2 XML)

private static void Linq2XmlAddEmailUsandoSetElementValue()
{
  XElement xmlContactos = XElement.Load("Contactos.xml");
 
  //Obtener el contacto con nombre Luis
  var contactoLuis =
     (from c in xmlContactos.Descendants("Contacto")
      where c.Element("Nombre").Value.ToUpper() == "LUIS"
      select c).FirstOrDefault();
 
  if (contactoLuis != null)
    contactoLuis.SetElementValue("EMail", luis2@gmail.com);
}

Usando este segundo código, sí logramos el resultado esperado, porque el método SetElementValue lo que hace es buscar dentro del elemento contactoLuis, el nodo o elemento EMail y si existe modifica su valor, en caso contrario lo adiciona. si por el contrario quisiéramos modificar todos los atributos de todos los nodos

XDocument xmlFile = XDocument.Load("Contactos.xml"); 
var query = from c in xmlFile.Elements("Contactos").Elements("contacto")    
            select c; 
foreach (XElement contacto in query) 
{
    contacto.Attribute("attr1").Value = "MyNewValue";
}
xmlFile.Save("Contactos.xml");

Si nuestro objetivo hubiese sido eliminar el nodo Email, podríamos haber usado el método SetElementValue con el segundo parámetro en null:
contactoLuis.SetElementValue("EMail", null);

XElement.SetAttributeValue

Al igual que el método SetElementValue, existe un método SetAttributeValue que establece, agrega o elimina un atributo de un elemento. Pero veamos un ejemplo donde le cambiemos el atributo del teléfono que tiene Luis (cambiarlo de Móvil a Trabajo):

// Código para modificar atributo usando SetAttributeValue (LINQ 2 XML)
private static void Linq2XmlSetAttributeValue()
{
  XElement xmlContactos = XElement.Load("Contactos.xml");
 
  //Obtener el contacto con nombre Luis
  var contactoLuis =
     (from c in xmlContactos.Descendants("Contacto")
      where c.Element("Nombre").Value.ToUpper() == "LUIS"
         && c.Element("Telefono").HasAttributes
      select c).FirstOrDefault();
 
  if (contactoLuis != null)
    contactoLuis.Element("Telefono").SetAttributeValue("Tipo", "Trabajo");
}

Nota: En el código anterior, en la consulta hemos preguntado por la propiedad HasAttributes, en realidad carece de sentido en nuestro ejemplo preguntar por dicha propiedad, porque si el elemento Telefono no tuviese atributos igual crearía el atributo Tipo. De todas formas me pareció interesante incorporar la propiedad para que el lector sepa de su existencia, en ocasiones suele ser de utilidad.

De similar manera que sucede con el SetElementValue, si quisiéramos eliminar un atributo usaríamos el método SetAttributeValue con el segundo parámetro nulo (null).

Seleccionar Información de Nodo Completo

private void buscarEnXML(string idCon)
{
 XDocument miXML = XDocument.Load(@"C:\Prueba\MiDoc.xml");
 
 var nombreusu = from nombre in miXML.Elements("Contactos").Elements("Contacto")
   where nombre.Attribute("Id_Empleado").Value == idCon //Consultamos por el atributo
   select nombre.Element("Nombre").Value; //Seleccionamos el nombre
 
 foreach (string minom in nombreusu)
 {
   MessageBox.Show(minom); //Mostramos un mensaje con el nombre del empleado 
 }
}

Agregar Nuevo Nodo a documento XML

Para agregar un nuevo nodo se usa la propiedad Root del XDocument, ésta propiedad obtiene la raiz del documento, dándonos la posibilidad de agregar los elementos y atributos de manera muy fácil, para esto, he creado un método addNode, que espera los parámetros id_Empleado, nombre y edad; todos del tipo string, el código queda así:
private void addNode(string idEmpleado, string nombre, string edad)
{
 XDocument miXML = XDocument.Load(@"C:\Prueba\MiDoc.xml"); //Cargamos
 miXML.Root.Add(   //Obtiene la raiz del documento (Empleados)                         
  new XElement("Empleado",
  new XAttribute("Id_Empleado", idEmpleado),
  new XElement("Nombre", nombre),
  new XElement("Edad", edad))
               );
 miXML.Save(@"C:\Prueba\MiDoc.xml");
}

Eliminar Nodo de documento XML

Para eliminar un nodo se debe consultar si este existe, luego si se dispone a eliminar elemento a elemento con un foreach, esta es la forma en que yo lo hago, no se si exista una mas optima, (si conoces alguna te agradecería me lo hicieras saber). Creé un método dropNode, que espera el parámetro IdEmpleado del tipo string, el método queda así:
private void dropNode(string idEmpleado)
{
 XDocument miXML = XDocument.Load(@"C:\Prueba\MiDoc.xml"); //Cargar el archivo           
 
 var consul = from persona in miXML.Elements("Empleados").Elements("Empleado")
              where persona.Attribute("Id_Empleado").Value == idEmpleado
              select persona;
 consul.ToList().ForEach(x => x.Remove()); //Remover elemento a elemento.
 miXML.Save(@"C:\Prueba\MiDoc.xml");            
}