rtm-activerecord 0.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.
- data/DISCLAIMER +13 -0
- data/LICENSE +201 -0
- data/README +4 -0
- data/lib/rtm/activerecord/001_initial_schema.rb +119 -0
- data/lib/rtm/activerecord/association_and_role.rb +54 -0
- data/lib/rtm/activerecord/base.rb +83 -0
- data/lib/rtm/activerecord/connect.rb +106 -0
- data/lib/rtm/activerecord/io/from_xtm2.rb +266 -0
- data/lib/rtm/activerecord/io/from_xtm2_libxml.rb +97 -0
- data/lib/rtm/activerecord/io/to_jtm.rb +141 -0
- data/lib/rtm/activerecord/io/to_string.rb +130 -0
- data/lib/rtm/activerecord/io/to_xtm1.rb +162 -0
- data/lib/rtm/activerecord/io/to_xtm2.rb +163 -0
- data/lib/rtm/activerecord/io/to_yaml.rb +132 -0
- data/lib/rtm/activerecord/literal_index.rb +38 -0
- data/lib/rtm/activerecord/locators.rb +58 -0
- data/lib/rtm/activerecord/merging.rb +310 -0
- data/lib/rtm/activerecord/name_variant_occurrence.rb +86 -0
- data/lib/rtm/activerecord/persistent_code_output.rb +22 -0
- data/lib/rtm/activerecord/quaaxtm2rtm.rb +116 -0
- data/lib/rtm/activerecord/quaaxtm2rtmviews.rb +137 -0
- data/lib/rtm/activerecord/set_wrapper.rb +101 -0
- data/lib/rtm/activerecord/sugar/association/hash_access.rb +22 -0
- data/lib/rtm/activerecord/sugar/role/counterparts.rb +56 -0
- data/lib/rtm/activerecord/sugar/topic/characteristics.rb +15 -0
- data/lib/rtm/activerecord/sugar/topic/counterparts.rb +18 -0
- data/lib/rtm/activerecord/sugar/topic/hash_access.rb +78 -0
- data/lib/rtm/activerecord/sugar/topic/identifier_direct.rb +14 -0
- data/lib/rtm/activerecord/sugar/topic/predefined_associations.rb +53 -0
- data/lib/rtm/activerecord/tm_construct.rb +66 -0
- data/lib/rtm/activerecord/tm_delegator.rb +417 -0
- data/lib/rtm/activerecord/tm_set_delegator.rb +226 -0
- data/lib/rtm/activerecord/tmdm.rb +309 -0
- data/lib/rtm/activerecord/topic.rb +204 -0
- data/lib/rtm/activerecord/topic_map.rb +435 -0
- data/lib/rtm/activerecord/traverse_associations.rb +90 -0
- data/lib/rtm/activerecord.rb +106 -0
- metadata +120 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
|
2
|
+
# License: Apache License, Version 2.0
|
3
|
+
|
4
|
+
require 'active_record'
|
5
|
+
require 'rtm/backend/active_record/001_initial_schema'
|
6
|
+
require "rtm/backend/active_record/io/from_xtm2"
|
7
|
+
# require "rtm/backend/active_record/io/from_xtm2_libxml"
|
8
|
+
|
9
|
+
module RTM
|
10
|
+
|
11
|
+
class RTMAR
|
12
|
+
include Singleton
|
13
|
+
include RTM::TopicMapSystem
|
14
|
+
|
15
|
+
|
16
|
+
def create(base_locator)
|
17
|
+
AR::TopicMap.create(base_locator)
|
18
|
+
end
|
19
|
+
|
20
|
+
def topic_maps
|
21
|
+
AR::TopicMap.topic_maps
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](*args)
|
25
|
+
if args.size == 0
|
26
|
+
return AR::TopicMap.topic_maps
|
27
|
+
end
|
28
|
+
args.map { |m| AR::TopicMap.topic_maps[m] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Assignes a logger to active record (or STDOUT as default) which outputs
|
32
|
+
# the sql statements.
|
33
|
+
def log(to=STDOUT)
|
34
|
+
ActiveRecord::Base.logger = Logger.new(to)
|
35
|
+
ActiveRecord::Base.colorize_logging = false if PLATFORM =~ /mswin/
|
36
|
+
end
|
37
|
+
# This function generates the database schema using the default
|
38
|
+
# ActiveRecord connection.
|
39
|
+
def generate_database
|
40
|
+
RTM::AR::TMDM::InitialSchema.migrate(:up)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Connects to the sqlite3 database given in the path or to
|
44
|
+
# a default database if no path is given.
|
45
|
+
def connect_sqlite3(dbname="tmdm.sqlite3")
|
46
|
+
ActiveRecord::Base.establish_connection(
|
47
|
+
:adapter => platform_specific_adapter("sqlite3"),
|
48
|
+
:database => dbname
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Connects to a mysql database. Parameters are
|
53
|
+
# either
|
54
|
+
# database, username, password, host
|
55
|
+
# or
|
56
|
+
# only a keyword hash with these parameters.
|
57
|
+
def connect_mysql(database="rtm_development", username=nil, password=nil, host="localhost")
|
58
|
+
if database.is_a? Hash
|
59
|
+
return ActiveRecord::Base.establish_connection(database)
|
60
|
+
end
|
61
|
+
ActiveRecord::Base.establish_connection(
|
62
|
+
:adapter => platform_specific_adapter("mysql"),
|
63
|
+
:database => database,
|
64
|
+
:username => username,
|
65
|
+
:password => password,
|
66
|
+
:host => host
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Connects to a in-memory database. No parameters required.
|
71
|
+
# Schema will automatically be generated.
|
72
|
+
def connect_memory
|
73
|
+
ActiveRecord::Base.establish_connection(
|
74
|
+
:adapter => platform_specific_adapter("sqlite3"),
|
75
|
+
:database => ":memory:"
|
76
|
+
)
|
77
|
+
no_output do
|
78
|
+
generate_database
|
79
|
+
end
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Connects Active Record to a database or in Memory database if no parameters given.
|
84
|
+
def connect(*args)
|
85
|
+
if args.size == 0
|
86
|
+
return connect_memory
|
87
|
+
end
|
88
|
+
ActiveRecord::Base.establish_connection(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def platform_specific_adapter(adapter)
|
93
|
+
if PLATFORM && PLATFORM =~ /java/
|
94
|
+
return "jdbc#{adapter}"
|
95
|
+
end
|
96
|
+
return adapter
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
# Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
|
2
|
+
# License: Apache License, Version 2.0
|
3
|
+
|
4
|
+
# Ruby Topic Maps (RTM) http://rtm.rubyforge.org/
|
5
|
+
|
6
|
+
class RTM::RTMAR
|
7
|
+
def from_xtm2(*args)
|
8
|
+
RTM::AR::IO::FROMXTM2.from_xtm2(self, *args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
module RTM::AR::IO
|
12
|
+
# XTM2 Export.
|
13
|
+
# Each Topic Maps Construct gets a to_xtm2 method.
|
14
|
+
# The result is a REXML::Element except for TopicMap where it is a REXML::Document.
|
15
|
+
module FROMXTM2
|
16
|
+
require 'rexml/document'
|
17
|
+
require 'rexml/parsers/sax2parser'
|
18
|
+
require 'rexml/sax2listener'
|
19
|
+
#require 'jrexml'
|
20
|
+
XTM2DEBUG = false
|
21
|
+
|
22
|
+
# Reads XTM2 from source (io object).
|
23
|
+
# Example: RTM::IO::FROMXTM2.from_xtm2(File.open(file_name),"http://rtm.rubyforge.org/topicmaps/tm1/")
|
24
|
+
# supported options:
|
25
|
+
# :strip_whitespace (defaults to false, may be set to true),
|
26
|
+
# :deprefix (defaults to nil, may be set to a string (or regex) which will be removed from the beginning of an (unresolved) item_identifier if it is there.
|
27
|
+
def self.from_xtm2(base_tms, source, base_locator, target=nil,options={})
|
28
|
+
tm = base_tms.create(base_locator) unless target
|
29
|
+
list = XTM2Listener.new(base_locator, target || tm, options)
|
30
|
+
parser = REXML::Parsers::SAX2Parser.new(source)
|
31
|
+
parser.listen(list)
|
32
|
+
parser.parse
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
class XTM2Listener
|
37
|
+
include REXML::SAX2Listener
|
38
|
+
def initialize(base_locator, target,options={})
|
39
|
+
@base_locator = base_locator
|
40
|
+
@target = target
|
41
|
+
@targets = []
|
42
|
+
@path = []
|
43
|
+
@options=options
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def resolve(iri)
|
48
|
+
warn("resolving nil iri can have unexpected results") unless iri
|
49
|
+
# TODO handle %HH sequences, here or in backend/active_record.rb
|
50
|
+
if @options[:deprefix]
|
51
|
+
iri = iri.gsub(/^#{@options[:deprefix]}/,'')
|
52
|
+
end
|
53
|
+
@target.resolve(iri, @base_locator)
|
54
|
+
end
|
55
|
+
|
56
|
+
public
|
57
|
+
def start_element(uri, name, qname, attrs)
|
58
|
+
return unless uri == "http://www.topicmaps.org/xtm/"
|
59
|
+
a = attrs #Hash[* attrs.flatten]
|
60
|
+
#puts "Start of tag #{name} - attrs: #{a.inspect}"
|
61
|
+
|
62
|
+
case name
|
63
|
+
when "topic"
|
64
|
+
@topic = @targets.last.topic_by_item_identifier! resolve(a["id"])
|
65
|
+
@targets.push @topic
|
66
|
+
when "association"
|
67
|
+
assoc = nil
|
68
|
+
if a["reifier"]
|
69
|
+
ref = @targets.last.topic_map.topic_by_item_identifier! resolve(a["reifier"])
|
70
|
+
raise "The topic reifiing this association already reifies something else: #{ref}." if ref.reified && !ref.reified.is_a?(RTM::Association)
|
71
|
+
if ref.reified
|
72
|
+
assoc = ref.reified
|
73
|
+
else
|
74
|
+
assoc = @targets.last.create_association
|
75
|
+
assoc.reifier = ref
|
76
|
+
end
|
77
|
+
end
|
78
|
+
assoc ||= @targets.last.create_association
|
79
|
+
@targets.push assoc
|
80
|
+
when "role"
|
81
|
+
if a["reifier"]
|
82
|
+
ref = @targets.last.topic_map.topic_by_item_identifier! resolve(a["reifier"])
|
83
|
+
raise "The topic reifiing this association role already reifies something else: #{ref}." if ref.reified && !ref.reified.is_a?(RTM::AssociationRole)
|
84
|
+
if ref.reified
|
85
|
+
role = ref.reified
|
86
|
+
else
|
87
|
+
role = @targets.last.create_role
|
88
|
+
role.reifier = ref
|
89
|
+
end
|
90
|
+
end
|
91
|
+
role ||= @targets.last.create_role
|
92
|
+
@targets.push role
|
93
|
+
when "name"
|
94
|
+
if a["reifier"]
|
95
|
+
ref = @targets.last.topic_map.topic_by_item_identifier! resolve(a["reifier"])
|
96
|
+
raise "The topic reifiing this topic name already reifies something else: #{ref}." if ref.reified && !ref.reified.is_a?(RTM::TopicName)
|
97
|
+
if ref.reified
|
98
|
+
tname = ref.reified
|
99
|
+
else
|
100
|
+
tname = @targets.last.create_name
|
101
|
+
tname.reifier = ref
|
102
|
+
end
|
103
|
+
end
|
104
|
+
tname ||= @targets.last.create_name
|
105
|
+
@targets.push tname
|
106
|
+
when "value"
|
107
|
+
# this is handled in text()
|
108
|
+
when "variant"
|
109
|
+
if a["reifier"]
|
110
|
+
ref = @targets.last.topic_map.topic_by_item_identifier! resolve(a["reifier"])
|
111
|
+
raise "The topic reifiing this topic variant already reifies something else: #{ref}." if ref.reified && !ref.reified.is_a?(RTM::Variant)
|
112
|
+
if ref.reified
|
113
|
+
variant = ref.reified
|
114
|
+
else
|
115
|
+
variant = @targets.last.create_variant
|
116
|
+
variant.reifier = ref
|
117
|
+
end
|
118
|
+
end
|
119
|
+
variant ||= @targets.last.create_variant
|
120
|
+
@targets.push variant
|
121
|
+
when "scope"
|
122
|
+
# nothing to be done here :)
|
123
|
+
when "instanceOf"
|
124
|
+
# nothing to be done here :)
|
125
|
+
when "type"
|
126
|
+
# nothing to be done here :)
|
127
|
+
when "occurrence"
|
128
|
+
if a["reifier"]
|
129
|
+
ref = @targets.last.topic_map.topic_by_item_identifier! resolve(a["reifier"])
|
130
|
+
raise "The topic reifiing this topic occurrence already reifies something else: #{ref}." if ref.reified && !ref.reified.is_a?(RTM::Occurrence)
|
131
|
+
if ref.reified
|
132
|
+
occur = ref.reified
|
133
|
+
else
|
134
|
+
occur = @targets.last.create_occurrence
|
135
|
+
occur.reifier = ref
|
136
|
+
end
|
137
|
+
end
|
138
|
+
occur ||= @targets.last.create_occurrence
|
139
|
+
@targets.push occur
|
140
|
+
when "resourceData"
|
141
|
+
occur_or_variant = @targets.last
|
142
|
+
if a["datatype"]
|
143
|
+
occur_or_variant.datatype = a["datatype"]
|
144
|
+
# special handling is done in tag_end
|
145
|
+
else
|
146
|
+
occur_or_variant.datatype = RTM::PSI[:String]
|
147
|
+
end
|
148
|
+
when "topicRef"
|
149
|
+
case @path.last
|
150
|
+
when "scope"
|
151
|
+
@targets.last.scope << a["href"]
|
152
|
+
when "instanceOf"
|
153
|
+
assoc = @targets.last.topic_map.create_association :type => RTM::PSI[:type_instance]
|
154
|
+
assoc.create_role @targets.last, RTM::PSI[:instance]
|
155
|
+
assoc.create_role a["href"], RTM::PSI[:type]
|
156
|
+
when "type"
|
157
|
+
@targets.last.type = a["href"]
|
158
|
+
when "role"
|
159
|
+
@targets.last.player = a["href"]
|
160
|
+
end
|
161
|
+
when "resourceRef"
|
162
|
+
occurrence = @targets.last
|
163
|
+
occurrence.datatype = RTM::PSI[:IRI]
|
164
|
+
occurrence.value = a["href"]
|
165
|
+
when "subjectLocator"
|
166
|
+
@targets.last.subject_locators << a["href"]
|
167
|
+
when "subjectIdentifier"
|
168
|
+
@targets.last.subject_identifiers << a["href"]
|
169
|
+
when "itemIdentity"
|
170
|
+
@targets.last.item_identifiers << resolve(a["href"])
|
171
|
+
when "topicMap"
|
172
|
+
# check where we are
|
173
|
+
raise "unexpected element topicMap" unless @path.empty?
|
174
|
+
# check version
|
175
|
+
raise "Sorry, only XTM 2.0 is supported" if a["version"] != "2.0"
|
176
|
+
|
177
|
+
# reifier. if target topic map is already reified we have to merge, if not create topic
|
178
|
+
if a["reifier"]
|
179
|
+
if @target.reifier # TODO this might be no topic here. In this case something should be raised!
|
180
|
+
@target.reifier.item_identifiers << resolve(a["reifier"])
|
181
|
+
else
|
182
|
+
@target.topic_by_item_identifier! resolve(a["reifier"])
|
183
|
+
end
|
184
|
+
end
|
185
|
+
@targets.push @target
|
186
|
+
when "mergeMap"
|
187
|
+
# TODO mergeMap
|
188
|
+
warn "mergeMap is not supported yet!"
|
189
|
+
else
|
190
|
+
warn "Unhandled element: #{name}"
|
191
|
+
end
|
192
|
+
|
193
|
+
@path.push name
|
194
|
+
end
|
195
|
+
|
196
|
+
# Called when the end tag is reached. In the case of <tag/>, tag_end
|
197
|
+
# will be called immidiately after tag_start
|
198
|
+
# @p the name of the tag
|
199
|
+
def end_element(uri, name, qname)
|
200
|
+
return unless uri == "http://www.topicmaps.org/xtm/"
|
201
|
+
#puts "end of tag: #{name}"
|
202
|
+
|
203
|
+
old_name = @path.pop
|
204
|
+
raise "Tag_end did not match: expected: #{old_name}, got: #{name}." unless old_name == name
|
205
|
+
case name
|
206
|
+
when "topicMap", "topic", "association", "role", "name", "variant", "occurrence"
|
207
|
+
@targets.pop
|
208
|
+
when "resourceData"
|
209
|
+
occur = @targets.last
|
210
|
+
# puts "finalizing occurrence #{occur.inspect}"
|
211
|
+
if occur.datatype == RTM::PSI[:IRI]
|
212
|
+
occur.value = resolve(occur.value)
|
213
|
+
elsif occur.datatype == RTM::PSI[:anyType]
|
214
|
+
# TODO Content must be canonicalized according to http://www.isotopicmaps.org/sam/sam-xtm/#sect-xml-canonicalization
|
215
|
+
warn("Content canonicalization is not yet implemented!")
|
216
|
+
end
|
217
|
+
# puts "leaving finalizing occurrence"
|
218
|
+
end
|
219
|
+
# puts "after case"
|
220
|
+
end
|
221
|
+
# Called when text is encountered in the document
|
222
|
+
# @p text the text content.
|
223
|
+
def characters(text)
|
224
|
+
if @options[:strip_whitespace]
|
225
|
+
text.strip!
|
226
|
+
return if text.empty?
|
227
|
+
end
|
228
|
+
case @path.last
|
229
|
+
when "value", "resourceData"
|
230
|
+
if @targets.last.value
|
231
|
+
@targets.last.value += text
|
232
|
+
else
|
233
|
+
@targets.last.value = text
|
234
|
+
end
|
235
|
+
else
|
236
|
+
puts "Found text but don't know that to do: #{text}" unless text.strip.empty?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def cdata content
|
241
|
+
if @options[:strip_whitespace]
|
242
|
+
content.strip!
|
243
|
+
return if content.empty?
|
244
|
+
end
|
245
|
+
case @path.last
|
246
|
+
when "value", "resourceData"
|
247
|
+
if @targets.last.value
|
248
|
+
@targets.last.value += content
|
249
|
+
else
|
250
|
+
@targets.last.value = content
|
251
|
+
end
|
252
|
+
else
|
253
|
+
puts "Found cdata but don't know that to do: #{content}" unless content.strip.empty?
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def xmldecl version, encoding, standalone
|
258
|
+
warn "XML Version 1.0 expected. Not sure if we can handle this, but we will try." unless version == "1.0"
|
259
|
+
warn "Be aware there is no encoding manipulation done, everything gets in as-is." if encoding
|
260
|
+
# what about standalone?
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Copyright: Copyright 2009 Topic Maps Lab, University of Leipzig.
|
2
|
+
# License: Apache License, Version 2.0
|
3
|
+
|
4
|
+
|
5
|
+
class RTM::RTMAR
|
6
|
+
def from_xtm2lx(*args)
|
7
|
+
RTM::AR::IO::FROMXTM2LX.from_xtm2(self, *args)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# class RTM::RTMAR
|
12
|
+
# def self.from_xtm2lx(*args)
|
13
|
+
# RTM::AR::IO::FROMXTM2LX.from_xtm2(self, *args)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
module RTM::AR::IO
|
20
|
+
# XTM2 Export.
|
21
|
+
# Each Topic Maps Construct gets a to_xtm2 method.
|
22
|
+
# The result is a REXML::Element except for TopicMap where it is a REXML::Document.
|
23
|
+
module FROMXTM2LX
|
24
|
+
require 'libxml'
|
25
|
+
require 'rexml/document'
|
26
|
+
require 'rexml/parsers/sax2parser'
|
27
|
+
require 'rexml/sax2listener'
|
28
|
+
XTM2DEBUG = false
|
29
|
+
|
30
|
+
# Reads XTM2 from source (io object).
|
31
|
+
# Example: RTM::IO::FROMXTM2.from_xtm2(File.open(file_name),"http://rtm.rubyforge.org/topicmaps/tm1/")
|
32
|
+
# supported options:
|
33
|
+
# :strip_whitespace (defaults to false, may be set to true),
|
34
|
+
# :deprefix (defaults to nil, may be set to a string (or regex) which will be removed from the beginning of an (unresolved) item_identifier if it is there.
|
35
|
+
def self.from_xtm2(base_tms, source, base_locator, target=nil,options={})
|
36
|
+
tm = base_tms.create(base_locator) unless target
|
37
|
+
parser = XML::SaxParser.new
|
38
|
+
parser.callbacks = XML::LibXMLSax2wrapper.new(FROMXTM2::XTM2Listener.new(base_locator, target || tm, options))
|
39
|
+
parser.filename = source
|
40
|
+
parser.parse
|
41
|
+
#true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module XML
|
47
|
+
# Acts as Callback structure for the LibXML-Ruby SAX Parser and calls
|
48
|
+
# a REXML SAX2Listener API.
|
49
|
+
class LibXMLSax2wrapper
|
50
|
+
include LibXML::XML::SaxParser::Callbacks
|
51
|
+
def initialize(rexml_sax2listener)
|
52
|
+
@dest = rexml_sax2listener
|
53
|
+
@ns = Hash.new("http://www.topicmaps.org/xtm/")
|
54
|
+
end
|
55
|
+
def on_start_document
|
56
|
+
@dest.start_document
|
57
|
+
end
|
58
|
+
def on_end_document
|
59
|
+
@dest.end_document
|
60
|
+
end
|
61
|
+
def on_start_element(qname, attr)
|
62
|
+
prefix, localname = qname.split(":")
|
63
|
+
unless localname
|
64
|
+
localname = prefix
|
65
|
+
uri = @ns.default
|
66
|
+
else
|
67
|
+
uri = @ns[prefix]
|
68
|
+
end
|
69
|
+
@dest.start_element(uri, localname, qname, attr)
|
70
|
+
end
|
71
|
+
def on_end_element(qname)
|
72
|
+
prefix, localname = qname.split(":")
|
73
|
+
unless localname
|
74
|
+
localname = prefix
|
75
|
+
uri = @ns.default
|
76
|
+
else
|
77
|
+
uri = @ns[prefix]
|
78
|
+
end
|
79
|
+
@dest.end_element(uri, localname, qname)
|
80
|
+
end
|
81
|
+
def on_characters(text)
|
82
|
+
@dest.characters(text)
|
83
|
+
end
|
84
|
+
def on_cdata_block(content)
|
85
|
+
@dest.cdata(content)
|
86
|
+
end
|
87
|
+
def on_parser_error(msg)
|
88
|
+
warn("SAX Parser Error: #{msg}")
|
89
|
+
end
|
90
|
+
def on_parser_warning(*msg)
|
91
|
+
warn("SAX Parser Warning: #{msg}")
|
92
|
+
end
|
93
|
+
def on_parser_fatal_error(msg)
|
94
|
+
warn("SAX Parser Fatal Error: #{msg}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|