bcl 0.5.7.pre → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,530 +0,0 @@
1
- ######################################################################
2
- # Copyright (c) 2008-2014, Alliance for Sustainable Energy.
3
- # All rights reserved.
4
- #
5
- # This library is free software; you can redistribute it and/or
6
- # modify it under the terms of the GNU Lesser General Public
7
- # License as published by the Free Software Foundation; either
8
- # version 2.1 of the License, or (at your option) any later version.
9
- #
10
- # This library is distributed in the hope that it will be useful,
11
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
- # Lesser General Public License for more details.
14
- #
15
- # You should have received a copy of the GNU Lesser General Public
16
- # License along with this library; if not, write to the Free Software
17
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
- ######################################################################
19
-
20
- $have_win32ole = false
21
-
22
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
23
- begin
24
- # apparently this is not a gem
25
- require 'win32ole'
26
- mod = WIN32OLE
27
- $have_win32ole = true
28
- rescue NameError
29
- # do not have win32ole
30
- end
31
- end
32
-
33
- module BCL
34
- # each TagStruct represents a node in the taxonomy tree
35
- TagStruct = Struct.new(:level_hierarchy, :name, :description, :parent_tag, :child_tags, :terms)
36
-
37
- # each TermStruct represents a row in the master taxonomy
38
- TermStruct = Struct.new(:first_level, :second_level, :third_level, :level_hierarchy, :name, :description,
39
- :abbr, :data_type, :enums, :ip_written, :ip_symbol, :ip_mask, :si_written, :si_symbol, :si_mask,
40
- :unit_conversion, :default_val, :min_val, :max_val, :allow_multiple, :row, :tp_include,
41
- :tp_required, :tp_use_in_search, :tp_use_in_facets, :tp_show_data_to_data_users, :tp_third_party_testing,
42
- :tp_additional_web_dev_info, :tp_additional_data_user_info, :tp_additional_data_submitter_info)
43
-
44
- # class for parsing, validating, and querying the master taxonomy document
45
- class MasterTaxonomy
46
- # parse the master taxonomy document
47
- def initialize(xlsx_path = nil, sort_alpha = false)
48
- @sort_alphabetical = sort_alpha
49
-
50
- # hash of level_taxonomy to tag
51
- @tag_hash = {}
52
-
53
- if xlsx_path.nil?
54
- # load from the current taxonomy
55
- path = current_taxonomy_path
56
- puts "Loading current taxonomy from #{path}"
57
- File.open(path, 'r') do |file|
58
- @tag_hash = Marshal.load(file)
59
- end
60
- else
61
- xlsx_path = Pathname.new(xlsx_path).realpath.to_s
62
- puts "Loading taxonomy file #{xlsx_path}"
63
-
64
- # WINDOWS ONLY SECTION BECAUSE THIS USES WIN32OLE
65
- if $have_win32ole
66
- begin
67
- excel = WIN32OLE.new('Excel.Application')
68
- xlsx = excel.Workbooks.Open(xlsx_path)
69
- terms_worksheet = xlsx.Worksheets('Terms')
70
- parse_terms(terms_worksheet)
71
- ensure
72
- # not really saving just pretending so don't get prompted on quit
73
- xlsx.saved = true
74
- excel.Quit
75
- WIN32OLE.ole_free(excel)
76
- excel.ole_free
77
- xlsx = nil
78
- excel = nil
79
- GC.start
80
- end
81
- else # if $have_win32ole
82
- puts "MasterTaxonomy class requires 'win32ole' to parse master taxonomy document."
83
- puts 'MasterTaxonomy may also be stored and loaded from JSON if your platform does not support win32ole.'
84
- end # if $have_win32ole
85
- end
86
- end
87
-
88
- # save the current taxonomy
89
- def save_as_current_taxonomy(path = nil)
90
- unless path
91
- path = current_taxonomy_path
92
- end
93
- puts "Saving current taxonomy to #{path}"
94
- # this is really not JSON... it is a persisted format of ruby
95
- File.open(path, 'w') do |file|
96
- Marshal.dump(@tag_hash, file)
97
- end
98
- end
99
-
100
- # write taxonomy to xml
101
- def write_xml(path, output_type = 'tpex')
102
- root_tag = @tag_hash['']
103
-
104
- if root_tag.nil?
105
- puts 'Cannot find root tag'
106
- return false
107
- end
108
-
109
- File.open(path, 'w') do |file|
110
- xml = Builder::XmlMarkup.new(target: file, indent: 2)
111
-
112
- # setup the xml file
113
- xml.instruct!(:xml, version: '1.0', encoding: 'UTF-8')
114
- xml.schema('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance') do
115
- write_tag_to_xml(root_tag, 0, xml, output_type)
116
- end
117
- end
118
- end
119
-
120
- # get all terms for a given tag
121
- # this includes terms that are inherited from parent levels
122
- # e.g. master_taxonomy.get_terms("Space Use.Lighting.Lamp Ballast")
123
- def get_terms(tag)
124
- terms = tag.terms
125
-
126
- parent_tag = tag.parent_tag
127
- until parent_tag.nil?
128
- terms.concat(parent_tag.terms)
129
- parent_tag = parent_tag.parent_tag
130
- end
131
-
132
- # sort the terms as they come out
133
- result = terms.uniq
134
- if !@sort_alphabetical
135
- result = result.sort { |x, y| x.row <=> y.row }
136
- else
137
- result = result.sort { |x, y| x.name <=> y.name }
138
- end
139
-
140
- result
141
- end
142
-
143
- # check that the given component is conforms with the master taxonomy
144
- def check_component(component)
145
- valid = true
146
- tag = nil
147
-
148
- # see if we can find the component's tag in the taxonomy
149
- tags = component.tags
150
- if tags.empty?
151
- puts '[Check Component ERROR] Component does not have any tags'
152
- valid = false
153
- elsif tags.size > 1
154
- puts '[Check Component ERROR] Component has multiple tags'
155
- valid = false
156
- else
157
- tag = @tag_hash[tags[0].descriptor]
158
- unless tag
159
- puts "[Check Component ERROR] Cannot find #{tags[0].descriptor} in the master taxonomy"
160
- valid = false
161
- end
162
- end
163
-
164
- unless tag
165
- return false
166
- end
167
-
168
- terms = get_terms(tag)
169
-
170
- # TODO: check for all required attributes
171
- terms.each do |_term|
172
- # if term.required
173
- # make sure we find attribute
174
- # end
175
- end
176
-
177
- # check that all attributes are allowed
178
- component.attributes.each do |attribute|
179
- term = nil
180
- terms.each do |t|
181
- if t.name == attribute.name
182
- term = t
183
- break
184
- end
185
- end
186
-
187
- unless term
188
- puts "[Check Component ERROR] Cannot find term for #{attribute.name} in #{tag.level_hierarchy}"
189
- valid = false
190
- next
191
- end
192
-
193
- # TODO: validate value, datatype, units
194
- end
195
-
196
- valid
197
- end
198
-
199
- private
200
-
201
- def current_taxonomy_path
202
- File.dirname(__FILE__) + '/current_taxonomy.json'
203
- end
204
-
205
- def parse_terms(terms_worksheet)
206
- # check header
207
- header_error = validate_terms_header(terms_worksheet)
208
- if header_error
209
- fail 'Header Error on Terms Worksheet'
210
- end
211
-
212
- # add root tag
213
- root_terms = []
214
- root_terms << TermStruct.new('', '', '', '', 'OpenStudio Type', 'Type of OpenStudio Object')
215
- root_terms[0].row = 0
216
- # root_terms << TermStruct.new()
217
- root_tag = TagStruct.new('', 'root', 'Root of the taxonomy', nil, [], root_terms)
218
- @tag_hash[''] = root_tag
219
-
220
- ### puts "**** tag hash: #{@tag_hash}"
221
-
222
- # find number of rows by parsing until hit empty value in first column
223
- row_num = 3
224
- loop do
225
- term = parse_term(terms_worksheet, row_num)
226
- if term.nil?
227
- break
228
- end
229
-
230
- add_term(term)
231
-
232
- row_num += 1
233
- end
234
-
235
- # sort the tag tree
236
- sort_tag(root_tag)
237
-
238
- # check the tag tree
239
- check_tag(root_tag)
240
- end
241
-
242
- def validate_terms_header(terms_worksheet)
243
- test_arr = []
244
- test_arr << { 'name' => 'First Level', 'strict' => true }
245
- test_arr << { 'name' => 'Second Level', 'strict' => true }
246
- test_arr << { 'name' => 'Third Level', 'strict' => true }
247
- test_arr << { 'name' => 'Level Hierarchy', 'strict' => true }
248
- test_arr << { 'name' => 'Term', 'strict' => true }
249
- test_arr << { 'name' => 'Abbr', 'strict' => true }
250
- test_arr << { 'name' => 'Description', 'strict' => true }
251
- test_arr << { 'name' => 'Data Type', 'strict' => true }
252
- test_arr << { 'name' => 'Allow Multiple', 'strict' => true }
253
- test_arr << { 'name' => 'Enumerations', 'strict' => true }
254
- test_arr << { 'name' => 'IP Units Written Out', 'strict' => true }
255
- test_arr << { 'name' => 'IP Units Symbol', 'strict' => true }
256
- test_arr << { 'name' => 'IP Display Mask', 'strict' => true }
257
- test_arr << { 'name' => 'SI Units Written Out', 'strict' => true }
258
- test_arr << { 'name' => 'SI Units Symbol', 'strict' => true }
259
- test_arr << { 'name' => 'SI Display Mask', 'strict' => true }
260
- test_arr << { 'name' => 'Unit Conversion', 'strict' => true }
261
- test_arr << { 'name' => 'Default', 'strict' => true }
262
- test_arr << { 'name' => 'Min', 'strict' => true }
263
- test_arr << { 'name' => 'Max', 'strict' => true }
264
- test_arr << { 'name' => 'Source', 'strict' => true }
265
- test_arr << { 'name' => 'Review State', 'strict' => true }
266
- test_arr << { 'name' => 'General Comments', 'strict' => true }
267
- test_arr << { 'name' => 'Requested By / Project', 'strict' => true }
268
- test_arr << { 'name' => 'Include in TPE', 'strict' => false }
269
- test_arr << { 'name' => 'Required for Adding a New Product', 'strict' => false }
270
- test_arr << { 'name' => 'Use as a Column Header in Search Results', 'strict' => false }
271
- test_arr << { 'name' => 'Allow Users to Filter with this Facet', 'strict' => false }
272
- test_arr << { 'name' => 'Show Data to Data Users', 'strict' => false }
273
- test_arr << { 'name' => 'Additional Instructions for Web Developers', 'strict' => false }
274
- test_arr << { 'name' => 'Related Third Party Testing Standards', 'strict' => false }
275
- test_arr << { 'name' => 'Additional Guidance to Data Submitters', 'strict' => false }
276
- test_arr << { 'name' => 'Additional Guidance to Data Users', 'strict' => false }
277
-
278
- parse = true
279
- col = 1
280
- while parse
281
- if terms_worksheet.Columns(col).Rows(2).Value.nil? || col > test_arr.size
282
- parse = false
283
- else
284
- unless terms_worksheet.Columns(col).Rows(2).Value == test_arr[col - 1]['name']
285
- if test_arr[col - 1]['strict']
286
- fail "[ERROR] Header does not match: #{col}: '#{terms_worksheet.Columns(col).Rows(2).Value} <> #{test_arr[col - 1]['name']}'"
287
- else
288
- puts "[WARNING] Header does not match: #{col}: '#{terms_worksheet.Columns(col).Rows(2).Value} <> #{test_arr[col - 1]['name']}'"
289
- end
290
- end
291
- end
292
- col += 1
293
- end
294
- end
295
-
296
- def parse_term(terms_worksheet, row)
297
- term = TermStruct.new
298
- term.row = row
299
- term.first_level = terms_worksheet.Columns(1).Rows(row).Value
300
- term.second_level = terms_worksheet.Columns(2).Rows(row).Value
301
- term.third_level = terms_worksheet.Columns(3).Rows(row).Value
302
- term.level_hierarchy = terms_worksheet.Columns(4).Rows(row).Value
303
- term.name = terms_worksheet.Columns(5).Rows(row).Value
304
- term.abbr = terms_worksheet.Columns(6).Rows(row).Value
305
- term.description = terms_worksheet.Columns(7).Rows(row).Value
306
- term.data_type = terms_worksheet.Columns(8).Rows(row).Value
307
- term.allow_multiple = terms_worksheet.Columns(9).Rows(row).Value
308
- term.enums = terms_worksheet.Columns(10).Rows(row).Value
309
- term.ip_written = terms_worksheet.Columns(11).Rows(row).Value
310
- term.ip_symbol = terms_worksheet.Columns(12).Rows(row).Value
311
- term.ip_mask = terms_worksheet.Columns(13).Rows(row).Value
312
- term.si_written = terms_worksheet.Columns(14).Rows(row).Value
313
- term.si_symbol = terms_worksheet.Columns(15).Rows(row).Value
314
- term.si_mask = terms_worksheet.Columns(16).Rows(row).Value
315
- term.unit_conversion = terms_worksheet.Columns(17).Rows(row).Value
316
- term.default_val = terms_worksheet.Columns(18).Rows(row).Value
317
- term.min_val = terms_worksheet.Columns(19).Rows(row).Value
318
- term.max_val = terms_worksheet.Columns(20).Rows(row).Value
319
-
320
- # custom TPex Columns
321
- term.tp_include = terms_worksheet.Columns(25).Rows(row).Value
322
- term.tp_required = terms_worksheet.Columns(26).Rows(row).Value
323
- term.tp_use_in_search = terms_worksheet.Columns(27).Rows(row).Value
324
- term.tp_use_in_facets = terms_worksheet.Columns(28).Rows(row).Value
325
- term.tp_show_data_to_data_users = terms_worksheet.Columns(29).Rows(row).Value
326
- term.tp_additional_web_dev_info = terms_worksheet.Columns(30).Rows(row).Value
327
- term.tp_third_party_testing = terms_worksheet.Columns(31).Rows(row).Value
328
- term.tp_additional_data_submitter_info = terms_worksheet.Columns(32).Rows(row).Value
329
- term.tp_additional_data_user_info = terms_worksheet.Columns(33).Rows(row).Value
330
-
331
- # trigger to quit parsing the xcel doc
332
- if term.first_level.nil? || term.first_level.empty?
333
- return nil
334
- end
335
-
336
- term
337
- end
338
-
339
- def add_term(term)
340
- level_hierarchy = term.level_hierarchy
341
-
342
- # create the tag
343
- tag = @tag_hash[level_hierarchy]
344
-
345
- if tag.nil?
346
- tag = create_tag(level_hierarchy, term.description)
347
- end
348
-
349
- if term.name.nil? || term.name.strip.empty?
350
- # this row is really about the tag
351
- tag.description = term.description
352
-
353
- else
354
- # this row is about a term
355
- unless validate_term(term)
356
- return nil
357
- end
358
-
359
- tag.terms = [] if tag.terms.nil?
360
- tag.terms << term
361
- end
362
- end
363
-
364
- def create_tag(level_hierarchy, tag_description = '')
365
- # puts "create_tag called for #{level_hierarchy}"
366
-
367
- parts = level_hierarchy.split('.')
368
-
369
- name = parts[-1]
370
- parent_level = parts[0..-2].join('.')
371
-
372
- parent_tag = @tag_hash[parent_level]
373
- if parent_tag.nil?
374
- parent_tag = create_tag(parent_level)
375
- end
376
-
377
- description = tag_description
378
- child_tags = []
379
- terms = []
380
- tag = TagStruct.new(level_hierarchy, name, description, parent_tag, child_tags, terms)
381
-
382
- parent_tag.child_tags << tag
383
-
384
- @tag_hash[level_hierarchy] = tag
385
-
386
- tag
387
- end
388
-
389
- def sort_tag(tag)
390
- # tag.terms = tag.terms.sort {|x, y| x.level_hierarchy <=> y.level_hierarchy}
391
- tag.child_tags = tag.child_tags.sort { |x, y| x.level_hierarchy <=> y.level_hierarchy }
392
- tag.child_tags.each { |child_tag| sort_tag(child_tag) }
393
-
394
- # tag.terms = tag.terms.sort {|x, y| x.name <=> y.name}
395
- # tag.child_tags = tag.child_tags.sort {|x, y| x.name <=> y.name}
396
- # tag.child_tags.each {|child_tag| sort_tag(child_tag) }
397
- end
398
-
399
- def check_tag(tag)
400
- if tag.description.nil? || tag.description.empty?
401
- puts "[check_tag] tag '#{tag.level_hierarchy}' has no description"
402
- end
403
-
404
- tag.terms.each { |term| check_term(term) }
405
- tag.child_tags.each { |child_tag| check_tag(child_tag) }
406
- end
407
-
408
- def validate_term(term)
409
- valid = true
410
-
411
- parts = term.level_hierarchy.split('.')
412
-
413
- if parts.empty?
414
- puts "Hierarchy parts empty, #{term.level_hierarchy}"
415
- valid = false
416
- end
417
-
418
- if parts.size >= 1 && !term.first_level == parts[0]
419
- puts "First level '#{term.first_level}' does not match level hierarchy '#{term.level_hierarchy}', skipping term"
420
- valid = false
421
- end
422
-
423
- if parts.size >= 2 && !term.second_level == parts[1]
424
- puts "Second level '#{term.second_level}' does not match level hierarchy '#{term.level_hierarchy}', skipping term"
425
- valid = false
426
- end
427
-
428
- if parts.size >= 3 && !term.third_level == parts[2]
429
- puts "Third level '#{term.third_level}' does not match level hierarchy '#{term.level_hierarchy}', skipping term"
430
- valid = false
431
- end
432
-
433
- if parts.size > 3
434
- puts "Hierarchy cannot have more than 3 parts '#{term.level_hierarchy}', skipping term"
435
- valid = false
436
- end
437
-
438
- unless term.data_type.nil?
439
- valid_types = %w(double integer enum file string autocomplete)
440
- if (term.data_type.downcase != term.data_type) || !valid_types.include?(term.data_type)
441
- puts "[ERROR] Term '#{term.name}' does not have a valid data type with '#{term.data_type}'"
442
- end
443
-
444
- if term.data_type.downcase == 'enum'
445
- if term.enums.nil? || term.enums == '' || term.enums.downcase == 'no enum found'
446
- puts "[ERROR] Term '#{term.name}' does not have valid enumerations"
447
- end
448
- end
449
- end
450
-
451
- valid
452
- end
453
-
454
- def check_term(term)
455
- if term.description.nil? || term.description.empty?
456
- # puts "[check_term] term '#{term.level_hierarchy}.#{term.name}' has no description"
457
- end
458
- end
459
-
460
- # write term to xml
461
- def write_terms_to_xml(tag, xml, output_type)
462
- terms = get_terms(tag)
463
- if terms.size > 0
464
- terms.each do |term|
465
- xml.term do
466
- xml.name term.name
467
- xml.abbr term.abbr unless term.abbr.nil?
468
- xml.description term.description unless term.description.nil?
469
- xml.data_type term.data_type unless term.data_type.nil?
470
- xml.allow_multiple term.allow_multiple unless term.allow_multiple.nil?
471
-
472
- if !term.enums.nil? && term.enums != ''
473
- xml.enumerations do
474
- out = term.enums.split('|')
475
- out.sort! if @sort_alphabetical
476
- out.each do |enum|
477
- xml.enumeration enum
478
- end
479
- end
480
- end
481
- xml.ip_written term.ip_written unless term.ip_written.nil?
482
- xml.ip_symbol term.ip_symbol unless term.ip_symbol.nil?
483
- xml.ip_mask term.ip_mask unless term.ip_mask.nil?
484
- xml.si_written term.si_written unless term.si_written.nil?
485
- xml.si_symbol term.si_symbol unless term.si_symbol.nil?
486
- xml.si_mask term.si_mask unless term.si_mask.nil?
487
- xml.row term.row unless term.row.nil?
488
- xml.unit_conversion term.unit_conversion unless term.unit_conversion.nil?
489
- xml.default_val term.default_val unless term.default_val.nil?
490
- xml.min_val term.min_val unless term.min_val.nil?
491
- xml.max_val term.max_val unless term.max_val.nil?
492
-
493
- if output_type == 'tpex'
494
- xml.tp_include term.tp_include unless term.tp_include.nil?
495
- xml.tp_required term.tp_required unless term.tp_required.nil?
496
- xml.tp_use_in_search term.tp_use_in_search unless term.tp_use_in_search.nil?
497
- xml.tp_use_in_facets term.tp_use_in_facets unless term.tp_use_in_facets.nil?
498
- xml.tp_show_data_to_data_users term.tp_show_data_to_data_users unless term.tp_show_data_to_data_users.nil?
499
- xml.tp_third_party_testing term.tp_third_party_testing unless term.tp_third_party_testing.nil?
500
- xml.tp_additional_web_dev_info term.tp_additional_web_dev_info unless term.tp_additional_web_dev_info.nil?
501
- xml.tp_additional_data_user_info term.tp_additional_data_user_info unless term.tp_additional_data_user_info.nil?
502
- xml.tp_additional_data_submitter_info term.tp_additional_data_submitter_info unless term.tp_additional_data_submitter_info.nil?
503
- end
504
- end
505
- end
506
- end
507
- end
508
-
509
- # write a tag to xml
510
- def write_tag_to_xml(tag, level, xml, output_type)
511
- level_string = "level_#{level}"
512
- xml.tag!(level_string) do
513
- s_temp = tag.name
514
- xml.name s_temp
515
- xml.description tag.description
516
-
517
- level += 1
518
-
519
- if tag.child_tags.size == 0
520
- write_terms_to_xml(tag, xml, output_type)
521
- end
522
-
523
- child_tags = tag.child_tags
524
- child_tags.each do |child_tag|
525
- write_tag_to_xml(child_tag, level, xml, output_type)
526
- end
527
- end
528
- end
529
- end
530
- end # module BCL