Repository navigation
Expand file tree
/
Copy pathnode_leaf.go
More file actions
374 lines (328 loc) · 12.6 KB
/
Copy pathnode_leaf.go
File metadata and controls
374 lines (328 loc) · 12.6 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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package helium
import (
"bytes"
"fmt"
)
// CDATASection represents a CDATA section node in the DOM tree
// (libxml2: xmlNode with type XML_CDATA_SECTION_NODE).
type CDATASection struct {
node
}
func newCDATASection(b []byte) *CDATASection {
c := CDATASection{}
c.etype = CDATASectionNode
c.content = bytes.Clone(b)
c.name = "(CDATA)"
return &c
}
func (n *CDATASection) AddChild(cur Node) error {
// Reject a nil or typed-nil operand BEFORE the operand-type reference below so
// the call returns ErrNilNode instead of panicking and leaves the tree
// untouched.
if isNilNode(cur) {
return ErrNilNode
}
// A CDATA section carries character data, not child nodes, so no operand kind
// is a valid child. Reject with the same %w-wrapped ErrInvalidOperation shape
// the shared addChild uses for an invalid parent, so callers can errors.Is it
// like every other AddChild rejection.
return fmt.Errorf("%w: cannot add a %s as a child of a %s node", ErrInvalidOperation, cur.Type(), n.Type())
}
func (n *CDATASection) AppendText(b []byte) error {
n.content = append(n.content, b...)
return nil
}
func (n *CDATASection) AddSibling(cur Node) error {
return addSibling(n, cur)
}
// Content returns a defensive copy of the CDATA section's content. Mutating the
// returned slice does NOT affect the node; re-reading returns the original bytes.
func (n CDATASection) Content() []byte {
return bytes.Clone(n.content)
}
// rawContent returns the internal content slice without copying for read-only
// internal hot paths. See the package-level rawContent for the contract.
func (n CDATASection) rawContent() []byte {
return n.content
}
func (n *CDATASection) Replace(nodes ...Node) error {
return replaceNode(n, nodes...)
}
func (n *CDATASection) SetTreeDoc(doc *Document) {
setTreeDoc(n, doc)
}
// Comment represents an XML comment node (libxml2: xmlNode with type XML_COMMENT_NODE).
type Comment struct {
node
}
func newComment(b []byte) *Comment {
t := Comment{}
t.etype = CommentNode
t.content = bytes.Clone(b)
t.name = "(comment)"
return &t
}
func (n *Comment) AddChild(cur Node) error {
// Reject a nil or typed-nil operand BEFORE the type assertion / comment-merge
// fast path so the call returns ErrNilNode instead of panicking and leaves
// the tree untouched.
if isNilNode(cur) {
return ErrNilNode
}
// Run the shared self/cycle guard and auto-unlink BEFORE the comment-merge
// fast path. Otherwise comment.AddChild(comment) would double its own
// content, and merging an already-linked comment node would copy its
// content while leaving it linked under its old parent.
if t, ok := cur.(*Comment); ok {
if err := addChildPreflight(n, cur); err != nil {
return err
}
return n.AppendText(t.content)
}
// A comment carries character data, not child nodes; only another comment
// merges (handled above). Reject any other operand with the same %w-wrapped
// ErrInvalidOperation shape the shared addChild uses for an invalid parent, so
// callers can errors.Is it like every other AddChild rejection.
return fmt.Errorf("%w: cannot add a %s as a child of a %s node", ErrInvalidOperation, cur.Type(), n.Type())
}
func (n *Comment) AppendText(b []byte) error {
n.content = append(n.content, b...)
return nil
}
// AddSibling adds a new sibling to the end of the sibling nodes.
func (n *Comment) AddSibling(cur Node) error {
return addSibling(n, cur)
}
// Content returns a defensive copy of the comment's content. Mutating the
// returned slice does NOT affect the node; re-reading returns the original bytes.
func (n Comment) Content() []byte {
return bytes.Clone(n.content)
}
// rawContent returns the internal content slice without copying for read-only
// internal hot paths. See the package-level rawContent for the contract.
func (n Comment) rawContent() []byte {
return n.content
}
func (n *Comment) Replace(nodes ...Node) error {
return replaceNode(n, nodes...)
}
func (n *Comment) SetTreeDoc(doc *Document) {
setTreeDoc(n, doc)
}
// ProcessingInstruction represents an XML processing instruction node
// (libxml2: xmlNode with type XML_PI_NODE).
type ProcessingInstruction struct {
docnode
target string
data string
}
func (p *ProcessingInstruction) Name() string {
return p.target
}
func (p *ProcessingInstruction) Content() []byte {
return []byte(p.data)
}
func (p *ProcessingInstruction) Type() ElementType {
return ProcessingInstructionNode
}
// AddChild on a PI does not attach child nodes. A processing instruction
// carries its content as a string (the "data" portion), not as element/text
// children. This mirrors libxml2, where an xmlNode of type XML_PI_NODE stores
// its content in the node's content string and has no element/text children.
//
// A Text/CDATA child has its content appended to the PI data (mirroring
// xmlNodeAddContent on a PI). Any other node type is rejected, since attaching
// it would corrupt the tree and break serialization.
func (p *ProcessingInstruction) AddChild(cur Node) error {
// Reject a nil or typed-nil operand BEFORE any method call on cur (a typed
// nil is a non-nil interface wrapping a nil pointer, so cur.Type() would
// panic) so the call returns ErrNilNode and leaves the tree untouched.
if isNilNode(cur) {
return ErrNilNode
}
// An Attribute has no valid placement on a PI (attributes belong on an
// element). Reject it BEFORE the type switch with the same %w-wrapped
// ErrInvalidOperation shape the shared addChild uses for a non-element
// parent, so callers can errors.Is it like every other AddChild rejection.
if _, ok := cur.(*Attribute); ok {
return fmt.Errorf("%w: cannot add an attribute as a child of a %s node; attributes belong on an element", ErrInvalidOperation, p.Type())
}
switch cur.Type() {
case TextNode, CDATASectionNode:
// Run the shared self/cycle guard and auto-unlink BEFORE merging the
// source content into the PI data. Otherwise merging an already-linked
// text/CDATA node would copy its content while leaving it linked under
// its old parent, violating the AddChild auto-unlink contract.
if err := addChildPreflight(p, cur); err != nil {
return err
}
p.data += string(cur.Content())
return nil
default:
// A self-add (pi.AddChild(pi)) reaches here because a PI is not a text
// node; detect it by direct pointer identity so it matches every other
// leaf self-add (ErrCyclicNode), stopping short of the generic type
// rejection below. The check must be identity, NOT wouldCreateCycle: that guard
// walks the PI's ancestor chain, so it would also match an ancestor
// operand (pi.AddChild(parentElement)), which — like on the other
// strict leaves — is just another invalid operand and takes the shared
// ErrInvalidOperation shape below. The comparison is against p itself,
// not p.baseDocNode(), so a typed-nil receiver
// (var pi *ProcessingInstruction; pi.AddChild(elem)) never dereferences
// p and falls through to the error return below instead of panicking.
if cur == Node(p) {
return fmt.Errorf("%w: cannot add a node as a child of itself or one of its descendants", ErrCyclicNode)
}
// Any other node type has no valid placement on a PI. Reject with the same
// %w-wrapped ErrInvalidOperation shape the shared addChild uses for an
// invalid parent, so callers can errors.Is it like every other AddChild
// rejection.
return fmt.Errorf("%w: cannot add a %s as a child of a %s node", ErrInvalidOperation, cur.Type(), p.Type())
}
}
// AppendText appends text to the PI's data string, creating no child
// text node. See AddChild for rationale.
func (p *ProcessingInstruction) AppendText(b []byte) error {
p.data += string(b)
return nil
}
func (p *ProcessingInstruction) AddSibling(cur Node) error {
return addSibling(p, cur)
}
func (p *ProcessingInstruction) Replace(nodes ...Node) error {
return replaceNode(p, nodes...)
}
func (p *ProcessingInstruction) SetTreeDoc(doc *Document) {
setTreeDoc(p, doc)
}
// EntityRef represents an entity reference node in the DOM tree
// (libxml2: xmlNode with type XML_ENTITY_REF_NODE).
type EntityRef struct {
node
}
func newEntityRef() *EntityRef {
n := &EntityRef{}
n.etype = EntityRefNode
return n
}
// Content returns the referenced entity's value with every nested reference
// expanded (libxml2: xmlNodeGetContent on an entity reference): the Text and
// CDATA text of the entity's parsed children, leaving out any comment or PI.
// See appendEntityRefContent for how the entity is found and expanded. An
// empty result is nil.
func (e *EntityRef) Content() []byte {
var b bytes.Buffer
appendEntityRefContent(&b, e, false)
if b.Len() == 0 {
return nil
}
return b.Bytes()
}
func (e *EntityRef) AddChild(cur Node) error {
return addChild(e, cur)
}
func (e *EntityRef) AppendText(b []byte) error {
return appendText(e, b)
}
func (e *EntityRef) AddSibling(cur Node) error {
return addSibling(e, cur)
}
func (e *EntityRef) Replace(nodes ...Node) error {
return replaceNode(e, nodes...)
}
func (n *EntityRef) SetTreeDoc(doc *Document) {
setTreeDoc(n, doc)
}
// Text represents an XML text node (libxml2: xmlNode with type XML_TEXT_NODE).
type Text struct {
node
// fromCharRef records that some of this node's character data originated
// from a character reference (&#N;/&#xN;) — directly, or via re-parse of a
// general entity whose replacement text is a character reference. It is used
// ONLY by element-content validity (VC: Element Valid, XML §3.2.1 as clarified
// by errata 2e E15): whitespace produced by a character reference does NOT
// match the S nonterminal, so it is not ignorable in element-only content,
// unlike literal source whitespace or whitespace from an internal entity whose
// replacement text is itself literal whitespace. It is invisible to
// serialization, C14N, XPath string-value, and node copy — none read it — so
// it changes no output, only the validation verdict.
fromCharRef bool
}
const textNodeName = "(text)"
func newText(b []byte) *Text {
t := Text{}
t.etype = TextNode
t.content = make([]byte, len(b))
copy(t.content, b)
t.name = textNodeName
return &t
}
func (n *Text) AddChild(cur Node) error {
// Reject a nil or typed-nil operand BEFORE the type assertion / text-merge
// fast path so the call returns ErrNilNode instead of panicking and leaves
// the tree untouched.
if isNilNode(cur) {
return ErrNilNode
}
// Run the shared self/cycle guard and auto-unlink BEFORE the text-merge
// fast path. Otherwise txt.AddChild(txt) would double its own content, and
// merging an already-linked text node would copy its content while leaving
// it linked under its old parent.
if t, ok := cur.(*Text); ok {
if err := addChildPreflight(n, cur); err != nil {
return err
}
n.fromCharRef = n.fromCharRef || t.fromCharRef
return n.AppendText(t.content)
}
// A text node carries character data, not child nodes; only another text node
// merges (handled above). Reject any other operand with the same %w-wrapped
// ErrInvalidOperation shape the shared addChild uses for an invalid parent, so
// callers can errors.Is it like every other AddChild rejection.
return fmt.Errorf("%w: cannot add a %s as a child of a %s node", ErrInvalidOperation, cur.Type(), n.Type())
}
func (n *Text) AppendText(b []byte) error {
if doc := n.doc; doc != nil {
n.content = doc.growOwnedTextContent(n.content, len(b))
}
n.content = append(n.content, b...)
return nil
}
func (n *Text) AddSibling(cur Node) error {
// Reject a nil or typed-nil operand BEFORE the debug log (which dereferences
// cur) and the text-merge fast path so the call returns ErrNilNode instead of
// panicking and leaves the tree untouched.
if isNilNode(cur) {
return ErrNilNode
}
// Run the shared self/cycle guard and auto-unlink BEFORE the text-merge
// fast path. Otherwise t.AddSibling(t) would double its own content, and
// merging an already-linked text node would copy its content while leaving
// it linked under its old parent.
if err := addSiblingPreflight(n, cur); err != nil {
return err
}
if cur.Type() == TextNode {
if t, ok := cur.(*Text); ok {
n.fromCharRef = n.fromCharRef || t.fromCharRef
}
return n.AppendText(cur.Content())
}
return addSibling(n, cur)
}
// Content returns a defensive copy of the text node's content. Mutating the
// returned slice does NOT affect the node; re-reading returns the original bytes.
func (n Text) Content() []byte {
return bytes.Clone(n.content)
}
// rawContent returns the internal content slice without copying for read-only
// internal hot paths. See the package-level rawContent for the contract.
func (n Text) rawContent() []byte {
return n.content
}
func (n *Text) Replace(nodes ...Node) error {
return replaceNode(n, nodes...)
}
func (n *Text) SetTreeDoc(doc *Document) {
setTreeDoc(n, doc)
}