【问题标题】:Programatically build XML document using XElements.Decendents()使用 XElement.Descendants() 以编程方式构建 XML 文档
【发布时间】:2014-04-10 14:46:22
【问题描述】:

我正在编写一个与side word 一起工作并为其创建自定义菜单系统的应用程序。

我希望用户能够添加无限的子菜单。

我正在从一个制表符表示缩进的文本文件中读取。

示例文本文件:

GROUP
    MENU1
        SUBMENU11
               SUBSUBMENU111
                     SUBSUBMENU1111
        SUBMENU12
    MENU2
        SUBMENU21
                    SUBSUBMENU211 

示例 XML 输出:

 <group id="GROUP" label="GROUP">
      <menu id="MENU1" label="MENU1" size="normal">
        <menu id="SUBMENU11" label="SUBMENU11" size="normal" >
          <menu id="SUBMENU111" label="SUBMENU111" size="normal" >
            <menu id="SUBMENU1111" label="SUBMENU1111" size="normal" />
          </menu>
        </menu>
        <menu id="SUBMENU12" label="SUBMENU12" size="normal" />
      </menu>       
      <menu id="MENU2" label="MENU2" size="normal" >
        <menu id="SUBMENU21" label="SUBMENU21" size="normal" >
          <menu id="SUBMENU211" label="SUBMENU211" size="normal" />
        </menu>
      </menu>
    </group>              

目前在某些点添加,即向下 5 个标签:

path.Descendants(ns + "menu").ElementAt(currentMenuIndex)
                       .Descendants(ns + "menu").ElementAt(currentMenu1Index)
                           .Descendants(ns + "menu").ElementAt(currentMenu2Index)
                               .Descendants(ns + "menu").LastOrDefault()
                                   .Add(//add node info);

我目前正在为每个选项卡案例创建这些后代树中的每一个,这会使代码变得庞大,如果可以的话,我希望能够以编程方式为无限数量的选项卡执行此操作。

如果你能对此有所了解,那就太好了,我已经转了好几天了。

【问题讨论】:

  • 您的示例 XML 没有您想象的那么多级别。当 SubMenu11 自动关闭时,它不能有子。
  • 废话我就是个布偶!并写出糟糕的xml!我会纠正它!
  • 你能用递归函数吗?
  • 你自己的代码还不错,只是索引(和 ElmentAt)的使用让它有点尴尬。 XML 对索引不友好。

标签: c# xml ms-word linq-to-xml xelement


【解决方案1】:

有一个much simpler syntax 可以在Xml 文档中创建XElementXAttributes

new XElement("group",
    new XAttribute("id", "GROUP"),
    new XElement("menu",
      new XAttribute("id", "MENU1"),

...等

编辑 这里的递归策略怎么样,有一些 Linq:

const string menu = // I've hardcoded tabs and newlines as SO formats these out.
    "GROUP\r\n" +
    "MENU1\r\n" +
    "\tSUBMENU11\r\n" +
    "\t\tSUBSUBMENU111\r\n"+
    "\t\t\tSUBSUBMENU1111\r\n" +
    "\tSUBMENU12\r\n"+
    "MENU2\r\n" +
    "\tSUBMENU21\r\n"+
    "\t\tSUBSUBMENU211";

var sr = new StringReader(menu); // Obviously use a TextReader
var lines = sr.ReadToEnd()
              .Split(new[] { Environment.NewLine },
                     StringSplitOptions.RemoveEmptyEntries);

var indentedLines =
    lines
        .Skip(1) // Group isn't part of the menu
        .Select(
        _ => new Tuple<string, int>( // Remove whitespace
            Regex.Replace(_, @"\s+", ""), 
            _.Split(new[] { '\t' }).Count())) // Count the tabs
      .ToList();

var doc = new XDocument();
doc.Add(new XElement("group", // Add the root group
           new XAttribute("id", lines[0].Trim()),
           new XAttribute("label", lines[0].Trim()),
           BuildTree(indentedLines, 1, String.Empty))); // Add the top lvl nodes

使用这个递归助手:

IEnumerable<XElement> BuildTree(
     IList<Tuple<string, int>> indentedLines, 
     int indentLevel, String parentNode)
{
    Func<string, string> getNodeParent =
        node =>
            {
                var line = indentedLines.First(_ => _.Item1 == node);
                for (var index = indentedLines.IndexOf(line); index >= 0; index --)
                {
                    if (indentedLines[index].Item2 < line.Item2)
                    {
                        return indentedLines[index].Item1;
                    }
                }
                return String.Empty; // has no parent
            };
    foreach (var line in indentedLines.Where(_ => _.Item2 == indentLevel 
             && getNodeParent(_.Item1) == parentNode))
    {
        yield return new XElement("menu",
                                  new XAttribute("id", line.Item1),
                                  new XAttribute("label", line.Item1),
                                  new XAttribute("size", "normal"),
                                  BuildTree(indentedLines, indentLevel + 1, line.Item1));
    }
}

生成这个 Xml,我认为这就是你所追求的:

<group id="GROUP" label="GROUP">
  <menu id="MENU1" label="MENU1" size="normal">
    <menu id="SUBMENU11" label="SUBMENU11" size="normal">
      <menu id="SUBSUBMENU111" label="SUBSUBMENU111" size="normal">
        <menu id="SUBSUBMENU1111" label="SUBSUBMENU1111" size="normal" />
      </menu>
    </menu>
    <menu id="SUBMENU12" label="SUBMENU12" size="normal" />
  </menu>
  <menu id="MENU2" label="MENU2" size="normal">
    <menu id="SUBMENU21" label="SUBMENU21" size="normal">
      <menu id="SUBSUBMENU211" label="SUBSUBMENU211" size="normal" />
    </menu>
  </menu>
</group>

【讨论】:

  • 感谢您的回复,但这并不能真正回答我的问题,因为我不知道如何仅使用从文本文件中获得的信息以编程方式使用该结构创建文档。我希望系统读取文本文件,然后吐出 xml,而我必须硬编码放置所有内容的位置。
  • @DMA 我已经为你编写了代码,递归地从文件构建树。元组包含每个菜单的标题(Item1),以及缩进级别(Item2,制表符数)。
  • @StuartLC 非常感谢,我对这一切的理解非常有限,在尝试写这篇文章的过程中我很困惑,但你的回答确实让这一切变得更加清晰。非常感谢您的帮助!现在花一些时间来完全理解代码!
  • 我不得不说,这并不像我在发布原始帖子时所说的那么容易。一个不确定的位是getNodeParent - 如果有不止一个同名的父母,你会得到不确定的结果 - 希望不是这种情况。您还可以将 Func&lt;&gt; 移动到普通函数中,只需添加 indentedLines 闭包作为参数。
  • 而且它只比问题中的代码大几倍...... :)
【解决方案2】:

如果我们可以将当前菜单索引存储在一个数组中:

object[] currentMenuIndices;
void addNodeAtNTabs(nodeInfo, currentMenuIndices) {
    currentPath = path;
    name = ns + "menu"
    foreach menuIndex in currentMenuIndices {
        currentPath = Descendants(name).ElementAt(menuIndex);
    }
    currentPath.Descendants(name).LastOrDefault().Add(nodeInfo);
}

currentMenuIndices 的长度应该与节点缩进的选项卡数相关。 (对不起,如果我的代码看起来像 python)

【讨论】:

    猜你喜欢
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    相关资源
    最近更新 更多