这是正确使用模式匹配和活动模式吗?

时间:2010-11-07 13:23:47

标签: f#

我是F#的新手并且正在使用并正在处理一些HTML解析代码。我想从HTML文档中删除符合某些条件的元素。在这里,我有一系列对象(HtmlNodes),并希望从文档中删除它们。

这是使用模式匹配的惯用方法吗?此外,由于HtmlNode.Remove()对原始HtmlDocument对象有副作用,是否有任何特定的方法来构造代码以使副作用明显或如何处理。使用代码,你可以像你一样迂腐。

open HtmlAgilityPack

let removeNodes (node : HtmlNode) = 

    let (|HiddenNodeCount|) (n : HtmlNode) =
        match n.SelectNodes("*[@style[contains(.,'visibility:hidden')]]") with
        | null -> 0
        | _ as x -> Seq.length x

    match node with
        | x when x.Name.ToLower() = "script" -> node.Remove() 
        | x when x.NodeType = HtmlNodeType.Comment -> node.Remove()
        | HiddenNodeCount x when x > 0 -> node.Remove()
        | _ -> ()

let html = "some long messy html code would be here"
let dom = new HtmlDocument(OptionAutoCloseOnEnd=true)
dom.LoadHtml(html)

let nodes = dom.DocumentNode.DescendantNodes()
nodes |> Seq.toArray |> Array.iter removeNodes

2 个答案:

答案 0 :(得分:1)

就个人而言,当您没有要分解的数据结构时,我更喜欢if elif else而不是模式匹配(它只是更少的输入,也可以用来区分结构何时被分解而不是简单的案例测试)

您的代码中有一些奇怪的东西。 Active Pattern在这里不是很有用,原因有二:首先,它的范围仅限于removeNodes,所以它只使用一次。我将在稍后讨论第二个问题,但首先我将展示如何通过消除活动模式来编写它,至少对我来说,使副作用更明显(通过分离测试节点是否应该是的代码)从执行删除的代码中删除了:

let shouldRemoveNode (node : HtmlNode) = 
    if node.Name.ToLower() = "script" then true
    elif node.NodeType = HtmlNodeType.Comment then true
    else match node.SelectNodes("*[@style[contains(.,'visibility:hidden')]]") with
         | null -> false
         | x -> Seq.length x > 0

let removeNode (node: HtmlNode) = 
    if shouldRemoveNode(node) then node.Remove() else ()

注意我在可见性隐藏查询中使用了模式匹配,因为我确实要匹配null并绑定到x,否则(而不是绑定到x,然后使用if else测试x)。

Active Pattern的第二个奇怪之处在于,您正在使用它将节点转换为int,但是您获得的长度并不是立即有用(您仍然需要对它执行测试)。而在这里更强大的使用活动模式将是将节点划分为不同类型(假设这不是临时的,这可能是第一点)。所以你可以:

//expand to encompass several other kinds of nodes
let (|Script|Comment|Hidden|Other|) (node : HtmlNode) = 
    if node.Name.ToLower() = "script" then Script
    elif node.NodeType = HtmlNodeType.Comment then Comment
    else match node.SelectNodes("*[@style[contains(.,'visibility:hidden')]]") with
         | null -> Other
         | x -> if Seq.length x > 0 then Hidden
                else Other

let removeNode (node: HtmlNode) = 
    match node with
    | Script | Comment | Hidden -> node.Remove()
    | Other -> ()

修改

@Pascal在评论中观察到shouldRemoveNode可以进一步浓缩为一个大的布尔表达式:

let shouldRemoveNode (node : HtmlNode) = 
    node.Name.ToLower() = "script" || 
    node.NodeType = HtmlNodeType.Comment ||
    match node.SelectNodes("*[@style[contains(.,'visibility:hidden')]]") with
    | null -> false
    | x -> Seq.length x > 0

答案 1 :(得分:0)

我不清楚这比使用函数和if-then-else更好,例如

let HiddenNodeCount (n : HtmlNode) = 
    match n.SelectNodes("*[@style[contains(.,'visibility:hidden')]]") with 
    | null -> 0 
    | x -> Seq.length x 

if node.Name.ToLower() = "script" then 
    node.Remove()  
elif node.NodeType = HtmlNodeType.Comment then
    node.Remove()  
elif HiddenNodeCount node > 0 then 
    node.Remove()