从HTML标头标签创建树对象结构

时间:2016-06-07 09:13:02

标签: c# html html-agility-pack

从HTML标头标签创建树对象结构的最佳方法是什么? (之后我最关心的是可读性)

以下是我想将HTML映射到的对象:

public class Node
{
    public string Title { get; set; }
    public string Html { get; set; }
    public List<Node> SubNodes { get; set; }
}

以下是HTML示例:

<h1>Header 1</h1>
<p>Content under header 1</p>

<h2>H2 for header 1</h2>
<p>Content under H2 for header 1</p>

<h3>H3 for H2 under header 1</h3>
<h4>h4 for h3 under h2 and header 1<h4>
<p>Content under H4 for h3 and H2 under header 1</p>

<h2>Second H2 for header1</h2>
<p>Content under second H2 for header 1</p>

<h1>Second header 1</h1>
<p>Content under second header 1</p>

预期的结构看起来像这样(用JSON编写):

[{ 
    'Title': 'Header 1',
    'Html': '<h1>Header 1</h1><p>Content under header 1</p>',
    'SubNodes': [{
        'Title': 'H2 for header 1',
        'Html': '<h2>H2 for header 1</h2><p>Content under H2 for header 1</p>',
        'SubNodes': [{        
            'Title': 'H3 for H2 under header 1',
            'Html': '<h3>H3 for H2 under header 1</h3>',
            'SubNodes': [{
                'Title': 'h4 for h3 under h2 and header 1,'
                'Html': '<h4>h4 for h3 under h2 and header 1<h4><p>Content under H4 for h3 and H2 under header 1</p>',
                'SubNodes': []
            }]
        },{
            'Title': 'Second H2 for header1',
            'Html': '<h2>Second H2 for header1</h2><p>Content under second H2 for header 1</p>',
            'SubNodes': [] 
        }]
    }]
},{
    'Title': 'Second header 1',
    'Html': '<h1>Second header 1</h1><p>Content under second header 1</p>',
    'SubNodes': [] 
}]

1 个答案:

答案 0 :(得分:1)

嗯,这根本不是很好,如果HTML的结构变化太大,可能会破坏,但似乎是工作,也许它会让你知道从哪里开始。

首先,我将属性public int Level { get; set; }添加到您的Node课程,以简化操作。

接下来,您可能想要一种方法来判断标题有哪个Level

我做了这样的事情:

bool IsHeading(string tagName, out int? level)
{
  level = null;
  if (tagName.StartsWith("h", StringComparison.OrdinalIgnoreCase) == false)
  {
    return false;
  }

  int tempLevel;
  if (int.TryParse(tagName.Substring(1), out tempLevel) == false)
  {
    return false;
  }

  level = tempLevel;
  return true;
}

然后算法类似于:

  • 获取第一个标题并将其设置为当前节点
  • 如果下一个元素不是标题,请将其内容附加到当前节点
  • 重复最后一步,直到下一个元素为标题。
  • 将当前节点设置为父节点,获取下一个标题并将其设置为新的当前节点。
  • 如果下一个标题的级别较高,请将其添加到父级。
  • 如果级别相同或更低,请找到级别更低的最后一个节点,并将其添加到该级别。
  • 如果没有较低级别的节点,请将其视为&#34;第一个标题&#34;。
  • 重复

这样的事情:

  var nodeList = new List<Node>();
  var allNodes = new List<Node>();
  Node parentNode = null;
  Node currentNode = null;

  foreach (var htmlNode in body.ChildNodes)
  {
    int? level;

    if (IsHeading(htmlNode.Name, out level) && level.HasValue)
    {
      currentNode = new Node();
      currentNode.Title = htmlNode.InnerText;
      currentNode.Html = htmlNode.OuterHtml;
      currentNode.Level = level.Value;
      allNodes.Add(currentNode);

      if (!allNodes.Any(n => n.Level < currentNode.Level))
      {
        nodeList.Add(currentNode);
        parentNode = null;
      }

      if (parentNode != null)
      {
        if (parentNode.Level >= currentNode.Level)
        {
          parentNode = allNodes.Last(n => n.Level < currentNode.Level);
        }
        parentNode.SubNodes.Add(currentNode);
      }
      parentNode = currentNode;

      continue;
    }

    if (currentNode == null)
    {
      continue;
    }

    currentNode.Html += htmlNode.OuterHtml;
  }

再一次,不是自豪,而是从一开始就有所作为。

编辑1:不知道rootNode是什么。不需要;除去。

编辑2:哦,这可能是为了让它发挥作用,即使第一个标题不是<h1>。修好了。