Как построить XML Дерево в c# с доступной структурой XML Дерево - PullRequest
2 голосов
/ 24 февраля 2020

У меня есть список путей, который представляет собой 'List (Of String)', и я хочу создать дерево xml соответственно.

Например: допустим, у меня есть 10 путей следующим образом

  1. a / b.book
  2. a / b / c .book
  3. a / b / c / d / e.page
  4. a / b / c / d / f.page
  5. a / b / g.book
  6. a / b / g / h / i.page
  7. a / b / g /h/j.page
  8. k / l.book
  9. k / l / m / n.page
  10. o / p.book

Мой ожидаемый результат:

<?xml version="1.0" encoding="UTF-8"?>
<map>
<book navtitle = "a">
   <book navtitle = "b">
      <book navtitle = "c">
         <book navtitle = "d">
            <page navtitle = "e"/>
            <page navtitle = "f"/>
         </book>
      </book>
      <book navtitle = "g">
         <book navtitle = "h">
            <page navtitle = "i"/>
            <page navtitle = "j"/>
         </book>
      </book>
   </book>
</book>

<book navtitle = "k">
   <book navtitle = "l">
      <book navtitle = "m">
         <page navtitle = "n"/>
      </book>
   </book>
</book>

<book navtitle = "o">
   <book navtitle = "p">
   </book>
</book>
</map>

Ответы [ 3 ]

2 голосов
/ 24 февраля 2020

Попробуйте использовать рекурсивный алгоритм, используя Xml Linq

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Net;


namespace ConsoleApplication157
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            string[] inputs = {
                                  "a/b.book",
                                  "a/b/c.book",
                                  "a/b/c/d/e.page",
                                  "a/b/c/d/f.page",
                                  "a/b/g.book",
                                  "a/b/g/h/i.page",
                                  "a/b/g/h/j.page",
                                  "k/l.book",
                                  "k/l/m/n.page",
                                  "o/p.book"
                              };
            List<List<string>> splitArrays = inputs.Select(x => x.Split(new char[] { '/', '.' }).ToList()).ToList();

            XElement root = new XElement("root");
            GetTree(root, splitArrays);

        }
        static void GetTree(XElement parent, List<List<string>> splitArrays)
        {
            var groups = splitArrays.OrderBy(x => x[0]).GroupBy(x => new { path = x.First(), type = x.Last() }).ToArray();
            foreach (var group in groups)
            {
                List<List<string>> children = null;
                XElement element = new XElement(group.Key.type, new XAttribute("navtitle", group.Key.path));
                parent.Add(element);
                Boolean first = true;
                foreach (var child in group.OrderByDescending(x => x.Count))
                {
                    if (child.Count() == 2) //since we sorts by count, 1 indicates we are at the leaf
                    {
                        if (first)
                        {
                            if (children != null)
                            {
                                GetTree(element, children);
                                children = null;
                            }
                            first = false;
                        }
                    }
                    else
                    {
                        //remove first index of each splitArray
                        if (children == null) children = new List<List<string>>();
                        List<string> newChild = child.Skip(1).ToList();
                        children.Add(newChild);
                    }

                }
                //when there are no elements with count = 1 then call Getree here
                if (children != null)
                {
                    GetTree(element, children);
                }

            }
        }
    }
}
2 голосов
/ 24 февраля 2020

Это может выглядеть как этот рекурсивный метод.

Private Sub BuildTrie(path As String,
                      trie As XElement)
    Dim p As List(Of String)
    p = path.Split(New Char() {"/"c}, StringSplitOptions.RemoveEmptyEntries).ToList
    If p.Count > 0 Then
        Dim thisP As String = p(0)
        If thisP <> "" Then
            p.RemoveAt(0)
            Dim ie As IEnumerable(Of XElement)
            ie = From el In trie.Elements Where el.@navtitle = thisP Select el Take 1

            Dim thisND As XElement
            If ie.Count = 1 Then
                thisND = ie(0)
            Else
                thisND = <book navtitle=""></book>
                thisND.@navtitle = thisP
                trie.Add(thisND)
            End If
            If p.Count > 0 Then BuildTrie(String.Join("/"c, p), thisND)
        End If
    End If
End Sub

, чтобы увидеть, как это работает, попробуйте это

    Dim pths() As String = {"a/b/c", "a/b/c/d/f", "a/b/g", "a/b/g/h", "k/l", "k/l/m/n", "o/p"}
    Dim _trie As XElement = <books></books>
    For Each p As String In pths
        BuildTrie(p, _trie)
    Next

    Stop ' look at _trie

Это не завершено, но, возможно, это даст вам немного идеи.

редактировать: вывод сверху.

<books>
  <book navtitle="a">
    <book navtitle="b">
      <book navtitle="c">
        <book navtitle="d">
          <book navtitle="f"></book>
        </book>
      </book>
      <book navtitle="g">
        <book navtitle="h"></book>
      </book>
    </book>
  </book>
  <book navtitle="k">
    <book navtitle="l">
      <book navtitle="m">
        <book navtitle="n"></book>
      </book>
    </book>
  </book>
  <book navtitle="o">
    <book navtitle="p"></book>
  </book>
</books>
1 голос
/ 25 февраля 2020

Вы прокомментировали, что вы "Trying to build xml tree without usage of xmlelement or xmldocument", что я могу оценить. Я хотел бы предоставить еще один вариант: NET classes и Xml Serialization.

Сначала мы начнем с создания классов, которые могут представлять ваши данные. Так как это простой дизайн, классы просты

Imports System.Xml.Serialization

<XmlRoot("map")>
Public Class book
    <XmlAttribute> Public Property navtitle As String
    <XmlElement("book")> Public books As List(Of book)
    <XmlElement("page")> Public pages As List(Of page)
End Class

Public Class page
    <XmlAttribute> Public Property navtitle As String
End Class

Чтобы продемонстрировать, как вы можете создавать свои классы вручную, вот некоторый код, который находится в одной строке и хотя его трудно прочитать, он должен быть довольно легко увидеть, как он соответствует вашему xml файлу

Private Function createMap() As book
    Dim m As New book() With {
        .books = New List(Of book)() From {
            New book() With {.navtitle = "a",
                .books = New List(Of book)() From {
                    New book() With {.navtitle = "b",
                        .books = New List(Of book)() From {
                            New book With {.navtitle = "c",
                                .books = New List(Of book)() From {
                                    New book With {.navtitle = "d",
                                        .pages = New List(Of page)() From {
                                            New page With {.navtitle = "e"},
                                            New page With {.navtitle = "f"}}}}}}}}},
            New book() With {.navtitle = "g",
                .books = New List(Of book)() From {
                    New book() With {.navtitle = "h",
                        .pages = New List(Of page)() From {
                            New page() With {.navtitle = "i"},
                            New page() With {.navtitle = "j"}}}}},
            New book With {.navtitle = "k",
                .books = New List(Of book)() From {
                    New book() With {.navtitle = "l",
                        .books = New List(Of book)() From {
                            New book() With {.navtitle = "m",
                                .pages = New List(Of page)() From {
                                    New page With {.navtitle = "n"}}}}}}},
            New book With {.navtitle = "o",
                .books = New List(Of book)() From {
                    New book() With {.navtitle = "p"}}}}}
    Return m
End Function

Эта функция возвращает объект, содержащий все ваши данные, со строгой типизацией. Этот объект может быть записан в Xml файл с помощью Xml Сериализация просто

Private Sub createXmlFile(path As String, b As book)
    Dim s As New XmlSerializer(GetType(book))
    Using sw As New StreamWriter(path)
        s.Serialize(sw, b)
    End Using
End Sub
Dim m = createMap()
createXmlFile("path.xml", m)

Итак, у нас есть структура для ваших классов и для записи их в Xml, но без динамических c переводчик. Вот интерпретатор

Private Function createMap(titles As IEnumerable(Of String)) As book
    Dim root As New book()
    For Each title In titles
        Dim book = root
        Dim parts = title.Split("/"c)
        For Each part In parts
            Dim b As book
            If part.Contains(".") Then
                If part.Contains("page") Then
                    If book.pages Is Nothing Then book.pages = New List(Of page)()
                    book.pages.Add(New page() With {.navtitle = part.Split("."c).First()})
                Else
                    If book.books Is Nothing Then book.books = New List(Of book)()
                    book.books.Add(New book() With {.navtitle = part.Split("."c).First()})
                End If
            Else
                If book.books?.Any(Function(x) x.navtitle = part.First()) Then
                    b = book.books.Single(Function(x) x.navtitle = part.First())
                Else
                    b = New book() With {.navtitle = part.First()}
                    If book.books Is Nothing Then book.books = New List(Of book)()
                    book.books.Add(b)
                End If
                book = b
            End If
        Next
    Next
    Return root
End Function

Обратите внимание, что он не рекурсивный, но, если хотите, он может быть написан с использованием рекурсии, но в этом нет необходимости. Теперь мы можем вызвать перегрузку этой функции и передать ваши пути. Мы используем IEnumerable(Of String) вместо списка, потому что вы почти должны использовать список только тогда, когда вы хотите изменить его, например, изменить порядок. Кроме того, IEnumerable(Of String) будет принимать множество различных типов - например, массив ниже

Dim m = createMap(
    {"a/b.book",
    "a/b/c.book",
    "a/b/c/d/e.page",
    "a/b/c/d/f.page",
    "a/b/g.book",
    "a/b/g/h/i.page",
    "a/b/g/h/j.page",
    "k/l.book",
    "k/l/m/n.page",
    "o/p.book"})
createXmlFile("path1.xml", m)

И ваш файл будет создан

<?xml version="1.0" encoding="utf-8"?>
<map xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <book navtitle="a">
    <book navtitle="b">
      <book navtitle="c">
        <book navtitle="d">
          <page navtitle="e" />
          <page navtitle="f" />
        </book>
      </book>
      <book navtitle="g">
        <book navtitle="h">
          <page navtitle="i" />
          <page navtitle="j" />
        </book>
      </book>
    </book>
  </book>
  <book navtitle="k">
    <book navtitle="l">
      <book navtitle="m">
        <page navtitle="n" />
      </book>
    </book>
  </book>
  <book navtitle="o">
    <book navtitle="p" />
  </book>
</map>

Бонус: Xml Сериализация также делает чтение Xml файлы намного проще

Private Function readXmlFile(path As String) As book
    Dim b As book
    Dim s As New XmlSerializer(GetType(book))
    Using sr As New StreamReader(path)
        b = DirectCast(s.Deserialize(sr), book)
    End Using
    Return b
End Function
Dim m = readXmlFile("path1.xml")

и m содержит ту же карту, которую мы ранее создали и записали в файл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...