You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于起始文本将同级元素转为嵌套列表(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>:

  1. 解析每个<list-item>的编号,提取层级(如1.对应层级1,1.1对应层级2)
  2. 用栈保存当前各层级的父容器(List1/List2/List3)和对应的父<list-item>
  3. 根据当前项的层级调整栈深度,找到正确的父节点,将当前项添加到对应容器中
  4. 替换原文档中的同级<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:18:00