优化C#自定义Xml解析器类

时间:2013-04-02 01:59:27

标签: c# .net xml linq

我正在尝试编写一个XML解析器来解析大学提供给定日历年和学期的所有课程。特别是,我试图获得部门的首字母缩略词(即FIN财务等),课程编号(即数学415,415将是数字),课程名称以及课程价值的学分数。

我正在尝试解析的文件HERE

编辑和更新

读者深入了解XML解析,以及优化它的最佳方法,我偶然发现了这个博客POST

假设在该文章中运行的测试结果既诚实又准确,似乎XmlReader远远优于XDocument和XmlDocument,它们验证了下面的优秀答案中的内容。话虽如此,我使用XmlReader重新编写了我的解析器类,并限制了单个方法中使用的读取器数量。

这是新的解析器类:

        public void ParseDepartments()
        {
            // Create reader for the given calendar year and semester xml file
            using (XmlReader reader = XmlReader.Create(xmlPath)) {
                reader.ReadToFollowing("subjects");  // Navigate to the element 'subjects'
                while (!reader.EOF) {
                    string pth = reader.GetAttribute("href");  // Get department's xml path
                    string acro = reader.GetAttribute("id");  // Get the department's acronym
                    reader.Read();  // Read through current element, ensures we visit each element
                    if (acro != null && acro != string.Empty) {  // If the acronym is valid, add it to the department list
                        deps.AddDepartment(acro, pth);
                    }
                }
            }
        }

        public void ParseDepCourses()
        {
            //  Loop through all the departments, and visit there respective xml file
            foreach (KeyValuePair<string, string> department in deps.DepartmentPaths) {
                try {
                    using (XmlReader reader = XmlReader.Create(department.Value)) {
                        reader.ReadToFollowing("courses");  // Navigate to the element 'courses'
                        while (!reader.EOF) {
                            string pth = reader.GetAttribute("href");
                            string num = reader.GetAttribute("id");
                            reader.Read();
                            if (num != null && num != string.Empty) {
                                string crseName = reader.Value;  //  reader.Value is the element's value, i.e. <elementTag>Value</elementTag> 
                                deps[department.Key].Add(new CourseObject(num, crseName, termID, pth)); // Add the course to the department's course list
                            }
                        }
                    }
                } catch (WebException) { }  // WebException is thrown (Error 404) when there is no xml file found, or in other words, the department has no courses
            }

        }

        public void ParseCourseInformation()
        {
            Regex expr = new Regex(@"^\S(L*)\d\b|^\S(L*)\b|^\S\d\b|^\S\b", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace);  // A regular expression that will check each section and determine if it is a 'Lecture' section, at which point, that section's xml file is visited, and instructor added
            foreach (KeyValuePair<string, Collection<CourseObject>> pair in deps) {
                foreach (CourseObject crse in pair.Value) {
                    try {
                        using (XmlReader reader = XmlReader.Create(crse.XmlPath)) {
                            reader.ReadToFollowing("creditHours");  // Get credit hours for the course
                            crse.ParseCreditHours(reader.Value);  //  Class method to parse the string and grab the correct integer values
                            reader.ReadToFollowing("sections"); //  Navigate to the element 'sections'
                            while (!reader.EOF) {
                                string pth = reader.GetAttribute("href");
                                string crn = reader.GetAttribute("id");
                                reader.Read();
                                if (crn != null && crn != string.Empty) {
                                    string sction = reader.Value;
                                    if (expr.IsMatch(sction)) {  // Check if sction is a 'Lecture' section
                                        using (XmlReader reader2 = XmlReader.Create(pth)) {  //  Navigate to its xml file
                                            reader2.ReadToFollowing("instructors");  // Navigate to the element 'instructors'
                                            while (!reader2.EOF) {
                                                string firstName = reader2.GetAttribute("firstName");
                                                string lastName = reader2.GetAttribute("lastName");
                                                reader2.Read();
                                                if ((firstName != null && firstName != string.Empty) && (lastName != null && lastName != string.Empty)) { //  Check and make sure its a valid name
                                                    string instr = firstName + ". " + lastName;  //  Concatenate into full name
                                                    crse.AddSection(pth, sction, crn, instr);  //  Add section to course
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } catch (WebException) { }  // No course/section information found
                }
            }
        }

尽管此代码的执行需要相当长的时间(在10-30分钟之间),但是在解析大量数据的情况下可以预期。感谢所有发布答案的人,非常感谢。我希望这可以帮助其他可能有类似问题的人。

谢谢,

大卫

2 个答案:

答案 0 :(得分:3)

好吧,显然加载XML文件有点慢(例如因为它们很大或因为下载它们所花费的时间)并且使用XDocument将加载并将它们全部解析到内存中,即使你是只使用它的一小部分。以递归方式递增三个级别将使整个过程非常缓慢,但最终它将结束(无论是按预期还是通过OutOfMemoryException)。 1

看看XmlReader class。它允许您按顺序读取XML文件,挑选出您需要的任何内容,并在获得所需的所有信息时中止读取。但是,它与XDocument的工作方式有很大不同,而且不那么直观。该MSDN页面上有一些示例,on Stackoverflow也是如此。

作为旁注:在开始阅读另一个XML文件之前,使用XmlReader时,请考虑阅读并关闭阅读器。这样可以最大限度地减少应用程序的内存占用。

1 )例如,考虑一下您正在阅读一个包含10年3个季节的60个课程文件的文件,然后您的代码正在下载,解析,验证和处理10 * 3 * 60 = 1800个文件。它需要从(与本地PC相比)慢速互联网下载它们。不要指望整个过程很快。

答案 1 :(得分:1)

循环不会变得无限,它变得非常非常慢。这是因为

的召唤
XDocument hoursDoc = XDocument.Load(crsePath);

打开另一个XML文件并解析它。考虑到当所有信息都在内存中时处理持续25秒,因此为您遇到的每个课程打开一个额外的文件会使进程变慢,这就不足为奇了。