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 +7 -0
- data/lib/twb/action.rb +121 -0
- data/lib/twb/countNodes.rb +98 -0
- data/lib/twb/dashboard.rb +5 -1
- data/lib/twb/datasource.rb +196 -6
- data/lib/twb/fieldcalculation.rb +54 -18
- data/lib/twb/findDSFields.rb +153 -0
- data/lib/twb/graph.rb +53 -0
- data/lib/twb/graphedges.rb +36 -0
- data/lib/twb/graphnode.rb +53 -0
- data/lib/twb/identifyFields.rb +103 -0
- data/lib/twb/localfield.rb +7 -6
- data/lib/twb/util/graphedge.rb +69 -0
- data/lib/twb/util/graphedges.rb +38 -0
- data/lib/twb/util/graphnode.rb +94 -0
- data/lib/twb/workbook.rb +60 -40
- data/lib/twb/worksheet.rb +69 -1
- data/lib/twb.rb +6 -1
- metadata +25 -17
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
|
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
|
data/lib/twb/datasource.rb
CHANGED
@@ -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
|
-
|
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
|
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.
|
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='"towing"' 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
|
data/lib/twb/fieldcalculation.rb
CHANGED
@@ -19,38 +19,74 @@ module Twb
|
|
19
19
|
|
20
20
|
class FieldCalculation
|
21
21
|
|
22
|
-
attr_reader :node,
|
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
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
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
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
48
|
-
fields
|
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
|
89
|
+
def flattenFormula lines
|
54
90
|
formula = ''
|
55
91
|
lines.each do |line|
|
56
92
|
line.strip
|