line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package FrameMaker::MifTree; |
2
|
|
|
|
|
|
|
# $Id: MifTree.pm 2 2006-05-02 11:15:26Z roel $ |
3
|
3
|
|
|
3
|
|
97861
|
use 5.008_001; # minimum version for Unicode support |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
140
|
|
4
|
3
|
|
|
3
|
|
18
|
use strict; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
113
|
|
5
|
3
|
|
|
3
|
|
17
|
use warnings; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
135
|
|
6
|
3
|
|
|
3
|
|
25
|
use warnings::register; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
608
|
|
7
|
3
|
|
|
3
|
|
112
|
use Carp; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
311
|
|
8
|
3
|
|
|
3
|
|
4392
|
use File::Temp; |
|
3
|
|
|
|
|
85169
|
|
|
3
|
|
|
|
|
255
|
|
9
|
3
|
|
|
3
|
|
2693
|
use IO::Tokenized ':parse'; # These are... |
|
3
|
|
|
|
|
6782
|
|
|
3
|
|
|
|
|
817
|
|
10
|
3
|
|
|
3
|
|
3000
|
use IO::Tokenized::File; # ... from CPAN. |
|
3
|
|
|
|
|
7728
|
|
|
3
|
|
|
|
|
155
|
|
11
|
3
|
|
|
3
|
|
9987
|
use IO::Tokenized::Scalar; # Subclass in IO::Tokenized::File style, |
|
3
|
|
|
|
|
23
|
|
|
3
|
|
|
|
|
148
|
|
12
|
|
|
|
|
|
|
# uses IO::Scalar from the IO::Stringy bundle. |
13
|
3
|
|
|
3
|
|
4485
|
use Tree::DAG_Node 1.04; # Get this module from CPAN. |
|
3
|
|
|
|
|
76737
|
|
|
3
|
|
|
|
|
187
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
FrameMaker::MifTree - A MIF Parser |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 VERSION |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
This document describes version 0.075, released 2 May 2006. |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 SYNOPSIS |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use FrameMaker::MifTree; |
26
|
|
|
|
|
|
|
my $mif = FrameMaker::MifTree->new; |
27
|
|
|
|
|
|
|
$mif->parse_miffile('filename.mif'); |
28
|
|
|
|
|
|
|
@strings = $mif->daughters_by_name('String', recurse => 1); |
29
|
|
|
|
|
|
|
print $strings[0]->string; |
30
|
|
|
|
|
|
|
$strings[3]->string('Just another new string.'); |
31
|
|
|
|
|
|
|
$mif->dump_miffile('newmif.mif'); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 DESCRIPTION |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
The FrameMaker::MifTree class is implemented as a Tree::DAG_Node subclass, and |
36
|
|
|
|
|
|
|
thus inherits all the methods of that class. Two methods are overridden. Please |
37
|
|
|
|
|
|
|
read L to see what other methods are available. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
MIF (Maker Interchange Format) is an Adobe FrameMaker file format in ASCII, |
40
|
|
|
|
|
|
|
consisting of statements that create an easily parsed, readable text file of |
41
|
|
|
|
|
|
|
all the text, graphics, formatting, and layout constructs that FrameMaker |
42
|
|
|
|
|
|
|
understands. Because MIF is an alternative representation of a FrameMaker |
43
|
|
|
|
|
|
|
document, it allows FrameMaker and other applications to exchange information |
44
|
|
|
|
|
|
|
while preserving graphics, document content, and format. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
This document does not tell you what the syntax of a MIF file is, nor does it |
47
|
|
|
|
|
|
|
document the meaning of the MIF statements. For this, please read (and re-read) |
48
|
|
|
|
|
|
|
the MIF_Reference.pdf, provided by Adobe. |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
MifTree not only knows the MIF syntax, but it also has some understanding of |
51
|
|
|
|
|
|
|
the allowed structures (within their contexts) and attribute types. The file |
52
|
|
|
|
|
|
|
FrameMaker/MifTree/MifTreeTags holds all the valid MIF statements and the |
53
|
|
|
|
|
|
|
attribute type for every statement. This file may need some improvement, as it |
54
|
|
|
|
|
|
|
is created by analyzing a large collection of MIF files written by FrameMaker |
55
|
|
|
|
|
|
|
(and an automatic analysis of the I, which showed several typos |
56
|
|
|
|
|
|
|
and inconsistencies in that manual). The current file is for MIF version 7.00. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head2 Dependencies |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
This class implementation depends on the following modules, all available from |
61
|
|
|
|
|
|
|
CPAN: |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=over 4 |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=item * |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
Tree::DAG_Node |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=item * |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
IO::Tokenized and IO::Tokenized::File and the custom-made IO::Tokenized::Scalar |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=item * |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
IO::Stringy (only IO::Scalar is needed) |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=back |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
BEGIN { |
82
|
3
|
|
|
3
|
|
43
|
use Exporter (); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
427
|
|
83
|
3
|
|
|
3
|
|
8
|
our $VERSION = 0.075; |
84
|
3
|
|
|
|
|
96
|
our @ISA = qw(Tree::DAG_Node Exporter); |
85
|
3
|
|
|
|
|
10
|
our @EXPORT = qw("e &unquote &encode_path &decode_path &convert); |
86
|
3
|
|
|
|
|
8
|
our @EXPORT_OK = qw(%fmcharset %fmnamedchars); |
87
|
3
|
|
|
|
|
22235
|
our %EXPORT_TAGS = (); |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
our @EXPORT_OK; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
our (%mifnodes, %mifleaves, %attribute_types, %fmcharset, %fmnamedchars); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
our $use_unicode; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
for my $do (qw(FrameMaker/MifTree/MifTreeTags FrameMaker/MifTree/FmCharset)) { |
96
|
|
|
|
|
|
|
do $do or croak $! || $@; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
our $fm_to_unicode = '$s =~ tr/' . |
99
|
|
|
|
|
|
|
join('', map { sprintf '\x%02x', ord } keys %fmcharset) . '/' . |
100
|
|
|
|
|
|
|
join('', map { sprintf '\x{%04x}', ord } values %fmcharset) . '/'; |
101
|
|
|
|
|
|
|
our $unicode_to_fm = '$s =~ tr/' . |
102
|
|
|
|
|
|
|
join('', map { sprintf '\x{%04x}', ord } values %fmcharset) . '/' . |
103
|
|
|
|
|
|
|
join('', map { sprintf '\x%02x', ord } keys %fmcharset) . '/'; |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
our $default_unit = ''; |
106
|
|
|
|
|
|
|
our @parserdefinition = ( |
107
|
|
|
|
|
|
|
[ COMMENT => qr/#.*/ ], |
108
|
|
|
|
|
|
|
[ RANGLE => qr/>/, sub{''} ], |
109
|
|
|
|
|
|
|
[ MIFTAG => qr/<\s*[a-z][a-z0-9]*/i, sub {(my $m = shift) =~ s/^/; $m;} ], |
110
|
|
|
|
|
|
|
[ ATTRIBS => qr/`.*?'|[^=&>#]+/ ], |
111
|
|
|
|
|
|
|
[ FACET => qr/[=&].+/ ], |
112
|
|
|
|
|
|
|
[ MACRO => qr/define\s*\(.*?\)/ ] |
113
|
|
|
|
|
|
|
); |
114
|
|
|
|
|
|
|
our %unit_to_factor = ( |
115
|
|
|
|
|
|
|
'' => 1 / 72, |
116
|
|
|
|
|
|
|
pt => 1 / 72, |
117
|
|
|
|
|
|
|
point => 1 / 72, |
118
|
|
|
|
|
|
|
q(") => 1, |
119
|
|
|
|
|
|
|
in => 1, |
120
|
|
|
|
|
|
|
mm => 1 / 25.4, |
121
|
|
|
|
|
|
|
millimeter => 1 / 25.4, |
122
|
|
|
|
|
|
|
cm => 1 / 2.54, |
123
|
|
|
|
|
|
|
centimeter => 1 / 2.54, |
124
|
|
|
|
|
|
|
pc => 1 / 6, |
125
|
|
|
|
|
|
|
pica => 1 / 6, |
126
|
|
|
|
|
|
|
dd => 0.01483, |
127
|
|
|
|
|
|
|
didot => 0.01483, |
128
|
|
|
|
|
|
|
cc => 12 * 0.01483, |
129
|
|
|
|
|
|
|
cicero => 12 * 0.01483 |
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head2 Overridden Methods |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=over 4 |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item C |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Adds a list of daughter object to a node. The difference with the DAG_Node |
139
|
|
|
|
|
|
|
method is that it checks for a valid MIF construct. Only the mother/daughter |
140
|
|
|
|
|
|
|
relationship is checked. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=cut |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub add_daughters { |
145
|
|
|
|
|
|
|
# extends functionality of Tree::DAG_Node's sub |
146
|
14
|
|
|
14
|
1
|
28
|
my($mother, @daughters) = @_; |
147
|
|
|
|
|
|
|
|
148
|
14
|
100
|
66
|
|
|
53
|
if (ref $mother && $mother->name) { # only when called on object and if |
149
|
|
|
|
|
|
|
# we know the name of the mother |
150
|
|
|
|
|
|
|
# check for allowed daughters |
151
|
12
|
50
|
33
|
|
|
1483
|
if (warnings::enabled || $^W) { |
152
|
12
|
|
|
|
|
20
|
for my $daughter (@daughters) { |
153
|
12
|
50
|
0
|
|
|
26
|
warnings::warn 'Node "' . ($mother->name || '') . |
|
|
|
0
|
|
|
|
|
154
|
|
|
|
|
|
|
'" does not allow daughter "' . ($daughter->name || '') . '"' |
155
|
|
|
|
|
|
|
unless $mother->allows_daughter($daughter); |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
14
|
|
|
|
|
212
|
$mother->SUPER::add_daughters(@daughters); |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item C |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
The attributes method of the FrameMaker::MifTree class does not require a |
166
|
|
|
|
|
|
|
reference as an attribute, as does the DAG_Node equivalent. As an extra, the |
167
|
|
|
|
|
|
|
method checks if the method is called on a leaf, since the MIF structure does |
168
|
|
|
|
|
|
|
not allow attributes on non-ending nodes. The method reads/sets the raw |
169
|
|
|
|
|
|
|
attribute, no string conversion, path encoding/decoding or value extraction is |
170
|
|
|
|
|
|
|
done. To obtain or set one of those values, use the specific L
|
171
|
|
|
|
|
|
|
Methods> mentioned below. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub attributes { # read/write attribute-method |
176
|
|
|
|
|
|
|
# overrides Tree::DAG_Node's sub -- doesn't carp that 'attributes' needs |
177
|
|
|
|
|
|
|
# to be a ref |
178
|
57
|
|
|
57
|
1
|
85
|
my $this = shift; |
179
|
57
|
50
|
|
|
|
98
|
croak 'Must be called on object' unless ref $this; |
180
|
57
|
100
|
|
|
|
124
|
$this->{attributes} = $_[0] if (@_); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# check if the attribute is valid |
183
|
57
|
50
|
33
|
|
|
5867
|
if ((warnings::enabled || $^W) && ! $this->check_attribute) { |
|
|
|
33
|
|
|
|
|
184
|
0
|
|
|
|
|
0
|
warnings::warn $this->get_attribute_error; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
57
|
|
|
|
|
173
|
return $this->{attributes}; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=back |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head2 Quick Creators |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
The following methods can be used instead of the DAG_Node standard methods to |
195
|
|
|
|
|
|
|
build your MIF structure. It's just a lazy way of adding daughters, but it |
196
|
|
|
|
|
|
|
improves readability of your code if you create something like: |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
my $mif = FrameMaker::MifTree->new->add_node( |
199
|
|
|
|
|
|
|
AFrames => FrameMaker::MifTree->add_node( |
200
|
|
|
|
|
|
|
Frame => FrameMaker::MifTree->add_node( |
201
|
|
|
|
|
|
|
ImportObject => FrameMaker::MifTree->add_leaf( |
202
|
|
|
|
|
|
|
ImportObFileDI => encode_path('c:\bar\foo.eps')) |
203
|
|
|
|
|
|
|
), |
204
|
|
|
|
|
|
|
FrameMaker::MifTree->add_node( |
205
|
|
|
|
|
|
|
ImportObject => FrameMaker::MifTree->add_leaf( |
206
|
|
|
|
|
|
|
ImportObFileDI => encode_path('../../foo/boo.eps')) |
207
|
|
|
|
|
|
|
) |
208
|
|
|
|
|
|
|
) |
209
|
|
|
|
|
|
|
); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=over 4 |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=item C |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
Adds a new daughter to the object. The first argument specifies the name, all |
216
|
|
|
|
|
|
|
the following arguments are taken either as the attribute for the leaf, or as a |
217
|
|
|
|
|
|
|
list of granddaughter objects to add to the newly created daughter. (In MIFTree |
218
|
|
|
|
|
|
|
world, newly born daughters mature in split seconds.) |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=cut |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
sub add_leaf { |
223
|
|
|
|
|
|
|
# same sub to add either a leaf (2nd argument is a scalar) or a node (2nd |
224
|
|
|
|
|
|
|
# argument is a FrameMaker::MifTree object) |
225
|
13
|
|
|
13
|
1
|
27
|
my ($this, $name, @that) = @_; |
226
|
|
|
|
|
|
|
|
227
|
13
|
|
33
|
|
|
30
|
my $class = ref($this) || $this; |
228
|
13
|
|
|
|
|
36
|
my $daughter = $class->new(); |
229
|
13
|
|
|
|
|
363
|
$daughter->name($name); |
230
|
13
|
50
|
33
|
|
|
86
|
if ( ref $that[0] && $that[0]->isa('FrameMaker::MifTree') ) { |
231
|
|
|
|
|
|
|
# assume list of nodes |
232
|
0
|
|
|
|
|
0
|
$daughter->add_daughters(@that); |
233
|
|
|
|
|
|
|
} else { # probably dealing with an attribute for the leaf |
234
|
13
|
|
|
|
|
37
|
$daughter->attributes($that[0]); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
13
|
50
|
|
|
|
50
|
$this->add_daughters($daughter) if (ref $this) ; # called on object |
238
|
|
|
|
|
|
|
|
239
|
13
|
|
|
|
|
816
|
return $daughter; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=item C |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
An exact synonym for the C method. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub add_node { # alias |
249
|
13
|
|
|
13
|
1
|
26
|
my ($it, @them) = @_; |
250
|
13
|
|
|
|
|
24
|
$it->add_leaf(@them); |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=item C |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
Adds a facet to the object. In DAG_Node tree terms, this is implemented as a |
256
|
|
|
|
|
|
|
leaf with the name "_facet" and a filehandle to a temp file as its attribute. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=cut |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub add_facet { |
261
|
0
|
|
|
0
|
1
|
0
|
my $this = $_[0]; |
262
|
|
|
|
|
|
|
|
263
|
0
|
|
0
|
|
|
0
|
my $class = ref($this) || $this; |
264
|
0
|
|
|
|
|
0
|
my $daughter = $class->new(); |
265
|
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
0
|
$daughter->name('_facet'); |
267
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
0
|
my $fh = File::Temp::tempfile(); |
269
|
0
|
|
|
|
|
0
|
$daughter->attributes($fh); |
270
|
|
|
|
|
|
|
|
271
|
0
|
0
|
|
|
|
0
|
$this->add_daughters($daughter) if (ref $this) ; # called on object |
272
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
0
|
return $daughter; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=back |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=head2 Search in Tree |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=over 4 |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item C<$OBJ-Edaughters_by_name(NAMESTRING, recurse =E BOOLEAN)> |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Find all daughters that listen to the name NAMESTRING, either walking the tree |
285
|
|
|
|
|
|
|
("recurse" is true), or only on the mother's daughters ("recurse" false or |
286
|
|
|
|
|
|
|
omitted; the latter throws a warning that it will not recurse -- I've spent too |
287
|
|
|
|
|
|
|
much time debugging code where I forgot to add the "recurse" parameter). Returns |
288
|
|
|
|
|
|
|
the first object in scalar context, or a list of all found objects in list |
289
|
|
|
|
|
|
|
context. |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
Maybe one day I'll add magic to this function so you get the next item if you |
292
|
|
|
|
|
|
|
call the method on the same object without arguments. |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
Note that "daughter_by_name" is an exact alias for this method. |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=cut |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub daughters_by_name { |
299
|
7
|
|
|
7
|
1
|
47
|
my ($obj, $name, $recurse, $rec_val) = @_[0 .. 3]; |
300
|
7
|
|
|
|
|
9
|
my $wantsarray = wantarray; |
301
|
7
|
50
|
|
|
|
20
|
$rec_val = $recurse, $recurse = 'recurse' if @_ == 3; # backward compatible |
302
|
7
|
50
|
33
|
|
|
663
|
if ((warnings::enabled || $^W) && ! defined $recurse) { |
|
|
|
33
|
|
|
|
|
303
|
0
|
|
|
|
|
0
|
warnings::warn 'daughters_by_name will NOT recurse'; |
304
|
|
|
|
|
|
|
} |
305
|
7
|
|
100
|
|
|
21
|
$rec_val ||= 0; |
306
|
7
|
|
|
|
|
10
|
my @found = (); |
307
|
7
|
|
|
|
|
26
|
for my $daughter ($obj->daughters) { |
308
|
|
|
|
|
|
|
$daughter->walk_down({ |
309
|
|
|
|
|
|
|
callback => sub { |
310
|
41
|
100
|
66
|
41
|
|
724
|
push @found, $_[0] if (defined $_[0]->name && $_[0]->name eq $name); |
311
|
41
|
50
|
100
|
|
|
577
|
$rec_val = 0 if ($rec_val && @found && ! $wantsarray); # stop searching |
|
|
|
66
|
|
|
|
|
312
|
41
|
|
|
|
|
73
|
return $rec_val; |
313
|
|
|
|
|
|
|
} |
314
|
17
|
|
|
|
|
234
|
}); |
315
|
|
|
|
|
|
|
} |
316
|
7
|
50
|
|
|
|
106
|
return $wantsarray ? @found : $found[0]; |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=item C<$OBJ-Edaughter_by_name(NAMESTRING, recurse =E BOOLEAN)> |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
Alias for "daughters_by_name". |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=cut |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
sub daughter_by_name { # alias |
326
|
0
|
|
|
0
|
1
|
0
|
my ($it, @them) = @_; |
327
|
0
|
|
|
|
|
0
|
$it->daughters_by_name(@them); |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=item C<$OBJ-Edaughters_by_name_and_attr(NAMESTRING, ATTRIBUTE, recurse |
331
|
|
|
|
|
|
|
=E BOOLEAN)> |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
Find all daughters that listen to the name NAMESTRING and have the raw |
334
|
|
|
|
|
|
|
attribute ATTRIBUTE, either walking the tree ("recurse" is true), or only on |
335
|
|
|
|
|
|
|
the mother's daughters ("recurse" false or omitted). Returns the first object |
336
|
|
|
|
|
|
|
in scalar context, or a list of all found objects in list context. ATTRIBUTE |
337
|
|
|
|
|
|
|
must be raw data, so use C, C, C and |
338
|
|
|
|
|
|
|
C as appropriate. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
If you specify an empty string or undef as the NAMESTRING, this method will |
341
|
|
|
|
|
|
|
just look for ATTRIBUTE. |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
Note that "daughters_by_name_and_attr" is an exact alias for this method. |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=cut |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
sub daughters_by_name_and_attr { |
348
|
0
|
|
|
0
|
1
|
0
|
my ($obj, $name, $attr, $recurse, $rec_val) = @_[0 .. 4]; |
349
|
0
|
|
|
|
|
0
|
my $wantsarray = wantarray; |
350
|
0
|
0
|
|
|
|
0
|
$rec_val = $recurse, $recurse = 'recurse' if @_ == 4; # backward compatible |
351
|
0
|
0
|
0
|
|
|
0
|
if ((warnings::enabled || $^W) && ! defined $recurse) { |
|
|
|
0
|
|
|
|
|
352
|
0
|
|
|
|
|
0
|
warnings::warn 'daughters_by_name will NOT recurse'; |
353
|
|
|
|
|
|
|
} |
354
|
0
|
|
0
|
|
|
0
|
$rec_val ||= 0; |
355
|
0
|
|
|
|
|
0
|
my @found = (); |
356
|
0
|
|
|
|
|
0
|
for my $daughter ($obj->daughters) { |
357
|
|
|
|
|
|
|
$daughter->walk_down({ |
358
|
|
|
|
|
|
|
callback => sub { |
359
|
0
|
0
|
|
0
|
|
0
|
if ( $_[0]->is_leaf ) { |
360
|
0
|
0
|
0
|
|
|
0
|
if ( (!$name || (defined $_[0]->name && $_[0]->name eq $name)) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
361
|
|
|
|
|
|
|
&& (defined $_[0]->attributes && $_[0]->attributes eq $attr) ) { |
362
|
0
|
|
|
|
|
0
|
push @found, $_[0]; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
} |
365
|
0
|
0
|
0
|
|
|
0
|
$rec_val = 0 if ($rec_val && @found && ! $wantsarray); # stop searching |
|
|
|
0
|
|
|
|
|
366
|
0
|
|
|
|
|
0
|
return $rec_val; |
367
|
|
|
|
|
|
|
} |
368
|
0
|
|
|
|
|
0
|
}); |
369
|
|
|
|
|
|
|
} |
370
|
0
|
0
|
|
|
|
0
|
return $wantsarray ? @found : $found[0]; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item C<$OBJ-Edaughter_by_name_and_attr(NAMESTRING, ATTRIBUTE, recurse |
374
|
|
|
|
|
|
|
=E BOOLEAN)> |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Alias for "daughters_by_name_and_attr". |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=cut |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
sub daughter_by_name_and_attr { # alias |
381
|
0
|
|
|
0
|
1
|
0
|
my ($it, @them) = @_; |
382
|
0
|
|
|
|
|
0
|
$it->daughters_by_name_and_attr(@them); |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
=item C<$OBJ-Efind_string(QUOTED_REGEX)> |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
Returns a list of all strings that match QUOTED_REGEX under $OBJ. When called |
388
|
|
|
|
|
|
|
in scalar context, only the first match is returned. The string is in Unicode |
389
|
|
|
|
|
|
|
if the global modifier Cuse_unicode> is set (off by |
390
|
|
|
|
|
|
|
default.) |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=cut |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub find_string { |
395
|
0
|
|
|
0
|
1
|
0
|
my ($obj, $re, $use_unicode_deprecated) = @_[0 .. 2]; |
396
|
0
|
|
|
|
|
0
|
my $wantsarray = wantarray; |
397
|
0
|
|
|
|
|
0
|
my @found = (); |
398
|
0
|
|
|
|
|
0
|
for my $str_obj ($obj->daughters_by_name('String', recurse => 1)) { |
399
|
0
|
|
|
|
|
0
|
my $string = $str_obj->string(undef, $use_unicode_deprecated); |
400
|
0
|
0
|
|
|
|
0
|
push @found, $string if $string =~ /$re/; |
401
|
0
|
0
|
0
|
|
|
0
|
last if @found && ! $wantsarray; |
402
|
|
|
|
|
|
|
} |
403
|
0
|
0
|
|
|
|
0
|
return $wantsarray ? @found : $found[0]; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=item C<$OBJ-Echarleaves_to_strings()> |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Changes all the leaves with the name "Char" below $OBJ to their equivalent |
409
|
|
|
|
|
|
|
String leaves. This has no effect on the content of the MIF file; it just makes |
410
|
|
|
|
|
|
|
the file less ambiguous. Returns undef. |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=cut |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
#TODO I intend to move these two methods to a separate class later |
415
|
|
|
|
|
|
|
sub charleaves_to_strings { |
416
|
1
|
|
|
1
|
1
|
2
|
my $obj = $_[0]; |
417
|
1
|
|
|
|
|
58
|
local $use_unicode = 1; |
418
|
1
|
|
|
|
|
4
|
for ($obj->daughters_by_name('Char', recurse => 1)) { |
419
|
2
|
|
|
|
|
14
|
my $new_att_string = $fmnamedchars{$_->attribute}; |
420
|
2
|
|
|
|
|
7
|
$_->name('String'); |
421
|
2
|
|
|
|
|
13
|
$_->string($new_att_string); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
=item C<$OBJ-Efold_strings()> |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
This method folds all subsequent paragraph lines in a paragraph into one |
428
|
|
|
|
|
|
|
paragraph line. If you want to do operations on text, you should first use this |
429
|
|
|
|
|
|
|
method on (part of) the tree. In MIF, the flow of text over the lines is |
430
|
|
|
|
|
|
|
maintained, but since this information is not used while FrameMaker parses the |
431
|
|
|
|
|
|
|
MIF file, it is safe to remove this information. Returns undef. |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
All "Char" leaves except a "HardReturn" are transformed to their string |
434
|
|
|
|
|
|
|
equivalents. A "HardReturn" character forces a new paragraph line. |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
=cut |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
sub fold_strings { |
439
|
1
|
|
|
1
|
1
|
103
|
my $obj = $_[0]; |
440
|
1
|
|
|
|
|
3
|
local $use_unicode = 0; |
441
|
1
|
|
|
|
|
5
|
for my $para ($obj->daughters_by_name('Para', recurse => 1)) { |
442
|
|
|
|
|
|
|
|
443
|
1
|
|
|
|
|
5
|
$para->charleaves_to_strings; |
444
|
|
|
|
|
|
|
|
445
|
1
|
|
|
|
|
2
|
my $first_paraline; |
446
|
1
|
|
|
|
|
6
|
for my $daughter ($para->daughters) { |
447
|
4
|
50
|
|
|
|
19
|
if ($daughter->name ne 'ParaLine') { |
|
|
100
|
|
|
|
|
|
448
|
0
|
|
|
|
|
0
|
$first_paraline = undef; |
449
|
|
|
|
|
|
|
} elsif ( ! defined $first_paraline ) { |
450
|
1
|
|
|
|
|
10
|
$first_paraline = $daughter; |
451
|
|
|
|
|
|
|
} else { |
452
|
3
|
|
|
|
|
21
|
my @strobj = $first_paraline->daughters_by_name('String', recurse => 0); |
453
|
3
|
100
|
66
|
|
|
12
|
if (@strobj && $strobj[-1]->string =~ /\x09$/) { # character HardReturn |
454
|
2
|
|
|
|
|
4
|
$first_paraline = $daughter; # forces new ParaLine |
455
|
|
|
|
|
|
|
} else { |
456
|
1
|
|
|
|
|
9
|
$first_paraline->add_daughters( |
457
|
1
|
|
|
|
|
5
|
grep {$_->name ne 'TextRectID'} $daughter->daughters |
458
|
|
|
|
|
|
|
); |
459
|
1
|
|
|
|
|
96
|
$para->remove_daughter($daughter); |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
} |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
1
|
|
|
|
|
48
|
for my $paraline ($para->daughters_by_name('ParaLine', recurse => 0)) { |
465
|
3
|
|
|
|
|
54
|
my $first_str; |
466
|
3
|
|
|
|
|
10
|
for my $daughter ($paraline->daughters) { |
467
|
7
|
50
|
|
|
|
52
|
if ($daughter->name ne 'String') { |
|
|
100
|
|
|
|
|
|
468
|
0
|
|
|
|
|
0
|
$first_str = undef; |
469
|
|
|
|
|
|
|
} elsif ( ! defined $first_str ) { |
470
|
3
|
|
|
|
|
21
|
$first_str = $daughter; |
471
|
|
|
|
|
|
|
} else { |
472
|
4
|
|
|
|
|
22
|
(my $str = $daughter->string) =~ tr/\x06//d; |
473
|
4
|
|
|
|
|
11
|
$first_str->string($first_str->string . $str); |
474
|
4
|
|
|
|
|
13
|
$paraline->remove_daughter($daughter); |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
} |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=back |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=head2 Attribute Methods |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=over 4 |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=item C<$OBJ-Estring(STRING)> |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
Reads or sets the object's attribute as a MIF string. The method just calls |
491
|
|
|
|
|
|
|
C and C as appropriate. |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
If the global modifier Cuse_unicode> is set to true, |
494
|
|
|
|
|
|
|
the string will be converted from Unicode to the FrameMaker character set |
495
|
|
|
|
|
|
|
first. (The method now throws a warning when you specify USE_UNICODE as the |
496
|
|
|
|
|
|
|
second argument.) |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
=cut |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
sub string { # read/write attribute-method |
501
|
17
|
|
|
17
|
1
|
33
|
my ($this, $new_val, $unicode_deprecated) = @_[0 .. 2]; |
502
|
17
|
100
|
|
|
|
34
|
$this->attributes(quote($new_val, $unicode_deprecated)) if defined $new_val; |
503
|
17
|
|
|
|
|
33
|
return unquote($this->attributes, $unicode_deprecated); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
=item C<$OBJ-Epathname(PATHSTRING)> |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
Returns the object's attribute as local pathname, or sets it to the device |
509
|
|
|
|
|
|
|
independent pathname. The method just calls C and C |
510
|
|
|
|
|
|
|
as appropriate. PATHSTRING must also be a local pathname. |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=cut |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
sub pathname { # read/write attribute-method |
515
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
516
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
517
|
0
|
0
|
|
|
|
0
|
$this->attributes(encode_path($_[0])) if (@_); |
518
|
0
|
|
|
|
|
0
|
return decode_path($this->attributes); |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=item C<$OBJ-Eabs_pathname(FROMROOT)> |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
Returns the object's attribute as a local pathname. The method just calls |
524
|
|
|
|
|
|
|
C, passing on the FROMROOT argument. Use this method if you want |
525
|
|
|
|
|
|
|
to make sure that you always receive absolute pathnames, independently from |
526
|
|
|
|
|
|
|
what is stored in the attribute. |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=cut |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub abs_pathname { # read/write attribute-method |
531
|
0
|
|
|
0
|
1
|
0
|
my ($this, $root) = @_[0, 1]; |
532
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
533
|
0
|
|
|
|
|
0
|
return decode_path($this->attributes, $root); |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
=item C<$OBJ-Eboolean(BOOLEAN)> |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
Returns or sets the object's TRUE or FALSE value. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=cut |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
sub boolean { # read/write attribute-method |
543
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
544
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
545
|
0
|
0
|
|
|
|
0
|
$this->attributes($_[0] ? 'Yes' : 'No') if (@_); |
|
|
0
|
|
|
|
|
|
546
|
0
|
0
|
|
|
|
0
|
return $this->attributes eq 'Yes' ? 1 : $this->attributes eq 'No' ? 0 : undef; |
|
|
0
|
|
|
|
|
|
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=item C<$OBJ-Emeasurements(LIST)> |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
Returns or sets a list of measurements. When called in scalar context, only the |
552
|
|
|
|
|
|
|
first measurement is returned. Everything is in the default unit of |
553
|
|
|
|
|
|
|
measurement. (Can be set using Cdefault_unit>. If this |
554
|
|
|
|
|
|
|
variable is set to the empty string (which also happens to be the default), |
555
|
|
|
|
|
|
|
points are output.) You always get the values without the unit specifier, so |
556
|
|
|
|
|
|
|
calculations can be made directly on this. To get a value from the list, do |
557
|
|
|
|
|
|
|
something like: |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
my $q; |
560
|
|
|
|
|
|
|
$q = FrameMaker::MifTree->new->add_leaf( |
561
|
|
|
|
|
|
|
PgfCellMargins => "0.0 pt 1.0 pt 2.0 pt 3.0 pt" |
562
|
|
|
|
|
|
|
); |
563
|
|
|
|
|
|
|
my $k = ($q->measurements)[1]; |
564
|
|
|
|
|
|
|
print "k is now: $k\n" # prints "k is now: 1" |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
In MIF, a maximum of four values can be supplied, but this is never checked by |
567
|
|
|
|
|
|
|
this method. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=cut |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
sub measurements { # read/write attribute-method |
572
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
573
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
574
|
0
|
0
|
|
|
|
0
|
$this->attributes( join(' ', map { convert($_, 'pt') } @_) ) if @_; |
|
0
|
|
|
|
|
0
|
|
575
|
0
|
|
|
|
|
0
|
my @mlist = (); |
576
|
0
|
|
|
|
|
0
|
my $attribute = $this->attributes; |
577
|
0
|
|
|
|
|
0
|
while ( $attribute =~ /\G(\d*\.?\d+\D*)/gi ) { |
578
|
0
|
|
|
|
|
0
|
push @mlist, $1; |
579
|
|
|
|
|
|
|
} |
580
|
0
|
|
|
|
|
0
|
@mlist = map { convert($_, undef, 1) } @mlist; |
|
0
|
|
|
|
|
0
|
|
581
|
0
|
0
|
|
|
|
0
|
return wantarray ? @mlist : $mlist[0]; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=item C<$OBJ-Epercentage(FRACTION)> |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
Returns or sets the object's percentage value as a fraction (1 = 100%). |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
=cut |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub percentage { # read/write attribute-method |
591
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
592
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
593
|
0
|
0
|
|
|
|
0
|
$this->attributes($_[0] * 100 . '%') if @_; |
594
|
0
|
|
|
|
|
0
|
my ($value) = $this->attributes =~ /\d*\.?\d+/; |
595
|
0
|
|
|
|
|
0
|
return $value / 100; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=item C<$OBJ-Efacet_data()> |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
Returns the object's facet data as a list of lines. (Use a C to |
601
|
|
|
|
|
|
|
C to set the objects data. Not a very elegant implementation, but |
602
|
|
|
|
|
|
|
I consider a facet to be rather esoteric, and we have to be efficient on memory |
603
|
|
|
|
|
|
|
usage as well...) |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=cut |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
sub facet_data { # read-only attribute-method |
608
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
609
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
610
|
0
|
|
|
|
|
0
|
my $fh = $this->facet_handle; |
611
|
0
|
0
|
|
|
|
0
|
if ($fh) { |
612
|
0
|
|
|
|
|
0
|
sysseek $fh, 0, Fcntl::SEEK_SET; |
613
|
0
|
|
|
|
|
0
|
my @list = <$fh>; |
614
|
0
|
|
|
|
|
0
|
return @list; |
615
|
|
|
|
|
|
|
} else { |
616
|
0
|
0
|
0
|
|
|
0
|
warnings::warn 'No facet data available' if warnings::enabled || $^W; |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=item C<$OBJ-Efacet_handle()> |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
Returns the filehandle to the object's facet data. Since the temporary file is |
623
|
|
|
|
|
|
|
sysopened, you should use C instead of C to respect the |
624
|
|
|
|
|
|
|
buffering considerations. |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
=cut |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
sub facet_handle { # read-only attribute-method |
629
|
0
|
|
|
0
|
1
|
0
|
my $this = shift; |
630
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on object' unless ref $this; |
631
|
0
|
0
|
|
|
|
0
|
$this = $this->daughters_by_name( |
632
|
|
|
|
|
|
|
'_facet', |
633
|
|
|
|
|
|
|
recurse => 0, |
634
|
|
|
|
|
|
|
) unless $this->name eq '_facet'; |
635
|
0
|
0
|
|
|
|
0
|
croak 'Must be called on facet' unless $this->name eq '_facet'; |
636
|
0
|
0
|
|
|
|
0
|
return fileno($this->attributes) ? $this->attributes : undef; |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
=item Cdefault_unit(UNIT)> |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
This class method returns or sets the global default units of measurement. See |
642
|
|
|
|
|
|
|
C for a list of valid assignments. |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
FrameMaker::MifTree's default units of measurement can (and probably will) |
645
|
|
|
|
|
|
|
differ from the default that are specified in the MIF file. |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
The default for C is an empty string, which means that no unit |
648
|
|
|
|
|
|
|
specifier will be output, and all values are in "points". |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=cut |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
sub default_unit { # read/write attribute-method |
653
|
0
|
|
|
0
|
1
|
0
|
my ($this, $value) = @_[0, 1]; |
654
|
0
|
0
|
0
|
|
|
0
|
croak 'This does not seem to be a valid unit of measurement' |
655
|
|
|
|
|
|
|
if (defined $value && ! defined $unit_to_factor{$value}); |
656
|
0
|
|
|
|
|
0
|
$default_unit = $value; |
657
|
0
|
|
|
|
|
0
|
return $default_unit; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=item Cuse_unicode(BOOLEAN)> |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
This class global method returns or sets if strings are in Unicode or not. |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
B Most FrameMaker characters map easily to a Unicode |
665
|
|
|
|
|
|
|
equivalent. This is not true however, for the discretionary hyphen (hexadecimal |
666
|
|
|
|
|
|
|
04, EChar DiscHyphenE), the FrameMaker "soft hyphen" (hexadecimal 06 |
667
|
|
|
|
|
|
|
EChar SoftHyphenE), and the "do not hyphenate" character (hexadecimal |
668
|
|
|
|
|
|
|
05, EChar NoHyphenE). |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
The discretionary hyphen has a null default appearance in the middle of a line. |
671
|
|
|
|
|
|
|
At any intraword break that is used for a line break a hyphen glyph will be |
672
|
|
|
|
|
|
|
shown. Oddly enough this is defined in Unicode as a I, and so it |
673
|
|
|
|
|
|
|
maps to the soft hyphen (U+00AD) character. |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
The I in FrameMaker is used for automatically inserted hyphens by |
676
|
|
|
|
|
|
|
the FrameMaker hyphenation algorithm. It has no meaning in the MIF, since |
677
|
|
|
|
|
|
|
FrameMaker will reflow a document upon import. But to preserve it in the |
678
|
|
|
|
|
|
|
Unicode string, it is mapped to the Unicode hyphen character (U+2010). You |
679
|
|
|
|
|
|
|
should remove it with C |
if you don't want it.
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
The NoHyphen is a real control character that just prevents a word from being |
682
|
|
|
|
|
|
|
hyphenated automatically by FrameMaker. To preserve this character when doing |
683
|
|
|
|
|
|
|
a to and fro conversion, I decided to map it to the Unicode zero-width joiner |
684
|
|
|
|
|
|
|
(U+200D). |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
Everything is controlled from the C file, so make changes |
687
|
|
|
|
|
|
|
there if you don't like my choices. Or better, override the %fmcharset hash. |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
=cut |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
sub use_unicode { |
692
|
0
|
0
|
|
0
|
1
|
0
|
$use_unicode = $_[1] if exists $_[1]; |
693
|
0
|
|
|
|
|
0
|
return $use_unicode; |
694
|
|
|
|
|
|
|
} |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=back |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
=head2 Tests on Tree Object |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
=over 4 |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=item C<$OBJ-Eis_node()> |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
Tests if the object is a valid MIF node statement. That is, if its name occurs |
705
|
|
|
|
|
|
|
in the %mifnodes hash. Returns a list of valid daughters when a match is found. |
706
|
|
|
|
|
|
|
(In my terminology, "nodes" can have daughters, whereas leaves don't.) |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
=cut |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
sub is_node { |
711
|
12
|
|
|
12
|
1
|
23
|
my $this = shift; |
712
|
12
|
50
|
|
|
|
62
|
$this = $this->name if ref $this; |
713
|
12
|
50
|
|
|
|
80
|
return @{$mifnodes{$this}} if defined $mifnodes{$this}; |
|
12
|
|
|
|
|
72
|
|
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
=item C<$OBJ-Eis_leaf()> |
717
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
Tests if the object is a valid MIF leaf statement and thus can have an |
719
|
|
|
|
|
|
|
attribute value. The name is just looked up in the %mifleaves hash. |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
=cut |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
sub is_leaf { |
724
|
36
|
|
|
36
|
1
|
36
|
my $this = shift; |
725
|
36
|
50
|
|
|
|
122
|
$this = $this->name if ref $this; |
726
|
36
|
50
|
|
|
|
253
|
return $this if exists $mifleaves{$this}; |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
=item C<$OBJ-Eallows_daughter(DAUGHTEROBJECT)> |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
Checks if a mother object can have a specific daughter object. I just thought |
732
|
|
|
|
|
|
|
this could come in handy when you want to bind one object tree to another. |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
=cut |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
sub allows_daughter { |
737
|
12
|
|
|
12
|
1
|
18
|
my ($mother, $daughter) = @_[0, 1]; |
738
|
12
|
50
|
|
|
|
24
|
croak 'Must be called on object' unless ref $mother; |
739
|
12
|
50
|
|
|
|
44
|
croak 'Mother "' . $mother->name . '" must be called with daughter object' |
740
|
|
|
|
|
|
|
unless $daughter->isa('FrameMaker::MifTree'); |
741
|
12
|
50
|
|
|
|
32
|
if (defined $daughter->name) { |
742
|
12
|
|
|
|
|
86
|
return grep { $_ eq $daughter->name } $mother->is_node; |
|
276
|
|
|
|
|
1426
|
|
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
} |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
=item C<$OBJ-Echeck_attribute> |
747
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
Checks if the attribute conforms to the type. Currently the following types are |
749
|
|
|
|
|
|
|
defined: |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
0xnnn |
752
|
|
|
|
|
|
|
ID |
753
|
|
|
|
|
|
|
L_T_R_B |
754
|
|
|
|
|
|
|
L_T_W_H |
755
|
|
|
|
|
|
|
W_H |
756
|
|
|
|
|
|
|
W_W |
757
|
|
|
|
|
|
|
X_Y |
758
|
|
|
|
|
|
|
X_Y_W_H |
759
|
|
|
|
|
|
|
boolean |
760
|
|
|
|
|
|
|
data |
761
|
|
|
|
|
|
|
degrees |
762
|
|
|
|
|
|
|
dimension |
763
|
|
|
|
|
|
|
empty *) |
764
|
|
|
|
|
|
|
integer |
765
|
|
|
|
|
|
|
keyword |
766
|
|
|
|
|
|
|
number |
767
|
|
|
|
|
|
|
pathname |
768
|
|
|
|
|
|
|
percentage |
769
|
|
|
|
|
|
|
seconds_microseconds |
770
|
|
|
|
|
|
|
string |
771
|
|
|
|
|
|
|
tagstring |
772
|
|
|
|
|
|
|
*) no attribute allowed; some leaves and all non-ending nodes have this |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
The function returns TRUE if the attribute seems valid, and FALSE if there is |
775
|
|
|
|
|
|
|
an error. Use L to see the error. |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
=cut |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
sub check_attribute { |
780
|
57
|
|
|
57
|
1
|
72
|
my $it = shift; |
781
|
57
|
50
|
|
|
|
86
|
return $it->get_attribute_error ? undef : 1; |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
=item C<$OBJ-Eget_attribute_error> |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
Returns a meaningful text string if the attribute appears to be invalid. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=cut |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
sub get_attribute_error { |
791
|
57
|
|
|
57
|
1
|
52
|
my $it = shift; |
792
|
57
|
|
|
|
|
58
|
my $errVal; |
793
|
57
|
100
|
|
|
|
119
|
if ( defined $it->{attributes} ) { |
794
|
36
|
50
|
|
|
|
60
|
unless ( $it->is_leaf ) { |
795
|
0
|
|
|
|
|
0
|
$errVal = 'Node "' . $it->name . '" is not a leaf. ' . |
796
|
|
|
|
|
|
|
'Only leaves can have meaningful attributes'; |
797
|
|
|
|
|
|
|
} else { |
798
|
36
|
|
|
|
|
90
|
my $attrType = $mifleaves{$it->name}; |
799
|
|
|
|
|
|
|
# must access 'attributes' key directly; sorry |
800
|
36
|
50
|
|
|
|
398
|
unless ( $it->{attributes} =~ $attribute_types{$attrType} ) { |
801
|
0
|
|
|
|
|
0
|
$errVal = 'Attribute on leaf "' . $it->name . '" seems invalid. ' . |
802
|
|
|
|
|
|
|
qq(Expected "$attrType" for ") . $it->{attributes} . '"'; |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
} |
806
|
57
|
|
|
|
|
239
|
return $errVal; |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
=item C<$OBJ-Evalidate(FROMROOT)> |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
Not yet implemented. |
812
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
Validates a MIF tree object. If you set FROMROOT to true, the validation starts |
814
|
|
|
|
|
|
|
from $OBJ->root, and special checking is done on the root object. This special |
815
|
|
|
|
|
|
|
behaviour is needed because the method cannot know if a FrameMaker::MifTree |
816
|
|
|
|
|
|
|
object is to represent a complete MIF file, and not just a fragment. So please |
817
|
|
|
|
|
|
|
remember always to set FROMROOT if you want to validate a complete MIF tree, |
818
|
|
|
|
|
|
|
even if $OBJ already points to the root object. |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
=cut |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
sub validate { |
823
|
0
|
|
|
0
|
1
|
0
|
my ($it, $from_root) = @_[0, 1]; |
824
|
0
|
0
|
|
|
|
0
|
$it = $it->root if $from_root; |
825
|
0
|
|
|
|
|
0
|
croak 'Method not yet implemented.' |
826
|
|
|
|
|
|
|
# 1. hard-coded checking on root object |
827
|
|
|
|
|
|
|
# 2. walk_down, checking allows_daughter and is_leaf for every node |
828
|
|
|
|
|
|
|
# 3. if is_leaf: check_attribute |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=back |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
=head2 From/to MIF Syntax |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=over 4 |
836
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
=item Cdump_mif()> |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
Dumps out the current tree as a list of MIF statements in valid MIF file |
840
|
|
|
|
|
|
|
syntax. You can write the resulting list to a file. The method tries to mimic |
841
|
|
|
|
|
|
|
the Adobe MIF parser file layout as closely as possible. Please note that this |
842
|
|
|
|
|
|
|
method can be memory intensive, since it creates a whole new copy of your MIF |
843
|
|
|
|
|
|
|
tree in memory. If you just want to write the MIF tree to a file, you may want |
844
|
|
|
|
|
|
|
to use L instead. |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
=cut |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
sub dump_mif { |
849
|
0
|
|
|
0
|
1
|
0
|
my $obj = $_[0]; |
850
|
0
|
|
|
|
|
0
|
my @list = (); |
851
|
|
|
|
|
|
|
$obj->walk_down({ |
852
|
|
|
|
|
|
|
callback => sub { |
853
|
0
|
|
|
0
|
|
0
|
my $this = $_[0]; |
854
|
0
|
0
|
|
|
|
0
|
if (defined $this->mother) { # don't print root element |
855
|
0
|
0
|
0
|
|
|
0
|
if ((warnings::enabled || $^W) && ! $this->name) { |
|
|
|
0
|
|
|
|
|
856
|
0
|
|
|
|
|
0
|
warnings::warn 'Missing name on node ' . $this->address; |
857
|
|
|
|
|
|
|
} |
858
|
0
|
0
|
0
|
|
|
0
|
if ( ! $this->is_node && ! defined $this->attributes ) { |
859
|
0
|
0
|
0
|
|
|
0
|
if (warnings::enabled || $^W) { |
860
|
0
|
|
|
|
|
0
|
warnings::warn 'Undefined attribute on leaf "'. $this->name . '"'; |
861
|
|
|
|
|
|
|
} |
862
|
0
|
|
|
|
|
0
|
$this->attributes(''); |
863
|
|
|
|
|
|
|
} |
864
|
0
|
0
|
|
|
|
0
|
if ($this->name eq '_facet') { |
865
|
0
|
|
|
|
|
0
|
push @list, $this->facet_data; |
866
|
|
|
|
|
|
|
} else { |
867
|
0
|
0
|
|
|
|
0
|
push @list, |
|
|
0
|
|
|
|
|
|
868
|
|
|
|
|
|
|
' ' x (scalar $this->ancestors - 1) . |
869
|
|
|
|
|
|
|
'<' . $this->name . |
870
|
|
|
|
|
|
|
($this->name eq 'DocFileInfo' ? "\n" |
871
|
|
|
|
|
|
|
: ' ' ) . # not very elegant huh? |
872
|
|
|
|
|
|
|
($this->is_node ? "\n" |
873
|
|
|
|
|
|
|
: $this->attributes . ">\n"); |
874
|
|
|
|
|
|
|
} |
875
|
|
|
|
|
|
|
} |
876
|
0
|
|
|
|
|
0
|
1; # continue recursion |
877
|
|
|
|
|
|
|
}, |
878
|
|
|
|
|
|
|
callbackback => sub { |
879
|
0
|
|
|
0
|
|
0
|
my $this = $_[0]; |
880
|
0
|
0
|
|
|
|
0
|
if (defined $this->mother) { # don't print anything for root... |
881
|
0
|
0
|
|
|
|
0
|
if ($this->is_node) { # ... or for leaves |
882
|
0
|
|
|
|
|
0
|
push @list, ' ' x (scalar $this->ancestors - 1) . |
883
|
|
|
|
|
|
|
'> # End of ' . $this->name . "\n"; |
884
|
|
|
|
|
|
|
} |
885
|
|
|
|
|
|
|
} else { |
886
|
0
|
|
|
|
|
0
|
push @list, "# End of MIFFile\n"; |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
} |
889
|
0
|
|
|
|
|
0
|
}); |
890
|
0
|
|
|
|
|
0
|
return @list; |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=item Cdump_miffile(FILENAME)> |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
Dumps out the current tree of MIF statements into a valid MIF file syntax. The |
896
|
|
|
|
|
|
|
method returns with a FALSE result if the file cannot be written. |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
=cut |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
sub dump_miffile { |
901
|
0
|
|
|
0
|
1
|
0
|
my ($obj, $filename) = @_[0, 1]; |
902
|
0
|
0
|
|
|
|
0
|
open(my $MIF, ">$filename") || return undef; |
903
|
|
|
|
|
|
|
$obj->walk_down({ |
904
|
|
|
|
|
|
|
callback => sub { |
905
|
0
|
|
|
0
|
|
0
|
my $this = $_[0]; |
906
|
0
|
0
|
|
|
|
0
|
if (defined $this->mother) { # don't print root element |
907
|
0
|
0
|
0
|
|
|
0
|
if ((warnings::enabled || $^W) && ! $this->name) { |
|
|
|
0
|
|
|
|
|
908
|
0
|
|
|
|
|
0
|
warnings::warn 'Missing name on node ' . $this->address; |
909
|
|
|
|
|
|
|
} |
910
|
0
|
0
|
0
|
|
|
0
|
if ( ! $this->is_node && ! defined $this->attributes ) { |
911
|
0
|
0
|
0
|
|
|
0
|
if (warnings::enabled || $^W) { |
912
|
0
|
|
|
|
|
0
|
warnings::warn 'Undefined attribute on leaf "' . $this->name . '"'; |
913
|
|
|
|
|
|
|
} |
914
|
0
|
|
|
|
|
0
|
$this->attributes(''); |
915
|
|
|
|
|
|
|
} |
916
|
0
|
0
|
|
|
|
0
|
if ($this->name eq '_facet') { |
917
|
0
|
|
|
|
|
0
|
print $MIF $this->facet_data; |
918
|
|
|
|
|
|
|
} else { |
919
|
0
|
0
|
|
|
|
0
|
print $MIF |
|
|
0
|
|
|
|
|
|
920
|
|
|
|
|
|
|
' ' x (scalar $this->ancestors - 1) . |
921
|
|
|
|
|
|
|
'<' . $this->name . |
922
|
|
|
|
|
|
|
($this->name eq 'DocFileInfo' ? "\n" |
923
|
|
|
|
|
|
|
: ' ' ) . # not very elegant huh? |
924
|
|
|
|
|
|
|
($this->is_node ? "\n" |
925
|
|
|
|
|
|
|
: $this->attributes . ">\n"); |
926
|
|
|
|
|
|
|
} |
927
|
|
|
|
|
|
|
} |
928
|
0
|
|
|
|
|
0
|
1; # continue recursion |
929
|
|
|
|
|
|
|
}, |
930
|
|
|
|
|
|
|
callbackback => sub { |
931
|
0
|
|
|
0
|
|
0
|
my $this = $_[0]; |
932
|
0
|
0
|
|
|
|
0
|
if (defined $this->mother) { # don't print anything for root... |
933
|
0
|
0
|
|
|
|
0
|
if ($this->is_node) { # ... or for leaves |
934
|
0
|
|
|
|
|
0
|
print $MIF ' ' x (scalar $this->ancestors - 1) . |
935
|
|
|
|
|
|
|
'> # End of ' . $this->name . "\n"; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
} else { |
938
|
0
|
|
|
|
|
0
|
print $MIF "# End of MIFFile\n"; |
939
|
|
|
|
|
|
|
} |
940
|
|
|
|
|
|
|
} |
941
|
0
|
|
|
|
|
0
|
}); |
942
|
0
|
|
|
|
|
0
|
return 1; |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
=item C<$OBJ-Eparse_mif(STRING)> |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
Parses a string of MIF statements into the object. This is also a very quick |
948
|
|
|
|
|
|
|
way to set up an object tree: |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
my $new_obj = FrameMaker::MifTree->new(); |
951
|
|
|
|
|
|
|
$new_obj->parse_mif(<
|
952
|
|
|
|
|
|
|
# The only required statement |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
# The actual text of this document |
956
|
|
|
|
|
|
|
> # end of Paraline #End of ParaLine statement |
957
|
|
|
|
|
|
|
> # end of Para #End of Para statement |
958
|
|
|
|
|
|
|
ENDMIF |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
Implemented by tying the scalar to a filehandle and calling IO::Tokenizer on |
961
|
|
|
|
|
|
|
the resulting handle. |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
The parser currently has the following limitations: |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
=over 8 |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
=item * |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
All comments are lost. |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
=item * |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
Macro statements are not (yet) implemented. |
974
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
=item * |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
Include statements are not (yet) implemented. |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=back |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
Maybe I'll do something about it. Someday. |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
=cut |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
sub parse_mif { |
986
|
1
|
|
|
1
|
1
|
91
|
my ($obj, $string) = @_[0, 1]; |
987
|
1
|
|
33
|
|
|
4
|
my $class = ref($obj) || croak 'Must be called on object'; |
988
|
1
|
|
|
|
|
2
|
my $facet_handle = 0; |
989
|
|
|
|
|
|
|
|
990
|
1
|
|
|
|
|
11
|
my $fh = IO::Tokenized::Scalar->new(); |
991
|
1
|
|
|
|
|
11
|
$fh->setparser(@parserdefinition); |
992
|
1
|
|
|
|
|
443
|
$fh->open(\$string); |
993
|
|
|
|
|
|
|
|
994
|
1
|
|
|
|
|
10
|
my $cur_obj = $obj; |
995
|
1
|
|
|
|
|
11
|
while ( my ($tok, $val) = $fh->gettoken ) { |
996
|
40
|
50
|
|
|
|
1508
|
if ( $tok eq 'FACET' ) { |
997
|
0
|
0
|
|
|
|
0
|
unless ($facet_handle) { |
998
|
0
|
|
|
|
|
0
|
$cur_obj->add_facet; |
999
|
0
|
|
|
|
|
0
|
$facet_handle = $cur_obj->facet_handle; |
1000
|
|
|
|
|
|
|
} |
1001
|
0
|
|
|
|
|
0
|
syswrite $facet_handle, "$val\n"; |
1002
|
|
|
|
|
|
|
} else { |
1003
|
40
|
|
|
|
|
47
|
$facet_handle = 0; |
1004
|
40
|
100
|
|
|
|
100
|
if ( $tok eq 'MIFTAG' ) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1005
|
13
|
|
|
|
|
29
|
$cur_obj = $cur_obj->add_node($val); |
1006
|
|
|
|
|
|
|
} elsif ( $tok eq 'RANGLE' ) { |
1007
|
13
|
|
|
|
|
36
|
$cur_obj = $cur_obj->mother; |
1008
|
|
|
|
|
|
|
} elsif ( $tok eq 'ATTRIBS' ) { |
1009
|
8
|
50
|
|
|
|
17
|
if (defined $cur_obj->attributes) { |
1010
|
0
|
|
|
|
|
0
|
$cur_obj->attributes($cur_obj->attributes . $val) |
1011
|
|
|
|
|
|
|
} else { |
1012
|
8
|
|
|
|
|
15
|
$cur_obj->attributes($val) |
1013
|
|
|
|
|
|
|
} |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
} |
1017
|
1
|
|
|
|
|
48
|
$fh->close; |
1018
|
|
|
|
|
|
|
} |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=item C<$OBJ-Eparse_miffile(FILENAME)> |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
Parses a file from disk into a DAG_Node tree structure. See L for |
1023
|
|
|
|
|
|
|
details. |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
=cut |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
sub parse_miffile { |
1028
|
0
|
|
|
0
|
1
|
0
|
my ($obj, $filename) = @_[0, 1]; |
1029
|
0
|
0
|
|
|
|
0
|
croak qq(File "$filename" not found) unless -f $filename; |
1030
|
0
|
|
0
|
|
|
0
|
my $class = ref($obj) || croak 'Must be called on object'; |
1031
|
0
|
|
|
|
|
0
|
my $facet_handle = 0; |
1032
|
|
|
|
|
|
|
|
1033
|
0
|
|
|
|
|
0
|
my $fh = IO::Tokenized::File->new(); |
1034
|
0
|
|
|
|
|
0
|
$fh->setparser(@parserdefinition); |
1035
|
0
|
|
|
|
|
0
|
$fh->buffer_space(524_288); |
1036
|
0
|
|
|
|
|
0
|
$fh->open($filename); |
1037
|
|
|
|
|
|
|
|
1038
|
0
|
|
|
|
|
0
|
my $cur_obj = $obj; |
1039
|
0
|
|
|
|
|
0
|
while ( my ($tok, $val) = $fh->gettoken ) { |
1040
|
0
|
0
|
|
|
|
0
|
if ( $tok eq 'FACET' ) { |
1041
|
0
|
0
|
|
|
|
0
|
unless ($facet_handle) { |
1042
|
0
|
|
|
|
|
0
|
$cur_obj->add_facet; |
1043
|
0
|
|
|
|
|
0
|
$facet_handle = $cur_obj->facet_handle; |
1044
|
|
|
|
|
|
|
} |
1045
|
0
|
|
|
|
|
0
|
syswrite $facet_handle, "$val\n"; |
1046
|
|
|
|
|
|
|
} else { |
1047
|
0
|
|
|
|
|
0
|
$facet_handle = 0; |
1048
|
0
|
0
|
|
|
|
0
|
if ( $tok eq 'MIFTAG' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1049
|
0
|
|
|
|
|
0
|
$cur_obj = $cur_obj->add_node($val); |
1050
|
|
|
|
|
|
|
} elsif ( $tok eq 'RANGLE' ) { |
1051
|
0
|
|
|
|
|
0
|
$cur_obj = $cur_obj->mother; |
1052
|
|
|
|
|
|
|
} elsif ( $tok eq 'ATTRIBS' ) { |
1053
|
0
|
0
|
|
|
|
0
|
if (defined $cur_obj->attributes) { |
1054
|
0
|
|
|
|
|
0
|
$cur_obj->attributes($cur_obj->attributes . $val) |
1055
|
|
|
|
|
|
|
} else { |
1056
|
0
|
|
|
|
|
0
|
$cur_obj->attributes($val) |
1057
|
|
|
|
|
|
|
} |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
} |
1060
|
|
|
|
|
|
|
} |
1061
|
0
|
|
|
|
|
0
|
$fh->close; |
1062
|
|
|
|
|
|
|
} |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
=back |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
=head2 Old-style Functions |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
All these functions are exported by default. |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
=over 4 |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=item C |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
Quotes a string with MIF style quotes, and escapes forbidden characters. |
1075
|
|
|
|
|
|
|
Backslashes, backticks, single quotes, greater-than and tabs are escaped, |
1076
|
|
|
|
|
|
|
non-ASCII values are written in their hexadecimal representation. So: |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
Some `symbols': E \E<216>E<191>!> |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
is written as |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
`Some \Qsymbols\q: \> \\\xaf \xc0 !' |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
As a special case, escaped hexadecimals are preserved in the input string. If |
1085
|
|
|
|
|
|
|
you want a literal \x00 string, precede it with an extra backslash. |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
print quote("\x09 "); # prints `\x09 ', a forced return in FrameMaker |
1088
|
|
|
|
|
|
|
print quote("\\x09 "); # prints `\\x09 '; this will show up literally |
1089
|
|
|
|
|
|
|
# as \x09 in FrameMaker |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
(Note that after emitting a forced return, you I start a new ParaLine.) |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
If the global modifier $FrameMaker::MifTree::use_unicode is true, the string |
1094
|
|
|
|
|
|
|
will be converted from Unicode to the FrameMaker character set. |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
=cut |
1098
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
sub quote { |
1100
|
6
|
|
|
6
|
1
|
8
|
my ($s, $use_unicode_deprecated) = @_; |
1101
|
6
|
50
|
|
|
|
16
|
return unless defined $s; |
1102
|
6
|
50
|
33
|
|
|
547
|
if ((warnings::enabled || $^W) && defined $use_unicode_deprecated) { |
|
|
|
33
|
|
|
|
|
1103
|
0
|
|
|
|
|
0
|
warnings::warn 'USE_UNICODE as 2nd argument is now deprecated'; |
1104
|
|
|
|
|
|
|
} |
1105
|
|
|
|
|
|
|
|
1106
|
6
|
100
|
33
|
|
|
25
|
if ($use_unicode_deprecated || $use_unicode) { |
1107
|
2
|
|
|
|
|
2
|
my $s_orig = $s; |
1108
|
2
|
|
|
|
|
291
|
eval($unicode_to_fm); |
1109
|
2
|
50
|
|
|
|
14
|
warnings::warn qq(Error in "quote" while converting $s_orig\n$@) if $@; |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
|
1112
|
6
|
|
|
|
|
22
|
$s =~ s/\\(?!x[a-f0-9]{2})/\\\\/g; # single backslash to escaped backslash |
1113
|
|
|
|
|
|
|
# except when followed by hex sequence |
1114
|
6
|
|
|
|
|
11
|
$s =~ s/\\\\\\(?=x[a-f0-9]{2})/\\/g; # correct double backslash case |
1115
|
6
|
|
|
|
|
9
|
$s =~ s/`/\\Q/g; # backtick |
1116
|
6
|
|
|
|
|
11
|
$s =~ s/'/\\q/g; # single straight quote |
1117
|
6
|
|
|
|
|
10
|
$s =~ s/>/\\>/g; # escape 'greater than' |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
# control and high chars |
1120
|
6
|
|
|
|
|
18
|
$s =~ s/([\x00-\x1a\x80-\xff])/'\x' . sprintf('%02x ', ord $1)/ge; |
|
2
|
|
|
|
|
9
|
|
1121
|
|
|
|
|
|
|
|
1122
|
6
|
|
|
|
|
29
|
return "`$s'"; |
1123
|
|
|
|
|
|
|
} |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
=item C |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
The opposite action. Surrounding quotes are removed and all escaped sequences |
1128
|
|
|
|
|
|
|
are transliterated into their original character. |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
If the global modifier $FrameMaker::MifTree::use_unicode is true, the string |
1131
|
|
|
|
|
|
|
will be converted from the FrameMaker character set to Unicode. |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
$FrameMaker::MifTree::use_unicode can be exported on request. |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
=cut |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
sub unquote { |
1138
|
17
|
|
|
17
|
1
|
22
|
my ($s, $use_unicode_deprecated) = @_; |
1139
|
17
|
50
|
|
|
|
32
|
return unless defined $s; |
1140
|
17
|
50
|
33
|
|
|
1530
|
if ((warnings::enabled || $^W) && defined $use_unicode_deprecated) { |
|
|
|
33
|
|
|
|
|
1141
|
0
|
|
|
|
|
0
|
warnings::warn 'USE_UNICODE as 2nd argument is now deprecated'; |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
|
1144
|
17
|
50
|
|
|
|
111
|
$s =~ s/^`// && $s =~ s/'$//; # unquote |
1145
|
17
|
|
|
|
|
52
|
$s =~ s/\\x([a-f0-9]{1,2}) ?/chr hex $1/ge; # escaped non-ASCII chars |
|
8
|
|
|
|
|
34
|
|
1146
|
17
|
|
|
|
|
21
|
$s =~ s/\\>/>/g; # greater than |
1147
|
17
|
|
|
|
|
18
|
$s =~ s/\\q/'/g; # single quote |
1148
|
17
|
|
|
|
|
20
|
$s =~ s/\\Q/`/g; # backtick |
1149
|
17
|
|
|
|
|
16
|
$s =~ s/\\\\/\\/g; # backslash |
1150
|
|
|
|
|
|
|
|
1151
|
17
|
100
|
33
|
|
|
52
|
if ($use_unicode_deprecated || $use_unicode) { |
1152
|
2
|
|
|
|
|
4
|
my $s_orig = $s; |
1153
|
2
|
|
|
|
|
231
|
eval($fm_to_unicode); |
1154
|
2
|
50
|
|
|
|
10
|
warnings::warn qq(Error in "unquote" while converting $s_orig\n$@) if $@; |
1155
|
|
|
|
|
|
|
} |
1156
|
|
|
|
|
|
|
|
1157
|
17
|
|
|
|
|
59
|
return $s; |
1158
|
|
|
|
|
|
|
} |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
=item C |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
Encodes path names to the MIF path syntax. Usage: |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
$mifPathString = encode_path('D:\Dos\Path\With\Backslashes\Filename'); |
1165
|
|
|
|
|
|
|
$mifPathString = encode_path('..\..\Also\Relative\Path\Is\Allowed\Filename'); |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
The path name must not be in a MIF quoted style. It returns the device |
1168
|
|
|
|
|
|
|
independent path name I the quotes. |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
=cut |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
sub encode_path { |
1173
|
0
|
|
|
0
|
1
|
|
my $s = shift; |
1174
|
0
|
0
|
|
|
|
|
return unless defined $s; |
1175
|
|
|
|
|
|
|
|
1176
|
0
|
0
|
|
|
|
|
$s =~ s{^`}{} && $s =~ s{'$}{}; # Remove quotes, just in case... |
1177
|
0
|
|
|
|
|
|
$s =~ s{\\}{/}g; # All backslashes to forward slashes |
1178
|
|
|
|
|
|
|
|
1179
|
0
|
|
|
|
|
|
$s =~ s{^([a-z]:)}{$1}i; # drive letter |
1180
|
0
|
|
|
|
|
|
$s =~ s{^//}{}; # unc path |
1181
|
0
|
|
|
|
|
|
$s =~ s{\.\./}{}g; # .. 'up' in hierarchy to |
1182
|
0
|
|
|
|
|
|
$s =~ s{([^<])}{$1}g; # correct last to |
1183
|
0
|
|
|
|
|
|
$s =~ s{/}{}g; # 'component' separators |
1184
|
0
|
|
|
|
|
|
$s =~ s{^([^<])}{$1}; # start relative path with |
1185
|
0
|
|
|
|
|
|
$s =~ s{`}{\\Q}g; # backtick |
1186
|
0
|
|
|
|
|
|
$s =~ s{'}{\\q}g; # single straight quote |
1187
|
0
|
|
|
|
|
|
$s =~ s{([\x81-\xff])}{'\x' . sprintf('%lx', ord $1) . ' '}ge; # high chars |
|
0
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
|
1189
|
0
|
|
|
|
|
|
return "`$s'"; |
1190
|
|
|
|
|
|
|
} |
1191
|
|
|
|
|
|
|
|
1192
|
|
|
|
|
|
|
=item C |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
Usage: |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
print decode_path ('C:MydirSubdirFilename'); |
1197
|
|
|
|
|
|
|
# prints C:/Mydir/Subdir/Filename |
1198
|
|
|
|
|
|
|
print decode_path ('SubdirFilename'); |
1199
|
|
|
|
|
|
|
# prints ../../Subdir/Filename |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
Currently only Windows path names are supported (meaning that Unix and MacOS |
1202
|
|
|
|
|
|
|
style paths remain untested). MIF string quotes are removed. ROOTPATH, if |
1203
|
|
|
|
|
|
|
specified, is the path that is prepended if STRING happens to be a relative |
1204
|
|
|
|
|
|
|
path. |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
=cut |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
sub decode_path { |
1209
|
0
|
|
|
0
|
1
|
|
my ($s, $root) = @_[0, 1]; |
1210
|
0
|
0
|
|
|
|
|
return unless defined $s; |
1211
|
0
|
|
0
|
|
|
|
($root ||= '') =~ s{([^\\/])$}{$1/}; # add slash if necessary |
1212
|
|
|
|
|
|
|
|
1213
|
0
|
0
|
|
|
|
|
$s =~ s{^`}{} && $s =~ s{'$}{}; |
1214
|
|
|
|
|
|
|
|
1215
|
0
|
0
|
|
|
|
|
$root = '' unless $s =~ m{^<[cu]\\>}; # only use $root when |
1216
|
|
|
|
|
|
|
# relative path is found |
1217
|
0
|
|
|
|
|
|
$s =~ s{}{}; |
1218
|
0
|
|
|
|
|
|
$s =~ s{}{//}; |
1219
|
0
|
|
|
|
|
|
$s =~ s{()?}{../}g; |
1220
|
0
|
|
|
|
|
|
$s =~ s{^}{}g; # path starting with indicates relative path name |
1221
|
0
|
|
|
|
|
|
$s =~ s{}{/}g; |
1222
|
0
|
|
|
|
|
|
$s =~ s{\\q}{'}g; # single quote |
1223
|
0
|
|
|
|
|
|
$s =~ s{\\Q}{`}g; # backtick |
1224
|
0
|
|
|
|
|
|
$s =~ s{\\x([a-f0-9]{2}) ?}{chr hex $1}ge; # escaped non-ASCII chars |
|
0
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
|
1226
|
0
|
|
|
|
|
|
return "$root$s"; |
1227
|
|
|
|
|
|
|
} |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
=item C |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
Converts a value in one unit of measurement into another. If you leave out the |
1232
|
|
|
|
|
|
|
unit of measurement it defaults to FrameMaker::MifTree->default_unit (not to the |
1233
|
|
|
|
|
|
|
MIF document's default unit of measurement!). Other measurements are: |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
{ |
1236
|
|
|
|
|
|
|
pt => 1 / 72, |
1237
|
|
|
|
|
|
|
point => 1 / 72, |
1238
|
|
|
|
|
|
|
" => 1, |
1239
|
|
|
|
|
|
|
in => 1, |
1240
|
|
|
|
|
|
|
mm => 1 / 25.4, |
1241
|
|
|
|
|
|
|
millimeter => 1 / 25.4, |
1242
|
|
|
|
|
|
|
cm => 1 / 2.54, |
1243
|
|
|
|
|
|
|
centimeter => 1 / 2.54, |
1244
|
|
|
|
|
|
|
pc => 1 / 6, |
1245
|
|
|
|
|
|
|
pica => 1 / 6, |
1246
|
|
|
|
|
|
|
dd => 0.01483, |
1247
|
|
|
|
|
|
|
didot => 0.01483, |
1248
|
|
|
|
|
|
|
cc => 12 * 0.01483, |
1249
|
|
|
|
|
|
|
cicero => 12 * 0.01483 |
1250
|
|
|
|
|
|
|
} |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
The optional argument SUPPRESSUNIT determines if the unit of measurement needs |
1253
|
|
|
|
|
|
|
to be written in the result. Note that you won't get a unit of measurement |
1254
|
|
|
|
|
|
|
included in your result when you leave out NEWUNIT and specify |
1255
|
|
|
|
|
|
|
Cdefault_unit> to be the empty string, even if you set |
1256
|
|
|
|
|
|
|
SUPPRESSUNIT to be false. In that case the returned value is in points. So |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
FrameMaker::MifTree->default_unit(''); |
1259
|
|
|
|
|
|
|
print convert('12.0 didot'); # prints the value in points: 12.8131 |
1260
|
|
|
|
|
|
|
FrameMaker::MifTree->default_unit('mm'); |
1261
|
|
|
|
|
|
|
print convert('12.0 didot', 'pt', 1); # also prints 12.8131 |
1262
|
|
|
|
|
|
|
FrameMaker::MifTree->default_unit('pt'); |
1263
|
|
|
|
|
|
|
print convert('12.0 didot', '', 1); # also prints 12.8131 |
1264
|
|
|
|
|
|
|
|
1265
|
|
|
|
|
|
|
All values are rounded to 4 decimals. |
1266
|
|
|
|
|
|
|
|
1267
|
|
|
|
|
|
|
=cut |
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
sub convert { |
1270
|
0
|
|
|
0
|
1
|
|
my ($num_val, $old_unit) = shift =~ /(-?\d*\.?\d+)\s*(\D*)/; |
1271
|
0
|
|
0
|
|
|
|
my $new_unit = shift || $default_unit; |
1272
|
0
|
|
|
|
|
|
my $suppress_unit = shift; |
1273
|
0
|
|
0
|
|
|
|
$old_unit ||= $default_unit; |
1274
|
0
|
|
|
|
|
|
$old_unit =~ s/\s//g; |
1275
|
0
|
|
|
|
|
|
$new_unit =~ s/\s//g; |
1276
|
0
|
|
|
|
|
|
my $new_value = sprintf '%.4f', |
1277
|
|
|
|
|
|
|
$num_val * $unit_to_factor{$old_unit} / |
1278
|
|
|
|
|
|
|
$unit_to_factor{$new_unit}; |
1279
|
0
|
0
|
0
|
|
|
|
$new_unit = " $new_unit" unless $new_unit eq q(") || $new_unit eq ''; |
1280
|
0
|
0
|
|
|
|
|
return $new_value . ($suppress_unit ? '' : $new_unit); |
1281
|
|
|
|
|
|
|
} |
1282
|
|
|
|
|
|
|
|
1283
|
3
|
|
|
3
|
|
9925
|
END {} # Global destructor |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
1; |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
__END__ |