twb 1.0.5 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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