slaw 1.0.0.alpha.6 → 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
- data/README.md +13 -147
- data/bin/slaw +2 -1
- data/lib/slaw.rb +0 -6
- data/lib/slaw/generator.rb +2 -8
- data/lib/slaw/grammars/pl/act.treetop +10 -14
- data/lib/slaw/grammars/pl/act_text.xsl +271 -0
- data/lib/slaw/version.rb +1 -1
- data/slaw.gemspec +3 -3
- metadata +6 -17
- data/lib/slaw/act.rb +0 -452
- data/lib/slaw/bylaw.rb +0 -62
- data/lib/slaw/collection.rb +0 -60
- data/lib/slaw/lifecycle_event.rb +0 -23
- data/lib/slaw/render/html.rb +0 -70
- data/lib/slaw/render/xsl/act.xsl +0 -15
- data/lib/slaw/render/xsl/elements.xsl +0 -120
- data/lib/slaw/render/xsl/fragment.xsl +0 -16
- data/spec/act_spec.rb +0 -56
- data/spec/bylaw_spec.rb +0 -49
data/lib/slaw/bylaw.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'slaw/act'
|
2
|
-
|
3
|
-
module Slaw
|
4
|
-
# An extension of {Slaw::Act} which wraps an AkomaNtoso XML document describing an By-Law.
|
5
|
-
#
|
6
|
-
# There are minor differences between Acts and By-laws, the most notable being that a by-law
|
7
|
-
# is not identified by a year and a number, and therefore has a different FRBR uri structure.
|
8
|
-
class ByLaw < Act
|
9
|
-
|
10
|
-
# [String] The code of the region this by-law applies to
|
11
|
-
attr_reader :region
|
12
|
-
|
13
|
-
# [String] A short file-like name of this by-law, unique within its year and region
|
14
|
-
attr_reader :name
|
15
|
-
|
16
|
-
# ByLaws don't have numbers, use their short-name instead
|
17
|
-
def num
|
18
|
-
name
|
19
|
-
end
|
20
|
-
|
21
|
-
def title
|
22
|
-
node = @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: NS)
|
23
|
-
title = node ? node['value'] : "(Unknown)"
|
24
|
-
|
25
|
-
if amended? and not title.end_with?("as amended")
|
26
|
-
title = title + " as amended"
|
27
|
-
end
|
28
|
-
|
29
|
-
title
|
30
|
-
end
|
31
|
-
|
32
|
-
# Set the short (file-like) name for this bylaw. This changes the {#id_uri}.
|
33
|
-
def name=(value)
|
34
|
-
@name = value
|
35
|
-
rebuild_id_uri
|
36
|
-
end
|
37
|
-
|
38
|
-
# Set the region code for this bylaw. This changes the {#id_uri}.
|
39
|
-
def region=(value)
|
40
|
-
@region = value
|
41
|
-
rebuild_id_uri
|
42
|
-
end
|
43
|
-
|
44
|
-
protected
|
45
|
-
|
46
|
-
def extract_id_uri
|
47
|
-
# /za/by-law/cape-town/2010/public-parks
|
48
|
-
|
49
|
-
@id_uri = @meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRuri', a: NS)['value']
|
50
|
-
empty, @country, @nature, @region, date, @name = @id_uri.split('/')
|
51
|
-
|
52
|
-
# yyyy[-mm-dd]
|
53
|
-
@year = date.split('-', 2)[0]
|
54
|
-
end
|
55
|
-
|
56
|
-
def build_id_uri
|
57
|
-
# /za/by-law/cape-town/2010/public-parks
|
58
|
-
"/#{@country}/#{@nature}/#{@region}/#{@year}/#{@name}"
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
data/lib/slaw/collection.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'forwardable'
|
2
|
-
|
3
|
-
module Slaw
|
4
|
-
# A collection of Act instances.
|
5
|
-
#
|
6
|
-
# This is useful for looking up acts by their FRBR uri and for
|
7
|
-
# loading a collection of XML act documents.
|
8
|
-
#
|
9
|
-
# This collection is enumerable and can be iterated over. Use {#items} to
|
10
|
-
# access the underlying array of objects.
|
11
|
-
#
|
12
|
-
# @example Load a collection of acts and then iterate over them.
|
13
|
-
#
|
14
|
-
# acts = Slaw::DocumentCollection.new
|
15
|
-
# acts.discover('/path/to/acts/')
|
16
|
-
#
|
17
|
-
# for act in acts
|
18
|
-
# puts act.short_name
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
class DocumentCollection
|
22
|
-
|
23
|
-
include Enumerable
|
24
|
-
extend Forwardable
|
25
|
-
|
26
|
-
# [Array<Act>] The underlying array of acts
|
27
|
-
attr_accessor :items
|
28
|
-
|
29
|
-
def_delegators :items, :each, :<<, :length
|
30
|
-
|
31
|
-
def initialize(items=nil)
|
32
|
-
@items = items || []
|
33
|
-
end
|
34
|
-
|
35
|
-
# Find all XML files in `path` and add them into this
|
36
|
-
# collection.
|
37
|
-
#
|
38
|
-
# @param path [String] the path to glob for xml files
|
39
|
-
# @param cls [Class] the class to instantiate for each file
|
40
|
-
#
|
41
|
-
# @return [DocumentCollection] this collection
|
42
|
-
def discover(path, cls=Slaw::Act)
|
43
|
-
for fname in Dir.glob("#{path}/**/*.xml")
|
44
|
-
@items << cls.new(fname)
|
45
|
-
end
|
46
|
-
|
47
|
-
self
|
48
|
-
end
|
49
|
-
|
50
|
-
# Try to find an act who's FRBRuri matches this one,
|
51
|
-
# returning nil on failure
|
52
|
-
#
|
53
|
-
# @param uri [String] the uri to look for
|
54
|
-
#
|
55
|
-
# @return [Act, nil] the act, or nil
|
56
|
-
def for_uri(uri)
|
57
|
-
return @items.find { |doc| doc.id_uri == uri }
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/lib/slaw/lifecycle_event.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Slaw
|
2
|
-
# An event in the lifecycle of an act
|
3
|
-
class LifecycleEvent
|
4
|
-
include Slaw::Namespace
|
5
|
-
|
6
|
-
# Date of the event
|
7
|
-
attr_accessor :date
|
8
|
-
|
9
|
-
# type of the event
|
10
|
-
attr_accessor :type
|
11
|
-
|
12
|
-
# the source of the event, an XML reference element
|
13
|
-
attr_accessor :source
|
14
|
-
|
15
|
-
def initialize(element)
|
16
|
-
@date = element['date']
|
17
|
-
@type = element['type']
|
18
|
-
|
19
|
-
source_id = element['source'][1..-1]
|
20
|
-
@source = element.document.at_xpath("//a:references/*[@id=\"#{source_id}\"]", a: NS)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/lib/slaw/render/html.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
module Slaw
|
2
|
-
module Render
|
3
|
-
|
4
|
-
# Support for transforming XML AN documents into HTML.
|
5
|
-
#
|
6
|
-
# This rendering is done using XSLT stylesheets. Both an entire
|
7
|
-
# document and fragments can be rendered.
|
8
|
-
class HTMLRenderer
|
9
|
-
|
10
|
-
# [Hash] A Hash of Nokogiri::XSLT objects
|
11
|
-
attr_accessor :xslt
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
here = File.dirname(__FILE__)
|
15
|
-
|
16
|
-
@xslt = {
|
17
|
-
act: Nokogiri::XSLT(File.open(File.join([here, 'xsl/act.xsl']))),
|
18
|
-
fragment: Nokogiri::XSLT(File.open(File.join([here, 'xsl/fragment.xsl']))),
|
19
|
-
}
|
20
|
-
end
|
21
|
-
|
22
|
-
# Transform an entire XML document (a Nokogiri::XML::Document object) into HTML.
|
23
|
-
# Specify `base_url` to manage the base for relative URLs generated by
|
24
|
-
# the transform.
|
25
|
-
#
|
26
|
-
# @param doc [Nokogiri::XML::Document] document to render
|
27
|
-
# @param base_url [String] root URL for relative URLs (cannot be empty)
|
28
|
-
#
|
29
|
-
# @return [String]
|
30
|
-
def render(doc, base_url='')
|
31
|
-
params = _transform_params({'base_url' => base_url})
|
32
|
-
_run_xslt(:act, doc, params)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Transform just a single node and its children into HTML.
|
36
|
-
#
|
37
|
-
# If +elem+ has an id, we use xpath to tell the XSLT which
|
38
|
-
# element to transform. Otherwise we copy the node into a new
|
39
|
-
# tree and apply the XSLT to that.
|
40
|
-
#
|
41
|
-
# @param node [Nokogiri::XML::Node] node to render
|
42
|
-
# @param base_url [String] root URL for relative URLs (cannot be empty)
|
43
|
-
#
|
44
|
-
# @return [String]
|
45
|
-
def render_node(node, base_url='')
|
46
|
-
params = _transform_params({'base_url' => base_url})
|
47
|
-
|
48
|
-
if node.id
|
49
|
-
params += ['root_elem', "//*[@id='#{node.id}']"]
|
50
|
-
doc = node.document
|
51
|
-
else
|
52
|
-
# create a new document with just this element at the root
|
53
|
-
doc = Nokogiri::XML::Document.new
|
54
|
-
doc.root = node
|
55
|
-
params += ['root_elem', '*']
|
56
|
-
end
|
57
|
-
|
58
|
-
_run_xslt(:fragment, doc, params)
|
59
|
-
end
|
60
|
-
|
61
|
-
def _run_xslt(xslt, doc, params)
|
62
|
-
@xslt[xslt].transform(doc, params).to_s
|
63
|
-
end
|
64
|
-
|
65
|
-
def _transform_params(params)
|
66
|
-
Nokogiri::XSLT.quote_params(params)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
data/lib/slaw/render/xsl/act.xsl
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
<?xml version="1.0"?>
|
2
|
-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
|
3
|
-
xmlns:a="http://www.akomantoso.org/2.0"
|
4
|
-
exclude-result-prefixes="a">
|
5
|
-
|
6
|
-
<xsl:import href="elements.xsl" />
|
7
|
-
|
8
|
-
<xsl:output method="html" />
|
9
|
-
|
10
|
-
<xsl:template match="/">
|
11
|
-
<xsl:apply-templates select="a:akomaNtoso/a:act" />
|
12
|
-
</xsl:template>
|
13
|
-
|
14
|
-
</xsl:stylesheet>
|
15
|
-
|
@@ -1,120 +0,0 @@
|
|
1
|
-
<?xml version="1.0"?>
|
2
|
-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
|
3
|
-
xmlns:a="http://www.akomantoso.org/2.0"
|
4
|
-
exclude-result-prefixes="a">
|
5
|
-
|
6
|
-
<xsl:template match="a:act">
|
7
|
-
<xsl:element name="span" namespace="">
|
8
|
-
<xsl:attribute name="class">an-act</xsl:attribute>
|
9
|
-
<xsl:apply-templates select="a:preamble" />
|
10
|
-
<xsl:apply-templates select="a:body/a:part | a:body/a:chapter | a:body/a:section" />
|
11
|
-
</xsl:element>
|
12
|
-
</xsl:template>
|
13
|
-
|
14
|
-
<!-- for parts and chapters, include an easily stylable heading -->
|
15
|
-
<xsl:template match="a:part">
|
16
|
-
<div class="an-part" id="{@id}">
|
17
|
-
<h1>
|
18
|
-
<xsl:text>Part </xsl:text>
|
19
|
-
<xsl:value-of select="./a:num" />
|
20
|
-
<xsl:text> - </xsl:text>
|
21
|
-
<xsl:value-of select="./a:heading" />
|
22
|
-
</h1>
|
23
|
-
|
24
|
-
<xsl:apply-templates select="./*[not(self::a:num) and not(self::a:heading)]" />
|
25
|
-
</div>
|
26
|
-
</xsl:template>
|
27
|
-
|
28
|
-
<xsl:template match="a:chapter">
|
29
|
-
<div class="an-chapter" id="{@id}">
|
30
|
-
<h1>
|
31
|
-
<xsl:text>Chapter </xsl:text>
|
32
|
-
<xsl:value-of select="./a:num" />
|
33
|
-
<br/>
|
34
|
-
<xsl:value-of select="./a:heading" />
|
35
|
-
</h1>
|
36
|
-
|
37
|
-
<xsl:apply-templates select="./*[not(self::a:num) and not(self::a:heading)]" />
|
38
|
-
</div>
|
39
|
-
</xsl:template>
|
40
|
-
|
41
|
-
<!-- the schedules "chapter" isn't actually a chapter -->
|
42
|
-
<xsl:template match="a:chapter[starts-with(@id, 'schedule')]">
|
43
|
-
<div class="an-chapter" id="{@id}">
|
44
|
-
<h1>
|
45
|
-
<xsl:text>Schedule </xsl:text>
|
46
|
-
<xsl:value-of select="./a:num" />
|
47
|
-
<br/>
|
48
|
-
<xsl:value-of select="./a:heading" />
|
49
|
-
</h1>
|
50
|
-
|
51
|
-
<xsl:apply-templates select="./*[not(self::a:num) and not(self::a:heading)]" />
|
52
|
-
</div>
|
53
|
-
</xsl:template>
|
54
|
-
|
55
|
-
<xsl:template match="a:section">
|
56
|
-
<div class="an-{local-name()}" id="{@id}">
|
57
|
-
<h3>
|
58
|
-
<xsl:value-of select="./a:num" />
|
59
|
-
<xsl:text> </xsl:text>
|
60
|
-
<xsl:value-of select="./a:heading" />
|
61
|
-
</h3>
|
62
|
-
|
63
|
-
<xsl:apply-templates select="./*[not(self::a:num) and not(self::a:heading)]" />
|
64
|
-
</div>
|
65
|
-
</xsl:template>
|
66
|
-
|
67
|
-
<xsl:template match="a:subsection">
|
68
|
-
<span class="an-{local-name()}" id="{@id}">
|
69
|
-
<xsl:apply-templates select="./*[not(self::a:heading)]" />
|
70
|
-
</span>
|
71
|
-
</xsl:template>
|
72
|
-
|
73
|
-
<!-- for term nodes, ensure we keep the refersTo element -->
|
74
|
-
<xsl:template match="a:term">
|
75
|
-
<a class="an-{local-name()}">
|
76
|
-
<xsl:attribute name="data-refers-to">
|
77
|
-
<xsl:value-of select="@refersTo" />
|
78
|
-
</xsl:attribute>
|
79
|
-
|
80
|
-
<xsl:attribute name="href"><xsl:value-of select="$base_url" />/definitions/#def-<xsl:value-of select="translate(@refersTo, '#', '')" /></xsl:attribute>
|
81
|
-
|
82
|
-
<xsl:apply-templates />
|
83
|
-
</a>
|
84
|
-
</xsl:template>
|
85
|
-
|
86
|
-
<!-- for all nodes, generate a SPAN element with a class matching
|
87
|
-
the AN name of the node and copy over the ID if it exists -->
|
88
|
-
<xsl:template match="*">
|
89
|
-
<span class="an-{local-name()}">
|
90
|
-
<xsl:if test="@id">
|
91
|
-
<xsl:attribute name="id">
|
92
|
-
<xsl:value-of select="@id" />
|
93
|
-
</xsl:attribute>
|
94
|
-
</xsl:if>
|
95
|
-
<xsl:apply-templates />
|
96
|
-
</span>
|
97
|
-
</xsl:template>
|
98
|
-
|
99
|
-
<!-- For HTML table elements, copy them over then apply normal AN
|
100
|
-
processing to their contents -->
|
101
|
-
<xsl:template match="a:table | a:tr | a:th | a:td">
|
102
|
-
<xsl:element name="{local-name()}">
|
103
|
-
<xsl:copy-of select="@*" />
|
104
|
-
<xsl:apply-templates />
|
105
|
-
</xsl:element>
|
106
|
-
</xsl:template>
|
107
|
-
|
108
|
-
<!-- special HTML elements -->
|
109
|
-
<xsl:template match="a:a | a:abbr | a:b | a:i | a:span | a:sub | a:sup | a:u">
|
110
|
-
<xsl:element name="{local-name()}">
|
111
|
-
<xsl:copy-of select="@*" />
|
112
|
-
<xsl:apply-templates />
|
113
|
-
</xsl:element>
|
114
|
-
</xsl:template>
|
115
|
-
|
116
|
-
<xsl:template match="a:eol">
|
117
|
-
<xsl:element name="br" />
|
118
|
-
</xsl:template>
|
119
|
-
|
120
|
-
</xsl:stylesheet>
|
@@ -1,16 +0,0 @@
|
|
1
|
-
<?xml version="1.0"?>
|
2
|
-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
|
3
|
-
xmlns:a="http://www.akomantoso.org/2.0"
|
4
|
-
exclude-result-prefixes="a">
|
5
|
-
|
6
|
-
<xsl:import href="elements.xsl" />
|
7
|
-
|
8
|
-
<xsl:output method="html" />
|
9
|
-
|
10
|
-
<xsl:template match="/">
|
11
|
-
<!-- root_elem is passed in as an xpath parameter -->
|
12
|
-
<xsl:apply-templates select="$root_elem" />
|
13
|
-
</xsl:template>
|
14
|
-
|
15
|
-
</xsl:stylesheet>
|
16
|
-
|
data/spec/act_spec.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'slaw'
|
5
|
-
|
6
|
-
describe Slaw::Act do
|
7
|
-
let(:filename) { File.dirname(__FILE__) + "/fixtures/community-fire-safety.xml" }
|
8
|
-
subject { Slaw::Act.new(filename) }
|
9
|
-
|
10
|
-
it 'should have correct basic properties' do
|
11
|
-
subject.title.should == 'Community Fire Safety By-law'
|
12
|
-
subject.amended?.should be_true
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'should set the title correctly' do
|
16
|
-
subject.title = 'foo'
|
17
|
-
subject.meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: Slaw::NS)['value'].should == 'foo'
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'should set the title if it doesnt exist' do
|
21
|
-
subject.meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: Slaw::NS).remove
|
22
|
-
subject.title = 'bar'
|
23
|
-
subject.title.should == 'bar'
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'should set the publication details' do
|
27
|
-
subject.meta.at_xpath('./a:publication', a: Slaw::NS).remove
|
28
|
-
|
29
|
-
subject.published!(name: 'foo', number: '1234', date: '2014-01-01')
|
30
|
-
subject.publication['name'].should == 'foo'
|
31
|
-
subject.publication['showAs'].should == 'foo'
|
32
|
-
subject.publication['number'].should == '1234'
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'should get/set the work date' do
|
36
|
-
subject.date.should == '2002-02-28'
|
37
|
-
|
38
|
-
subject.date = '2014-01-01'
|
39
|
-
subject.date.should == '2014-01-01'
|
40
|
-
subject.meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRdate[@name="Generation"]', a: Slaw::NS)['date'].should == '2014-01-01'
|
41
|
-
subject.meta.at_xpath('./a:identification/a:FRBRExpression/a:FRBRdate[@name="Generation"]', a: Slaw::NS)['date'].should == '2014-01-01'
|
42
|
-
|
43
|
-
subject.id_uri.should == '/za/by-law/2014/2002'
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'should update the uri when the year changes' do
|
47
|
-
subject.id_uri.should == '/za/by-law/cape-town/2002/community-fire-safety'
|
48
|
-
subject.year = '1980'
|
49
|
-
subject.id_uri.should == '/za/by-law/1980/2002'
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should validate' do
|
53
|
-
subject.validate.should == []
|
54
|
-
subject.validates?.should be_true
|
55
|
-
end
|
56
|
-
end
|
data/spec/bylaw_spec.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'slaw'
|
5
|
-
|
6
|
-
describe Slaw::ByLaw do
|
7
|
-
let(:filename) { File.dirname(__FILE__) + "/fixtures/community-fire-safety.xml" }
|
8
|
-
subject { Slaw::ByLaw.new(filename) }
|
9
|
-
|
10
|
-
it 'should have correct basic properties' do
|
11
|
-
subject.title.should == 'Community Fire Safety By-law as amended'
|
12
|
-
subject.amended?.should be_true
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'should update the uri when the region changes' do
|
16
|
-
subject.id_uri.should == '/za/by-law/cape-town/2002/community-fire-safety'
|
17
|
-
subject.region = 'foo-bar'
|
18
|
-
subject.id_uri.should == '/za/by-law/foo-bar/2002/community-fire-safety'
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should update the uri when the name changes' do
|
22
|
-
subject.id_uri.should == '/za/by-law/cape-town/2002/community-fire-safety'
|
23
|
-
subject.name = 'foo-bar'
|
24
|
-
subject.id_uri.should == '/za/by-law/cape-town/2002/foo-bar'
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'should set the title if it doesnt exist' do
|
28
|
-
subject.meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRalias', a: Slaw::NS).remove
|
29
|
-
subject.title = 'bar'
|
30
|
-
subject.title.should == 'bar as amended'
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'should get/set the work date' do
|
34
|
-
subject.date.should == '2002-02-28'
|
35
|
-
|
36
|
-
subject.date = '2014-01-01'
|
37
|
-
subject.date.should == '2014-01-01'
|
38
|
-
subject.meta.at_xpath('./a:identification/a:FRBRWork/a:FRBRdate[@name="Generation"]', a: Slaw::NS)['date'].should == '2014-01-01'
|
39
|
-
subject.meta.at_xpath('./a:identification/a:FRBRExpression/a:FRBRdate[@name="Generation"]', a: Slaw::NS)['date'].should == '2014-01-01'
|
40
|
-
|
41
|
-
subject.id_uri.should == '/za/by-law/cape-town/2014/community-fire-safety'
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'should update the uri when the year changes' do
|
45
|
-
subject.id_uri.should == '/za/by-law/cape-town/2002/community-fire-safety'
|
46
|
-
subject.year = '1980'
|
47
|
-
subject.id_uri.should == '/za/by-law/cape-town/1980/community-fire-safety'
|
48
|
-
end
|
49
|
-
end
|