bcl 0.1.4

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,6 @@
1
+ require 'bcl/ComponentSpreadsheet'
2
+ require 'bcl/ComponentXml'
3
+ require 'bcl/GatherComponents'
4
+ require 'bcl/TarBall'
5
+ require 'bcl/MasterTaxonomy'
6
+ require 'bcl/MongoToComponent'
@@ -0,0 +1,292 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2010, 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
+ # Converts a custom Excel spreadsheet format to BCL components for upload
21
+ # Format of the Excel spreadsheet is documented in /doc/ComponentSpreadsheet.docx
22
+
23
+ require 'rubygems'
24
+
25
+ require 'pathname'
26
+ require 'fileutils'
27
+ require 'uuid' # gem install uuid
28
+
29
+ require 'bcl/ComponentXml'
30
+ require 'bcl/GatherComponents'
31
+ require 'bcl/MasterTaxonomy'
32
+
33
+ $have_win32ole = false
34
+ begin
35
+ # apparently this is not a gem
36
+ require 'win32ole'
37
+ mod = WIN32OLE
38
+ $have_win32ole = true
39
+ rescue NameError
40
+ # do not have win32ole
41
+ end
42
+
43
+ module BCL
44
+
45
+ WorksheetStruct = Struct.new(:name, :components)
46
+ HeaderStruct = Struct.new(:name, :children)
47
+ ComponentStruct = Struct.new(:row, :name, :uid, :version_id, :headers, :values)
48
+
49
+ class ComponentSpreadsheet
50
+
51
+ public
52
+
53
+ # WINDOWS ONLY SECTION BECAUSE THIS USES WIN32OLE
54
+ if $have_win32ole
55
+
56
+ #initialize with Excel spreadsheet to read
57
+ def initialize(xlsx_path,worksheet_names = "all")
58
+
59
+ @xlsx_path = Pathname.new(xlsx_path).realpath.to_s
60
+ @worksheets = []
61
+
62
+ begin
63
+
64
+ excel = WIN32OLE::new('Excel.Application')
65
+
66
+ xlsx = excel.Workbooks.Open(@xlsx_path)
67
+
68
+ #by default, operate on all worksheets
69
+ if worksheet_names == "all"
70
+ xlsx.Worksheets.each do |xlsx_worksheet|
71
+ parse_xlsx_worksheet(xlsx_worksheet)
72
+ end
73
+ else #if specific worksheets are specified, operate on them
74
+ worksheet_names.each do |worksheet_name|
75
+ parse_xlsx_worksheet(xlsx.Worksheets(worksheet_name))
76
+ end
77
+ end
78
+
79
+ xlsx.saved = true
80
+ xlsx.Save
81
+
82
+ ensure
83
+
84
+ excel.Quit
85
+ WIN32OLE.ole_free(excel)
86
+ excel.ole_free
87
+ xlsx=nil
88
+ excel=nil
89
+ GC.start
90
+
91
+ end
92
+
93
+ end
94
+
95
+ else # if $have_win32ole
96
+
97
+ # parse the master taxonomy document
98
+ def initialize(xlsx_path)
99
+ puts "ComponentSpreadsheet class requires 'win32ole' to parse the component spreadsheet."
100
+ puts "ComponentSpreadsheet may also be stored and loaded from JSON if your platform does not support win32ole."
101
+ end
102
+
103
+ end # if $have_win32ole
104
+
105
+ def save(save_path)
106
+
107
+ # load master taxonomy to validate components
108
+ taxonomy = BCL::MasterTaxonomy.new
109
+
110
+ FileUtils.rm_rf(save_path) if File.exists?(save_path) and File.directory?(save_path)
111
+
112
+ @worksheets.each do |worksheet|
113
+ worksheet.components.each do |component|
114
+
115
+ component_xml = Component.new(save_path)
116
+ component_xml.name = component.name
117
+ component_xml.uid = component.uid
118
+ component_xml.comp_version_id = component.version_id
119
+
120
+ # this tag is how we know where this goes in the taxonomy
121
+ component_xml.add_tag(worksheet.name)
122
+
123
+ values = component.values[0]
124
+ component.headers.each do |header|
125
+
126
+ if /description/i.match(header.name)
127
+
128
+ name = values.delete_at(0)
129
+ uid = values.delete_at(0)
130
+ version_id = values.delete_at(0)
131
+ description = values.delete_at(0)
132
+ fidelity_level = values.delete_at(0).to_int
133
+ # name, uid, and version_id already processed
134
+ component_xml.description = description
135
+ component_xml.fidelity_level = fidelity_level
136
+
137
+ elsif /provenance/i.match(header.name)
138
+
139
+ author = values.delete_at(0)
140
+ datetime = values.delete_at(0)
141
+ comment = values.delete_at(0)
142
+ component_xml.add_provenance(author.to_s, datetime.to_s, comment.to_s)
143
+
144
+ elsif /tag/i.match(header.name)
145
+
146
+ value = values.delete_at(0)
147
+ component_xml.add_tag(value)
148
+
149
+ elsif /attribute/i.match(header.name)
150
+
151
+ value = values.delete_at(0)
152
+ name = header.children[0]
153
+ units = ""
154
+ if match_data = /(.*)\((.*)\)/.match(name)
155
+ name = match_data[1].strip
156
+ units = match_data[2].strip
157
+ end
158
+ component_xml.add_attribute(name, value, units)
159
+
160
+ elsif /source/i.match(header.name)
161
+
162
+ manufacturer = values.delete_at(0)
163
+ model = values.delete_at(0)
164
+ serial_no = values.delete_at(0)
165
+ year = values.delete_at(0)
166
+ url = values.delete_at(0)
167
+ component_xml.source_manufacturer = manufacturer
168
+ component_xml.source_model = model
169
+ component_xml.source_serial_no = serial_no
170
+ component_xml.source_year = year
171
+ component_xml.source_url = url
172
+
173
+ elsif /file/i.match(header.name)
174
+
175
+ software_program = values.delete_at(0)
176
+ version = values.delete_at(0)
177
+ filename = values.delete_at(0)
178
+ filetype = values.delete_at(0)
179
+ filepath = values.delete_at(0)
180
+ component_xml.add_file(software_program, version, filepath, filename, filetype)
181
+
182
+ else
183
+ raise "Unknown section #{header.name}"
184
+
185
+ end
186
+
187
+ end
188
+
189
+ taxonomy.check_component(component_xml)
190
+
191
+ component_xml.save_tar_gz(false)
192
+
193
+ end
194
+
195
+ end
196
+
197
+ BCL.gather_components(save_path)
198
+
199
+ end
200
+
201
+ private
202
+
203
+ def parse_xlsx_worksheet(xlsx_worksheet)
204
+
205
+ worksheet = WorksheetStruct.new
206
+ worksheet.name = xlsx_worksheet.Range("A1").Value
207
+ worksheet.components = []
208
+ puts "[ComponentSpreadsheet] Starting parsing components of type #{worksheet.name}"
209
+
210
+ # find number of rows, first column should be name, should not be empty
211
+ num_rows = 1
212
+ while true do
213
+ test = xlsx_worksheet.Range("A#{num_rows}").Value
214
+ if test.nil? or test.empty?
215
+ num_rows -= 1
216
+ break
217
+ end
218
+ num_rows += 1
219
+ end
220
+
221
+ # scan number of columns
222
+ headers = []
223
+ header = nil
224
+ max_col = nil
225
+ xlsx_worksheet.Columns.each do |col|
226
+ value1 = col.Rows("1").Value
227
+ value2 = col.Rows("2").Value
228
+
229
+ if not value1.nil? and not value1.empty?
230
+ if not header.nil?
231
+ headers << header
232
+ end
233
+ header = HeaderStruct.new
234
+ header.name = value1
235
+ header.children = []
236
+ end
237
+
238
+ if not value2.nil? and not value2.empty?
239
+ if not header.nil?
240
+ header.children << value2
241
+ end
242
+ end
243
+
244
+ if (value1.nil? or value1.empty?) and (value2.nil? or value2.empty?)
245
+ break
246
+ end
247
+
248
+ matchdata = /^\$(.+):/.match(col.Address)
249
+ max_col = matchdata[1]
250
+ end
251
+
252
+ if not header.nil?
253
+ headers << header
254
+ end
255
+
256
+ if not headers.empty?
257
+ headers[0].name = "description"
258
+ end
259
+
260
+ components = []
261
+ for i in 3..num_rows do
262
+ component = ComponentStruct.new
263
+ component.row = i
264
+
265
+ # get name
266
+ component.name = xlsx_worksheet.Range("A#{i}").value
267
+
268
+ # get uid, if empty set it
269
+ component.uid = xlsx_worksheet.Range("B#{i}").value
270
+ if component.uid.nil? or component.uid.empty?
271
+ component.uid = UUID.new.generate
272
+ xlsx_worksheet.Range("B#{i}").value = component.uid
273
+ end
274
+
275
+ # always write new version id
276
+ component.version_id = UUID.new.generate
277
+ xlsx_worksheet.Range("C#{i}").value = component.version_id
278
+
279
+ component.headers = headers
280
+ component.values = xlsx_worksheet.Range("A#{i}:#{max_col}#{i}").value
281
+ worksheet.components << component
282
+ end
283
+
284
+ @worksheets << worksheet
285
+
286
+ puts "[ComponentSpreadsheet] Finished parsing components of type #{worksheet.name}"
287
+
288
+ end
289
+
290
+ end
291
+
292
+ end # module BCL
@@ -0,0 +1,451 @@
1
+ ######################################################################
2
+ # Copyright (c) 2008-2010, 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
+ # Provides programmatic access to the component.xsd schema needed for
21
+ # generating the component information that will be uploaded to
22
+ # the Building Component Library.
23
+
24
+ require 'rubygems'
25
+
26
+ require 'pathname'
27
+ require 'csv'
28
+ require 'builder' #gem install builder (creates xml files)
29
+ require 'uuid' # gem install uuid
30
+ require 'fileutils'
31
+
32
+ require 'bcl/TarBall'
33
+
34
+ module BCL
35
+
36
+ SCHEMA_LOCATION = "component.xsd"
37
+
38
+ ProvStruct = Struct.new(:author, :datetime, :comment)
39
+ TagsStruct = Struct.new(:descriptor)
40
+ AttrStruct = Struct.new(:name, :value, :datatype, :units)
41
+ FileStruct = Struct.new(:version_software_program, :version_id, :fqp_file, :filename, :filetype)
42
+ #cost_type is an enumeration (not enforced) of installation, material, operations and maintenance,
43
+ #variable operations and maintenance, salvage
44
+ CostStruct = Struct.new(:cost_name, :cost_type, :category, :value, :interval,
45
+ :interval_units, :year, :location, :units, :currency, :source,
46
+ :reference_component_name, :reference_component_id)
47
+ ObjectStruct = Struct.new(:obj_type, :obj_instance)
48
+
49
+ class Component
50
+ attr_accessor :name
51
+ attr_accessor :uid
52
+ attr_accessor :comp_version_id
53
+ attr_accessor :description
54
+ attr_accessor :comment
55
+ attr_accessor :fidelity_level
56
+ attr_accessor :source_manufacturer
57
+ attr_accessor :source_model
58
+ attr_accessor :source_serial_no
59
+ attr_accessor :source_year
60
+ attr_accessor :source_url
61
+ attr_accessor :tags
62
+ attr_accessor :provenance
63
+ attr_accessor :attributes
64
+ attr_accessor :files
65
+ attr_accessor :costs
66
+ attr_accessor :objects
67
+
68
+ public
69
+
70
+ #the save path is where the component will be saved
71
+ def initialize(save_path)
72
+ @name = "" #this is also a unique identifier to the component...
73
+ @uid = UUID.new.generate
74
+ @comp_version_id = UUID.new.generate
75
+ @description = ""
76
+ @comment = ""
77
+ @fidelity_level = 0 #restricted to level_1 to level_5
78
+ @source_manufacturer = ""
79
+ @source_model = ""
80
+ @source_serial_no = ""
81
+ @source_year = ""
82
+ @source_url = ""
83
+
84
+ #these items have multiple instances
85
+ @provenance = []
86
+ @tags = []
87
+ @attributes = []
88
+ @files = []
89
+ @costs = []
90
+ @objects = [] #container for saving the idf/osm snippets
91
+
92
+ @path = save_path
93
+
94
+ #puts "[ComponentXml] " + @path
95
+ #need to hit a webservice to validate which tags and attributes are
96
+ #available (including units?)
97
+
98
+ #todo: validate against master taxonomy
99
+ end
100
+
101
+ def open_component(filename)
102
+ read_component_xml(filename)
103
+ end
104
+
105
+ # savefile, save the component xml along with
106
+ # the files that have been added to the object
107
+ def save_tar_gz(delete_files = true)
108
+ current_d = Dir.pwd
109
+ paths = []
110
+
111
+ save_component_xml
112
+
113
+ paths << "./component.xml"
114
+
115
+ #copy over the files to the directory
116
+ @files.each do |file|
117
+ src_path = Pathname.new(file.fqp_file)
118
+ dest_path = Pathname.new("#{resolve_path}/#{file.filename}")
119
+ if File.exists?(src_path)
120
+ if src_path == dest_path
121
+ #do nothing, file is already where it needs to be
122
+ else
123
+ #move the file where it needs to go
124
+ FileUtils.cp(src_path, dest_path)
125
+ end
126
+ else
127
+ puts "#{src_path} -> File does not exist"
128
+ end
129
+ paths << "./#{file.filename}"
130
+ end
131
+
132
+ #take all the files and tar.gz them -- name the file the same as
133
+ #the directory
134
+
135
+ Dir.chdir("#{resolve_path}")
136
+ destination = "#{@name.gsub(" ","_").gsub("/","_").gsub("-","_").gsub("___","_").gsub("__","_").gsub("in.","in").gsub(",","").strip}.tar.gz"
137
+
138
+ File.delete(destination) if File.exists?(destination)
139
+
140
+ BCL.tarball(destination, paths)
141
+
142
+ Dir.chdir(current_d)
143
+
144
+ if (delete_files)
145
+ @files.each do |file|
146
+ if File.exists?(File.dirname(file.fqp_file))
147
+ puts "[ComponentXml] Deleting: #{File.dirname(file.fqp_file)}"
148
+ FileUtils.rm_rf(File.dirname(file.fqp_file))
149
+ end
150
+ end
151
+ end
152
+
153
+ #puts "[ComponentXml] " + Dir.pwd
154
+ end
155
+
156
+ def add_provenance(author, datetime, comment)
157
+ prov = ProvStruct.new
158
+ prov.author = author
159
+ prov.datetime = datetime
160
+ prov.comment = comment
161
+
162
+ @provenance << prov
163
+ end
164
+
165
+ def add_tag(tag_name)
166
+ tag = TagsStruct.new
167
+ tag.descriptor = tag_name
168
+
169
+ @tags << tag
170
+ end
171
+
172
+ def add_attribute(name, value, units)
173
+ attr = AttrStruct.new
174
+ attr.name = name
175
+ attr.value = value
176
+
177
+ if value.is_a? Fixnum
178
+ attr.datatype = "int"
179
+ elsif value.is_a? Float
180
+ attr.datatype = "float"
181
+ else
182
+ attr.datatype = "string"
183
+ end
184
+
185
+ attr.units = units
186
+
187
+ @attributes << attr
188
+ end
189
+
190
+ def add_file(version_sp, version_id, fqp_file, filename, filetype)
191
+ fs = FileStruct.new
192
+ fs.version_software_program = version_sp
193
+ fs.version_id = version_id
194
+ fs.fqp_file = fqp_file
195
+ fs.filename = filename
196
+ fs.filetype = filetype
197
+
198
+ @files << fs
199
+ end
200
+
201
+
202
+ def add_cost(cost_name, cost_type, category, value, units, interval, interval_units, year, location, currency,
203
+ source, reference_component_name, reference_component_id)
204
+ cs = CostStruct.new
205
+ cs.cost_name = cost_name
206
+ cs.cost_type = cost_type
207
+ cs.category = category
208
+ cs.value = value
209
+ cs.interval = interval
210
+ cs.interval_units = interval_units
211
+ cs.year = year
212
+ cs.location = location
213
+ cs.units = units
214
+ cs.currency = currency
215
+ cs.source = source
216
+ cs.reference_component_name = reference_component_name
217
+ cs.reference_component_id = reference_component_id
218
+
219
+ @costs << cs
220
+ end
221
+
222
+ def add_object(object_type, object_instance)
223
+ ob = ObjectStruct.new
224
+ ob.obj_type = object_type
225
+ ob.obj_instance = object_instance
226
+
227
+ @objects << ob
228
+ end
229
+
230
+ def resolve_path
231
+ FileUtils.mkdir_p(@path) unless File.directory?(@path)
232
+ new_path = "#{@path}/#{name.gsub(" ","_").gsub("/","_").gsub("-","_").gsub("___","_").gsub("__","_").gsub("in.","in").gsub(",","").strip}"
233
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
234
+ result = new_path
235
+ end
236
+
237
+ def osm_resolve_path
238
+ FileUtils.mkdir_p(@path) unless File.directory?(@path)
239
+ new_path = "#{@path}/osm_#{name.gsub(" ","_").gsub("/","_").gsub("-","_").gsub("___","_").gsub("__","_").gsub("in.","in").gsub(",","").strip}"
240
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
241
+ result = new_path
242
+ end
243
+
244
+ def osc_resolve_path
245
+ FileUtils.mkdir_p(@path) unless File.directory?(@path)
246
+ new_path = "#{@path}/osc_#{name.gsub(" ","_").gsub("/","_").gsub("-","_").gsub("___","_").gsub("__","_").gsub("in.","in").gsub(",","").strip}"
247
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
248
+ result = new_path
249
+ end
250
+
251
+ def resolve_component_path(component_type)
252
+ FileUtils.mkdir_p(@path) unless File.directory?(@path)
253
+ new_path = @path + '/OpenStudio'
254
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
255
+ new_path = new_path + "/#{component_type}"
256
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
257
+ return new_path
258
+ end
259
+
260
+ def tmp_resolve_path
261
+ FileUtils.mkdir_p(@path) unless File.directory?(@path)
262
+ new_path = "#{@path}/tmp_#{name.gsub(" ","_").gsub("/","_").gsub("-","_").gsub("___","_").gsub("__","_").gsub("in.","in").gsub(",","").strip}"
263
+ FileUtils.mkdir_p(new_path) unless File.directory?(new_path)
264
+ result = new_path
265
+ end
266
+
267
+
268
+ def create_os_component(osobj)
269
+ osobj.getCostLineItems.each do |os|
270
+ @costs.each do |cost|
271
+ #figure out costs for constructions
272
+ os.setMaterialCost(cost.value.to_f) if cost.category == "material"
273
+ if cost.category == "installation"
274
+ os.setInstallationCost(cost.value.to_f)
275
+ os.setExpectedLife(cost.interval.to_i)
276
+ end
277
+ os.setFixedOM(cost.value.to_f) if cost.category == "operations and maintenance"
278
+ os.setVariableOM(cost.value.to_f) if cost.category == "variable operations and maintenance"
279
+ os.setSalvageCost(cost.value.to_f) if cost.category == "salvage"
280
+ end
281
+ end
282
+ newcomp = osobj.createComponent
283
+
284
+ cd = newcomp.componentData
285
+ cd.setDescription(@description)
286
+ cd.setFidelityLevel(@fidelity_level)
287
+
288
+ at = newcomp.componentData.componentDataAttributes
289
+ @attributes.each do |attrib|
290
+ if (attrib.value.to_s != "") and (attrib.name.to_s != "")
291
+ if attrib.units != ""
292
+ at.addAttribute(tc(attrib.name), attrib.value, attrib.units)
293
+ else
294
+ at.addAttribute(tc(attrib.name), attrib.value)
295
+ end
296
+ end
297
+ end
298
+
299
+ tg = newcomp.componentData.componentDataTags
300
+ comp_tag = ""
301
+ @tags.each do |tag|
302
+ tg.addTag(tc(tag.descriptor))
303
+ if (tag.descriptor != "energyplus") and (tag.descriptor != "construction")
304
+ #create a map of component tags to directories
305
+ comp_tag = tag.descriptor
306
+ if comp_tag == "interior wall"
307
+ comp_tag = "interiorwalls"
308
+ elsif comp_tag == "exterior wall"
309
+ comp_tag = "exteriorwalls"
310
+ elsif comp_tag == "exterior slab"
311
+ comp_tag = "exteriorslabs"
312
+ elsif comp_tag == "exposed floor"
313
+ comp_tag = "exposedfloors"
314
+ elsif comp_tag == "attic floor"
315
+ comp_tag = "atticfloors"
316
+ elsif comp_tag == "roof"
317
+ comp_tag = "roofs"
318
+ elsif comp_tag == "door"
319
+ comp_tag = "doors"
320
+ elsif comp_tag == "skylight"
321
+ comp_tag = "skylights"
322
+ elsif comp_tag == "window"
323
+ comp_tag = "windows"
324
+ end
325
+ puts comp_tag
326
+ end
327
+ end
328
+
329
+ return newcomp
330
+ end
331
+
332
+ def save_component_xml(dir_path = resolve_path)
333
+ xmlfile = File.new(dir_path + '/component.xml', 'w')
334
+ comp_xml = Builder::XmlMarkup.new(:target => xmlfile, :indent=>2)
335
+
336
+ #setup the xml file
337
+ comp_xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
338
+ comp_xml.component("xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
339
+ "xsi:noNamespaceSchemaLocation"=>"#{SCHEMA_LOCATION}") {
340
+ comp_xml.name @name
341
+ comp_xml.uid @uid
342
+ comp_xml.version_id @comp_version_id
343
+ comp_xml.description @description if @description != ""
344
+ comp_xml.comment @comment if @comment != ""
345
+ comp_xml.fidelity_level @fidelity_level
346
+
347
+ comp_xml.provenances {
348
+ @provenance.each do |prov|
349
+ comp_xml.provenance {
350
+ comp_xml.author prov.author
351
+ comp_xml.datetime prov.datetime
352
+ comp_xml.comment prov.comment
353
+ }
354
+ end
355
+ }
356
+
357
+ comp_xml.tags {
358
+ @tags.each do |tag|
359
+ comp_xml.tag tag.descriptor
360
+ end
361
+ }
362
+
363
+ comp_xml.attributes {
364
+ @attributes.each do |attrib|
365
+ if (attrib.value.to_s != "") and (attrib.name.to_s != "") then
366
+ comp_xml.attribute {
367
+ comp_xml.name attrib.name
368
+ comp_xml.value attrib.value
369
+ comp_xml.datatype attrib.datatype
370
+ comp_xml.units attrib.units if attrib.units != ""
371
+ }
372
+ end
373
+ end
374
+ }
375
+
376
+ comp_xml.source {
377
+ comp_xml.manufacturer @source_manufacturer if @source_manufacturer != ""
378
+ comp_xml.model @source_model if @source_model != ""
379
+ comp_xml.serial_no @source_serial_no if @source_serial_no != ""
380
+ comp_xml.year @source_year if @source_year != ""
381
+ comp_xml.url @source_url if @source_url != ""
382
+ }
383
+
384
+ if not @files.nil?
385
+ comp_xml.files {
386
+ @files.each do |file|
387
+ comp_xml.file {
388
+ comp_xml.version {
389
+ comp_xml.software_program file.version_software_program
390
+ comp_xml.identifier file.version_id
391
+ }
392
+
393
+ comp_xml.filename file.filename
394
+ comp_xml.filetype file.filetype
395
+ }
396
+ end
397
+ }
398
+ end
399
+
400
+ #check if we should write out costs, don't write if all values are 0 or nil
401
+ #DLM: schema always expects costs
402
+ write_costs = true
403
+ #if not @costs.nil?
404
+ # @costs.each do |cost|
405
+ # if (cost.value.nil?) && (not cost.value == 0)
406
+ # write_costs = true
407
+ # break
408
+ # end
409
+ # end
410
+ #end
411
+
412
+ if write_costs
413
+ comp_xml.costs {
414
+ @costs.each do |cost|
415
+ comp_xml.cost {
416
+ comp_xml.instance_name cost.cost_name
417
+ comp_xml.cost_type cost.cost_type
418
+ comp_xml.category cost.category
419
+ comp_xml.value cost.value
420
+ comp_xml.units cost.units if cost.units != ""
421
+ comp_xml.interval cost.interval if cost.interval != ""
422
+ comp_xml.interval_units cost.interval_units if cost.interval_units != ""
423
+ comp_xml.year cost.year if cost.year != ""
424
+ comp_xml.currency cost.currency if cost.currency != ""
425
+ comp_xml.source cost.source if cost.source != ""
426
+ comp_xml.reference_component_name cost.reference_component_name if cost.reference_component_name != ""
427
+ comp_xml.reference_component_id cost.reference_component_id if cost.reference_component_id != ""
428
+ }
429
+ end
430
+ }
431
+ end
432
+
433
+ }
434
+
435
+ xmlfile.close
436
+ end
437
+
438
+ private
439
+
440
+ #return the title case of the string
441
+ def tc(input)
442
+ val = input.gsub(/\b\w/){$&.upcase}
443
+ if val.downcase == "energyplus"
444
+ val = "EnergyPlus"
445
+ end
446
+ return val
447
+ end
448
+
449
+ end
450
+
451
+ end # module BCL