twb 0.5.3 → 1.0

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.
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