metanorma-plugin-glossarist 0.3.0 → 0.3.2

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.
@@ -1,59 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "asciidoctor"
4
-
5
- require "liquid"
6
4
  require "asciidoctor/reader"
7
5
  require "glossarist"
8
- require "metanorma/plugin/glossarist/document"
9
- require "metanorma/plugin/glossarist/liquid/custom_filters/filters"
6
+
7
+ require_relative "sanitize"
8
+ require_relative "concept_renderer"
9
+ require_relative "bibliography_renderer"
10
+ require_relative "concept_serializer"
11
+ require_relative "document"
12
+ require_relative "liquid/custom_filters/filters"
13
+ require_relative "liquid/custom_blocks/with_glossarist_context"
10
14
 
11
15
  module Metanorma
12
16
  module Plugin
13
17
  module Glossarist
14
18
  class DatasetPreprocessor < Asciidoctor::Extensions::Preprocessor
15
- include Metanorma::Plugin::Glossarist::Liquid::CustomFilters::Filters
16
-
17
- GLOSSARIST_DATASET_REGEX = /^:glossarist-dataset:\s*(.*?)$/m.freeze
18
- GLOSSARIST_IMPORT_REGEX = /^glossarist::import\[(.*?)\]$/m.freeze
19
- GLOSSARIST_RENDER_REGEX = /^glossarist::render\[(.*?)\]$/m.freeze
20
- GLOSSARIST_BLOCK_REGEX = /^\[glossarist,(.+?),(.+?)\]$/m.freeze
21
- GLOSSARIST_BIBLIOGRAPHY_REGEX = /^glossarist::render_bibliography\[(.*?)\]$/m.freeze # rubocop:disable Layout/LineLength
22
- GLOSSARIST_BIBLIOGRAPHY_ENTRY_REGEX = /^glossarist::render_bibliography_entry\[(.*?)\]$/m.freeze # rubocop:disable Layout/LineLength
23
-
24
- # Search document for the following blocks
25
- # - :glossarist-dataset: dataset1:./dataset1;dataset2:./dataset2
26
- # This will load `glossarist` concepts from `./dataset1` path into
27
- # `dataset1` and concepts from `./dataset2` path into `dataset2`,
28
- # These can then be used anywhere in the document like
29
- # {{dataset1.concept_name.en.definition.content}}
30
- #
31
- # - glossarist:render[dataset1, concept_name]
32
- # this will render the `concept_name` using the below format
33
- #
34
- # ==== concept term
35
- # alt:[if additional terms]
36
- #
37
- # definition text
38
- #
39
- # NOTE: if there is a note.
40
- #
41
- # [example]
42
- # If there is an example.
43
- #
44
- # [.source]
45
- # <<If there is some source>>
46
- #
47
- # - glossarist:import[dataset1]
48
- # this will render all concepts in the `dataset1` using the above
49
- # format
19
+ DATASET_ATTR_REGEX = /^:glossarist-dataset:\s*(.*?)$/m
20
+ IMPORT_REGEX = /^glossarist::import\[(.*?)\]$/m
21
+ RENDER_REGEX = /^glossarist::render\[(.*?)\]$/m
22
+ BLOCK_REGEX = /^\[glossarist,(.+?),(.+?)\]$/m
23
+ BIBLIOGRAPHY_REGEX = /^glossarist::render_bibliography\[(.*?)\]$/m
24
+ BIBLIOGRAPHY_ENTRY_REGEX = /^glossarist::render_bibliography_entry\[(.*?)\]$/m
25
+
50
26
  def initialize(config = {})
51
27
  super
52
28
  @config = config
53
29
  @datasets = {}
54
- @title_depth = { value: 2 }
55
- @rendered_bibliographies = {}
56
- @seen_glossarist = []
30
+ @title_depth = 2
31
+ @bibliography_renderer = BibliographyRenderer.new
32
+ @seen_glossarist = false
57
33
  @context_names = []
58
34
  end
59
35
 
@@ -61,7 +37,7 @@ module Metanorma
61
37
  input_lines = reader.lines.to_enum
62
38
  @config[:file_system] = relative_file_path(document, "")
63
39
  processed_doc = prepare_document(document, input_lines)
64
- log(document, processed_doc.to_s) unless @seen_glossarist.empty?
40
+ log(document, processed_doc.to_s) if @seen_glossarist
65
41
  Asciidoctor::PreprocessorReader.new(document,
66
42
  processed_doc.to_s.split("\n"))
67
43
  end
@@ -77,10 +53,8 @@ module Metanorma
77
53
 
78
54
  private
79
55
 
80
- def prepare_document( # rubocop:disable Metrics/MethodLength
81
- document, input_lines, end_mark = nil,
82
- skip_dataset: false
83
- )
56
+ def prepare_document(document, input_lines, end_mark = nil,
57
+ skip_dataset: false)
84
58
  liquid_doc = Document.new
85
59
  liquid_doc.file_system = @config[:file_system]
86
60
 
@@ -88,9 +62,7 @@ module Metanorma
88
62
  current_line = input_lines.next
89
63
  break if end_mark && current_line == end_mark
90
64
 
91
- if !(
92
- skip_dataset && current_line.start_with?(":glossarist-dataset:")
93
- )
65
+ unless skip_dataset && current_line.start_with?(":glossarist-dataset:")
94
66
  process_line(document, input_lines, current_line, liquid_doc)
95
67
  end
96
68
  end
@@ -98,346 +70,192 @@ module Metanorma
98
70
  liquid_doc
99
71
  end
100
72
 
101
- def process_line( # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
102
- document, input_lines, current_line, liquid_doc
103
- )
104
- if match = current_line.match(GLOSSARIST_DATASET_REGEX)
73
+ def process_line(document, input_lines, current_line, liquid_doc)
74
+ if (match = current_line.match(DATASET_ATTR_REGEX))
105
75
  process_dataset_tag(document, input_lines, liquid_doc, match)
106
- elsif match = current_line.match(GLOSSARIST_RENDER_REGEX)
76
+ elsif (match = current_line.match(RENDER_REGEX))
107
77
  process_render_tag(liquid_doc, match)
108
- elsif match = current_line.match(GLOSSARIST_IMPORT_REGEX)
78
+ elsif (match = current_line.match(IMPORT_REGEX))
109
79
  process_import_tag(liquid_doc, match)
110
- elsif match = current_line.match(GLOSSARIST_BIBLIOGRAPHY_REGEX)
80
+ elsif (match = current_line.match(BIBLIOGRAPHY_REGEX))
111
81
  process_bibliography(document, liquid_doc, match)
112
- elsif match = current_line.match(GLOSSARIST_BIBLIOGRAPHY_ENTRY_REGEX)
82
+ elsif (match = current_line.match(BIBLIOGRAPHY_ENTRY_REGEX))
113
83
  process_bibliography_entry(document, liquid_doc, match)
114
- elsif match = current_line.match(GLOSSARIST_BLOCK_REGEX)
84
+ elsif (match = current_line.match(BLOCK_REGEX))
115
85
  process_glossarist_block(document, liquid_doc, input_lines, match)
116
86
  else
117
87
  if /^==+ \S/.match?(current_line)
118
- @title_depth[:value] = current_line.sub(/ .*$/, "").size
88
+ @title_depth = current_line.sub(/ .*$/,
89
+ "").size
119
90
  end
120
91
  liquid_doc.add_content(current_line)
121
92
  end
122
93
  end
123
94
 
124
95
  def process_dataset_tag(document, input_lines, liquid_doc, match)
125
- @seen_glossarist << "x"
96
+ @seen_glossarist = true
126
97
  @context_names << prepare_dataset_contexts(document, match[1])
127
98
  @context_names.flatten!
128
- dataset_section = <<~TEMPLATE
129
- #{prepare_document(document, input_lines)}
130
- TEMPLATE
131
-
132
- liquid_doc.add_content(
133
- dataset_section,
134
- render: false,
135
- )
99
+ liquid_doc.add_content(prepare_document(document, input_lines).to_s,
100
+ render: false)
136
101
  end
137
102
 
138
103
  def process_glossarist_block(document, liquid_doc, input_lines, match)
139
- @seen_glossarist << "x"
104
+ @seen_glossarist = true
140
105
  end_mark = input_lines.next
141
106
 
142
- glossarist_params, template =
143
- prepare_glossarist_block_params(document, match)
144
- glossarist_section = <<~TEMPLATE.strip
145
- {% with_glossarist_context #{glossarist_params} %}
146
- #{prepare_document(
147
- document, input_lines, end_mark,
148
- skip_dataset: true
149
- )}
107
+ params, template = prepare_glossarist_block_params(document, match)
108
+ section = <<~SECTION.strip
109
+ {% with_glossarist_context #{params} %}
110
+ #{prepare_document(document, input_lines, end_mark, skip_dataset: true)}
150
111
  {% endwith_glossarist_context %}
151
- TEMPLATE
112
+ SECTION
152
113
 
153
114
  options = { render: true }
154
115
  options[:template] = template if template
155
-
156
- liquid_doc.add_content(glossarist_section, options)
116
+ liquid_doc.add_content(section, options)
157
117
  end
158
118
 
159
- def prepare_glossarist_block_params(document, match) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
119
+ def prepare_glossarist_block_params(document, match)
160
120
  path = get_context_path(document, match[1])
161
121
  matched_arr = match[2].split(",").map(&:strip)
162
- # get the last element as context name
163
122
  context_name = matched_arr.last
164
123
 
165
- glossarist_block_params = []
166
- glossarist_block_params << "#{context_name}=#{path}"
167
-
124
+ params = ["#{context_name}=#{path}"]
168
125
  template = nil
169
- if matched_arr.size > 1
170
- filters_or_template = matched_arr[0..-2]
171
- filters_or_template.each do |item|
172
- if item.start_with?("filter=")
173
- filters = item.gsub(/^filter=/, "").strip
174
- glossarist_block_params << filters
175
- elsif item.start_with?("template=")
176
- template = relative_file_path(
177
- document,
178
- item.gsub(/^template=/, "").strip,
179
- )
180
- end
126
+
127
+ matched_arr[0..-2].each do |item|
128
+ if item.start_with?("filter=")
129
+ filters = item.delete_prefix("filter=").strip
130
+ params << filters
131
+ elsif item.start_with?("template=")
132
+ template = relative_file_path(document,
133
+ item.delete_prefix("template=").strip)
181
134
  end
182
135
  end
183
136
 
184
- [glossarist_block_params.join(";"), template]
137
+ [params.join(";"), template]
185
138
  end
186
139
 
187
- def get_context_path(document, key) # rubocop:disable Metrics/MethodLength
188
- context_path = nil
189
- # try to get context_path from glossarist-dataset definition
140
+ def get_context_path(document, key)
190
141
  if @context_names && !@context_names.empty?
191
142
  context_names = @context_names.map(&:strip)
192
- context_path = context_names.find do |context|
143
+ found = context_names.find do |context|
193
144
  context_name, = context.split("=")
194
145
  context_name == key
195
146
  end
147
+ return found.split("=").last.strip if found
196
148
  end
197
149
 
198
- return context_path.split("=").last.strip if context_path
199
-
200
150
  relative_file_path(document, key)
201
151
  end
202
152
 
203
153
  def process_render_tag(liquid_doc, match)
204
- @seen_glossarist << "x"
154
+ @seen_glossarist = true
205
155
  matches = match[1].split(",").map(&:strip)
206
-
207
156
  context_name = matches[0]
208
157
  concept_name = matches[1]
209
- render_options = matches[2..-1]
158
+ options = parse_options(matches[2..])
210
159
 
211
- liquid_doc.add_content(
212
- concept_template(context_name.strip, concept_name.strip,
213
- render_options),
214
- )
160
+ concept = find_concept(context_name, concept_name)
161
+ return unless concept
162
+
163
+ renderer = ConceptRenderer.new(concept,
164
+ depth: @title_depth,
165
+ anchor_prefix: options["anchor-prefix"])
166
+ liquid_doc.add_content(renderer.render)
215
167
  end
216
168
 
217
- def process_import_tag(liquid_doc, match) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
218
- @seen_glossarist << "x"
169
+ def process_import_tag(liquid_doc, match)
170
+ @seen_glossarist = true
219
171
  matches = match[1].split(",").map(&:strip)
220
172
  context_name = matches[0]
221
- render_options = matches[1..-1]
173
+ options = parse_options(matches[1..])
222
174
  dataset = @datasets[context_name.strip]
175
+ return unless dataset
223
176
 
224
- liquid_doc.add_content(
225
- dataset.map do |concept|
226
- concept_name = concept.data.localizations["eng"].data
227
- .terms[0].designation
228
- concept_template(context_name, concept_name, render_options)
229
- end.join("\n"),
230
- )
177
+ rendered = dataset.filter_map do |concept|
178
+ designation = concept.default_designation
179
+ next unless designation
180
+
181
+ renderer = ConceptRenderer.new(concept,
182
+ depth: @title_depth,
183
+ anchor_prefix: options["anchor-prefix"])
184
+ renderer.render
185
+ end.join("\n\n")
186
+
187
+ liquid_doc.add_content(rendered)
231
188
  end
232
189
 
233
- def process_bibliography(document, liquid_doc, match) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
234
- @seen_glossarist << "x"
190
+ def process_bibliography(document, liquid_doc, match)
191
+ @seen_glossarist = true
235
192
  dataset_name = match[1].strip
236
- dataset = @datasets[dataset_name]
237
-
238
- if dataset.nil? || @datasets.empty?
239
- path = relative_file_path(document, dataset_name)
240
- dataset = load_dataset_from_path(path)
241
- end
193
+ dataset = resolve_dataset(document, dataset_name)
194
+ return unless dataset
242
195
 
243
- liquid_doc.add_content(
244
- dataset.map do |concept|
245
- concept_bibliography(concept)
246
- end.compact.sort.join("\n"),
247
- )
196
+ liquid_doc.add_content(@bibliography_renderer.render_all(dataset))
248
197
  end
249
198
 
250
199
  def process_bibliography_entry(document, liquid_doc, match)
251
- @seen_glossarist << "x"
200
+ @seen_glossarist = true
252
201
  dataset_name, concept_name = match[1].split(",").map(&:strip)
253
- concept = get_concept(dataset_name, concept_name, document)
254
- bibliography = concept_bibliography(concept)
202
+ concept = find_concept(dataset_name, concept_name, document)
203
+ return unless concept
255
204
 
256
- if bibliography
257
- liquid_doc.add_content(bibliography)
258
- end
259
- end
260
-
261
- def concept_bibliography(concept) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
262
- sources = concept.data.localizations["eng"].data.sources
263
- return nil if sources.nil? || sources.empty?
264
-
265
- bibliography = sources.map do |source|
266
- ref = source.origin.text
267
- next if @rendered_bibliographies[ref] || ref.nil? || ref.empty?
268
-
269
- @rendered_bibliographies[ref] = ref.gsub(/[ \/:]/, "_")
270
- "* [[[#{@rendered_bibliographies[ref]},#{ref}]]]"
271
- end.compact.join("\n")
272
-
273
- bibliography == "" ? nil : bibliography
205
+ entry = @bibliography_renderer.render_entry(concept)
206
+ liquid_doc.add_content(entry) if entry
274
207
  end
275
208
 
276
209
  def prepare_dataset_contexts(document, contexts)
277
210
  contexts.split(";").map do |context|
278
211
  context_name, file_path = context.split(":").map(&:strip)
279
212
  path = relative_file_path(document, file_path)
280
- dataset = load_dataset_from_path(path)
281
- @datasets[context_name] = dataset.map(&:to_liquid)
282
-
213
+ dataset = load_dataset(path)
214
+ @datasets[context_name] = dataset.to_a
283
215
  "#{context_name}=#{path}"
284
216
  end
285
217
  end
286
218
 
287
- def load_dataset_from_path(path)
288
- dataset = ::Glossarist::ManagedConceptCollection.new
289
- dataset.load_from_files(path)
290
- dataset
219
+ def load_dataset(path)
220
+ collection = ::Glossarist::ManagedConceptCollection.new
221
+ collection.load_from_files(path)
222
+ collection
291
223
  end
292
224
 
293
- def relative_file_path(document, file_path)
294
- docfile_directory = File.dirname(
295
- document.attributes["docfile"] || ".",
296
- )
297
- document
298
- .path_resolver
299
- .system_path(file_path, docfile_directory)
300
- end
301
-
302
- def concept_template(dataset_name, concept_name, options = [])
303
- options = options_to_hash(options)
304
-
305
- <<~CONCEPT_TEMPLATE
306
- [[#{identifier(dataset_name, concept_name, options)}]]
307
- #{'=' * (@title_depth[:value] + 1)} #{concept_title(dataset_name, concept_name)}
308
-
309
- #{alt_terms(dataset_name, concept_name)}
225
+ def find_concept(dataset_name, concept_name, document = nil)
226
+ dataset = resolve_dataset(document, dataset_name)
227
+ return unless dataset
310
228
 
311
- #{concept_definition(dataset_name, concept_name)}
312
-
313
- #{examples(dataset_name, concept_name)}
314
-
315
- #{notes(dataset_name, concept_name)}
316
-
317
- #{sources(dataset_name, concept_name)}
318
- CONCEPT_TEMPLATE
229
+ dataset.find do |concept|
230
+ concept.default_designation == concept_name
231
+ end
319
232
  end
320
233
 
321
- def get_concept(dataset_name, concept_name, document = nil)
234
+ def resolve_dataset(document, dataset_name)
322
235
  dataset = @datasets[dataset_name]
236
+ return dataset if dataset
323
237
 
324
- if document && dataset.nil?
325
- path = relative_file_path(document, dataset_name)
326
- dataset = load_dataset_from_path(path)
327
- end
238
+ return unless document
328
239
 
329
- dataset.find do |c|
330
- c.data.localizations["eng"]
331
- .data.terms[0]
332
- .designation == concept_name
333
- end
240
+ path = relative_file_path(document, dataset_name)
241
+ collection = load_dataset(path)
242
+ @datasets[dataset_name] = collection.to_a
334
243
  end
335
244
 
336
- def identifier(dataset_name, concept_name, options = {})
337
- prefix = options["anchor-prefix"].to_s if options["anchor-prefix"]
338
- concept = get_concept(dataset_name, concept_name)
339
-
340
- id = "#{prefix}#{concept.data.id}"
341
- # id starts with digits 0-9
342
- return id if id.start_with?(/[0-9]/)
343
-
344
- Metanorma::Utils.to_ncname(id.gsub(":", "_"))
245
+ def relative_file_path(document, file_path)
246
+ docfile_directory = File.dirname(
247
+ document.attributes["docfile"] || ".",
248
+ )
249
+ document.path_resolver.system_path(file_path, docfile_directory)
345
250
  end
346
251
 
347
- def options_to_hash(options_arr)
252
+ def parse_options(options_arr)
348
253
  return {} if !options_arr || options_arr.empty?
349
254
 
350
- options_arr.map { |option| option.split("=") }.to_h
351
- end
352
-
353
- def concept_title(dataset_name, concept_name)
354
- concept = get_concept(dataset_name, concept_name)
355
- concept.data.localizations["eng"].data.terms[0].designation
356
- end
357
-
358
- def concept_definition(dataset_name, concept_name)
359
- concept = get_concept(dataset_name, concept_name)
360
- definition = concept.data.localizations["eng"].data
361
- .definition[0].content
362
- sanitize_references(definition.to_s)
363
- end
364
-
365
- def alt_terms(dataset_name, concept_name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
366
- concept = get_concept(dataset_name, concept_name)
367
- term_types = %w[preferred admitted deprecated]
368
- concept_terms = concept.data.localizations["eng"].data.terms
369
-
370
- concept_terms[1..-1].map do |term|
371
- type = if term_types.include?(term.normative_status)
372
- term.normative_status
373
- else
374
- "alt"
375
- end
376
- "#{type}:[#{term.designation}]"
377
- end.join("\n")
378
- end
379
-
380
- def examples(dataset_name, concept_name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
381
- concept = get_concept(dataset_name, concept_name)
382
- examples = concept.data.localizations["eng"].data.examples
383
- result = []
384
-
385
- examples.each do |example|
386
- content = <<~EXAMPLE
387
- [example]
388
- #{sanitize_references(example.content)}
389
-
390
- EXAMPLE
391
- result << content
255
+ options_arr.each_with_object({}) do |option, hash|
256
+ key, value = option.split("=", 2)
257
+ hash[key] = value if key && value
392
258
  end
393
-
394
- result.join("\n")
395
- end
396
-
397
- def notes(dataset_name, concept_name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
398
- concept = get_concept(dataset_name, concept_name)
399
- notes = concept.data.localizations["eng"].data.notes
400
- result = []
401
-
402
- notes.each do |note|
403
- content = <<~NOTE
404
- [NOTE]
405
- ====
406
- #{sanitize_references(note.content)}
407
- ====
408
-
409
- NOTE
410
- result << content
411
- end
412
-
413
- result.join("\n")
414
- end
415
-
416
- def sources(dataset_name, concept_name) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
417
- concept = get_concept(dataset_name, concept_name)
418
- sources = concept.data.localizations["eng"].data.sources
419
- result = []
420
-
421
- sources.each do |source|
422
- if source.origin.text &&
423
- source.origin.text != "" &&
424
- source.origin.locality.type == "clause"
425
- source_origin_text = source.origin.text
426
- .gsub(" ", "_")
427
- .gsub("/", "_")
428
- .gsub(":", "_")
429
- source_content = "#{source_origin_text}," \
430
- "#{source.origin.locality.reference_from}"
431
- content = <<~SOURCES
432
- [.source]
433
- <<#{source_content}>>
434
-
435
- SOURCES
436
- result << content
437
- end
438
- end
439
-
440
- result.join("\n")
441
259
  end
442
260
  end
443
261
  end
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "liquid/custom_blocks/with_glossarist_context"
4
- require_relative "liquid/custom_filters/filters"
3
+ require "liquid"
5
4
  require_relative "liquid/multiply_local_file_system"
6
5
 
7
6
  module Metanorma
8
7
  module Plugin
9
8
  module Glossarist
10
9
  class Document
11
- attr_accessor :content, :bibliographies, :file_system
10
+ attr_accessor :content, :file_system
12
11
 
13
12
  def initialize
14
13
  @content = []
15
- @bibliographies = []
16
14
  end
17
15
 
18
16
  def add_content(content, options = {})
@@ -27,33 +25,20 @@ module Metanorma
27
25
  @content.compact.join("\n")
28
26
  end
29
27
 
30
- def render_liquid(file_content, options = {}) # rubocop:disable Metrics/AbcSize
28
+ private
29
+
30
+ def render_liquid(file_content, options = {})
31
31
  include_paths = [file_system, options[:template]].compact
32
- template = ::Liquid::Template
33
- .parse(file_content, environment: create_liquid_environment)
32
+ template = ::Liquid::Template.parse(file_content)
34
33
  template.registers[:file_system] = ::Metanorma::Plugin::Glossarist::Liquid::LocalFileSystem.new(
35
34
  include_paths, ["%s.liquid", "_%s.liquid", "_%s.adoc"]
36
35
  )
37
- rendered_template = template.render(strict_variables: false,
38
- error_mode: :warn)
36
+ rendered = template.render
39
37
 
40
- return rendered_template unless template.errors.any?
38
+ return rendered unless template.errors.any?
41
39
 
42
40
  raise template.errors.first.cause
43
41
  end
44
-
45
- def create_liquid_environment
46
- ::Liquid::Environment.new.tap do |liquid_env|
47
- liquid_env.register_tag(
48
- "with_glossarist_context",
49
- ::Metanorma::Plugin::Glossarist::Liquid::CustomBlocks::
50
- WithGlossaristContext,
51
- )
52
- liquid_env.register_filter(
53
- ::Metanorma::Plugin::Glossarist::Liquid::CustomFilters::Filters,
54
- )
55
- end
56
- end
57
42
  end
58
43
  end
59
44
  end