2012-01-05 20 views
6

Decir que tengo un XmlDocument que generará que tiene InnerXml que tiene este aspecto:nodos XML Ordenar por Alpha.Numeric usando C#

<ORM_O01> 
    <MSH> 
    <MSH.9> 
     <MSG.2>O01</MSG.2> 
    </MSH.9> 
    <MSH.6> 
     <HD.1>13702</HD.1> 
    </MSH.6> 
    </MSH> 
    <ORM_O01.PATIENT> 
    <PID>  
    <PID.18> 
     <CX.1>SecondTestFin</CX.1> 
    </PID.18> 
    <PID.3> 
     <CX.1>108</CX.1> 
    </PID.3> 
    </PID> 
    </ORM_O01.PATIENT> 
</ORM_O01> 

Como se puede ver nodo <PID.18> es antes nodo <PID.3>. (<MSH.9> es también anterior a <MSH.6>.)

La reestructuración de mi generación haría que mi buen código de limpieza se volviera muy complicado.

¿Hay alguna manera de ordenar los nodos para que clasifique alfa hasta que llegue al último período y luego ordene numérico (si los últimos valores son números)?

Por "clasificación numérica" ​​me refiero a que verá el número entero en lugar de char por char. (Entonces 18> 3).

Respuesta

3

La respuesta obvia es sí.

Si este es el resultado deseado:

<ORM_O01> 
    <MSH> 
    <MSH.6> 
     <HD.1>13702</HD.1> 
    </MSH.6> 
    <MSH.9> 
     <MSG.2>O01</MSG.2> 
    </MSH.9> 
    </MSH> 
    <ORM_O01.PATIENT> 
    <PID> 
     <PID.3> 
     <CX.1>108</CX.1> 
     </PID.3> 
     <PID.18> 
     <CX.1>SecondTestFin</CX.1> 
     </PID.18> 
    </PID> 
    </ORM_O01.PATIENT> 
</ORM_O01> 

Entonces esta clase lo hará: (Debería pagan por esto ...)

using System; 
using System.IO; 
using System.Linq; 
using System.Xml.Linq; 

namespace Test 
{ 
    public class SortXmlFile 
    { 
     XElement rootNode; 

     public SortXmlFile(FileInfo file) 
     { 
      if (file.Exists) 
       rootNode = XElement.Load(file.FullName); 
      else 
       throw new FileNotFoundException(file.FullName); 
     } 

     public XElement SortFile() 
     { 
      SortElements(rootNode); 
      return rootNode; 
     } 

     public void SortElements(XElement root) 
     { 
      bool sortWithNumeric = false; 
      XElement[] children = root.Elements().ToArray(); 
      foreach (XElement child in children) 
      { 
       string name; 
       int value; 
       // does any child need to be sorted by numeric? 
       if (!sortWithNumeric && Sortable(child, out name, out value)) 
        sortWithNumeric = true; 
       child.Remove(); // we'll re-add it in the sort portion 
       // sorting child's children 
       SortElements(child); 
      } 
      // re-add children after sorting 

      // sort by name portion, which is either the full name, 
      // or name that proceeds period that has a numeric value after the period. 
      IOrderedEnumerable<XElement> childrenSortedByName = children 
        .OrderBy(child => 
         { 
          string name; 
          int value; 
          Sortable(child, out name, out value); 
          return name; 
         }); 
      XElement[] sortedChildren; 
      // if needed to sort numerically 
      if (sortWithNumeric) 
      { 
       sortedChildren = childrenSortedByName 
        .ThenBy(child => 
         { 
          string name; 
          int value; 
          Sortable(child, out name, out value); 
          return value; 
         }) 
         .ToArray(); 
      } 
      else 
       sortedChildren = childrenSortedByName.ToArray(); 

      // re-add the sorted children 
      foreach (XElement child in sortedChildren) 
       root.Add(child); 
     } 

     public bool Sortable(XElement node, out string name, out int value) 
     { 
      var dot = new char[] { '.' }; 
      name = node.Name.ToString(); 
      if (name.Contains(".")) 
      { 
       string[] parts = name.Split(dot); 
       if (Int32.TryParse(parts[1], out value)) 
       { 
        name = parts[0]; 
        return true; 
       } 
      } 
      value = -1; 
      return false; 
     } 
    } 
} 

Alguien puede ser capaz de escribir este es más limpio y más malo, pero esto debería ayudarte.

2

utilizando System.Xml.Linq, este código puede ayudarlo.

Uso:

string xmlString= 
    @" 
    ....your string..... 
    "; 

XDocument xDoc = XDocument.Load(new StringReader(xmlString)); 
XDocument newXDoc = SortXml(xDoc); 
Console.WriteLine(newXDoc); 

SortXml función:

XDocument SortXml(XDocument xDoc) 
{ 
    Func<XElement, string> keyBuilder = 
     s => s.Name.ToString().Split('.') 
      .Aggregate("",(sum, str) => sum += str.PadLeft(32,' ')); 

    XElement root = new XElement(xDoc.Root.Name); 
    SortXml(root, xDoc.Elements(), keyBuilder); 
    return new XDocument(root); 
} 

void SortXml(XElement newXDoc, IEnumerable<XElement> elems, Func<XElement, string> keyBuilder) 
{ 
    foreach (var newElem in elems.OrderBy(e => keyBuilder(e))) 
    { 
     XElement t = new XElement(newElem); 
     t.RemoveNodes(); 
     newXDoc.Add(t); 
     SortXml(t, newElem.Elements(), keyBuilder); 
    } 
} 
3

estaba interesado en su pregunta aquí está mi granito de arena.

Implementé IComparer<T> para manejar las comparaciones de elementos y dos métodos que manejan la recursión. El código podría limpiarse un poco, pero he pegado el código de la aplicación de la consola que creé para mostrarle mi solución, que creo que funcionó bien.

Editar:Para que esto sea más fácil de leer que he roto esto abajo en las piezas de la base de que me he dejado la aplicación de consola funcional

IComparer<T> implementación:

public class SplitComparer : IComparer<string> 
{ 
    public int Compare(string x, string y) 
    { 
     var partsOfX = x.Split('.'); 

     int firstNumber; 
     if (partsOfX.Length > 1 && int.TryParse(partsOfX[1], out firstNumber)) 
     { 
      var secondNumber = Convert.ToInt32(y.Split('.')[1]); 

      return firstNumber.CompareTo(secondNumber); 
     } 

     return x.CompareTo(y); 
    } 
} 

Métodos para el manejo de la recursión:

private static XElement Sort(XElement element) 
{ 
    var xe = new XElement(element.Name, element.Elements().OrderBy(x => x.Name.ToString(), new SplitComparer()).Select(x => Sort(x))); 

    if (!xe.HasElements) 
    { 
     xe.Value = element.Value; 
    } 

    return xe; 
} 

private static XDocument Sort(XDocument file) 
{ 
    return new XDocument(Sort(file.Root)); 
} 

functi onal aplicación de consola:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net; 
using System.IO; 
using System.Xml.Linq; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var xml = @"<ORM_O01> 
          <ORM_O01.PATIENT> 
          <PID>  
          <PID.18> 
           <CX.1>SecondTestFin</CX.1> 
          </PID.18> 
          <PID.3> 
           <CX.1>108</CX.1> 
          </PID.3> 
          </PID> 
          </ORM_O01.PATIENT> 
          <MSH> 
          <MSH.9> 
           <MSG.2>O01</MSG.2> 
          </MSH.9> 
          <MSH.6> 
           <HD.1>13702</HD.1> 
          </MSH.6> 
          </MSH> 
         </ORM_O01>"; 

      var xDoc = XDocument.Parse(xml); 

      var result = Sort(xDoc); 

      Console.WriteLine(result.ToString()); 

      Console.Read(); 
     } 

     private static XElement Sort(XElement element) 
     { 
      var xe = new XElement(element.Name, element.Elements().OrderBy(x => x.Name.ToString(), new SplitComparer()).Select(x => Sort(x))); 

      if (!xe.HasElements) 
      { 
       xe.Value = element.Value; 
      } 

      return xe; 
     } 

     private static XDocument Sort(XDocument file) 
     { 
      return new XDocument(Sort(file.Root)); 
     } 
    } 

    public class SplitComparer : IComparer<string> 
    { 
     public int Compare(string x, string y) 
     { 
      var partsOfX = x.Split('.'); 

      int firstNumber; 
      if (partsOfX.Length > 1 && int.TryParse(partsOfX[1], out firstNumber)) 
      { 
       var secondNumber = Convert.ToInt32(y.Split('.')[1]); 

       return firstNumber.CompareTo(secondNumber); 
      } 

      return x.CompareTo(y); 
     } 
    } 
} 
1

Sin embargo, otro intento, utilizando un modificado Dotnet.Commons .xml.

Obtiene la clase XmlUtils here.

Crear un comparador personalizado que tiene toda su lógica (esto le ayudará a ir)

public class CustomComparer : IComparer 
{ 
    public int Compare(object x, object y) 
    { 
     string o1 = x as string; 
     string o2 = y as string; 

     string[] parts1 = o1.Split('.'); 
     string[] parts2 = o2.Split('.'); 

     // Assuming first part is alpha, last part is numeric and both of them has second part. Otherwise compare original ones. 
     if (parts1.Length < 2 || parts2.Length < 2) 
      return o1.CompareTo(o2); 

     if (parts1[0].Equals(parts2[0])) 
     { 
      // Do a numeric compare 
      return int.Parse(parts1[parts1.Length - 1]).CompareTo(int.Parse(parts2[parts2.Length - 1])); 
     } 
     else 
     { 
      // Just compare the first part 
      return parts1[0].CompareTo(parts2[0]); 
     } 
    } 

A continuación, modifique la función XmlUtils SortElements, añadir esto a la parte superior:

CustomComparer comparador = new CustomComparer ();

y cambiar la línea:

if (String.Compare(node.ChildNodes[i].Name, node.ChildNodes[i-1].Name, true) < 0) 

a

if (comparer.Compare(node.ChildNodes[i].Name, node.ChildNodes[i - 1].Name) < 0) 
Cuestiones relacionadas