jfreeze-ruby-gdsii 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG.txt +6 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.txt +113 -0
  4. data/Rakefile +30 -0
  5. data/bin/rgds-debug +43 -0
  6. data/bin/rgds-dump +38 -0
  7. data/bin/rgds-join +98 -0
  8. data/bin/rgds-layers +53 -0
  9. data/bin/rgds-sremove +136 -0
  10. data/bin/rgds-ssplit +113 -0
  11. data/bin/rgds-stats +134 -0
  12. data/bin/rgds-structs +41 -0
  13. data/bin/rgds-tree +167 -0
  14. data/bin/rgds2rb +99 -0
  15. data/lib/gdsii.rb +137 -0
  16. data/lib/gdsii/aref.rb +243 -0
  17. data/lib/gdsii/bnf.rb +309 -0
  18. data/lib/gdsii/boundary.rb +53 -0
  19. data/lib/gdsii/box.rb +65 -0
  20. data/lib/gdsii/byte_order.rb +36 -0
  21. data/lib/gdsii/element.rb +172 -0
  22. data/lib/gdsii/group.rb +98 -0
  23. data/lib/gdsii/library.rb +518 -0
  24. data/lib/gdsii/mixins.rb +378 -0
  25. data/lib/gdsii/node.rb +65 -0
  26. data/lib/gdsii/path.rb +169 -0
  27. data/lib/gdsii/property.rb +108 -0
  28. data/lib/gdsii/record.rb +606 -0
  29. data/lib/gdsii/record/consts.rb +384 -0
  30. data/lib/gdsii/record/datatypes/ascii.rb +145 -0
  31. data/lib/gdsii/record/datatypes/bitarray.rb +101 -0
  32. data/lib/gdsii/record/datatypes/data.rb +111 -0
  33. data/lib/gdsii/record/datatypes/int2.rb +67 -0
  34. data/lib/gdsii/record/datatypes/int4.rb +65 -0
  35. data/lib/gdsii/record/datatypes/nodata.rb +60 -0
  36. data/lib/gdsii/record/datatypes/real4.rb +51 -0
  37. data/lib/gdsii/record/datatypes/real8.rb +120 -0
  38. data/lib/gdsii/sref.rb +61 -0
  39. data/lib/gdsii/strans.rb +133 -0
  40. data/lib/gdsii/structure.rb +352 -0
  41. data/lib/gdsii/text.rb +203 -0
  42. data/pkg/ruby-gdsii.gem +46 -0
  43. data/samples/hello.gds +0 -0
  44. data/samples/hello.out.rb +84 -0
  45. data/samples/hello.rb +94 -0
  46. data/test/baseline/dcp1.gds +0 -0
  47. data/test/baseline/h_write.gds +0 -0
  48. data/test/h_pthru.rb +22 -0
  49. data/test/h_write.rb +117 -0
  50. data/test/hs_pthru.rb +31 -0
  51. data/test/l_pthru.rb +23 -0
  52. data/test/test_gds_group.rb +379 -0
  53. data/test/test_gds_record.rb +99 -0
  54. metadata +117 -0
@@ -0,0 +1,120 @@
1
+ require 'gdsii/record/datatypes/data.rb'
2
+
3
+ module Gdsii
4
+
5
+ module RecData
6
+
7
+ #
8
+ # Class for REAL8 data type
9
+ #
10
+ class Real8 < Data
11
+
12
+ # Value is an array of floating point numbers
13
+ attr_reader :value
14
+
15
+ # Construct an Gdsii::RecData::Real8 data object. The value is an array
16
+ # of floating point numbers (Float).
17
+ def initialize(value)
18
+ super(GDT_REAL8, value)
19
+ end
20
+
21
+ # Set the value for this object; verify that the value items are of
22
+ # type Float (or at least can be coerced using "to_f").
23
+ def value=(value)
24
+ @value = Data.coerce_value(value, Float, :to_f)
25
+ end
26
+
27
+ # Returns the size of the record *data* in bytes. Each array element
28
+ # consumes 8 bytes (hence REAL8).
29
+ def byte_size()
30
+ @value.length * 8
31
+ end
32
+
33
+ # Reads a REAL8 record from the given file and for the length of bytes
34
+ # given and returns a new Gdsii::RecData::Real8 object.
35
+ def Real8.read(file, byte_count)
36
+ data = []
37
+ while (byte_count > 0)
38
+
39
+ # read the first byte and get sign and exponent values from it
40
+ raw = file.read(1)
41
+ sign_val = raw.unpack('B')[0].to_i
42
+ exponent = raw.unpack('C')[0]
43
+ exponent -= (sign_val == 0) ? 64 : 192 # exponent is in Excess 64 fmt
44
+
45
+ # read the rest of the real number - save as binary
46
+ raw = file.read(7)
47
+ mant_binary = raw.unpack('b*')[0]
48
+
49
+ # convert mantissa from binary to decimal
50
+ mantissa = 0.0
51
+ (1...8).each do |i|
52
+ str = mant_binary[(i-1)*8,8]
53
+ ub = [("0"*32+str.reverse.to_s)[-32..-1]].pack("B32").unpack("N")[0]
54
+ mantissa += ub / (256.0 ** i)
55
+ end
56
+ real = mantissa * (16**exponent)
57
+ real = -real if (sign_val != 0)
58
+ data.push real
59
+
60
+ byte_count -= 8
61
+
62
+ end
63
+
64
+ Real8.new(data)
65
+ end
66
+
67
+ # Writes the integer values in this Gdsii::RecData::Real8 object to the
68
+ # given file as a GDSII REAL8 record.
69
+ def write(file)
70
+
71
+ self.value.each do |item|
72
+
73
+ if item == 0 then
74
+ file.write "\x00" * 8
75
+ else
76
+ # Note: process differently for big endian?
77
+ bit_str = [item].pack('G').unpack('B*')[0]
78
+
79
+ # get the sign and expt (IEEE)
80
+ sign = bit_str[0,1]
81
+ expt = ['00000'+bit_str[1,11]].pack('B16').unpack('n')[0] - 1023
82
+
83
+ # Divide by 4 because 16**x == (2**4)**x == 2**(4*x)
84
+ b16_expt = (expt / 4).floor
85
+ b16_rem = expt % 4
86
+
87
+ # Shift the mantissa 4 bits to the right and increment the expt
88
+ b16_expt += 1
89
+ mant = ['0', '0', '0', '1', bit_str[12..63].split('')].flatten
90
+
91
+ # Shift the mantissa to left to take care of the expt remainder
92
+ (0...b16_rem).each do |i|
93
+ mant.shift
94
+ mant.push '0'
95
+ end
96
+
97
+ # Bias the base-16 expoent
98
+ b16_expt += 64
99
+
100
+ # Convert the expt to a 7-bit binary string
101
+ b16_expt_str = [b16_expt].pack('C').unpack('B*')[0][1..7]
102
+
103
+ # Now assemble the sign, expt and mantissa
104
+ real8_fmt = sign + b16_expt_str + mant.join('')
105
+ file.write [real8_fmt].pack('B*')
106
+ end
107
+
108
+ end
109
+ end
110
+
111
+ # Converts the array of floating point values to a string (values are
112
+ # joined by spaces).
113
+ def to_s()
114
+ value.map {|v| v.to_s}.join(' ')
115
+ end
116
+
117
+ end
118
+ end
119
+ end
120
+
data/lib/gdsii/sref.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'gdsii/group'
2
+ require 'gdsii/element'
3
+ require 'gdsii/strans'
4
+
5
+ module Gdsii
6
+
7
+ #
8
+ # Represents a GDSII structure reference (SRef) element. Most
9
+ # methods are from Element or from the various included Access module
10
+ # methods.
11
+ #
12
+ class SRef < Element
13
+
14
+ # Include various record accessors
15
+ include Access::XY
16
+ include Access::ELFlags
17
+ include Access::Plex
18
+ include Access::StransGroup
19
+ include Access::Sname
20
+
21
+ #
22
+ # SRef BNF description:
23
+ #
24
+ # <sref> ::= SREF [ELFLAGS] [PLEX] SNAME [<strans>] XY
25
+ #
26
+ self.bnf_spec = BnfSpec.new(
27
+ BnfItem.new(GRT_SREF),
28
+ BnfItem.new(GRT_ELFLAGS, true),
29
+ BnfItem.new(GRT_PLEX, true),
30
+ BnfItem.new(GRT_SNAME),
31
+ BnfItem.new(Strans, true),
32
+ BnfItem.new(GRT_XY),
33
+ BnfItem.new(Properties, true),
34
+ BnfItem.new(GRT_ENDEL)
35
+ )
36
+
37
+ #
38
+ # Create a structure reference (SREF) within a Structure object (also
39
+ # known as a structure "instantiation").
40
+ #
41
+ # struct1 = Gdsii::Structure.new('top')
42
+ # struct2 = Gdsii::Structure.new('sub')
43
+ # struct1.add SRef.new('sub')
44
+ #
45
+ # Alternatively, any object with a #to_s method can be passed and the
46
+ # #to_s method will be used to coerce the object into a string. For
47
+ # example, a structure object itself can be used (instead of the structure
48
+ # name) through Structure#to_s:
49
+ #
50
+ # struct1.add SRef.new(struct2)
51
+ #
52
+ def initialize(sname=nil, xy=nil)
53
+ super()
54
+ @records[GRT_SREF] = Record.new(GRT_SREF)
55
+ self.sname = sname.to_s unless sname.nil?
56
+ self.xy = xy unless xy.nil?
57
+ yield self if block_given?
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,133 @@
1
+ require 'gdsii/group'
2
+
3
+ module Gdsii
4
+
5
+ #
6
+ # Represents a GDSII structure translation object (Strans).
7
+ #
8
+ class Strans < Group
9
+
10
+ #
11
+ # Strans BNF description:
12
+ #
13
+ # <strans> ::= STRANS [MAG] [ANGLE]
14
+ #
15
+ self.bnf_spec = BnfSpec.new(
16
+ BnfItem.new(GRT_STRANS),
17
+ BnfItem.new(GRT_MAG, true),
18
+ BnfItem.new(GRT_ANGLE, true)
19
+ )
20
+
21
+ # Constructor
22
+ def initialize(mag=nil, angle=nil, reflect_x=false, abs_mag=false, abs_angle=false)
23
+ super()
24
+ @records[GRT_STRANS] = Record.new(GRT_STRANS, 0)
25
+ self.reflect_x = true if reflect_x
26
+ self.abs_mag = true if abs_mag
27
+ self.abs_angle = true if abs_angle
28
+ self.mag = mag unless mag.nil?
29
+ self.angle = angle unless angle.nil?
30
+ yield self if block_given?
31
+ end
32
+
33
+ #
34
+ # Get the strans bitarray (returns Record)
35
+ #
36
+ def record() @records.get(GRT_STRANS); end
37
+
38
+ #
39
+ # Get the strans bitarray data (returns Fixnum). The recommendation is to
40
+ # not access this directly but rather use the various bitwise query
41
+ # methods instead: #reflect_x?, #abs_angle?, #abs_mag?.
42
+ #
43
+ def value() @records.get_data(GRT_STRANS); end
44
+
45
+ #
46
+ # Set the strans bitarray record. The recommendation is to not access
47
+ # this directly but rather use the various bitwise manipulation methods
48
+ # instead: #reflect_x=, #abs_angle=, #abs_mag=.
49
+ #
50
+ # * 15 = reflect_x
51
+ # * 2 = abs_mag
52
+ # * 1 = abs_angle
53
+ # * All others reserved for future use.
54
+ #
55
+ def value=(val)
56
+ @records.set(GRT_STRANS,val);
57
+ end
58
+
59
+ #
60
+ # Get the strans angle (returns Record)
61
+ #
62
+ def angle_record() @records.get_data(GRT_ANGLE); end
63
+
64
+ #
65
+ # Get the strans angle value (returns Fixnum)
66
+ #
67
+ def angle() @records.get_data(GRT_ANGLE); end
68
+
69
+ #
70
+ # Set the strans angle record
71
+ #
72
+ def angle=(val) @records.set(GRT_ANGLE,val); end
73
+
74
+ #
75
+ # Get the strans magnification (returns Record)
76
+ #
77
+ def mag_record() @records.get_data(GRT_MAG); end
78
+
79
+ #
80
+ # Get the strans magnification value (returns Fixnum)
81
+ #
82
+ def mag() @records.get_data(GRT_MAG); end
83
+
84
+ #
85
+ # Set the strans magnification record
86
+ #
87
+ def mag=(val) @records.set(GRT_MAG,val); end
88
+
89
+ #
90
+ # Return true if the translation bitarray indicates that a reflection
91
+ # about the x-axis is set.
92
+ #
93
+ def reflect_x?()
94
+ (value & 0x8000) == 0x8000
95
+ end
96
+
97
+ #
98
+ # Set or clear the strans x-reflect bit (true = set; false = clear)
99
+ #
100
+ def reflect_x=(flag)
101
+ self.value = flag ? value | 0x8000 : value & 0x7fff
102
+ end
103
+
104
+ #
105
+ # Return true if an absolute magnification is set; false if not
106
+ #
107
+ def abs_mag?()
108
+ (value & 0x0004) == 0x0004
109
+ end
110
+
111
+ #
112
+ # Set or clear the absolute magnification bit (true = set; false = clear)
113
+ #
114
+ def abs_mag=(flag)
115
+ self.value = flag ? value | 0x0004 : value & 0xfffb
116
+ end
117
+
118
+ #
119
+ # Return true if the absolute angle bit is set; false if not
120
+ #
121
+ def abs_angle?()
122
+ (value & 0x0002) == 0x0002
123
+ end
124
+
125
+ #
126
+ # Set the strans absAngle bit
127
+ #
128
+ def abs_angle=(flag)
129
+ self.value = flag ? value | 0x0002 : value & 0xfffd
130
+ end
131
+
132
+ end
133
+ end
@@ -0,0 +1,352 @@
1
+ require 'time'
2
+ require 'gdsii/mixins'
3
+ require 'gdsii/element'
4
+ require 'gdsii/boundary'
5
+ require 'gdsii/path'
6
+ require 'gdsii/text'
7
+ require 'gdsii/node'
8
+ require 'gdsii/box'
9
+ require 'gdsii/sref'
10
+ require 'gdsii/aref'
11
+
12
+ module Gdsii
13
+
14
+ #
15
+ # Represents a GDSII Structure.
16
+ #
17
+ class Structure < Group
18
+
19
+ include Access::GdsiiTime
20
+
21
+ #
22
+ # Structure BNF description:
23
+ #
24
+ # <structure> ::= BGNSTR STRNAME [STRCLASS] {<element>}* ENDSTR
25
+ # <element> ::= {<boundary> | <path> | <sref> | <aref> |
26
+ # <text> | <node> | <box>} {<property>}* ENDEL
27
+ #
28
+ self.bnf_spec = BnfSpec.new(
29
+ BnfItem.new(GRT_BGNSTR),
30
+ BnfItem.new(GRT_STRNAME),
31
+ BnfItem.new(GRT_STRCLASS, true),
32
+ BnfItem.new(Elements, true),
33
+ BnfItem.new(GRT_ENDSTR)
34
+ )
35
+
36
+ #
37
+ # Creates a Structure object. Various GDSII Elements are added to a
38
+ # structure such as a Boundary, Path, SRef, ARef, Text, Node, and Box.
39
+ #
40
+ # str_sub = Structure.new('sub')
41
+ # str_top = Structure.new('top')
42
+ # str_top.add SRef.new(str_sub)
43
+ # str_top.add Boundary.new(1, 0, [0,0, 0,10, 10,10, 10,0, 0,0])
44
+ #
45
+ def initialize(name=nil)
46
+ # Create the record grouping
47
+ super()
48
+ @records[Elements] = Elements.new
49
+ @records[GRT_ENDSTR] = Record.new(GRT_ENDSTR)
50
+
51
+ # set the name
52
+ self.name = name unless name.nil?
53
+
54
+ # Set create/modify time to the current time
55
+ now = Time.now
56
+ self.create_time = now
57
+ self.modify_time = now
58
+
59
+ yield self if block_given?
60
+ end
61
+
62
+ #
63
+ # Access to the Elements object. See Elements for a listing of methods.
64
+ #
65
+ def elements(); @records.get(Elements); end
66
+
67
+ #
68
+ # Shortcut for Elements#add. For example, instead of:
69
+ #
70
+ # struct = Structure.new('test')
71
+ # struct.elements.add Text(1, 0, [0,0], 'hello')
72
+ #
73
+ # It could be simply:
74
+ #
75
+ # struct.add Text(1, 0, [0,0], 'hello')
76
+ #
77
+ def add(*args); elements.add(*args); end
78
+
79
+ #
80
+ # Shortcut for Elements#remove. For example, instead of:
81
+ #
82
+ # struct.elements.remove {|e| e.class == Gdsii::Text}
83
+ #
84
+ # It could be simply:
85
+ #
86
+ # struct.remove {|e| e.class == Gdsii::Text}
87
+ #
88
+ def remove(*args); elements.remove(*args); end
89
+
90
+ #
91
+ # Get the Structure STRNAME record (returns Record).
92
+ #
93
+ def name_record() @records.get(GRT_STRNAME); end
94
+
95
+ #
96
+ # Get the Structure name (returns String).
97
+ #
98
+ def name() @records.get_data(GRT_STRNAME); end
99
+
100
+ #
101
+ # Set the Structure name.
102
+ #
103
+ def name=(val) @records.set(GRT_STRNAME, val); end
104
+
105
+ #
106
+ # Get the strclass record (returns Record).
107
+ #
108
+ def strclass_record() @records.get(GRT_STRCLASS); end
109
+
110
+ #
111
+ # Get the strclass bitarray number (returns Fixnum).
112
+ #
113
+ def strclass() @records.get_data(GRT_STRCLASS); end
114
+
115
+ #
116
+ # Set the strclass bitarray number. According to the GDSII specification,
117
+ # this is only to be used by Calma - otherwise it should be omitted or
118
+ # set to 0. It is probably a good idea to not touch this property.
119
+ #
120
+ def strclass=(val) @records.set(GRT_STRCLASS, val); end
121
+
122
+ #
123
+ # Get the bgnstr record (returns Record).
124
+ #
125
+ def bgnstr_record() @records.get(GRT_BGNSTR); end
126
+
127
+ #
128
+ # Get the bgnstr number (returns Fixnum). This holds the create/modify
129
+ # time stamp for the structure. It is probably easier to not access this
130
+ # directly but to use #create_time and #modify_time instead.
131
+ #
132
+ def bgnstr() @records.get_data(GRT_BGNSTR); end
133
+
134
+ #
135
+ # Set the bgnstr number. The value is a Fixnum representation of the
136
+ # create/modify time stamp for the structure. It is probably easier to
137
+ # not access this directly but to use #create_time= and #modify_time=
138
+ # instead.
139
+ #
140
+ def bgnstr=(val) @records.set(GRT_BGNSTR, val); end
141
+
142
+ #
143
+ # Accepts a Time object and sets the create time for the structure.
144
+ #
145
+ # struct.create_time = Time.now
146
+ #
147
+ def create_time=(time)
148
+ @create_time = time
149
+ update_times
150
+ end
151
+
152
+ #
153
+ # Returns the create time for this structure (returns Time)
154
+ #
155
+ def create_time(); @create_time; end
156
+
157
+ #
158
+ # Accepts a Time object and sets the modify time for the structure.
159
+ #
160
+ # struct.modify_time = Time.now
161
+ #
162
+ def modify_time=(time)
163
+ @modify_time = time
164
+ update_times
165
+ end
166
+
167
+ #
168
+ # Returns the modify time for this structure (returns Time)
169
+ #
170
+ def modify_time(); @modify_time; end
171
+
172
+ #
173
+ # Reads records related to a Structure header from the given file handle.
174
+ # It is assumed that the file position is already at BGNSTR (likely after
175
+ # Library#read_header). The structure is yielded (if desired) and
176
+ # returned. The iterative version of this method is likely preferable
177
+ # in most cases (Structure#read_each_header).
178
+ #
179
+ def Structure.read_header(file)
180
+ Structure.read(file, nil, nil, :before_group) {|struct|
181
+ yield struct if block_given?
182
+ break struct
183
+ }
184
+ end
185
+
186
+ #
187
+ # Reads each Structure and its Elements from the given file handle. Each
188
+ # Structure is yielded after the entire Structure is read from the file.
189
+ # Compare this with Structure#read_each_header which might be more
190
+ # efficient, more streamlined, and consume less memory.
191
+ #
192
+ # The Library#read_header method must be called as a prerequisite (the file
193
+ # handle must be at a BGNSTR record).
194
+ #
195
+ # File.open(file_name, 'rb') do |file|
196
+ # Library.read_header(file) do |lib|
197
+ # Structure.read_each(file) do |struct|
198
+ # puts "#{struct.name} has #{struct.elements.length} elements"
199
+ # end
200
+ # end
201
+ # end
202
+ #
203
+ def Structure.read_each(file)
204
+ while (Record.peek(file).type == GRT_BGNSTR) do
205
+ yield Structure.read(file)
206
+ end
207
+ end
208
+
209
+ #
210
+ # Reads the Structure header records from a file handle but without reading
211
+ # any Element records. The resulting Structure element is yielded. This
212
+ # is useful for using the high-level GDSII access methods as a stream file
213
+ # is being read in.
214
+ #
215
+ # Prior to using this method, the file position must be at the first
216
+ # structure definition (i.e. after the Library header). The best method
217
+ # to do this is to call Library#read_header first.
218
+ #
219
+ # Also, you _MUST_ advance the file position to the next structure record
220
+ # header (BGNSTR) either with Structure#seek_next or
221
+ # Structure#read_each_element within the code block. Otherwise the file
222
+ # pointer will not be advanced properly and only the first Structure will
223
+ # be yielded.
224
+ #
225
+ # File.open(file_name, 'rb') do |file|
226
+ # Library.read_header(file) do |lib|
227
+ # Structure.read_each_header(file) do |struct|
228
+ # if struct.name == 'TOP'
229
+ # # Show all elements in structure "TOP"
230
+ # puts "Processing structure #{struct.name}"
231
+ # Element.read_each(file) do |element|
232
+ # puts "--> Element: #{element.class}"
233
+ # end
234
+ # else
235
+ # # Skip past elements in other blocks
236
+ # puts "Ignoring structure #{struct.name}"
237
+ # Structure.seek_next(file)
238
+ # end
239
+ # end
240
+ # end
241
+ # end
242
+ #
243
+ def Structure.read_each_header(file)
244
+ while (Record.peek(file).type == GRT_BGNSTR) do
245
+ yield Structure.read_header(file)
246
+ end
247
+ end
248
+
249
+ #
250
+ # Reads from the given file handle until a ENDSTR record is met (presumably
251
+ # to a BGNSTR or ENDLIB record. This effectively "skips" past all elements
252
+ # within a structure and prepares the file handle to read the next
253
+ # structure in the file (or ENDLIB if at the end of the GDSII library).
254
+ # See Structure#read_each_header for an example. The new file position is
255
+ # returned.
256
+ #
257
+ # Compare this with Element#read_each which accomplishes the same thing
258
+ # but instead yields each element as it is read from the file.
259
+ #
260
+ def Structure.seek_next(file)
261
+ Record.read_each(file) do |record|
262
+ break file.pos if record.is_endstr?
263
+ end
264
+ nil
265
+ end
266
+
267
+ #
268
+ # Writes only the header portion of the Structure to a file (no elements).
269
+ # This is useful when streamlined writing is desired (for better
270
+ # performance or when writing GDSII as another GDSII is being read). Be
271
+ # sure to either:
272
+ #
273
+ # 1. Call #write_footer after writing the header and any Element
274
+ # objects. Also be sure to wrap this around Library#write_header and
275
+ # Library#write_footer; Or
276
+ # 2. Pass a block whereupon the footer will automatically be added after
277
+ # the block is processed.
278
+ #
279
+ # See Library#write_header for an example.
280
+ #
281
+ def write_header(file)
282
+ # alter the BNF to exclude Elements and ENDSTR; then write to file
283
+ # according to the modified BNF
284
+ alt_bnf = BnfSpec.new(*Structure.bnf_spec.bnf_items[0..-3])
285
+ self.write(file, alt_bnf)
286
+
287
+ # if block is given, then yield to that block and then write the
288
+ # footer afterwards
289
+ if block_given?
290
+ yield
291
+ self.write_footer(file)
292
+ end
293
+ end
294
+
295
+ #
296
+ # Writes only the Structure footer (just ENDSTR record) to file. To be
297
+ # used with #write_header.
298
+ #
299
+ def write_footer(file)
300
+ Record.new(GRT_ENDSTR).write(file)
301
+ end
302
+
303
+ #####################
304
+ ## PRIVATE METHODS ##
305
+ #####################
306
+
307
+ private
308
+
309
+ # Used by #create_time and #modify_time
310
+ def update_times()
311
+ if create_time and modify_time
312
+ self.bgnstr = build_time(create_time) + build_time(modify_time)
313
+ else
314
+ self.bgnstr = nil
315
+ end
316
+ end
317
+
318
+ end
319
+
320
+ ############################################################################
321
+
322
+ #
323
+ # Class to hold a collection of Structure objects. This is used in the
324
+ # Library class BNF.
325
+ #
326
+ class Structures < Group
327
+
328
+ include Access::EnumerableGroup
329
+
330
+ #
331
+ # Structures BNF description:
332
+ #
333
+ # <structures> ::= {<structure>}*
334
+ # <structure> ::= {<boundary> | <path> | <sref> | <aref> |
335
+ # <text> | <node> | <box>} {<property>}* ENDEL
336
+ #
337
+ self.bnf_spec = BnfSpec.new(
338
+ BnfItem.new(Structure, true, true)
339
+ )
340
+
341
+ #
342
+ # Create an Structures object.
343
+ #
344
+ def initialize(structures=[])
345
+ super()
346
+ @records[Structure] = @list = structures
347
+ end
348
+
349
+ end
350
+
351
+
352
+ end