twb 0.5.3 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d8e5620e8ff52252576b9ba62c2c274341859698
4
+ data.tar.gz: de116f608f42f388d3527c09f23a2786c7193918
5
+ SHA512:
6
+ metadata.gz: 65e5bda53ed6f4b1dc53e6dfce49fd6a70483369a5738ddd8fe8000d8aa997a4a4f09d5aff7082f96e71d0358f0c8dacfb68c6d46223d0f0acec3ee28defb7eb
7
+ data.tar.gz: fc675deeba6612bbaa5eb8523494abd580d65e3bceff019525e4a6519f5a66dedcd970dcf6c95b5ce340d86d5fa61ca0dbc504a638784ec4d2eb8451f50dff61
data/lib/twb/action.rb ADDED
@@ -0,0 +1,121 @@
1
+ # Copyright (C) 2014, 2016 Chris Gerrard
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'nokogiri'
17
+ require 'digest/md5'
18
+
19
+ module Twb
20
+
21
+ class Action
22
+
23
+ # Notes
24
+ # Sources:
25
+ # <source>
26
+ # type="datasource"
27
+ # <source datasource="Sample - Superstore - English (Extract)" type="datasource">
28
+ # <exclude-sheets - use to exclude these from the Workbook's sheets
29
+ # - haven't seen an example of named sheet for datasoure
30
+ # - can only target a single worksheet, by observation
31
+ # type="sheet"
32
+ # Worksheet
33
+ # <source type="sheet" worksheet="Sheet 3"/>
34
+ # - use the worksheet attribute
35
+ # - can only target a single worksheet, by observation
36
+ # Dashboard
37
+ # <source dashboard="Dashboard 1" type="sheet" worksheet="Sheet 5"/>
38
+ # - dashboard attribute is present
39
+ # - use the worksheet attribute, if available
40
+ # <source dashboard="Dashboard 2" type="sheet"/>
41
+ # NO <exclude-sheets - source is all of the dashboard's sheets
42
+
43
+ @@hasher = Digest::SHA256.new
44
+
45
+ attr_reader :workbook, :node
46
+ attr_reader :name, :caption, :uiname
47
+ attr_reader :type
48
+ attr_reader :sourceDash, :sourceType, :sourceSheet
49
+ attr_reader :cmdTarget, :cmdExclude, :cmdSpecFields
50
+ attr_reader :linkExpression
51
+
52
+ def initialize actionNode, workbookNode
53
+ @workbook = workbookNode
54
+ @node = actionNode
55
+ # --
56
+ @name = @node.attr('name')
57
+ @caption = @node.attr('caption')
58
+ @uiname = @caption.nil? ? @name : @caption
59
+ # --
60
+ @type = setType
61
+ # --
62
+ @cmdTarget = cmdParam 'target'
63
+ @cmdExclude = cmdParam 'exclude'
64
+ @cmdSpecFields = cmdParam 'special-fields'
65
+ # --
66
+ @linkExpression = linkExpr
67
+ process
68
+ return self
69
+ end
70
+
71
+ def process
72
+
73
+ end
74
+
75
+ def setType
76
+ linkNode = @node.at_xpath('./link')
77
+ return :link unless linkNode.nil?
78
+ command = @node.at_xpath('./command/@command')
79
+ return :notset if command.nil?
80
+ type = case command.text
81
+ when 'tsc:brush'
82
+ :highlight
83
+ when 'tsc:tsl-filter'
84
+ :filter
85
+ else
86
+ command.text
87
+ end
88
+ end
89
+
90
+ def linkExpr
91
+ attr = @node.at_xpath('./link/@expression')
92
+ attr.nil? ? nil : attr.text
93
+ end
94
+
95
+ def cmdParam param
96
+ n = @node.at_xpath("./command/param[@name='#{param}']")
97
+ return nil if n.nil?
98
+ v = n.attribute('value')
99
+ v.nil? ? nil : v.text
100
+ end
101
+
102
+ def sourceDash
103
+ attr = @node.at_xpath('./source/@dashboard')
104
+ attr.nil? ? nil : attr.text
105
+ end
106
+
107
+ def sourceSheet
108
+ attr = @node.at_xpath('./source/@worksheet')
109
+ attr.nil? ? nil : attr.text
110
+ end
111
+
112
+ def sourceType
113
+ @node.at_xpath('./source/@type')
114
+ end
115
+
116
+ def typeConv rawType
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,98 @@
1
+ require 'nokogiri'
2
+ require 'csv'
3
+ require 'twb'
4
+
5
+ def init
6
+ system 'cls'
7
+ $pFile = File.open('sqlFromTwbDc.txt','w')
8
+
9
+ sqiCSV = 'TWBFieldsByType.csv'
10
+ $fieldsCSV = CSV.open(sqiCSV, 'w')
11
+ $fieldsCSV << [
12
+ 'TWB',
13
+ 'Data Connection - UI',
14
+ 'Field Name',
15
+ 'Field Type',
16
+ ]
17
+ $path = if ARGV.empty? then '**/*.twb' else ARGV[0] end
18
+ emit " "
19
+ emit " Generating SQL Create Table code for Tableau Workbook Data Connections (TWDCs)."
20
+ emit " Each TWDC will have it's own *.sql file containing the SQL code.\n "
21
+ emit " Looking for Workbooks matching: '#{$path}' - from: #{ARGV[0]}\n\n "
22
+ end
23
+
24
+ def method_name
25
+ datasources = {}
26
+ dataSourcesNode = doc.at_xpath('//workbook/datasources')
27
+ dataSourcesNodes = doc.xpath('//workbook/datasources/datasource').to_a
28
+ puts " dsn: #{dataSourcesNodes}"
29
+ datasourceNodes.each do |node|
30
+ datasource = Twb::DataSource.new(node)
31
+ datasources[datasource.name] = datasource
32
+ end
33
+ end
34
+
35
+ $localEmit = true
36
+ def emit stuff
37
+ $pFile.puts stuff
38
+ puts stuff if $localEmit
39
+ end
40
+
41
+ $paths = {
42
+ 'Relation Columns' => './connection/relation/columns/column',
43
+ 'Metadata Records' => './connection/metadata-records/metadata-record',
44
+ 'Columns' => './column',
45
+ }
46
+
47
+ def proc file
48
+ emit "\n == #{file}"
49
+ end
50
+
51
+ def process file
52
+ emit "\n == #{file}"
53
+ doc = Nokogiri::XML(open(file))
54
+ dataSourcesNodes = doc.xpath('//workbook/datasources/datasource')
55
+ dataSourcesNodes.each do |ds|
56
+ emit "\n dc: #{ds.attribute('caption')}"
57
+ emit " dn: #{ds.attribute('name')}\n ---"
58
+ typeCounts = {}
59
+ $paths.each do |type, path|
60
+ nodes = ds.xpath(path).to_a
61
+ # emit " : %3i %-17s %-s" % [nodes.size, type, path]
62
+ typeCnt = nodes.size
63
+ nodes.each do |n|
64
+ $fieldsCSV << [file,ds.attribute('name'),n.attribute('name'),type]
65
+ end
66
+ if type == 'Columns' then
67
+ calcCnt = 0
68
+ nodes.each do |n|
69
+ calc = n.xpath('./calculation')
70
+ # emit " c: #{calc.size} "
71
+ calcCnt += calc.size
72
+ end
73
+ # emit ' ---'
74
+ # emit " c#: %3i " % [calcCnt]
75
+ # emit " !c#: %3i " % [nodes.size - calcCnt]
76
+ typeCounts['Columns'] = nodes.size
77
+ typeCounts['Columns - calc'] = calcCnt
78
+ typeCounts['Columns - not calc'] = nodes.size - calcCnt
79
+ else
80
+ typeCounts[type] = typeCnt
81
+ end
82
+ end
83
+ # emit ' ---'
84
+ typeCounts.each do |t,c|
85
+ emit " tc: %3i %-s" % [c, t]
86
+ end
87
+ end
88
+ end
89
+
90
+ init
91
+
92
+ $paths.each do |path|
93
+ emit " p: #{path}"
94
+ end
95
+
96
+ Dir.glob($path) {|twb| process twb }
97
+
98
+ emit "\n\n== Done ==\n "
data/lib/twb/dashboard.rb CHANGED
@@ -22,7 +22,11 @@ module Twb
22
22
 
23
23
  @@hasher = Digest::SHA256.new
24
24
 
25
- attr_reader :node, :name, :worksheets, :autosize, :size, :maxheight, :maxwidth, :minheight, :minwidth, :rangesize, :dimensions, :zonecount
25
+ attr_reader :node
26
+ attr_reader :name
27
+ attr_reader :worksheets
28
+ attr_reader :autosize, :size, :maxheight, :maxwidth, :minheight, :minwidth, :rangesize, :dimensions
29
+ attr_reader :zonecount
26
30
 
27
31
  def initialize dashboardNode, twbworksheets
28
32
  @node = dashboardNode
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014, 2015 Chris Gerrard
1
+ # Copyright (C) 2014, 2015, 2016 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
@@ -15,6 +15,7 @@
15
15
 
16
16
  require 'nokogiri'
17
17
  require 'digest/md5'
18
+ require 'json'
18
19
 
19
20
  module Twb
20
21
 
@@ -22,7 +23,25 @@ module Twb
22
23
 
23
24
  @@hasher = Digest::SHA256.new
24
25
 
25
- attr_reader :node, :name, :caption, :uiname, :connHash, :dsclass, :connection, :tables, :localfields, :metadatafields
26
+ @@connGNodeParamsJSON = %q(
27
+ { "csv" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "source" },
28
+ "excel" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "source" },
29
+ "dataengine" : { "label" : ["dbname" ], "id" : ["directory","filename"], "type" : "source" },
30
+ "msaccess" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "source" },
31
+ "textscan" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "source" },
32
+ "excel-direct" : { "label" : ["filename"], "id" : [ "filename"], "type" : "source" },
33
+ "salesforce" : { "label" : ["server" ], "id" : [ "server" ], "type" : "source" }
34
+ }
35
+ )
36
+ @@cgNodeParams = JSON.parse @@connGNodeParamsJSON
37
+
38
+
39
+ attr_reader :name, :caption, :uiname, :dsclass
40
+ attr_reader :connection, :connHash
41
+ attr_reader :tables
42
+ attr_reader :localfields, :metadatafields, :mappedFields, :fieldUINames, :calculatedFields
43
+ attr_reader :filters
44
+ attr_reader :node
26
45
 
27
46
  def initialize dataSourceNode
28
47
  @node = dataSourceNode
@@ -31,11 +50,22 @@ module Twb
31
50
  @uiname = if @caption.nil? || @caption == '' then @name else @caption end
32
51
  processConnection
33
52
  processFields
53
+ processFilters
54
+ loadTableFields
34
55
  return self
35
56
  end
36
57
 
58
+ def tableauVersion tabVersion
59
+ @tableauVersion = tabVersion
60
+ end
61
+
62
+ def tableauVersion
63
+ @tableauVersion
64
+ end
65
+
37
66
  def processConnection
38
67
  @connection = @node.at_xpath('./connection')
68
+ @dsclass = @name # handles 'Parameters' data source, which has no connection element (& others if so)
39
69
  unless @connection.nil?
40
70
  @dsclass = @connection.attribute('class').text
41
71
  # note: must use "dsclass" as "class" would override Rubys ".class" Kernel method
@@ -67,25 +97,27 @@ module Twb
67
97
  end
68
98
 
69
99
  def Parameters?
70
- @name == 'Parameters'
100
+ 'Parameters'.eql? @name
71
101
  end
72
102
 
73
103
  def processFields
74
104
  # --
75
- @localfields = {}
76
- @metadatafields = {}
105
+ @localfields = {}
106
+ @metadatafields = {}
107
+ @calculatedFields = @node.xpath("./column/calculation")
77
108
  return if @connection.nil?
78
109
  ## load local fields
79
110
  connClass = @node.at_xpath('./connection').attribute('class').text
80
111
  fxpath = case connClass
81
112
  when 'dataengine' then './column'
113
+ when 'sqlserver' then './column'
82
114
  else './connection/relation/columns/column'
83
115
  end
84
116
  nodes = @node.xpath(fxpath)
85
117
  # puts "DATASOURCE ::=>> @node: connClass: '#{connClass.class}' ::: #{connClass.eql?('dataengine')} fxpath: #{fxpath} :: #{nodes.length}"
86
118
  nodes.each do |node|
87
119
  field = Twb::LocalField.new(node)
88
- @localfields[field.name] = field
120
+ @localfields[field.dbname] = field
89
121
  end
90
122
  ## load metadata fields
91
123
  nodes = @node.xpath("./connection/metadata-records/metadata-record[@class='column']")
@@ -95,8 +127,166 @@ module Twb
95
127
  field = Twb::MetadataField.new(node)
96
128
  @metadatafields[field.name] = field
97
129
  end
130
+ # load calculated fields
131
+ # @calculatedFields = if 'Parameters'.eql? @name
132
+ # @node.xpath("./column/calculation")
133
+ # else
134
+ # @node.xpath(".//column/calculation")
135
+ # end
136
+ end
137
+
138
+ def fieldUIName fieldName
139
+ loadFieldUINames if @fieldUINames.nil?
140
+ @fieldUINames[fieldName]
141
+ end
142
+
143
+ def fieldUINames
144
+ loadFieldUINames if @fieldUINames.nil?
145
+ return @fieldUINames
146
+ end
147
+
148
+ def loadFieldUINames
149
+ @fieldUINames = {}
150
+ columnNodes = @node.xpath('./column')
151
+ columnNodes.each do |cn|
152
+ name = cn.attribute('name').text.gsub(/^\[|\]$/,'')
153
+ caption = cn.attribute('caption')
154
+ uiName = caption.nil? ? name : caption.text
155
+ @fieldUINames[name] = uiName
156
+ @fieldUINames[caption.text] = caption.text unless caption.nil? # in case of unintended usage: lookup by Caption name
157
+ end
158
+ mappedFields.each do |fname,fmap|
159
+ dbTable = fmap['table']
160
+ dbName = fmap['dbName']
161
+ @fieldUINames[dbName] = fname unless @fieldUINames.include?(dbName)
162
+ @fieldUINames[fname] = fname unless @fieldUINames.include?(fname)
163
+ end
164
+ end
165
+
166
+ def mappedFields
167
+ return @mappedFields unless @mappedFields.nil?
168
+ loadTableFields
169
+ return @mappedFields
170
+ end
171
+
172
+ def fieldTable fieldName
173
+ loadTableFields if @mappedFields.nil?
174
+ tf = @mappedFields[fieldName]
175
+ return tf.nil? ? nil : tf['table']
176
+ end
177
+
178
+ # fields are unique in the data source by UI name
179
+ def loadTableFields
180
+ # puts "DATA SOURCE FIELD TABLE LOAD"
181
+ @mappedFields = {}
182
+ fieldNodes = @node.xpath('./connection/cols/map')
183
+ fieldNodes.each do |fn|
184
+ uiName = fn.attribute('key').text.gsub(/^\[|\]$/,'')
185
+ fldRef = fn.attribute('value').text.gsub(/^\[|\]$/,'')
186
+ parts = fldRef.split('].[')
187
+ table = parts[0]
188
+ dbName = parts[1]
189
+ @mappedFields[uiName] = {'table' => table, 'dbName' => dbName}
190
+ # puts "=== #{uiName} :: T=#{@mappedFields[uiName]['table']} DBN=#{@mappedFields[uiName]['dbName']} "
191
+ end
192
+ relTableNodes = @node.xpath('.//relation[@table]')
193
+ relTableNodes.each do |relNode|
194
+ table = relNode.attribute('name').text
195
+ cols = relNode.xpath('./columns/column')
196
+ cols.each do |col|
197
+ fldName = col.attribute('name')
198
+ @mappedFields[fldName.text] = {'table' => table, 'dbName' => @uiname} unless fldName.nil?
199
+ end
200
+ end
98
201
  end
99
202
 
203
+ =begin
204
+ <filter class='categorical' column='[enforcement_type]' filter-group='2'>
205
+ <groupfilter function='member' level='[enforcement_type]' member='&quot;towing&quot;' user:ui-domain='database' user:ui-enumeration='inclusive' user:ui-marker='enumerate' />
206
+ </filter>
207
+ =end
208
+ def processFilters
209
+ if @filters.nil?
210
+ @filters = {}
211
+ fnodes = @node.xpath('./filter')
212
+ fnodes.each do |fn|
213
+ columnN = fn.attribute('column')
214
+ unless columnN.nil?
215
+ memberNodes = fn.xpath('.//groupfilter/@member')
216
+ unless memberNodes.nil?
217
+ members = []
218
+ memberNodes.each do |m|
219
+ members.push m.text
220
+ end
221
+ @filters[columnN.text] = members
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
100
227
  end
228
+
229
+ # Generates and returns a set of graph node-edge-node triplets.
230
+ # The intention is to create a graph that can be standalone used as a subgraph by the function's caller.
231
+ # The initial implementation only considers a linear list of node names as input, resulting in
232
+ # the creation of a single-path structure, e.g.
233
+ # [inNode] -> node1 -> node2 -> ... -> nodeN
234
+ # this may manifest as a tree when there are siblings at any level, e.g.
235
+ # [inNode] -> node1 -> node11 -> ... -> nodeX
236
+ # -> node22 -> ... -> nodeY
237
+ # -> node2 -> node21 -> ... -> nodeZ
238
+ # Params:
239
+ # +inNodes+:: the node(s) to which this function's generated graph is to be linked, may be nil:
240
+ # + , or multiple)
241
+ # +nodesList+:: list of node types by name to be built and linked together, in the order named
242
+ def graphNodes nodesList
243
+ graph = Twb::Util::Graphedges.new()
244
+ nodesList.each do |nName|
245
+ end
246
+ case @dsclass
247
+ when 'federated'
248
+ graph = processFederatedSource nodesList
249
+ end
250
+ return graph
251
+ end
252
+
253
+ def processFederatedSource nodesList
254
+ emit false, " (federated) #{dataSource.uiname}"
255
+ dsGNode = Twb::Util::Graphnode.new(name: @uiname, id: @name, type: 'Data Connection')
256
+ graph = Twb::Util::Graphedges.new(dsGNode)
257
+ edges = []
258
+ dsNode = dataSource.node
259
+ connections = dsNode.xpath('./connection/named-connections/named-connection/connection')
260
+ connections.each do |conn|
261
+ connClass = conn.attribute('class').text
262
+ emit true, "CONN CLASS: #{connClass}"
263
+ # -- Generating Source Node
264
+ cgParams = @@cgNodeParams[connClass]
265
+ # emit true, "cgparams : #{cgParams}"
266
+ cLabel = buildConnGraphPart( conn, cgParams['label'])
267
+ cID = buildConnGraphPart( conn, cgParams['id'])
268
+ cType = cgParams['type']
269
+ # emit true, " label : #{cLabel}"
270
+ # emit true, " id : #{cID}"
271
+ # emit true, " type : #{cType}"
272
+ srcNode = Twb::Util::Graphnode.new(name: cLabel, id: cID, type: 'Data Source')
273
+ graphEdge = Twb::Util::Graphedge.new(from: dsGNode, to: srcNode, relationship: 'is located at')
274
+ graph.edges << graphEdge
275
+ end
276
+ return graph
277
+ end
278
+
279
+ def buildConnGraphPart connNode, attributes
280
+ return connNode.attribute(attributes) if attributes.is_a? String
281
+ emit false, "ATTRIBUTES :: #{attributes}"
282
+ str = ''
283
+ attributes.each do |attName|
284
+ attrib = connNode.attribute(attName)
285
+ emit false, " -#{attName}\t-> #{attrib}"
286
+ str += attrib.text unless attrib.nil?
287
+ end
288
+ return str
289
+ end
290
+
101
291
 
102
292
  end
@@ -19,38 +19,74 @@ module Twb
19
19
 
20
20
  class FieldCalculation
21
21
 
22
- attr_reader :node, :formula, :fields, :comments, :class
22
+ attr_reader :node, :fieldNode
23
+ attr_reader :formula, :formulaLines, :formulaFlat
24
+ attr_reader :uiname, :techname, :caption
25
+ attr_reader :class, :scopeIsolation
26
+ attr_reader :fields, :resolvedFields
27
+ attr_reader :comments
23
28
 
24
29
  def initialize calcNode
25
- #puts "CALCNODE:: '#{calcNode}' -> #{calcNode.class}"
26
30
  if calcNode
27
- @node = calcNode
31
+ @node = calcNode
32
+ @fieldNode = @node.xpath('..')
33
+ # field names
34
+ @caption, = attribText(@fieldNode, 'caption')
35
+ @techname = @fieldNode.attribute('name').text.gsub(/^\[|\]$/,'') # assumes the name attribute exists
36
+ @uiname = @caption.nil? ? @techName : @caption
28
37
  #-- Calculation --
29
- formulaCode = calcNode.xpath('./@formula').text.gsub(/\r\n/, ' ')
30
- formulaLines = calcNode.xpath('./@formula').text.split(/\r\n/)
31
- @formula = getFormula( formulaLines )
32
- @comments = getComments( formulaLines )
33
- @class = calcNode.xpath('./@class').text
34
- @scopeIsolation = calcNode.xpath('./@scope-isolation').text
38
+ @has_formula = @node.has_attribute?('formula')
39
+ if @has_formula
40
+ @formula = @node.attribute('formula').text
41
+ @formulaLines = formula.split(/\n|\r\n/)
42
+ @formulaFlat = flattenFormula(formulaLines)
43
+ @comments = getComments(formulaLines)
44
+ end
45
+ @class = attribText(@node, 'class')
46
+ @scopeIsolation = attribText(@node, 'scope-isolation')
35
47
  #-- Fields --
36
- @fields = parseFieldsFromFormula(formulaCode)
48
+ parseFormFields # establishes @ fields
49
+ resolveFields # establishes @ resolvedFields
37
50
  end
38
51
  end
52
+
53
+ def attribText(node, attribute)
54
+ node.attribute(attribute).nil? ? nil : node.attribute(attribute).text
55
+ end
56
+
39
57
 
40
- def parseFieldsFromFormula formula
41
- fieldSet = Set.new []
42
- if formula =~ /\[.+\]/ then
43
- stripFrt = formula.gsub( /^[^\[]*[\[]/ , '[' )
58
+ def parseFormFields
59
+ @fields = Set.new []
60
+ formula = formulaFlat
61
+ if formula.include?('[') && formula.include?(']')
62
+ noSqLits = formula.gsub(/'[\[\.\]]+'/, ' ')
63
+ flatForm = noSqLits.gsub( /\n/, ' ')
64
+ stripFrt = flatForm.gsub( /^[^\[]*[\[]/ , '[' )
44
65
  stripBck = stripFrt.gsub( /\][^\]]+$/ , ']' )
45
66
  stripMid = stripBck.gsub( /\][^\]]{2,}\[/ , ']]..[[' )
46
67
  stripCom = stripMid.gsub( /\][ ]*,[ ]*\[/ , ']]..[[' )
47
- fields = stripCom.split(']..[')
48
- fields.each { |field| fieldSet.add field}
68
+ stripFns = stripMid.gsub( /\][ ]*[\*\/+\-,][ ]*\[/ , ']]..[[' )
69
+ fields = stripFns.split(']..[')
70
+ fields.each { |field| @fields.add field.gsub(/^\[|\]$/, '')}
71
+ end
72
+ end
73
+
74
+
75
+ def resolveFields
76
+ @resolvedFields = []
77
+ fields.each do |field|
78
+ rawField = field.gsub(/^\[|\]$/,'')
79
+ parts = rawField.split('].[')
80
+ if parts.length > 1
81
+ hash = { :field => parts[1], :source => parts[0] }
82
+ else
83
+ hash = { :field => parts[0], :source => nil }
84
+ end
85
+ @resolvedFields << hash unless hash.nil?
49
86
  end
50
- return fieldSet
51
87
  end
52
88
 
53
- def getFormula lines
89
+ def flattenFormula lines
54
90
  formula = ''
55
91
  lines.each do |line|
56
92
  line.strip