如何解决如何基于嵌套的数据结构列表构建 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
<td rowspan={number of offsprints}>
结构
对于上述系列,输出将如下所示:
.------------------.
| | |
| | son1 |
| |-------|-----------.
| | | grandkid1 |
| grandpa1 | |-----------.
| | | grandkid2 |
| | son2 |-----------.
| | | grandkid3 |
|----------|-------.-----------.
| grandpa2 |
.----------.
在上表中,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
没有孩子,并且从级别 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 举报,一经查实,本站将立刻删除。