Go 語(yǔ)言 示例: 基于標(biāo)記的XML解碼

2023-03-14 16:55 更新

原文鏈接:https://gopl-zh.github.io/ch7/ch7-14.html


7.14. 示例: 基于標(biāo)記的XML解碼

第4.5章節(jié)展示了如何使用encoding/json包中的Marshal和Unmarshal函數(shù)來(lái)將JSON文檔轉(zhuǎn)換成Go語(yǔ)言的數(shù)據(jù)結(jié)構(gòu)。encoding/xml包提供了一個(gè)相似的API。當(dāng)我們想構(gòu)造一個(gè)文檔樹(shù)的表示時(shí)使用encoding/xml包會(huì)很方便,但是對(duì)于很多程序并不是必須的。encoding/xml包也提供了一個(gè)更低層的基于標(biāo)記的API用于XML解碼。在基于標(biāo)記的樣式中,解析器消費(fèi)輸入并產(chǎn)生一個(gè)標(biāo)記流;四個(gè)主要的標(biāo)記類型-StartElement,EndElement,CharData,和Comment-每一個(gè)都是encoding/xml包中的具體類型。每一個(gè)對(duì)(*xml.Decoder).Token的調(diào)用都返回一個(gè)標(biāo)記。

這里顯示的是和這個(gè)API相關(guān)的部分:

encoding/xml

package xml

type Name struct {
    Local string // e.g., "Title" or "id"
}

type Attr struct { // e.g., name="value"
    Name  Name
    Value string
}

// A Token includes StartElement, EndElement, CharData,
// and Comment, plus a few esoteric types (not shown).
type Token interface{}
type StartElement struct { // e.g., <name>
    Name Name
    Attr []Attr
}
type EndElement struct { Name Name } // e.g., </name>
type CharData []byte                 // e.g., <p>CharData</p>
type Comment []byte                  // e.g., <!-- Comment -->

type Decoder struct{ /* ... */ }
func NewDecoder(io.Reader) *Decoder
func (*Decoder) Token() (Token, error) // returns next Token in sequence

這個(gè)沒(méi)有方法的Token接口也是一個(gè)可識(shí)別聯(lián)合的例子。傳統(tǒng)的接口如io.Reader的目的是隱藏滿足它的具體類型的細(xì)節(jié),這樣就可以創(chuàng)造出新的實(shí)現(xiàn):在這個(gè)實(shí)現(xiàn)中每個(gè)具體類型都被統(tǒng)一地對(duì)待。相反,滿足可識(shí)別聯(lián)合的具體類型的集合被設(shè)計(jì)為確定和暴露,而不是隱藏??勺R(shí)別聯(lián)合的類型幾乎沒(méi)有方法,操作它們的函數(shù)使用一個(gè)類型分支的case集合來(lái)進(jìn)行表述,這個(gè)case集合中每一個(gè)case都有不同的邏輯。

下面的xmlselect程序獲取和打印在一個(gè)XML文檔樹(shù)中確定的元素下找到的文本。使用上面的API,它可以在輸入上一次完成它的工作而從來(lái)不要實(shí)例化這個(gè)文檔樹(shù)。

gopl.io/ch7/xmlselect

// Xmlselect prints the text of selected elements of an XML document.
package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "os"
    "strings"
)

func main() {
    dec := xml.NewDecoder(os.Stdin)
    var stack []string // stack of element names
    for {
        tok, err := dec.Token()
        if err == io.EOF {
            break
        } else if err != nil {
            fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err)
            os.Exit(1)
        }
        switch tok := tok.(type) {
        case xml.StartElement:
            stack = append(stack, tok.Name.Local) // push
        case xml.EndElement:
            stack = stack[:len(stack)-1] // pop
        case xml.CharData:
            if containsAll(stack, os.Args[1:]) {
                fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok)
            }
        }
    }
}

// containsAll reports whether x contains the elements of y, in order.
func containsAll(x, y []string) bool {
    for len(y) <= len(x) {
        if len(y) == 0 {
            return true
        }
        if x[0] == y[0] {
            y = y[1:]
        }
        x = x[1:]
    }
    return false
}

main函數(shù)中的循環(huán)每遇到一個(gè)StartElement時(shí),它把這個(gè)元素的名稱壓到一個(gè)棧里,并且每次遇到EndElement時(shí),它將名稱從這個(gè)棧中推出。這個(gè)API保證了StartElement和EndElement的序列可以被完全的匹配,甚至在一個(gè)糟糕的文檔格式中。注釋會(huì)被忽略。當(dāng)xmlselect遇到一個(gè)CharData時(shí),只有當(dāng)棧中有序地包含所有通過(guò)命令行參數(shù)傳入的元素名稱時(shí),它才會(huì)輸出相應(yīng)的文本。

下面的命令打印出任意出現(xiàn)在兩層div元素下的h2元素的文本。它的輸入是XML的說(shuō)明文檔,并且它自己就是XML文檔格式的。

$ go build gopl.io/ch1/fetch
$ ./fetch http://www.w3.org/TR/2006/REC-xml11-20060816 |
    ./xmlselect div div h2
html body div div h2: 1 Introduction
html body div div h2: 2 Documents
html body div div h2: 3 Logical Structures
html body div div h2: 4 Physical Structures
html body div div h2: 5 Conformance
html body div div h2: 6 Notation
html body div div h2: A References
html body div div h2: B Definitions for Character Normalization
...

練習(xí) 7.17: 擴(kuò)展xmlselect程序以便讓元素不僅可以通過(guò)名稱選擇,也可以通過(guò)它們CSS風(fēng)格的屬性進(jìn)行選擇。例如一個(gè)像這樣

<div id="page" class="wide">

的元素可以通過(guò)匹配id或者class,同時(shí)還有它的名稱來(lái)進(jìn)行選擇。

練習(xí) 7.18: 使用基于標(biāo)記的解碼API,編寫一個(gè)可以讀取任意XML文檔并構(gòu)造這個(gè)文檔所代表的通用節(jié)點(diǎn)樹(shù)的程序。節(jié)點(diǎn)有兩種類型:CharData節(jié)點(diǎn)表示文本字符串,和 Element節(jié)點(diǎn)表示被命名的元素和它們的屬性。每一個(gè)元素節(jié)點(diǎn)有一個(gè)子節(jié)點(diǎn)的切片。

你可能發(fā)現(xiàn)下面的定義會(huì)對(duì)你有幫助。

import "encoding/xml"

type Node interface{} // CharData or *Element

type CharData string

type Element struct {
    Type     xml.Name
    Attr     []xml.Attr
    Children []Node
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)