2015-07-31 4 views
0

J'essaie d'ajouter un attribut "index" à tous les nœuds XML en utilisant la récursivité en C#. Mon problème est que lorsque j'essaie d'ajouter un attribut à un nœud qui n'a pas de nœuds enfants, il échoue avec une référence nulle. par exemple, pour un simple fichier XML (html) Je veux que ce soit comme ça:Indexation XML récursivement avec xmldocument

<div index="1"> 
    <div index="1.1"> 
     <h2 index="1.1.1">some text1</h2> 
     <h2 index="1.1.2">some text</h2> 
    </div> 
</div> 
<div index="2"> 
    <table index="2.1"> 
     <tr index="2.1.1"> 
      <td index="2.1.1.1">some cell</td> 
      <td index="2.1.1.2">some cell</td> 
     </tr> 
     <tr index="2.1.2"> 
      <td index="2.1.2.1">some cell</td> 
     </tr> 
    </table> 
</div> 
<div index="3"> 
    <h1 index="3.1">some text</h1> 
</div> 

Ma fonction ressemble maintenant à ceci:

public static string TraverseNodes(XmlNode node,XmlDocument xmlDoc,bool isChild) 
     { 
      int i = 1; 
      foreach (XmlNode subNode in node) 
      { 

       var child = subNode.ChildNodes[i]; 
       if (subNode.ChildNodes[i] != null) 
       { 
        XmlAttribute typeAttr = xmlDoc.CreateAttribute("realIndex"); 
        typeAttr.Value = (isChild ? (i+ ".") : "") + (i + 1); 
        subNode.Attributes.Append(typeAttr); 
       } 


       i++; 
       TraverseNodes(subNode, xmlDoc, isChild); 
      } 
      return PrintXml(xmlDoc); 

     } 

peut-être mon approche tout est faux. je serais heureux pour toute aide.

+0

peut-on utiliser LINQ pour XML au lieu de XmlDocument? Il est certainement possible d'écrire ce code avec XmlDocument, mais LINQ to XML est beaucoup plus propre ... (Votre échantillon serait plus facile à lire avec indentation, btw ...) –

+0

Cela peut être LINQ. Une bonne idée comment l'obtenir? \ – Basilf

+0

Vous avez déjà du code - que se passe-t-il quand vous essayez? (Et pourquoi renvoyez-vous une chaîne de cela?) –

Répondre

0

1Ok, ceci est une option. Vous l'appelez avec

AssignIndex(myXmlDoc.DocumentElement,0,0) 

et cela devrait fonctionner (non testé);

public static string AssignIndex(XmlNode node, int nodeIdx, int childIdx) 
    { 
     if (childIdx != 0) { 
      XmlAttribute typeAttr = xmlDoc.CreateAttribute("realIndex"); 
      typeAttr.Value = (nodeIdx == 0 ? "": (nodeIdx+ ".")) + childIdx; 
      node.Attributes.Append(typeAttr); 
     } 
     int i=1; 
     foreach (XmlNode subNode in node.ChildNodes) 
     { 
      AssignIndex(subNode, childIdx, i++); 
     } 
    } 
3

J'utiliserais LINQ to XML pour cela. Ma première quelque peu inefficace façon de le faire serait:

foreach (var element in doc.Descendants()) 
{ 
    int indexInLevel = element.ElementsBeforeSelf().Count() + 1; 
    var parent = element.Parent; 
    string prefix = parent == null ? "" : (string) parent.Attribute("index") + "."; 
    element.SetAttributeValue("index", prefix + indexInLevel); 
} 

Notez que cela fera élément racine ont un indice de « 1 ». Il repose sur le fait que Descendants parcourt dans l'ordre des documents, de sorte que le parent d'un élément aura déjà un attribut d'index défini avant de passer aux enfants. Maintenant, j'ai dit que c'est assez inefficace, car il faut compter tous les frères et soeurs plus tôt à chaque fois. Vous pouvez rendre la place plus efficace avec récursion, et être un peu plus souple aussi:

public void AssignIndexes(XElement element, string prefix, int index) 
{ 
    string value = prefix + index; 
    element.SetAttributeValue("index", value); 
    value += "."; // As the prefix for all children 
    int subindex = 1; 
    foreach (var child in element.Elements()) 
    { 
     AssignIndexes(child, value, subindex++); 
    } 
} 

public void AssignIndexesToChildren(XElement element) 
{ 
    int subindex = 1; 
    foreach (var child in element.Elements()) 
    { 
     AssignIndexes(child, "", subindex++); 
    } 
} 

Maintenant, vous pouvez appeler AssignIndexesToChildren(doc.Root) et il ignorer l'élément racine, mais créer « 1 » pour le premier enfant, etc.

+0

qui était parfait. Je vous remercie. J'ai seulement changé le sous-index à zéro et ajouté à l'intérieur de la foreach par un. sous-index ++. Une idée de comment faire pour ignorer le premier élément et ne pas l'indexer? – Basilf

+0

@Basilf: Désolé, je voulais utiliser 'subindex ++' dans l'appel de méthode en premier. Voulez-vous dire "ignorer le premier élément dans chaque élément"? Pourquoi ferais-tu ça? –

+0

jon je veux dire l'élément racine. par exemple, en ignorant la balise dans le cas d'un code html – Basilf

1

Je l'ai fait avec XML LINQ, mais le code peut être converti en XML directement

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

namespace ConsoleApplication1 
{ 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      string input = 
       "<html>" + 
       "<body>" + 
       "<div>" + 
       "<div>" + 
       "<h2>some text1</h2>" + 
       "<h2>some text</h2>" + 
       "</div>" + 
       "</div>" + 
       "<div>" + 
       "<table>" + 
       "<tr>" + 
       "<td>some cell</td>" + 
       "<td>some cell</td>" + 
       "</tr>" + 
       "<tr>" + 
       "<td>some cell</td>" + 
       "</tr>" + 
       "</table>" + 
       "</div>" + 
       "<div>" + 
       "<h1>some text</h1>" + 
       "</div>" + 
       "</body>" + 
       "</html>"; 

      XDocument doc = XDocument.Parse(input); 

      XElement body = doc.Descendants("body").FirstOrDefault(); 
      List<int> indexes = new List<int>(); 
      AddIndex(body, indexes); 

     } 
     static void AddIndex(XElement elements, List<int> indexes) 
     { 
      indexes.Add(0); 
      foreach (XElement element in elements.Elements()) 
      { 

       indexes[indexes.Count - 1] += 1; 
       element.Add(new XAttribute("index", string.Join(".",indexes.Select(x => x.ToString())))); 
       if (element.HasElements) 
       { 
        AddIndex(element, indexes); 
       } 

      } 
      indexes.RemoveAt(indexes.Count - 1); 
     } 
    } 
} 
​​