mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-16 11:23:40 +00:00
ch5-2: fmt code
This commit is contained in:
parent
152f42f599
commit
60f822294d
126
ch5/ch5-02.md
126
ch5/ch5-02.md
@ -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。我們以fetch(1.5章)的輸出作爲findlinks的輸入。下面的輸出做了簡化處理。
|
讓我們以Go的主頁(golang.org)作爲目標,運行findlinks。我們以fetch(1.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。
|
||||||
|
Loading…
Reference in New Issue
Block a user