yore 0.0.1

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.
@@ -0,0 +1,454 @@
1
+ require 'tmpdir'
2
+ require 'logger'
3
+ require 'pathname'
4
+
5
+ module MiscUtils
6
+
7
+ def self.logger
8
+ @logger || @logger = Logger.new(STDERR)
9
+ end
10
+
11
+ # applies the given block to key/value pairs included/excluded by the given parameters
12
+ # aSource must be a hash, and may contain hashes which will be recursively processed.
13
+ # aInclude/aExclude may be nil, an array of selected keys, or a hash containing arrays of keys for inner hashes of aSource
14
+ # When aInclude/aExclude are a hash, non-hash source values may be selected by passing a value of true. Hash source values
15
+ # will be selected as a whole (all keys) when true is passed.
16
+ # input = { 'a' => 1, 'b' => {'x' => 9, 'y' => 8}, 'c' => 3 }
17
+ # filter_multilevel_hash(input,['a','b'],{'b'=>['y']}) {|h,k,v| h[k] = true}
18
+ # input now = { 'a' => true, 'b' => {'x' => true, 'y' => 8}, 'c' => 3 }
19
+ def self.filter_multilevel_hash(aSource,aInclude=nil,aExclude=nil,&block)
20
+ aSource.each do |key,value|
21
+ next if aInclude.is_a?(Array) and !aInclude.include?(key) # skip if not in aInclude array
22
+ next if aExclude.is_a?(Array) and aExclude.include?(key) # skip if in aExclude array
23
+ next if aExclude.is_a?(Hash) and aExclude[key]==true # skip if in aExclude hash with value=true
24
+ next if aInclude.is_a?(Hash) and !aInclude.include?(key) # skip if not in aInclude hash at all
25
+ if value.is_a?(Hash) # value is hash so recursively apply filter
26
+ filter_multilevel_hash(
27
+ value,
28
+ aInclude.is_a?(Hash) && (f = aInclude[key]) ? f : nil, # pass include array if provided for key
29
+ aExclude.is_a?(Hash) && (f = aExclude[key]) ? f : nil, # pass exclude array if provided for key
30
+ &block
31
+ )
32
+ else
33
+ yield(aSource,key,value)
34
+ end
35
+ end
36
+ end
37
+
38
+ # returns output string if succesful, or integer return code if not, or nil
39
+ def self.execute(aCommand,aWorkingDir=nil,aTimeout=nil,aTimeoutClass=nil)
40
+ return nil unless !aWorkingDir || File.exists?(aWorkingDir)
41
+ begin
42
+ orig_wd = Dir.getwd
43
+ pipe = nil
44
+ result = nil
45
+ Dir.chdir(aWorkingDir) if aWorkingDir
46
+ Timeout.timeout(aTimeout,aTimeoutClass || Timeout::Error) do # nil aTimeout will not time out
47
+ pipe = IO.popen(aCommand)
48
+ logger.debug "command PID:"+pipe.pid.to_s
49
+ result = pipe.read
50
+ end
51
+ ensure
52
+ pipe.close if pipe
53
+ Dir.chdir(orig_wd)
54
+ end
55
+ return result
56
+ end
57
+
58
+ def self.execute_string(aCmdString,aWorkingDir=nil)
59
+ result = nil
60
+ begin
61
+ orig_dir = Dir.pwd
62
+ Dir.chdir(aWorkingDir) if aWorkingDir
63
+ result = `#{aCmdString}`
64
+ ensure
65
+ Dir.chdir(orig_dir) if aWorkingDir
66
+ end
67
+ return result
68
+ end
69
+
70
+ def self.temp_file(aExt=nil,aDir=nil)
71
+ aExt ||= '.tmp'
72
+ File.expand_path(("%08X" % rand(0x3FFFFFFF)) + aExt, aDir||Dir.tmpdir)
73
+ end
74
+
75
+ def self.make_temp_file(aName=nil,aDir=nil,aContent=nil)
76
+ filename = aName ? File.expand_path(aName,aDir || Dir.tmpdir) : temp_file(nil,aDir)
77
+ FileUtils.mkdir_p(File.dirname(filename))
78
+ aContent ||= "content of "+filename
79
+ string_to_file(aContent,filename)
80
+ filename
81
+ end
82
+
83
+ def self.make_temp_dir(aPrefix='')
84
+ new_dir = nil
85
+ begin
86
+ new_dir = File.join(Dir.tmpdir,aPrefix+("%08X" % rand(0x3FFFFFFF)))
87
+ end until new_dir && !File.exists?(new_dir)
88
+ Dir.mkdir new_dir
89
+ return new_dir
90
+ end
91
+
92
+ def self.mkdir?(aPath,aPermissions)
93
+ if File.exists?(aPath)
94
+ File.chmod(aPermissions, aPath)
95
+ else
96
+ Dir.mkdir(aPath, aPermissions)
97
+ end
98
+ end
99
+
100
+ def self.string_to_file(aString,aFilename)
101
+ File.open(aFilename,'wb') {|file| file.puts aString }
102
+ end
103
+
104
+ def self.string_from_file(aFilename)
105
+ result = nil
106
+ File.open(aFilename, "rb") { |f| result = f.read }
107
+ return result && result[0..-2] # quick hack to stop returning false \n at end
108
+ end
109
+
110
+ def self.sniff_seperator(aPath)
111
+ result = 0.upto(aPath.length-1) do |i|
112
+ char = aPath[i,1]
113
+ break char if char=='\\' || char=='/'
114
+ end
115
+ result = File::SEPARATOR if result==0
116
+ return result
117
+ end
118
+
119
+ def self.append_slash(aPath,aSep=nil)
120
+ aSep = sniff_seperator(aPath) unless aSep
121
+ last_char = aPath[-1,1]
122
+ aPath += aSep unless last_char=='\\' || last_char=='/'
123
+ return aPath
124
+ end
125
+
126
+ def self.remove_slash(aPath)
127
+ last_char = aPath[-1,1]
128
+ aPath = aPath[0..-2] if last_char=='\\' || last_char=='/'
129
+ return aPath
130
+ end
131
+
132
+ # Remove base dir from given path. Result will be relative to base dir and not have a leading or trailing slash
133
+ #'/a/b/c','/a' = 'b/c'
134
+ #'/a/b/c','/' = 'a/b/c'
135
+ #'/','/' = ''
136
+ def self.path_debase(aPath,aBase)
137
+ aBase = MiscUtils::append_slash(aBase)
138
+ aPath = MiscUtils::remove_slash(aPath) unless aPath=='/'
139
+ aPath[aBase.length,aPath.length-aBase.length]
140
+ end
141
+
142
+ def self.path_rebase(aPath,aOldBase,aNewBase)
143
+ rel_path = path_debase(aPath,aOldBase)
144
+ append_slash(aNewBase)+rel_path
145
+ end
146
+
147
+ def self.path_combine(aBasePath,aPath)
148
+ return aBasePath if !aPath
149
+ return aPath if !aBasePath
150
+ return path_relative?(aPath) ? File.join(aBasePath,aPath) : aPath
151
+ end
152
+
153
+ def self.path_parent(aPath)
154
+ MiscUtils.append_slash(File.dirname(MiscUtils.remove_slash(File.expand_path(aPath))))
155
+ end
156
+
157
+ def self.simple_dir_name(aPath)
158
+ File.basename(remove_slash(aPath))
159
+ end
160
+
161
+ def self.path_parts(aPath)
162
+ sep = sniff_seperator(aPath)
163
+ aPath.split(sep)
164
+ end
165
+
166
+ def self.file_extension(aFile,aExtended=true)
167
+ f = File.basename(aFile)
168
+ dot = aExtended ? f.index('.') : f.rindex('.')
169
+ return dot ? f[dot+1..-1] : f
170
+ end
171
+
172
+ def self.file_no_extension(aFile,aExtended=true)
173
+ ext = file_extension(aFile,aExtended)
174
+ return aFile.chomp('.'+ext)
175
+ end
176
+
177
+ def self.file_change_ext(aFile,aExt,aExtend=false)
178
+ file_no_extension(aFile,false)+(aExtend ? '.'+aExt+'.'+file_extension(aFile,false) : '.'+aExt)
179
+ end
180
+
181
+ def self.platform
182
+ RUBY_PLATFORM.scan(/-(.+)$/).flatten.first
183
+ end
184
+
185
+ def self.windows_path(aPath)
186
+ aPath.gsub('/','\\')
187
+ end
188
+
189
+ def self.ruby_path(aPath)
190
+ aPath.gsub('\\','/')
191
+ end
192
+
193
+ def self.is_uri?(aString)
194
+ /^[a-zA-Z0-9+_]+\:\/\// =~ aString ? true : false
195
+ end
196
+
197
+ def self.native_path(aPath)
198
+ is_windows? ? windows_path(aPath) : ruby_path(aPath)
199
+ end
200
+
201
+ def self.path_relative?(aPath)
202
+ return false if aPath[0,1]=='/'
203
+ return false if aPath =~ /^[a-zA-Z]:/
204
+ return true
205
+ end
206
+
207
+ def self.path_absolute?(aPath)
208
+ !path_relative(aPath)
209
+ end
210
+
211
+ def self.is_windows?
212
+ platform=='mswin32'
213
+ end
214
+
215
+ # takes a path and combines it with a root path (which defaults to Dir.pwd) unless it is absolute
216
+ # the final result is then expanded
217
+ def self.canonize_path(aPath,aRootPath=nil)
218
+ path = Pathname.new(aPath)
219
+ path = Pathname.new(aRootPath || Dir.pwd)+path if path.relative?
220
+ File.expand_path(path)
221
+ end
222
+
223
+ def self.get_files(aArray,aPath,aFullPath=true,aRootPath=nil,&block)
224
+ #puts "get_files: aPath='#{aPath}'"
225
+ if aRootPath
226
+ abssrcpath = path_combine(aRootPath,aPath)
227
+ else
228
+ abssrcpath = aRootPath = aPath
229
+ aPath = nil
230
+ end
231
+ #abssrcpath is real path to query
232
+ #aRootPath is highest level path
233
+ #aPath is current path relative to aRootPath
234
+ Dir.new(abssrcpath).to_a.each do |file|
235
+ next if ['.','..'].include? file
236
+ fullpath = File.join(abssrcpath,file)
237
+ resultpath = aFullPath ? fullpath : path_combine(aPath,file)
238
+ if !block_given? || yield(resultpath)
239
+ if FileTest.directory?(fullpath)
240
+ block_given? ? get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath,&block) : get_files(aArray,path_combine(aPath,file),aFullPath,aRootPath)
241
+ else
242
+ aArray << resultpath
243
+ end
244
+ end
245
+ end
246
+ return aArray
247
+ end
248
+
249
+ def self.recursive_file_list(aPath,aFullPath=true,&block)
250
+ block_given? ? get_files([],aPath,aFullPath,nil,&block) : get_files([],aPath,aFullPath)
251
+ end
252
+
253
+ # returns true if aPath1 and aPath2 are the same path (doesn't query file system)
254
+ # both must be absolute or both relative
255
+ def self.path_same(aPath1,aPath2)
256
+ return nil unless path_relative?(aPath1) == path_relative?(aPath2)
257
+ remove_slash(aPath1) == remove_slash(aPath2)
258
+ end
259
+
260
+ # returns true if aPath is under aPathParent
261
+ # both must be absolute or both relative
262
+ def self.path_ancestor(aPathParent,aPath)
263
+ return nil unless path_relative?(aPathParent) == path_relative?(aPath)
264
+ aPath.index(append_slash(aPathParent))==0
265
+ end
266
+
267
+ # returns the lowest path containing all files (assumes aFiles contains only absolute paths)
268
+ def self.file_list_ancestor(aFiles)
269
+ files = aFiles.is_a?(Hash) ? aFiles.keys : aFiles
270
+ result = File.dirname(files.first)
271
+ files.each do |fp|
272
+ filedir = File.dirname(fp)
273
+ while path_same(result,filedir)==false && path_ancestor(result,filedir)==false
274
+ result = path_parent(result)
275
+ end
276
+ end
277
+ result
278
+ end
279
+
280
+ def self.path_match(aPath,aPatterns)
281
+ aPatterns = [aPatterns] unless aPatterns.is_a? Array
282
+ aPatterns.any? do |pat|
283
+ case pat
284
+ when String then aPath[0,pat.length] == pat
285
+ when Regexp then aPath =~ pat
286
+ else false
287
+ end
288
+ end
289
+ end
290
+
291
+
292
+ # takes a hash and returns a single closed tag containing the hash pairs as attributes, correctly encoded
293
+ def self.hash_to_xml_tag(aName,aHash)
294
+ atts = ''
295
+ aHash.each do |k,v|
296
+ atts += ' ' + k.to_s + "=\"#{v.to_s.to_xs}\""
297
+ end
298
+ "<#{aName}#{atts}/>"
299
+ end
300
+
301
+ def self.filelist_from_patterns(aPatterns,aBasePath)
302
+ return [] unless aPatterns
303
+ aPatterns = [aPatterns] unless aPatterns.is_a? Array
304
+
305
+ aPatterns.map do |fp|
306
+ fp = File.expand_path(fp,aBasePath) # relative to rails root
307
+ fp = FileList[fp] if fp['*'] || fp['?']
308
+ fp
309
+ end.flatten
310
+ end
311
+
312
+ #:host
313
+ #:port
314
+ #:helodomain
315
+ #:user
316
+ #:password
317
+ #:from
318
+ #:from_alias
319
+ #:to
320
+ #:to_alias
321
+ #:subject
322
+ #:message
323
+ #:auth : 'plain', 'login', 'cram_md5'
324
+
325
+ # send an email via an SMTP server
326
+ def self.send_email(aArgs)
327
+ msg = <<END_OF_MESSAGE
328
+ From: #{aArgs[:from_alias]} <#{aArgs[:from]}>
329
+ To: #{aArgs[:to_alias]} <#{aArgs[:to]}>
330
+ Subject: #{aArgs[:subject]}
331
+
332
+ #{aArgs[:message]}
333
+ END_OF_MESSAGE
334
+
335
+ Net::SMTP.start(
336
+ aArgs[:host],
337
+ aArgs[:port],
338
+ aArgs[:helodomain],
339
+ aArgs[:user],
340
+ aArgs[:password],
341
+ aArgs[:auth]
342
+ ) do |smtp|
343
+ smtp.send_message msg, aArgs[:from], aArgs[:to]
344
+ end
345
+ end
346
+
347
+ end
348
+
349
+ class Logger
350
+ attr_reader :logdev
351
+ end
352
+
353
+ module LogUtils
354
+
355
+ # eg.
356
+ # {
357
+ # 'destination' => 'STDERR|STDOUT|FILE',
358
+ # 'filename' => '/path/to/file.ext',
359
+ # 'level' => 'DEBUG|INFO|...',
360
+ #
361
+ # 'age' = 'daily|weekly|monthly',
362
+ # OR
363
+ # 'max_files' => 3,
364
+ # 'max_bytes' => 1024000
365
+ # }
366
+ def self.create_logger_from_config(aConfigHash)
367
+ if not aConfigHash
368
+ result = Logger.new(STDERR)
369
+ result.level = Logger::DEBUG
370
+ return result
371
+ end
372
+
373
+ result = nil
374
+ case aConfigHash['destination']
375
+ when 'STDERR' then
376
+ result = Logger.new(STDERR)
377
+ when 'STDOUT' then
378
+ result = Logger.new(STDOUT)
379
+ when 'FILE' then
380
+ result = aConfigHash['age'] ?
381
+ Logger.new(aConfigHash['filename'],aConfigHash['age']) :
382
+ Logger.new(
383
+ aConfigHash['filename'],
384
+ (aConfigHash['max_files'] || 3).to_i,
385
+ (aConfigHash['max_bytes'] || 1024000).to_i
386
+ )
387
+ else
388
+ result = Logger.new(STDERR)
389
+ end
390
+ puts valstr = "Logger::#{(aConfigHash['level'] || 'INFO').upcase}"
391
+ result.level = eval(valstr)
392
+ return result
393
+ end
394
+
395
+ # use this to trunc a log file to 0 bytes
396
+ def self.trunc(aFilename)
397
+ f = File.open(aFilename, "w")
398
+ f.close
399
+ end
400
+
401
+ class ReportFormatter < Logger::Formatter
402
+ def call(severity, time, progname, msg)
403
+ "|%s %1s %s\n" % [(time.strftime('%H%M%S.')<<"%03d" % (time.usec/1000)),severity[0..0],msg2str(msg)]
404
+ end
405
+ end
406
+
407
+ class Reporter < Logger
408
+ def initialize(logdev)
409
+ super(logdev)
410
+ end
411
+
412
+ end
413
+
414
+ def self.create_reporter(aFilename=nil)
415
+ aFilename ||= MiscUtils::temp_file()
416
+ result = Logger.new(aFilename)
417
+ result.formatter = ReportFormatter.new
418
+ result
419
+ end
420
+ end
421
+
422
+
423
+
424
+ # include this at the top of a class to protect it from baddies.
425
+ # eg.
426
+ # + nearly all ancestor public_instance_methods will be hidden
427
+ # + inspect will only return the class name
428
+ # + methods will return public methods
429
+ module SecureThisClass
430
+ def self.hack(aClass,aOptions={})
431
+ include_actions = (aOptions[:include] || aClass.public_instance_methods.clone)
432
+ exclude_actions = ['class','public_methods'] | (aOptions[:exclude] || [])
433
+ actions_to_hide = include_actions-exclude_actions
434
+ aClass.class_eval do
435
+ actions_to_hide.each { |m| protected m.to_sym }
436
+
437
+ def inspect
438
+ return self.class.name
439
+ end
440
+
441
+ def methods
442
+ public_methods
443
+ end
444
+ end
445
+ end
446
+ end
447
+
448
+
449
+ module ::Kernel
450
+ def secure_class(aOptions={})
451
+ SecureThisClass::hack(self,aOptions)
452
+ end
453
+ end
454
+
@@ -0,0 +1,53 @@
1
+ module StringUtils
2
+ def self.crop(aString,aLength,aEllipsis=true,aConvertNil=true)
3
+ return aConvertNil ? ' '*aLength : nil if !aString
4
+
5
+ increase = aLength-aString.length
6
+ return aString+' '*increase if increase>=0
7
+ return aEllipsis ? aString[0,aLength-3]+'...' : aString[0,aLength]
8
+ end
9
+
10
+ # aTemplate is a string containing tokens like ${SOME_TOKEN}
11
+ # aValues is a hash of token names eg. 'SOME_TOKEN' and their values to substitute
12
+ def self.render_template(aTemplate,aValues)
13
+ # get positions of tokens
14
+ result = aTemplate.gsub(/\$\{(.*?)\}/) do |s|
15
+ key = s[2..-2]
16
+ rep = (aValues[key] || s)
17
+ #puts "replacing #{s} with #{rep}"
18
+ rep
19
+ end
20
+ #puts "rendered :\n#{result}"
21
+ return result
22
+ end
23
+
24
+ def self.clean_number(aString)
25
+ aString.gsub(/[^0-9.-]/,'')
26
+ end
27
+
28
+ # supply a block with 2 parameters, and it will get called for each char as an integer
29
+ def self.each_unicode_char(aString)
30
+ len = 1
31
+ index = 0
32
+ char = 0
33
+ aString.each_byte do |b|
34
+ if index==0
35
+ len = 1
36
+ len = 2 if b & 0b11000000 != 0
37
+ len = 3 if b & 0b11100000 != 0
38
+ len = 4 if b & 0b11110000 != 0
39
+ char = 0
40
+ end
41
+
42
+ char |= b << index*8
43
+
44
+ yield(char,len) if index==len-1 # last byte; char is complete
45
+
46
+ index += 1
47
+ index = 0 if index >= len
48
+ end
49
+ end
50
+
51
+
52
+ end
53
+
@@ -0,0 +1,163 @@
1
+ require 'net/http'
2
+ require 'rexml/document'
3
+
4
+ module XmlUtils
5
+
6
+ BASIC_HEADER = '<?xml version="1.0"?>'
7
+
8
+ def self.get_url_root(url)
9
+ xml = Net::HTTP.get(URI(url))
10
+ return nil if !xml
11
+ xdoc = REXML::Document.new(xml)
12
+ return nil if !xdoc
13
+ root = xdoc.root
14
+ end
15
+
16
+ def self.get_xml_root(xml)
17
+ xdoc = REXML::Document.new(xml)
18
+ return nil if !xdoc
19
+ root = xdoc.root
20
+ end
21
+
22
+ def self.get_file_root(aFilename)
23
+ get_xml_root(MiscUtils.string_from_file(aFilename))
24
+ end
25
+
26
+ def self.single_node(node,xpath,default=nil)
27
+ return default if node.nil? || xpath.nil? || xpath==''
28
+ val = REXML::XPath.first(node,xpath)
29
+ return val.nil? ? default : val
30
+ end
31
+
32
+ def self.peek_node(node,xpath,default=nil)
33
+ return default if node.nil? || xpath.nil? || xpath==''
34
+ val = REXML::XPath.first(node,xpath)
35
+ return val.nil? ? default : val.to_s
36
+ end
37
+
38
+ def self.peek_node_value(node,xpath,default=nil)
39
+ peek_node(node,xpath+'/text()',default)
40
+ end
41
+
42
+ # convert <root><a>one</a><b>two</b></root> to {'a' => 'one', 'b' => 'two'}
43
+ def self.hash_from_elements(aXMLRoot)
44
+ result = {}
45
+ aXMLRoot.elements.each{|e| result[e.name] = e.text}
46
+ return result
47
+ end
48
+
49
+ # given '<tag a="1" b="2">textblah</tag>' returns {:name=>"tag", :text=>"textblah", "a"=>"1", "b"=>"2"}
50
+ def self.hash_from_tag(aTagString)
51
+ result = {}
52
+ tag = get_xml_root(aTagString)
53
+ return nil if !tag
54
+ tag.attributes.each_attribute {|attr| result[attr.expanded_name] = attr.value }
55
+ result[:name] = tag.name
56
+ result[:text] = tag.text if tag.text
57
+ return result
58
+ end
59
+
60
+ # given {:name=>"tag", :text=>"textblah", "a"=>"1", "b"=>"2"} returns '<tag a="1" b="2">textblah</tag>'
61
+ def self.tag_from_hash(aHash,aName=nil,aText=nil)
62
+ aName ||= aHash[:name]
63
+ aText ||= aHash[:text]
64
+ result = "<#{aName}"
65
+ aHash.each {|k,v| result += " #{k}=\"#{encode(v.to_s)}\"" if k.is_a? String}
66
+ result += aText ? " >#{aText}</#{aName}>" : " />"
67
+ end
68
+
69
+ def self.hash_to_xml(aHash,aRootName,aDocHeader=true)
70
+ xdoc = REXML::Document.new(BASIC_HEADER)
71
+ root = xdoc.add_element(aRootName)
72
+ aHash.each do |n,v|
73
+ root.add_element(n).add_text(v)
74
+ end
75
+ return xdoc
76
+ end
77
+
78
+ def self.read_simple_items(aRoot,aParentXPath)
79
+ result = {}
80
+ REXML::XPath.each(aRoot, aParentXPath+'/Item') do |item|
81
+ result[item.attribute('Name').to_s] = item.text
82
+ end
83
+ return result
84
+ end
85
+
86
+ def self.quick_write_simple_items(aHash,aParent)
87
+ return "<#{aParent} />\n" if !aHash || aHash.empty?
88
+ result = "<#{aParent}>\n"
89
+ aHash.each {|key,value| result += "\t<Item Name=\"#{key.to_s}\">#{value.to_s}</Item>\n" }
90
+ result += "<#{aParent}/>\n"
91
+ return result
92
+ end
93
+
94
+ def self.read_config_values(aXmlConfig)
95
+ xmlRoot = get_file_root(aXmlConfig)
96
+ return read_simple_items(xmlRoot,'/Config/SimpleItems')
97
+ end
98
+
99
+ # Takes a node or xml string and writes it out formatted nicely.
100
+ # aOutput may be given eg. a stream or nil can be given to get a returned string
101
+ def self.format_nicely(aXml,aOutput=nil)
102
+ aXml = REXML::Document.new(aXml) unless aXml.is_a?(REXML::Element)
103
+ f = REXML::Formatters::Pretty.new(2,true)
104
+ f.compact = true
105
+ f.width = 120
106
+ aOutput ||= ''
107
+ f.write(aXml,aOutput)
108
+ return aOutput
109
+ end
110
+
111
+ def self.encode(aString)
112
+ result = aString.clone;
113
+ result.gsub!('&','&amp;')
114
+ result.gsub!('<','&lt;')
115
+ result.gsub!('>','&gt;')
116
+ result.gsub!('"','&quot;')
117
+ result.gsub!("'",'&apos;')
118
+ result.gsub!(/[\x80-\xFF]/) {|c| "&#x#{'%X' % c[0]};"}
119
+ return result
120
+ end
121
+
122
+ def self.hash_to_deflist(aHash,aBuilder=nil)
123
+ aBuilder ||= Builder::XmlMarkup.new(:indent => 2)
124
+ aBuilder.dl do
125
+ aHash.each do |k,v|
126
+ aBuilder.dt(k.to_s)
127
+ aBuilder.dd(v.to_s)
128
+ end
129
+ end
130
+ end
131
+
132
+ def self.data_to_table(aRowHashes,aCaption=nil,aColNames=nil,aBuilder=nil)
133
+ aBuilder ||= Builder::XmlMarkup.new(:indent => 2)
134
+ aBuilder.table do
135
+ if aCaption.is_a? String
136
+ aBuilder.caption(aCaption)
137
+ elsif aCaption.is_a? Hash
138
+ aBuilder.caption do
139
+ XmlUtils.hash_to_deflist(aCaption,aBuilder)
140
+ end
141
+ end
142
+ aColNames ||= aRowHashes.first.keys
143
+ aBuilder.thead do
144
+ aBuilder.tr do
145
+ aColNames.each do |name|
146
+ aBuilder.td(name.to_s)
147
+ end
148
+ end
149
+ end
150
+ aBuilder.tbody do
151
+ aRowHashes.each do |row|
152
+ aBuilder.tr do
153
+ aColNames.each do |name|
154
+ aBuilder.td(row[name].to_s)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ end
163
+