File Coverage

lib/FrameMaker/MifTree.pm
Criterion Covered Total %
statement 171 373 45.8
branch 57 222 25.6
condition 27 136 19.8
subroutine 30 56 53.5
pod 37 37 100.0
total 322 824 39.0


if you don't want it.
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/^
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
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__