如何基于数据结构的嵌套列表构建HTML table?
How to build an HTML table based on nested Lists of data structure?
我有一个嵌套列表结构如下:
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;
}
};
}
编辑:我的真实数据结构与parents/children无关。我只是选择了一个简单的数据结构来使问题易于理解。假设每个爷爷可以有任意数量的儿子。每个儿子可以有任意数量的孩子。没有两个爷爷可以共享同一个儿子。同样,儿子不能共享孩子。
Edit2:我没有为每个单元格获取 RowSpan 的问题。那很容易。问题是:如何在迭代和遍历数据结构时解析 table 的 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 } ;
我正在尝试使用 <td rowspan={number of offsprints}>
在 HTML table 中表示 family
结构
对于上述系列,输出如下:
.------------------.
| | |
| | son1 |
| |-------|-----------.
| | | grandkid1 |
| grandpa1 | |-----------.
| | | grandkid2 |
| | son2 |-----------.
| | | grandkid3 |
|----------|-------.-----------.
| grandpa2 |
.----------.
在上面的table中,grandpa
有一个rowspan
的4
。 son2
的 rowspan
为 3
。
等价的 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
没有 children,并且从级别 3 出队会获取 son2
..
的孩子
2- 我尝试遍历 grandparents 级别并为每个祖父母附加一个 TR,其中一个 TD 具有他的 sons/grandsons 的行跨度。然后遍历儿子级别并做同样的事情,同样在孙子级别。但是总体 table 将是 ill-formatted.
我可以轻松地在 Excel 中生成结构,因为我能够在迭代第一级时引用单元格及其合并状态,并且可以返回到之前的行并附加新单元格。
有没有一种简单的方法可以从上面的数据结构中生成家族结构table?
我之前的回答没有产生预期的结果。请参阅以下答案。
实际上,我认为将树“tablefy”成二维数组并用相应的值填充它会更容易。要确定行跨度,您需要在其下方的区域中查看 null
值。行跨度将等于所有列中包含 null 的行数,包括当前列。
例如,Son1 的正下方没有任何 null
,这意味着它的行跨度为 1。Son2 有 4 个 null
值,但行跨度为 3,因为在行索引 4 和column index 0 有 Grandpa 2。这就是为什么直接查看当前行(同一列)下方是不够的,您需要检查从开始到当前列(包括当前列)的每一列当前单元格下方的区域。
您应该能够将其扩展到任意数量的级别,并且仍然获得适当的 table。
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;
}
当child 没有为 parent 提供 grandchildren 时,你需要考虑的事情是,在这种情况下你需要指定一个 col span以及填充 space,或用剩余的 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;
}
您可能需要收集在 parent 方面处于同一水平的人,即:所有具有相同 parent 的人,所有具有相同水平的人盛大 parent 等等。
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());
}
您可以使用这些方法生成 table。
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>");
}
我有一个嵌套列表结构如下:
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;
}
};
}
编辑:我的真实数据结构与parents/children无关。我只是选择了一个简单的数据结构来使问题易于理解。假设每个爷爷可以有任意数量的儿子。每个儿子可以有任意数量的孩子。没有两个爷爷可以共享同一个儿子。同样,儿子不能共享孩子。
Edit2:我没有为每个单元格获取 RowSpan 的问题。那很容易。问题是:如何在迭代和遍历数据结构时解析 table 的 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 } ;
我正在尝试使用 <td rowspan={number of offsprints}>
family
结构
对于上述系列,输出如下:
.------------------.
| | |
| | son1 |
| |-------|-----------.
| | | grandkid1 |
| grandpa1 | |-----------.
| | | grandkid2 |
| | son2 |-----------.
| | | grandkid3 |
|----------|-------.-----------.
| grandpa2 |
.----------.
在上面的table中,grandpa
有一个rowspan
的4
。 son2
的 rowspan
为 3
。
等价的 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
没有 children,并且从级别 3 出队会获取 son2
..
2- 我尝试遍历 grandparents 级别并为每个祖父母附加一个 TR,其中一个 TD 具有他的 sons/grandsons 的行跨度。然后遍历儿子级别并做同样的事情,同样在孙子级别。但是总体 table 将是 ill-formatted.
我可以轻松地在 Excel 中生成结构,因为我能够在迭代第一级时引用单元格及其合并状态,并且可以返回到之前的行并附加新单元格。
有没有一种简单的方法可以从上面的数据结构中生成家族结构table?
我之前的回答没有产生预期的结果。请参阅以下答案。
实际上,我认为将树“tablefy”成二维数组并用相应的值填充它会更容易。要确定行跨度,您需要在其下方的区域中查看 null
值。行跨度将等于所有列中包含 null 的行数,包括当前列。
例如,Son1 的正下方没有任何 null
,这意味着它的行跨度为 1。Son2 有 4 个 null
值,但行跨度为 3,因为在行索引 4 和column index 0 有 Grandpa 2。这就是为什么直接查看当前行(同一列)下方是不够的,您需要检查从开始到当前列(包括当前列)的每一列当前单元格下方的区域。
您应该能够将其扩展到任意数量的级别,并且仍然获得适当的 table。
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;
}
当child 没有为 parent 提供 grandchildren 时,你需要考虑的事情是,在这种情况下你需要指定一个 col span以及填充 space,或用剩余的 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;
}
您可能需要收集在 parent 方面处于同一水平的人,即:所有具有相同 parent 的人,所有具有相同水平的人盛大 parent 等等。
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());
}
您可以使用这些方法生成 table。
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>");
}