入坑写代码2——C#解析xml文件续集

Posted 马克布克打字机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了入坑写代码2——C#解析xml文件续集相关的知识,希望对你有一定的参考价值。

        第一次写解析xml文件算是顺利搞定,没想到后来陆续提出了更多的需求,主要是遇到以下几个问题:

  1. load整个文件。

  2. 代码专用了。

  3. 存在多个同名元素。

        



        对于问题1,load整个文件这个问题。既然不能将上G的文件一次性load进内存,那就改用流读取(stream)的形式。在网上找到一个不错的读取函数,IEnumerable<XElement> StreamXElements(string uri, string matchname),输入xmlFileUri,以及需要匹配的元素的名字matchname,即可以IEnumerable的形式返回,这样写代码时可以foreach了。StreamXElements的具体代码如下:

1static IEnumerable<XElement> StreamXElements(string uri, string matchname)
2        {
3            XmlReaderSettings settings = new XmlReaderSettings();
4            settings.IgnoreComments = true;
5            settings.IgnoreWhitespace = true;
6
7            using (XmlReader reader = XmlReader.Create(uri, settings))
8            {
9                reader.MoveToContent();
10                while (!reader.EOF)
11                {
12                    if (reader.NodeType == XmlNodeType.Element
13                        && reader.Name == matchname)
14                    {
15                        XElement el = XElement.ReadFrom(reader) as XElement;
16                        if (el != null)
17                        {
18                            yield return el;
19                        }
20                    }
21                    else
22                    {
23                        reader.Read();
24                    }
25                }
26            }
27        }


       对于问题2,代码专用,因为后边陆续查看了几个xml的Schema,发现什么深度的都有,所以考虑采用递归的形式去解决这块问题,即递归不断的去下钻子代,直到边界条件:1、无子代;或2、子代为item元素。递归这块的伪代码是:

 1tatic Dictionary<string, string> getDic(输入的eleArg,输入的key)
2        {
3            if (eleArg有子代)
4            {
5                if (eleArg的子代是不是item)
6                {
7                    // 有子代,且是item,
8                    // 则按照处理item的方式做处理,返回dic
9                }
10                else
11                {
12                    // 有子代,但不是item
13                    // 则继续下钻,下钻时,将不断延伸的key传递下去
14                    foreach (遍历eleArg的每一个子代eleSon)
15                    {
16                        // MergeDic,合并字典用的
17                        dic = MergeDic(dic, getDic(eleSon, key+=eleArg的name);
18                    }
19                }
20            }
21            else
22            {
23                // 没有子代,到头了。
24                // 则按照处理末端元素的方式,返回dic
25            }
26            return dic;
27        }


        具体代码为:

 1static Dictionary<string, string> getDic(XElement ElementArg, string keyArg)
2        {
3            Dictionary<string, string> dic = new Dictionary<string, string>();
4
5            if (ElementArg.HasElements)
6            {
7                XElement ele = ElementArg.FirstNode as XElement;
8                if (ele.Name.ToString().Equals("item"))
9                {
10                    // 有子代,且是item,则对ElementArg的子代开做处理,返回dic
11                    IEnumerable<XElement> items = from temp in ElementArg.Elements("item")
12                                                  select temp;
13                    foreach (var item in items)
14                    {
15                        string strKey = keyArg + ElementArg.Name.ToString() + "_" + item.Element("name").Value.ToString() + "_";
16                        string strValue = item.Element("value").Value.ToString();
17                        dic.Add(strKey, strValue);
18                    }
19                    return dic;
20                }
21                else
22                {
23                    // 有子代,但不是item,则继续下钻。下钻时,将不断延伸的key传递下去
24                    IEnumerable<XElement> items = ElementArg.Elements();
25                    foreach (var item in items)
26                    {
27                        dic = MergeDic(dic, getDic(item, keyArg + ElementArg.Name.ToString() + "_"));
28                    }
29                }
30            }
31            else
32            {
33                // 没有子代,到头了。则返回 name-value
34                string strKey = keyArg + ElementArg.Name.ToString() + "_";
35                string strValue = ElementArg.Value.ToString();
36                dic.Add(strKey, strValue);
37            }
38
39            return dic;
40        }


        对于问题3,同级别下存在同名元素。由于解决第二个问题时已采用递归的方式,那么同级别下的同名元素,最可能出现的地方,就是对同级别的每一个元素做下钻并返回值做字典合并时(上述伪代码中的MergeDic)会遇到。这里直接采用字段后添加序号的形式来处理,举例:某字段key为AAA,mergeDic遇到时,则新增字段为AAA_1,还有下一个,则为AAA_2。所以这里在MergeDic中加了个while循环判断containsKey,若该序号已存在,则往上累加。MergeDic,和GetSequenceNextCount的具体代码为:

 1static Dictionary<string, string> MergeDic(Dictionary<string, string> dic1, Dictionary<string, string> dic2)
2        {
3            Dictionary<string, string> dicRes = new Dictionary<string, string>(dic1);
4            foreach (string key in dic2.Keys)
5            {
6                string newKey = key;
7                while (dicRes.ContainsKey(newKey))
8                {
9                    newKey = GetSequenceNextCount(newKey);
10                }
11                dicRes.Add(newKey, dic2[key]);
12            }
13
14            return dicRes;
15        }
16
17private static string GetSequenceNextCount(string str)
18        
{
19            string res;
20            int count;
21
22            Regex r = new Regex("\\d+$");
23            var ms = r.Matches(str);
24            if (ms.Count > 0)
25            {
26                int.TryParse(
27                    ms.OfType<Match>().Last().ToString(),
28                    out  count);
29                int nextCount = count + 1;
30                res = str.Substring(0, str.Length - count.ToString().Length) + nextCount.ToString();
31            }
32            else
33            {
34                res = str + "1";
35            }
36
37            return res;
38        }


        解决三个问题后,解析xml文件的主体就只剩下几行了(见下边代码块)。比如还是处理Topological.xml文件,对每一个Topological元素,只需一句dic = getDic(MatchEle, "")即可。

 1static void Main(string[] args)
2        
{
3            string strFileUri = @"D:\aaa.xml";
4            string strMatchname = "TrailNtwProtection";
5            IEnumerable<XElement> MatchElements = StreamXElements(strFileUri, strMatchname);
6
7            Dictionary<string, string> dic = new Dictionary<string, string>();
8            foreach (var MatchEle in MatchElements)
9            {
10                dic = getDic(MatchEle, "");
11                // do sth with dic
12                dic.Clear();
13            }
14        }


        比如TopologicalLink.xml文件:

        

        比如TrailNtwProtection.xml文件:


        就这样,不管采集平台那边来什么xml文件都可以解析了。下一步就看看窗口界面怎么写了,定时更新、多个文件的勾选、单击运行、FTP的文件目录、下载、解压缩。

以上是关于入坑写代码2——C#解析xml文件续集的主要内容,如果未能解决你的问题,请参考以下文章

C#解析JSON文件和XML文件(2021.05.26)

python3 读取XML文件的入坑经历

在 C# 代码中解析(大)XML 的最佳方法是啥?

在 C# 中解析 XML 数据并显示到 ListBox

C# XML 解析问题

C# 将 XML 文件从给定标签解析为对象