twb 1.0.5 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/twb.rb +10 -6
- data/lib/twb/analysis/CalculatedFields/CSVEmitter.rb +154 -0
- data/lib/twb/analysis/CalculatedFields/CalculatedFieldsAnalyzer.rb +527 -0
- data/lib/twb/analysis/CalculatedFields/MarkdownEmitter.rb +71 -0
- data/lib/twb/analysis/DataSources/DataSourceTableFieldsCSVEmitter.rb +117 -0
- data/lib/twb/analysis/Sheets/WorksheetDataStructureCSVEmitter.rb +192 -0
- data/lib/twb/calculatedfield.rb +47 -0
- data/lib/twb/columnfield.rb +94 -0
- data/lib/twb/dashboard.rb +1 -1
- data/lib/twb/datasource.rb +368 -121
- data/lib/twb/fieldcalculation.rb +157 -81
- data/lib/twb/localfield.rb +32 -29
- data/lib/twb/metadatafield.rb +57 -15
- data/lib/twb/util/ftpPublisher.rb +48 -0
- data/lib/twb/util/joinUtilities.rb +52 -0
- data/lib/twb/workbook.rb +27 -10
- data/lib/twb/worksheet.rb +146 -53
- metadata +11 -5
- data/lib/twb/analysis/calculatedfieldsanalyzer.rb +0 -508
- data/lib/twb/apps/X-Ray Dashboards.rb +0 -80
- data/lib/twb/countNodes.rb +0 -98
data/lib/twb/fieldcalculation.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2014, 2015 Chris Gerrard
|
1
|
+
# Copyright (C) 2014, 2015, 2017 Chris Gerrard
|
2
2
|
#
|
3
3
|
# This program is free software: you can redistribute it and/or modify
|
4
4
|
# it under the terms of the GNU General Public License as published by
|
@@ -19,141 +19,144 @@ module Twb
|
|
19
19
|
|
20
20
|
class FieldCalculation
|
21
21
|
|
22
|
-
attr_reader :node, :fieldNode,
|
23
|
-
attr_reader :
|
24
|
-
attr_reader :
|
22
|
+
attr_reader :node, :field, :fieldNode, :dataSource
|
23
|
+
attr_reader :uiname, :techname, :caption
|
24
|
+
attr_reader :has_formula
|
25
|
+
attr_reader :formula
|
26
|
+
attr_reader :formulaUC
|
27
|
+
attr_reader :formulaResolved
|
28
|
+
attr_reader :formulaFlat
|
29
|
+
attr_reader :formulaFlatResolved
|
30
|
+
attr_reader :formulaLines
|
31
|
+
attr_reader :is_tableCalc
|
32
|
+
attr_reader :is_lod, :lodCodePos
|
25
33
|
attr_reader :class, :scopeIsolation
|
26
|
-
attr_reader :fields, :
|
34
|
+
attr_reader :fields, :remoteFields, :calcFields
|
27
35
|
attr_reader :comments
|
28
36
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
37
|
+
@@tableCalcs = [ 'FIRST', 'INDEX', 'LAST', 'SIZE',
|
38
|
+
'LOOKUP', 'PREVIOUS_VALUE',
|
39
|
+
'RANK', 'RANK_DENSE', 'RANK_MODIFIED', 'RANK_PERCENTILE', 'RANK_UNIQUE',
|
40
|
+
'RUNNING_AVG', 'RUNNING_COUNT', 'RUNNING_MAX', 'RUNNING_MIN', 'RUNNING_SUM',
|
41
|
+
'SCRIPT_BOOL', 'SCRIPT_INT', 'SCRIPT_REAL', 'SCRIPT_STR',
|
42
|
+
'TOTAL',
|
43
|
+
'WINDOW_SUM', 'WINDOW_AVG', 'WINDOW_COUNT',
|
44
|
+
'WINDOW_MIN', 'WINDOW_MEDIAN', 'WINDOW_MAX', 'WINDOW_PERCENTILE',
|
45
|
+
'WINDOW_STDEV', 'WINDOW_STDEVP',
|
46
|
+
'WINDOW_CORR', 'WINDOW_COVAR', 'WINDOW_COVARP',
|
47
|
+
'WINDOW_VAR', 'WINDOW_VARP'
|
48
|
+
]
|
49
|
+
|
50
|
+
def initialize(calcField, datasource=nil)
|
51
|
+
raise ArgumentError.new("FieldCalculation must be initialized with a CalculatedField, has been provided with a #{calcField.class}") if calcField.class != Twb::CalculatedField
|
52
|
+
@field = calcField
|
53
|
+
calcNode = calcField.node
|
54
|
+
@istableCalc = false
|
55
|
+
# puts "FieldCalculation calcNode.nil? :: #{calcNode.nil?} "
|
56
|
+
unless calcNode.nil?
|
57
|
+
@node = calcNode.at_xpath('./calculation')
|
58
|
+
@fieldNode = calcField.node
|
33
59
|
@dataSource = datasource
|
34
60
|
@class = attribText(@node, 'class')
|
35
61
|
@remoteFields = {}
|
36
62
|
if 'categorical-bin'.eql? @class
|
37
|
-
# puts calcNode
|
38
63
|
# <calculation class='categorical-bin'
|
39
64
|
# column='[Calculation_507569757376950272]'
|
40
65
|
# default='"Other"'
|
41
66
|
# new-bin='true'>
|
42
|
-
@techname =
|
67
|
+
@techname = @node.attribute('column').text.gsub(/^\[|\]$/,'') # assumes the column attribute exists
|
43
68
|
@caption = attribText(@fieldNode, 'caption')
|
44
69
|
uiname = if datasource.nil?
|
45
70
|
@techName
|
46
71
|
else
|
47
72
|
datasource.fieldUIName(@techname)
|
48
73
|
end
|
49
|
-
@uiname = "#{uiname}
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
74
|
+
@uiname = "#{uiname} <<group>>"
|
75
|
+
@has_formula = true
|
76
|
+
@formula = "grouped <<#{uiname}>> values"
|
77
|
+
@formulaLines = [ @formula ]
|
78
|
+
@formulaFlat = @formula
|
79
|
+
@comments = [ ]
|
80
|
+
@is_lod = false
|
53
81
|
else
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@caption = @fieldNode.attribute('caption').nil? ? nil : @fieldNode.attribute('caption').text
|
58
|
-
@techname = @fieldNode.attribute('name').text.gsub(/^\[|\]$/,'') # assumes the name attribute exists
|
59
|
-
@uiname = @caption.nil? ? @techname : @caption
|
60
|
-
# puts " caption: #{@caption}"
|
61
|
-
# puts "techname: #{@techname}"
|
62
|
-
# puts " uiname: #{@uiname}"
|
82
|
+
@caption = calcField.caption
|
83
|
+
@techname = calcField.name
|
84
|
+
@uiname = calcField.uiname
|
63
85
|
#--
|
64
86
|
@scopeIsolation = attribText(@node, 'scope-isolation')
|
65
87
|
#-- Formula --
|
66
88
|
@has_formula = @node.has_attribute?('formula')
|
67
89
|
if @has_formula
|
68
|
-
@formula = @node.attribute('formula').text
|
90
|
+
@formula = @node.attribute('formula').text.gsub(/\r\n/,"\n")
|
91
|
+
@formulaUC = @formula.upcase
|
69
92
|
@formulaLines = formula.split(/\n|\r\n/)
|
70
93
|
@formulaFlat = flattenFormula(@formulaLines)
|
71
94
|
@comments = getComments(@formulaLines)
|
72
|
-
|
73
|
-
@
|
74
|
-
@
|
75
|
-
@
|
76
|
-
@comments = [ ]
|
95
|
+
@lodCodePos = @formula =~ /^[ ]*{[ ]*(fixed|include|exclude)[ ]*/i
|
96
|
+
@is_lod = !lodCodePos.nil? && @lodCodePos >= 0
|
97
|
+
@is_tableCalc = @@tableCalcs.any? { |tc| @formulaUC.include?(tc) } #assessTableCalc @formula
|
98
|
+
# puts "#{@lodCodePos} \t #{@is_lod} \t #{@is_lod.class} \t => #{@formula}"
|
77
99
|
end
|
78
|
-
#-- Fields --
|
79
|
-
parseFormFields # establishes @ fields
|
80
|
-
resolveFields # establishes @ resolvedFields
|
81
100
|
end
|
82
101
|
end
|
83
102
|
end
|
84
103
|
|
104
|
+
# def assessTableCalc formula
|
105
|
+
# @@tableCalcs.any? { |tc| string.include?(tc) }
|
106
|
+
# end
|
107
|
+
|
85
108
|
def attribText(node, attribute)
|
86
109
|
node.attribute(attribute).nil? ? nil : node.attribute(attribute).text
|
87
110
|
end
|
88
111
|
|
89
112
|
|
90
113
|
def parseFormFields
|
91
|
-
@fields
|
92
|
-
|
114
|
+
@fields = Set.new []
|
115
|
+
@calcFields = SortedSet.new []
|
116
|
+
formula = @formulaFlat
|
93
117
|
if !formula.nil? && formula.include?('[') && formula.include?(']')
|
94
|
-
noSqLits = formula.gsub(/'[\[\.\]]+'/, ' ')
|
118
|
+
noSqLits = formula.gsub( /'[\[\.\]]+'/, ' ')
|
95
119
|
flatForm = noSqLits.gsub( /\n/, ' ')
|
96
120
|
stripFrt = flatForm.gsub( /^[^\[]*[\[]/ , '[' )
|
97
121
|
stripBck = stripFrt.gsub( /\][^\]]+$/ , ']' )
|
98
122
|
stripMid = stripBck.gsub( /\][^\]]{2,}\[/ , ']]..[[' )
|
99
123
|
stripCom = stripMid.gsub( /\][ ]*,[ ]*\[/ , ']]..[[' )
|
100
|
-
stripFns = stripMid.gsub( /\][ ]*[
|
124
|
+
stripFns = stripMid.gsub( /\][ ]*[\*\/+\-,=][ ]*\[/ , ']]..[[' )
|
101
125
|
fields = stripFns.split(']..[')
|
102
126
|
fields.each { |field| @fields.add field.gsub(/^\[|\]$/, '')}
|
127
|
+
fields.each do |field|
|
128
|
+
cf = CalculationField.new( field.gsub(/^\[|\]$/, ''), @dataSource, @dataSource.workbook )
|
129
|
+
@calcFields.add cf
|
130
|
+
end
|
103
131
|
end
|
104
132
|
end
|
105
133
|
|
106
|
-
|
107
|
-
|
108
|
-
@resolvedFields = []
|
109
|
-
@fields.each do |field|
|
110
|
-
rawField = field.gsub(/^\[|\]$/,'')
|
111
|
-
parts = rawField.split('].[')
|
112
|
-
if parts.length > 1
|
113
|
-
source = parts[0]
|
114
|
-
fieldCalc = parts[1]
|
115
|
-
fieldui = fieldCalc
|
116
|
-
else
|
117
|
-
source = nil
|
118
|
-
fieldCalc = parts[0]
|
119
|
-
fieldui = @dataSource.fieldUIName(fieldCalc)
|
120
|
-
end
|
121
|
-
hash = { :field => fieldCalc, :fieldui => fieldui, :source => source }
|
122
|
-
@resolvedFields << hash
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
|
127
|
-
def resolvedFormula
|
128
|
-
@resolvedFormula ||= resolveFormula
|
134
|
+
def formulaResolved
|
135
|
+
@formulaResolved ||= resolveFormula
|
129
136
|
end
|
130
|
-
|
137
|
+
|
131
138
|
def resolveFormula
|
132
|
-
# puts "resolveFormula :\n-- formula --\n %s \n-- @dataSource.nil? : %s \n-- @resolvedFields.nil? : %s \n--" % [ @formula, @dataSource.nil?, @resolvedFields.nil? ]
|
133
139
|
formula = @formula
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
fieldInternal = field # "[#{rf[:field]}]"
|
142
|
-
fieldUI = @dataSource.fieldUIName(field)
|
143
|
-
# puts " f?: #{formula.include?(field)}"
|
144
|
-
# puts " fui: #{fieldUI}"
|
145
|
-
formula = formula.gsub(field,fieldUI) unless fieldUI.nil?
|
146
|
-
# puts "formula:\n--\n#{formula}"
|
147
|
-
else
|
148
|
-
@remoteFields[source] = SortedSet.new unless @remoteFields.include? source
|
149
|
-
@remoteFields[source].add field
|
150
|
-
end
|
140
|
+
parseFormFields # - extracts the fields from the formula; as persisted they're the internal names
|
141
|
+
# puts "::++ #{formula}"
|
142
|
+
@calcFields.each do |calcField|
|
143
|
+
if calcField.techUIdiff
|
144
|
+
# puts ":::: #{calcField.techCode} // #{calcField.uiCode}"
|
145
|
+
formula = formula.gsub(calcField.techCode,calcField.uiCode)
|
146
|
+
# puts ":--: #{formula}"
|
151
147
|
end
|
152
148
|
end
|
153
|
-
# puts "resolvedFormula:\n==\n#{formula}\n--"
|
154
149
|
return formula
|
155
150
|
end
|
156
151
|
|
152
|
+
def formulaFlatResolved
|
153
|
+
@formulaFlatResolved ||= flattenResolvedFormula
|
154
|
+
end
|
155
|
+
|
156
|
+
def flattenResolvedFormula
|
157
|
+
formula = formulaResolved
|
158
|
+
formula.gsub(/\n/, ' ')
|
159
|
+
end
|
157
160
|
|
158
161
|
def flattenFormula lines
|
159
162
|
formula = ''
|
@@ -175,7 +178,80 @@ module Twb
|
|
175
178
|
return comments.strip
|
176
179
|
end
|
177
180
|
|
178
|
-
|
179
181
|
end # class FieldCalculation
|
180
182
|
|
183
|
+
|
184
|
+
|
185
|
+
class CalculationField
|
186
|
+
# is a field used in a calculation, resolved into it's human-meaningful form
|
187
|
+
|
188
|
+
include Comparable
|
189
|
+
|
190
|
+
attr_reader :techName, :techCode
|
191
|
+
attr_reader :uiName, :uiCode
|
192
|
+
attr_reader :dataSource, :dataSourceRef, :dataSourceExists
|
193
|
+
attr_reader :fqName
|
194
|
+
attr_reader :techUIdiff
|
195
|
+
|
196
|
+
def initialize code, datasource, workbook
|
197
|
+
# puts "\nCalculationField <= \n\t #{code}"
|
198
|
+
@dataSource = datasource.uiname
|
199
|
+
@dataSourceRef = :local
|
200
|
+
@dataSourceExists = true
|
201
|
+
@techUIdiff = false
|
202
|
+
@uiName = ''
|
203
|
+
rawCode = code.gsub(/^\[|\]$/,'')
|
204
|
+
parts = rawCode.split('].[')
|
205
|
+
# puts "\n\nField: #{code} \t parts: #{parts.length} - #{parts.inspect}"
|
206
|
+
if parts.length == 1
|
207
|
+
@techName = parts[0]
|
208
|
+
@techCode = "[#{parts[0]}]"
|
209
|
+
if datasource.nil?
|
210
|
+
@uiName = @techName
|
211
|
+
@uiCode = @techCode
|
212
|
+
@techUIdiff = false
|
213
|
+
else # !datasource.nil?
|
214
|
+
# puts "#{@techName} \t:: #{datasource.fieldUIName(@techName).nil?} \t:: #{datasource.fieldUIName(@techName)} "
|
215
|
+
@uiName = datasource.fieldUIName(@techName).nil? ? @techName : datasource.fieldUIName(@techName)
|
216
|
+
@uiCode = @uiName.nil? ? @techCode : "[#{@uiName}]"
|
217
|
+
@techUIdiff = !@techCode.eql?(@uiCode)
|
218
|
+
end
|
219
|
+
else # parts.length <> 1
|
220
|
+
rdstech = parts[0]
|
221
|
+
calcField = parts[1]
|
222
|
+
@uiName = calcField
|
223
|
+
@dataSource = rdstech
|
224
|
+
@dataSourceRef = :remote
|
225
|
+
@techCode = "[#{rdstech}].[#{calcField}]"
|
226
|
+
workbook = datasource.workbook
|
227
|
+
remoteds = workbook.nil? ? nil : workbook.datasource(rdstech)
|
228
|
+
# puts "\t twb: #{workbook.class} / remoteds: #{remoteds.class} : #{remoteds.nil? ? "<<NOT FOUND:#{rdstech}:>>" : remoteds.uiname} "
|
229
|
+
#--
|
230
|
+
if remoteds.nil? || remoteds.fieldUIName(calcField).nil?
|
231
|
+
@uiName = calcField
|
232
|
+
@uiCode = "[<<NOT FOUND>>#{rdstech}].[#{calcField}]"
|
233
|
+
@techUIdiff = true
|
234
|
+
@dataSourceExists = false
|
235
|
+
else !remoteds.nil?
|
236
|
+
@dataSource = remoteds.uiname
|
237
|
+
@uiName = remoteds.fieldUIName(calcField)
|
238
|
+
@uiCode = "[#{@dataSource}].[#{@uiName}]"
|
239
|
+
@techUIdiff = !@techCode.eql?(@uiCode)
|
240
|
+
@dataSourceExists = true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
@fqName = "#{@dataSource}::#{@uiName}"
|
244
|
+
end # initialize
|
245
|
+
|
246
|
+
def <=>(other)
|
247
|
+
# myName = @uiName.nil? ? '' : @uiName
|
248
|
+
# otherName = other.uiName.nil? ? "" : other.uiName
|
249
|
+
# # puts "#{@uiName} / #{myName} <=> #{otherName} / #{other.uiName}"
|
250
|
+
# # puts "#{@uiName.nil?} // #{other.uiName.nil?}"
|
251
|
+
# myName <=> otherName
|
252
|
+
@fqName <=> other.fqName
|
253
|
+
end
|
254
|
+
|
255
|
+
end # class CalculationField
|
256
|
+
|
181
257
|
end # module Twb
|
data/lib/twb/localfield.rb
CHANGED
@@ -21,44 +21,47 @@ module Twb
|
|
21
21
|
|
22
22
|
class LocalField
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :node, :type, :datatype, :name, :ordinal
|
25
25
|
|
26
26
|
def initialize fieldNode
|
27
|
+
puts "lf:: #{fieldNode}"
|
27
28
|
@node = fieldNode
|
28
29
|
@type = 'local'
|
29
|
-
@tableauname = @node.attr('name')
|
30
|
-
@dbname = @node.attr('name').gsub(/^\[/,'').gsub(/\]$/,'')
|
31
30
|
@datatype = @node.attr('datatype')
|
32
|
-
@
|
33
|
-
@
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
40
|
-
|
31
|
+
@name = @node.attr('name')
|
32
|
+
@ordinal = @node.attr('ordinal')
|
33
|
+
# @dbname = @node.attr('name').gsub(/^\[/,'').gsub(/\]$/,'')
|
34
|
+
# @datatype = @node.attr('datatype')
|
35
|
+
# @role = @node.attr('role')
|
36
|
+
# @type = @node.attr('type')
|
37
|
+
# @hidden = @node.attr('hidden')
|
38
|
+
# @caption = @node.attr('caption')
|
39
|
+
# @aggregation = @node.attr('aggregation')
|
40
|
+
# @calculation = getCalculation
|
41
|
+
# @comments = getComments
|
42
|
+
# @uiname = if @caption.nil? || @caption == '' then @dbname else @caption end
|
43
|
+
# return self
|
41
44
|
end
|
42
45
|
|
43
|
-
def getCalculation
|
44
|
-
|
45
|
-
|
46
|
-
end
|
46
|
+
# def getCalculation
|
47
|
+
# calcNode = @node.at_xpath("./calculation")
|
48
|
+
# FieldCalculation.new(calcNode) unless calcNode.nil?
|
49
|
+
# end
|
47
50
|
|
48
|
-
def getComments
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
51
|
+
# def getComments
|
52
|
+
# comments = ''
|
53
|
+
# runs = node.xpath('./desc/formatted-text/run')
|
54
|
+
# runs.each do |run|
|
55
|
+
# unless run.nil?
|
56
|
+
# comments += run.text
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
# return comments
|
60
|
+
# end
|
58
61
|
|
59
|
-
def remove_attribute attribute
|
60
|
-
|
61
|
-
end
|
62
|
+
# def remove_attribute attribute
|
63
|
+
# @node.remove_attribute(attribute)
|
64
|
+
# end
|
62
65
|
|
63
66
|
end
|
64
67
|
|
data/lib/twb/metadatafield.rb
CHANGED
@@ -17,37 +17,79 @@ require 'nokogiri'
|
|
17
17
|
|
18
18
|
module Twb
|
19
19
|
|
20
|
-
# Assumption: A field can only be either a MetadataField or a LocalField, not both in a given Workbook data connection.
|
21
|
-
|
22
20
|
class MetadataField
|
23
21
|
|
24
|
-
|
25
|
-
|
22
|
+
include Comparable
|
23
|
+
|
24
|
+
attr_reader :node, :class
|
25
|
+
attr_accessor :source
|
26
|
+
attr_reader :parentName, :table # parentName is the Db table
|
27
|
+
attr_reader :name, :caption, :family
|
28
|
+
attr_reader :localName, :localType
|
29
|
+
attr_reader :remoteName, :remoteAlias
|
30
|
+
attr_reader :remoteType, :aggregation
|
31
|
+
attr_reader :containsNull, :ordinal
|
32
|
+
attr_reader :precision, :width, :scale
|
33
|
+
|
34
|
+
# Child nodes of <metadata-record nodes
|
35
|
+
# <aggregation
|
36
|
+
# <approx-count
|
37
|
+
# <attributes
|
38
|
+
# <caption
|
39
|
+
# <collation
|
40
|
+
# <contains-null
|
41
|
+
# <family
|
42
|
+
# <layered
|
43
|
+
# <local-name
|
44
|
+
# <local-type
|
45
|
+
# <ordinal
|
46
|
+
# <parent-name
|
47
|
+
# <precision
|
48
|
+
# <remote-alias
|
49
|
+
# <remote-name
|
50
|
+
# <remote-type
|
51
|
+
# <scale
|
52
|
+
# <statistics
|
53
|
+
# <width
|
54
|
+
|
26
55
|
|
27
56
|
def initialize fieldNode
|
57
|
+
@class = fieldNode.attribute('class').text
|
28
58
|
@node = fieldNode
|
59
|
+
@parentName = load 'parent-name'
|
60
|
+
@family = load 'family'
|
61
|
+
@table = @family.nil? ? @parentName : @family
|
62
|
+
@remoteName = load 'remote-name'
|
63
|
+
@remoteAlias = load 'remote-alias'
|
64
|
+
@remoteType = load 'remote-type'
|
65
|
+
@caption = load 'caption'
|
66
|
+
@localName = load 'local-name'
|
67
|
+
@name = @caption.nil? ? @localName : @caption
|
29
68
|
@aggregation = load 'aggregation'
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@localtype = load 'local-type'
|
69
|
+
@containsNull = load 'contains-null'
|
70
|
+
@localType = load 'local-type'
|
33
71
|
@ordinal = load 'ordinal'
|
34
|
-
@parentname = load 'parent-name'
|
35
72
|
@precision = load 'precision'
|
36
|
-
@
|
37
|
-
@remotename = load 'remote-name'
|
38
|
-
@name = @remotename
|
39
|
-
@remotetype = load 'remote-type'
|
73
|
+
@scale = load 'scale'
|
40
74
|
@width = load 'width'
|
41
|
-
|
75
|
+
@id = "'%s::%s' " % [@table,@remoteName]
|
76
|
+
# return self
|
42
77
|
end
|
43
78
|
|
44
79
|
def load nodeName
|
45
80
|
node = @node.at_xpath(nodeName)
|
46
|
-
val
|
47
|
-
# puts "==== MD node:'#{nodeName}' \t nil?'#{node.nil?}' \t == val:#{val} \t = '#{node}' "
|
81
|
+
val = node.nil? ? nil : node.text.strip.gsub(/^\[|\]$/,'')
|
48
82
|
return val
|
49
83
|
end
|
50
84
|
|
85
|
+
def to_s
|
86
|
+
@id
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=>(other)
|
90
|
+
@id <=> other.id
|
91
|
+
end
|
92
|
+
|
51
93
|
end
|
52
94
|
|
53
95
|
end
|