twb 3.9.7 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -42,8 +42,11 @@ module Twb
42
42
 
43
43
  attr_reader :workbook
44
44
  attr_reader :name, :caption, :uiname
45
+ attr_reader :attributes
45
46
  attr_reader :dsclass, :isExtract
46
- attr_reader :connection, :connections, :connHash, :uuid
47
+ attr_reader :connection, :connections, :connHash, :connAttributes
48
+ attr_reader :isPublished
49
+ attr_reader :uuid
47
50
  attr_reader :tables, :joinPairs
48
51
  attr_reader :localFields, :localFieldNames, :localField, :hasField
49
52
  attr_reader :columnFields
@@ -73,6 +76,24 @@ module Twb
73
76
  return self
74
77
  end
75
78
 
79
+ def attributes
80
+ @attributes ||= @attributes = loadAttributes(@node)
81
+ end
82
+
83
+ def connAttributes
84
+ @connAttributes ||= @connAttributes = loadAttributes(@connection)
85
+ end
86
+
87
+ def loadAttributes node
88
+ attributes = {}
89
+ unless node.nil?
90
+ node.attributes.each do |k,v|
91
+ attributes[k] = v.text
92
+ end
93
+ end
94
+ return attributes
95
+ end
96
+
76
97
  def id
77
98
  @id ||= @id = @name
78
99
  end
@@ -145,6 +166,14 @@ module Twb
145
166
  @isExtract ||= @isExtract = !@node.at_xpath('./extract').nil?
146
167
  end
147
168
 
169
+ def isPublished
170
+ @isPublished ||= loadIsPublished
171
+ end
172
+
173
+ def loadIsPublished
174
+ @isPublished = !node.at_xpath('./repository-location').nil?
175
+ end
176
+
148
177
  def loadTables connection
149
178
  @tables = {}
150
179
  nodes = connection.xpath(".//relation[@type='table']")
@@ -269,8 +298,10 @@ module Twb
269
298
  end
270
299
 
271
300
  def fieldAlias fieldName, value
272
- return value unless fieldHasAliases(fieldName)
273
- fldAlias = aliases[fieldName][value]
301
+ emit "fieldAlias: #{fieldName.class} -> #{value.class}"
302
+ loadAliases if @aliases.nil?
303
+ return value if @aliases.nil? || @aliases[fieldName].nil? # unless fieldHasAliases(fieldName)
304
+ fldAlias = @aliases[fieldName][value]
274
305
  fldAlias.nil? ? value : fldAlias
275
306
  end
276
307
 
@@ -336,7 +367,7 @@ module Twb
336
367
 
337
368
  def fieldUIName fieldName
338
369
  loadFieldUINames if @fieldUINames.nil?
339
- @fieldUINames[fieldName]
370
+ @fieldUINames[fieldName].nil? ? fieldName : @fieldUINames[fieldName]
340
371
  end
341
372
 
342
373
  def fieldUINames
@@ -38,7 +38,7 @@ module Twb
38
38
  attr_reader :fields, :remoteFields, :calcFields
39
39
  attr_reader :comments, :uuid
40
40
 
41
- attr_accessor :ttlogfile
41
+ # attr_accessor :ttlogfile
42
42
 
43
43
  @@tableCalcs = [ 'FIRST', 'INDEX', 'LAST', 'SIZE',
44
44
  'LOOKUP', 'PREVIOUS_VALUE',
@@ -53,16 +53,15 @@ module Twb
53
53
  'WINDOW_VAR', 'WINDOW_VARP'
54
54
  ]
55
55
 
56
- @ttlogfile =
57
-
58
56
  def initialize(calcField, datasource=nil)
59
57
  raise ArgumentError.new("FieldCalculation must be initialized with a CalculatedField, has been provided with a #{calcField.class}") if calcField.class != Twb::CalculatedField
58
+ init
60
59
  # initLogger
61
60
  @field = calcField
62
61
  calcNode = calcField.node
63
62
  @istableCalc = false
64
63
  # @localEmit = false
65
- # puts "FieldCalculation calcNode.nil? :: #{calcNode.nil?} "
64
+ emit "FieldCalculation calcNode.nil? :: #{calcNode.nil?} "
66
65
  unless calcNode.nil?
67
66
  @node = calcNode.at_xpath('./calculation')
68
67
  @fieldNode = calcField.node
@@ -94,7 +93,7 @@ module Twb
94
93
  @has_formula = @node.has_attribute?('formula')
95
94
  if @has_formula
96
95
  @formula = @node.attribute('formula').text.gsub(/\r\n/,"\n")
97
- # puts "\n-- init: #{@formula}"
96
+ emit "\n-- init: #{@formula}"
98
97
  @formulaUC = @formula.upcase
99
98
  @formulaLines = formula.split(/\n|\r\n/)
100
99
  @formulaFlat = flattenFormula(@formulaLines)
@@ -108,12 +107,6 @@ module Twb
108
107
  end
109
108
  end
110
109
 
111
- # def initLogger(logfile=nil)
112
- # logfilename = docFile(logfile.nil? ? @ttlogfile : logfile)
113
- # @logger = Logger.new(logfilename)
114
- # @logger.level = Logger::DEBUG
115
- # end
116
-
117
110
  def id
118
111
  @id ||= @id = @formulaFlat.hash + calcField.hash
119
112
  end
@@ -122,10 +115,6 @@ module Twb
122
115
  @uuid ||= @uuid = Digest::MD5.hexdigest(@formula)
123
116
  end
124
117
 
125
- # def assessTableCalc formula
126
- # @@tableCalcs.any? { |tc| string.include?(tc) }
127
- # end
128
-
129
118
  def attribText(node, attribute)
130
119
  node.attribute(attribute).nil? ? nil : node.attribute(attribute).text
131
120
  end
@@ -23,7 +23,7 @@ module Twb
23
23
  include TabTool
24
24
 
25
25
  attr_reader :field, :name, :uiname
26
- attr_reader :type
26
+ attr_reader :type, :kind
27
27
  attr_reader :dataSource
28
28
  attr_reader :node, :values
29
29
  attr_reader :inexclude, :inexMode, :includeNull
@@ -32,8 +32,9 @@ module Twb
32
32
  init
33
33
  emit "\nFILTER:\n#{node}\n====="
34
34
  @node = node
35
- filterClass = node['class']
35
+ filterClass = @node['class']
36
36
  @type = filterClass.gsub('-',' ').capitalize
37
+ @kind = @node['kind']
37
38
  fieldCode = node['column']
38
39
  codedField = Twb::CodedField.new(fieldCode)
39
40
  fieldTech = codedField.name
@@ -83,7 +84,7 @@ module Twb
83
84
  when 'quantitative' then resolveQuantitative
84
85
  when 'categorical' then resolveCategoricalValues
85
86
  end
86
- end
87
+ end # def initialize
87
88
 
88
89
  def to_s
89
90
  "%s => %s" % [uiname, values]
@@ -93,7 +94,7 @@ module Twb
93
94
 
94
95
  def recordValue value, valias=nil
95
96
  # puts "recordValue--: #{value} :: @'#{valias}'"
96
- valias = value if valias.nil?
97
+ # valias = value if valias.nil?
97
98
  @values << {:value => value, :alias => valias}
98
99
  # puts "recordValue--: done"
99
100
  end
@@ -194,9 +195,8 @@ module Twb
194
195
  # <filter class='categorical' column='[Sample - Superstore - English (Extract)].[none:Region:nk]'>
195
196
  # <groupfilter from='&quot;East&quot;' function='range' level='[none:Region:nk]' to='&quot;West&quot;' user:ui-domain='relevant' user:ui-enumeration='inclusive' user:ui-marker='enumerate' />
196
197
  # </filter>
197
- #--
198
198
  def resolveCategoricalValues
199
- emit "resolveCategoricalValues"
199
+ emit "########################## resolveCategoricalValues"
200
200
  emit "@measureNames: #{@measureNames}"
201
201
  if @node.element_children.empty?
202
202
  # <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]' />
@@ -337,8 +337,8 @@ module Twb
337
337
  recordValue range, range
338
338
  end
339
339
  t.each do |name,alia|
340
- # emit "%%%% range Name: %-20s ALIAS: %-s " % [name, alia]
341
- recordValue name, @dataSource.fieldAlias(@uiname,name)
340
+ # emit true, "++++ range Name: %-20s ALIAS: %-s " % [name, alia]
341
+ # recordValue name, @dataSource.fieldAlias(@uiname,name)
342
342
  end
343
343
  end
344
344
  end
@@ -346,6 +346,7 @@ module Twb
346
346
  end
347
347
 
348
348
  def filtersFromRangeNode node
349
+ emit "########################## filtersFromRangeNode"
349
350
  unless @twbDomainsLoaded
350
351
  loadDomains
351
352
  end
@@ -368,7 +369,7 @@ module Twb
368
369
  end
369
370
 
370
371
  def filtersInRange from, to
371
- emit "filtersInRange"
372
+ emit "########################## filtersInRange"
372
373
  # results = {}
373
374
  dsFields = @twbFielddomains[@dataSource.uiname]
374
375
  emit "dsFields : #{dsFields}"
@@ -16,12 +16,14 @@
16
16
  require 'logger'
17
17
  require 'nokogiri'
18
18
  require 'digest/md5'
19
+ require 'yaml'
19
20
 
20
21
  module TabTool
21
22
 
22
23
  @@licensed = false
23
24
 
24
- TTDOCDIR = './ttdoc'
25
+ @@TTDOCDIR = './ttdoc'
26
+ @configFile = './ttconfig.yml'
25
27
 
26
28
  attr_accessor :ttdocdir, :logger, :loglevel, :logfilename
27
29
  attr_accessor :uuid, :type, :id, :properties
@@ -42,9 +44,9 @@ module TabTool
42
44
  end
43
45
 
44
46
  def initDocDir
45
- # return if TTDOCDIR.nil?
46
- # return if ''.eql?($ttdocdir) && ''.eql?(TTDOCDIR)
47
- @docDir = TTDOCDIR
47
+ # return if @@TTDOCDIR.nil?
48
+ # return if ''.eql?($ttdocdir) && ''.eql?(@@TTDOCDIR)
49
+ @docDir = @@TTDOCDIR
48
50
  return if Dir.exists?(@docDir)
49
51
  if File.exists? @docDir
50
52
  @docDir = ''
@@ -55,9 +57,12 @@ module TabTool
55
57
  end
56
58
 
57
59
  def initLogger
58
- logFileName = docFile("#{self.class.to_s.split('::').last}.ttlog")
59
- @logger = Logger.new(logFileName, 2, 1000*1024)
60
- @logger.level = Logger::DEBUG
60
+ @logger = nil
61
+ if config(:log)
62
+ logFileName = docFile("#{self.class.to_s.split('::').last}.ttlog")
63
+ @logger = Logger.new(logFileName)
64
+ @logger.level = Logger::DEBUG
65
+ end
61
66
  return @logger
62
67
  end
63
68
 
@@ -77,6 +82,23 @@ module TabTool
77
82
  @funcdoc.nil? ? {:class=>'n/a', :blurb=>'generic TabTool blurb', :description=>'A useful Tableau Tool.'} : @funcdoc
78
83
  end
79
84
 
85
+ def hasConfig param
86
+ loadConfig if @configParams.nil?
87
+ @configParams[param] ? true : false
88
+ end
89
+
90
+ def config param
91
+ hasConfig(param) ? @configParams[param] : nil
92
+ end
93
+
94
+ def loadConfig
95
+ @configParams = if File.exist?('./ttconfig.yml')
96
+ YAML.load( File.open('./ttconfig.yml').read )
97
+ else
98
+ {}
99
+ end
100
+ end
101
+
80
102
  def docfiles
81
103
  @docfiles ||= @docfiles = []
82
104
  end
@@ -95,21 +117,28 @@ module TabTool
95
117
  return maxlen
96
118
  end
97
119
 
98
- def docfilesdoc
120
+ def docfilesdoc(pad=' ',desc='For documentation and generated data see the following:')
99
121
  lines = SortedSet.new
122
+ paddesc = "#{pad}#{desc}"
100
123
  unless @docfiles.nil? || @docfiles.empty?
101
124
  nameLen = docFileMaxNameLen
102
125
  docfiles.each do |dfi|
103
- lines << " - %-#{nameLen}s %-s " % [ dfi[:name], dfi[:description] ]
126
+ lines << "#{pad}- %-#{nameLen}s %-s " % [ dfi[:name], dfi[:description] ]
104
127
  end
105
128
  end
106
- docLines = lines.empty? ? [] : [' ',' For documentation and generated data see the following:',' ']
129
+ docLines = lines.empty? || paddesc =~ /^[ ]+/ ? [] : [' ',"#{pad}#{desc}"]
107
130
  lines.each do |l|
108
131
  docLines << l
109
132
  end
110
133
  return docLines
111
134
  end
112
135
 
136
+ def docfilesdocto_s(pad=' ',desc='For documentation and generated data see the following:')
137
+ str = ''
138
+ docfilesdoc(pad,desc).each { |l| str += "#{l}\n"}
139
+ return str
140
+ end
141
+
113
142
  # def metrics
114
143
  # {}
115
144
  # end
@@ -154,6 +183,5 @@ module TabTool
154
183
  # @logger.close unless @logger.nil? || @logger.closed?
155
184
  end
156
185
 
157
-
158
186
  end # module TabTool
159
187
 
@@ -1,4 +1,3 @@
1
-
2
1
  # Copyright (C) 2014, 2018 Chris Gerrard
3
2
  #
4
3
  # This program is free software: you can redistribute it and/or modify
@@ -25,11 +24,14 @@ module Twb
25
24
  #
26
25
  class Workbook < TabClass
27
26
 
28
- attr_reader :workbooknode
29
- attr_reader :name, :dir
27
+ attr_reader :workbooknode, :node
28
+ attr_reader :name, :dir, :type
30
29
  attr_reader :modtime, :version, :build
30
+ #--
31
31
  attr_reader :datasources, :datasource
32
32
  attr_reader :datasourceNames, :datasourceUINames, :dataSourceNamesMap
33
+ attr_reader :orphanDataSources # i.e. not referenced in any Worksheet
34
+ #--
33
35
  attr_reader :dashboards, :storyboards, :worksheets
34
36
  attr_reader :parameters, :actions
35
37
  attr_reader :valid, :ndoc
@@ -66,18 +68,21 @@ module Twb
66
68
  Zip::File.open(twbxWithDir) do |zip_file|
67
69
  twb = zip_file.glob('*.twb').first
68
70
  @ndoc = Nokogiri::XML(twb.get_input_stream)
71
+ @type = :twbx
69
72
  processDoc
70
73
  end
71
74
  end
72
75
 
73
76
  def processTWB(twbFile)
74
- @ndoc = Nokogiri::XML(open(twbFile))
77
+ @ndoc = Nokogiri::XML(open(twbFile))
78
+ @type = :twb
75
79
  processDoc
76
80
  end
77
81
 
78
82
  def processDoc
79
- @workbooknode = @ndoc.at_xpath('//workbook')
80
- @version = @ndoc.xpath('/workbook/@version').first.text
83
+ @node = @ndoc.at_xpath('//workbook')
84
+ @workbooknode = @node
85
+ @version = @node.nil? ? nil : @node["version"]
81
86
  loaddatasources
82
87
  loadWorksheets
83
88
  loadDashboards
@@ -204,6 +209,20 @@ module Twb
204
209
  @dashboards[name]
205
210
  end
206
211
 
212
+ def orphanDataSources
213
+ @orphanDataSources ||= identifyOrphandatasoUrceS
214
+ end
215
+
216
+ def identifyOrphandatasoUrceS
217
+ sheetDataSources = Set.new
218
+ @worksheets.values.each do |sheet|
219
+ sheet.datasources.each do |ds|
220
+ sheetDataSources << ds.uiname
221
+ end
222
+ end
223
+ @orphanDataSources = @datasourceUINames - sheetDataSources
224
+ end
225
+
207
226
  def storyboards
208
227
  @storyboards.values
209
228
  end
@@ -283,12 +302,15 @@ module Twb
283
302
  # Write the TWB to a file, with an optional name.
284
303
  # Can be used to write over the existing TWB (dangerous), or to a new file (preferred).
285
304
  def write(name=@name)
286
- $f = File.open(name,'w')
287
- if $f
288
- $f.puts @ndoc
289
- $f.close
305
+ case @type
306
+ when :twb
307
+ writeTwb(name)
308
+ when :twbx
309
+ writeTwbx(name)
310
+ else
311
+ emit "Cannot write this Workbook - it has an invalid type: #{@type}"
312
+ raise "Cannot write this Workbook - it has an invalid type: #{@type}"
290
313
  end
291
- return name
292
314
  end
293
315
 
294
316
  # Write the TWB to a file, appending the base name with the provided string.
@@ -298,7 +320,22 @@ module Twb
298
320
  write newName
299
321
  end
300
322
 
301
- private
323
+ private
324
+
325
+ def writeTwb(name=@name)
326
+ $f = File.open(name,'w')
327
+ if $f
328
+ $f.puts @ndoc
329
+ $f.close
330
+ end
331
+ return name
332
+ end
333
+
334
+ def writeTwbx(name=@name)
335
+ emit "Writing the Workbook, need implementation"
336
+ end
337
+
338
+
302
339
 
303
340
  def loadParameters
304
341
  @parameters = {}
@@ -24,16 +24,20 @@ module Twb
24
24
 
25
25
  @@hasher = Digest::SHA256.new
26
26
 
27
+ @fieldEncodingsXPath = './table/panes/pane//encodings'
28
+
27
29
  attr_reader :node, :name, :datasourcenames, :datasources
28
30
  attr_reader :panesCount
29
- attr_reader :fields, :rowFields, :colFields, :paneFields, :datasourceFields
31
+ attr_reader :fields, :rowFields, :colFields, :paneFields, :datasourceFields, :pageFields, :encodedFields
30
32
  attr_reader :filters
33
+ attr_reader :tooltip
31
34
  attr_reader :hidden, :visible
32
35
 
33
36
  def initialize sheetNode, twb
34
37
  @twb = twb
35
38
  @node = sheetNode
36
- @name = @node.attr('name')
39
+ @name = @node['name']
40
+ emit "########################## Worksheet initialize name: #{@name}"
37
41
  loadDataSourceNames
38
42
  loadFields
39
43
  return self
@@ -77,6 +81,10 @@ module Twb
77
81
  @filters ||= loadFilters
78
82
  end
79
83
 
84
+ def tooltip
85
+ @tooltip ||= loadTooltip
86
+ end
87
+
80
88
  def hidden
81
89
  @hidden ||= resolveHidden
82
90
  end
@@ -105,8 +113,39 @@ module Twb
105
113
  @fields[:rows] = @rowFields
106
114
  @colFields ||= loadRowColFields(:cols) # returns map of data source => (Set of) field names
107
115
  @fields[:cols] = @colFields
116
+ loadFieldEncodings
117
+ end
118
+
119
+ def loadFieldEncodings
120
+ @encodedFields = Hash.new { |h,k| h[k] = [] }
121
+ enodes = node.xpath('.//table/panes/pane/encodings')
122
+ enodes.each do |enode|
123
+ enode.children.each do |child|
124
+ unless child['column'].nil?
125
+ @encodedFields[child.name] << CodedField.new(child['column'])
126
+ end
127
+ end
128
+ end
129
+ @encodedFields.each do |type, fields|
130
+ @fields[type] = fields
131
+ end
108
132
  end
109
133
 
134
+ # def loadFieldEncodings node
135
+ # $encodedFields = Hash.new { |h,k| h[k] = [] }
136
+ # enodes = node.xpath('.//table/panes/pane/encodings')
137
+ # enodes.each do |enode|
138
+ # enode.children.each do |child|
139
+ # unless child['column'].nil?
140
+ # $encodedFields[child.name] << CodedField.new(child['column'])
141
+ # end
142
+ # end
143
+ # end
144
+ # $encodedFields.each do |type, fields|
145
+ # $fields[type] = fields
146
+ # end
147
+ # end
148
+
110
149
  def addDSFields fields, usage
111
150
  fields.each do
112
151
  end
@@ -119,12 +158,16 @@ module Twb
119
158
  def loadRowColFields(type)
120
159
  fields = []
121
160
  xpath = case type
122
- when :rows then './/rows'
123
- when :cols then './/cols'
161
+ when :rows then './table/rows'
162
+ when :cols then './table/cols'
124
163
  end
125
164
  return fields if xpath.nil?
126
165
  node = @node.at_xpath(xpath)
127
- RowsColsSplitter.new(node).fields.each do |fcode|
166
+ emit "def loadRowColFields:: type: #{type} \t node: #{node.class} \t path: #{node.nil? ? '<<nil>>' : node.path }"
167
+ emit "node : #{node.inspect}"
168
+ nodeFields = RowsColsSplitter.new(node).fields
169
+ emit "nodeFields: #{nodeFields}"
170
+ nodeFields.each do |fcode|
128
171
  fields << CodedField.new(fcode)
129
172
  end
130
173
  return fields
@@ -139,18 +182,32 @@ module Twb
139
182
  panes = @node.xpath('.//pane')
140
183
  end
141
184
 
185
+ def pageFields
186
+ @pageFields ||= loadpageFields
187
+ end
188
+
189
+ def loadpageFields
190
+ @pageFields = [] # { |h,k| h[k] = [] }
191
+ nodes = node.xpath('.//table/pages/column')
192
+ nodes.each do |node|
193
+ @pageFields << CodedField.new(node.text)
194
+ end
195
+ return @pageFields
196
+ end
197
+
142
198
  def datasourcenames
143
199
  @datasources.keys
144
200
  end
145
201
 
146
202
  def resolveHidden
147
- windowNode = node.at_xpath("//windows/window[@name='#{@name}']")
203
+ windowNode = node.at_xpath("//windows/window[@name=\"#{@name}\"]")
148
204
  @hidden = !windowNode.nil? && 'true' == windowNode['hidden']
149
205
  end
150
206
 
151
207
  private
152
208
 
153
209
  def loadFilters
210
+ emit ""########################## loadFilters"
154
211
  @filters = []
155
212
  filterNodes = @node.xpath('./table/view//filter[@column]')
156
213
  filterNodes.each do |fnode|
@@ -159,6 +216,10 @@ module Twb
159
216
  end
160
217
  return @filters
161
218
  end
219
+
220
+ def loadTooltip
221
+ @tooltip = @node.xpath('./table/panes/pane/customized-tooltip')
222
+ end
162
223
 
163
224
  end # class Worksheet
164
225
 
@@ -201,7 +262,9 @@ module Twb
201
262
  return fields if node.nil?
202
263
  unless node.text.nil?
203
264
  codes = node.text.split(/[\])] [\/+*] [(]?\[/)
204
- codes.each {|c| @fields << c.gsub(/^[(]*[\[]*|[)\]]*$/,'')}
265
+ codes.each do |c|
266
+ @fields << c.gsub(/^[(]*[\[]*|[)\]]*$/,'')
267
+ end
205
268
  end
206
269
  end
207
270
  end # class RowsColsSplitter