twb 1.0.5 → 1.9.1

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.
@@ -65,7 +65,7 @@ module Twb
65
65
  @minheight = size.attr('minheight')
66
66
  @minwidth = size.attr('minwidth')
67
67
  @rangesize = size.attr('rangesize')
68
- @dimensions = @minwidth.to_s + ':' + @minheight.to_s + ':' +@maxwidth.to_s + ':' +@maxheight.to_s
68
+ @dimensions = "%s:%s:%s:%s" % [@minwidth.to_s, @minheight.to_s, @maxwidth.to_s, @maxheight.to_s]
69
69
  end
70
70
 
71
71
  end
@@ -20,36 +20,47 @@ require 'json'
20
20
  module Twb
21
21
 
22
22
  class DataSource
23
-
23
+
24
24
  @@hasher = Digest::SHA256.new
25
25
 
26
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" }
27
+ { "csv" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "CSV" },
28
+ "excel" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "Excel" },
29
+ "dataengine" : { "label" : ["dbname" ], "id" : ["directory","filename"], "type" : "TDE" },
30
+ "msaccess" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "MS Access" },
31
+ "oracle" : { "label" : ["server" ], "id" : [ "server" ], "type" : "Oracle" },
32
+ "postgres" : { "label" : ["server" ], "id" : [ "server" ], "type" : "PostgreSQL" },
33
+ "textscan" : { "label" : ["filename"], "id" : ["directory","filename"], "type" : "CSV / TSV" },
34
+ "excel-direct" : { "label" : ["filename"], "id" : [ "filename"], "type" : "Excel" },
35
+ "salesforce" : { "label" : ["server" ], "id" : [ "server" ], "type" : "Salesforce" }
34
36
  }
35
37
  )
36
38
  @@cgNodeParams = JSON.parse @@connGNodeParamsJSON
37
39
 
38
40
 
39
- attr_reader :name, :caption, :uiname, :dsclass
40
- attr_reader :connection, :connHash
41
- attr_reader :tables
42
- attr_reader :localfields, :metadatafields, :mappedFields, :fieldUINames, :calculatedFields
41
+ attr_reader :workbook
42
+ attr_reader :name, :caption, :uiname
43
+ attr_reader :dsclass, :isExtract
44
+ attr_reader :connection, :connHash
45
+ attr_reader :tables, :joinPairs
46
+ attr_reader :localFields, :localFieldNames, :localField, :hasField
47
+ attr_reader :columnFields
48
+ attr_reader :metadataFields
49
+ attr_reader :dbFields
50
+ attr_reader :mappedFields
51
+ attr_reader :fieldUINames
52
+ attr_reader :calculatedFields, :calculatedFieldNamesMap, :calculatedFieldNames, :calculatedField
53
+ attr_reader :allFields
43
54
  attr_reader :filters
44
55
  attr_reader :node
45
56
 
46
- def initialize dataSourceNode
47
- @node = dataSourceNode
48
- @name = @node.attr('name')
49
- @caption = @node.attr('caption')
50
- @uiname = if @caption.nil? || @caption == '' then @name else @caption end
57
+ def initialize dataSourceNode, workbook
58
+ @node = dataSourceNode
59
+ @workbook = workbook
60
+ @name = @node.attr('name')
61
+ @caption = @node.attr('caption')
62
+ @uiname = if @caption.nil? || @caption == '' then @name else @caption end
51
63
  processConnection
52
- processFields
53
64
  processFilters
54
65
  loadTableFields
55
66
  return self
@@ -88,6 +99,10 @@ module Twb
88
99
  @connHash = Digest::MD5.hexdigest(dsConnStr)
89
100
  end
90
101
 
102
+ def isExtract
103
+ @isExtract ||= @isExtract = !@node.at_xpath('./extract').nil?
104
+ end
105
+
91
106
  def loadTables connection
92
107
  @tables = {}
93
108
  nodes = connection.xpath(".//relation[@type='table']")
@@ -96,43 +111,115 @@ module Twb
96
111
  end
97
112
  end
98
113
 
114
+ def joinPairs
115
+ @joinPairs ||= loadJoinPairs
116
+ end
117
+
118
+ def loadJoinPairs
119
+ @joinPairs = Set.new
120
+ mainJoin = @node.xpath("./connection/relation[@type='join']")
121
+ clauses = @node.xpath(".//relation[@type='join']/clause")
122
+ clauses.each do |clause|
123
+ leafs = clause.xpath('.//expression[not(node())]')
124
+ @joinPairs << [ pullTable(leafs[0]) , pullTable(leafs[1]) ]
125
+ end
126
+ return @joinPairs
127
+ end
128
+
129
+ def joinTree
130
+ @joinTree ||= loadJoinTree
131
+ end
132
+
133
+ def loadJoinTree
134
+ loadJoinPairs if @joinPairs.nil?
135
+ # puts "LJT::#{@uiname}::joinPairs:: #{@joinPairs.inspect}"
136
+ # @joinPairs.each { |jp| puts "JP::#{jp}" }
137
+ @joinTree = JoinTree.new(@name)
138
+ @joinPairs.each do |from,to|
139
+ # puts "from:#{from} -> to:#{to}"
140
+ tableFrom = JoinTable.new(from)
141
+ tableTo = JoinTable.new(to)
142
+ @joinTree.add(tableFrom, tableTo)
143
+ end
144
+ # puts '---'
145
+ return @joinTree
146
+ end
147
+
148
+ def pullTable xml
149
+ code =xml.attribute('op').text
150
+ table = code.split('].[')[0][1..-1]
151
+ end
152
+
153
+
99
154
  def Parameters?
100
155
  'Parameters'.eql? @name
101
156
  end
102
157
 
103
- def processFields
104
- # --
105
- @localfields = {}
106
- @metadatafields = {}
107
- @calculatedFields = @node.xpath("./column/calculation")
108
- return if @connection.nil?
109
- ## load local fields
110
- connClass = @node.at_xpath('./connection').attribute('class').text
111
- fxpath = case connClass
112
- when 'dataengine' then './column'
113
- when 'sqlserver' then './column'
114
- else './connection/relation/columns/column'
115
- end
116
- nodes = @node.xpath(fxpath)
117
- # puts "DATASOURCE ::=>> @node: connClass: '#{connClass.class}' ::: #{connClass.eql?('dataengine')} fxpath: #{fxpath} :: #{nodes.length}"
118
- nodes.each do |node|
119
- field = Twb::LocalField.new(node)
120
- @localfields[field.dbname] = field
158
+ def columnFields
159
+ @columnFields ||= loadColumnFields
160
+ end
161
+
162
+ def loadColumnFields
163
+ @columnFields = Set.new
164
+ nodes = @node.xpath('./column')
165
+ nodes.each do |n|
166
+ field = Twb::ColumnField.new n
167
+ @columnFields << field
121
168
  end
122
- ## load metadata fields
123
- nodes = @node.xpath("./connection/metadata-records/metadata-record[@class='column']")
124
- # note: there are other nodes "<metadata-record class='capability'>" whose nature is unclear
125
- # these nodes have no value for their <name node, so we're not loading them
126
- nodes.each do |node|
127
- field = Twb::MetadataField.new(node)
128
- @metadatafields[field.name] = field
169
+ return @columnFields
170
+ end
171
+
172
+ def localFields
173
+ @localFields ||= loadLocalFields
174
+ end
175
+
176
+ def loadLocalFields
177
+ @localFields = {}
178
+ unless @connection.nil? # Parameters has no connection node, & no local fields
179
+ connClass = @node.at_xpath('./connection').attribute('class').text
180
+ fxpath = case connClass
181
+ when 'dataengine' then './column'
182
+ when 'sqlserver' then './column'
183
+ else './connection/relation/columns/column'
184
+ end
185
+ nodes = @node.xpath(fxpath)
186
+ nodes.each do |node|
187
+ field = Twb::LocalField.new(node)
188
+ @localFields[field.name] = field
189
+ end
129
190
  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
191
+ return @localFields
192
+ end
193
+
194
+ def metadataFields
195
+ @metadataFields ||= loadMetadataFields
196
+ end
197
+
198
+ def loadMetadataFields
199
+ @metadataFields = Set.new
200
+ unless @connection.nil? # Parameters has no connection node, & no metadata fields
201
+ # nodes = @node.xpath(".//metadata-record[@class='column']")
202
+ # # note: there are other nodes "<metadata-record class='capability'>" whose nature is unclear
203
+ # # these nodes have no value for their <name node, so are not loaded
204
+ nodes = @node.xpath('./connection//metadata-record')
205
+ nodes.each do |node|
206
+ field = Twb::MetadataField.new(node)
207
+ field.source = :db
208
+ @metadataFields << field
209
+ end
210
+ nodes = @node.xpath('./extract//metadata-record')
211
+ nodes.each do |node|
212
+ field = Twb::MetadataField.new(node)
213
+ field.source = :extract
214
+ @metadataFields << field
215
+ end
216
+ end
217
+ return @metadataFields
218
+ end
219
+
220
+ def updateTime
221
+ attr = @node.xpath('.//@update-time').first
222
+ @updateTime = attr.nil? ? nil : attr.value
136
223
  end
137
224
 
138
225
  def fieldUIName fieldName
@@ -163,12 +250,58 @@ module Twb
163
250
  end
164
251
  end
165
252
 
166
- def mappedFields
253
+ def dbFields
167
254
  return @mappedFields unless @mappedFields.nil?
168
255
  loadTableFields
169
256
  return @mappedFields
170
257
  end
171
258
 
259
+ def mappedFields
260
+ loadTableFields if @mappedFields.nil?
261
+ return @mappedFields
262
+ end
263
+
264
+ # NOTE: Calculated Fields are mapped by their UI name
265
+ def calculatedFieldsMap
266
+ @calculatedFieldsMap ||= loadCalculatedFields
267
+ end
268
+
269
+ def calculatedFields
270
+ loadCalculatedFields if @calculatedFieldsMap.nil?
271
+ return @calculatedFieldsMap.values
272
+ end
273
+
274
+ def calculatedFieldNames
275
+ loadCalculatedFields if @calculatedFieldsMap.nil?
276
+ return @calculatedFieldsMap.keys
277
+ end
278
+
279
+ def calculatedField(name)
280
+ loadCalculatedFields if @calculatedFieldsMap.nil?
281
+ return @calculatedFieldsMap[name]
282
+ end
283
+
284
+ def loadCalculatedFields
285
+ @calculatedFieldsMap = {}
286
+ cfnodes = @node.xpath("./column[calculation]")
287
+ cfnodes.each do |node|
288
+ calcField = Twb::CalculatedField.new node, self
289
+ @calculatedFieldsMap[calcField.uiname] = calcField
290
+ end
291
+ return @calculatedFieldsMap
292
+ end
293
+
294
+ def allFields
295
+ return @allFields unless @allFields.nil?
296
+ @allFields = SortedSet.new
297
+ dbf = dbFields
298
+ @allFields << dbf.keys
299
+ end
300
+
301
+ def has_field? fieldName
302
+ dbFields.has_key? fieldName
303
+ end
304
+
172
305
  def fieldTable fieldName
173
306
  loadTableFields if @mappedFields.nil?
174
307
  tf = @mappedFields[fieldName]
@@ -200,11 +333,11 @@ module Twb
200
333
  end
201
334
  end
202
335
 
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
336
+ # =begin
337
+ # <filter class='categorical' column='[enforcement_type]' filter-group='2'>
338
+ # <groupfilter function='member' level='[enforcement_type]' member='&quot;towing&quot;' user:ui-domain='database' user:ui-enumeration='inclusive' user:ui-marker='enumerate' />
339
+ # </filter>
340
+ # end
208
341
  def processFilters
209
342
  if @filters.nil?
210
343
  @filters = {}
@@ -224,69 +357,183 @@ module Twb
224
357
  end
225
358
  end
226
359
  end
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
-
291
-
292
- end
360
+
361
+ end # class DataSource
362
+
363
+ # DataSource Utilities
364
+
365
+ # # Generates and returns a set of graph node-edge-node triplets.
366
+ # # The intention is to create a graph that can be standalone used as a subgraph by the function's caller.
367
+ # # The initial implementation only considers a linear list of node names as input, resulting in
368
+ # # the creation of a single-path structure, e.g.
369
+ # # [inNode] -> node1 -> node2 -> ... -> nodeN
370
+ # # this may manifest as a tree when there are siblings at any level, e.g.
371
+ # # [inNode] -> node1 -> node11 -> ... -> nodeX
372
+ # # -> node22 -> ... -> nodeY
373
+ # # -> node2 -> node21 -> ... -> nodeZ
374
+ # # Params:
375
+ # # +inNodes+:: the node(s) to which this function's generated graph is to be linked, may be nil:
376
+ # # + , or multiple)
377
+ # # +nodesList+:: list of node types by name to be built and linked together, in the order named
378
+ # def graphNodes nodesList
379
+ # graph = Twb::Util::Graphedges.new()
380
+ # nodesList.each do |nName|
381
+ # end
382
+ # case @dsclass
383
+ # when 'federated'
384
+ # graph = processFederatedSource nodesList
385
+ # end
386
+ # return graph
387
+ # end
388
+
389
+ # def processFederatedSource nodesList
390
+ # emit false, " (federated) #{dataSource.uiname}"
391
+ # dsGNode = Twb::Util::Graphnode.new(name: @uiname, id: @name, type: 'Data Connection')
392
+ # graph = Twb::Util::Graphedges.new(dsGNode)
393
+ # edges = []
394
+ # dsNode = dataSource.node
395
+ # connections = dsNode.xpath('./connection/named-connections/named-connection/connection')
396
+ # connections.each do |conn|
397
+ # connClass = conn.attribute('class').text
398
+ # emit true, "CONN CLASS: #{connClass}"
399
+ # # -- Generating Source Node
400
+ # cgParams = @@cgNodeParams[connClass]
401
+ # # emit true, "cgparams : #{cgParams}"
402
+ # cLabel = buildConnGraphPart( conn, cgParams['label'])
403
+ # cID = buildConnGraphPart( conn, cgParams['id'])
404
+ # cType = cgParams['type']
405
+ # # emit true, " label : #{cLabel}"
406
+ # # emit true, " id : #{cID}"
407
+ # # emit true, " type : #{cType}"
408
+ # srcNode = Twb::Util::Graphnode.new(name: cLabel, id: cID, type: 'Data Source')
409
+ # graphEdge = Twb::Util::Graphedge.new(from: dsGNode, to: srcNode, relationship: 'is located at')
410
+ # graph.edges << graphEdge
411
+ # end
412
+ # return graph
413
+ # end
414
+
415
+ # def buildConnGraphPart connNode, attributes
416
+ # return connNode.attribute(attributes) if attributes.is_a? String
417
+ # emit false, "ATTRIBUTES :: #{attributes}"
418
+ # str = ''
419
+ # attributes.each do |attName|
420
+ # attrib = connNode.attribute(attName)
421
+ # emit false, " -#{attName}\t-> #{attrib}"
422
+ # str += attrib.text unless attrib.nil?
423
+ # end
424
+ # return str
425
+ # end
426
+
427
+
428
+ class JoinTablePair
429
+ attr_reader :from, :to
430
+ def initialize(from, to)
431
+ raise ParamaterException.new("'From' cannot be nil.") if from.nil?
432
+ raise ParamaterException.new("'To' cannot be nil.") if to.nil?
433
+ @from = from
434
+ @to = to
435
+ end
436
+ def to_s
437
+ "#{@from} -> #{@to}"
438
+ end
439
+ end # class JoinTablePair
440
+
441
+
442
+ class JoinTable
443
+ include Comparable
444
+
445
+ attr_reader :name, :datasource #, :str
446
+ attr_accessor :children, :depth
447
+
448
+ def initialize(name, datasource=nil)
449
+ @name = name
450
+ @datasource = datasource
451
+ # @str = nil
452
+ @children = {}
453
+ @depth = 0
454
+ end
455
+
456
+ def to_s
457
+ str = "[#{@datasource}].[#{@name}] :: (#{@depth}) :: #{@children.length} :: "
458
+ @children.each { |n,c| str << "#{n}, " }
459
+ return str
460
+ end
461
+
462
+ def <=>(anOther)
463
+ @str <=> anOther.str
464
+ end
465
+
466
+ def addChild child
467
+ # puts "#{@name}.addChild(#{child.name})"
468
+ # puts "children: #{@children}"
469
+ @children[child.name] = child if @children[child.name].nil?
470
+ # puts "children: #{@children}"
471
+ end
472
+
473
+ def child name
474
+ @children[name]
475
+ end
476
+
477
+ end # class JoinTable
478
+
479
+
480
+ class JoinTree
481
+
482
+ attr_reader :datasource, :root, :maxdepth, :tables
483
+
484
+ def initialize datasource
485
+ @datasource = datasource
486
+ @root = nil
487
+ @maxdepth = 0
488
+ @tables = {}
489
+ end
490
+
491
+ def add host, dest
492
+ # puts "\nJT add() host: #{host}"
493
+ # puts " dest: #{dest}"
494
+ from = @tables[host.name].nil? ? host : @tables[host.name]
495
+ to = @tables[dest.name].nil? ? dest : @tables[dest.name]
496
+ from.addChild(to)
497
+ @tables[from.name] = from
498
+ @tables[to.name] = to
499
+ if @root.nil? || @root.name.eql?(to.name)
500
+ @root = from
501
+ end
502
+ setDepth(@root,1)
503
+ end
504
+
505
+ def table tableName
506
+ @tables[tableName]
507
+ end
508
+
509
+ def setDepth table, depth
510
+ # puts "-- setDepth(#{table.class}[#{table.name}] \t, #{depth})"
511
+ @tables[table.name].depth = depth
512
+ childrenDepth = depth+1
513
+ table.children.each { |n,c| setDepth(c,childrenDepth)}
514
+ end
515
+
516
+ def tableDepth tableName
517
+ @tables[tableName].nil? ? 0 : @tables[tableName].depth
518
+ end
519
+
520
+ def disp table
521
+ puts "%s %s %s (%d)" % [' ' * table.depth, '-' * table.depth, table.name, table.depth]
522
+ table.children.each { |n,c| disp c}
523
+ end
524
+
525
+ def iterate
526
+ disp @root
527
+ end
528
+
529
+ def to_s
530
+ str = "root: #{@root}\ntables"
531
+ @tables.each do |t|
532
+ str << " tbl:: #{t}"
533
+ end
534
+ return str
535
+ end
536
+
537
+ end # class JoinTree
538
+
539
+ end # module Twb