如何基于起始文本将同级元素转为嵌套列表(C#实现)
如何基于同级list-item的起始编号实现XML嵌套列表转换?
我需要根据<list-item>元素开头的编号(如1.、1.1、1.1.1),将同级的列表项转换为嵌套结构。目前用正则替换的方式尝试实现,但现有C#代码无法正确完成分组和嵌套逻辑。以下是输入XML、尝试的代码及期望输出,求正确实现方案:
输入XML
<?xml version="1.0" encoding="UTF-8"?> <article> <p>The Simple list sample</p> <list-item>1. First</list-item> <list-item>2. Second</list-item> <list-item>3. Third</list-item> <p>The Nested list sample</p> <list-item>1. FirstLevel First Text</list-item> <list-item>1.1 SecondLevel First Text</list-item> <list-item>1.1.1 ThirdLevel First Text</list-item> <list-item>1.1.2 ThirdLevel Second Text</list-item> <list-item>1.2 SecondLevel Second Text</list-item> <list-item>2. FirstLevel Second Text</list-item> <list-item>2.1 SecondLevel First Text</list-item> <list-item>2.2 SecondLevel Second Text</list-item> <list-item>3. FirstLevel Third Text</list-item> <list-item>4. FirstLevel Fourth Text</list-item> </article>
尝试的C#代码
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using System.Linq; using System.Linq.Expressions; namespace ListNesting1 { class Program { static void Main(string[] args) { XmlDocument XMLDoc1 = new XmlDocument(); XmlNodeList NDL1; XmlElement XEle1; String S1, S2, StrFinal, StrEle1; StreamReader SR1; StreamWriter SW1; try { SR1 = new StreamReader(args[0]); S1 = SR1.ReadToEnd(); SR1.Close(); } catch (Exception e) { Console.WriteLine(e.Message); return; } XMLDoc1.LoadXml(S1); NDL1 = XMLDoc1.SelectNodes("//list-item"); for(int i=0; i<NDL1.Count; i++) { if (Regex.IsMatch(NDL1[i].InnerText, @"^[0-9]\. ")) { StrEle1 = "List1"; } else if (Regex.IsMatch(NDL1[i].InnerText, @"^[0-9]\.[0-9] ")) { StrEle1 = "List2"; } else if (Regex.IsMatch(NDL1[i].InnerText, @"^[0-9]\.[0-9]\.[0-9] ")) { StrEle1 = "List3"; } else { StrEle1 = "List4"; } XEle1 = XMLDoc1.CreateElement(StrEle1); S2 = NDL1[i].OuterXml; XEle1.InnerXml = S2; NDL1[i].ParentNode.InsertAfter(XEle1, NDL1[i]); NDL1[i].ParentNode.RemoveChild(NDL1[i]); } StrFinal = XMLDoc1.OuterXml; StrFinal = StrFinal.Replace("</List1><List1>", ""); StrFinal = StrFinal.Replace("</List2><List2>", ""); StrFinal = StrFinal.Replace("</List3><List3>", ""); StrFinal = StrFinal.Replace("</List4><List4>", ""); StrFinal = StrFinal.Replace("</list-item></List1><List2>", "<List2>"); StrFinal = StrFinal.Replace("</list-item></List2><List3>", "<List3>"); StrFinal = StrFinal.Replace("</list-item></List3><List4>", "<List4>"); StrFinal = StrFinal.Replace("</List2><List1>", "</List2></list-item>"); StrFinal = StrFinal.Replace("</List3><List2>", "</List3></list-item>"); StrFinal = StrFinal.Replace("</List4><List3>", "</List4></list-item>"); StrFinal = StrFinal.Replace("><", "> <"); SW1 = new StreamWriter(args[1]); SW1.Write(StrFinal); SW1.Close(); } } }
期望输出XML
<?xml version="1.0" encoding="UTF-8"?> <article> <p>The Simple list sample</p> <List1> <list-item>1. First</list-item> <list-item>2. Second</list-item> <list-item>3. Third</list-item> </List1> <p>The Nested list sample</p> <List1> <list-item>1. FirstLevel First Text <List2> <list-item>1.1 SecondLevel First Text <List3> <list-item>1.1.1 ThirdLevel First Text</list-item> <list-item>1.1.2 ThirdLevel Second Text</list-item> </List3> </list-item> <list-item>1.2 SecondLevel Second Text</list-item> </List2> </list-item> <list-item>2. FirstLevel Second Text <List2> <list-item>2.1 SecondLevel First Text</list-item> <list-item>2.2 SecondLevel Second Text</list-item> </List2> </list-item> <list-item>3. FirstLevel Third Text</list-item> <list-item>4. FirstLevel Fourth Text</list-item> </List1> </article>
解决方案
正则替换的方式很难处理复杂的嵌套层级逻辑,建议采用栈结构跟踪当前嵌套层级,逐个处理每个<list-item>:
- 解析每个
<list-item>的编号,提取层级(如1.对应层级1,1.1对应层级2) - 用栈保存当前各层级的父容器(
List1/List2/List3)和对应的父<list-item> - 根据当前项的层级调整栈深度,找到正确的父节点,将当前项添加到对应容器中
- 替换原文档中的同级
<list-item>为生成的嵌套结构
以下是实现代码:
using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System.Xml; namespace ListNesting { class Program { static void Main(string[] args) { if (args.Length < 2) { Console.WriteLine("请输入输入文件路径和输出文件路径"); return; } XmlDocument doc = new XmlDocument(); try { doc.Load(args[0]); } catch (Exception ex) { Console.WriteLine($"加载XML失败: {ex.Message}"); return; } XmlNode articleNode = doc.SelectSingleNode("//article"); if (articleNode == null) { Console.WriteLine("未找到<article>节点"); return; } // 分组处理连续的list-item节点(被非list-item节点分隔) List<XmlNode> currentListItems = new List<XmlNode>(); foreach (XmlNode node in articleNode.ChildNodes) { if (node.Name == "list-item") { currentListItems.Add(node); } else { if (currentListItems.Count > 0) { ProcessListGroup(currentListItems, doc); currentListItems.Clear(); } } } // 处理最后一组list-item if (currentListItems.Count > 0) { ProcessListGroup(currentListItems, doc); } // 格式化输出 StringWriter sw = new StringWriter(); XmlTextWriter xw = new XmlTextWriter(sw) { Formatting = Formatting.Indented, Indentation = 3 }; doc.WriteTo(xw); xw.Close(); File.WriteAllText(args[1], sw.ToString()); } static void ProcessListGroup(List<XmlNode> listItems, XmlDocument doc) { // 栈元素:(层级, 父容器节点, 父list-item节点) Stack<Tuple<int, XmlElement, XmlNode>> stack = new Stack<Tuple<int, XmlElement, XmlNode>>(); foreach (XmlNode item in listItems) { // 提取编号和层级 string text = item.InnerText.TrimStart(); Match match = Regex.Match(text, @"^(\d+(\.\d+)*)\s"); if (!match.Success) { // 无编号的项直接保留(可根据需求调整) continue; } string numberStr = match.Groups[1].Value; int level = numberStr.Split('.').Length; // 调整栈到当前层级的上一层 while (stack.Count > 0 && stack.Peek().Item1 >= level) { stack.Pop(); } XmlElement parentContainer; XmlNode parentItem; if (stack.Count == 0) { // 层级1,创建List1容器,作为article的子节点(替换第一个list-item的位置) parentContainer = doc.CreateElement($"List{level}"); item.ParentNode.InsertBefore(parentContainer, item); parentItem = null; } else { // 获取上一层的容器和父item var prevLevel = stack.Peek(); parentContainer = prevLevel.Item2; parentItem = prevLevel.Item3; // 检查当前层级的容器是否已存在于父item中 XmlElement currentContainer = parentItem.SelectSingleNode($"List{level}") as XmlElement; if (currentContainer == null) { currentContainer = doc.CreateElement($"List{level}"); parentItem.AppendChild(currentContainer); } parentContainer = currentContainer; } // 克隆当前list-item(避免原节点被移除后引用失效) XmlNode clonedItem = item.CloneNode(true); parentContainer.AppendChild(clonedItem); // 将当前层级信息压入栈 stack.Push(Tuple.Create(level, parentContainer, clonedItem)); // 移除原节点 item.ParentNode.RemoveChild(item); } } } }
这段代码通过栈结构精准跟踪嵌套层级,确保每个列表项被添加到正确的位置,相比正则替换更可靠,也支持扩展更多层级。
内容的提问来源于stack exchange,提问作者Rudramuni TP
相关产品推荐
相关产品推荐

