rtm-activerecord 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|