relaton-bib 2.0.0.pre.alpha.1 → 2.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
- data/.gitignore +3 -1
- data/.rubocop.yml +1 -1
- data/CLAUDE.md +96 -0
- data/Gemfile +1 -0
- data/README.adoc +14 -2
- data/grammars/basicdoc.rng +75 -45
- data/grammars/biblio-standoc.rng +37 -56
- data/grammars/biblio.rng +68 -36
- data/grammars/versions.json +25 -25
- data/lib/relaton/bib/converter/asciibib/to_asciibib.rb +663 -0
- data/lib/relaton/bib/converter/asciibib.rb +13 -0
- data/lib/relaton/bib/converter/bibtex/from_bibtex.rb +245 -0
- data/lib/relaton/bib/converter/bibtex/to_bibtex.rb +341 -0
- data/lib/relaton/bib/converter/bibtex.rb +23 -0
- data/lib/relaton/bib/converter/bibxml/from_rfcxml.rb +383 -0
- data/lib/relaton/bib/converter/bibxml/from_rfcxml_referencegroup.rb +71 -0
- data/lib/relaton/bib/{renderer/rfc.rb → converter/bibxml/to_rfcxml.rb} +100 -92
- data/lib/relaton/bib/converter/bibxml/to_rfcxml_referencegroup.rb +52 -0
- data/lib/relaton/bib/converter/bibxml.rb +51 -0
- data/lib/relaton/bib/hash_parser_v1.rb +121 -78
- data/lib/relaton/bib/item_data.rb +83 -22
- data/lib/relaton/bib/model/abstract.rb +16 -0
- data/lib/relaton/bib/model/date.rb +6 -4
- data/lib/relaton/bib/model/depiction.rb +2 -0
- data/lib/relaton/bib/model/ext.rb +0 -3
- data/lib/relaton/bib/model/formattedref.rb +43 -0
- data/lib/relaton/bib/model/ics.rb +9 -0
- data/lib/relaton/bib/model/item.rb +12 -4
- data/lib/relaton/bib/model/item_base.rb +17 -9
- data/lib/relaton/bib/model/keyword.rb +3 -3
- data/lib/relaton/bib/model/logo.rb +2 -0
- data/lib/relaton/bib/model/organization_type.rb +1 -1
- data/lib/relaton/bib/model/relation.rb +2 -2
- data/lib/relaton/bib/model/series.rb +5 -3
- data/lib/relaton/bib/model/subdivision.rb +2 -0
- data/lib/relaton/bib/model/title.rb +38 -0
- data/lib/relaton/bib/model/type/string_date.rb +48 -0
- data/lib/relaton/bib/model/validity.rb +3 -3
- data/lib/relaton/bib/namespace_helper.rb +21 -0
- data/lib/relaton/bib/version.rb +1 -1
- data/lib/relaton/bib.rb +5 -82
- data/relaton-bib.gemspec +5 -2
- metadata +63 -29
- data/lib/relaton/bib/model/editorial_group.rb +0 -14
- data/lib/relaton/bib/model/technical_committee.rb +0 -14
- data/lib/relaton/bib/model/workgroup.rb +0 -20
- data/lib/relaton/bib/parser/rfc_address.rb +0 -60
- data/lib/relaton/bib/parser/rfc_contacts.rb +0 -35
- data/lib/relaton/bib/parser/rfc_docidentifier.rb +0 -70
- data/lib/relaton/bib/parser/rfc_organization.rb +0 -31
- data/lib/relaton/bib/parser/rfc_person.rb +0 -42
- data/lib/relaton/bib/parser/rfc_reference.rb +0 -143
- data/lib/relaton/bib/parser/rfc_referencegroup.rb +0 -45
- data/lib/relaton/bib/parser/rfc_shared.rb +0 -23
- data/lib/relaton/bib/renderer/bibtex_builder.rb +0 -348
- data/lib/relaton/bibtex_parser.rb +0 -243
- data/lib/relaton/bibxml_parser.rb +0 -419
- data/lib/relaton/deep_dup.rb +0 -30
- data/lib/relaton/hit.rb +0 -52
- data/lib/relaton/hit_collection.rb +0 -105
- data/lib/relaton/workers_pool.rb +0 -43
- data/lib/relaton/xml_parser.rb +0 -716
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
module Relaton
|
|
2
2
|
module Bib
|
|
3
|
-
module
|
|
4
|
-
module
|
|
5
|
-
|
|
6
|
-
if item.docnumber&.match(/^BCP/) ||
|
|
7
|
-
(item.docidentifier.detect(&:primary) || item.docidentifier[0])&.content&.include?("BCP")
|
|
8
|
-
Refrencegroup.new(item, include_keywords: include_keywords).transform
|
|
9
|
-
else
|
|
10
|
-
Reference.new(item, include_keywords: include_keywords).transform
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
module Shared
|
|
3
|
+
module Converter
|
|
4
|
+
module BibXml
|
|
5
|
+
class ToRfcxml
|
|
15
6
|
def initialize(item, include_keywords: true)
|
|
16
7
|
@item = item
|
|
17
8
|
@include_keywords = include_keywords
|
|
18
9
|
end
|
|
19
10
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
def transform
|
|
12
|
+
model = ::Rfcxml::V3::Reference.new
|
|
13
|
+
model.anchor = @item.docnumber || derive_anchor
|
|
14
|
+
model.target = create_target
|
|
15
|
+
model.front = create_front
|
|
16
|
+
model.format = create_format
|
|
17
|
+
model
|
|
18
|
+
end
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def derive_anchor
|
|
23
|
+
di = @item.docidentifier.detect(&:primary) || @item.docidentifier[0]
|
|
24
|
+
di&.content&.to_s&.gsub(" ", ".")
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def create_target
|
|
@@ -31,46 +31,10 @@ module Relaton
|
|
|
31
31
|
|
|
32
32
|
target.content.to_s
|
|
33
33
|
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
class Refrencegroup
|
|
37
|
-
include Shared
|
|
38
|
-
|
|
39
|
-
def transform
|
|
40
|
-
reference = @item.relation&.each_with_object([]) do |rel, refs|
|
|
41
|
-
next unless rel.type == "includes"
|
|
42
|
-
|
|
43
|
-
refs << Reference.new(rel.bibitem, include_keywords: @include_keywords).transform
|
|
44
|
-
end
|
|
45
|
-
Rfcxml::V3::Referencegroup.new(
|
|
46
|
-
anchor: create_anchor,
|
|
47
|
-
target: create_target,
|
|
48
|
-
reference: reference,
|
|
49
|
-
)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
class Reference
|
|
54
|
-
include Shared
|
|
55
|
-
|
|
56
|
-
ORGNAMES = {
|
|
57
|
-
"IEEE" => "Institute of Electrical and Electronics Engineers",
|
|
58
|
-
"W3C" => "World Wide Web Consortium",
|
|
59
|
-
"3GPP" => "3rd Generation Partnership Project",
|
|
60
|
-
}.freeze
|
|
61
|
-
|
|
62
|
-
def transform
|
|
63
|
-
model = ::Rfcxml::V3::Reference.new
|
|
64
|
-
model.anchor = create_anchor
|
|
65
|
-
model.target = create_target
|
|
66
|
-
model.front = create_front
|
|
67
|
-
model.format = create_format
|
|
68
|
-
model
|
|
69
|
-
end
|
|
70
34
|
|
|
71
35
|
def create_front # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
72
36
|
front = ::Rfcxml::V3::Front.new
|
|
73
|
-
front.title = Rfcxml::V3::Title.new(content: @item.title[0].content)
|
|
37
|
+
front.title = Rfcxml::V3::Title.new(content: @item.title[0].content) if @item.title.any?
|
|
74
38
|
front.series_info = create_seriesinfo
|
|
75
39
|
front.author = create_authors
|
|
76
40
|
front.date = create_date
|
|
@@ -81,13 +45,14 @@ module Relaton
|
|
|
81
45
|
end
|
|
82
46
|
|
|
83
47
|
def create_seriesinfo
|
|
84
|
-
|
|
48
|
+
docidentifier_to_seriesinfo + series_to_seriesinfo
|
|
85
49
|
end
|
|
86
50
|
|
|
87
|
-
def
|
|
51
|
+
def docidentifier_to_seriesinfo
|
|
88
52
|
@item.docidentifier.each_with_object([]) do |di, si|
|
|
89
53
|
if di.type == "DOI" && di.scope != "trademark"
|
|
90
|
-
si << Rfcxml::V3::SeriesInfo.new(name: di.type,
|
|
54
|
+
si << Rfcxml::V3::SeriesInfo.new(name: di.type,
|
|
55
|
+
value: di.content)
|
|
91
56
|
end
|
|
92
57
|
end
|
|
93
58
|
end
|
|
@@ -99,12 +64,13 @@ module Relaton
|
|
|
99
64
|
s.title.find { |t| t.content != "DOI" }.content
|
|
100
65
|
end.each_with_object([]) do |s, si|
|
|
101
66
|
title = s.title.find { |t| t.content != "DOI" }
|
|
102
|
-
si << Rfcxml::V3::SeriesInfo.new(name: title.content,
|
|
67
|
+
si << Rfcxml::V3::SeriesInfo.new(name: title.content,
|
|
68
|
+
value: s.number)
|
|
103
69
|
end
|
|
104
70
|
end
|
|
105
71
|
|
|
106
72
|
def create_authors # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
107
|
-
@item.contributor.map do |contrib|
|
|
73
|
+
@item.contributor.reject { |c| committee_contributor?(c) }.map do |contrib|
|
|
108
74
|
role = "editor" if contrib.role.detect { |r| r.type == "editor" }
|
|
109
75
|
Rfcxml::V3::Author.new(
|
|
110
76
|
role: role,
|
|
@@ -123,9 +89,7 @@ module Relaton
|
|
|
123
89
|
if person.name.completename
|
|
124
90
|
person.name.completename.content
|
|
125
91
|
elsif person.name.forename.any?
|
|
126
|
-
parts = person.name.forename.map
|
|
127
|
-
n.content || n.initial
|
|
128
|
-
end
|
|
92
|
+
parts = person.name.forename.map { |n| n.content || n.initial }
|
|
129
93
|
parts << person.name.surname.content if person.name.surname
|
|
130
94
|
parts.join(" ")
|
|
131
95
|
end
|
|
@@ -137,7 +101,9 @@ module Relaton
|
|
|
137
101
|
if person.name.formatted_initials
|
|
138
102
|
person.name.formatted_initials.content
|
|
139
103
|
elsif person.name.forename.any?
|
|
140
|
-
person.name.forename.map
|
|
104
|
+
person.name.forename.map do |f|
|
|
105
|
+
"#{f.initial || f.content[0]}."
|
|
106
|
+
end.join " "
|
|
141
107
|
end
|
|
142
108
|
end
|
|
143
109
|
|
|
@@ -147,21 +113,33 @@ module Relaton
|
|
|
147
113
|
person.name.surname&.content
|
|
148
114
|
end
|
|
149
115
|
|
|
150
|
-
def create_organization(contrib
|
|
151
|
-
org = contrib
|
|
116
|
+
def create_organization(contrib) # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity,Metrics/AbcSize
|
|
117
|
+
org = contrib_org(contrib)
|
|
152
118
|
return unless org
|
|
153
119
|
|
|
154
120
|
abbrev = org.abbreviation&.content
|
|
121
|
+
orgname = resolve_orgname(org, abbrev)
|
|
122
|
+
Rfcxml::V3::Organization.new(
|
|
123
|
+
content: orgname, abbrev: abbrev,
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def contrib_org(contrib)
|
|
128
|
+
contrib.organization ||
|
|
129
|
+
(contrib.person && contrib.person.affiliation[0]&.organization)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def resolve_orgname(org, abbrev)
|
|
155
133
|
orgname = org.name&.first&.content
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
Rfcxml::V3::Organization.new(content: orgname, abbrev: abbrev)
|
|
134
|
+
if ORGNAMES.key?(abbrev) then abbrev
|
|
135
|
+
else ORGNAMES.key(orgname) || orgname || abbrev
|
|
136
|
+
end
|
|
160
137
|
end
|
|
161
138
|
|
|
162
139
|
def create_address(contrib) # rubocop:disable Metrics/AbcSize
|
|
163
140
|
entity = contrib.person || contrib.organization
|
|
164
|
-
return unless entity.address.any? || entity.phone.any? ||
|
|
141
|
+
return unless entity.address.any? || entity.phone.any? ||
|
|
142
|
+
entity.email.any? || entity.uri.any?
|
|
165
143
|
|
|
166
144
|
Rfcxml::V3::Address.new(
|
|
167
145
|
postal: address_postal(entity),
|
|
@@ -190,26 +168,34 @@ module Relaton
|
|
|
190
168
|
end
|
|
191
169
|
|
|
192
170
|
def address_cities(entity)
|
|
193
|
-
entity.address.each_with_object([]) do |
|
|
194
|
-
|
|
171
|
+
entity.address.each_with_object([]) do |addr, cities|
|
|
172
|
+
next unless addr.city
|
|
173
|
+
|
|
174
|
+
cities << Rfcxml::V3::City.new(content: addr.city)
|
|
195
175
|
end
|
|
196
176
|
end
|
|
197
177
|
|
|
198
178
|
def address_postcodes(entity)
|
|
199
|
-
entity.address.each_with_object([]) do |
|
|
200
|
-
|
|
179
|
+
entity.address.each_with_object([]) do |addr, codes|
|
|
180
|
+
next unless addr.postcode
|
|
181
|
+
|
|
182
|
+
codes << Rfcxml::V3::Code.new(content: addr.postcode)
|
|
201
183
|
end
|
|
202
184
|
end
|
|
203
185
|
|
|
204
186
|
def address_countries(entity)
|
|
205
|
-
entity.address.each_with_object([]) do |
|
|
206
|
-
|
|
187
|
+
entity.address.each_with_object([]) do |addr, countries|
|
|
188
|
+
next unless addr.country
|
|
189
|
+
|
|
190
|
+
countries << Rfcxml::V3::Country.new(content: addr.country)
|
|
207
191
|
end
|
|
208
192
|
end
|
|
209
193
|
|
|
210
194
|
def address_states(entity)
|
|
211
|
-
entity.address.each_with_object([]) do |
|
|
212
|
-
|
|
195
|
+
entity.address.each_with_object([]) do |addr, states|
|
|
196
|
+
next unless addr.state
|
|
197
|
+
|
|
198
|
+
states << Rfcxml::V3::Region.new(content: addr.state)
|
|
213
199
|
end
|
|
214
200
|
end
|
|
215
201
|
|
|
@@ -222,8 +208,12 @@ module Relaton
|
|
|
222
208
|
end
|
|
223
209
|
|
|
224
210
|
def address_postal_lines(entity)
|
|
225
|
-
entity.address.each_with_object([]) do |
|
|
226
|
-
|
|
211
|
+
entity.address.each_with_object([]) do |addr, plines|
|
|
212
|
+
next unless addr.formatted_address
|
|
213
|
+
|
|
214
|
+
plines << Rfcxml::V3::PostalLine.new(
|
|
215
|
+
content: addr.formatted_address,
|
|
216
|
+
)
|
|
227
217
|
end
|
|
228
218
|
end
|
|
229
219
|
|
|
@@ -259,35 +249,53 @@ module Relaton
|
|
|
259
249
|
Rfcxml::V3::Date.new(**args)
|
|
260
250
|
end
|
|
261
251
|
|
|
252
|
+
def committee_contributor?(contrib)
|
|
253
|
+
contrib.role.any? do |r|
|
|
254
|
+
r.type == "author" &&
|
|
255
|
+
r.description.any? { |d| d.content == "committee" }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
262
259
|
def create_workgroup
|
|
263
|
-
@item.
|
|
264
|
-
|
|
260
|
+
@item.contributor.each_with_object([]) do |contrib, wgs|
|
|
261
|
+
next unless committee_contributor?(contrib)
|
|
262
|
+
|
|
263
|
+
contrib.organization&.subdivision&.each do |sd|
|
|
264
|
+
sd.name.each do |n|
|
|
265
|
+
wgs << Rfcxml::V3::Workgroup.new(content: n.content)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
265
268
|
end
|
|
266
269
|
end
|
|
267
270
|
|
|
268
271
|
def create_keyword
|
|
269
272
|
@item.keyword.inject([]) do |a, k|
|
|
270
|
-
|
|
273
|
+
if k.vocab
|
|
274
|
+
a + [Rfcxml::V3::Keyword.new(content: k.vocab.content)]
|
|
275
|
+
else
|
|
276
|
+
a + k.taxon.map { |t| Rfcxml::V3::Keyword.new(content: t.content) }
|
|
277
|
+
end
|
|
271
278
|
end
|
|
272
279
|
end
|
|
273
280
|
|
|
274
281
|
def create_abstract
|
|
275
282
|
return unless @item.abstract.any?
|
|
276
283
|
|
|
277
|
-
|
|
284
|
+
content = @item.abstract[0].content
|
|
285
|
+
Rfcxml::V3::Abstract.new(
|
|
286
|
+
t: [Rfcxml::V3::Text.new(content: content)],
|
|
287
|
+
)
|
|
278
288
|
end
|
|
279
289
|
|
|
280
|
-
|
|
281
|
-
Rfcxml::V3::Text.new content: @item.abstract[0].content
|
|
282
|
-
end
|
|
290
|
+
FORMAT_TYPES = %w[TXT HTML PDF XML DOC].freeze
|
|
283
291
|
|
|
284
|
-
def create_format
|
|
285
|
-
@item.source.
|
|
286
|
-
next
|
|
287
|
-
l.type.casecmp("PDF").zero? || l.type.casecmp("XML").zero? ||
|
|
288
|
-
l.type.casecmp("DOC").zero?
|
|
292
|
+
def create_format # rubocop:disable Metrics/AbcSize
|
|
293
|
+
@item.source.each_with_object([]) do |l, a|
|
|
294
|
+
next unless FORMAT_TYPES.any? { |ft| l.type.casecmp(ft).zero? }
|
|
289
295
|
|
|
290
|
-
a << Rfcxml::V3::Format.new(
|
|
296
|
+
a << Rfcxml::V3::Format.new(
|
|
297
|
+
type: l.type, target: l.content,
|
|
298
|
+
)
|
|
291
299
|
end
|
|
292
300
|
end
|
|
293
301
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Bib
|
|
3
|
+
module Converter
|
|
4
|
+
module BibXml
|
|
5
|
+
class ToRfcxmlReferencegroup
|
|
6
|
+
def initialize(item, include_keywords: true)
|
|
7
|
+
@item = item
|
|
8
|
+
@include_keywords = include_keywords
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def transform
|
|
12
|
+
Rfcxml::V3::Referencegroup.new(
|
|
13
|
+
anchor: create_anchor,
|
|
14
|
+
target: create_target,
|
|
15
|
+
reference: build_references,
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def build_references
|
|
22
|
+
@item.relation&.each_with_object([]) do |rel, refs|
|
|
23
|
+
next unless rel.type == "includes"
|
|
24
|
+
|
|
25
|
+
converter = ToRfcxml.new(
|
|
26
|
+
rel.bibitem, include_keywords: @include_keywords
|
|
27
|
+
)
|
|
28
|
+
refs << converter.transform
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_anchor
|
|
33
|
+
docid = @item.docidentifier.detect(&:primary) ||
|
|
34
|
+
@item.docidentifier[0]
|
|
35
|
+
return unless docid
|
|
36
|
+
|
|
37
|
+
id = docid.content.to_s
|
|
38
|
+
id.sub(/^(RFC|BCP|FYI|STD) /, '\1').sub(/^\w+\./, "")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_target
|
|
42
|
+
target = @item.source.detect { |l| l.type.casecmp("src").zero? } ||
|
|
43
|
+
@item.source.detect { |l| l.type.casecmp("doi").zero? }
|
|
44
|
+
return unless target
|
|
45
|
+
|
|
46
|
+
target.content.to_s
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require_relative "bibxml/to_rfcxml"
|
|
2
|
+
require_relative "bibxml/to_rfcxml_referencegroup"
|
|
3
|
+
require_relative "bibxml/from_rfcxml"
|
|
4
|
+
require_relative "bibxml/from_rfcxml_referencegroup"
|
|
5
|
+
|
|
6
|
+
module Relaton
|
|
7
|
+
module Bib
|
|
8
|
+
module Converter
|
|
9
|
+
module BibXml
|
|
10
|
+
ORGNAMES = {
|
|
11
|
+
"IEEE" => "Institute of Electrical and Electronics Engineers",
|
|
12
|
+
"W3C" => "World Wide Web Consortium",
|
|
13
|
+
"3GPP" => "3rd Generation Partnership Project",
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
RFCPREFIXES = %w[RFC BCP FYI STD].freeze
|
|
17
|
+
|
|
18
|
+
# Forward: ItemData -> Rfcxml model
|
|
19
|
+
def self.from_item(item, include_keywords: true) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
20
|
+
if bcp?(item)
|
|
21
|
+
ToRfcxmlReferencegroup.new(
|
|
22
|
+
item, include_keywords: include_keywords
|
|
23
|
+
).transform
|
|
24
|
+
else
|
|
25
|
+
ToRfcxml.new(
|
|
26
|
+
item, include_keywords: include_keywords
|
|
27
|
+
).transform
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.bcp?(item) # rubocop:disable Metrics/CyclomaticComplexity
|
|
32
|
+
item.docnumber&.match(/^BCP/) ||
|
|
33
|
+
(item.docidentifier.detect(&:primary) ||
|
|
34
|
+
item.docidentifier[0])&.content&.to_s&.include?("BCP")
|
|
35
|
+
end
|
|
36
|
+
private_class_method :bcp?
|
|
37
|
+
|
|
38
|
+
# Reverse: XML string -> ItemData
|
|
39
|
+
def self.to_item(xml)
|
|
40
|
+
if xml.include?("<referencegroup") || xml.include?("<Referencegroup")
|
|
41
|
+
referencegroup = Rfcxml::V3::Referencegroup.from_xml(xml)
|
|
42
|
+
FromRfcxmlReferencegroup.new(referencegroup).transform
|
|
43
|
+
else
|
|
44
|
+
reference = Rfcxml::V3::Reference.from_xml(xml)
|
|
45
|
+
FromRfcxml.new(reference).transform
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|