这个树遍历代码中的错误在哪里?

时间:2010-11-09 21:25:11

标签: c# html-agility-pack

Traverse()中存在一个错误,导致它多次迭代节点。

错误代码

public IEnumerable<HtmlNode> Traverse()
{
    foreach (var node in _context)
    {
        yield return node;
        foreach (var child in Children().Traverse())
            yield return child;
    }
}

public SharpQuery Children()
{
    return new SharpQuery(_context.SelectMany(n => n.ChildNodes).Where(n => n.NodeType == HtmlNodeType.Element), this);
}

public SharpQuery(IEnumerable<HtmlNode> nodes, SharpQuery previous = null)
{
    if (nodes == null) throw new ArgumentNullException("nodes");
    _previous = previous;
    _context = new List<HtmlNode>(nodes);
}

测试代码

    static void Main(string[] args)
    {
        var sq = new SharpQuery(@"
<a>
    <b>
        <c/>
        <d/>
        <e/>
        <f>
            <g/>
            <h/>
            <i/>
        </f>
    </b>
</a>");
        var nodes = sq.Traverse();
        Console.WriteLine("{0} nodes: {1}", nodes.Count(), string.Join(",", nodes.Select(n => n.Name)));
        Console.ReadLine();

输出

  

19个节点:#document,a,b,c,g,h,i,d,g,h,i,e,g,h,i,f,g,h,i

预期产出

  

每封字母a-i打印一次。

似乎无法弄清楚它出错的地方...... node.ChildNodes 确实只返回直接的孩子,对吧? (来自HtmlAgilityPack)


全班(浓缩),如果你想尝试自己运行它。

public class SQLite
{
    private readonly List<HtmlNode> _context = new List<HtmlNode>();
    private readonly SQLite _previous = null;

    public SQLite(string html)
    {
        var doc = new HtmlDocument();
        doc.LoadHtml(html);
        _context.Add(doc.DocumentNode);
    }

    public SQLite(IEnumerable<HtmlNode> nodes, SQLite previous = null)
    {
        if (nodes == null) throw new ArgumentNullException("nodes");
        _previous = previous;
        _context = new List<HtmlNode>(nodes);
    }

    public IEnumerable<HtmlNode> Traverse()
    {
        foreach (var node in _context)
        {
            yield return node;
            foreach (var child in Children().Traverse())
                yield return child;
        }
    }

    public SQLite Children()
    {
        return new SQLite(_context.SelectMany(n => n.ChildNodes).Where(n => n.NodeType == HtmlNodeType.Element), this);
    }
}

6 个答案:

答案 0 :(得分:4)

首先,请注意,只要我们迭代没有任何兄弟的元素,一切都按计划进行。一旦我们点击元素<c>,事情就会开始变得混乱。同样有趣的是<c><d><e>都认为它们包含<f>的孩子。

让我们仔细看看您对SelectMany()的电话:

_context.SelectMany(n => n.ChildNodes)

遍历_context中的项目,累积每个项目的子节点。我们来看看_context。一切都应该没问题,因为它是List长度1。或者是吗?

我怀疑你的SharpQuery(string)构造函数将兄弟元素存储在同一个列表中。在这种情况下,_context可能不再是1长度,请记住,SelectMany() 累积列表中每个项目的子节点。

例如,如果_context是包含<c><d><e><f>的列表,则只有<f>有子女, SelectMany()会为每个元素调用一次,它会累积并返回<f>个子元素四次。

我认为这是你的错误。

编辑:由于您已发布完整课程,因此我不必再猜测了。迭代<b>(迭代器替换为列表以便更好地理解)时,请查看操作序列:

  1. 致电Children()上的<b>,其中<c><d><e><f>
  2. 在结果列表中调用Traverse()
    1. Children()上致电<c>,但 _context实际上包含<c><d><e>和{ {1}},不仅<f>,还有<c><g><h>
    2. <i>上致电Children(),同样的事情,因为<d> _context和{ {1}}(以及<c><d>),
    3. 泡沫,冲洗,重复。

答案 1 :(得分:3)

儿童()被打破,它也选择了兄弟姐妹的孩子。我会改写成这样的东西:

public IEnumerable<HtmlNode> Traverse(IEnumerable<HtmlNode> nodes)
{
    foreach (var node in nodes)
    {
        yield return node;
        foreach (var child in Traverse(ChildNodes(node)))
            yield return child;
    }
}

private IEnumerable<HtmlNode> ChildNodes(HtmlNode node)
{
    return node.ChildNodes.Where(n => n.NodeType == HtmlNodeType.Element);
}


public SharpQuery(IEnumerable<HtmlNode> nodes, SharpQuery previous = null)
{
    if (nodes == null) throw new ArgumentNullException("nodes");
    _previous = previous; // Is this necessary?
    _context = new List<HtmlNode>(nodes);
}

答案 2 :(得分:2)

这不应该足够吗?

public IEnumerable<HtmlNode> Traverse()
{
    foreach (var node in _context)
    {
        Console.WriteLine(node.Name);
        yield return node;
        foreach (var child in Children().Traverse())
            {} //yield return child;
    }
}

答案 3 :(得分:1)

我无法确切地说出来,但你可以看到模式是,一旦你的东西遇到“c”的“/”,它开始考虑“g,h,i”成为后续每一个的一部分直到它遇到“f”的“/”。

很可能你有一个额外的循环,你应该骑。

或者出于某种原因,它无法正确计算“/&gt;”部分。

答案 4 :(得分:1)

我会做这样的事情,看看它是否解决了这个问题:

public IEnumerable<HtmlNode> Traverse()
{
foreach (var node in _context)
{
    Console.WriteLine(node.Name);
    if(!node.HasChildren) {
     yield return node;
    }
    else {
    foreach (var child in Children().Traverse())
        yield return child;
    }
    }
}
}

答案 5 :(得分:0)

public IEnumerable<HtmlNode> All()
{
    var queue = new Queue<HtmlNode>(_context);

    while (queue.Count > 0)
    {
        var node = queue.Dequeue();
        yield return node;
        foreach (var next in node.ChildNodes.Where(n => n.NodeType == HtmlNodeType.Element))
            queue.Enqueue(next);
    }
}

courtesy link