rexleparser 0.9.9 → 1.0.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/rexleparser.rb +192 -143
- data.tar.gz.sig +0 -0
- metadata +28 -28
- metadata.gz.sig +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6291ec758a59d1737240656c502a96250c5fe6cdd230a452c220ba34c17f091
|
4
|
+
data.tar.gz: c9edbdc47b4e728598407ddd23da8d5622ed699069061742497d3017477e25bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2665fb624f0b3fee4e4304c304c093900685e6708bfc62209fa54a0f0108e7e195844285fa7a677a91d83f0ee72c306ca8f785052cd7d6c51dba3ab1ea3950b9
|
7
|
+
data.tar.gz: 8c7d22a214d798f867d670c300d2b61c2235a17c437823ac7f5978dcb3c80015d3887dfc2f7f22b421d58d6b3b321eaa327ac931cb0448c06c1d1f71f70640ae
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/rexleparser.rb
CHANGED
@@ -7,20 +7,20 @@
|
|
7
7
|
class Attributes < Hash
|
8
8
|
|
9
9
|
class Value < String
|
10
|
-
|
10
|
+
|
11
11
|
def initialize(value)
|
12
12
|
#jr2020-04-30 super(value.gsub("'", '''))
|
13
13
|
super(value)
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def <(val2)
|
17
17
|
self.to_f < val2.to_f
|
18
|
-
end
|
19
|
-
|
18
|
+
end
|
19
|
+
|
20
20
|
def >(val2)
|
21
21
|
self.to_f > val2.to_f
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def inspect()
|
25
25
|
super().gsub('<','<',).gsub('>','>').gsub('&pos;',"'")
|
26
26
|
end
|
@@ -28,207 +28,256 @@ class Attributes < Hash
|
|
28
28
|
def to_s(unescape: true)
|
29
29
|
unescape ? self.gsub('&','&').gsub('&pos;',"'") : self
|
30
30
|
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
34
|
def initialize(h={})
|
35
35
|
super().merge! h
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def []=(k,v)
|
39
39
|
super(k, k != :class ? Value.new(v) : v)
|
40
40
|
end
|
41
41
|
|
42
|
+
def delete(key=nil)
|
43
|
+
|
44
|
+
if key then
|
45
|
+
super(key)
|
46
|
+
else
|
47
|
+
keys.each {|key| super(key)}
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
42
52
|
def merge(h)
|
43
53
|
|
44
|
-
h2 = h.inject({}) do |r, kv|
|
54
|
+
h2 = h.inject({}) do |r, kv|
|
45
55
|
k, raw_v = kv
|
46
56
|
v = raw_v.is_a?(String) ? Value.new(raw_v) : raw_v
|
47
|
-
r.merge(k => v)
|
57
|
+
r.merge(k => v)
|
48
58
|
end
|
49
|
-
|
59
|
+
|
50
60
|
super(h2)
|
51
|
-
|
61
|
+
|
52
62
|
end
|
53
63
|
end
|
54
64
|
|
65
|
+
|
55
66
|
class RexleParserException < Exception
|
56
67
|
end
|
57
68
|
|
58
69
|
class RexleParser
|
59
70
|
|
60
|
-
attr_reader :
|
61
|
-
|
62
|
-
def initialize(raw_s)
|
71
|
+
attr_reader :stack
|
63
72
|
|
64
|
-
|
65
|
-
s = raw_s.clone.strip
|
66
|
-
return if s.empty?
|
73
|
+
def initialize(raws, debug: false)
|
67
74
|
|
75
|
+
s = raws.strip
|
76
|
+
@debug = debug
|
77
|
+
@a = []
|
78
|
+
@stack = []
|
79
|
+
|
68
80
|
raw_xml, raw_instrctns = if s.lines.first =~ /<?xml/ then
|
69
|
-
s.split(/(
|
81
|
+
s.split(/(?<=\?>)/,2).reverse
|
70
82
|
else
|
71
83
|
s
|
72
84
|
end
|
85
|
+
puts 'raw_xml: ' + raw_xml.inspect if @debug
|
73
86
|
@instructions = raw_instrctns ? \
|
74
87
|
raw_instrctns.scan(/<\?([\w-]+) ([^\?]+)/) : []
|
75
88
|
@doctype = s.slice!(/<!DOCTYPE html>\n?/) if s.lines.first =~ /<\!DOCTYPE/
|
76
|
-
|
89
|
+
|
90
|
+
# scancom is run twice because we 1st check for comment tags and then cdata tags
|
91
|
+
@a = parse(scancom(scancom(raw_xml), type: :cdata)).flatten(1)
|
77
92
|
|
78
93
|
end
|
79
94
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def scan_next(r, tagname)
|
84
|
-
|
85
|
-
j = tagname
|
86
|
-
|
87
|
-
if r[0] == '>' then
|
88
|
-
|
89
|
-
# end tag match
|
90
|
-
tag = r[/^>[^<]+</]
|
91
|
-
|
92
|
-
if tag[1][/[ \w"']/] and tag[-2] != '/' then
|
93
|
-
|
94
|
-
# is it the end tag to match the start tag?
|
95
|
-
tag = r.slice!(/^>[^<]+</)
|
96
|
-
end_tag = tag[/^>[^>]*#{j}<$/]
|
97
|
-
|
98
|
-
if end_tag then
|
99
|
-
|
100
|
-
j = nil
|
101
|
-
return [:end_tag, end_tag]
|
102
|
-
|
103
|
-
elsif tag[/^>[^>]*\w+<$/] then
|
104
|
-
# broken tag found
|
105
|
-
broken_tag = tag
|
106
|
-
return [:child, [nil, [], broken_tag]] if broken_tag
|
107
|
-
else
|
108
|
-
|
109
|
-
text, newtag = tag.sub('>',';tg&').split(/>/,2)
|
110
|
-
|
111
|
-
if newtag then
|
112
|
-
tag = newtag
|
113
|
-
r.prepend '>' + tag
|
114
|
-
end
|
115
|
-
|
116
|
-
return [:child, text]
|
117
|
-
end
|
118
|
-
elsif r[0,3] == '>--' then # comment tag found
|
95
|
+
def to_a()
|
96
|
+
@a
|
97
|
+
end
|
119
98
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
99
|
+
private
|
100
|
+
|
101
|
+
def ehead(raws)
|
102
|
+
|
103
|
+
s = raws.lstrip
|
104
|
+
puts '_s: ' + s.inspect if @debug
|
105
|
+
# fetch the element head
|
106
|
+
tag = s =~ /<[^>]+\/?>/
|
107
|
+
s2 = s[tag+1..-1]
|
108
|
+
tagb = s2 =~ />/
|
109
|
+
return unless tag
|
110
|
+
|
111
|
+
len = tagb+1-tag
|
112
|
+
|
113
|
+
if @debug then
|
114
|
+
puts 'ehead()/detail: ' + [tag, tagb, len, s[tag,len+1]].inspect
|
115
|
+
end
|
124
116
|
|
125
|
-
|
117
|
+
[s[tag,len+1], s[len+1..-1]]
|
126
118
|
|
127
|
-
|
119
|
+
end
|
128
120
|
|
129
|
-
|
121
|
+
def get_attributes(raw_attributes)
|
122
|
+
|
123
|
+
r1 = /([\w\-:\(\)]+\='[^']*)'/
|
124
|
+
r2 = /([\w\-:\(\)]+\="[^"]*)"/
|
125
|
+
|
126
|
+
r = raw_attributes.scan(/#{r1}|#{r2}/).map(&:compact)\
|
127
|
+
.flatten.inject(Attributes.new) do |r, x|
|
128
|
+
attr_name, raw_val = x.split(/=/,2)
|
129
|
+
val = attr_name != 'class' ? raw_val[1..-1] : raw_val[1..-1].split
|
130
|
+
r.merge(attr_name.to_sym => val)
|
131
|
+
end
|
130
132
|
|
131
|
-
|
132
|
-
|
133
|
-
s = r.slice!(0,i)
|
134
|
-
r.slice!(0,9)
|
133
|
+
return r
|
134
|
+
end
|
135
135
|
|
136
|
-
|
136
|
+
def parse(raws, a=[], cur=nil)
|
137
137
|
|
138
|
-
|
138
|
+
s = raws #.lstrip
|
139
|
+
|
140
|
+
if @debug then
|
141
|
+
puts '.parse() s: ' + s.inspect[0..600]
|
142
|
+
puts '.parse() a: ' + a.inspect[0..699]
|
143
|
+
puts '.parse() cur: ' + cur.inspect[0..799]
|
144
|
+
end
|
139
145
|
|
140
|
-
|
146
|
+
# if self-closing tag
|
147
|
+
if s =~ /^<[^<]+\/>/ then
|
141
148
|
|
142
|
-
|
149
|
+
tag = s[/^<[^<]+\/>/]
|
150
|
+
puts 'parse() self-closing/tag: ' + tag.inspect if @debug
|
151
|
+
tail = $'
|
152
|
+
|
153
|
+
if @debug then
|
154
|
+
puts 'parse() self-closing tag found'
|
155
|
+
puts 'parse()/tail: ' + tail.inspect
|
156
|
+
end
|
157
|
+
|
158
|
+
a2 = parsetag(tag)
|
159
|
+
puts '_a: ' + a.inspect if @debug
|
160
|
+
cur ? a.last << a2 : a << a2
|
161
|
+
|
162
|
+
parse(tail, a, cur)
|
163
|
+
|
164
|
+
# is it the head?
|
165
|
+
elsif (s =~ /^<[^\/>]+>/) == 0 then
|
166
|
+
|
167
|
+
puts 'parse()/head found' if @debug
|
168
|
+
|
169
|
+
tag, tail = ehead(s)
|
170
|
+
|
171
|
+
if @debug then
|
172
|
+
puts 'parse() tag: ' + tag.inspect
|
173
|
+
puts 'parse() tail: ' + tail.inspect
|
174
|
+
end
|
175
|
+
# add it to the list
|
176
|
+
a2 = parsetag(tag)
|
143
177
|
|
178
|
+
puts '_cur: ' + cur.inspect if @debug
|
179
|
+
if cur then
|
180
|
+
cur << a2
|
181
|
+
cur2 = cur.last
|
144
182
|
else
|
183
|
+
a << a2
|
184
|
+
cur2 = a.last
|
185
|
+
end
|
145
186
|
|
146
|
-
|
147
|
-
i = r =~ />(?:[\-\/"'\w]|\]\])/ # collect until a tag is found or a CDATA element
|
148
|
-
text = r.slice!(0,i)
|
149
|
-
|
150
|
-
return [:child, text] if text
|
151
|
-
|
152
|
-
end # end of tag match
|
153
|
-
|
154
|
-
else
|
155
|
-
|
156
|
-
# it's a text value
|
157
|
-
i = r =~ />(?:[\-\/"'\w]|\]\])/ # collect until a tag is found or a CDATA element
|
158
|
-
text = r.slice!(0,i)
|
159
|
-
|
160
|
-
return [:child, text] if text
|
161
|
-
end
|
162
|
-
end
|
187
|
+
puts '_a: ' + a.inspect if @debug
|
163
188
|
|
164
|
-
|
189
|
+
# add it to the stack
|
190
|
+
@stack.push cur2
|
165
191
|
|
166
|
-
|
167
|
-
|
168
|
-
|
192
|
+
parse(tail, a, cur2)
|
193
|
+
|
194
|
+
elsif (s =~ /^[^<]/) == 0
|
169
195
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
196
|
+
puts 'parse() we have text!' if @debug
|
197
|
+
text = raws[/[^<]+/m] #
|
198
|
+
remaining = $'
|
199
|
+
|
200
|
+
if @debug then
|
201
|
+
puts 'parse() text: ' + text.inspect
|
202
|
+
puts 'cur tag: ' + cur[0].inspect
|
203
|
+
end
|
204
|
+
|
205
|
+
cur << if cur[0][0] == '!' then
|
206
|
+
text.gsub('<','<').gsub('>','>').gsub('&','&')
|
207
|
+
else
|
208
|
+
text.gsub(/>/,'>').gsub(/</, '<')
|
209
|
+
end
|
174
210
|
|
175
|
-
|
211
|
+
puts 'remaining: ' + remaining.inspect if @debug
|
212
|
+
parse(remaining, a, cur) if remaining.length > 0
|
213
|
+
|
214
|
+
|
215
|
+
# is it a closing tag?
|
216
|
+
elsif s =~ /^\s?<\/\w+>/m
|
217
|
+
|
218
|
+
tail = s[/^\s*<\/\w+>(.*)/m,1]
|
219
|
+
|
220
|
+
if @debug then
|
221
|
+
puts 'parse()/closing tag ' + s[/^\s*<\/\w+>/].inspect
|
222
|
+
puts '>a: ' + a.inspect
|
223
|
+
end
|
224
|
+
|
225
|
+
@stack.pop
|
226
|
+
#a << []
|
227
|
+
parse(tail, a, @stack.last)
|
228
|
+
|
229
|
+
elsif s.empty? and @stack.length > 0
|
230
|
+
|
231
|
+
puts 'parse() no end tag!' if @debug
|
176
232
|
|
177
|
-
unless start_tag[1..-3][/\w+$/] then
|
178
|
-
raise RexleParserException, 'invalid closing tag found ' + \
|
179
|
-
start_tag.reverse + '; context: ' + r[0..120].reverse.inspect
|
180
233
|
end
|
181
234
|
|
182
|
-
|
183
|
-
|
184
|
-
key, res = scan_next r, tagname
|
185
|
-
|
186
|
-
case key
|
187
|
-
when :end_tag
|
188
|
-
end_tag = res
|
189
|
-
r2 = [start_tag, children, end_tag]
|
190
|
-
end_tag = nil
|
191
|
-
|
192
|
-
return r2
|
193
|
-
when :child
|
194
|
-
children << res
|
195
|
-
when :newnode
|
196
|
-
children << parse_node(r, tagname)
|
197
|
-
else
|
198
|
-
break
|
199
|
-
end
|
200
|
-
end
|
235
|
+
return a
|
201
236
|
|
202
|
-
[start_tag, children, end_tag]
|
203
237
|
end
|
204
238
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
239
|
+
# we parse the tag because it contains more than just the name it often contains attributes
|
240
|
+
#
|
241
|
+
def parsetag(s)
|
242
|
+
|
243
|
+
puts 'parsetag:' + s.inspect if @debug
|
244
|
+
rawtagname, rawattr = s[1..-2].sub(/\/$/,'').match(/^(\w+) *(.*)/)\
|
245
|
+
.values_at(1,2)
|
246
|
+
|
247
|
+
tagname = case rawtagname.to_sym
|
248
|
+
when :_comment
|
249
|
+
'!-'
|
250
|
+
when :_cdata
|
251
|
+
'!['
|
252
|
+
else
|
253
|
+
rawtagname
|
215
254
|
end
|
216
255
|
|
217
|
-
|
256
|
+
[tagname, get_attributes(rawattr)]
|
218
257
|
end
|
219
258
|
|
220
|
-
def
|
259
|
+
def scancom(s, type=:comment)
|
221
260
|
|
222
|
-
|
223
|
-
|
224
|
-
|
261
|
+
tag1 = ['<!--', '-->', 'comment', '<!--']
|
262
|
+
tag2 = ['<![CDATA[', '\]\]>', 'cdata', '\<!\[CDATA\[']
|
263
|
+
tag = type == :comment ? tag1 : tag2
|
225
264
|
|
226
|
-
tag
|
265
|
+
#puts 'tag: ' + tag.inspect
|
266
|
+
istart = s =~ /#{tag[3]}/
|
267
|
+
return s unless istart
|
227
268
|
|
228
|
-
|
269
|
+
iend = s =~ /#{tag[1]}/
|
270
|
+
comment ="<_%s>%s</_%s>" % [tag[2], s[istart+tag[0].length.. iend-1].gsub('&','&').gsub('<','<').gsub('>','>'), tag[2]]
|
229
271
|
|
230
|
-
|
272
|
+
if @debug then
|
273
|
+
puts 'comment: ' + comment.inspect
|
274
|
+
# construct the new string
|
275
|
+
puts 'istart: ' + istart.inspect
|
276
|
+
end
|
277
|
+
|
278
|
+
s3 = s[0,istart].to_s + comment + s[iend+3..-1]
|
279
|
+
scancom(s3, type)
|
231
280
|
|
232
|
-
return [tag[/[!\-\w:\[]+/], get_attributes(tag), *r]
|
233
281
|
end
|
282
|
+
|
234
283
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rexleparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Robertson
|
@@ -10,32 +10,33 @@ bindir: bin
|
|
10
10
|
cert_chain:
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
13
|
+
MIIEljCCAv6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBIMRIwEAYDVQQDDAlnZW1t
|
14
|
+
YXN0ZXIxHjAcBgoJkiaJk/IsZAEZFg5qYW1lc3JvYmVydHNvbjESMBAGCgmSJomT
|
15
|
+
8ixkARkWAmV1MB4XDTIzMDMwNzEyNDM0MVoXDTI0MDMwNjEyNDM0MVowSDESMBAG
|
16
|
+
A1UEAwwJZ2VtbWFzdGVyMR4wHAYKCZImiZPyLGQBGRYOamFtZXNyb2JlcnRzb24x
|
17
|
+
EjAQBgoJkiaJk/IsZAEZFgJldTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
18
|
+
ggGBAMXdVvebuQa1+janwRx6yjABUKs2WSd6ns81LBol0KgH8Lmjj5CdIJHK/IFZ
|
19
|
+
pcjvbJCSNJS9eREO4RnHkJTUpYE6xgTboCsSMdTpJU3MK2Y+PHXQu5YJHBQQBWSe
|
20
|
+
LORpuKuhQuhU+oQgxnuszksIO1UBU+Xh0D5dntbWpiFBGPzTctoBTtJqBdClZwXc
|
21
|
+
s1mAmXhAkeK2hmT0Rw/IY2CqZAMeMbrVZBaqazYvqXvfDisRPMMZVZMz9al3w6IE
|
22
|
+
L9E4tDbU1sExjUgVGB+BIV6SIG5kYrOzpDKnZXhvPbmUR08iZeTe0IpUIFMIYPIy
|
23
|
+
kPJxO45OaxLwnabV+jC38P2CV4Pbx6dij/M/mWisD/az4kzmw1kGUMGJiPIn/XRX
|
24
|
+
mLOOxuCQxHDts+7tvD+/wTtSIxklsvVKz49QH1ybNrOdoYQuB8qNnLPFzKFr+5SC
|
25
|
+
ojuaJ45mf/Uv3Orps8LXj8WmOBvbJWC1/Lglhy+hhGV/7gh1EKVMpx6AwKcJ1+85
|
26
|
+
R19PMQIDAQABo4GKMIGHMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
27
|
+
BBQiPSrne0dHxLynj/1izBmEwv/lkTAmBgNVHREEHzAdgRtnZW1tYXN0ZXJAamFt
|
28
|
+
ZXNyb2JlcnRzb24uZXUwJgYDVR0SBB8wHYEbZ2VtbWFzdGVyQGphbWVzcm9iZXJ0
|
29
|
+
c29uLmV1MA0GCSqGSIb3DQEBCwUAA4IBgQBWIQG28MQXuVSHsAw/rQuaE1FpOmz0
|
30
|
+
AXWhHK1oDWxkoPTBIoLil2M2PI3htcBBRLUzyli9XFvqjzuO+J8LpWqs2iddxjXP
|
31
|
+
3xtUg0h+0urVRk00xwsnPLppwT7VxvycQifN31En8rMEzd24V5FKkn90brdokVQC
|
32
|
+
5aL/9LD3S5k3t4AyUcGeOFuU+k87lEz8bzKk2wjOjzpgxjNyqeK6h0iFeA94rhnp
|
33
|
+
HPaebr7ytgAR3dKU/Zr/gmZdQroli86LaOqGK1AJQ9E1RFBKwKluNa27dR3VyqBA
|
34
|
+
VG1Z/9QNxm0ivqKr7samwkNUGiql+s4CPZndbJD/hmDDdmCtYf1mywCAUOh+DrfU
|
35
|
+
iKwWhO5Qmo/RUOvr0a4KtP5i0N7qY1LFMSCpfCRTS9zTlfTIjs1ipAnWhLcqp5Js
|
36
|
+
NMSYHad41dFYLrVyE7mdZdIVlFqIdG+V1x7iY+zPpg7FS/8EUnK5zUxMC0YoLeK2
|
37
|
+
hcHycb1NL9Ujb7onXEop5Dobym6xO6V+yBY=
|
37
38
|
-----END CERTIFICATE-----
|
38
|
-
date:
|
39
|
+
date: 2023-03-07 00:00:00.000000000 Z
|
39
40
|
dependencies: []
|
40
41
|
description:
|
41
42
|
email: digital.robertson@gmail.com
|
@@ -63,8 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
64
|
- !ruby/object:Gem::Version
|
64
65
|
version: '0'
|
65
66
|
requirements: []
|
66
|
-
|
67
|
-
rubygems_version: 2.7.10
|
67
|
+
rubygems_version: 3.4.6
|
68
68
|
signing_key:
|
69
69
|
specification_version: 4
|
70
70
|
summary: Rexleparser is an XML parser used by the Rexle gem
|
metadata.gz.sig
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
��
|
2
|
-
�
|
3
|
-
|
1
|
+
,����<�a��1�����$n�]��ᆧAWεp����J���z{�0aM��p�٥tP�u���,k���l�ygܖ|8��P�t"}����ej
|
2
|
+
+֢��PJ{ݍ��}ׄ��vj� �a�m,�G��]��ӡ� ���h&r����C*�r��3I=�i��O�ɂ�7�]~��?����CH��Z��j���*�9���ե�ӱM��R+��a����n�3ͣJ��Lm�,��mU&���2>@G���a�OQ�0�b�&��%�.��
|
3
|
+
|d�!lDO#�3{�oV��+�(��7<j��pc�"�z���֤�ϗ'�m�si�0�}��y��eP1�:���Dcյ���6=�R,�s�{�7�d}�jj�h�f�j��
|