twb 3.9.3 → 3.9.7

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.
@@ -0,0 +1,81 @@
1
+ # WorksheetFields.rb Copyright (C) 2017, 2018 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 'twb'
17
+ require 'csv'
18
+
19
+ module Twb
20
+ module Analysis
21
+
22
+ class SheetFiltersAnalyzerA
23
+
24
+ include TabTool
25
+
26
+ attr_accessor :localEmit
27
+
28
+ def initialize
29
+ init
30
+ @funcdoc = {:class=>self.class, :blurb=>'Analyze Worksheet filters.', :description=>'Documents Quick Filters and the values they employ, if any. Work in progess.',}
31
+ #--
32
+ docFileName = docFile('TWBWorksheetFiltersA.csv')
33
+ $sheetFieldsCSV = CSV.open( docFileName ,'w')
34
+ $sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Operation','Data Source','Field','Value','Alias', 'Alias?']
35
+ addDocFile docFileName, "Workbooks, Worksheets and the sheets' Quick Filters"
36
+ #--
37
+ @sheetCount = 0
38
+ @filterCount = 0
39
+ end
40
+
41
+ def metrics
42
+ {
43
+ '# of Worksheets' => @sheetCount,
44
+ '# of Worksheet Filters' => @filterCount
45
+ }
46
+ end
47
+
48
+ def processTWB twb
49
+ @twb = twb
50
+ @twbName = @twb.name
51
+ emit " -- #{@twbName}"
52
+ @twbDomainsLoaded = false
53
+ parseFilters
54
+ end
55
+
56
+ def parseFilters
57
+ @worksheets = @twb.worksheets
58
+ @worksheets.each do |sheet|
59
+ emit "\n\nSHEET: #{sheet.name}"
60
+ filters = sheet.filters
61
+ filters.each do |filter|
62
+ filter.emit "-----------------------------\nWORKSHEET:: #{sheet.name}\n-----------------------------"
63
+ if filter.values.empty?
64
+ # $sheetFieldsCSV << ['Workbook','Worksheet','Filter Type','Operation' ,'Data Source','Field','Value','Alias', 'Alias?']
65
+ $sheetFieldsCSV << [@twbName ,sheet.name, filter.type ,filter.inexclude, filter.dataSource.uiname, filter.uiname,nil,nil,nil]
66
+ end
67
+ filter.values.each do |valueMap|
68
+ value = valueMap[:value]
69
+ valias = valueMap[:alias]
70
+ same = value.eql? valias
71
+ # puts "RECORDING FILTER VALUES: %-25s -- %-25s same? %s " % [value,valias,same]
72
+ $sheetFieldsCSV << [@twbName ,sheet.name, filter.type ,filter.inexclude, filter.dataSource.uiname, filter.uiname, value, valias, same]
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ end # class SheetFiltersAnalyzerA
79
+
80
+ end # module Twb
81
+ end # module Analysis
@@ -18,7 +18,9 @@ require 'digest/md5'
18
18
 
19
19
  module Twb
20
20
 
21
- class CalculatedField < TabClass
21
+ class CalculatedField
22
+
23
+ include TabTool
22
24
 
23
25
  attr_reader :dataSource
24
26
  attr_reader :node, :properties
@@ -13,13 +13,15 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
- require 'nokogiri'
17
- require 'digest/md5'
16
+ # require 'nokogiri'
17
+ # require 'digest/md5'
18
18
  require 'json'
19
19
 
20
20
  module Twb
21
21
 
22
- class DataSource < TabClass
22
+ class DataSource
23
+
24
+ include TabTool
23
25
 
24
26
  @@hasher = Digest::SHA256.new
25
27
 
@@ -53,6 +55,7 @@ module Twb
53
55
  attr_reader :aliases
54
56
  attr_reader :calculatedFields, :calculatedFieldNamesMap, :calculatedFieldNames, :calculatedField
55
57
  attr_reader :allFields
58
+ attr_reader :groups
56
59
  attr_reader :filters
57
60
  attr_reader :node
58
61
 
@@ -222,6 +225,7 @@ module Twb
222
225
  def loadAliases
223
226
  @aliases = {}
224
227
  # puts $node.xpath('.//column/aliases/..').length
228
+ cnt = 0
225
229
  @node.xpath('./column//aliases/..').each do |anode|
226
230
  # puts "anode:: #{anode}"
227
231
  # puts " path:: #{anode.path}"
@@ -252,6 +256,7 @@ module Twb
252
256
  end
253
257
  @aliases[name] = aliasMap
254
258
  end
259
+ emit "FIELD ALIASES: @aliases"
255
260
  return @aliases
256
261
  end
257
262
 
@@ -366,6 +371,10 @@ module Twb
366
371
  @fieldUINames[fld.name] = fld.uiname
367
372
  @fieldUINames[fld.uiname] = fld.uiname
368
373
  end
374
+ groups.each do |fld|
375
+ @fieldUINames[fld.name] = fld.uiname
376
+ @fieldUINames[fld.uiname] = fld.uiname
377
+ end
369
378
  return @fieldUINames
370
379
  end
371
380
 
@@ -463,6 +472,20 @@ module Twb
463
472
  end
464
473
  end
465
474
 
475
+ def groups
476
+ @groups ||= loadGroups
477
+ end
478
+
479
+ def loadGroups
480
+ @groups = []
481
+ groupNodes = @node.xpath('.//group')
482
+ groupNodes.each do |groupNode|
483
+ groupField = Twb::GroupField.new(groupNode)
484
+ @groups << groupField
485
+ end
486
+ return @groups
487
+ end
488
+
466
489
  def processFilters
467
490
  if @filters.nil?
468
491
  @filters = {}
@@ -0,0 +1,55 @@
1
+ # Copyright (C) 2018 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
+
18
+ module Twb
19
+ # module Data
20
+
21
+ class GroupField
22
+
23
+ include TabTool
24
+
25
+ attr_reader :node
26
+ attr_reader :name, :uiname, :caption
27
+
28
+ def initialize node
29
+ @node = node
30
+ @caption = @node['caption']
31
+ @name = @node['name'].gsub(/^\[|\]$/,'')
32
+ @uiname = @caption.nil? ? @name : @caption
33
+ end
34
+
35
+ def id
36
+ @id ||= @id = @name.hash
37
+ end
38
+
39
+ def properties
40
+ @properties ||= loadProperties
41
+ end
42
+
43
+ def loadProperties
44
+ @properties= {}
45
+ @node.attributes.each do |name,attr|
46
+ @properties[name] = attr.value
47
+ end
48
+ @properties[:uiname] = @name
49
+ return @properties
50
+ end
51
+
52
+ end # class GroupField
53
+
54
+ # end # module Data
55
+ end # module Twb
@@ -0,0 +1,119 @@
1
+ # Copyright (C) 2018 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 Parameter
22
+
23
+ include TabTool
24
+
25
+ attr_reader :caption, :name, :uiname
26
+ attr_reader :dataType, :format, :dataTypeCustom
27
+ attr_reader :domainType, :role, :type
28
+ attr_reader :currentValue, :values
29
+ attr_reader :node
30
+
31
+ def initialize(paramNode)
32
+ @node = paramNode
33
+ # --
34
+ @name = @node['name'].gsub(/^\[|\]$/,'')
35
+ @caption = @node['caption']
36
+ @uiname = caption.nil? ? nameTech : caption
37
+ @dataType = @node['datatype']
38
+ @format = @node['default-format'].nil? ? '<<default>>' : @node['default-format']
39
+ @dataTypeCustom = @node['datatype-customized'].nil? ? false : true
40
+ @domainType = @node['param-domain-type']
41
+ @role = @node['role']
42
+ @type = @node['type']
43
+ @currentValue = paramNode.at_xpath('./calculation[@class="tableau"]')['formula'].gsub(/^[#"]|[#"]$/,'')
44
+ @values = []
45
+ processNode
46
+ end
47
+
48
+ def to_s
49
+ "%s => %s" % [uiname, values]
50
+ end
51
+
52
+ private
53
+
54
+ def processNode
55
+ processMembers
56
+ processRange
57
+ processAll
58
+ end
59
+
60
+ def processMembers
61
+ #-- for when there are members, i.e. Lists
62
+ # <column caption='Date - List' datatype='date' datatype-customized='true' name='[Date - All 9/5/2018 Std Short Date (copy)]' param-domain-type='list' role='measure' type='quantitative' value='#2002-01-02#'>
63
+ # <calculation class='tableau' formula='#2002-01-02#' />
64
+ # <members>
65
+ # <member value='#2002-01-02#' />
66
+ # <member value='#2006-12-05#' />
67
+ # <member value='#1999-11-01#' />
68
+ # </members>
69
+ members = @node.xpath('.//member')
70
+ @hasMembers = !members.empty?
71
+ emit "# members: #{members.length}"
72
+ members.each do |m|
73
+ mvalue = m['value'].gsub(/^[#"]|[#"]$/,'')
74
+ malias = m['alias']
75
+ mvalui = malias.nil? ? mvalue : malias
76
+ @values << {:valueTech => mvalue, :alias => malias, :value => mvalui}
77
+
78
+ end
79
+ end
80
+
81
+ def processRange
82
+ # <column caption='Date - Range CV 3/3/2003 : ...12/5/2006' datatype='date' datatype-customized='true' name='[Date - Range CV 8/29/2001 3/23/2000...12/5/2006 (copy)]' param-domain-type='range' role='measure' type='quantitative' value='#2003-03-03#'>
83
+ # <calculation class='tableau' formula='#2003-03-03#' />
84
+ # <range max='#2006-12-05#' />
85
+ #--
86
+ # <column caption='Integer List Range -73...2,109 SS1' datatype='integer' datatype-customized='true' name='[Integer List Primes (copy)]' param-domain-type='range' role='measure' type='quantitative' value='1'>
87
+ # <calculation class='tableau' formula='1' />
88
+ # <range max='2109' min='1' />
89
+ # </column>
90
+ #--
91
+ # <column caption='Date - Range CV 8/29/2001 3/23/2000...' datatype='date' datatype-customized='true' name='[Date - Range CV 8/29/2001 3/23/2000...12/5/2006 (copy 2)]' param-domain-type='range' role='measure' type='quantitative' value='#2001-09-29#'>
92
+ # <calculation class='tableau' formula='#2001-09-29#' />
93
+ # <range min='#2000-03-23#' />
94
+ # </column>
95
+ #--
96
+ # <column caption='Integer List Range -73...2,109 SS3' datatype='integer' datatype-customized='true' name='[Integer List Range -73...2,109 SS1 (copy)]' param-domain-type='range' role='measure' type='quantitative' value='1'>
97
+ # <calculation class='tableau' formula='1' />
98
+ # <range granularity='3' max='2109' min='1' />
99
+ # </column>
100
+ if 'range' == domainType
101
+ @hasRange = true
102
+ rangeNode = @node.at_xpath('./range')
103
+ min = rangeNode['min'].nil? ? nil : rangeNode['min'].gsub(/^[#"]|[#"]$/,'')
104
+ max = rangeNode['max'].nil? ? nil : rangeNode['max'].gsub(/^[#"]|[#"]$/,'')
105
+ range = "#{min}...#{max}"
106
+ @values << {:value => range}
107
+ end
108
+ end
109
+
110
+ def processAll
111
+ unless @hasMembers || @hasRange
112
+ @values << {:value => '<<All>>'}
113
+ end
114
+ end
115
+
116
+ end # class Parameter
117
+
118
+ end # module Twb
119
+
@@ -0,0 +1,442 @@
1
+ # Copyright (C) 2018 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 QuickFilter
22
+
23
+ include TabTool
24
+
25
+ attr_reader :field, :name, :uiname
26
+ attr_reader :type
27
+ attr_reader :dataSource
28
+ attr_reader :node, :values
29
+ attr_reader :inexclude, :inexMode, :includeNull
30
+
31
+ def initialize(node,twb)
32
+ init
33
+ emit "\nFILTER:\n#{node}\n====="
34
+ @node = node
35
+ filterClass = node['class']
36
+ @type = filterClass.gsub('-',' ').capitalize
37
+ fieldCode = node['column']
38
+ codedField = Twb::CodedField.new(fieldCode)
39
+ fieldTech = codedField.name
40
+ inexclusions = fieldTech =~ /^(Ex|In)clusions/
41
+ @measureNames = ':Measure Names' == fieldTech
42
+ srcTech = codedField.dataSource
43
+ @dataSource = twb.datasource(srcTech)
44
+ @dsAliases = @dataSource.aliases
45
+ field = @measureNames || inexclusions ? fieldTech : @dataSource.field(fieldTech)
46
+ uiname = @dataSource.fieldUIName(fieldTech)
47
+ @uiname = @measureNames || inexclusions ? fieldTech : uiname.nil? ? fieldTech : uiname
48
+ @field = @uiname
49
+ @includeNull = if @node['include-null'].nil?
50
+ true
51
+ elsif 'false' == @node['include-null']
52
+ false
53
+ else
54
+ true
55
+ end
56
+ @values = []
57
+ enode = @node.at_xpath('.//*[@user:ui-enumeration]')
58
+ @inexclude = if enode.nil?
59
+ 'Include'
60
+ else case enode['user:ui-enumeration']
61
+ when 'inclusive' then 'Include'
62
+ when 'exclusive' then 'Exclude'
63
+ when nil then 'Include'
64
+ when 'all' then 'Include'
65
+ else 'undefined'
66
+ end
67
+ end
68
+ @inexMode = enode.nil? ? 'Default' : 'Specified'
69
+ emit "\n:: FIELD :: #{field} == #{@uiname} -- #{fieldTech} -- #{codedField.rawCode}"
70
+ emit " filter class: #{filterClass}"
71
+ emit " field code: #{fieldCode}"
72
+ emit " coded field: #{codedField}"
73
+ emit " field tech: #{fieldTech}"
74
+ emit " field name: nil? #{@uiname.nil?} #{@uiname} "
75
+ emit " src tech: #{srcTech}"
76
+ emit " @measureNames: #{@measureNames}"
77
+ emit " ds: #{@dataSource.uiname}"
78
+ emit " field: #{@uiname}"
79
+ emit " @inexclude: #{@inexclude}"
80
+ emit " "
81
+ aaa = case filterClass
82
+ when 'relative-date' then resolveRelativeDate
83
+ when 'quantitative' then resolveQuantitative
84
+ when 'categorical' then resolveCategoricalValues
85
+ end
86
+ end
87
+
88
+ def to_s
89
+ "%s => %s" % [uiname, values]
90
+ end
91
+
92
+ private
93
+
94
+ def recordValue value, valias=nil
95
+ # puts "recordValue--: #{value} :: @'#{valias}'"
96
+ valias = value if valias.nil?
97
+ @values << {:value => value, :alias => valias}
98
+ # puts "recordValue--: done"
99
+ end
100
+
101
+
102
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='-1' period-type='day' />
103
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='-1' period-type='year' />
104
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-1' include-future='true' include-null='false' last-period='0' period-type='year' />
105
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-2' include-future='true' include-null='false' last-period='0' period-type='day' />
106
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-2' include-future='true' include-null='false' last-period='0' period-type='year' />
107
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='-6' include-future='true' include-null='false' last-period='0' period-type='day' />
108
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='false' include-null='false' last-period='0' period-type='year' />
109
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='0' period-type='day' />
110
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='0' period-type='year' />
111
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='2' period-type='day' />
112
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='2' period-type='year' />
113
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='0' include-future='true' include-null='false' last-period='4' period-type='day' />
114
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='1' include-future='true' include-null='false' last-period='1' period-type='day' />
115
+ # filter class='relative-date' column='[Sample - Superstore].[none:Order Date:qk]' first-period='1' include-future='true' include-null='false' last-period='1' period-type='year' />
116
+ def resolveRelativeDate
117
+ emit "resolveRelativeDate"
118
+ periodType = @node['period-type']
119
+ inclFuture = @node['include-future'] == 'true'
120
+ firstPeriod = @node['first-period'].to_i
121
+ lastPeriod = @node['last-period'].to_i
122
+ sum = firstPeriod + lastPeriod
123
+ prod = firstPeriod * lastPeriod
124
+ periodTech = "#{periodType} : #{firstPeriod} -> #{lastPeriod}"
125
+ period = periodTech
126
+ if sum == 0 && prod == 0
127
+ period = case periodType
128
+ when 'day' then "Today"
129
+ else inclFuture ? "This #{periodType.capitalize}" : "#{periodType.capitalize} to date"
130
+ end
131
+ elsif firstPeriod == lastPeriod
132
+ future = firstPeriod > 0
133
+ reln = future ? 'Next' : 'Previous'
134
+ period = case periodType
135
+ when 'day' then future ? 'Tomorrow' : 'Yesterday'
136
+ else "#{reln} #{periodType.capitalize}"
137
+ end
138
+ else
139
+ span = lastPeriod - firstPeriod + 1
140
+ future = firstPeriod == 0
141
+ reln = future ? "Next" : "Last"
142
+ period = "#{reln} #{span} #{periodType.capitalize}s"
143
+ end
144
+ recordValue period, periodTech
145
+ end
146
+
147
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
148
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
149
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='in-range'>
150
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='all' />
151
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='non-null' />
152
+ # <filter class='quantitative' column='[Sample - Superstore].[none:Profit:qk]' included-values='null' />
153
+ def resolveQuantitative
154
+ emit "resolveQuantitative"
155
+ inclValues = @node['included-values']
156
+ qvalues = if 'in-range' == inclValues
157
+ quantRangeValues
158
+ else
159
+ "#{inclValues.capitalize} Values"
160
+ end
161
+ # recordValue qvalues, inclValues
162
+ end
163
+
164
+ def quantRangeValues
165
+ min = @node.at_xpath('min')
166
+ max = @node.at_xpath('max')
167
+ emit "min: nil? %-6s val: %-s " % [min.nil?,min]
168
+ emit "max: nil? %-6s val: %-s " % [max.nil?,max]
169
+ minrv = parseRangeVal min unless min.nil?
170
+ maxrv = parseRangeVal max unless max.nil?
171
+ mintxt = min.nil? ? '' : max.nil? ? "At least: #{minrv}" : "#{minrv}"
172
+ maxtxt = max.nil? ? '' : min.nil? ? "At most: #{maxrv}" : ",...,#{maxrv}"
173
+ recordValue "#{mintxt}#{maxtxt}"
174
+ # emit "#{mintxt}#{maxtxt}"
175
+ # return "#{mintxt} #{maxtxt}"
176
+ end
177
+
178
+ def parseRangeVal node
179
+ return 'nil' if node.nil?
180
+ text = node.text
181
+ if text.start_with?('#')
182
+ return text.gsub(/^[#]|[#]$/,'')
183
+ end
184
+ num = text.to_f
185
+ # num.negative? ? num.floor : num.ceil
186
+ result = if num < 0
187
+ num.floor
188
+ else
189
+ num.ceil
190
+ end
191
+ return result
192
+ end
193
+
194
+ # <filter class='categorical' column='[Sample - Superstore - English (Extract)].[none:Region:nk]'>
195
+ # <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
+ # </filter>
197
+ #--
198
+ def resolveCategoricalValues
199
+ emit "resolveCategoricalValues"
200
+ emit "@measureNames: #{@measureNames}"
201
+ if @node.element_children.empty?
202
+ # <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]' />
203
+ recordValue 'All'
204
+ return
205
+ end
206
+ firstChild = @node.at_xpath('./groupfilter')
207
+ unless firstChild.nil? # should not happen per 'if @node.element_children.empty?' above
208
+ function = firstChild['function']
209
+ emit "function : #{function}"
210
+ emit node.to_s
211
+ #-- single element filter
212
+ case function
213
+ when 'member'
214
+ member = firstChild['member']
215
+ value = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
216
+ recordValue value, @dataSource.deAlias(@uiname,value)
217
+ when 'empty-level'
218
+ recordValue 'None'
219
+ when 'range'
220
+ filtersFromRangeNode firstChild
221
+ when 'union'
222
+ emit "No resolving to do here. 'union' function accommodated with collection of members"
223
+ when 'except'
224
+ emit "UNRESOLVED function: #{function}"
225
+ when 'reorder-dimensionality'
226
+ emit "UNRESOLVED function: #{function}, involved with Inclusions & Exclusions - complicated to handle"
227
+ when 'level-members'
228
+ uiEnum = firstChild['user:ui-enumeration']
229
+ case uiEnum
230
+ when 'all'
231
+ recordValue 'All'
232
+ else
233
+ recordValue 'N/A'
234
+ emit "###### ALERT - Unresolved Quick filter ######"
235
+ end
236
+ else
237
+ recordValue "UNRESOLVED fn: #{function}"
238
+ end
239
+ # if 'member'.eql? function
240
+ # emit "HANDLING SINGLE MEMBER FILTER"
241
+ # member = firstChild['member']
242
+ # value = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
243
+ # alia = @dataSource.deAlias(@uiname,value)
244
+ # emit "value :%-25s => alias: %-s" % [value, alia]
245
+ # recordValue value, alia
246
+ # return
247
+ # end
248
+ # if 'empty-level' == function
249
+ # recordValue 'None', 'None'
250
+ # return
251
+ # end
252
+ # if 'range'.eql? function
253
+ # emit "HANDLING RANGE 1st Child FILTER"
254
+ # # values = filtersFromRangeNode firstChild
255
+ # filtersFromRangeNode firstChild
256
+ # # values.each do |valMap|
257
+ # # recordValue valMap[:value], valMap[:alias]
258
+ # # end
259
+ # end
260
+ #-- another single element filter
261
+ # <groupfilter function="level-members" level="[none:Business Line:nk]" user:ui-enumeration="all" user:ui-marker="enumerate"/>
262
+ # <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
263
+ # <groupfilter function='level-members'
264
+ # level='[none:Business Line:nk]'
265
+ # user:ui-enumeration='all'
266
+ # user:ui-exclude='true'
267
+ # user:ui-marker='enumerate' />
268
+ # </filter>
269
+ # <filter class='categorical' column='[federated.1astm0q1hl2ydc1dyqhqq0igvxkp].[none:Business Line:nk]' context='true'>
270
+ # <groupfilter function='level-members'
271
+ # level='[none:Business Line:nk]'
272
+ # user:ui-enumeration='all'
273
+ # user:ui-marker='enumerate' />
274
+ # </filter>
275
+ if 'level-members'.eql? function
276
+ emit "HANDLING level-members FILTER"
277
+ #--
278
+ # <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
279
+ # <groupfilter function='level-members' level='[Customer Name]' user:ui-enumeration='all' user:ui-marker='enumerate' />
280
+ # </filter>
281
+ #--
282
+ # <filter class='categorical' column='[Sample - Superstore].[none:Customer Name:nk]'>
283
+ # <groupfilter function='level-members' level='[none:Customer Name:nk]' user:ui-enumeration='all' user:ui-marker='enumerate' />
284
+ # </filter>
285
+ #--
286
+ # <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
287
+ # <groupfilter function='level-members' level='[Customer Name]' user:ui-enumeration='all' user:ui-exclude='true' user:ui-marker='enumerate' />
288
+ # </filter>
289
+ #--
290
+ # <filter class='categorical' column='[Sample - Superstore].[none:Customer Name:nk]'>
291
+ # <groupfilter function='level-members' level='[none:Customer Name:nk]' user:ui-enumeration='all' user:ui-marker='enumerate' />
292
+ # </filter>
293
+ #--
294
+ # <filter class='categorical' column='[Sample - Superstore].[Top Customers by Profit (copy)]'>
295
+ # <groupfilter function='except' user:ui-domain='relevant' user:ui-enumeration='exclusive' user:ui-marker='enumerate'>
296
+ # <groupfilter function='level-members' level='[Customer Name]' />
297
+ # <groupfilter function='union'>
298
+ # <groupfilter function='member' level='[Customer Name]' member='&quot;Adrian Barton&quot;' />
299
+ # <groupfilter function='member' level='[Customer Name]' member='&quot;Raymond Buch&quot;' />
300
+ # </groupfilter>
301
+ # </groupfilter>
302
+ # </filter>
303
+ #--
304
+ uiEnum = firstChild['user:ui-enumeration']
305
+ case uiEnum
306
+ when 'all'
307
+ recordValue 'All', 'All'
308
+ when 'inclusive'
309
+ @inexclude = 'Include'
310
+ when 'exclusive'
311
+ @inexclude = 'Exclude'
312
+ end
313
+ end
314
+ #-- otherwise filter contains multiple elements
315
+ #-- handle individual member elements
316
+ elements = firstChild.xpath('.//groupfilter')
317
+ elements.each do |element|
318
+ function = element.attribute('function').text
319
+ emit "element function: #{function}\n node:\n#{element}"
320
+ if 'member'.eql? function
321
+ member = element.attribute('member').text
322
+ name = @measureNames ? @dataSource.fieldUIName(Twb::CodedField.new(member).name) : member.gsub(/^"|"$/,'')
323
+ emit "%%%% member NAME:: #{name}"
324
+ if '%null%' == name then name = 'Null' end
325
+ alia = @dataSource.fieldAlias(@uiname,name) # $TableNameAliases[name]
326
+ recordValue name, alia
327
+ end
328
+ if 'range'.eql? function
329
+ emit "%%%% range element:: #{element}"
330
+ t = filtersFromRangeNode element
331
+ if t.empty?
332
+ fromAttr = element['from']
333
+ from = fromAttr.nil? ? '' : fromAttr.gsub(/^[#"']|[#"']$/,'')
334
+ toAttr = element['to']
335
+ to = toAttr.nil? ? '' : toAttr.gsub(/^[#"']|[#"']$/,'')
336
+ range = "#{from},...,#{to}"
337
+ recordValue range, range
338
+ end
339
+ t.each do |name,alia|
340
+ # emit "%%%% range Name: %-20s ALIAS: %-s " % [name, alia]
341
+ recordValue name, @dataSource.fieldAlias(@uiname,name)
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ def filtersFromRangeNode node
349
+ unless @twbDomainsLoaded
350
+ loadDomains
351
+ end
352
+ # results = []
353
+ emit "filtersFromRangeNode"
354
+ emit " from: #{node.attribute('from')}"
355
+ emit " to. : #{node.attribute('to')}"
356
+ from = node.attribute('from').text.gsub(/^"|"$/,'')
357
+ to = node.attribute( 'to').text.gsub(/^"|"$/,'')
358
+ emit " from: #{from}"
359
+ emit " to. : #{to}"
360
+ if @twbDomainsLoaded
361
+ # results = filtersInRange from, to
362
+ filtersInRange from, to
363
+ else
364
+ range = "#{from},...,#{to}"
365
+ recordValue range, range
366
+ end
367
+ # return results
368
+ end
369
+
370
+ def filtersInRange from, to
371
+ emit "filtersInRange"
372
+ # results = {}
373
+ dsFields = @twbFielddomains[@dataSource.uiname]
374
+ emit "dsFields : #{dsFields}"
375
+ if dsFields.nil? || dsFields.empty?
376
+ alert "#### ALERT #### - '#{@uiname}' FIELD DOMAIN VALUES FOR '#{@twb.name} DATASOURCE #{@dataSource.uiname} NOT LOADED ####"
377
+ emit @twbFieldDomains
378
+ emit "==========="
379
+ emit dsFields
380
+ emit "==========="
381
+ else
382
+ fieldVals = dsFields[@uiname].to_a
383
+ if @dataSource.fieldHasAliases @uiname
384
+ #-- resolve aliases
385
+ dbValues = SortedSet.new
386
+ aliases = @dataSource.fieldAliases @uiname
387
+ fieldVals.each do |fv|
388
+ fvAliased = aliases.has_value? fv
389
+ if fvAliased
390
+ dbValues << aliases.key(fv)
391
+ else
392
+ dbValues << fv
393
+ end
394
+ end
395
+ $fieldVals = dbValues.to_a
396
+ else
397
+ #-- use domain values as returned
398
+ $fieldVals = dsFields[@uiname].to_a
399
+ end
400
+ # domainVals = dsFields[field].to_a
401
+ # #-- need to dealias (unalias?) the field domain values
402
+ # dbVals = SortedSet.new
403
+ # domainVals.each do |dv|
404
+ emit "field values: #{$fieldVals}"
405
+ unless $fieldVals.nil? || $fieldVals.empty?
406
+ values = $fieldVals
407
+ f_i = values.index from
408
+ t_i = values.index to
409
+ emit " from: #{f_i} :: '#{from}'"
410
+ emit " to: #{t_i} :: '#{to}'"
411
+ badRange = f_i.nil? || t_i.nil?
412
+ emit "badRange: #{badRange}"
413
+ if badRange
414
+ emit "BAD RANGE"
415
+ else
416
+ tnames = values[f_i..t_i]
417
+ tnames.each do |tname|
418
+ # results[tname] = @dataSource.deAlias(@uiname,tname) # $TableNameAliases[tname]
419
+ recordValue tname, @dataSource.deAlias(@uiname,tname)
420
+ end
421
+ # emit " filtersInRange f:%-35s t:%-s " % ["'#{from}:#{f_i}'", "'#{to}:#{t_i}'"]
422
+ # emit " names: #{results.keys}"
423
+ # emit " aliases: #{results.values}"
424
+ end
425
+ end
426
+ end
427
+ # return results
428
+ end
429
+
430
+ def loadDomains
431
+ emit "def loadDomains\n=="
432
+ unless @twb.nil?
433
+ loader = Twb::Util::FieldDomainLoader.new
434
+ @twbFielddomains = loader.loadWorkbook @twb
435
+ emit "FIELD DOMAINS:: #{@twbFielddomains}\n=="
436
+ @twbDomainsLoaded = true
437
+ end
438
+ end
439
+
440
+ end # class QuickFilter
441
+
442
+ end # module Twb