问题返回<a> link excluding mailto links

时间:2015-10-19 15:47:05

标签: c# linq html-agility-pack linq-to-objects

I am making use of the HTML Agility Pack to define a function that returns the links on a web page. The issue is that it returns all links including mailto.

Later in the program, when the links are processed the mailto links break. I'm trying to eliminate their inclusion in the function output list of _links

My function is defined as:

var linkNodes = _htmlDocument.Value.DocumentNode.SelectNodes("//a");
if (linkNodes == null)
    return Enumerable.Empty<Link>();

var links = new List<Link>();
foreach (var linkNode in linkNodes)
{
    var href = linkNode.GetAttributeValue("href", "#");
    if (!Uri.IsWellFormedUriString(href, UriKind.RelativeOrAbsolute))
        continue;

    var url = href.ToAbsoluteUri(Url);
    var follow = linkNode.GetAttributeValue("rel", "follow");

    links.Add(new Link(Url, url, linkNode.InnerText, follow));
}

_links = links;

My LINQ that almost worked (worked in getting rid of mailto, but returned strings instead of the nodes that match the fighters used):

var linkNodes = _htmlDocument.Value.DocumentNode.SelectNodes("//a[@href]")
                        .Select(a => a.Attributes["href"].Value)
                        .Where(href => !href.StartsWith("mailto:")) // skip emails, find only url links
                        .ToList();

1 个答案:

答案 0 :(得分:2)

关于选择和位置:

根据MSDN

Linq Select会根据该集合的项目将您的集合转换为新表单。这是一个简单的例子。

IEnumerable<int> collectionOfInt = Enumerable.Range(0, 10);
IEnumerable<string> collectionOfString = collectionOfInt.Select(i => i.ToString());
IEnumerable<int> lengthOfStrings = collectionOfString.Select(str => str.Length);

首先,您有一个从0到9的int集合。如您所见,Select会返回一个新的字符串集合,但会基于collectionOfInt的项目,因此您拥有来自"0","1",...,"9"的字符串。请注意,Select的执行是延迟的,因此必须使用ToList来实际执行该查询。

Select上执行collectionOfString时也是如此。正如您所看到的那样,您松散了实际的字符串,而您将获得这些字符串的长度(1,1,...,1)。

现在你的Linq

var linkNodes = _htmlDocument.Value.DocumentNode.SelectNodes("//a[@href]")
                    .Select(a => a.Attributes["href"].Value)
                    .Where(href => !href.StartsWith("mailto:"))
                    .ToList();

同样的事情发生在这里。你有一组节点,但Select(a => a.Attributes["href"].Value)实际上会将你的节点变成字符串集合,你会松散实际的节点。

.Select(a => a.Attributes["href"].Value) // Changes main collection into values
.Where(href => !href.StartsWith("mailto:")) // searches on values not main collection thus returns values again.

所以你必须把它全部放在Where部分。因为Where不会更改集合类型。它只在该元素的条件为真时从集合中选择元素。

根据我的解释href在之前的查询中是a.Attributes["href"].Value。因此,为了不松散原始元素,只需将a.Attributes["href"].Value包裹在href内,所以你将拥有

.Where(node => !node.Attributes["href"].Value.StartsWith("mailto:")) // searches on nodes collection thus returns nodes

关于Null例外部分:

Where Linq查询不会搜索null的项目。因此,只要前一个查询中的hrefa.Attributes["href"].Value为空,它就会跳过该项而不选择它。

Select内联到Where后,现在只检查node的可为空性,而不是执行函数!node.Attributes["href"].Value.StartsWith("mailto:")

基本上因为Value可能为null,你将在StartsWith("mailto:")上获得无法处理null的异常。

在C#6中,您可以通过混合null conditionalNull-coalescing运算符来解决此问题。

htmlDocument.Value.DocumentNode.SelectNodes("//a[@href]")
    .Where(node => !node.Attributes["href"].Value?.StartsWith("mailto:") ?? false).ToList();

如果Value?.的值为null,则不会继续执行StartsWith("mailto:"),而是直接返回null。

由于?.的返回类型为nullable bool,因此当运算符的左侧为空时,?? false将返回false。