bcl 0.5.7.pre → 0.7.0

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.
@@ -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