找到元素时停止递归函数

I am trying to solve an exercise from The go programming language book: The starting code can be found here: exercise.

What I need to do:

Modify forEachNode so that the pre and post functions return a boolean result indicating whether to continue the traversal. Use it to write a function ElementByID with the following signature that finds the first HTML element with the specified id attribute. The function should stop the traversal as soon as a match is found.

Signature: func ElementByID(doc *html.Node, id string) *html.Node

What I did:

func ElementByID(doc *html.Node, id string) *html.Node {                                                         
  if doc.Data == id {                                                                                            
    fmt.Printf(" %s: %s
", "found", doc.Data)                                                                   
    return doc                                                                                                   
  }                                                                                                              
  return nil                                                                                                     
}

func startElement(n *html.Node) bool {
  if n.Type == html.ElementNode {
    if ElementById(n, "a") != nil {
      return true
    }
    fmt.Printf("%*s<%s>
", depth*2, "", n.Data)
    depth++
  }
  return false
}
func endElement(n *html.Node) bool {
  if n.Type == html.ElementNode {
    if ElementById(n, "a") != nil {
      return true
    }
    depth--
    fmt.Printf("%*s</%s>
", depth*2, "", n.Data)
  }
  return false
}

Is the above right?, or I've missed something? How can I stop the traversal where element is found?

The forEachNode is the same, only the pre and post signature was changed to return a bool.

You can create a closure and "close" found node. Example below.

Modify forEachNode so that the pre and post functions return a boolean result indicating whether to continue the traversal.:

func forEachNode(n *html.Node, pre, post func(n *html.Node) bool) {
    if pre != nil && !pre(n) {
        return
    }

    for c := n.FirstChild; c != nil; c = c.NextSibling {
        forEachNode(c, pre, post)
    }

    if post != nil && !post(n) {
        return
    }
}

Use it to write a function ElementByID with the following signature that finds the first HTML element with the specified id attribute. The function should stop the traversal as soon as a match is found.:

func ElementByID(doc *html.Node, id string) *html.Node {

    var found *html.Node

    pre := func(n *html.Node) bool {
        for _, a := range n.Attr {
            if a.Key == "id" && a.Val == id {
                found = n // memorize matching node
                return false // stop traversing
            }
        }
        return true
    }

    forEachNode(doc, pre, nil)
    return found
}