2010-11-03 6 views
4

Je vais avoir un fichier xml comme:Comment lire le fichier XML à un dictionnaire <String, Liste <String>> avec des chaînes vides pour des éléments nuls

<root> 
    <RowDetails RowName="A" ColumnSize="1"> 
    <ColumnDetails ColumnName="A1" /> 
    </RowDetails> 
    <RowDetails RowName="B" ColumnSize="2"> 
    <ColumnDetails ColumnName="B1" /> 
    <ColumnDetails ColumnName="B2" /> 
    </RowDetails> 
    <RowDetails RowName="C" ColumnSize="3"> 
    <ColumnDetails ColumnName="C1" /> 
    <ColumnDetails ColumnName="C2" /> 
    <ColumnDetails ColumnName="C3" /> 
    </RowDetails> 
</root> 

et un dictionnaire comme:

Dictionary<String, List<String>>MyDict = new Dictioanary<String, List<String>>(); 

et je lis le fichier XML à MyDict est comme:

XDocument XDoc = XDocument.Load(Application.StartupPath + @"\foo.xml"); 
MyDict = XDoc.Descendants("RowDetails").ToDictionary(X => X.Attribute("RowName").Value, 
                X => X.Descendants("ColumnDetails") 
                  .Select(Y => Y.Attribute("ColumnName").Value).ToList()); 

maintenant le dictionnaire c j'ai:

"A"   { "A1" } 
"B"   { "B1", "B2" } 
"C"   { "C1", "C2", "C3" } 

mais mon problème est que j'ai besoin de toute la liste avec le même nombre. La chaîne vide doit être ajoutée pour une entrée nulle, le résultat attendu est donc:

"A"   { "A1", "", "" } 
"B"   { "B1", "B2", "" } 
"C"   { "C1", "C2", "C3" } 

Comment puis-je modifier ma requête LINQ?

S'il vous plaît aidez-moi à faire cela en utilisant LINQ.

Merci d'avance.

Répondre

5

En supposant que vous voulez que cela soit fait comme une étape de post-traitement après MyDict est créé, l'idée de base serait:

  1. Découvrez ce que la taille de chaque liste devrait être (basé sur le compte de la plus grande liste).
  2. Tamponnez chaque liste avec des chaînes vides si nécessaire pour l'amener à la bonne taille.

// The required size is the count of the biggest list 
var sizeRequired = MyDict.Values.Max(l => l.Count); 

// Pad each list as necessary 
foreach (var list in MyDict.Values) 
    list.AddRange(Enumerable.Repeat(string.Empty, sizeRequired - list.Count)); 

est ici un moyen de retourner un nouveau dictionnaire avec les caractéristiques requises sans muter la collection originale:

// The required size is the count of the biggest list 
var sizeRequired = MyDict.Values.Max(l => l.Count); 

// Each string key should map to a key in the new dictionary 
// Each List<string> value should map to a new list, padded as necessary. 
var paddedDict = MyDict.ToDictionary 
    (
    kvp => kvp.Key, 
    kvp => kvp.Value 
       .Concat(Enumerable.Repeat(string.Empty, sizeRequired - kvp.Value.Count)) 
       .ToList() 
); 
+0

(+1). Pourriez-vous s'il vous plaît suggérer une méthode dans LINQ (au lieu de 'Foreach') –

+0

@Pramodh: Est-ce que ça va? – Ani

+0

oui ... Maintenant, j'utilise comme int i = XDoc.Descendants ("RowDetails"). Max (X => Convert.ToInt32 (X.Attribute ("ColumnSize"). Valeur)); ' –

-1

Je ne sais pas s'il y a un moyen " dans LINQ "... Je ferais probablement ce que vous voulez faire en code C# aussi.

Mais, d'où vient cette exigence? pourrait-il être que le code devrait être changé là où il s'attend à ce que toutes les listes dans votre dictionnaire soient de la même taille?

2

Vous pouvez créer une méthode d'extension sur IEnumerable<T> au pad à une taille minimale, en ligne:

public static class IEnumerableExtensions 
{ 
    public static IEnumerable<TSource> PadIfFewerThan<TSource>(
     this IEnumerable<TSource> items, 
     int size, TSource padValue = default(TSource)) 
    { 
     int count = 0; 

     foreach (var item in items) 
     { 
      ++count; 
      yield return item; 
     } 

     foreach (var index in Enumerable.Range(count, size - count)) 
      yield return padValue; 
    } 
} 

Votre code résultant ressemblera:

var dictionary = doc.Descendants("RowDetails") 
    .ToDictionary(
     x => x.Attribute("RowName").Value, 
     x => x.Descendants("ColumnDetails") 
      .Select(y => y.Attribute("ColumnName").Value) 
      .PadIfFewerThan(3, "") 
     ); 

foreach (var entry in dictionary) 
    Console.WriteLine(@"""{0}"" {{""{1}""}}", 
     entry.Key, 
     string.Join(@""",""", entry.Value) 
     ); 

"A" { "A1 », "", ""}
"B"{ "B1", "B2", ""}
"C"{ "C1", "C2", "C3"}

Si vous devez avoir un Dictionary<string, List<string>>, plutôt que le Dictionary<string, IEnumerable<string>> qui est retourné ici, il suffit de mettre votre .ToList() retour dans le mix:

var dictionary = doc.Descendants("RowDetails") 
    .ToDictionary(
     x => x.Attribute("RowName").Value, 
     x => x.Descendants("ColumnDetails") 
      .Select(y => y.Attribute("ColumnName").Value) 
      .PadIfFewerThan(3) 
      .ToList() 
     ); 
+0

(+1): Nice extension .... mais je ne comprends pas pourquoi il y a deux 'PadIfFewerThan' –

+0

Une autre option (que je prendrais personnellement si je le pouvais) est de se débarrasser de cette sérialisation XML personnalisée, et d'utiliser le sérialiseur XML par défaut où possible . Je trouve la «lisibilité/éditabilité humaine» de XML surestimée;) –

+0

@Pramodh: Vous obtenez 3 surcharges de méthodes différentes ici, pour le prix d'implémentation. Je pense que si vous pouvez le rendre générique rapidement, d'une manière propre (surtout sans bug), pourquoi pas :) –