forked from skn3/xml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxml.monkey
2520 lines (2054 loc) · 62.1 KB
/
xml.monkey
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#rem
MIT License.
http://opensource.org/licenses/MIT
Copyright (c) 2013 SKN3
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
#end
'XML for monkey - Written by skn3 (Jon Pittock)
'A single file xml lib for monkey that supports comments,cdata,self-closing tags,relaxed line breaks,unquoted attributes,attributes without assignment,single + double quotes,
'whitespace trimming.
'The only string manipulation performed is for tag value data (anything outside of a tag) that gets chopped off by a child tag. All other string manipulation is buffered in an
'int array so you will not have a ton of newly created temporary strings. Most text parsing has been designed to compare asc integer values instead of strings.
'Each node will have its document line,column and offset values added to it for each debugging. Error messages will also report correct document details.
'The lib was written from scratch with no reference.
'version 26
' - fixed typo in missing variable
'version 25
' - added so newlines in XML will be included in xml text/values
' - added so XML_STRIP_NEWLINE can now be used in ParseXML to strip any newlines within text/value
'version 24
' - fixed doctype bug (thanks difference, sorry the delay ;)
' - removed left in print satement (thanks difference, sorry the delay ;)
' - added support for text/whitespace characters (the value of a node) to be split into child nodes. This should be transparently working and you can still use node.value
' - added node.text bool to indicate if the node is a text node
' - added text boolean flag to many of the methods. This allows text nodes to be scanned/returned. The text boolean defaults to false, which will ignore text nodes. For example GetChild(true) would return the first child node, GetChild(false) would find the first NON-text node.
' - added AddText() method this will either add a child node or append to teh nodes value (depending on the parse mode used)
' - added example7.monkey demonstrating text nodes
'version 23
' - added tweak/fix to parser to ignore doctype tag (later on can add support) (cheers copper circle)
' - added tweak/fix to parser to allow : in tag/attribute names (later can add support for contexts) (cheers copper circle)
'version 22
' - fixed self closing tags where no space is provided e.g. <tag/> - thanks AdamRedwoods and ComputerCoder
' - moved into jungle solution
' - added test example 5 for testing self closing tags without space
'version 21
' - added value param to AddChild() method to quicker to create children
'version 20
' - renamed internal function HasStringAtOffset to XMLHasStringAtOffset (monkey was showing conflict when same function name used elsewhere even though its private in xml?)
' - fixed typo (thanks computercoder)
'version 19
' - added GetChildren() override to get ALL children (thanks computercoder)
' - added fixes to Export method, thanks computercoder
' - added GetDescendants) override to get all
' - added result param to all GetChildren/GetDescendants methods. This lets you pass in the list that results will be populated in
'version 18
' - added GetAttributeOrAttribute() to XMLNode. This allows you to get attribute by id. If that attribute doesnt exist it looks for second id. If neither exist, default value is returned.
' - fixed null node returns in Get Previous/Next sibling methods
'version 17
' - added simple test for no xml data passed in
'version 16
' - added a new example3 to demonstrate how you would load xml generated by the MakeAtlas tool
'version 15
' - added tweak to stringbuffer size to improve performance/crashes on larger documents, thanks AdamRedwoods
' - made node freeing more effecient by keeping track of list node
'version 14
' - fixed crash bug in creating node with attributes query string, thanks Rushino
' - added XML_STRIP_NEWLINE export flag which will add line feeds to exported data
' - added XML_STRIP_CLOSING_TAGS export flag so that xml nodes with no children would get exported as <tag /> instead of <tag></tag>
'version 13
' - fixed small bug where calling GetChild() on manually created doc was causing null reference. Thanks Difference.
'version 12
' - added CountChildren() to the node class. Allows for counting with tag and also attribute matching
'version 11
' - added support for '-' character in tags and attributes (thanks midimaster)
'version 10
' - renamed the attribute query and stringbuffer classes
'version 9
' - added HasChildren() method to node
'version 8
' - fixed bool to string bug in SetAttribute
' - fixed GetNextSibling and GetPreviousSibling to return nullnode instead of null (make sure to check return object for valid instead of null .. eg while node.valid instead of while node)
'version 7
' - added GetChild() (with no name or attributes) this will allow to get first child of a node
' - added SetAttribute() and GetAttribute() overloads for bool,int,float, string and no value so don't have to do value conversion in user code!
'version 6
' - fix by David (DGuy) to fix unreocgnised tag end when case was different. Also added licenses.
'version 5
' - added GetNextSibling() and GetPreviousSibling() for searching for siblings adjacent that match tag-name and/or attributes.
'version 4
' - changed readonly to valid to make more sense. Can now check for valid nodes like so If doc.GetChild("").valid
'version 3
' - speed improvement repalced string.Find with XMLHasStringAtOffset() for searching for tag starts
'version 2
' - changed Find___ functions to Get___
' - added GetDescendants() for getting all descendants of node
' - add path lookup ability see GetChildAtPath() and GetChildrenAtPath()
' - added default null return nodes so function chaining wont crash app
' - made it so node can be valid, used for the default null node
' - added GetParent() for safe traversal of teh node strucutre
' - added special @value into query string to look for a nodes value
'version 1
' - first release
Strict
Import monkey.list
Import monkey.map
Const XML_STRIP_WHITESPACE:= 1'this will remove whitespace characters from NON attribute data
Const XML_STRIP_NEWLINE:= 2'this will remove new line characters from NON attribute data
Const XML_STRIP_CLOSING_TAGS:= 4'this will remove closing tags when node has no children
Private
Const XML_FORMAT_OPEN:= "<?xml"
Const XML_FORMAT_CLOSE:= "?>"
Const COMMENT_OPEN:= "<!--"
Const COMMENT_CLOSE:= "-->"
Const CDATA_OPEN:= "<![CDATA["
Const CDATA_CLOSE:= "]]>"
Const DOCTYPE_OPEN:= "<!DOCTYPE"
Const DOCTYPE_CLOSE:= ">"
Class XMLStringBuffer
Field data:int[]
Field chunk:Int = 128
Field count:Int
Field dirty:Int = False
Field cache:String
'constructor/destructor
Method New(chunk:Int = 128)
Self.chunk = chunk
End
'properties
Method value:String() Property
' --- get the property ---
'rebuild cache
If dirty
dirty = False
If count = 0
cache = ""
Else
cache = String.FromChars(data[0 .. count])
EndIf
EndIf
'return cache
Return cache
End
'api
Method Add:Void(asc:Int)
' --- add single asc to buffer ---
'resize
If count = data.Length data = data.Resize(data.Length + chunk)
'fill data
data[count] = asc
'move pointer
count += 1
'flag dirty
dirty = True
End
Method Add:Void(text:String)
' --- add text to buffer ---
If text.Length = 0 Return
'resize
If count + text.Length >= data.Length data = data.Resize(data.Length + (chunk * Ceil(Float(text.Length) / chunk)))
'fill data
For Local textIndex:= 0 Until text.Length
data[count] = text[textIndex]
'move pointer
count += 1
Next
'flag dirty
dirty = True
End
Method Add:Void(text:String, offset:Int, suggestedLength:Int = 0)
' --- add text clipping to buffer ---
'figure out real length of the import
Local realLength:= text.Length - offset
If suggestedLength > 0 And suggestedLength < realLength realLength = suggestedLength
'skip
If realLength = 0 Return
'resize
If count + realLength >= data.Length data = data.Resize(data.Length + (chunk * Ceil(Float(realLength) / chunk)))
'fill data
For Local textIndex:= offset Until offset + realLength
data[count] = text[textIndex]
'move pointer
count += 1
Next
'flag dirty
dirty = True
End
Method Clear:Void()
' --- clear the buffer ---
count = 0
cache = ""
dirty = False
End
Method Shrink:Void()
' --- shrink the data ---
Local newSize:Int
'get new size
If count = 0
newSize = chunk
Else
newSize = Ceil(float(count) / chunk)
EndIf
'only bother resizing if its changed
If newSize <> data.Length data = data.Resize(newSize)
End
Method Trim:Bool()
' --- this will trim whitespace from the start and end ---
'skip
If count = 0 Return False
'quick trim
If (count = 1 and (data[0] = 32 or data[0] = 9 or data[0] = 10)) or (count = 2 And (data[0] = 32 or data[0] = 9 or data[0] = 10) And (data[1] = 32 or data[1] = 9 or data[1] = 10))
Clear()
Return True
EndIf
'full trim
'get start trim
Local startIndex:Int
For startIndex = 0 Until count
If data[startIndex] <> 32 And data[startIndex] <> 9 And data[startIndex] <> 10 Exit
Next
'check if there was only whitespace
If startIndex = count
Clear()
Return True
EndIf
'get end trim
Local endIndex:Int
For endIndex = count - 1 To 0 Step - 1
If data[endIndex] <> 32 And data[endIndex] <> 9 and data[endIndex] <> 10 Exit
Next
'check for no trim
If startIndex = 0 And endIndex = count - 1 Return False
'we have to trim so set new length (count)
count = endIndex - startIndex + 1
'do we need to shift data left?
If startIndex > 0
For Local trimIndex:= 0 Until count
data[trimIndex] = data[trimIndex + startIndex]
Next
EndIf
'return that we trimmed
Return True
End
Method Length:Int() Property
' --- return length ---
Return count
End
Method Last:Int(defaultValue:Int = -1)
' --- return the last asc ---
'skip
If count = 0 Return defaultValue
'return
Return data[count - 1]
End
End
Class XMLAttributeQuery
Field chunk:Int = 32
Field items:XMLAttributeQueryItem[]
Field count:Int
'constructor/destructor
Method New(query:String)
' --- this will create a new query object ---
'query is in the format of 'title1=value1&title2=value2'
'the = and & character can be escaped with a \ character
'a value pair can also be shortcut like 'value1&value2&value3'
Local queryIndex:Int
Local queryAsc:Int
Local buffer:= New XMLStringBuffer(256)
Local isEscaped:= False
Local processBuffer:= False
Local processItem:= False
Local hasId:= False
Local hasValue:= False
Local hasEquals:= False
Local hasSepcial:= False
Local itemId:String
Local itemValue:String
For queryIndex = 0 Until query.Length
'looking for title
queryAsc = query[queryIndex]
If isEscaped
'escaped character
isEscaped = False
buffer.Add(queryAsc)
Else
'test character
Select queryAsc
Case 38'&
processBuffer = True
processItem = True
Case 61'=
processBuffer = True
hasEquals = True
Case 64'@
If hasId = False
'switch on special value
If buffer.Length = 0 hasSepcial = True
Else
'value so just add it
buffer.Add(queryAsc)
EndIf
Case 92'\
isEscaped = True
Default
'skip character if we are building id and there is not valid alphanumeric
If hasId or (queryAsc = 45 or queryAsc = 95 or (queryAsc >= 48 and queryAsc <= 57) or (queryAsc >= 65 and queryAsc <= 90) or (queryAsc >= 97 and queryAsc <= 122)) buffer.Add(queryAsc)
End
EndIf
'check for end condition
If queryIndex = query.Length - 1
processBuffer = True
processItem = True
'add escape character if it was left over
If isEscaped And hasId buffer.Add(92)
'check for blank =
If hasEquals And buffer.Length = 0 hasValue = True
EndIf
'process the buffer
If processBuffer
'unflag process
processBuffer = False
'check condition
If hasId = False
itemId = buffer.value
buffer.Clear()
hasId = itemId.Length > 0
Else
itemValue = buffer.value
buffer.Clear()
hasValue = True
EndIf
EndIf
'process the item
If processItem
'unflag process
processItem = False
'check condition
If hasId
'insert new value
'resize
If count = items.Length items = items.Resize(items.Length + chunk)
'create new item
items[count] = New XMLAttributeQueryItem(itemId, itemValue, hasValue, hasSepcial)
'increase count
count += 1
'reset
itemId = ""
itemValue = ""
hasId = False
hasValue = False
hasSepcial = False
EndIf
EndIf
Next
End
'api
Method Test:Bool(node:XMLNode)
' --- this will test the given node against the query ---
Local attribute:XMLAttribute
For Local index:= 0 Until count
If items[index].special = False
'attribute comparison
'get attribute
attribute = node.GetXMLAttribute(items[index].id)
'check conditions for fail
If attribute = Null or (items[index].required And attribute.value <> items[index].value) Return False
Else
'special query
Select items[index].id
Case "value"
'check conditions for fail
If (items[index].required And node.value <> items[index].value) Return False
End
EndIf
Next
'success
Return True
End
Method Length:Int()
Return count
End
End
Class XMLAttributeQueryItem
Field id:String
Field value:String
Field required:Bool
Field special:Bool
'constructor/destructor
Method New(id:String, value:String, required:Bool, special:Bool)
Self.id = id
Self.value = value
Self.required = required
Self.special = special
End
End
Function XMLHasStringAtOffset:Bool(needle:String, haystack:String, offset:Int)
' --- quick function for testing a string at given offset ---
'skip
If offset + needle.Length > haystack.Length Return False
'scan characters
For Local index:= 0 Until needle.Length
If needle[index] <> haystack[offset + index] Return False
Next
'return success
Return True
End
Function XMLFindNextAsc:Int(data:String, asc:Int, offset:Int = 0)
' --- find next asc ---
Local length:= data.Length
'skip
If offset >= length Return - 1
'fix negative
If offset < 0 offset = 0
'scan
For offset = offset Until length
If data[offset] = asc Return offset
Next
'nope
Return -1
End
Function XMLFindStringNotInQuotes:Int(needle:String, haystack:String, offset:Int)
' --- find character not in quotes ---
'get first needle
Local needlePos:Int
Repeat
'check needle pos
needlePos = haystack.Find(needle, offset)
If needlePos = -1 Return - 1
'get quote pos
offset = XMLFindNextAsc(haystack, 34, offset)
'is the needle before quote
If needlePos < offset or offset = -1 Return needlePos
'this is a quote so find end of quote
offset = XMLFindNextAsc(haystack, 34, offset + 1)
If offset = -1 Return - 1
offset += 1
Forever
'nope
Return -1
End
Public
Class XMLDoc Extends XMLNode
Field nullNode:XMLNode
Field version:String
Field encoding:String
Field paths:= New StringMap<List<XMLNode>>
'constructor/destructor
Method New(name:String, version:String = "", encoding:String = "")
' --- create node with name ---
valid = True
'link doc to self so we can reference it in base class xmlnode
doc = Self
'create null node
nullNode = New XMLNode("", False)
nullNode.doc = Self
'fix casing
Self.name = name.ToLower()
Self.version = version
Self.encoding = encoding
'setup path
path = name
pathList = New List<XMLNode>
pathListNode = pathList.AddLast(Self)
paths.Insert(path, pathList)
End
'api
Method Export:String(options:Int = XML_STRIP_WHITESPACE | XML_STRIP_NEWLINE | XML_STRIP_CLOSING_TAGS)
' --- convert the node to a string ---
'create a buffer
Local buffer:= New XMLStringBuffer(1024)
'add xml details
'add open
buffer.Add(XML_FORMAT_OPEN)
'add version
If version.Length
buffer.Add(" version=")
buffer.Add(34)
buffer.Add(version)
buffer.Add(34)
EndIf
'add encoding
If encoding.Length
buffer.Add(" encoding=")
buffer.Add(34)
buffer.Add(encoding)
buffer.Add(34)
EndIf
'add close
buffer.Add(XML_FORMAT_CLOSE)
'add new line
If options & XML_STRIP_NEWLINE = False buffer.Add(10)
'call internal export
Super.Export(options, buffer, 0)
'return
Return buffer.value
End
End
Class XMLNode
Field text:Bool
Field valid:Bool
Field name:String
Field value:String
Field path:String
Field doc:XMLDoc
Field parent:XMLNode
Field nextSibling:XMLNode
Field previousSibling:XMLNode
Field firstChild:XMLNode
Field lastChild:XMLNode
Field line:Int
Field column:Int
Field offset:Int
Field children:= New List<XMLNode>
Field attributes:= New StringMap<XMLAttribute>
Private
Field pathList:List<XMLNode>
Field pathListNode:list.Node<XMLNode>
Public
'constructor/destructor
Method New(name:String, valid:Bool = True)
' --- create node with name ---
If name.Length Self.name = name.ToLower()'fix casing
Self.valid = valid
End
Method Free:Void()
' --- used when the node is removed the doc ---
'remove from paths list
pathListNode.Remove()
pathListNode = Null
'recurse
If firstChild
Local child:= firstChild
While child
child.Free()
child = child.nextSibling
Wend
EndIf
End
'internal
Private
Method Export:Void(options:Int, buffer:XMLStringBuffer, depth:Int)
' --- convert the node to a string ---
'make sure there is a buffer to work with
If buffer = Null buffer = New XMLStringBuffer(1024)
Local index:Int
'text node?
If text
'yup text node
For index = 0 Until value.Length
buffer.Add(value[index])
Next
Else
'nope normal node
'add opening tag
'ident
If options & XML_STRIP_WHITESPACE = False
For index = 0 Until depth
buffer.Add(9)
Next
EndIf
buffer.Add(60)
buffer.Add(name)
'add attributes
For Local id:= EachIn attributes.Keys()
buffer.Add(32)
buffer.Add(id)
buffer.Add(61)
buffer.Add(34)
buffer.Add(attributes.Get(id).value)
buffer.Add(34)
Next
'check for short tag
If children.IsEmpty() And options & XML_STRIP_CLOSING_TAGS And Not value.Length
'no children so short tag
'finish opening tag
buffer.Add(32)
buffer.Add(47)
buffer.Add(62)
'add new line
If options & XML_STRIP_NEWLINE = False buffer.Add(10)
Else
'has children need to do opening tag only
'finish opening tag
buffer.Add(62)
'add new line
If options & XML_STRIP_NEWLINE = False buffer.Add(10)
'add children
If children.IsEmpty() = False
For Local child:= Eachin children
child.Export(options, buffer, depth + 1)
Next
Endif
'add value
If value.Length
buffer.Add(value)
If options & XML_STRIP_NEWLINE = False buffer.Add(10)
Endif
'add closing tag
'ident
If options & XML_STRIP_WHITESPACE = False
For index = 0 Until depth
buffer.Add(9)
Next
Endif
'tag
buffer.Add(60)
buffer.Add(47)
buffer.Add(name)
buffer.Add(62)
'add new line
If options & XML_STRIP_NEWLINE = False buffer.Add(10)
EndIf
EndIf
End
Method GetXMLAttribute:XMLAttribute(id:String)
' --- get attribute object ---
Return attributes.Get(id.ToLower())
End
Method GetDescendants:Void(result:List<XMLNode>, name:string, text:Bool)
' --- internal method for recurse ---
'scan children
Local child:= firstChild
While child
'test
If child.name = name And (text or child.text = False) result.AddLast(child)
'recurse
If child.firstChild And child.text = False child.GetDescendants(result, name)
'next child
child = child.nextSibling
Wend
End
Method GetDescendants:Void(result:List<XMLNode>, name:string, query:XMLAttributeQuery, text:Bool)
' --- internal method for recurse ---
'scan children
Local child:= firstChild
While child
'test
If (name.Length = 0 or child.name = name) And (text or child.text = False) And query.Test(child) result.AddLast(child)
'recurse
If child.firstChild And child.text = False child.GetDescendants(result, name, query)
'next child
child = child.nextSibling
Wend
End
Public
'child api
Method HasChildren:Bool(text:Bool = False)
' --- returns true if has children ---
If firstChild = Null Return False
If text Return True
'scan children
Local child:= firstChild
While child
'test
If child.text = False Return True
'next child
child = child.nextSibling
Wend
'nope
Return False
End
Method AddChild:XMLNode(name:String, attributes:String = "", value:String = "")
' --- add a child node ---
'skip
If valid = False or text Return Null
'create child
Local child:= New XMLNode(name)
child.doc = doc
child.parent = Self
child.value = value
'setup path
child.path = path + "/" + child.name
child.pathList = doc.paths.Get(child.path)
If child.pathList = Null
'create new path list
child.pathList = New List<XMLNode>
doc.paths.Set(child.path, child.pathList)
EndIf
child.pathListNode = child.pathList.AddLast(child)
'any attributes to add?
If attributes.Length
Local query:= New XMLAttributeQuery(attributes)
If query.Length() > 0
For Local index:= 0 Until query.Length()
child.SetAttribute(query.items[index].id, query.items[index].value)
Next
EndIf
EndIf
'setup link nodes
If lastChild
'not first child
'set previously last child to point next to new child
lastChild.nextSibling = child
'set new child previous to last child
child.previousSibling = lastChild
'update this last child to the new child
lastChild = child
Else
'first child
firstChild = child
lastChild = child
EndIf
'add to self
children.AddLast(child)
'return it
Return child
End
Method AddText:XMLNode(data:String)
' --- add a text node ---
'skip
If valid = False or text Return Null
'always add to the value
value += data
'create text node
Local child:= New XMLNode(name)
child.text = True
child.doc = doc
child.parent = Self
child.value = data
'setup link nodes
If lastChild
'not first child
'set previously last child to point next to new child
lastChild.nextSibling = child
'set new child previous to last child
child.previousSibling = lastChild
'update this last child to the new child
lastChild = child
Else
'first child
firstChild = child
lastChild = child
EndIf
'add to self
children.AddLast(child)
Return child
End
Method RemoveChild:Void(child:XMLNode)
' --- remove child ---
'skip
If valid = False or firstChild = Null or child = Null or child.parent <> Self Return
'call child to be freed
child.Free()
'update first and last pointer
If lastChild = child lastChild = child.previousSibling
If firstChild = child firstChild = child.nextSibling
'update sibling pointers
If child.previousSibling child.previousSibling.nextSibling = child.nextSibling
If child.nextSibling child.nextSibling.previousSibling = child.previousSibling
'dettach
child.parent = Null
child.previousSibling = Null
child.nextSibling = Null
'remove from list
children.Remove(child)
'do we need to rebuild the value
If child.text
Local buffer:= New XMLStringBuffer()
'scan text siblings
Local pointer:= firstChild
While pointer
If pointer.text
buffer.Add(pointer.value)
EndIf
pointer = pointer.nextSibling
Wend
'save value
value = buffer.value
EndIf
End
Method ClearChildren:Void(text:Bool = False)
' --- clears all children ---
'skip
If valid = False or firstChild = Null Return
'iterate
Local child:= firstChild
While child
If text or child.text = False
'call child to be freed
child.Free()
'dettach from doc and parent
child.previousSibling = Null
child.nextSibling = Null
child.parent = Null
child.doc = Null
'next child
child = child.nextSibling
EndIf
Wend
'reset value
If text
value = ""
EndIf
'reset lists
children.Clear()
firstChild = Null
lastChild = Null
End
Method ClearText:Void()
' --- clear text from node ---
'reset teh value
value = ""
'clear all text nodes
Local pointer:= firstChild
Local nextPointer:XMLNode
While pointer
nextPointer = pointer.nextSibling
If pointer.text
'call child to be freed
pointer.Free()
'dettach from doc and parent
pointer.previousSibling = Null
pointer.nextSibling = Null
pointer.parent = Null
pointer.doc = Null
EndIf
pointer = nextPointer
Wend
End
Method GetNextSibling:XMLNode(name:String = "", text:Bool = False)
' --- search for next sibling with matching tag name ---
'skip
If nextSibling = Null Return doc.nullNode
'quick
If name.Length = 0 Return nextSibling
'fix casing
name = name.ToLower()
'scan siblings
Local pointer:= nextSibling
While pointer
If pointer.name = name And (text or pointer.text = False) Return pointer