-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstream_parser.go
More file actions
124 lines (102 loc) · 4 KB
/
stream_parser.go
File metadata and controls
124 lines (102 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package libxml
/* StreamParser implements declarative XML structure parsing.
It takes structure definition to define document tags or can setup structure and tags processing after creation.
*/
import (
"encoding/xml"
"errors"
"fmt"
)
var (
errUnexpectedTag = errors.New("unexpected tag")
errUnexpectedTransition = errors.New("unexpected transition")
)
// StreamParser implements libxml.XmlStreamParser.
// It requires parsing structure created before parse with Tag and Parse methods.
type StreamParser struct {
strict bool
current *Element
}
// Tag adds and returns new element with specified tag name.
// Parsing functions for tag should set using Element OnEnter, OnData and OnExit methods.
func (parser *StreamParser) Tag(names ...TagName) *Element {
return parser.current.Root().Tag(names...)
}
// Parse creates new root element one time with parsing functions set for enter, processing data and tag exit.
func (parser *StreamParser) Parse(name TagName, in EnterFunc, dataFunc DataFunc, out ExitFunc) *Element {
return parser.current.Root().Parse(name, in, dataFunc, out)
}
// ProcessComment implements comments parse. Implements XmlStreamParser.
func (parser StreamParser) ProcessComment(_ xml.Comment) error { return nil }
// ProcessProcInst implements processing instruction parse. Implements XmlStreamParser.
func (parser StreamParser) ProcessProcInst(_ xml.ProcInst) error { return nil }
// ProcessDirective implements directive parse. Implements XmlStreamParser.
func (parser StreamParser) ProcessDirective(_ xml.Directive) error { return nil }
// Root returns root Element.
func (parser *StreamParser) Root() *Element {
return parser.current.Root()
}
// ProcessStartElement processes token start. Implements XmlStreamParser.
// It checks if current Element has specified Tag child, set detected child element as current
// and executes OnEnter function if defined.
// If Strict mode set and no child defined returns with error.
func (parser *StreamParser) ProcessStartElement(token xml.StartElement) (err error) {
var (
next *Element
ok bool
)
tagName := TagName(token.Name.Local)
next, ok = parser.current.structure[tagName]
switch {
case parser.strict && !ok:
return fmt.Errorf("%w: %v.%v", errUnexpectedTransition, parser.current.tagName, token.Name.Local)
case !ok && !parser.strict:
next = parser.current.Tag(tagName)
}
// set current element
parser.current = next
// take element enter function and call it if set
if next.enter != nil {
return next.enter(token)
}
return nil
}
// ProcessCharData processes tag (string) data. Processing Element should have OnData function set.
// Returns error from current tag processing function if encountered.
func (parser *StreamParser) ProcessCharData(data xml.CharData) error {
if parser.current.data != nil {
return parser.current.data(data)
}
return nil
}
// ProcessEndElement processes tag closing.
// Calls ExitFunc for current element if set with OnExit method of Element.
// If no exit processing function set to current Element it simply returns previous element.
// Returns ExitFunc error if encountered.
func (parser *StreamParser) ProcessEndElement(element xml.EndElement) (err error) {
tagName := TagName(element.Name.Local)
if parser.current.tagName != tagName {
return fmt.Errorf("%w: closing %v in %v", errUnexpectedTag, tagName, parser.current.tagName)
}
defer func() {
if parser.current.parent != nil { // move to outer element
parser.current = parser.current.parent
}
}()
if parser.current.exit != nil {
return parser.current.exit()
}
return nil
}
// NewParser creates new parser.
// If strict is false, any unexpected tag will be silently ignored.
// If strict is true structure should define all required tag elements
// with parser root or some element tag method, f.e. parser.Tag("ignore", "this", "tree").
// In struct mode any unexpected tag will produce parsing error.
func NewParser(strict bool) *StreamParser {
p := &StreamParser{
strict: strict,
current: MakeElement(nil, RootTag, nil, nil, nil),
}
return p
}