微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何基于嵌套的数据结构列表构建 HTML 表?

如何解决如何基于嵌套的数据结构列表构建 HTML 表?

我有如下嵌套列表结构:

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 } ;

我正在尝试使用 family

在 HTML 表格中表示 <td rowspan={number of offsprints}> 结构

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

.------------------.
|          |       |
|          |  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<TagBuilder> 并遍历最高级别和 Enqueue 一个 TD,行跨度为较低级别的受抚养人数量。然后我为其他级​​别生成了其他队列。最后,我的计划是立即从所有级别 DeQueue 并将 TD 附加到新的 TR 中。但这不起作用,因为 son1 没有孩子,并且从级别 3 出列将获取 son2 的孩子..

2- 我尝试遍历祖父母级别,并为每个祖父母附加一个 TR,其中一个 TD 具有他儿子/孙子的行跨度。然后遍历儿子级别并执行相同操作,同样在孙级别。但是,整个表格的格式将不正确。

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

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

解决方法

我之前的回答没有产生想要的结果。请参阅以下答案。

实际上,我认为将树“tablefy”成二维数组并用相应的值填充它会更容易。要确定行跨度,您需要查看其下方区域中的 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;
}

上一个回答

正如我在评论中所说,行跨度等于从当前节点开始的叶子数(包括当前节点,行跨度默认为 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>");
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。