Class: Slaw::Act

Inherits:
AknBase show all
Defined in:
lib/slaw/act.rb

Overview

An Act wraps a single / AkomaNtoso 2.0 XML act document in the form of a Nokogiri::XML::Document object.

The Act object provides quick access to certain sections of the document, such as the metadata and the body, as well as common operations such as identifying whether it has been amended (#amended?), repealed (#repealed?) or what chapters (#chapters), parts (#parts) and sections (#sections) it contains.

Direct Known Subclasses

ByLaw

Constant Summary collapse

@@acts =

Allow us to jump from the XML document for an act to the Act instance itself

{}

Constants included from Namespace

Namespace::NS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AknBase

#parse, #to_xml

Constructor Details

#initialize(filename = nil) ⇒ Act

Create a new instance, loading from ‘filename` if given.

Parameters:

  • filename (String) (defaults to: nil)

    filename to load XML from



82
83
84
85
# File 'lib/slaw/act.rb', line 82

def initialize(filename=nil)
  self.load(filename) if filename
  @schema = nil
end

Instance Attribute Details

#bodyObject (readonly)

Nokogiri::XML::Node

The ‘body` XML node



50
51
52
# File 'lib/slaw/act.rb', line 50

def body
  @body
end

#docObject

Nokogiri::XML::Document

The underlying Nokogiri::XML::Document instance



44
45
46
# File 'lib/slaw/act.rb', line 44

def doc
  @doc
end

#filenameObject (readonly)

String, nil

The source filename, or nil



62
63
64
# File 'lib/slaw/act.rb', line 62

def filename
  @filename
end

#id_uriObject

String

The FRBR URI of this act, which uniquely identifies it globally



59
60
61
# File 'lib/slaw/act.rb', line 59

def id_uri
  @id_uri
end

#metaObject (readonly)

Nokogiri::XML::Node

The ‘meta` XML node



47
48
49
# File 'lib/slaw/act.rb', line 47

def meta
  @meta
end

#mtimeObject (readonly)

Time, nil

The mtime of when the source file was last modified



65
66
67
# File 'lib/slaw/act.rb', line 65

def mtime
  @mtime
end

#natureObject (readonly)

String

The underlying nature of this act, usually ‘act` although subclasses my override this.



68
69
70
# File 'lib/slaw/act.rb', line 68

def nature
  @nature
end

#numObject (readonly)

String

The act number in the year this act was published



56
57
58
# File 'lib/slaw/act.rb', line 56

def num
  @num
end

#schemaObject

Nokogiri::XML::Schema

schema to validate against



71
72
73
# File 'lib/slaw/act.rb', line 71

def schema
  @schema
end

#yearObject

String

The year this act was published



53
54
55
# File 'lib/slaw/act.rb', line 53

def year
  @year
end

Class Method Details

.for_node(node) ⇒ Act

Get the act that wraps the document that owns this XML node

Parameters:

Returns:

  • (Act)

    owning act



76
77
78
# File 'lib/slaw/act.rb', line 76

def self.for_node(node)
  @@acts[node.document]
end

Instance Method Details

#amended?Boolean

Has this act been amended? This is determined by testing the ‘contains` attribute of the `act` root element.

Returns:

  • (Boolean)


194
195
196
# File 'lib/slaw/act.rb', line 194

def amended?
  @doc.at_xpath('/a:akomaNtoso/a:act', a: NS)['contains'] != 'originalVersion'
end

#amended_by!(act, opts = {}) ⇒ Object

Mark this act as being amended by another act, either ‘act` or the details in `opts`.

It is assumed that there can be only one amendment event on a particular date. An existing amendment on this date is overwritten.

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :uri (String)

    uri of the amending act

  • :title (String)

    title of the amending act

  • :date (String)

    date of the amendment (YYYY-MM-DD)



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/slaw/act.rb', line 215

def amended_by!(act, opts={})
  if act
    opts[:uri] ||= act.id_uri
    opts[:title] ||= act.short_title
    opts[:date] ||= act.publication['date']
  end

  date = opts[:date]
  source_id = "amendment-#{date}"

  # assume we now hold a single version and not the original version
  @doc.at_xpath('/a:akomaNtoso/a:act', a: NS)['contains'] = 'singleVersion'

  # add the lifecycle event
  lifecycle = @meta.at_xpath('./a:lifecycle', a: NS)
  if not lifecycle
    lifecycle = @doc.create_element('lifecycle', source: "#this")
    @meta.at_xpath('./a:publication', a: NS).after(lifecycle)
  end

  event = lifecycle.at_xpath('./a:eventRef[@date="' + date + '"][@type="amendment"]', a: NS)
  if event
    # clear up old event
    src = @doc.at_css(event['source'])
    src.remove if src
  else
    # new event
    event = @doc.create_element('eventRef', type: 'amendment')
    lifecycle << event
  end

  event['date'] = date
  event['id'] = "amendment-event-#{date}"
  event['source'] = '#' + source_id

  # add reference
  ref = @doc.create_element('passiveRef',
                            id: source_id,
                            href: opts[:uri],
                            showAs: opts[:title])

  @meta.at_xpath('./a:references/a:TLCTerm', a: NS).before(ref)
end

#amendment_eventsArray<Slaw::LifecycleEvent>

Get a list of LifecycleEvent objects for amendment events, in date order.

Returns:



200
201
202
203
204
# File 'lib/slaw/act.rb', line 200

def amendment_events
  @meta.xpath('./a:lifecycle/a:eventRef[@type="amendment"]', a: NS).map do |event|
    LifecycleEvent.new(event)
  end.sort_by { |e| e.date }
end

#chaptersArray<Nokogiri::XML::Node>

Top-level chapters of this act. Chapters inside parts are ignored.

Returns:



279
280
281
# File 'lib/slaw/act.rb', line 279

def chapters
  @body.xpath('./a:chapter', a: NS)
end

#chapters?Boolean

Does this Act have chapters?

Returns:

  • (Boolean)


273
274
275
# File 'lib/slaw/act.rb', line 273

def chapters?
  !chapters.empty?
end

#dateString

The date at which this act was first created/promulgated.

Returns:

  • (String)

    date, YYYY-MM-DD



142
143
144
145
# File 'lib/slaw/act.rb', line 142

def date
  node = @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRdate[@name="Generation"]', a: NS)
  node && node['date']
end

#date=(value) ⇒ Object

Set the date at which this act was first created/promulgated. This is usually the same as the publication date but this is not enforced.

This also updates the #year of this act, which in turn updates the #id_uri.

Parameters:

  • date (String)

    date, YYYY-MM-DD



153
154
155
156
157
158
159
# File 'lib/slaw/act.rb', line 153

def date=(value)
  for frbr in ['FRBRWork', 'FRBRExpression'] do
    @meta.at_xpath("./a:identification/a:#{frbr}/a:FRBRdate[@name=\"Generation\"]", a: NS)['date'] = value
  end

  self.year = value.split('-')[0]
end

#definitionsNokogiri::XML::Node?

The primary definitions section of this act, identified by either an ‘id` of `definitions` or the first section with a heading of `Definitions`.

Returns:



294
295
296
297
298
299
300
301
302
303
304
# File 'lib/slaw/act.rb', line 294

def definitions
  # try looking for the definition list
  defn = @body.at_css('#definitions')
  return defn.parent if defn

  # try looking for the heading
  defn = @body.at_xpath('.//a:section/a:heading[text() = "Definitions"]', a: NS)
  return defn.parent if defn

  nil
end

#inspectObject



425
426
427
# File 'lib/slaw/act.rb', line 425

def inspect
  "<#{self.class.name} @id_uri=\"#{@id_uri}\">"
end

#load(filename) ⇒ Object

Load the XML in ‘filename` into this instance

Parameters:

  • filename (String)

    filename



89
90
91
92
93
94
# File 'lib/slaw/act.rb', line 89

def load(filename)
  @filename = filename
  @mtime = File::mtime(@filename)

  File.open(filename) { |f| parse(f) }
end

#manifestation_dateString

The date at which this particular XML manifestation of this document was generated.

Returns:

  • (String)

    date, YYYY-MM-DD



404
405
406
407
# File 'lib/slaw/act.rb', line 404

def manifestation_date
  node = @meta.at_xpath('./a:identification/a:FRBRManifestation/a:FRBRdate[@name="Generation"]', a: NS)
  node && node['date']
end

#partsArray<Nokogiri::XML::Node>

Top-level parts of this act. Parts inside chapters are ignored.

Returns:



267
268
269
# File 'lib/slaw/act.rb', line 267

def parts
  @body.xpath('./a:part', a: NS)
end

#parts?Boolean

Does this Act have parts?

Returns:

  • (Boolean)


261
262
263
# File 'lib/slaw/act.rb', line 261

def parts?
  !parts.empty?
end

#publicationNokogiri::XML::Node?

Returns the publication element, if any.

Returns:



340
341
342
# File 'lib/slaw/act.rb', line 340

def publication
  @meta.at_xpath('./a:publication', a: NS)
end

#published!(details) ⇒ Object

Update the publication details of the act. All elements are optional.

Parameters:

  • details (Hash)

    a customizable set of options

Options Hash (details):

  • :name (String)

    name of the publication

  • :number (String)

    publication number

  • :date (String)

    date of publication (YYYY-MM-DD)



349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/slaw/act.rb', line 349

def published!(details)
  node = @meta.at_xpath('./a:publication', a: NS)
  unless node
    node = @doc.create_element('publication')
    @meta.at_xpath('./a:identification', a: NS).after(node)
  end

  node['showAs'] = details[:name] if details.has_key? :name
  node['name'] = details[:name] if details.has_key? :name
  node['date'] = details[:date] if details.has_key? :date
  node['number'] = details[:number] if details.has_key? :number
end

#repealNokogiri::XML::Node

The XML element representing the event of repeal of this act, or nil

Returns:



392
393
394
395
396
397
398
399
# File 'lib/slaw/act.rb', line 392

def repeal
  # <lifecycle source="#this">
  #   <eventRef id="e1" date="2010-07-28" source="#original" type="generation"/>
  #   <eventRef id="e2" date="2012-04-26" source="#amendment-1" type="amendment"/>
  #   <eventRef id="e3" date="2014-01-17" source="#repeal" type="repeal"/>
  # </lifecycle>
  @meta.at_xpath('./a:lifecycle/a:eventRef[@type="repeal"]', a: NS)
end

#repealed?Boolean

Has this by-law been repealed?

Returns:

  • (Boolean)


365
366
367
# File 'lib/slaw/act.rb', line 365

def repealed?
  !!repealed_on
end

#repealed_byNokogiri::XML::Node

The element representing the reference that caused the repeal of this act, or nil.

Returns:



381
382
383
384
385
386
387
# File 'lib/slaw/act.rb', line 381

def repealed_by
  repeal_el = repeal
  return nil unless repeal_el

  source_id = repeal_el['source'].sub(/^#/, '')
  @meta.at_xpath("./a:references/a:passiveRef[@id='#{source_id}']", a: NS)
end

#repealed_onString

The date on which this act was repealed, or nil if never repealed

Returns:

  • (String)

    date of repeal or nil



372
373
374
375
# File 'lib/slaw/act.rb', line 372

def repealed_on
  repeal_el = repeal
  repeal_el ? Time.parse(repeal_el['date']) : nil
end

#schedulesNokogiri::XML::Node?

An act can contain schedules, additional (generally free-form) documents that are addendums to the the main body. A definition element must be part of a separate ‘component` and have a `doc` element with a name attribute of `schedules`.

Returns:



312
313
314
# File 'lib/slaw/act.rb', line 312

def schedules
  @doc.at_xpath('/a:akomaNtoso/a:components/a:component/a:doc[@name="schedules"]/a:mainBody', a: NS)
end

#sectionsArray<Nokogiri::XML::Node>

Sections of this act

Returns:



285
286
287
# File 'lib/slaw/act.rb', line 285

def sections
  @body.xpath('.//a:section', a: NS)
end

#term_definitionsString => List(String, Nokogiri::XML::Node)

Get a map from term ids to ‘[term, defn]` pairs, where `term+ is the plain text term and `defn` is the Nokogiri::XML::Node containing the definition.

Returns:

  • (String => List(String, Nokogiri::XML::Node))

    map from strings to ‘[term, definition]` pairs



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/slaw/act.rb', line 321

def term_definitions
  terms = {}

  @meta.xpath('a:references/a:TLCTerm', a: NS).each do |node|
    # <TLCTerm id="term-affected_land" href="/ontology/term/this.eng.affected_land" showAs="affected land"/>

    # find the point with id 'def-term-foo'
    defn = @body.at_xpath(".//*[@id='def-#{node['id']}']", a: NS)
    next unless defn

    terms[node['id']] = [node['showAs'], defn]
  end

  terms
end

#titleString

An applicable short title for this act, either from the ‘FRBRalias` element or based on the act number and year.

Returns:

  • (String)


174
175
176
177
# File 'lib/slaw/act.rb', line 174

def title
  node = @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: NS)
  node ? node['value'] : "Act #{num} of #{year}"
end

#title=(value) ⇒ Object

Change the title of this act.



180
181
182
183
184
185
186
187
188
# File 'lib/slaw/act.rb', line 180

def title=(value)
  node = @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: NS)
  unless node
    node = @doc.create_element('FRBRalias')
    @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRuri', a: NS).after(node)
  end

  node['value'] = value
end

#validateObject

Validate the XML behind this document against the Akoma Ntoso schema and return any errors.

Returns:

  • (Object)

    array of errors, possibly empty



413
414
415
416
# File 'lib/slaw/act.rb', line 413

def validate
  @schema ||= Dir.chdir(File.dirname(__FILE__) + "/schemas") { Nokogiri::XML::Schema(File.read('akomantoso20.xsd')) }
  @schema.validate(@doc)
end

#validates?Boolean

Does this document validate against the schema?

Returns:

  • (Boolean)

See Also:

  • Slaw::Act.{{#validate}


421
422
423
# File 'lib/slaw/act.rb', line 421

def validates?
  validate.empty?
end