y-rb 0.6.0-x86_64-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/y/xml.rb ADDED
@@ -0,0 +1,1141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Y
4
+ # rubocop:disable Metrics/ClassLength
5
+
6
+ # A XMLElement
7
+ #
8
+ # Someone should not instantiate an element directly, but use
9
+ # {Y::Doc#get_xml_element} instead
10
+ #
11
+ # @example
12
+ # doc = Y::Doc.new
13
+ # xml_element = doc.get_xml_element("my xml")
14
+ #
15
+ # puts xml_element.to_s
16
+ class XMLElement
17
+ # @!attribute [r] document
18
+ #
19
+ # @return [Y::Doc] The document this array belongs to
20
+ attr_accessor :document
21
+
22
+ # Create a new XMLElement instance
23
+ #
24
+ # @param doc [Y::Doc]
25
+ def initialize(doc = nil)
26
+ @document = doc || Y::Doc.new
27
+
28
+ super()
29
+ end
30
+
31
+ # Retrieve node at index
32
+ #
33
+ # @param index [Integer]
34
+ # @return [Y::XMLElement, nil]
35
+ def [](index)
36
+ node = document.current_transaction { |tx| yxml_element_get(tx, index) }
37
+ node&.document = document
38
+ node
39
+ end
40
+
41
+ # Create a node at index
42
+ #
43
+ # @param index [Integer]
44
+ # @param name [String] Name of node, e.g. `<p />`
45
+ # @return [Y::XMLElement]
46
+ # rubocop:disable Lint/Void
47
+ def []=(index, name)
48
+ node = document.current_transaction do |tx|
49
+ yxml_element_insert_element(tx, index, name)
50
+ end
51
+ node.document = document
52
+ node
53
+ end
54
+ # rubocop:enable Lint/Void
55
+
56
+ # Returns first child in list or nil if no child exists
57
+ #
58
+ # @return [Hash]
59
+ def attrs
60
+ document.current_transaction { |tx| yxml_element_attributes(tx) }
61
+ end
62
+
63
+ alias attributes attrs
64
+
65
+ # Returns first child in list or nil if no child exists
66
+ #
67
+ # @return [Y::XMLElement]
68
+ def first_child
69
+ child = document.current_transaction { |tx| yxml_element_first_child(tx) }
70
+ child&.document = document
71
+ child
72
+ end
73
+
74
+ # Insert text into element at given index
75
+ #
76
+ # Optional input is pushed to the text if provided
77
+ #
78
+ # @param index [Integer]
79
+ # @param input [String, nil]
80
+ # @return [Y::XMLText]
81
+ def insert_text(index, input = "")
82
+ text = document.current_transaction do |tx|
83
+ yxml_element_insert_text(tx, index, input)
84
+ end
85
+ text.document = document
86
+ text
87
+ end
88
+
89
+ # Retrieve element or text adjacent (next) to this element
90
+ #
91
+ # @return [Y::XMLElement, Y::XMLText, nil]
92
+ def next_sibling
93
+ node = document.current_transaction { |tx| yxml_element_next_sibling(tx) }
94
+ node&.document = document
95
+ node
96
+ end
97
+
98
+ # Attach listener to get notified about changes to the element
99
+ #
100
+ # This supports either a `Proc` or a `Block`.
101
+ #
102
+ # @example Receive changes via Proc
103
+ # doc = Y::Doc.new
104
+ # xml_element = doc.get_xml_element("my xml element")
105
+ # xml_element.attach ->(changes) { … }
106
+ #
107
+ # @example Receive changes via Block
108
+ # doc = Y::Doc.new
109
+ # xml_element = doc.get_xml_element("my xml element")
110
+ # xml_element.attach { |changes| … }
111
+ #
112
+ # @param callback [Proc]
113
+ # @param block [Block]
114
+ # @return [Integer] The subscription ID
115
+ def attach(callback = nil, &block)
116
+ return yxml_element_observe(callback) unless callback.nil?
117
+
118
+ yxml_element_observe(block.to_proc) unless block.nil?
119
+ end
120
+
121
+ # Retrieve parent element
122
+ #
123
+ # @return [Y::XMLElement, nil]
124
+ def parent
125
+ node = yxml_element_parent
126
+ node.document = document
127
+ node
128
+ end
129
+
130
+ # Retrieve element or text adjacent (previous) to this element
131
+ #
132
+ # @return [Y::XMLElement, Y::XMLText, nil]
133
+ def prev_sibling
134
+ node = document.current_transaction { |tx| yxml_element_prev_sibling(tx) }
135
+ node&.document = document
136
+ node
137
+ end
138
+
139
+ # Creates a new child an inserts at the end of the children list
140
+ #
141
+ # @param name [String]
142
+ # @return [Y::XMLElement]
143
+ def <<(name)
144
+ xml_element = document.current_transaction do |tx|
145
+ yxml_element_push_element_back(tx, name)
146
+ end
147
+ xml_element.document = document
148
+ xml_element
149
+ end
150
+
151
+ alias push_child <<
152
+
153
+ # Insert new text at the end of this elements child list
154
+ #
155
+ # The optional str argument initializes the text node with its value
156
+ #
157
+ # @param str [String]
158
+ # @return [Y::XMLText]
159
+ def push_text(str = "")
160
+ text = document.current_transaction do |tx|
161
+ yxml_element_push_text_back(tx, str)
162
+ end
163
+ text.document = document
164
+ text
165
+ end
166
+
167
+ # Number of children
168
+ #
169
+ # @return [Integer]
170
+ def size
171
+ document.current_transaction { |tx| yxml_element_size(tx) }
172
+ end
173
+
174
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
175
+
176
+ # Removes one or more children from XML Element
177
+ #
178
+ # @example Removes a single element
179
+ # doc = Y::Doc.new
180
+ #
181
+ # xml_element = doc.get_xml_element("my xml")
182
+ # xml_element << "A"
183
+ # xml_element << "B"
184
+ # xml_element << "C"
185
+ #
186
+ # xml_element.slice!(1)
187
+ #
188
+ # xml_element.to_s # <UNDEFINED><A></A><C></C></UNDEFINED>
189
+ #
190
+ # @overload slice!(n)
191
+ # Removes nth node from child list
192
+ #
193
+ # @overload slice!(start, length)
194
+ # Removes a range of nodes
195
+ #
196
+ # @overload slice!(range)
197
+ # Removes a range of nodes
198
+ #
199
+ # @return [void]
200
+ def slice!(*args)
201
+ document.current_transaction do |tx| # rubocop:disable Metrics/BlockLength
202
+ if args.empty?
203
+ raise ArgumentError,
204
+ "Provide one of `index`, `range`, `start, length` as arguments"
205
+ end
206
+
207
+ if args.size == 1
208
+ arg = args.first
209
+
210
+ if arg.is_a?(Range)
211
+ if arg.exclude_end?
212
+ yxml_element_remove_range(tx, arg.first,
213
+ arg.last - arg.first)
214
+ end
215
+ unless arg.exclude_end?
216
+ yxml_element_remove_range(tx, arg.first,
217
+ arg.last + 1 - arg.first)
218
+ end
219
+ return nil
220
+ end
221
+
222
+ if arg.is_a?(Numeric)
223
+ yxml_element_remove_range(tx, arg.to_int, 1)
224
+ return nil
225
+ end
226
+ end
227
+
228
+ if args.size == 2
229
+ first, second = args
230
+
231
+ if first.is_a?(Numeric) && second.is_a?(Numeric)
232
+ yxml_element_remove_range(tx, first, second)
233
+ return nil
234
+ end
235
+ end
236
+
237
+ raise ArgumentError, "Please check your arguments, can't slice."
238
+ end
239
+ end
240
+
241
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
242
+
243
+ # Tag name
244
+ #
245
+ # @return [String]
246
+ def tag
247
+ yxml_element_tag
248
+ end
249
+
250
+ # String representation of this node and all its children
251
+ #
252
+ # @return [String]
253
+ def to_s
254
+ document.current_transaction { |tx| yxml_element_to_s(tx) }
255
+ end
256
+
257
+ # Detach a listener
258
+ #
259
+ # @param subscription_id [Integer]
260
+ # @return [void]
261
+ def detach(subscription_id)
262
+ yxml_element_unobserve(subscription_id)
263
+ end
264
+
265
+ # Creates a new node and puts it in front of the child list
266
+ #
267
+ # @param name [String]
268
+ # @return [Y::XMLElement]
269
+ def unshift_child(name)
270
+ xml_element = document.current_transaction do |tx|
271
+ yxml_element_push_element_front(tx, name)
272
+ end
273
+ xml_element.document = document
274
+ xml_element
275
+ end
276
+
277
+ # Insert new text at the front of this elements child list
278
+ #
279
+ # The optional str argument initializes the text node with its value
280
+ #
281
+ # @param str [String]
282
+ # @return [Y::XMLText]
283
+ def unshift_text(str = "")
284
+ text = document.current_transaction do |tx|
285
+ yxml_element_push_text_front(tx, str)
286
+ end
287
+ text.document = document
288
+ text
289
+ end
290
+
291
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
292
+
293
+ # make attributes just work on an element in the form of `attr_name` and
294
+ # `attr_name=`
295
+ #
296
+ # @example Set and get an attribute
297
+ # doc = Y::Doc.new
298
+ # xml_element = doc.get_xml_element("my xml")
299
+ # xml_element.attr_name = "Hello"
300
+ #
301
+ # puts xml_element.attr_name # "Hello"
302
+ #
303
+ # @!visibility private
304
+ def method_missing(method_name, *args, &block)
305
+ is_setter = method_name.to_s.end_with?("=")
306
+
307
+ setter = method_name
308
+ setter += "=" unless is_setter
309
+ getter = method_name
310
+ getter = getter.to_s.slice(0...-1)&.to_sym if is_setter
311
+
312
+ define_singleton_method(setter.to_sym) do |new_val|
313
+ document.current_transaction do |tx|
314
+ yxml_element_insert_attribute(tx,
315
+ method_name.to_s
316
+ .delete_suffix("=")
317
+ .delete_prefix("attr_"),
318
+ new_val)
319
+ end
320
+ end
321
+
322
+ define_singleton_method(getter) do
323
+ document.current_transaction do |tx|
324
+ yxml_element_get_attribute(tx,
325
+ method_name.to_s.delete_prefix("attr_"))
326
+ end
327
+ end
328
+
329
+ if is_setter
330
+ value = args[0]
331
+ send(setter, value)
332
+ end
333
+ rescue StandardError
334
+ super(method_name, *args, &block)
335
+ end
336
+
337
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
338
+
339
+ # Make sure we only respond to attributes
340
+ # @!visibility private
341
+ def respond_to_missing?(method_name, include_private = false)
342
+ method_name.to_s.start_with?("attr_") || super
343
+ end
344
+
345
+ # @!method yxml_element_attributes
346
+ #
347
+ # @return [Hash]
348
+
349
+ # @!method yxml_element_first_child(tx)
350
+ #
351
+ # @param tx [Y::Transaction]
352
+ # @return [Y::XMLElement, Y::XMLText]
353
+
354
+ # @!method yxml_element_get_attribute(tx, name)
355
+ #
356
+ # @param tx [Y::Transaction]
357
+ # @param name [String]
358
+ # @return [String, nil]
359
+
360
+ # @!method yxml_element_get(tx, index)
361
+ #
362
+ # @param tx [Y::Transaction]
363
+ # @param index [Integer]
364
+ # @return [Y::XMLElement, Y::XMLText, nil]
365
+
366
+ # @!method yxml_element_insert_attribute(tx, name, value)
367
+ #
368
+ # @param tx [Y::Transaction]
369
+ # @param name [String]
370
+ # @param value [String]
371
+ # @return [String, nil]
372
+
373
+ # @!method yxml_element_insert_element(tx, index, name)
374
+ # Insert XML element into this XML element
375
+ #
376
+ # @!visibility private
377
+ # @param tx [Y::Transaction]
378
+ # @param index [Integer]
379
+ # @param name [String]
380
+ # @return [Y::XMLElement]
381
+
382
+ # @!method yxml_element_insert_text(tx, index, text)
383
+ #
384
+ # @param tx [Y::Transaction]
385
+ # @param index [Integer]
386
+ # @param text [String]
387
+ # @return [Y::XMLText]
388
+
389
+ # @!method yxml_element_next_sibling(tx)
390
+ #
391
+ # @param tx [Y::Transaction]
392
+ # @return [Y::XMLElement, XMLText, nil]
393
+
394
+ # @!method yxml_element_observe(callback)
395
+ #
396
+ # @param callback [Proc]
397
+ # @return [Integer] The subscription ID
398
+
399
+ # @!method yxml_element_parent()
400
+ #
401
+ # @return [Y::XMLElement, nil]
402
+
403
+ # @!method yxml_element_prev_sibling(tx)
404
+ #
405
+ # @param tx [Y::Transaction]
406
+ # @return [Y::XMLElement, XMLText, nil]
407
+
408
+ # @!method yxml_element_push_element_back(tx, name)
409
+ #
410
+ # @param tx [Y::Transaction]
411
+ # @param name [String]
412
+ # @return [Y::XMLElement]
413
+
414
+ # @!method yxml_element_push_element_front(tx, name)
415
+ #
416
+ # @param tx [Y::Transaction]
417
+ # @param name [String]
418
+ # @return [Y::XMLElement]
419
+
420
+ # @!method yxml_element_push_text_back(tx, text)
421
+ #
422
+ # @param tx [Y::Transaction]
423
+ # @param text [string]
424
+ # @return [Y::XMLText]
425
+
426
+ # @!method yxml_element_push_text_front(tx, text)
427
+ #
428
+ # @param tx [Y::Transaction]
429
+ # @param text [string]
430
+ # @return [Y::XMLText]
431
+
432
+ # @!method yxml_element_remove_attribute(tx, name)
433
+ #
434
+ # @param tx [Y::Transaction]
435
+ # @param name [String] name
436
+ # @return [void]
437
+
438
+ # @!method yxml_element_remove_range(tx, index, length)
439
+ #
440
+ # @param tx [Y::Transaction]
441
+ # @param index [Integer]
442
+ # @param length [Integer]
443
+ #
444
+ # @return [void]
445
+
446
+ # @!method yxml_element_size(tx)
447
+ #
448
+ # @param tx [Y::Transaction]
449
+ # @return [Integer]
450
+
451
+ # @!method yxml_element_tag
452
+ #
453
+ # @return [String]
454
+
455
+ # @!method yxml_element_to_s(tx)
456
+ #
457
+ # @param tx [Y::Transaction]
458
+ # @return [String]
459
+
460
+ # @!method yxml_element_unobserve(subscription_id)
461
+ #
462
+ # @param subscription_id [Integer]
463
+ # @return [void]
464
+ end
465
+
466
+ # A XMLText
467
+ #
468
+ # Someone should not instantiate a text directly, but use
469
+ # {Y::Doc#get_xml_text}, {Y::XMLElement#insert_text},
470
+ # {Y::XMLElement#push_text}, {Y::XMLElement#unshift_text} instead.
471
+ #
472
+ # The XMLText API is similar to {Y::Text}, but adds a few methods to make it
473
+ # easier to work in structured XML documents.
474
+ #
475
+ # @example
476
+ # doc = Y::Doc.new
477
+ # xml_text = doc.get_xml_text("my xml text")
478
+ #
479
+ # puts xml_text.to_s
480
+ class XMLText
481
+ # @!attribute [r] document
482
+ #
483
+ # @return [Y::Doc] The document this array belongs to
484
+ attr_accessor :document
485
+
486
+ # Create a new XMLText instance
487
+ #
488
+ # @param doc [Y::Doc]
489
+ def initialize(doc = nil)
490
+ @document = doc || Y::Doc.new
491
+
492
+ super()
493
+ end
494
+
495
+ # Push a string to the end of the text node
496
+ #
497
+ # @param str [String]
498
+ # @return {void}
499
+ def <<(str)
500
+ document.current_transaction { |tx| yxml_text_push(tx, str) }
501
+ end
502
+
503
+ alias push <<
504
+
505
+ # Attach a listener to get notified about changes
506
+ #
507
+ # @param callback [Proc]
508
+ # @return [Integer] subscription_id
509
+ def attach(callback = nil, &block)
510
+ yxml_text_observe(callback) unless callback.nil?
511
+ yxml_text_observe(block.to_proc) unless block.nil?
512
+ end
513
+
514
+ # Return text attributes
515
+ #
516
+ # @return [Hash]
517
+ def attrs
518
+ document.current_transaction { |tx| yxml_text_attributes(tx) }
519
+ end
520
+
521
+ # Detach a listener
522
+ #
523
+ # @param subscription_id [Integer]
524
+ # @return [void]
525
+ def detach(subscription_id)
526
+ yxml_text_unobserve(subscription_id)
527
+ end
528
+
529
+ # Format text
530
+ #
531
+ # @param index [Integer]
532
+ # @param length [Integer]
533
+ # @param attrs [Hash]
534
+ # @return [void]
535
+ def format(index, length, attrs)
536
+ document.current_transaction do |tx|
537
+ yxml_text_format(tx, index, length, attrs)
538
+ end
539
+ end
540
+
541
+ # rubocop:disable Metrics/MethodLength
542
+
543
+ # Insert a value at position and with optional attributes. This method is
544
+ # similar to [String#insert](https://ruby-doc.org/core-3.1.2/String.html),
545
+ # except for the optional third `attrs` argument.
546
+ #
547
+ # @example Insert a string at position
548
+ # doc = Y::Doc.new
549
+ # text = doc.get_text("my text")
550
+ # text << "Hello, "
551
+ #
552
+ # text.insert(7, "World!")
553
+ #
554
+ # puts text.to_s == "Hello, World!" # true
555
+ #
556
+ # The value can be any of the supported types:
557
+ # - Boolean
558
+ # - String
559
+ # - Numeric
560
+ # - Array (where element types must be supported)
561
+ # - Hash (where the the types of key and values must be supported)
562
+ #
563
+ # @param index [Integer]
564
+ # @param value [String, Float, Integer, Array, Hash, Boolean]
565
+ # @param attrs [Hash, nil]
566
+ # @return [void]
567
+ def insert(index, value, attrs = nil)
568
+ document.current_transaction do |tx|
569
+ if value.is_a?(String)
570
+ yxml_text_insert(tx, index, value) if attrs.nil?
571
+ unless attrs.nil?
572
+ yxml_text_insert_with_attrs(tx, index, value,
573
+ attrs)
574
+ end
575
+
576
+ return nil
577
+ end
578
+
579
+ if can_insert?(value)
580
+ yxml_text_insert_embed(tx, index, value) if attrs.nil?
581
+ unless attrs.nil?
582
+ yxml_text_insert_embed_with_attrs(tx, index, value,
583
+ attrs)
584
+ end
585
+
586
+ return nil
587
+ end
588
+
589
+ raise ArgumentError,
590
+ "Can't insert value. `#{value.class.name}` isn't supported."
591
+ end
592
+ end
593
+
594
+ # rubocop:enable Metrics/MethodLength
595
+
596
+ # Return length of string
597
+ #
598
+ # @return [void]
599
+ def length
600
+ document.current_transaction { |tx| yxml_text_length(tx) }
601
+ end
602
+
603
+ alias size length
604
+
605
+ # Return adjacent XMLElement or XMLText node (next)
606
+ #
607
+ # @return [Y::XMLElement, Y::XMLText, nil]
608
+ def next_sibling
609
+ node = document.current_transaction { |tx| yxml_text_next_sibling(tx) }
610
+ node.document = document
611
+ node
612
+ end
613
+
614
+ # Return parent XMLElement
615
+ #
616
+ # @return [Y::XMLElement, nil]
617
+ def parent
618
+ node = yxml_text_parent
619
+ node.document = document
620
+ node
621
+ end
622
+
623
+ # Return adjacent XMLElement or XMLText node (prev)
624
+ #
625
+ # @return [Y::XMLElement, Y::XMLText, nil]
626
+ def prev_sibling
627
+ node = document.current_transaction { |tx| yxml_text_prev_sibling(tx) }
628
+ node&.document = document
629
+ node
630
+ end
631
+
632
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
633
+
634
+ # Removes a part from text
635
+ #
636
+ # **Attention:** In comparison to String#slice, {XMLText#slice!} will not
637
+ # return the substring that gets removed. Even this being technically
638
+ # possible, it requires us to read the substring before removing it, which
639
+ # is not desirable in most situations.
640
+ #
641
+ # @example Removes a single character
642
+ # doc = Y::Doc.new
643
+ #
644
+ # text = doc.get_xml_text("my xml text")
645
+ # text << "Hello"
646
+ #
647
+ # text.slice!(0)
648
+ #
649
+ # text.to_s == "ello" # true
650
+ #
651
+ # @example Removes a range of characters
652
+ # doc = Y::Doc.new
653
+ #
654
+ # text = doc.get_xml_text("my xml text")
655
+ # text << "Hello"
656
+ #
657
+ # text.slice!(1..2)
658
+ # text.to_s == "Hlo" # true
659
+ #
660
+ # text.slice!(1...2)
661
+ # text.to_s == "Ho" # true
662
+ #
663
+ # @example Removes a range of chars from start and for given length
664
+ # doc = Y::Doc.new
665
+ #
666
+ # text = doc.get_xml_text("my xml text")
667
+ # text << "Hello"
668
+ #
669
+ # text.slice!(0, 3)
670
+ #
671
+ # text.to_s == "lo" # true
672
+ #
673
+ # @overload slice!(index)
674
+ # Removes a single character at index
675
+ #
676
+ # @overload slice!(start, length)
677
+ # Removes a range of characters
678
+ #
679
+ # @overload slice!(range)
680
+ # Removes a range of characters
681
+ #
682
+ # @return [void]
683
+ def slice!(*args)
684
+ document.current_transaction do |tx|
685
+ if args.empty?
686
+ raise ArgumentError,
687
+ "Provide one of `index`, `range`, `start, length` as arguments"
688
+ end
689
+
690
+ if args.size == 1
691
+ arg = args.first
692
+
693
+ if arg.is_a?(Range)
694
+ yxml_text_remove_range(tx, arg.first, arg.last - arg.first)
695
+ return nil
696
+ end
697
+
698
+ if arg.is_a?(Numeric)
699
+ yxml_text_remove_range(tx, arg.to_int, 1)
700
+ return nil
701
+ end
702
+ end
703
+
704
+ if args.size == 2
705
+ first, second = args
706
+
707
+ if first.is_a?(Numeric) && second.is_a?(Numeric)
708
+ yxml_text_remove_range(tx, first, second)
709
+ return nil
710
+ end
711
+ end
712
+
713
+ raise ArgumentError, "Please check your arguments, can't slice."
714
+ end
715
+ end
716
+
717
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
718
+
719
+ # Returns string representation of XMLText
720
+ #
721
+ # @return [String]
722
+ def to_s
723
+ document.current_transaction { |tx| yxml_text_to_s(tx) }
724
+ end
725
+
726
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
727
+
728
+ # make attributes just work on an element in the form of `attr_name` and
729
+ # `attr_name=`
730
+ #
731
+ # @example Set and get an attribute
732
+ # doc = Y::Doc.new
733
+ # xml_element = doc.get_xml_element("my xml")
734
+ # xml_element.attr_name = "Hello"
735
+ #
736
+ # puts xml_element.attr_name # "Hello"
737
+ #
738
+ # @!visibility private
739
+ def method_missing(method_name, *args, &block)
740
+ is_setter = method_name.to_s.end_with?("=")
741
+
742
+ setter = method_name
743
+ setter += "=" unless is_setter
744
+ getter = method_name
745
+ getter = getter.to_s.slice(0...-1)&.to_sym if is_setter
746
+
747
+ define_singleton_method(setter.to_sym) do |new_val|
748
+ document.current_transaction do |tx|
749
+ yxml_text_insert_attribute(tx,
750
+ method_name.to_s
751
+ .delete_suffix("=")
752
+ .delete_prefix("attr_"),
753
+ new_val)
754
+ end
755
+ end
756
+
757
+ define_singleton_method(getter) do
758
+ document.current_transaction do |tx|
759
+ yxml_text_get_attribute(tx, method_name.to_s.delete_prefix("attr_"))
760
+ end
761
+ end
762
+
763
+ if is_setter
764
+ value = args[0]
765
+ send(setter, value)
766
+ end
767
+ rescue StandardError
768
+ super(method_name, *args, &block)
769
+ end
770
+
771
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
772
+
773
+ # Make sure we only respond to attributes
774
+ # @!visibility private
775
+ def respond_to_missing?(method_name, include_private = false)
776
+ method_name.to_s.start_with?("attr_") || super
777
+ end
778
+
779
+ private
780
+
781
+ def can_insert?(value)
782
+ value.is_a?(NilClass) ||
783
+ value.is_a?(Symbol) ||
784
+ [true, false].include?(value) ||
785
+ value.is_a?(Numeric) ||
786
+ value.is_a?(Enumerable) ||
787
+ value.is_a?(Hash)
788
+ end
789
+
790
+ # @!method yxml_text_attributes
791
+ #
792
+ # @return [Hash]
793
+
794
+ # @!method yxml_text_format(tx, index, length, attrs)
795
+ #
796
+ # @param tx [Y::Transaction]
797
+ # @param index [Integer]
798
+ # @param length [Integer]
799
+ # @param attrs [Hash]
800
+ # @return [void]
801
+
802
+ # @!method yxml_text_get_attribute(tx, name)
803
+ #
804
+ # @param tx [Y::Transaction]
805
+ # @param name [String]
806
+ # @return [String, nil]
807
+
808
+ # @!method yxml_text_insert(tx, index, str)
809
+ #
810
+ # @param tx [Y::Transaction]
811
+ # @param index [Integer]
812
+ # @param str [String]
813
+ # @return [void]
814
+
815
+ # @!method yxml_text_insert_attribute(tx, name, value)
816
+ #
817
+ # @param tx [Y::Transaction]
818
+ # @param name [String] name
819
+ # @param value [String] value
820
+ # @return [void]
821
+
822
+ # @!method yxml_text_insert_with_attrs(tx, index, value, attrs)
823
+ #
824
+ # @param tx [Y::Transaction]
825
+ # @param index [Integer]
826
+ # @param value [String]
827
+ # @param attrs [Hash]
828
+ # @return [void]
829
+
830
+ # @!method yxml_text_insert_embed(tx, index, value)
831
+ #
832
+ # @param tx [Y::Transaction]
833
+ # @param index [Integer]
834
+ # @param value [String]
835
+ # @return [void]
836
+
837
+ # @!method yxml_text_insert_embed_with_attrs(tx, index, value, attrs)
838
+ #
839
+ # @param tx [Y::Transaction]
840
+ # @param index [Integer]
841
+ # @param value [true, false, Float, Integer, Array, Hash]
842
+ # @param attrs [Hash]
843
+ # @return [void]
844
+
845
+ # @!method yxml_text_length(tx)
846
+ #
847
+ # @param tx [Y::Transaction]
848
+ # @return [Integer]
849
+
850
+ # @!method yxml_text_next_sibling(tx)
851
+ #
852
+ # @param tx [Y::Transaction]
853
+ # @return [Y::XMLElement, Y::XMLText, nil]
854
+
855
+ # @!method yxml_text_observe(callback)
856
+ #
857
+ # @param callback [Proc]
858
+ # @return [Integer] A subscription ID
859
+
860
+ # @!method yxml_text_parent
861
+ #
862
+ # @return [Y::XMLElement, nil]
863
+
864
+ # @!method yxml_text_prev_sibling(tx)
865
+ #
866
+ # @param tx [Y::Transaction]
867
+ # @return [Y::XMLElement, Y::XMLText, nil]
868
+
869
+ # @!method yxml_text_push(tx, str)
870
+ #
871
+ # @param tx [Y::Transaction]
872
+ # @param str [String]
873
+ # @return [void]
874
+
875
+ # @!method yxml_text_remove_range(tx, index, length)
876
+ #
877
+ # @param tx [Y::Transaction]
878
+ # @param index [Integer]
879
+ # @param length [Integer]
880
+ # @return [void]
881
+
882
+ # @!method yxml_text_to_s(tx)
883
+ #
884
+ # @param tx [Y::Transaction]
885
+ # @return [void]
886
+
887
+ # @!method yxml_text_unobserve(subscription_id)
888
+ #
889
+ # @param subscription_id [Integer]
890
+ # @return [void]
891
+ end
892
+
893
+ # @!visibility private
894
+ class XMLFragment
895
+ # @!attribute [r] document
896
+ #
897
+ # @return [Y::Doc] The document this array belongs to
898
+ attr_accessor :document
899
+
900
+ # Create a new XMLElement instance
901
+ #
902
+ # @param doc [Y::Doc]
903
+ def initialize(doc = nil)
904
+ @document = doc || Y::Doc.new
905
+
906
+ super()
907
+ end
908
+
909
+ # Retrieve node at index
910
+ #
911
+ # @param index [Integer]
912
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
913
+ def [](index)
914
+ node = document.current_transaction { |tx| yxml_fragment_get(tx, index) }
915
+ node&.document = document
916
+ node
917
+ end
918
+
919
+ # Create a node at index
920
+ #
921
+ # @param index [Integer]
922
+ # @param name [String] Name of node, e.g. `<p />`
923
+ # @return [Y::XMLElement]
924
+ # rubocop:disable Lint/Void
925
+ def []=(index, name)
926
+ node = document.current_transaction do |tx|
927
+ yxml_fragment_insert(tx, index, name)
928
+ end
929
+ node.document = document
930
+ node
931
+ end
932
+ # rubocop:enable Lint/Void
933
+
934
+ # Retrieve first child
935
+ #
936
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
937
+ def first_child
938
+ node = yxml_fragment_first_child
939
+ node&.document = document
940
+ node
941
+ end
942
+
943
+ # Retrieve child at index
944
+ #
945
+ # @param [Integer] index
946
+ # @param [String] tag
947
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
948
+ def insert(index, tag)
949
+ document.current_transaction { |tx| yxml_fragment_insert(tx, index, tag) }
950
+ end
951
+
952
+ # Length of the fragment
953
+ #
954
+ # @return [Integer]
955
+ def length
956
+ document.current_transaction { |tx| yxml_fragment_len(tx) }
957
+ end
958
+
959
+ alias size length
960
+
961
+ # Retrieve parent element
962
+ #
963
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
964
+ def parent
965
+ node = yxml_fragment_parent
966
+ node.document = document
967
+ node
968
+ end
969
+
970
+ # Creates a new child an inserts at the end of the children list
971
+ #
972
+ # @param name [String]
973
+ # @return [Y::XMLElement]
974
+ def <<(name)
975
+ xml_element = document.current_transaction do |tx|
976
+ yxml_fragment_push_back(tx, name)
977
+ end
978
+ xml_element.document = document
979
+ xml_element
980
+ end
981
+
982
+ alias push <<
983
+
984
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
985
+
986
+ # Removes one or more children from XML Fragment
987
+ #
988
+ # @example Removes a single element
989
+ # doc = Y::Doc.new
990
+ #
991
+ # xml_fragment = doc.get_xml_fragment("my xml fragment")
992
+ # xml_fragment << "A"
993
+ # xml_fragment << "B"
994
+ # xml_fragment << "C"
995
+ #
996
+ # xml_fragment.slice!(1)
997
+ #
998
+ # xml_fragment.to_s # <UNDEFINED><A></A><C></C></UNDEFINED>
999
+ #
1000
+ # @overload slice!(n)
1001
+ # Removes nth node from child list
1002
+ #
1003
+ # @overload slice!(start, length)
1004
+ # Removes a range of nodes
1005
+ #
1006
+ # @overload slice!(range)
1007
+ # Removes a range of nodes
1008
+ #
1009
+ # @return [void]
1010
+ def slice!(*args)
1011
+ document.current_transaction do |tx| # rubocop:disable Metrics/BlockLength
1012
+ if args.empty?
1013
+ raise ArgumentError,
1014
+ "Provide one of `index`, `range`, `start, length` as arguments"
1015
+ end
1016
+
1017
+ if args.size == 1
1018
+ arg = args.first
1019
+
1020
+ if arg.is_a?(Range)
1021
+ if arg.exclude_end?
1022
+ yxml_fragment_remove_range(tx, arg.first,
1023
+ arg.last - arg.first)
1024
+ end
1025
+ unless arg.exclude_end?
1026
+ yxml_fragment_remove_range(tx, arg.first,
1027
+ arg.last + 1 - arg.first)
1028
+ end
1029
+ return nil
1030
+ end
1031
+
1032
+ if arg.is_a?(Numeric)
1033
+ yxml_fragment_remove_range(tx, arg.to_int, 1)
1034
+ return nil
1035
+ end
1036
+ end
1037
+
1038
+ if args.size == 2
1039
+ first, second = args
1040
+
1041
+ if first.is_a?(Numeric) && second.is_a?(Numeric)
1042
+ yxml_fragment_remove_range(tx, first, second)
1043
+ return nil
1044
+ end
1045
+ end
1046
+
1047
+ raise ArgumentError, "Please check your arguments, can't slice."
1048
+ end
1049
+ end
1050
+
1051
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
1052
+
1053
+ # Traverse over the successors of the current XML element and return
1054
+ # a flat representation of all successors.
1055
+ #
1056
+ # @return [Array<Y::XMLElement, Y::XMLFragment, Y::XMLText]
1057
+ def successors
1058
+ document.current_transaction { |tx| yxml_fragment_successors(tx) }
1059
+ end
1060
+
1061
+ # Returns string representation of XMLFragment
1062
+ #
1063
+ # @return [String]
1064
+ def to_s
1065
+ document.current_transaction { |tx| yxml_fragment_to_s(tx) }
1066
+ end
1067
+
1068
+ # Creates a new node and puts it in front of the child list
1069
+ #
1070
+ # @param name [String]
1071
+ # @return [Y::XMLElement]
1072
+ def unshift(name)
1073
+ xml_element = document.current_transaction do |tx|
1074
+ yxml_fragment_push_front(tx, name)
1075
+ end
1076
+ xml_element.document = document
1077
+ xml_element
1078
+ end
1079
+
1080
+ # @!method yxml_fragment_first_child
1081
+ #
1082
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
1083
+
1084
+ # @!method yxml_fragment_get(tx, index)
1085
+ #
1086
+ # @param [Y::Transaction] tx
1087
+ # @param [Integer] index
1088
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
1089
+
1090
+ # @!method yxml_fragment_insert(tx, index, tag)
1091
+ #
1092
+ # @param tx [Y::Transaction]
1093
+ # @param [Integer] index
1094
+ # @param [String] tag
1095
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
1096
+
1097
+ # @!method yxml_fragment_len(tx)
1098
+ #
1099
+ # @param tx [Y::Transaction]
1100
+ # @return [Integer]
1101
+
1102
+ # @!method yxml_fragment_parent
1103
+ #
1104
+ # @return [Y::XMLElement, Y::XMLFragment, Y::XMLText, nil]
1105
+
1106
+ # @!method yxml_fragment_push_back(tx, tag)
1107
+ #
1108
+ # @param tx [Y::Transaction]
1109
+ # @param [String] tag
1110
+ # @return [Y::XMLElement]
1111
+
1112
+ # @!method yxml_fragment_push_front(tx, tag)
1113
+ #
1114
+ # @param tx [Y::Transaction]
1115
+ # @param [String] tag
1116
+ # @return [Y::XMLElement]
1117
+
1118
+ # @!method yxml_fragment_remove(tx, index)
1119
+ #
1120
+ # @param tx [Y::Transaction]
1121
+ # @param [Integer] index
1122
+
1123
+ # @!method yxml_fragment_remove_range(tx, index, length)
1124
+ #
1125
+ # @param tx [Y::Transaction]
1126
+ # @param [Integer] index
1127
+ # @param [Integer] length
1128
+
1129
+ # @!method yxml_fragment_successors(tx)
1130
+ #
1131
+ # @param tx [Y::Transaction]
1132
+ # @return [Array<Y::XMLElement, Y::XMLFragment, Y::XMLText>]
1133
+
1134
+ # @!method yxml_fragment_to_s(tx)
1135
+ #
1136
+ # @param tx [Y::Transaction]
1137
+ # @return [String]
1138
+ end
1139
+
1140
+ # rubocop:enable Metrics/ClassLength
1141
+ end