rexml 3.3.1 → 3.3.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rexml might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/NEWS.md +85 -0
- data/lib/rexml/formatters/pretty.rb +1 -1
- data/lib/rexml/parsers/baseparser.rb +67 -19
- data/lib/rexml/parsers/pullparser.rb +4 -0
- data/lib/rexml/parsers/sax2parser.rb +6 -19
- data/lib/rexml/parsers/streamparser.rb +2 -2
- data/lib/rexml/rexml.rb +1 -1
- data/lib/rexml/source.rb +16 -6
- data/lib/rexml/text.rb +34 -14
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e5e2317fb4a12cc855de221be85a9d62c2966c4997ead5a4ede3600561d5ede
|
4
|
+
data.tar.gz: a2b8f326e706211d00a9a8446b84ebd658c9cb82a4f7c98e5760ed2b10d8866c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d26167dc282f9ff928b263927a9f003bddb6591a938b43dfddcd8a2fe2c1ddb4f931f09ec52dd3bf1912953365dcaafafb359bdd6dba1f9ca33a55bbc62ec5b
|
7
|
+
data.tar.gz: b3216114c5978079b102a6492cd0d8afde5eaf0af5ebc803873dc7a9ad4dc9afa785000c923f296b88c3b5c663a543348f65a3734801149f792518a1bcb5844c
|
data/NEWS.md
CHANGED
@@ -1,5 +1,90 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 3.3.3 - 2024-08-01 {#version-3-3-3}
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Added support for detecting invalid XML that has unsupported
|
8
|
+
content before root element
|
9
|
+
* GH-184
|
10
|
+
* Patch by NAITOH Jun.
|
11
|
+
|
12
|
+
* Added support for `REXML::Security.entity_expansion_limit=` and
|
13
|
+
`REXML::Security.entity_expansion_text_limit=` in SAX2 and pull
|
14
|
+
parsers
|
15
|
+
* GH-187
|
16
|
+
* Patch by NAITOH Jun.
|
17
|
+
|
18
|
+
* Added more tests for invalid XMLs.
|
19
|
+
* GH-183
|
20
|
+
* Patch by Watson.
|
21
|
+
|
22
|
+
* Added more performance tests.
|
23
|
+
* Patch by Watson.
|
24
|
+
|
25
|
+
* Improved parse performance.
|
26
|
+
* GH-186
|
27
|
+
* Patch by tomoya ishida.
|
28
|
+
|
29
|
+
### Thanks
|
30
|
+
|
31
|
+
* NAITOH Jun
|
32
|
+
|
33
|
+
* Watson
|
34
|
+
|
35
|
+
* tomoya ishida
|
36
|
+
|
37
|
+
## 3.3.2 - 2024-07-16 {#version-3-3-2}
|
38
|
+
|
39
|
+
### Improvements
|
40
|
+
|
41
|
+
* Improved parse performance.
|
42
|
+
* GH-160
|
43
|
+
* Patch by NAITOH Jun.
|
44
|
+
|
45
|
+
* Improved parse performance.
|
46
|
+
* GH-169
|
47
|
+
* GH-170
|
48
|
+
* GH-171
|
49
|
+
* GH-172
|
50
|
+
* GH-173
|
51
|
+
* GH-174
|
52
|
+
* GH-175
|
53
|
+
* GH-176
|
54
|
+
* GH-177
|
55
|
+
* Patch by Watson.
|
56
|
+
|
57
|
+
* Added support for raising a parse exception when an XML has extra
|
58
|
+
content after the root element.
|
59
|
+
* GH-161
|
60
|
+
* Patch by NAITOH Jun.
|
61
|
+
|
62
|
+
* Added support for raising a parse exception when an XML
|
63
|
+
declaration exists in wrong position.
|
64
|
+
* GH-162
|
65
|
+
* Patch by NAITOH Jun.
|
66
|
+
|
67
|
+
* Removed needless a space after XML declaration in pretty print mode.
|
68
|
+
* GH-164
|
69
|
+
* Patch by NAITOH Jun.
|
70
|
+
|
71
|
+
* Stopped to emit `:text` event after the root element.
|
72
|
+
* GH-167
|
73
|
+
* Patch by NAITOH Jun.
|
74
|
+
|
75
|
+
### Fixes
|
76
|
+
|
77
|
+
* Fixed a bug that SAX2 parser doesn't expand predefined entities for
|
78
|
+
`characters` callback.
|
79
|
+
* GH-168
|
80
|
+
* Patch by NAITOH Jun.
|
81
|
+
|
82
|
+
### Thanks
|
83
|
+
|
84
|
+
* NAITOH Jun
|
85
|
+
|
86
|
+
* Watson
|
87
|
+
|
3
88
|
## 3.3.1 - 2024-06-25 {#version-3-3-1}
|
4
89
|
|
5
90
|
### Improvements
|
@@ -111,7 +111,7 @@ module REXML
|
|
111
111
|
# itself, then we don't need a carriage return... which makes this
|
112
112
|
# logic more complex.
|
113
113
|
node.children.each { |child|
|
114
|
-
next if child
|
114
|
+
next if child.instance_of?(Text)
|
115
115
|
unless child == node.children[0] or child.instance_of?(Text) or
|
116
116
|
(child == node.children[1] and !node.children[0].writethis)
|
117
117
|
output << "\n"
|
@@ -124,11 +124,10 @@ module REXML
|
|
124
124
|
}
|
125
125
|
|
126
126
|
module Private
|
127
|
-
INSTRUCTION_END = /#{NAME}(\s+.*?)?\?>/um
|
128
127
|
TAG_PATTERN = /((?>#{QNAME_STR}))\s*/um
|
129
128
|
CLOSE_PATTERN = /(#{QNAME_STR})\s*>/um
|
130
129
|
ATTLISTDECL_END = /\s+#{NAME}(?:#{ATTDEF})*\s*>/um
|
131
|
-
NAME_PATTERN =
|
130
|
+
NAME_PATTERN = /#{NAME}/um
|
132
131
|
GEDECL_PATTERN = "\\s+#{NAME}\\s+#{ENTITYDEF}\\s*>"
|
133
132
|
PEDECL_PATTERN = "\\s+(%)\\s+#{NAME}\\s+#{PEDEF}\\s*>"
|
134
133
|
ENTITYDECL_PATTERN = /(?:#{GEDECL_PATTERN})|(?:#{PEDECL_PATTERN})/um
|
@@ -146,6 +145,7 @@ module REXML
|
|
146
145
|
self.stream = source
|
147
146
|
@listeners = []
|
148
147
|
@prefixes = Set.new
|
148
|
+
@entity_expansion_count = 0
|
149
149
|
end
|
150
150
|
|
151
151
|
def add_listener( listener )
|
@@ -153,10 +153,12 @@ module REXML
|
|
153
153
|
end
|
154
154
|
|
155
155
|
attr_reader :source
|
156
|
+
attr_reader :entity_expansion_count
|
156
157
|
|
157
158
|
def stream=( source )
|
158
159
|
@source = SourceFactory.create_from( source )
|
159
160
|
@closed = nil
|
161
|
+
@have_root = false
|
160
162
|
@document_status = nil
|
161
163
|
@tags = []
|
162
164
|
@stack = []
|
@@ -239,7 +241,7 @@ module REXML
|
|
239
241
|
if @document_status == nil
|
240
242
|
start_position = @source.position
|
241
243
|
if @source.match("<?", true)
|
242
|
-
return process_instruction
|
244
|
+
return process_instruction
|
243
245
|
elsif @source.match("<!", true)
|
244
246
|
if @source.match("--", true)
|
245
247
|
md = @source.match(/(.*?)-->/um, true)
|
@@ -309,7 +311,11 @@ module REXML
|
|
309
311
|
raise REXML::ParseException.new( "Bad ELEMENT declaration!", @source ) if md.nil?
|
310
312
|
return [ :elementdecl, "<!ELEMENT" + md[1] ]
|
311
313
|
elsif @source.match("ENTITY", true)
|
312
|
-
|
314
|
+
match_data = @source.match(Private::ENTITYDECL_PATTERN, true)
|
315
|
+
unless match_data
|
316
|
+
raise REXML::ParseException.new("Malformed entity declaration", @source)
|
317
|
+
end
|
318
|
+
match = [:entitydecl, *match_data.captures.compact]
|
313
319
|
ref = false
|
314
320
|
if match[1] == '%'
|
315
321
|
ref = true
|
@@ -341,7 +347,7 @@ module REXML
|
|
341
347
|
contents = md[0]
|
342
348
|
|
343
349
|
pairs = {}
|
344
|
-
values = md[0].scan( ATTDEF_RE )
|
350
|
+
values = md[0].strip.scan( ATTDEF_RE )
|
345
351
|
values.each do |attdef|
|
346
352
|
unless attdef[3] == "#IMPLIED"
|
347
353
|
attdef.compact!
|
@@ -435,7 +441,7 @@ module REXML
|
|
435
441
|
raise REXML::ParseException.new( "Declarations can only occur "+
|
436
442
|
"in the doctype declaration.", @source)
|
437
443
|
elsif @source.match("?", true)
|
438
|
-
return process_instruction
|
444
|
+
return process_instruction
|
439
445
|
else
|
440
446
|
# Get the next tag
|
441
447
|
md = @source.match(Private::TAG_PATTERN, true)
|
@@ -460,8 +466,12 @@ module REXML
|
|
460
466
|
@closed = tag
|
461
467
|
@nsstack.shift
|
462
468
|
else
|
469
|
+
if @tags.empty? and @have_root
|
470
|
+
raise ParseException.new("Malformed XML: Extra tag at the end of the document (got '<#{tag}')", @source)
|
471
|
+
end
|
463
472
|
@tags.push( tag )
|
464
473
|
end
|
474
|
+
@have_root = true
|
465
475
|
return [ :start_element, tag, attributes ]
|
466
476
|
end
|
467
477
|
else
|
@@ -469,6 +479,16 @@ module REXML
|
|
469
479
|
if text.chomp!("<")
|
470
480
|
@source.position -= "<".bytesize
|
471
481
|
end
|
482
|
+
if @tags.empty?
|
483
|
+
unless /\A\s*\z/.match?(text)
|
484
|
+
if @have_root
|
485
|
+
raise ParseException.new("Malformed XML: Extra content at the end of the document (got '#{text}')", @source)
|
486
|
+
else
|
487
|
+
raise ParseException.new("Malformed XML: Content at the start of the document (got '#{text}')", @source)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
return pull_event if @have_root
|
491
|
+
end
|
472
492
|
return [ :text, text ]
|
473
493
|
end
|
474
494
|
rescue REXML::UndefinedNamespaceException
|
@@ -486,7 +506,9 @@ module REXML
|
|
486
506
|
def entity( reference, entities )
|
487
507
|
value = nil
|
488
508
|
value = entities[ reference ] if entities
|
489
|
-
if
|
509
|
+
if value
|
510
|
+
record_entity_expansion
|
511
|
+
else
|
490
512
|
value = DEFAULT_ENTITIES[ reference ]
|
491
513
|
value = value[2] if value
|
492
514
|
end
|
@@ -511,7 +533,11 @@ module REXML
|
|
511
533
|
|
512
534
|
# Unescapes all possible entities
|
513
535
|
def unnormalize( string, entities=nil, filter=nil )
|
514
|
-
|
536
|
+
if string.include?("\r")
|
537
|
+
rv = string.gsub( Private::CARRIAGE_RETURN_NEWLINE_PATTERN, "\n" )
|
538
|
+
else
|
539
|
+
rv = string.dup
|
540
|
+
end
|
515
541
|
matches = rv.scan( REFERENCE_RE )
|
516
542
|
return rv if matches.size == 0
|
517
543
|
rv.gsub!( Private::CHARACTER_REFERENCES ) {
|
@@ -521,12 +547,17 @@ module REXML
|
|
521
547
|
}
|
522
548
|
matches.collect!{|x|x[0]}.compact!
|
523
549
|
if matches.size > 0
|
550
|
+
sum = 0
|
524
551
|
matches.each do |entity_reference|
|
525
552
|
unless filter and filter.include?(entity_reference)
|
526
553
|
entity_value = entity( entity_reference, entities )
|
527
554
|
if entity_value
|
528
555
|
re = Private::DEFAULT_ENTITIES_PATTERNS[entity_reference] || /&#{entity_reference};/
|
529
556
|
rv.gsub!( re, entity_value )
|
557
|
+
sum += rv.bytesize
|
558
|
+
if sum > Security.entity_expansion_text_limit
|
559
|
+
raise "entity expansion has grown too large"
|
560
|
+
end
|
530
561
|
else
|
531
562
|
er = DEFAULT_ENTITIES[entity_reference]
|
532
563
|
rv.gsub!( er[0], er[2] ) if er
|
@@ -539,6 +570,14 @@ module REXML
|
|
539
570
|
end
|
540
571
|
|
541
572
|
private
|
573
|
+
|
574
|
+
def record_entity_expansion
|
575
|
+
@entity_expansion_count += 1
|
576
|
+
if @entity_expansion_count > Security.entity_expansion_limit
|
577
|
+
raise "number of entity expansions exceeded, processing aborted."
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
542
581
|
def need_source_encoding_update?(xml_declaration_encoding)
|
543
582
|
return false if xml_declaration_encoding.nil?
|
544
583
|
return false if /\AUTF-16\z/i =~ xml_declaration_encoding
|
@@ -548,14 +587,14 @@ module REXML
|
|
548
587
|
def parse_name(base_error_message)
|
549
588
|
md = @source.match(Private::NAME_PATTERN, true)
|
550
589
|
unless md
|
551
|
-
if @source.match(/\
|
590
|
+
if @source.match(/\S/um)
|
552
591
|
message = "#{base_error_message}: invalid name"
|
553
592
|
else
|
554
593
|
message = "#{base_error_message}: name is missing"
|
555
594
|
end
|
556
595
|
raise REXML::ParseException.new(message, @source)
|
557
596
|
end
|
558
|
-
md[
|
597
|
+
md[0]
|
559
598
|
end
|
560
599
|
|
561
600
|
def parse_id(base_error_message,
|
@@ -624,15 +663,24 @@ module REXML
|
|
624
663
|
end
|
625
664
|
end
|
626
665
|
|
627
|
-
def process_instruction
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
666
|
+
def process_instruction
|
667
|
+
name = parse_name("Malformed XML: Invalid processing instruction node")
|
668
|
+
if @source.match(/\s+/um, true)
|
669
|
+
match_data = @source.match(/(.*?)\?>/um, true)
|
670
|
+
unless match_data
|
671
|
+
raise ParseException.new("Malformed XML: Unclosed processing instruction", @source)
|
672
|
+
end
|
673
|
+
content = match_data[1]
|
674
|
+
else
|
675
|
+
content = nil
|
676
|
+
unless @source.match("?>", true)
|
677
|
+
raise ParseException.new("Malformed XML: Unclosed processing instruction", @source)
|
678
|
+
end
|
633
679
|
end
|
634
|
-
if
|
635
|
-
|
680
|
+
if name == "xml"
|
681
|
+
if @document_status
|
682
|
+
raise ParseException.new("Malformed XML: XML declaration is not at the start", @source)
|
683
|
+
end
|
636
684
|
version = VERSION.match(content)
|
637
685
|
version = version[1] unless version.nil?
|
638
686
|
encoding = ENCODING.match(content)
|
@@ -647,7 +695,7 @@ module REXML
|
|
647
695
|
standalone = standalone[1] unless standalone.nil?
|
648
696
|
return [ :xmldecl, version, encoding, standalone ]
|
649
697
|
end
|
650
|
-
[:processing_instruction,
|
698
|
+
[:processing_instruction, name, content]
|
651
699
|
end
|
652
700
|
|
653
701
|
def parse_attributes(prefixes, curr_ns)
|
@@ -22,6 +22,10 @@ module REXML
|
|
22
22
|
@parser.source
|
23
23
|
end
|
24
24
|
|
25
|
+
def entity_expansion_count
|
26
|
+
@parser.entity_expansion_count
|
27
|
+
end
|
28
|
+
|
25
29
|
def add_listener( listener )
|
26
30
|
@parser.add_listener( listener )
|
27
31
|
end
|
@@ -157,25 +161,8 @@ module REXML
|
|
157
161
|
end
|
158
162
|
end
|
159
163
|
when :text
|
160
|
-
|
161
|
-
|
162
|
-
copy = event[1].clone
|
163
|
-
|
164
|
-
esub = proc { |match|
|
165
|
-
if @entities.has_key?($1)
|
166
|
-
@entities[$1].gsub(Text::REFERENCE, &esub)
|
167
|
-
else
|
168
|
-
match
|
169
|
-
end
|
170
|
-
}
|
171
|
-
|
172
|
-
copy.gsub!( Text::REFERENCE, &esub )
|
173
|
-
copy.gsub!( Text::NUMERICENTITY ) {|m|
|
174
|
-
m=$1
|
175
|
-
m = "0#{m}" if m[0] == ?x
|
176
|
-
[Integer(m)].pack('U*')
|
177
|
-
}
|
178
|
-
handle( :characters, copy )
|
164
|
+
unnormalized = @parser.unnormalize( event[1], @entities )
|
165
|
+
handle( :characters, unnormalized )
|
179
166
|
when :entitydecl
|
180
167
|
handle_entitydecl( event )
|
181
168
|
when :processing_instruction, :comment, :attlistdecl,
|
@@ -36,8 +36,8 @@ module REXML
|
|
36
36
|
@listener.tag_end( event[1] )
|
37
37
|
@tag_stack.pop
|
38
38
|
when :text
|
39
|
-
|
40
|
-
@listener.text(
|
39
|
+
unnormalized = @parser.unnormalize( event[1] )
|
40
|
+
@listener.text( unnormalized )
|
41
41
|
when :processing_instruction
|
42
42
|
@listener.instruction( *event[1,2] )
|
43
43
|
when :start_doctype
|
data/lib/rexml/rexml.rb
CHANGED
data/lib/rexml/source.rb
CHANGED
@@ -204,10 +204,20 @@ module REXML
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
|
207
|
-
def read(term = nil)
|
207
|
+
def read(term = nil, min_bytes = 1)
|
208
208
|
term = encode(term) if term
|
209
209
|
begin
|
210
|
-
|
210
|
+
str = readline(term)
|
211
|
+
@scanner << str
|
212
|
+
read_bytes = str.bytesize
|
213
|
+
begin
|
214
|
+
while read_bytes < min_bytes
|
215
|
+
str = readline(term)
|
216
|
+
@scanner << str
|
217
|
+
read_bytes += str.bytesize
|
218
|
+
end
|
219
|
+
rescue IOError
|
220
|
+
end
|
211
221
|
true
|
212
222
|
rescue Exception, NameError
|
213
223
|
@source = nil
|
@@ -237,10 +247,9 @@ module REXML
|
|
237
247
|
read if @scanner.eos? && @source
|
238
248
|
end
|
239
249
|
|
240
|
-
# Note: When specifying a string for 'pattern', it must not include '>' except in the following formats:
|
241
|
-
# - ">"
|
242
|
-
# - "XXX>" (X is any string excluding '>')
|
243
250
|
def match( pattern, cons=false )
|
251
|
+
# To avoid performance issue, we need to increase bytes to read per scan
|
252
|
+
min_bytes = 1
|
244
253
|
while true
|
245
254
|
if cons
|
246
255
|
md = @scanner.scan(pattern)
|
@@ -250,7 +259,8 @@ module REXML
|
|
250
259
|
break if md
|
251
260
|
return nil if pattern.is_a?(String)
|
252
261
|
return nil if @source.nil?
|
253
|
-
return nil unless read
|
262
|
+
return nil unless read(nil, min_bytes)
|
263
|
+
min_bytes *= 2
|
254
264
|
end
|
255
265
|
|
256
266
|
md.nil? ? nil : @scanner
|
data/lib/rexml/text.rb
CHANGED
@@ -151,25 +151,45 @@ module REXML
|
|
151
151
|
end
|
152
152
|
end
|
153
153
|
|
154
|
-
|
155
|
-
string.
|
156
|
-
if
|
157
|
-
raise "Illegal character #{
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
154
|
+
pos = 0
|
155
|
+
while (index = string.index(/<|&/, pos))
|
156
|
+
if string[index] == "<"
|
157
|
+
raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
|
158
|
+
end
|
159
|
+
|
160
|
+
unless (end_index = string.index(/[^\s];/, index + 1))
|
161
|
+
raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
|
162
|
+
end
|
163
|
+
|
164
|
+
value = string[(index + 1)..end_index]
|
165
|
+
if /\s/.match?(value)
|
166
|
+
raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
|
167
|
+
end
|
168
|
+
|
169
|
+
if value[0] == "#"
|
170
|
+
character_reference = value[1..-1]
|
171
|
+
|
172
|
+
unless (/\A(\d+|x[0-9a-fA-F]+)\z/.match?(character_reference))
|
173
|
+
if character_reference[0] == "x" || character_reference[-1] == "x"
|
174
|
+
raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
|
162
175
|
else
|
163
|
-
raise "Illegal character #{
|
176
|
+
raise "Illegal character #{string.inspect} in raw string #{string.inspect}"
|
164
177
|
end
|
165
|
-
# FIXME: below can't work but this needs API change.
|
166
|
-
# elsif @parent and $3 and !SUBSTITUTES.include?($1)
|
167
|
-
# if !doctype or !doctype.entities.has_key?($3)
|
168
|
-
# raise "Undeclared entity '#{$1}' in raw string \"#{string}\""
|
169
|
-
# end
|
170
178
|
end
|
179
|
+
|
180
|
+
case (character_reference[0] == "x" ? character_reference[1..-1].to_i(16) : character_reference[0..-1].to_i)
|
181
|
+
when *VALID_CHAR
|
182
|
+
else
|
183
|
+
raise "Illegal character #{string.inspect} in raw string #{string.inspect}"
|
184
|
+
end
|
185
|
+
elsif !(/\A#{Entity::NAME}\z/um.match?(value))
|
186
|
+
raise "Illegal character \"#{string[index]}\" in raw string #{string.inspect}"
|
171
187
|
end
|
188
|
+
|
189
|
+
pos = end_index + 1
|
172
190
|
end
|
191
|
+
|
192
|
+
string
|
173
193
|
end
|
174
194
|
|
175
195
|
def node_type
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rexml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.3.
|
4
|
+
version: 3.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kouhei Sutou
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2024-
|
10
|
+
date: 2024-08-01 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: strscan
|
@@ -116,7 +116,7 @@ homepage: https://github.com/ruby/rexml
|
|
116
116
|
licenses:
|
117
117
|
- BSD-2-Clause
|
118
118
|
metadata:
|
119
|
-
changelog_uri: https://github.com/ruby/rexml/releases/tag/v3.3.
|
119
|
+
changelog_uri: https://github.com/ruby/rexml/releases/tag/v3.3.3
|
120
120
|
rdoc_options:
|
121
121
|
- "--main"
|
122
122
|
- README.md
|