ch5-2: fmt code

This commit is contained in:
chai2010 2016-01-02 21:34:50 +08:00
parent 152f42f599
commit 60f822294d

View File

@ -6,73 +6,80 @@
例子中調用golang.org/x/net/html的部分api如下所示。html.Parse函數讀入一組bytes.解析後返迴html.node類型的HTML頁面樹狀結構根節點。HTML擁有很多類型的結點如text文本,commnets註釋類型在下面的例子中我們 隻關註< name key='value' >形式的結點。 例子中調用golang.org/x/net/html的部分api如下所示。html.Parse函數讀入一組bytes.解析後返迴html.node類型的HTML頁面樹狀結構根節點。HTML擁有很多類型的結點如text文本,commnets註釋類型在下面的例子中我們 隻關註< name key='value' >形式的結點。
``` ```Go
golang.org/x/net/html golang.org/x/net/html
package html package html
type Node struct { type Node struct {
Type NodeType Type NodeType
Data string Data string
Attr []Attribute Attr []Attribute
FirstChild, NextSibling *Node FirstChild, NextSibling *Node
} }
type NodeType int32 type NodeType int32
const ( const (
ErrorNode NodeType = iota ErrorNode NodeType = iota
TextNode TextNode
DocumentNode DocumentNode
ElementNode ElementNode
CommentNode CommentNode
DoctypeNode DoctypeNode
) )
type Attribute struct { type Attribute struct {
Key, Val string Key, Val string
} }
func Parse(r io.Reader) (*Node, error) func Parse(r io.Reader) (*Node, error)
``` ```
main函數解析HTML標準輸入通過遞歸函數visit獲得links鏈接併打印出這些links main函數解析HTML標準輸入通過遞歸函數visit獲得links鏈接併打印出這些links
``` ```Go
gopl.io/ch5/findlinks1 gopl.io/ch5/findlinks1
// Findlinks1 prints the links in an HTML document read // Findlinks1 prints the links in an HTML document read from standard input.
from standard input.
package main package main
import ( import (
"fmt" "fmt"
"os" "os"
"golang.org/x/net/html"
"golang.org/x/net/html"
) )
func main() { func main() {
doc, err := html.Parse(os.Stdin) doc, err := html.Parse(os.Stdin)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
os.Exit(1) os.Exit(1)
} }
for _, link := range visit(nil, doc) { for _, link := range visit(nil, doc) {
fmt.Println(link) fmt.Println(link)
} }
} }
``` ```
visit函數遍歷HTML的節點樹從每一個anchor元素的href屬性獲得link,將這些links存入字符串數組中併返迴這個字符串數組。 visit函數遍歷HTML的節點樹從每一個anchor元素的href屬性獲得link,將這些links存入字符串數組中併返迴這個字符串數組。
``` ```Go
// visit appends to links each link found in n and returns // visit appends to links each link found in n and returns the result.
the result.
func visit(links []string, n *html.Node) []string { func visit(links []string, n *html.Node) []string {
if n.Type == html.ElementNode && n.Data == "a" { if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr { for _, a := range n.Attr {
if a.Key == "href" { if a.Key == "href" {
links = append(links, a.Val) links = append(links, a.Val)
} }
} }
} }
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {
links = visit(links, c) links = visit(links, c)
} }
return links return links
} }
``` ```
爲了遍歷結點n的所有後代結點每次遇到n的孩子結點時visit遞歸的調用自身。這些孩子結點存放在FirstChild鏈表中。 爲了遍歷結點n的所有後代結點每次遇到n的孩子結點時visit遞歸的調用自身。這些孩子結點存放在FirstChild鏈表中。
讓我們以Go的主頁golang.org作爲目標運行findlinks。我們以fetch1.5章的輸出作爲findlinks的輸入。下面的輸出做了簡化處理。 讓我們以Go的主頁golang.org作爲目標運行findlinks。我們以fetch1.5章的輸出作爲findlinks的輸入。下面的輸出做了簡化處理。
@ -94,34 +101,35 @@ https://golang.org/dl/
/doc/tos.html /doc/tos.html
http://www.google.com/intl/en/policies/privacy/ http://www.google.com/intl/en/policies/privacy/
``` ```
註意在頁面中出現的鏈接格式在之後我們會介紹如何將這些鏈接根據根路徑https://golang.org生成可以直接訪問的url。
註意在頁面中出現的鏈接格式,在之後我們會介紹如何將這些鏈接,根據根路徑( https://golang.org 生成可以直接訪問的url。
在函數outline中我們通過遞歸的方式遍歷整個HTML結點樹併輸出樹的結構。在outline內部每遇到一個HTML元素標籤就將其入棧併輸出。 在函數outline中我們通過遞歸的方式遍歷整個HTML結點樹併輸出樹的結構。在outline內部每遇到一個HTML元素標籤就將其入棧併輸出。
``` ```Go
gopl.io/ch5/outline gopl.io/ch5/outline
func main() { func main() {
doc, err := html.Parse(os.Stdin) doc, err := html.Parse(os.Stdin)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "outline: %v\n", err) fmt.Fprintf(os.Stderr, "outline: %v\n", err)
os.Exit(1) os.Exit(1)
} }
outline(nil, doc) outline(nil, doc)
} }
func outline(stack []string, n *html.Node) { func outline(stack []string, n *html.Node) {
if n.Type == html.ElementNode { if n.Type == html.ElementNode {
stack = append(stack, n.Data) // push tag stack = append(stack, n.Data) // push tag
fmt.Println(stack) fmt.Println(stack)
} }
for c := n.FirstChild; c != nil; c = c.NextSibling { for c := n.FirstChild; c != nil; c = c.NextSibling {
outline(stack, c) outline(stack, c)
} }
} }
``` ```
有一點值得註意outline有入棧操作但沒有相對應的出棧操作。當outline調用自身時被調用者接收的是stack的拷貝。被調用者的入棧操作脩改的是stack的拷貝而不是調用者的stack,因對當函數返迴時,調用者的stack併未被脩改。 有一點值得註意outline有入棧操作但沒有相對應的出棧操作。當outline調用自身時被調用者接收的是stack的拷貝。被調用者的入棧操作脩改的是stack的拷貝而不是調用者的stack,因對當函數返迴時,調用者的stack併未被脩改。
下面是https://golang.org頁面的簡要結構: 下面是 https://golang.org 頁面的簡要結構:
``` ```
$ go build gopl.io/ch5/outline $ go build gopl.io/ch5/outline
@ -140,11 +148,15 @@ $ ./fetch https://golang.org | ./outline
[html body div div form div a] [html body div div form div a]
... ...
``` ```
正如你在上面實驗中所見大部分HTML頁面隻需幾層遞歸就能被處理但仍然有些頁面需要深層次的遞歸。 正如你在上面實驗中所見大部分HTML頁面隻需幾層遞歸就能被處理但仍然有些頁面需要深層次的遞歸。
大部分編程語言使用固定大小的函數調用棧常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度當你用遞歸處理大量數據時需要避免棧溢出除此之外還會導致安全性問題。與相反,Go語言使用可變棧棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。 大部分編程語言使用固定大小的函數調用棧常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度當你用遞歸處理大量數據時需要避免棧溢出除此之外還會導致安全性問題。與相反,Go語言使用可變棧棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。
練習**5.1** :脩改findlinks代碼中遍歷n.FirstChild鏈表的部分將循環調用visit改成遞歸調用。 練習**5.1** :脩改findlinks代碼中遍歷n.FirstChild鏈表的部分將循環調用visit改成遞歸調用。
練習**5.2** : 編寫函數記録在HTML樹中出現的同名元素的次數。 練習**5.2** : 編寫函數記録在HTML樹中出現的同名元素的次數。
練習**5.3** : 編寫函數輸出所有text結點的內容。註意不要訪問< script >< style >,
練習**5.3** : 編寫函數輸出所有text結點的內容。註意不要訪問`<script>``<style>`,
練習**5.4** : 擴展vist函數使其能夠處理其他類型的結點如images、scripts和style sheets。 練習**5.4** : 擴展vist函數使其能夠處理其他類型的結點如images、scripts和style sheets。