slaw 9.0.0 → 10.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6784c4dd73555dd25e51d2f2bc75e547a6c6b067f5c097695d70a00c9b2cedff
4
- data.tar.gz: f8424f209e1f681fcb3ea3841b9af2f608a691fe3e929979c4f184b6fe558049
3
+ metadata.gz: f76b66445a595e5130d1a3af8b153498d41007416bcd7c5ced418d4339030303
4
+ data.tar.gz: cd28f4839c8ecd4b430a6111a3c5ec3df764af84674b1e6205d54ac27b3c734f
5
5
  SHA512:
6
- metadata.gz: 0c3116bf7d489212f239a8b15ec2082141eb92c749109144b49071b4cce35775c1a9753c2117975ec7406b0d6a73d5c1e8ce9f82d213c8a83bf4140d658b5965
7
- data.tar.gz: 74ec26e6b94f4417cb50187d0ec183c5b47b9fbf21119e4e814f1c7f57f729ade48440950d669b9cec9e60c355c0eb03eb9efecccdad42b8e11d4a05edb52dd3
6
+ metadata.gz: f04d34041c3dc21f552b81af8d331d4440cd7c36d94e38007d15be8b467cca15b2bedbc1b5d1fb7d652ebd2b263f695a02afd337ce20df5c0e5eca7b1ec02050
7
+ data.tar.gz: dae3ef8b12f13761543a90b1711b301af9a3368cbde2387c8c00adf717c6a297ddfcfe108b9ef53073b370178574877a978fb8968d63317ed736ca1c49417232
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Slaw [![Build Status](https://travis-ci.org/longhotsummer/slaw.svg)](http://travis-ci.org/longhotsummer/slaw) [![Gem Version](https://badge.fury.io/rb/slaw.svg)](https://badge.fury.io/rb/slaw)
2
2
 
3
- Slaw is a lightweight library for generating Akoma Ntoso 2.0 Act XML from plain text documents.
3
+ Slaw is a lightweight library for generating Akoma Ntoso 3.0 Act XML from plain text documents.
4
4
  It is used to power [Indigo](https://github.com/laws-africa/indigo) and uses grammars developed for the legal
5
5
  tradition in South Africa, although others traditions are supported.
6
6
 
@@ -79,8 +79,39 @@ You can create your own grammar by creating a gem that provides these files and
79
79
  7. Push to the branch: `git push origin my-new-feature`
80
80
  8. Create a new Pull Request
81
81
 
82
+ ## Releasing
83
+
84
+ 1. Update `lib/slaw/version.rb`
85
+ 2. Run `rake release`
86
+
82
87
  ## Changelog
83
88
 
89
+ ### 10.2.0 (4 September 2020)
90
+
91
+ * support inline superscript `^^text^^`
92
+ * support inline subscript `_^text^_`
93
+
94
+ ### 10.1.0 (18 June 2020)
95
+
96
+ * hcontainer elements have name attributes, to be compliant with AKN 3.0
97
+
98
+ ### 10.0.0 (12 June 2020)
99
+
100
+ * BREAKING: Create XML with AKN 3 namespace (http://docs.oasis-open.org/legaldocml/ns/akn/3.0), AKN2 is no longer supported
101
+ * BREAKING: replace id attributes with eId attributes
102
+ * BREAKING: serialize schedules as attachments to act, not as components as peers of the act
103
+ * BREAKING: anonymous blocks are serialized as hcontainers, not paragraphs
104
+ * BREAKING: crossheading hcontainer IDs correctly use hcontainer
105
+ * Remove unnecessary schemaLocation header in root element
106
+
107
+ ### 9.2.0 (10 June 2020)
108
+
109
+ * Subpart numbers are optional
110
+
111
+ ### 9.1.0 (15 April 2020)
112
+
113
+ * Subsections can have numbers such as 1.1A and 1.1bis
114
+
84
115
  ### 9.0.0 (17 Mar 2020)
85
116
 
86
117
  * Support SUBPART
data/bin/slaw CHANGED
@@ -19,6 +19,7 @@ class SlawCLI < Thor
19
19
  option :section_number_position, enum: ['before-title', 'after-title', 'guess'], desc: "Where do section titles come in relation to the section number? Default: before-title"
20
20
  option :grammar, type: :string, desc: "Grammar name (usually a two-letter country code). Default is za."
21
21
  option :ascii, type: :boolean, default: false, desc: "Process text as ASCII using %-encoding. This can provide significant speed improvements if the grammar uses only ASCII literals. See https://github.com/cjheath/treetop/issues/31."
22
+ option :namespace, enum: ['akn3'], default: 'akn3', desc: 'AKN XML namespace to use.'
22
23
  def parse(name)
23
24
  logging
24
25
 
@@ -33,6 +34,11 @@ class SlawCLI < Thor
33
34
  text = extractor.extract_from_file(name)
34
35
  end
35
36
 
37
+ case options[:namespace]
38
+ when 'akn3'
39
+ Slaw.akn_namespace = Slaw::AKN3_NS
40
+ end
41
+
36
42
  generator = Slaw::ActGenerator.new(options[:grammar] || 'za')
37
43
 
38
44
  if options[:fragment]
@@ -49,7 +55,7 @@ class SlawCLI < Thor
49
55
 
50
56
  if options[:id_prefix]
51
57
  prefix = options[:id_prefix]
52
- prefix += "." unless prefix.end_with?('.')
58
+ prefix += "__" unless prefix.end_with?('__')
53
59
  generator.builder.fragment_id_prefix = prefix
54
60
  end
55
61
  end
@@ -4,16 +4,14 @@ module Slaw
4
4
  # Counters for generating element IDs. This is a hash from the element ID
5
5
  # prefix, to another hash that maps the element type name to a count.
6
6
  #
7
- # For backwards compatibility, counters always start at -1, and must be
8
- # incremented before being used. This ensures that element ids start at 0.
9
- # This is NOT compatible with AKN 3.0 which requires that element numbers
10
- # start at 1.
7
+ # Counters always start at 0, and must be incremented before being used.
8
+ # This ensures that element ids start at 1, as per AKN 3.0 spec.
11
9
  #
12
10
  # eg.
13
11
  #
14
12
  # section-1 => paragraph => 2
15
13
  #
16
- @@counters = Hash.new{ |h, k| h[k] = Hash.new(-1) }
14
+ @@counters = Hash.new{ |h, k| h[k] = Hash.new(0) }
17
15
 
18
16
  def self.counters
19
17
  @@counters
@@ -22,6 +20,25 @@ module Slaw
22
20
  def self.reset!
23
21
  @@counters.clear
24
22
  end
23
+
24
+ # Clean a <num> value for use in an eId
25
+ # See https://docs.oasis-open.org/legaldocml/akn-nc/v1.0/os/akn-nc-v1.0-os.html#_Toc531692306
26
+ #
27
+ # The number part of the identifiers of such elements corresponds to the
28
+ # stripping of all final punctuation, meaningless separations as well as
29
+ # redundant characters in the content of the <num> element. The
30
+ # representation is case-sensitive
31
+ #
32
+ # (i) -> i
33
+ # 1.2. -> 1-2
34
+ # 3a bis -> 3abis
35
+ def self.clean(num)
36
+ num
37
+ .gsub(/[ ()\[\]]/, '')
38
+ .gsub(/\.+$/, '')
39
+ .gsub(/^\.+/, '')
40
+ .gsub(/\.+/, '-')
41
+ end
25
42
  end
26
43
  end
27
44
  end
@@ -20,7 +20,7 @@ module Slaw
20
20
  end
21
21
 
22
22
  rule inline_item
23
- remark / image / ref / bold / italics / [^\n]
23
+ remark / image / ref / bold / italics / superscript / subscript / [^\n]
24
24
  <InlineItem>
25
25
  end
26
26
 
@@ -57,6 +57,18 @@ module Slaw
57
57
  <Ref>
58
58
  end
59
59
 
60
+ rule superscript
61
+ # ^^foo^^
62
+ '^^' content:(!'^^' inline_item)+ '^^'
63
+ <Superscript>
64
+ end
65
+
66
+ rule subscript
67
+ # _^foo^_
68
+ '_^' content:(!'^_' inline_item)+ '^_'
69
+ <Subscript>
70
+ end
71
+
60
72
  end
61
73
  end
62
74
  end
@@ -71,6 +71,26 @@ module Slaw
71
71
  end
72
72
  end
73
73
 
74
+ class Superscript < Treetop::Runtime::SyntaxNode
75
+ def to_xml(b, idprefix)
76
+ b.sup { |b|
77
+ for e in content.elements
78
+ e.inline_item.to_xml(b, idprefix)
79
+ end
80
+ }
81
+ end
82
+ end
83
+
84
+ class Subscript < Treetop::Runtime::SyntaxNode
85
+ def to_xml(b, idprefix)
86
+ b.sub { |b|
87
+ for e in content.elements
88
+ e.inline_item.to_xml(b, idprefix)
89
+ end
90
+ }
91
+ end
92
+ end
93
+
74
94
  end
75
95
  end
76
96
  end
@@ -10,7 +10,7 @@ module Slaw
10
10
 
11
11
  class ScheduleContainer < Treetop::Runtime::SyntaxNode
12
12
  def to_xml(b, idprefix="")
13
- b.components { |b|
13
+ b.attachments { |b|
14
14
  schedules.children.elements.each_with_index { |e, i|
15
15
  e.to_xml(b, idprefix, i+1)
16
16
  }
@@ -86,6 +86,9 @@ module Slaw
86
86
  end
87
87
 
88
88
  def to_xml(b, idprefix=nil, i=1)
89
+ # reset counters for this new schedule document
90
+ Slaw::Grammars::Counters.reset!
91
+
89
92
  heading_text = self.schedule_title.heading_text
90
93
  if not heading_text
91
94
  heading_text = "Schedule"
@@ -95,12 +98,13 @@ module Slaw
95
98
  # the schedule id is derived from the heading
96
99
  schedule_id = self.schedule_id(heading_text, i)
97
100
 
98
- b.component(id: "component-#{schedule_id}") { |b|
99
- b.doc_(name: schedule_id) { |b|
101
+ b.attachment(eId: "att_#{i}") { |b|
102
+ schedule_title.to_xml(b, '', heading_text)
103
+ b.doc_(name: "schedule") { |b|
100
104
  b.meta { |b|
101
105
  b.identification(source: "#slaw") { |b|
102
106
  b.FRBRWork { |b|
103
- b.FRBRthis(value: "#{WORK_URI}/#{schedule_id}")
107
+ b.FRBRthis(value: "#{WORK_URI}/!#{schedule_id}")
104
108
  b.FRBRuri(value: WORK_URI)
105
109
  b.FRBRalias(value: heading_text)
106
110
  b.FRBRdate(date: '1980-01-01', name: 'Generation')
@@ -108,14 +112,14 @@ module Slaw
108
112
  b.FRBRcountry(value: 'za')
109
113
  }
110
114
  b.FRBRExpression { |b|
111
- b.FRBRthis(value: "#{EXPRESSION_URI}/#{schedule_id}")
115
+ b.FRBRthis(value: "#{EXPRESSION_URI}/!#{schedule_id}")
112
116
  b.FRBRuri(value: EXPRESSION_URI)
113
117
  b.FRBRdate(date: '1980-01-01', name: 'Generation')
114
118
  b.FRBRauthor(href: '#council')
115
119
  b.FRBRlanguage(language: 'eng')
116
120
  }
117
121
  b.FRBRManifestation { |b|
118
- b.FRBRthis(value: "#{MANIFESTATION_URI}/#{schedule_id}")
122
+ b.FRBRthis(value: "#{MANIFESTATION_URI}/!#{schedule_id}")
119
123
  b.FRBRuri(value: MANIFESTATION_URI)
120
124
  b.FRBRdate(date: Time.now.strftime('%Y-%m-%d'), name: 'Generation')
121
125
  b.FRBRauthor(href: '#slaw')
@@ -124,14 +128,7 @@ module Slaw
124
128
  }
125
129
 
126
130
  b.mainBody { |b|
127
- idprefix = "#{schedule_id}."
128
-
129
- # there is no good AKN hierarchy container for schedules, so we
130
- # use hcontainer instead
131
- b.hcontainer(id: schedule_id, name: "schedule") { |b|
132
- schedule_title.to_xml(b, idprefix, heading_text)
133
- body.children.elements.each_with_index { |e| e.to_xml(b, idprefix, i) } if body.is_a? Body
134
- }
131
+ body.children.elements.each_with_index { |e| e.to_xml(b, '', i) } if body.is_a? Body
135
132
  }
136
133
  }
137
134
  }
@@ -3,7 +3,9 @@ module Slaw
3
3
  module Tables
4
4
  class Table < Treetop::Runtime::SyntaxNode
5
5
  def to_xml(b, idprefix, i=0)
6
- b.table(id: "#{idprefix}table#{i}") { |b|
6
+ cnt = Slaw::Grammars::Counters.counters[idprefix]['table'] += 1
7
+
8
+ b.table(eId: "#{idprefix}table_#{cnt}") { |b|
7
9
  # we'll gather cells into this row list
8
10
  rows = []
9
11
  cells = []
@@ -20,7 +20,11 @@ module Slaw
20
20
  end
21
21
 
22
22
  rule dotted_number_2
23
- number '.' number
23
+ # 9.1
24
+ # 9.1A
25
+ # 9.1A1
26
+ # NOT: 9.A
27
+ number '.' number alphanums?
24
28
  end
25
29
 
26
30
  rule number
@@ -257,7 +257,7 @@ module Slaw
257
257
  end
258
258
 
259
259
  rule subpart_heading_prefix
260
- 'subpart'i space alphanums [ :-]*
260
+ 'subpart'i num:(space alphanums)? [ :-]*
261
261
  end
262
262
 
263
263
  rule chapter_heading_prefix
@@ -12,13 +12,13 @@ module Slaw
12
12
  MANIFESTATION_URI = EXPRESSION_URI
13
13
 
14
14
  def to_xml(b, idprefix=nil, i=0)
15
- b.act(contains: "originalVersion") { |b|
15
+ b.act(contains: 'originalVersion', name: 'act') { |b|
16
16
  write_meta(b)
17
17
  write_preface(b)
18
18
  write_preamble(b)
19
19
  write_body(b)
20
+ write_schedules(b)
20
21
  }
21
- write_schedules(b)
22
22
  end
23
23
 
24
24
  def write_meta(b)
@@ -26,8 +26,8 @@ module Slaw
26
26
  write_identification(b)
27
27
 
28
28
  b.references(source: "#this") {
29
- b.TLCOrganization(id: 'slaw', href: 'https://github.com/longhotsummer/slaw', showAs: "Slaw")
30
- b.TLCOrganization(id: 'council', href: '/ontology/organization/za/council', showAs: "Council")
29
+ b.TLCOrganization(eId: 'slaw', href: 'https://github.com/longhotsummer/slaw', showAs: "Slaw")
30
+ b.TLCOrganization(eId: 'council', href: '/ontology/organization/za/council', showAs: "Council")
31
31
  }
32
32
  }
33
33
  end
@@ -38,7 +38,7 @@ module Slaw
38
38
  b.FRBRWork { |b|
39
39
  b.FRBRthis(value: "#{WORK_URI}/main")
40
40
  b.FRBRuri(value: WORK_URI)
41
- b.FRBRalias(value: 'Short Title')
41
+ b.FRBRalias(value: 'Short Title', name: 'title')
42
42
  b.FRBRdate(date: '1980-01-01', name: 'Generation')
43
43
  b.FRBRauthor(href: '#council')
44
44
  b.FRBRcountry(value: 'za')
@@ -125,7 +125,7 @@ module Slaw
125
125
  if !stmts.empty?
126
126
  b.preamble { |b|
127
127
  stmts.each { |e|
128
- e.preamble_statement.to_xml(b, "")
128
+ e.preamble_statement.to_xml(b, "preamble__")
129
129
  }
130
130
  }
131
131
  end
@@ -138,11 +138,11 @@ module Slaw
138
138
  end
139
139
 
140
140
  def to_xml(b, id_prefix='', *args)
141
- id = id_prefix + "part-#{num}"
141
+ id = id_prefix + "part_#{Slaw::Grammars::Counters.clean(num)}"
142
142
 
143
- b.part(id: id) { |b|
143
+ b.part(eId: id) { |b|
144
144
  heading.to_xml(b)
145
- children.elements.each_with_index { |e, i| e.to_xml(b, id + '.', i) }
145
+ children.elements.each_with_index { |e, i| e.to_xml(b, id + '__', i) }
146
146
  }
147
147
  end
148
148
  end
@@ -168,22 +168,29 @@ module Slaw
168
168
  end
169
169
 
170
170
  def to_xml(b, id_prefix='', *args)
171
- id = id_prefix + "subpart-#{num}"
171
+ num = self.num
172
+ if num.empty?
173
+ num = Slaw::Grammars::Counters.counters[id_prefix]['subpart'] += 1
174
+ else
175
+ num = Slaw::Grammars::Counters.clean(num)
176
+ end
172
177
 
173
- b.subpart(id: id) { |b|
178
+ id = id_prefix + "subpart_#{num}"
179
+
180
+ b.subpart(eId: id) { |b|
174
181
  heading.to_xml(b)
175
- children.elements.each_with_index { |e, i| e.to_xml(b, id + '.', i) }
182
+ children.elements.each_with_index { |e, i| e.to_xml(b, id + '__', i) }
176
183
  }
177
184
  end
178
185
  end
179
186
 
180
187
  class SubpartHeading < Treetop::Runtime::SyntaxNode
181
188
  def num
182
- subpart_heading_prefix.alphanums.text_value
189
+ subpart_heading_prefix.num.text_value.strip()
183
190
  end
184
191
 
185
192
  def to_xml(b)
186
- b.num(num)
193
+ b.num(num) unless self.num.empty?
187
194
  if heading.respond_to? :inline_items
188
195
  b.heading { |b|
189
196
  heading.inline_items.to_xml(b)
@@ -198,11 +205,11 @@ module Slaw
198
205
  end
199
206
 
200
207
  def to_xml(b, id_prefix='', *args)
201
- id = id_prefix + "chapter-#{num}"
208
+ id = id_prefix + "chp_#{Slaw::Grammars::Counters.clean(num)}"
202
209
 
203
- b.chapter(id: id) { |b|
210
+ b.chapter(eId: id) { |b|
204
211
  heading.to_xml(b)
205
- children.elements.each_with_index { |e, i| e.to_xml(b, id + '.', i) }
212
+ children.elements.each_with_index { |e, i| e.to_xml(b, id + '__', i) }
206
213
  }
207
214
  end
208
215
  end
@@ -228,11 +235,11 @@ module Slaw
228
235
  end
229
236
 
230
237
  def to_xml(b, *args)
231
- id = "section-#{num}"
232
- b.section(id: id) { |b|
238
+ id = "sec_#{Slaw::Grammars::Counters.clean(num)}"
239
+ b.section(eId: id) { |b|
233
240
  section_title.to_xml(b)
234
241
 
235
- idprefix = "#{id}."
242
+ idprefix = "#{id}__"
236
243
  children.elements.each_with_index { |e, i| e.to_xml(b, idprefix, i) }
237
244
  }
238
245
  end
@@ -303,11 +310,11 @@ module Slaw
303
310
 
304
311
  class BlockElements < Treetop::Runtime::SyntaxNode
305
312
  def to_xml(b, idprefix='', i=0)
306
- cnt = Slaw::Grammars::Counters.counters[idprefix]['paragraph'] += 1
307
- id = "#{idprefix}paragraph#{cnt}"
308
- idprefix = "#{id}."
313
+ cnt = Slaw::Grammars::Counters.counters[idprefix]['hcontainer'] += 1
314
+ id = "#{idprefix}hcontainer_#{cnt}"
315
+ idprefix = "#{id}__"
309
316
 
310
- b.paragraph(id: id) { |b|
317
+ b.hcontainer(eId: id, name: 'hcontainer') { |b|
311
318
  b.content { |b|
312
319
  elements.each_with_index { |e, i| e.to_xml(b, idprefix, i) }
313
320
  }
@@ -321,10 +328,10 @@ module Slaw
321
328
  end
322
329
 
323
330
  def to_xml(b, idprefix, i)
324
- id = idprefix + num.gsub(/[()]/, '')
325
- idprefix = id + "."
331
+ id = idprefix + "subsec_" + Slaw::Grammars::Counters.clean(num)
332
+ idprefix = id + "__"
326
333
 
327
- b.subsection(id: id) { |b|
334
+ b.subsection(eId: id) { |b|
328
335
  b.num(num)
329
336
  block_elements_with_inline.to_xml(b, idprefix)
330
337
  }
@@ -336,10 +343,10 @@ module Slaw
336
343
  # yield to it a builder to insert a listIntroduction node
337
344
  def to_xml(b, idprefix, i=0, &block)
338
345
  cnt = Slaw::Grammars::Counters.counters[idprefix]['list'] += 1
339
- id = idprefix + "list#{cnt}"
340
- idprefix = id + '.'
346
+ id = idprefix + "list_#{cnt}"
347
+ idprefix = id + '__'
341
348
 
342
- b.blockList(id: id, renest: true) { |b|
349
+ b.blockList(eId: id, renest: true) { |b|
343
350
  b.listIntroduction { |b| yield b } if block_given?
344
351
 
345
352
  elements.each { |e| e.to_xml(b, idprefix) }
@@ -353,7 +360,7 @@ module Slaw
353
360
  end
354
361
 
355
362
  def to_xml(b, idprefix)
356
- b.item(id: idprefix + num.gsub(/[()]/, '')) { |b|
363
+ b.item(eId: idprefix + "item_" + Slaw::Grammars::Counters.clean(num)) { |b|
357
364
  b.num(num)
358
365
  b.p { |b|
359
366
  item_content.inline_items.to_xml(b, idprefix) if respond_to? :item_content and item_content.respond_to? :inline_items
@@ -364,10 +371,10 @@ module Slaw
364
371
 
365
372
  class Crossheading < Treetop::Runtime::SyntaxNode
366
373
  def to_xml(b, idprefix, i=0)
367
- cnt = Slaw::Grammars::Counters.counters[idprefix]['crossheading'] += 1
368
- id = "#{idprefix}crossheading-#{cnt}"
374
+ cnt = Slaw::Grammars::Counters.counters[idprefix]['hcontainer'] += 1
375
+ id = "#{idprefix}hcontainer_#{cnt}"
369
376
 
370
- b.hcontainer(id: id, name: 'crossheading') { |b|
377
+ b.hcontainer(eId: id, name: 'crossheading') { |b|
371
378
  b.heading { |b|
372
379
  inline_items.to_xml(b, idprefix)
373
380
  }