/
search_compact.go
153 lines (142 loc) · 3.43 KB
/
search_compact.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package rets
import (
"bytes"
"encoding/hex"
"encoding/xml"
"io"
"strings"
"context"
)
// SearchCompact wraps up most of the intermediate steps
func SearchCompact(ctx context.Context, requester Requester, r SearchRequest) (*CompactSearchResult, error) {
body, err := SearchStream(SearchResponse(ctx, requester, r))
if err != nil {
return nil, err
}
return NewCompactSearchResult(body)
}
// CompactSearchResult provides processing given the components from the search
type CompactSearchResult struct {
Response Response
Count int
Delimiter string
Columns Row
body io.ReadCloser
parser *xml.Decoder
buf bytes.Buffer
}
// EachRow ...
type EachRow func(row Row, err error) error
// ForEach returns MaxRows and any error that 'each' wont handle
func (c *CompactSearchResult) ForEach(each EachRow) (bool, error) {
if c.body == nil {
return false, nil
}
maxRows := false
for {
token, err := c.parser.Token()
if err != nil {
// dont catch io.EOF here since a clean read should exit at the </RETS> tag
if err = each(nil, err); err != nil {
return maxRows, err
}
continue
}
switch t := token.(type) {
case xml.StartElement:
// clear any accumulated data
c.buf.Reset()
// check tags
switch t.Name.Local {
case "MAXROWS":
maxRows = true
}
case xml.EndElement:
switch t.Name.Local {
case "DATA":
err := each(CompactRow(c.buf.String()).Parse(c.Delimiter), nil)
if err != nil {
return maxRows, err
}
case XMLElemRETS, XMLElemRETSStatus:
c.Close() // close the stream since we've read to the end
return maxRows, nil
}
case xml.CharData:
bytes := xml.CharData(t)
c.buf.Write(bytes)
}
}
}
// Close ...
func (c *CompactSearchResult) Close() error {
if c == nil || c.body == nil {
return nil
}
tmp := c.body
c.body = nil
return tmp.Close()
}
// NewCompactSearchResult _always_ close this
func NewCompactSearchResult(body io.ReadCloser) (*CompactSearchResult, error) {
parser := DefaultXMLDecoder(body, false)
result := &CompactSearchResult{
body: body,
parser: parser,
}
// extract the basic content before delving into the data
for {
token, err := parser.Token()
if err != nil {
return result, err
}
switch t := token.(type) {
case xml.StartElement:
// clear any accumulated data
result.buf.Reset()
switch t.Name.Local {
case XMLElemRETS, XMLElemRETSStatus:
resp, er := ResponseTag(t).Parse()
if er != nil {
return result, er
}
result.Response = *resp
case "COUNT":
result.Count, err = countTag(t).Parse()
if err != nil {
return result, err
}
case "DELIMITER":
result.Delimiter, err = DelimiterTag(t).Parse()
if err != nil {
return result, err
}
}
case xml.EndElement:
switch t.Name.Local {
case "COLUMNS":
result.Columns = CompactRow(result.buf.String()).Parse(result.Delimiter)
return result, nil
case XMLElemRETS, XMLElemRETSStatus:
// if there is only a RETS tag.. close the stream and just exit
result.Close()
return result, nil
}
case xml.CharData:
bytes := xml.CharData(t)
result.buf.Write(bytes)
}
}
}
// DelimiterTag holds the separator for compact data
type DelimiterTag xml.StartElement
// Parse ...
func (dt DelimiterTag) Parse() (string, error) {
del := dt.Attr[0].Value
pad := strings.Repeat("0", 2-len(del))
decoded, err := hex.DecodeString(pad + del)
if err != nil {
return "", err
}
return string(decoded), nil
}