omf_common 6.0.0.pre.10 → 6.0.0.pre.11
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/bin/monitor_topic.rb +80 -0
- data/bin/send_create.rb +94 -0
- data/bin/send_request.rb +58 -0
- data/example/engine_alt.rb +136 -0
- data/example/vm_alt.rb +65 -0
- data/lib/omf_common.rb +224 -3
- data/lib/omf_common/comm.rb +113 -46
- data/lib/omf_common/comm/amqp/amqp_communicator.rb +76 -0
- data/lib/omf_common/comm/amqp/amqp_topic.rb +91 -0
- data/lib/omf_common/comm/local/local_communicator.rb +64 -0
- data/lib/omf_common/comm/local/local_topic.rb +42 -0
- data/lib/omf_common/comm/topic.rb +190 -0
- data/lib/omf_common/{dsl/xmpp.rb → comm/xmpp/communicator.rb} +93 -53
- data/lib/omf_common/comm/xmpp/topic.rb +147 -0
- data/lib/omf_common/{dsl → comm/xmpp}/xmpp_mp.rb +2 -0
- data/lib/omf_common/eventloop.rb +94 -0
- data/lib/omf_common/eventloop/em.rb +57 -0
- data/lib/omf_common/eventloop/local_evl.rb +78 -0
- data/lib/omf_common/message.rb +112 -229
- data/lib/omf_common/message/json/json_message.rb +129 -0
- data/lib/omf_common/message/xml/message.rb +410 -0
- data/lib/omf_common/message/xml/relaxng_schema.rb +17 -0
- data/lib/omf_common/message/xml/topic_message.rb +20 -0
- data/lib/omf_common/protocol/6.0.rnc +11 -21
- data/lib/omf_common/protocol/6.0.rng +52 -119
- data/lib/omf_common/version.rb +1 -1
- data/omf_common.gemspec +4 -2
- data/test/fixture/pubsub.rb +19 -19
- data/test/omf_common/{dsl/xmpp_spec.rb → comm/xmpp/communicator_spec.rb} +47 -111
- data/test/omf_common/comm/xmpp/topic_spec.rb +113 -0
- data/test/omf_common/comm_spec.rb +1 -0
- data/test/omf_common/message/xml/message_spec.rb +136 -0
- data/test/omf_common/message_spec.rb +37 -131
- data/test/test_helper.rb +4 -1
- metadata +38 -28
- data/lib/omf_common/core_ext/object.rb +0 -21
- data/lib/omf_common/relaxng_schema.rb +0 -17
- data/lib/omf_common/topic.rb +0 -34
- data/lib/omf_common/topic_message.rb +0 -20
- data/test/omf_common/topic_message_spec.rb +0 -114
- data/test/omf_common/topic_spec.rb +0 -75
@@ -0,0 +1,129 @@
|
|
1
|
+
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module OmfCommon
|
5
|
+
class Message
|
6
|
+
class Json
|
7
|
+
class Message < OmfCommon::Message
|
8
|
+
|
9
|
+
|
10
|
+
def self.create(type, properties, body = {})
|
11
|
+
if type == :request
|
12
|
+
unless properties.kind_of?(Array)
|
13
|
+
raise "Expected array, but got #{properties.class} for request message"
|
14
|
+
end
|
15
|
+
properties = {select: properties}
|
16
|
+
elsif not properties.kind_of?(Hash)
|
17
|
+
raise "Expected hash, but got #{properties.class}"
|
18
|
+
end
|
19
|
+
content = body.merge({
|
20
|
+
operation: type,
|
21
|
+
mid: SecureRandom.uuid,
|
22
|
+
properties: properties
|
23
|
+
})
|
24
|
+
self.new(content)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.create_inform_message(itype = nil, properties = {}, body = {})
|
28
|
+
body[:itype] = itype if itype
|
29
|
+
create(:inform, properties, body)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create and return a message by parsing 'str'
|
33
|
+
#
|
34
|
+
def self.parse(str)
|
35
|
+
content = JSON.parse(str, :symbolize_names => true)
|
36
|
+
#puts content
|
37
|
+
new(content)
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_property(&block)
|
41
|
+
@properties.each do |k, v|
|
42
|
+
#unless INTERNAL_PROPS.include?(k.to_sym)
|
43
|
+
block.call(k, v)
|
44
|
+
#end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_properties?
|
49
|
+
not @properties.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid?
|
53
|
+
true # don't do schema verification , yet
|
54
|
+
end
|
55
|
+
|
56
|
+
# Loop over all the unbound (sent without a value) properties
|
57
|
+
# of a request message.
|
58
|
+
#
|
59
|
+
def each_unbound_request_property(&block)
|
60
|
+
unless type == :request
|
61
|
+
raise "Can only be used for request messages"
|
62
|
+
end
|
63
|
+
self[:select].each do |el|
|
64
|
+
#puts "UUU: #{el}::#{el.class}"
|
65
|
+
if el.is_a? Symbol
|
66
|
+
block.call(el)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Loop over all the bound (sent with a value) properties
|
72
|
+
# of a request message.
|
73
|
+
#
|
74
|
+
def each_bound_request_property(&block)
|
75
|
+
unless type == :request
|
76
|
+
raise "Can only be used for request messages"
|
77
|
+
end
|
78
|
+
self[:select].each do |el|
|
79
|
+
#puts "BBB #{el}::#{el.class}"
|
80
|
+
if el.is_a? Hash
|
81
|
+
el.each do |key, value|
|
82
|
+
block.call(key, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
"JsonMessage: #{@content.inspect}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def marshall
|
94
|
+
@content.to_json
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def initialize(content)
|
99
|
+
debug "Create message: #{content.inspect}"
|
100
|
+
@content = content
|
101
|
+
unless op = content[:operation]
|
102
|
+
raise "Missing message type (:operation)"
|
103
|
+
end
|
104
|
+
content[:operation] = op.to_sym # needs to be symbol
|
105
|
+
@properties = content[:properties] || []
|
106
|
+
#@properties = Hashie::Mash.new(content[:properties])
|
107
|
+
end
|
108
|
+
|
109
|
+
def _set_core(key, value)
|
110
|
+
@content[key] = value
|
111
|
+
end
|
112
|
+
|
113
|
+
def _get_core(key)
|
114
|
+
@content[key]
|
115
|
+
end
|
116
|
+
|
117
|
+
def _set_property(key, value)
|
118
|
+
@properties[key] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
def _get_property(key)
|
122
|
+
#puts key
|
123
|
+
@properties[key]
|
124
|
+
end
|
125
|
+
|
126
|
+
end # class
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,410 @@
|
|
1
|
+
require 'niceogiri'
|
2
|
+
require 'hashie'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'openssl'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
require 'omf_common/message/xml/relaxng_schema'
|
8
|
+
|
9
|
+
module OmfCommon
|
10
|
+
class Message
|
11
|
+
class XML
|
12
|
+
|
13
|
+
# @example To create a valid omf message, e.g. a 'create' message:
|
14
|
+
#
|
15
|
+
# Message.create(:create,
|
16
|
+
# { p1: 'p1_value', p2: { unit: 'u', precision: 2 } },
|
17
|
+
# { guard: { p1: 'p1_value' } })
|
18
|
+
class Message < OmfCommon::Message
|
19
|
+
include Comparable
|
20
|
+
|
21
|
+
attr_accessor :xml
|
22
|
+
attr_accessor :content
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Create a OMF message
|
26
|
+
def create(operation_type, properties = {}, core_elements= {})
|
27
|
+
# For request messages, properties will be an array
|
28
|
+
if properties.kind_of? Array
|
29
|
+
properties = Hashie::Mash.new.tap do |mash|
|
30
|
+
properties.each { |p| mash[p] = nil }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
properties = Hashie::Mash.new(properties)
|
35
|
+
core_elements = Hashie::Mash.new(core_elements)
|
36
|
+
|
37
|
+
if operation_type.to_sym == :create
|
38
|
+
core_elements[:rtype] ||= properties[:type]
|
39
|
+
end
|
40
|
+
|
41
|
+
content = core_elements.merge({
|
42
|
+
operation: operation_type,
|
43
|
+
type: operation_type,
|
44
|
+
properties: properties
|
45
|
+
})
|
46
|
+
|
47
|
+
new(content)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse(xml)
|
51
|
+
raise ArgumentError, 'Can not parse an empty XML into OMF message' if xml.nil? || xml.empty?
|
52
|
+
|
53
|
+
xml_node = Nokogiri::XML(xml).root
|
54
|
+
|
55
|
+
self.create(xml_node.name.to_sym).tap do |message|
|
56
|
+
message.xml = xml_node
|
57
|
+
|
58
|
+
message.send(:_set_core, :mid, message.xml.attr('mid'))
|
59
|
+
|
60
|
+
message.xml.elements.each do |el|
|
61
|
+
unless %w(digest props guard).include? el.name
|
62
|
+
message.send(:_set_core, el.name, message.read_content(el.name))
|
63
|
+
end
|
64
|
+
|
65
|
+
if el.name == 'props'
|
66
|
+
message.read_element('props').first.element_children.each do |prop_node|
|
67
|
+
message.send(:_set_property,
|
68
|
+
prop_node.element_name,
|
69
|
+
message.reconstruct_data(prop_node))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if el.name == 'guard'
|
74
|
+
message.read_element('guard').first.element_children.each do |guard_node|
|
75
|
+
message.guard ||= Hashie::Mash.new
|
76
|
+
message.guard[guard_node.element_name] = message.reconstruct_data(guard_node)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
if OmfCommon::Measure.enabled? && !@@mid_list.include?(message.mid)
|
82
|
+
MPMessage.inject(Time.now.to_f, message.operation.to_s, message.mid, message.cid, message.to_s.gsub("\n",''))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def resource
|
89
|
+
r_id = _get_property(:res_id)
|
90
|
+
OmfCommon::Comm::XMPP::Topic.create(r_id)
|
91
|
+
end
|
92
|
+
|
93
|
+
def itype
|
94
|
+
@content.itype.to_s.upcase.gsub(/_/, '.') unless @content.itype.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
def marshall
|
98
|
+
build_xml
|
99
|
+
@xml.to_xml
|
100
|
+
end
|
101
|
+
|
102
|
+
alias_method :to_xml, :marshall
|
103
|
+
alias_method :to_s, :marshall
|
104
|
+
|
105
|
+
def build_xml
|
106
|
+
@xml = Niceogiri::XML::Node.new(self.operation.to_s, nil, OMF_NAMESPACE)
|
107
|
+
|
108
|
+
@xml.write_attr(:mid, mid)
|
109
|
+
|
110
|
+
props_node = Niceogiri::XML::Node.new(:props)
|
111
|
+
guard_node = Niceogiri::XML::Node.new(:guard)
|
112
|
+
|
113
|
+
props_ns.each do |k, v|
|
114
|
+
props_node.add_namespace_definition(k.to_s, v.to_s)
|
115
|
+
end
|
116
|
+
|
117
|
+
@xml.add_child(props_node)
|
118
|
+
@xml.add_child(guard_node) if _get_core(:guard)
|
119
|
+
|
120
|
+
(OMF_CORE_READ - [:mid, :guard, :operation]).each do |attr|
|
121
|
+
attr_value = self.send(attr)
|
122
|
+
|
123
|
+
next unless attr_value
|
124
|
+
|
125
|
+
add_element(attr, attr_value) unless (self.operation != :release && attr == :res_id)
|
126
|
+
end
|
127
|
+
|
128
|
+
self.properties.each { |k, v| add_property(k, v) }
|
129
|
+
self.guard.each { |k, v| add_property(k, v, :guard) } if _get_core(:guard)
|
130
|
+
|
131
|
+
#digest = OpenSSL::Digest::SHA512.new(@xml.canonicalize)
|
132
|
+
|
133
|
+
#add_element(:digest, digest)
|
134
|
+
@xml
|
135
|
+
end
|
136
|
+
|
137
|
+
# Construct a property xml node
|
138
|
+
#
|
139
|
+
def add_property(key, value = nil, add_to = :props)
|
140
|
+
if !default_props_ns.empty? && add_to == :props
|
141
|
+
key_node = Niceogiri::XML::Node.new(key, nil, default_props_ns)
|
142
|
+
else
|
143
|
+
key_node = Niceogiri::XML::Node.new(key)
|
144
|
+
end
|
145
|
+
|
146
|
+
unless value.nil?
|
147
|
+
key_node.write_attr('type', ruby_type_2_prop_type(value.class))
|
148
|
+
c_node = value_node_set(value)
|
149
|
+
|
150
|
+
if c_node.class == Array
|
151
|
+
c_node.each { |c_n| key_node.add_child(c_n) }
|
152
|
+
else
|
153
|
+
key_node.add_child(c_node)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
read_element(add_to).first.add_child(key_node)
|
157
|
+
key_node
|
158
|
+
end
|
159
|
+
|
160
|
+
def value_node_set(value, key = nil)
|
161
|
+
case value
|
162
|
+
when Hash
|
163
|
+
[].tap do |array|
|
164
|
+
value.each_pair do |k, v|
|
165
|
+
n = Niceogiri::XML::Node.new(k, nil, OMF_NAMESPACE)
|
166
|
+
n.write_attr('type', ruby_type_2_prop_type(v.class))
|
167
|
+
|
168
|
+
c_node = value_node_set(v, k)
|
169
|
+
if c_node.class == Array
|
170
|
+
c_node.each { |c_n| n.add_child(c_n) }
|
171
|
+
else
|
172
|
+
n.add_child(c_node)
|
173
|
+
end
|
174
|
+
array << n
|
175
|
+
end
|
176
|
+
end
|
177
|
+
when Array
|
178
|
+
value.map do |v|
|
179
|
+
n = Niceogiri::XML::Node.new('it', nil, OMF_NAMESPACE)
|
180
|
+
n.write_attr('type', ruby_type_2_prop_type(v.class))
|
181
|
+
|
182
|
+
c_node = value_node_set(v, 'it')
|
183
|
+
if c_node.class == Array
|
184
|
+
c_node.each { |c_n| n.add_child(c_n) }
|
185
|
+
else
|
186
|
+
n.add_child(c_node)
|
187
|
+
end
|
188
|
+
n
|
189
|
+
end
|
190
|
+
else
|
191
|
+
if key.nil?
|
192
|
+
string_value(value)
|
193
|
+
else
|
194
|
+
n = Niceogiri::XML::Node.new(key, nil, OMF_NAMESPACE)
|
195
|
+
n.add_child(string_value(value))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Generate SHA1 of canonicalised xml and write into the ID attribute of the message
|
201
|
+
#
|
202
|
+
def sign
|
203
|
+
write_attr('mid', SecureRandom.uuid)
|
204
|
+
write_attr('ts', Time.now.utc.to_i)
|
205
|
+
canonical_msg = self.canonicalize
|
206
|
+
|
207
|
+
#priv_key = OmfCommon::Key.instance.private_key
|
208
|
+
#digest = OpenSSL::Digest::SHA512.new(canonical_msg)
|
209
|
+
|
210
|
+
#signature = Base64.encode64(priv_key.sign(digest, canonical_msg)).encode('utf-8') if priv_key
|
211
|
+
#write_attr('digest', digest)
|
212
|
+
#write_attr('signature', signature) if signature
|
213
|
+
|
214
|
+
if OmfCommon::Measure.enabled?
|
215
|
+
MPMessage.inject(Time.now.to_f, operation.to_s, mid, cid, self.to_s.gsub("\n",''))
|
216
|
+
@@mid_list << mid
|
217
|
+
end
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
# Validate against relaxng schema
|
222
|
+
#
|
223
|
+
def valid?
|
224
|
+
build_xml
|
225
|
+
|
226
|
+
validation = RelaxNGSchema.instance.validate(@xml.document)
|
227
|
+
if validation.empty?
|
228
|
+
true
|
229
|
+
else
|
230
|
+
logger.error validation.map(&:message).join("\n")
|
231
|
+
logger.debug @xml.to_s
|
232
|
+
false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Short cut for adding xml node
|
237
|
+
#
|
238
|
+
def add_element(key, value = nil, &block)
|
239
|
+
key_node = Niceogiri::XML::Node.new(key)
|
240
|
+
@xml.add_child(key_node)
|
241
|
+
if block
|
242
|
+
block.call(key_node)
|
243
|
+
else
|
244
|
+
key_node.content = value if value
|
245
|
+
end
|
246
|
+
key_node
|
247
|
+
end
|
248
|
+
|
249
|
+
# Short cut for grabbing a group of nodes using xpath, but with default namespace
|
250
|
+
def element_by_xpath_with_default_namespace(xpath_without_ns)
|
251
|
+
xpath_without_ns = xpath_without_ns.to_s
|
252
|
+
if !default_props_ns.empty? && xpath_without_ns !~ /props|guard|ts|src|mid|rtype|res_id|cid|itype/
|
253
|
+
@xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, "\\1#{rtype.to_s}:\\2"), default_props_ns)
|
254
|
+
else
|
255
|
+
@xml.xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, '\1xmlns:\2'), :xmlns => OMF_NAMESPACE)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# In case you think method :element_by_xpath_with_default_namespace is too long
|
260
|
+
#
|
261
|
+
alias_method :read_element, :element_by_xpath_with_default_namespace
|
262
|
+
|
263
|
+
# We just want to know the content of an non-repeatable element
|
264
|
+
#
|
265
|
+
def read_content(element_name)
|
266
|
+
element_content = read_element("#{element_name}").first.content rescue nil
|
267
|
+
unless element_content.nil?
|
268
|
+
element_content.empty? ? nil : element_content
|
269
|
+
else
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Reconstruct xml node into Ruby object
|
275
|
+
#
|
276
|
+
# @param [Niceogiri::XML::Node] property xml node
|
277
|
+
# @return [Object] the content of the property, as string, integer, float, or mash(hash with indifferent access)
|
278
|
+
def reconstruct_data(node, data_binding = nil)
|
279
|
+
node_type = node.attr('type')
|
280
|
+
case node_type
|
281
|
+
when 'array'
|
282
|
+
node.element_children.map do |child|
|
283
|
+
reconstruct_data(child, data_binding)
|
284
|
+
end
|
285
|
+
when /hash/
|
286
|
+
mash ||= Hashie::Mash.new
|
287
|
+
node.element_children.each do |child|
|
288
|
+
mash[child.attr('key') || child.element_name] ||= reconstruct_data(child, data_binding)
|
289
|
+
end
|
290
|
+
mash
|
291
|
+
when /boolean/
|
292
|
+
node.content == "true"
|
293
|
+
else
|
294
|
+
if node.content.empty?
|
295
|
+
nil
|
296
|
+
elsif data_binding && node_type == 'string'
|
297
|
+
ERB.new(node.content).result(data_binding)
|
298
|
+
else
|
299
|
+
node.content.ducktype
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def <=>(another)
|
305
|
+
@content <=> another.content
|
306
|
+
end
|
307
|
+
|
308
|
+
def properties
|
309
|
+
@content.properties
|
310
|
+
end
|
311
|
+
|
312
|
+
def has_properties?
|
313
|
+
@content.properties.empty?
|
314
|
+
end
|
315
|
+
|
316
|
+
def guard?
|
317
|
+
@content.guard.empty?
|
318
|
+
end
|
319
|
+
|
320
|
+
# Pretty print for application event message
|
321
|
+
#
|
322
|
+
def print_app_event
|
323
|
+
"APP_EVENT (#{read_property(:app)}, ##{read_property(:seq)}, #{read_property(:event)}): #{read_property(:msg)}"
|
324
|
+
end
|
325
|
+
|
326
|
+
# Iterate each property key value pair
|
327
|
+
#
|
328
|
+
def each_property(&block)
|
329
|
+
properties.each { |k, v| block.call(k, v) }
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
def each_unbound_request_property(&block)
|
334
|
+
raise "Can only be used for request messages. Got #{type}." if type != :request
|
335
|
+
properties.each { |k, v| block.call(k, v) if v.nil? }
|
336
|
+
end
|
337
|
+
|
338
|
+
def each_bound_request_property(&block)
|
339
|
+
raise "Can only be used for request messages. Got #{type}." if type != :request
|
340
|
+
properties.each { |k, v| block.call(k, v) unless v.nil? }
|
341
|
+
end
|
342
|
+
|
343
|
+
#def [](name, evaluate = false)
|
344
|
+
# value = properties[name]
|
345
|
+
|
346
|
+
# if evaluate && value.kind_of?(String)
|
347
|
+
# ERB.new(value).result(evaluate)
|
348
|
+
# else
|
349
|
+
# value
|
350
|
+
# end
|
351
|
+
#end
|
352
|
+
|
353
|
+
alias_method :read_property, :[]
|
354
|
+
|
355
|
+
alias_method :write_property, :[]=
|
356
|
+
|
357
|
+
private
|
358
|
+
|
359
|
+
def initialize(content = {})
|
360
|
+
@content = content
|
361
|
+
@content.mid = SecureRandom.uuid
|
362
|
+
@content.ts = Time.now.utc.to_i
|
363
|
+
end
|
364
|
+
|
365
|
+
def _set_core(key, value)
|
366
|
+
@content[key] = value
|
367
|
+
end
|
368
|
+
|
369
|
+
def _get_core(key)
|
370
|
+
@content[key]
|
371
|
+
end
|
372
|
+
|
373
|
+
def _set_property(key, value, ns = nil)
|
374
|
+
# TODO what to do here
|
375
|
+
@content.properties[key] = value
|
376
|
+
end
|
377
|
+
|
378
|
+
def _get_property(key, ns = nil)
|
379
|
+
# TODO what to do here
|
380
|
+
@content.properties[key]
|
381
|
+
end
|
382
|
+
|
383
|
+
def ruby_type_2_prop_type(ruby_class_type)
|
384
|
+
v_type = ruby_class_type.to_s.downcase
|
385
|
+
case v_type
|
386
|
+
when *%w(trueclass falseclass)
|
387
|
+
'boolean'
|
388
|
+
when *%w(fixnum bignum)
|
389
|
+
'integer'
|
390
|
+
when /hash|mash/
|
391
|
+
'hash'
|
392
|
+
else
|
393
|
+
v_type
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Get string of a value object, escape if object is string
|
398
|
+
def string_value(value)
|
399
|
+
if value.kind_of? String
|
400
|
+
value = CGI::escape_html(value)
|
401
|
+
else
|
402
|
+
value = value.to_s
|
403
|
+
end
|
404
|
+
value
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|