yore 0.0.1

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