-
Notifications
You must be signed in to change notification settings - Fork 24
/
xml.go
119 lines (101 loc) · 2.69 KB
/
xml.go
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
package xml
import (
"context"
"encoding/xml"
"fmt"
"io"
"strings"
"github.com/observiq/stanza/entry"
"github.com/observiq/stanza/operator"
"github.com/observiq/stanza/operator/helper"
)
func init() {
operator.Register("xml_parser", func() operator.Builder { return NewXMLParserConfig("") })
}
// NewXMLParserConfig creates a new XML parser config with default values
func NewXMLParserConfig(operatorID string) *XMLParserConfig {
return &XMLParserConfig{
ParserConfig: helper.NewParserConfig(operatorID, "xml_parser"),
}
}
// XMLParserConfig is the configuration of an XML parser operator.
type XMLParserConfig struct {
helper.ParserConfig `yaml:",inline"`
Strict *bool `yaml:"strict,omitempty"`
}
// Build will build an XML parser operator.
func (c XMLParserConfig) Build(context operator.BuildContext) ([]operator.Operator, error) {
parserOperator, err := c.ParserConfig.Build(context)
if err != nil {
return nil, err
}
strict := true
if c.Strict != nil {
strict = *c.Strict
}
xmlParser := &XMLParser{
ParserOperator: parserOperator,
strict: strict,
}
return []operator.Operator{xmlParser}, nil
}
// XMLParser is an operator that parses XML.
type XMLParser struct {
helper.ParserOperator
strict bool
}
// Process will parse an entry for XML.
func (x *XMLParser) Process(ctx context.Context, entry *entry.Entry) error {
return x.ParserOperator.ProcessWith(ctx, entry, x.parse)
}
// parse will parse an xml value
func (x *XMLParser) parse(value interface{}) (interface{}, error) {
strValue, ok := value.(string)
if !ok {
return nil, fmt.Errorf("value passed to parser is not a string")
}
reader := strings.NewReader(strValue)
decoder := xml.NewDecoder(reader)
decoder.Strict = x.strict
token, err := decoder.Token()
if err != nil {
return nil, fmt.Errorf("failed to decode as xml: %w", err)
}
elements := []*Element{}
var parent *Element
var current *Element
for token != nil {
switch token := token.(type) {
case xml.StartElement:
parent = current
current = newElement(token)
current.Parent = parent
if parent != nil {
parent.Children = append(parent.Children, current)
} else {
elements = append(elements, current)
}
case xml.EndElement:
current = parent
if parent != nil {
parent = parent.Parent
}
case xml.CharData:
if current != nil {
current.Content = getValue(token)
}
}
token, err = decoder.Token()
if err != nil && err != io.EOF {
return nil, fmt.Errorf("failed to get next xml token: %w", err)
}
}
switch len(elements) {
case 0:
return nil, fmt.Errorf("no xml elements found")
case 1:
return convertToMap(elements[0]), nil
default:
return convertToMaps(elements), nil
}
}