Skip to content

Commit 64ca73d

Browse files
committed
Merge PR '#38'. Expose functions:AddAttr, AddChild, AddSibling, RemoveFromTree
2 parents 8049e7d + 2958a82 commit 64ca73d

File tree

4 files changed

+84
-43
lines changed

4 files changed

+84
-43
lines changed

node.go

+16-6
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ func (n *Node) OutputXML(self bool) string {
141141
return buf.String()
142142
}
143143

144-
func addAttr(n *Node, key, val string) {
144+
// AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'.
145+
func AddAttr(n *Node, key, val string) {
145146
var attr xml.Attr
146147
if i := strings.Index(key, ":"); i > 0 {
147148
attr = xml.Attr{
@@ -158,10 +159,13 @@ func addAttr(n *Node, key, val string) {
158159
n.Attr = append(n.Attr, attr)
159160
}
160161

161-
func addChild(parent, n *Node) {
162+
// AddChild adds a new node 'n' to a node 'parent' as its last child.
163+
func AddChild(parent, n *Node) {
162164
n.Parent = parent
165+
n.NextSibling = nil
163166
if parent.FirstChild == nil {
164167
parent.FirstChild = n
168+
n.PrevSibling = nil
165169
} else {
166170
parent.LastChild.NextSibling = n
167171
n.PrevSibling = parent.LastChild
@@ -170,21 +174,27 @@ func addChild(parent, n *Node) {
170174
parent.LastChild = n
171175
}
172176

173-
func addSibling(sibling, n *Node) {
177+
// AddSibling adds a new node 'n' as a sibling of a given node 'sibling'.
178+
// Note it is not necessarily true that the new node 'n' would be added
179+
// immediately after 'sibling'. If 'sibling' isn't the last child of its
180+
// parent, then the new node 'n' will be added at the end of the sibling
181+
// chain of their parent.
182+
func AddSibling(sibling, n *Node) {
174183
for t := sibling.NextSibling; t != nil; t = t.NextSibling {
175184
sibling = t
176185
}
177186
n.Parent = sibling.Parent
178187
sibling.NextSibling = n
179188
n.PrevSibling = sibling
189+
n.NextSibling = nil
180190
if sibling.Parent != nil {
181191
sibling.Parent.LastChild = n
182192
}
183193
}
184194

185-
// removes a node and its subtree from the tree it is in.
186-
// If the node is the root of the tree, then it's no-op.
187-
func removeFromTree(n *Node) {
195+
// RemoveFromTree removes a node and its subtree from the document
196+
// tree it is in. If the node is the root of the tree, then it's no-op.
197+
func RemoveFromTree(n *Node) {
188198
if n.Parent == nil {
189199
return
190200
}

node_test.go

+47-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
package xmlquery
22

33
import (
4+
"encoding/xml"
45
"html"
56
"reflect"
67
"strings"
78
"testing"
89
)
910

11+
func findRoot(n *Node) *Node {
12+
if n == nil {
13+
return nil
14+
}
15+
for ; n.Parent != nil; n = n.Parent {
16+
}
17+
return n
18+
}
19+
1020
func findNode(root *Node, name string) *Node {
1121
node := root.FirstChild
1222
for {
@@ -107,6 +117,36 @@ func verifyNodePointers(t *testing.T, n *Node) {
107117
testTrue(t, parent == nil || parent.LastChild == cur)
108118
}
109119

120+
func TestAddAttr(t *testing.T) {
121+
for _, test := range []struct {
122+
name string
123+
n *Node
124+
key string
125+
val string
126+
expected string
127+
}{
128+
{
129+
name: "node has no existing attr",
130+
n: &Node{Type: AttributeNode},
131+
key: "ns:k1",
132+
val: "v1",
133+
expected: `< ns:k1="v1"></>`,
134+
},
135+
{
136+
name: "node has existing attrs",
137+
n: &Node{Type: AttributeNode, Attr: []xml.Attr{{Name: xml.Name{Local: "k1"}, Value: "v1"}}},
138+
key: "k2",
139+
val: "v2",
140+
expected: `< k1="v1" k2="v2"></>`,
141+
},
142+
} {
143+
t.Run(test.name, func(t *testing.T) {
144+
AddAttr(test.n, test.key, test.val)
145+
testValue(t, test.n.OutputXML(true), test.expected)
146+
})
147+
}
148+
}
149+
110150
func TestRemoveFromTree(t *testing.T) {
111151
xml := `<?procinst?>
112152
<!--comment-->
@@ -123,7 +163,7 @@ func TestRemoveFromTree(t *testing.T) {
123163
doc := parseXML()
124164
n := FindOne(doc, "//aaa/ddd/eee")
125165
testTrue(t, n != nil)
126-
removeFromTree(n)
166+
RemoveFromTree(n)
127167
verifyNodePointers(t, doc)
128168
testValue(t, doc.OutputXML(false),
129169
`<?procinst?><!--comment--><aaa><bbb></bbb><ddd></ddd><ggg></ggg></aaa>`)
@@ -133,7 +173,7 @@ func TestRemoveFromTree(t *testing.T) {
133173
doc := parseXML()
134174
n := FindOne(doc, "//aaa/bbb")
135175
testTrue(t, n != nil)
136-
removeFromTree(n)
176+
RemoveFromTree(n)
137177
verifyNodePointers(t, doc)
138178
testValue(t, doc.OutputXML(false),
139179
`<?procinst?><!--comment--><aaa><ddd><eee><fff></fff></eee></ddd><ggg></ggg></aaa>`)
@@ -143,7 +183,7 @@ func TestRemoveFromTree(t *testing.T) {
143183
doc := parseXML()
144184
n := FindOne(doc, "//aaa/ddd")
145185
testTrue(t, n != nil)
146-
removeFromTree(n)
186+
RemoveFromTree(n)
147187
verifyNodePointers(t, doc)
148188
testValue(t, doc.OutputXML(false),
149189
`<?procinst?><!--comment--><aaa><bbb></bbb><ggg></ggg></aaa>`)
@@ -153,7 +193,7 @@ func TestRemoveFromTree(t *testing.T) {
153193
doc := parseXML()
154194
n := FindOne(doc, "//aaa/ggg")
155195
testTrue(t, n != nil)
156-
removeFromTree(n)
196+
RemoveFromTree(n)
157197
verifyNodePointers(t, doc)
158198
testValue(t, doc.OutputXML(false),
159199
`<?procinst?><!--comment--><aaa><bbb></bbb><ddd><eee><fff></fff></eee></ddd></aaa>`)
@@ -163,7 +203,7 @@ func TestRemoveFromTree(t *testing.T) {
163203
doc := parseXML()
164204
procInst := doc.FirstChild
165205
testValue(t, procInst.Type, DeclarationNode)
166-
removeFromTree(procInst)
206+
RemoveFromTree(procInst)
167207
verifyNodePointers(t, doc)
168208
testValue(t, doc.OutputXML(false),
169209
`<!--comment--><aaa><bbb></bbb><ddd><eee><fff></fff></eee></ddd><ggg></ggg></aaa>`)
@@ -173,15 +213,15 @@ func TestRemoveFromTree(t *testing.T) {
173213
doc := parseXML()
174214
commentNode := doc.FirstChild.NextSibling.NextSibling // First .NextSibling is an empty text node.
175215
testValue(t, commentNode.Type, CommentNode)
176-
removeFromTree(commentNode)
216+
RemoveFromTree(commentNode)
177217
verifyNodePointers(t, doc)
178218
testValue(t, doc.OutputXML(false),
179219
`<?procinst?><aaa><bbb></bbb><ddd><eee><fff></fff></eee></ddd><ggg></ggg></aaa>`)
180220
})
181221

182222
t.Run("remove call on root does nothing", func(t *testing.T) {
183223
doc := parseXML()
184-
removeFromTree(doc)
224+
RemoveFromTree(doc)
185225
verifyNodePointers(t, doc)
186226
testValue(t, doc.OutputXML(false),
187227
`<?procinst?><!--comment--><aaa><bbb></bbb><ddd><eee><fff></fff></eee></ddd><ggg></ggg></aaa>`)

parse.go

+14-14
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (p *parser) parse() (*Node, error) {
7676
if p.level == 0 {
7777
// mising XML declaration
7878
node := &Node{Type: DeclarationNode, Data: "xml", level: 1}
79-
addChild(p.prev, node)
79+
AddChild(p.prev, node)
8080
p.level = 1
8181
p.prev = node
8282
}
@@ -112,14 +112,14 @@ func (p *parser) parse() (*Node, error) {
112112
}
113113
//fmt.Println(fmt.Sprintf("start > %s : %d", node.Data, node.level))
114114
if p.level == p.prev.level {
115-
addSibling(p.prev, node)
115+
AddSibling(p.prev, node)
116116
} else if p.level > p.prev.level {
117-
addChild(p.prev, node)
117+
AddChild(p.prev, node)
118118
} else if p.level < p.prev.level {
119119
for i := p.prev.level - p.level; i > 1; i-- {
120120
p.prev = p.prev.Parent
121121
}
122-
addSibling(p.prev.Parent, node)
122+
AddSibling(p.prev.Parent, node)
123123
}
124124
// If we're in the streaming mode, we need to remember the node if it is the target node
125125
// so that when we finish processing the node's EndElement, we know how/what to return to
@@ -172,26 +172,26 @@ func (p *parser) parse() (*Node, error) {
172172
case xml.CharData:
173173
node := &Node{Type: CharDataNode, Data: string(tok), level: p.level}
174174
if p.level == p.prev.level {
175-
addSibling(p.prev, node)
175+
AddSibling(p.prev, node)
176176
} else if p.level > p.prev.level {
177-
addChild(p.prev, node)
177+
AddChild(p.prev, node)
178178
} else if p.level < p.prev.level {
179179
for i := p.prev.level - p.level; i > 1; i-- {
180180
p.prev = p.prev.Parent
181181
}
182-
addSibling(p.prev.Parent, node)
182+
AddSibling(p.prev.Parent, node)
183183
}
184184
case xml.Comment:
185185
node := &Node{Type: CommentNode, Data: string(tok), level: p.level}
186186
if p.level == p.prev.level {
187-
addSibling(p.prev, node)
187+
AddSibling(p.prev, node)
188188
} else if p.level > p.prev.level {
189-
addChild(p.prev, node)
189+
AddChild(p.prev, node)
190190
} else if p.level < p.prev.level {
191191
for i := p.prev.level - p.level; i > 1; i-- {
192192
p.prev = p.prev.Parent
193193
}
194-
addSibling(p.prev.Parent, node)
194+
AddSibling(p.prev.Parent, node)
195195
}
196196
case xml.ProcInst: // Processing Instruction
197197
if p.prev.Type != DeclarationNode {
@@ -202,13 +202,13 @@ func (p *parser) parse() (*Node, error) {
202202
for _, pair := range pairs {
203203
pair = strings.TrimSpace(pair)
204204
if i := strings.Index(pair, "="); i > 0 {
205-
addAttr(node, pair[:i], strings.Trim(pair[i+1:], `"`))
205+
AddAttr(node, pair[:i], strings.Trim(pair[i+1:], `"`))
206206
}
207207
}
208208
if p.level == p.prev.level {
209-
addSibling(p.prev, node)
209+
AddSibling(p.prev, node)
210210
} else if p.level > p.prev.level {
211-
addChild(p.prev, node)
211+
AddChild(p.prev, node)
212212
}
213213
p.prev = node
214214
case xml.Directive:
@@ -293,7 +293,7 @@ func (sp *StreamParser) Read() (*Node, error) {
293293
// Because this is a streaming read, we need to release/remove last
294294
// target node from the node tree to free up memory.
295295
if sp.p.streamNode != nil {
296-
removeFromTree(sp.p.streamNode)
296+
RemoveFromTree(sp.p.streamNode)
297297
sp.p.prev = sp.p.streamNodePrev
298298
sp.p.streamNode = nil
299299
sp.p.streamNodePrev = nil

parse_test.go

+7-16
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,6 @@ func TestStreamParser_InvalidXPath(t *testing.T) {
270270
}
271271
}
272272

273-
func root(n *Node) *Node {
274-
if n == nil {
275-
return nil
276-
}
277-
for ; n.Parent != nil; n = n.Parent {
278-
}
279-
return n
280-
}
281-
282273
func testOutputXML(t *testing.T, msg string, expectedXML string, n *Node) {
283274
if n.OutputXML(true) != expectedXML {
284275
t.Fatalf("%s, expected XML: '%s', actual: '%s'", msg, expectedXML, n.OutputXML(true))
@@ -309,7 +300,7 @@ func TestStreamParser_Success1(t *testing.T) {
309300
t.Fatal(err.Error())
310301
}
311302
testOutputXML(t, "first call result", `<BBB>b1</BBB>`, n)
312-
testOutputXML(t, "doc after first call", `<><?xml?><AAA><CCC>c1</CCC><BBB>b1</BBB></AAA></>`, root(n))
303+
testOutputXML(t, "doc after first call", `<><?xml?><AAA><CCC>c1</CCC><BBB>b1</BBB></AAA></>`, findRoot(n))
313304

314305
// Second `<BBB>` read
315306
n, err = sp.Read()
@@ -318,7 +309,7 @@ func TestStreamParser_Success1(t *testing.T) {
318309
}
319310
testOutputXML(t, "second call result", `<BBB>b2<ZZZ z="1">z1</ZZZ></BBB>`, n)
320311
testOutputXML(t, "doc after second call",
321-
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b2<ZZZ z="1">z1</ZZZ></BBB></AAA></>`, root(n))
312+
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b2<ZZZ z="1">z1</ZZZ></BBB></AAA></>`, findRoot(n))
322313

323314
// Third `<BBB>` read (Note we will skip 'b3' since the streamElementFilter excludes it)
324315
n, err = sp.Read()
@@ -330,7 +321,7 @@ func TestStreamParser_Success1(t *testing.T) {
330321
// been filtered out and is not our target node, thus it is considered just like any other
331322
// non target nodes such as `<CCC>`` or `<DDD>`
332323
testOutputXML(t, "doc after third call",
333-
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b3</BBB><BBB>b4</BBB></AAA></>`, root(n))
324+
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b3</BBB><BBB>b4</BBB></AAA></>`, findRoot(n))
334325

335326
// Fourth `<BBB>` read
336327
n, err = sp.Read()
@@ -340,7 +331,7 @@ func TestStreamParser_Success1(t *testing.T) {
340331
testOutputXML(t, "fourth call result", `<BBB>b5</BBB>`, n)
341332
// Note the inclusion of `<BBB>b3</BBB>` in the document.
342333
testOutputXML(t, "doc after fourth call",
343-
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b3</BBB><BBB>b5</BBB></AAA></>`, root(n))
334+
`<><?xml?><AAA><CCC>c1</CCC><DDD>d1</DDD><BBB>b3</BBB><BBB>b5</BBB></AAA></>`, findRoot(n))
344335

345336
_, err = sp.Read()
346337
if err != io.EOF {
@@ -369,7 +360,7 @@ func TestStreamParser_Success2(t *testing.T) {
369360
t.Fatal(err.Error())
370361
}
371362
testOutputXML(t, "first call result", `<CCC>c1</CCC>`, n)
372-
testOutputXML(t, "doc after first call", `<><?xml?><AAA><CCC>c1</CCC></AAA></>`, root(n))
363+
testOutputXML(t, "doc after first call", `<><?xml?><AAA><CCC>c1</CCC></AAA></>`, findRoot(n))
373364

374365
// Second Read() should return d1
375366
n, err = sp.Read()
@@ -378,7 +369,7 @@ func TestStreamParser_Success2(t *testing.T) {
378369
}
379370
testOutputXML(t, "second call result", `<DDD>d1</DDD>`, n)
380371
testOutputXML(t, "doc after second call",
381-
`<><?xml?><AAA><BBB>b1</BBB><DDD>d1</DDD></AAA></>`, root(n))
372+
`<><?xml?><AAA><BBB>b1</BBB><DDD>d1</DDD></AAA></>`, findRoot(n))
382373

383374
// Third call should return c2
384375
n, err = sp.Read()
@@ -387,7 +378,7 @@ func TestStreamParser_Success2(t *testing.T) {
387378
}
388379
testOutputXML(t, "third call result", `<CCC>c2</CCC>`, n)
389380
testOutputXML(t, "doc after third call",
390-
`<><?xml?><AAA><BBB>b1</BBB><BBB>b2</BBB><CCC>c2</CCC></AAA></>`, root(n))
381+
`<><?xml?><AAA><BBB>b1</BBB><BBB>b2</BBB><CCC>c2</CCC></AAA></>`, findRoot(n))
391382

392383
_, err = sp.Read()
393384
if err != io.EOF {

0 commit comments

Comments
 (0)