使用相同的键值解析Xml

时间:2014-05-30 07:08:19

标签: c# wpf windows-phone-8 xml-parsing linq-to-xml

我正在使用Windows Phone 8应用程序,

我有一些看起来像这样的用户界面:

主要项目A ---将其desc和子项列表作为键值

主要项目B ---将其desc和子项列表作为键值

主要项目C ---将其desc和子项列表作为键值

现在单击A移动到下一页,将显示其描述及其子项。

点击主要项目A

主要项目A的描述

子项1 ---点击此显示其desc 子项目2 ---点击此显示其desc

以下是Xml的样子:

<plist version="1.0">

    <dict>
        <key>Category</key>
        <array>
            <dict>
                <key>Name</key>
                <string>A</string>
                <key>Description</key>
                <string>Some data</string>
                <key>SubItems</key>
                <array>
                    <dict>
                        <key>Description</key>
                        <string>Some data</string>
                        <key>Name</key>
                        <string>One</string>
                    </dict>
                    <dict>
                        <key>Name</key>
                        <string>Two</string>
                        <key>Description</key>
                        <string>Some data</string>
                    </dict>

                </array>
            </dict>
            <dict>
                <key>Name</key>
                <string>B</string>
                <key>Description</key>
                <string>Some data</string>
                <key>SubItems</key>
                <array>
                    <dict>
                        <key>Description</key>
                        <string>Some data</string>
                        <key>Name</key>
                        <string>One</string>
                    </dict>
                    <dict>
                        <key>Name</key>
                        <string>Two</string>
                        <key>Description</key>
                        <string>Some data</string>
                    </dict>

                </array>
            </dict>

如何解析这个例子?

更新

我这样解决了:

Dictionary<string, List<Tricks>> plistData =
                    doc.Root.Element("dict").Element("array").Elements("dict")
                        .Select(GetValues)
                        .ToDictionary(v => (string)v["Name"],
                                      v => v["SubItems"]
                                      .Elements("dict").Select(Parse).ToList());

static Tricks Parse(XElement dict)
        {
            var values = GetValues(dict);

            return new Tricks
            {
                SubTitle = (string)values["Name"],
                SubTitleDescription = (string)values["Description"]
            };
        }

static Dictionary<string, XElement> GetValues(XElement dict)
        {
            return dict.Elements("key")
                       .ToDictionary(k => (string)k, k => (XElement)k.NextNode);
        }

在上面我能够得到除except MainTitle Description之外的所有内容,你能不能帮我纠正一下。

1 个答案:

答案 0 :(得分:1)

您正试图在数据模型中压缩3条信息(名称,描述和子项目列表),该数据模型只有两个项目(一个键和一个值)的空间。

我提供两种解决方案。一个“修复”代码中的问题,另一个是更灵活的解决方案。选择你喜欢的任何一个

的QuickFix

最大的变化是字典不再返回字符串而是返回一个完整的Tricks对象。

public Dictionary<Tricks, List<Tricks>> Clumsy(XDocument doc)
{
    var plistData =
        doc
            .Root
            .Element("dict")
            .Element("array")
            .Elements("dict")
            .Select( ele => new     
                {
                    key = Parse(ele),
                    val = ele.Element("array")
                           .Elements("dict")
                           .Select(Parse).ToList()
                }).ToDictionary(pair => pair.key,
                                pair => pair.val);
    return plistData;
}

static Tricks Parse(XElement dict)
{
    var values = GetValues(dict);

    return new Tricks
    {
        SubTitle = (string)values["Name"],
        SubTitleDescription = (string)values["Description"]
    };
}

static Dictionary<string, XElement> GetValues(XElement dict)
{
    return dict.Elements("key")
               .ToDictionary(k => (string)k, k => (XElement)k.NextNode);
}

更灵活的解决方案

假设你像一个MenuRoot类一样,它拥有一个Menu项集合,而这些集合又可以保存一组Menu项目,我使用下面的PlistParser类来返回上面提到的类模型。

public class PListParser
{
    public T Deserialize<T>(Stream stream) where T : new()
    {
        return Deserialize<T>(XDocument.Load(stream));
    }

    public T Deserialize<T>(string xml) where T:new()
    {
        return Deserialize<T>(XDocument.Parse(xml));
    }

    private T Deserialize<T>(XDocument doc) where T : new()
    {
        return DeserializeObject<T>(
            doc.Document.
            Element("plist").
            Element("dict"));
    }

    // parse th xml for an object
    private T DeserializeObject<T>(XElement dict) where T:new()
    {
        var obj = new T();
        var objType = typeof (T);

        // get either propertty names or XmlElement values
        var map = GetMapping(objType);

        // iterate over the key elements and match them against
        // the names of the properties of ther class
        foreach (var key in dict.Elements("key"))
        {
            var pi = map[key.Value];
            if (pi != null)
            {
                // the next node is the value
                var value = key.NextNode as XElement;
                if (value != null)
                {
                    // what is the type of that value
                    switch (value.Name.ToString())
                    {
                        case "array":
                            // assume a generic List for arrays
                            // process subelements
                            object subitems = InvokeDeserializeArray(
                                pi.PropertyType.GetGenericArguments()[0],
                                value);
                            pi.SetValue(obj, subitems, null);
                            break;
                        case "string":
                            // simple assignment
                            pi.SetValue(obj, value.Value, null);
                            break;
                        case "integer":
                            int valInt;
                            if (Int32.TryParse(value.Value, out valInt))
                            {
                                pi.SetValue(obj, valInt, null);
                            }
                            break;
                        default:
                            throw new NotImplementedException(value.Name.ToString());
                            break;
                    }
                }
                else
                {
                    Debug.WriteLine("value null");
                }
            }
            else
            {
                Debug.WriteLine(key.Value);
            }
        }
        return obj;
    }

    // map a name to a properyinfo
    private static Dictionary<string, PropertyInfo> GetMapping(Type objType)
    {
        // TODO: Cache..
        var map = new Dictionary<string, PropertyInfo>();
        // iterate over all properties to find...
        foreach (var propertyInfo in objType.GetProperties())
        {
            // .. if it has an XmlElementAttribute on it
            var eleAttr = propertyInfo.GetCustomAttributes(
                typeof (XmlElementAttribute), false);
            string key;
            if (eleAttr.Length == 0)
            {
                // ... if it doesn't the property name is our key
                key = propertyInfo.Name;
            }
            else
            {
                // ... if it does the ElementName given the attribute 
                // is the key.
                var attr = (XmlElementAttribute) eleAttr[0];
                key = attr.ElementName;
            }
            map.Add(key, propertyInfo);
        }
        return map;
    }

    //http://stackoverflow.com/a/232621/578411
    private object InvokeDeserializeArray(Type type, XElement value)
    {
        MethodInfo method = typeof(PListParser).GetMethod(
            "DeserializeArray",
            BindingFlags.Instance | 
            BindingFlags.InvokeMethod | 
            BindingFlags.NonPublic);
        MethodInfo generic = method.MakeGenericMethod(type);
        return generic.Invoke(this, new object[] {value});
    }

    // array handling, returns a list
    private List<T> DeserializeArray<T>(XElement array) where T:new()
    {
        var items = new List<T>();
        foreach (var dict in array.Elements("dict"))
        {
            items.Add(DeserializeObject<T>(dict));
        }
        return items;
    }

}

Classmodel

public class MenuRoot
{
    public List<Tricks> Category { get; set; }
}

public class Tricks
{
    [XmlElementAttribute("Name")]
    public string SubTitle { get; set; }
    [XmlElementAttribute("Description")]
    public string SubTitleDescription { get; set; }
    public List<Tricks> SubItems { get; set; }
}

用法

var parser = new PListParser();
var menu =  parser.Deserialize<MenuRoot>(@"c:\my\path\to\the\plist.xml");