【问题标题】:How to build an HTML table based on nested Lists of data structure?如何基于数据结构的嵌套列表构建 HTML 表格?
【发布时间】:2021-09-20 22:42:02
【问题描述】:

我有一个嵌套列表结构如下:

class Person {
   public string Name;
   public List<Person> Children;
   public int RowSpan { 
       get 
       { int childrenCount = 0;
          /* go through children (if any) and update childrenCount */ 
          return childrenCount == 0 ? 1 : childrenCount;
       }
    };
}

编辑:我的真实数据结构与父母/孩子无关。我只是选择了一个简单的数据结构,使问题易于理解。假设每个爷爷可以有任意数量的儿子。每个儿子可以有任意数量的孩子。没有两个爷爷可以共享同一个儿子。同样,儿子不能分享孩子。

Edit2:我没有为每个单元格获取 RowSpan 的问题。这很容易。问题是:如何在迭代和遍历数据结构的同时解析表格的HTML结构。

我按如下方式填充此结构的示例:

Person grandpa1  = new Person { Name = "GrandPa1" };
Person grandpa2  = new Person { Name = "GrandPa2" };
Person son1      = new Person { Name = "Son 1" };
Person son2      = new Person { Name = "Son 2" };
Person grandkid1 = new Person { Name = "GrandKid1" };
Person grandkid2 = new Person { Name = "GrandKid2" };
Person grandkid3 = new Person { Name = "GrandKid3" };

grandpa1.Children = new List<Person> { son1, son2 };
son2.Children    = new List<Person> { grandkid1, grandkid2, grandkid3 };

List<Person> family = new List<Person> { grandpa1, grandpa2 } ;

我正在尝试使用 &lt;td rowspan={number of offsprints}&gt; 在 HTML 表中表示 family 结构

对于上述系列,输出如下所示:

.------------------.
|          |       |
|          |  son1 |
|          |-------|-----------.
|          |       | grandkid1 |
| grandpa1 |       |-----------.
|          |       | grandkid2 |
|          |  son2 |-----------.
|          |       | grandkid3 |
|----------|-------.-----------.
| grandpa2 |
.----------.

在上表中,grandparowspan4son2rowspan3

等价的 HTML 应该是这样的:

table {
  border-collapse: collapse;
}

td, th {
  border: 1px solid black;
  padding: 4px;
}

th
{
  font-family: monospace;
  background-color: #def;
  padding: 0 2em;
}
<table>
  <thead>
    <tr>
      <th>Grand Parents</th>
      <th>Sons</th>
      <th>Kids</th>
    </tr>
  </thead>
  <tr>
    <td rowspan="4"> grandpa1 </td>
    <td> son1 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1:in progress -->
    <!-- no cell for son1: fulfilled      -->
    <td rowspan="3"> son2 </td>
    <td> kid1 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: in progress -->
    <!-- no cell for son2:     in progress -->
    <td> kid2 </td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: in progress -->
    <!-- no cell for son2:     in progress -->
    <td> kid3</td>
  </tr>
  <tr>
    <!-- no cell for grandpa1: fullfilled -->
    <!-- no cell for son2:     fullfilled -->
    <td> grandpa2 </td>
  </tr>
</table>

我正在努力编写一个生成上述 HTML 结构的算法。 我尝试了多种方法:

1- 我为家庭的每个级别生成了一个Queue&lt;TagBuilder&gt;,并遍历了最高级别和Enqueue TD,其中行跨度为较低级别的受抚养人数量。然后我为其他级​​别生成了其他队列。最后,我的计划是一次从所有级别 DeQueue 并将 TD 附加到新的 TR 中。但这不起作用,因为son1 没有孩子,从第 3 级出列会获取 son2 的孩子..

2- 我尝试迭代祖父母级别并为每个祖父母附加一个 TR,其中包含一个具有他儿子/孙子行跨度的单个 TD。然后遍历儿子级别并做同样的事情,同样在孙子级别。但是整个表格的格式会不正确。

我可以在 Excel 中轻松生成结构,因为我能够在迭代第一级时引用单元格及其合并状态,并且可以从之前的行返回并追加新单元格。

有没有一种简单的方法可以从上面的数据结构中生成家族结构表?

【问题讨论】:

  • 你会如何代表两个父母的孩子?通常需要两个来制作一个。我希望爷爷 1 和爷爷 2 有相同的孩子,尽管情况并非总是如此(你也可以包括继父母)
  • @Andrei15193 我更新了我的问题。各级不共享后代。
  • 尝试将 RowSpan 属性添加到 person 类,并在遍历每个项目时更新它。这似乎是编写递归函数来遍历每个关系的一个很好的用例。
  • @Ahmad 在这种情况下,如果你把它看成一棵树,你需要的是每个节点的叶子数。即:祖父母有 2 个孩子,1 有 2 个孩子,另外 3 个。祖父母节点的叶子是 5(总共 5 个孙子),而第一个孩子是 2 个,其他 3 个。每个孙子是 1,因为每个其中是树上的一片叶子。您可以编写一个方法来获取当前节点(包括当前节点)的叶子数。

标签: c# html tagbuilder


【解决方案1】:

我之前的回答没有产生预期的结果。请参阅下面的答案。

实际上,我认为将树“表格化”成二维数组并用相应的值填充它会更容易。要确定行跨度,您需要在其下方的区域中查找 null 值。行跨度将等于所有列上包含 null 的行数,包括当前列。

例如,Son1 在其下方没有任何null,这意味着它的行跨度为 1。Son2 有 4 个null 值,但行跨度为 3,因为在行索引 4 和列索引 0 上有爷爷2. 这就是为什么直接查看当前行(同一列)下方是不够的,您需要从开始到当前列检查每一列上当前单元格下方的区域。

您应该能够将其扩展到任意数量的级别,并且仍然可以获得正确的表格。

class Person
{
    public string Name { get; set; }

    public List<Person> Children { get; set; }
}

void WritePeopleTable(TextWriter textWriter, IEnumerable<Person> people)
{
    var peopleTable = GetPeopleTable(people);

    textWriter.WriteLine("<table>");
    for (var rowIndex = 0; rowIndex < peopleTable.GetLength(0); rowIndex++)
    {
        textWriter.WriteLine("<tr>");
        for (var columnIndex = 0; columnIndex < peopleTable.GetLength(1); columnIndex++)
        {
            var current = peopleTable[rowIndex, columnIndex];
            if (current != null)
            {
                var rowSpan = 1;
                var foundValue = false;
                while (!foundValue && rowIndex + rowSpan < peopleTable.GetLength(0))
                {
                    var searchColumnIndex = 0;
                    while (!foundValue && searchColumnIndex <= columnIndex)
                        if (peopleTable[rowIndex + rowSpan, searchColumnIndex] != null)
                            foundValue = true;
                        else
                            searchColumnIndex++;

                    if (!foundValue)
                        rowSpan++;
                }

                textWriter.Write($"<td rowspan=\"{rowSpan}\">");
                textWriter.Write(current.Name);
                textWriter.WriteLine("</td>");
            }
        }
        textWriter.WriteLine("</tr>");
    }
    textWriter.WriteLine("</table>");
}

struct PersonLevel
{
    public PersonLevel(Person person, int level)
    {
        Person = person;
        Level = level;
    }

    public Person Person { get; }

    public int Level { get; }
}

Person[,] GetPeopleTable(IEnumerable<Person> people)
{
    var rowCount = people.Sum(person => GetLeafCount(person)); // or people.Sum(GetLeafCount);
    var columnCount = people.Max(person => GetTreeHeight(person)); // or people.Max(GetTreeHeight);
    var peopleTable = new Person[rowCount, columnCount];
    var currentRowIndex = 0;
    var previousColumnIndex = -1;
    var toProcess = new Stack<PersonLevel>();
    foreach (var person in people.Reverse())
        toProcess.Push(new PersonLevel(person, 0));

    while (toProcess.Count > 0)
    {
        var current = toProcess.Pop();
        if (current.Person.Children != null)
            foreach (var child in current.Person.Children.AsEnumerable().Reverse())
                toProcess.Push(new PersonLevel(child, current.Level + 1));

        if (current.Level <= previousColumnIndex)
            currentRowIndex++;
        previousColumnIndex = current.Level;

        peopleTable[currentRowIndex, current.Level] = current.Person;
    }

    return peopleTable;
}

int GetLeafCount(Person person)
{
    var leafCount = 0;
    var toVisit = new Queue<Person>();
    toVisit.Enqueue(person);
    do
    {
        var current = toVisit.Dequeue();
        if (current.Children == null || !current.Children.Any())
            leafCount++;
        else
            foreach (var child in current.Children)
                toVisit.Enqueue(child);
    } while (toVisit.Count > 0);

    return leafCount;
}

int GetTreeHeight(Person person)
{
    var height = 0;
    var level = Enumerable.Repeat(person, 1);
    do
    {
        height++;
        level = level.SelectMany(current => current.Children ?? Enumerable.Empty<Person>());
    } while (level.Any());

    return height;
}

上一个答案

正如我在cmets中所说,行跨度等于当前节点的叶子数(包括当前节点,行跨度默认为1)。

int GetLeafCount(Person person)
{
    var leafCount = 0;
    var toVisit = new Queue<Person>();
    toVisit.Enqueue(person);
    do
    {
        var current = toVisit.Dequeue();
        if (!current.Children.Any())
            leafCount++;
        else
            foreach (var child in current.Children)
                toVisit.Enqueue(child);

    } while (toVisit.Count > 0);

    return leafCount;
}

在执行此操作时需要考虑的情况是子代没有为父代提供孙代,在这种情况下,您还需要指定一个 col 跨度来填充空间,或者用剩余的 TD 元素填充它。您可以通过计算树的高度来获得列数。

int GetTreeHeight(Person person)
{
    var height = 0;
    var level = Enumerable.Repeat(person, 1);
    do
    {
        height++;
        level = level.SelectMany(current => current.Children);
    } while (level.Any());

    return height;
}

您可能需要收集在父母方面处于同一水平的人,即:所有具有相同父母的人,所有具有相同祖父母的人等等。

IEnumerable<IEnumerable<Person>> GetLevels(Person person)
{
    var level = Enumerable.Repeat(person, 1);
    do
    {
        yield return level;
        level = level.SelectMany(current => current.Children);
    } while (level.Any());
}

您可以使用这些方法来生成表格。

void WriteTable(TextWriter textWriter, IEnumerable<Person> people)
{
    textWriter.WriteLine("<table>");
    foreach (var person in people)
        foreach (var rowIndex in Enumerable.Range(0, GetLeafCount(person)))
        {
            textWriter.WriteLine("<tr>");
            foreach (var columnIndex in Enumerable.Range(0, GetTreeHeight(person)))
            {
                var currentPerson = GetLevels(person).ElementAtOrDefault(columnIndex)?.ElementAtOrDefault(rowIndex);

                if (currentPerson != null)
                {
                    textWriter.Write($"<td rowspan=\"{GetLeafCount(currentPerson)}\">");
                    textWriter.Write(currentPerson.Name);
                    textWriter.WriteLine("</td>");
                }
            }
            textWriter.WriteLine("</tr>");
        }
    textWriter.WriteLine("</table>");
}

【讨论】:

  • 在任何节点获取 numberOfchildren 都没有任何问题。我正在努力解决的是表格的布局,因为它被解析到浏览器。我不能像祖父母一样多TR
  • @Ahmad 您需要与根节点(每个祖父母)处的叶子一样多的 TR。叶子的总数,基本上。
  • 我不确定每个根节点是否需要一个 TR。请再次查看我的更新问题
  • @Ahmad 不是每个根节点,每个根节点都有很多叶子,你把这些加起来得到TR的总数,我已经更新了我的答案。
  • 我设法修改了您的代码以使其正常工作。我只需要朝着正确的方向前进。
猜你喜欢
  • 2021-04-20
  • 1970-01-01
  • 2019-12-20
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 1970-01-01
  • 1970-01-01
  • 2018-11-19
相关资源
最近更新 更多