home *** CD-ROM | disk | FTP | other *** search
/ Acorn User 10 / AU_CD10.iso / Updates / Perl / Non-RPC / !Perl / riscos / Text / DDF.pm
Text File  |  1998-07-12  |  11KB  |  595 lines

  1. package Text::DDF;
  2.  
  3. require Exporter;
  4. require 5.003;
  5.  
  6. $VERSION = 0.01;
  7. @ISA = Exporter;
  8. @EXPORT = qw(&ddfencode);
  9. @EXPORT_OK = qw($strict_7bit);
  10.  
  11. $strict_7bit = 0;
  12.  
  13. sub ddfencode {
  14.     foreach (@_)
  15.     {
  16.     s/\{/\{\\123\}/g;
  17.     }
  18.     if( $strict_7bit )
  19.     {
  20.     foreach (@_)
  21.     {
  22.         s/[\200-\377]/'{\\' . ord ($&) . '}'/ge;
  23.     }
  24.     }
  25.     @_;
  26. }
  27.  
  28. =head1 NAME
  29.  
  30. Text::DDF -- package for creating Impression Document Description Format
  31.  
  32. =head1 SYNOPSIS
  33.  
  34.     use Text::DDF
  35.     
  36.     print ddfencode(@text);
  37.  
  38.  
  39.     use Text::DDF qw(ddfencode $strict_7bit);
  40.     
  41.     $strict_7bit = 1;
  42.     ddfencode(@text);
  43.  
  44.     $index = new Text::DDF::Style 'Index', qw (index on);
  45.     print $index->StyleWrap( @text );
  46.  
  47. =head1 DESCRIPTION
  48.  
  49. B<Text::DDF> provides subroutines and classes for creating files in Impression
  50. B<D>ocument B<D>escription B<F>ormat.
  51.  
  52. C<Text::DDF::ddfencode> converts the array to C<DDF>, converting all
  53. instances of 'C<{>' to 'C<{\123}>'. If C<Text::DDF::strict_7bit> is true,
  54. it will also convert top-bit-set characters to 'C<{\###}>' notation.
  55.  
  56. C<Text::DDF::ddfencode> modifies the array passed, and for ease of use
  57. additionally returns the array that it converted.
  58.  
  59. =head2 Effects and Styles
  60.  
  61. C<Text::DDF> provides classes for manipulating effects and styles -
  62. C<Text::DDF::Effect>, C<Text::DDF::Justify>, C<Text::DDF::Style>. The overloaded
  63. interface hides the differences in the DDF syntax used to turn styles and
  64. effects on and off.
  65.  
  66. =head2 Common methods
  67.  
  68. =over 4
  69.  
  70. =item new <name> ... 
  71.  
  72. Declare a style/effect with this name.
  73.  
  74.     $bold = new Text::DDF::Effect 'bold';
  75.     $centre = new Text::DDF::Justify 'centre';
  76.     $normal = new Text::DDF::Style 'Normal', qw(
  77.       font Trinity.Medium
  78.       fontsize 12pt );
  79.  
  80. Arguments after the style name are passed to the C<Add> method
  81.  
  82.     $no_op = new Text::DDF::Effect;
  83.  
  84. defines an empty effect
  85.  
  86. =item StyleOn
  87.  
  88. =item StyleOff
  89.  
  90. Return the C<DDF> instruction to turn the style/effect on/off.
  91.  
  92. =item StyleWrap
  93.  
  94. Return the C<DDF> turn the style/effect on, the verbatim text, the DDF to
  95. turn the style/effect off. In array context returns an array:
  96.  
  97.     (
  98.      StyleOn(),
  99.      @_,
  100.      StyleOf()
  101.     )
  102.  
  103. In scalar context this array is concatenated
  104.  
  105. =item Define()
  106.  
  107. Returns the C<DDF> needed to defined the style/effect. Returns an empty string
  108. for an effect, so that a mixed array of effects and styles can be C<Define>d
  109. safely.
  110.  
  111. =item Copy <newname>
  112.  
  113. Returns a new style/effect clone. (with the same class as its parent.)
  114.  
  115. =item Print
  116.  
  117. Returns a formatted definition of the style/effect. This is mostly of use to the
  118. C<Define> method.
  119.  
  120. In array context effects return an empty array, whereas styles return an array
  121. of lines.
  122.  
  123. In scalar context styles return text of the form:
  124.  
  125.     style  "Normal";
  126.      font Trinity.Medium;
  127.      fontsize 12pt;
  128.  
  129. effects return:
  130.     effect bold;
  131.  
  132. B<NB> scalars are terminated by "C<\n>"
  133.  
  134. =back
  135.  
  136. =head2 Style Methods
  137.  
  138. =over 4
  139.  
  140. =item Add
  141.  
  142. Takes pairs of C<attribute, value> and adds them to the style definition.
  143.  
  144.     $verbatim->Add( qw(spacebelow 0pt) );
  145.  
  146. You can use C<AddTabs> and C<AddTabComma> to add tabs, but the C<Add> method
  147. will spot lines of the form
  148.  
  149.     Add( 'tabs', 'r18pt 24pt' );
  150.  
  151. and call C<AddTabsComma>.
  152.  
  153. =item AddTabs
  154.  
  155. Takes an array of tabs, and adds them to the style's array of tabs.
  156.  
  157. Tabs are strings such as
  158.  
  159.     r18pt
  160.     25.4mm
  161.     c4pi
  162.  
  163. The available prefixes are
  164.  
  165. =over 2
  166.  
  167. =item l
  168.  
  169. Left justify tab (the default if no prefix)
  170.  
  171. =item c
  172.  
  173. Centre justify tab
  174.  
  175. =item r
  176.  
  177. Right justify tab
  178.  
  179. =item d
  180.  
  181. Decimal tab
  182.  
  183. =item v
  184.  
  185. Vertical rule off
  186.  
  187. =back
  188.  
  189.     $itemlist->AddTabs( qw( r18pt 24pt 72pt 144pt 216pt 288pt 360pt 432pt 504pt ) );
  190.  
  191. =item AddTabsComma
  192.  
  193. Adds a comma separated list of tabs. Literally:
  194.  
  195.     sub AddTabsComma {
  196.     my $self = shift;
  197.     $self->AddTabs( split (/,/, $_[0]) );
  198.     }
  199.  
  200. =item Get <attribute> ...
  201.  
  202. Returns an array of values corresponding to the array of attributes
  203. passed
  204.  
  205. =item Delete <attribute> ...
  206.  
  207. Delete values corresponding to the array of attributes passed
  208.  
  209. =item GetTabs
  210.  
  211. In array context returns the sorted array of tabs in the format
  212.  
  213.     right 18pt
  214.     centre 3pi
  215.      25.5mm
  216.  
  217. In scalar context joins the list with commas
  218.  
  219.     right 18pt,centre 3pi, 25.5mm
  220.  
  221. =item DeleteTabs
  222.  
  223. Not implemented yet.
  224.  
  225. =item PrintTabs
  226.  
  227. In array context returns the sorted array of tabs in the format
  228.  
  229.     right 18pt
  230.     centre 3pi
  231.      25.5mm
  232.  
  233. In scalar context joins the list with commas and prefixes 'tabs'
  234.  
  235.     tabs right 18pt,centre 3pi, 25.5mm
  236.     
  237. or returns '' if there are no tabs defined.
  238.  
  239. =back
  240.  
  241. =head1 BUGS
  242.  
  243. =head2 Shortcomings of Impression's DDF interpreter
  244.  
  245. =over 4
  246.  
  247. =item *
  248.  
  249. DDF effects don't nest, whereas styles do.
  250.  
  251.     {bold on}Hello {bold on}W{bold}orld!{bold}
  252.  
  253.     {"bold" on}Hello {"bold" on}W{"bold"}orld!{"bold"}
  254.  
  255. produce
  256.  
  257. B<Hello W>orld!
  258.  
  259. B<Hello World!>
  260.  
  261. respectively if C<"bold"> is a style with the C<bold> attribute.
  262.  
  263. =item *
  264.  
  265. Even in Impression 4.11 the DDF exporter can't cope with style names
  266. containing C<"> characters. Text::DDF::Style makes no checks about
  267. the validity of characters in the style name.
  268.  
  269. =item *
  270.  
  271. There's no way to C<{keepregion}> two adjacent regions.
  272.  
  273.     {keepregion on}Pugh, Pugh, Barney McGrew,
  274.     Cuthbert, Dibble, Grub
  275.     {keepregion}{keepregion on}Time files by when you're the driver of a train,
  276.     and you ride on the footplate there and back again
  277.     {keepregion}
  278.  
  279. B<won't> allow a breaks between the two regions.
  280.  
  281.     {keepregion on}Pugh, Pugh, Barney McGrew,
  282.     Cuthbert, Dibble, Grub{keepregion}
  283.     {keepregion on}Time files by when you're the driver of a train,
  284.     and you ride on the footplate there and back again
  285.     {keepregion}
  286.  
  287. B<will> allow a break between I<Pugh, Pugh, Barney McGrew,> and
  288. I<Cuthbert, Dibble, Grub>.
  289.  
  290. =back
  291.  
  292. =head2 Shortcomings of this package
  293.  
  294. You can't (yet) merge styles.
  295.  
  296. You can't (yet) merge effects into styles. (This would require a hash/function
  297. to map effect names into style attributes).
  298.  
  299.  
  300. =head1 AUTHOR
  301.  
  302. Nicholas Clark <F<nick@unfortu.net>>
  303.  
  304. =cut
  305.  
  306. #    Ruler ISA Effect
  307. #    Style ISA Ruler
  308. # Internal keys start __
  309.  
  310. ################################################################################
  311. package Text::DDF::Effect;
  312.  
  313. use strict;
  314. use vars qw($VERSION);
  315.  
  316. $VERSION = 0.01;
  317.  
  318. # Declare an effect.
  319. sub new {
  320.     my $proto = shift;
  321.     my $class = ref($proto) || $proto;
  322.     my $self  = {};
  323.     my $name  = shift;
  324.     
  325.     $name = '' unless defined $name;
  326.     
  327.     $self->{'__NAME'} = $name;
  328.     bless ($self, $class);
  329. }
  330.  
  331. # Turn the effect/style on
  332. #
  333. # DDF Syntax differs
  334. #
  335. # {bold on}    turns on the effect 'bold'
  336. # {"bold" on}    turns on the style "bold" (which isn't the same thing)
  337. #
  338. # But you don't need to know this with an OO Interface. 8-)
  339.  
  340. sub StyleOn {
  341.     my $self = shift;
  342.  
  343.     return $self->{'__NAME'} ? "{$self->{'__NAME'} on}" : '';
  344. }
  345.  
  346. sub StyleOff {
  347.     my $self = shift;
  348.  
  349.     return $self->{'__NAME'} ? "{$self->{'__NAME'}}" : '';
  350. }
  351.  
  352.  
  353. sub StyleWrap {
  354.     my $self = shift;
  355.  
  356.     if( wantarray )
  357.     {
  358.     return ( $self->StyleOn(), @_, $self->StyleOff() );
  359.     }
  360.     else
  361.     {
  362.     return ( $self->StyleOn() . join( '', @_) . $self->StyleOff() );
  363.     }
  364. }
  365.  
  366. sub Print {
  367.     return () if( wantarray );
  368.     
  369.     my $self = shift;
  370.     return $self->{'__NAME'} ? "effect $self->{'__NAME'};\n" : '';
  371. }
  372.  
  373. sub Define {
  374.     '';        # Effects don't need defining.
  375. }
  376.  
  377. sub Copy {
  378.     my $self = shift;
  379.     my $clone = {%$self};
  380.     bless ($clone, ref($self));
  381.     $clone->{'__NAME'} = $_[0] if defined $_[0];
  382.     $clone;
  383. }
  384.  
  385. ################################################################################
  386. package Text::DDF::Justify;
  387.  
  388. use strict;
  389. use vars qw($VERSION);
  390.  
  391. $VERSION = 0.01;
  392.  
  393. # Declare a justification effect. These differ only in how they are applied
  394.  
  395. sub StyleOn {
  396.     my $self = shift;
  397.  
  398.     return "{justify $self->{'__NAME'}}";
  399. }
  400. sub StyleOff {
  401.     return '{justify}';
  402. }
  403.  
  404. ################################################################################
  405. package Text::DDF::Ruler;
  406.  
  407. use strict;
  408. use vars qw($VERSION @ISA %tabs %special);
  409.  
  410.  
  411. @ISA = 'Text::DDF::Effect';
  412. $VERSION = 0.01;
  413.  
  414. use Carp;
  415. use Units;
  416.  
  417. %tabs = ( l => '', r => 'right', c => 'centre', d => 'decimal', v => 'vertrule' );
  418.  
  419. %special = (    'tabs' => [\&AddTabsComma, \&GetTabs, sub {delete shift->{'__TABS'}}] );
  420.  
  421. sub quote ($) {
  422.     # Will get improved to deal with style names with embedded "
  423.     return "\"$_[0]\"";
  424. }
  425.  
  426. sub __new ($;) {
  427.     my $proto = shift;
  428.     my $class = ref($proto) || $proto;
  429.     my $self  = {};
  430.     $self->{'__NAME'} = shift;
  431.     bless ($self, $class);
  432.  
  433.     $self->Add( @_ );
  434. }
  435.  
  436. sub new ($;) {
  437.     push @_, 'effectruler', 'on';
  438.     goto &__new
  439. }
  440.  
  441. sub Name {
  442.     my $self = shift;
  443.     my $oldname = $self->{'__NAME'};
  444.     $self->{'__NAME'} = $_[0] if( defined $_[0] );
  445.     $oldname;
  446. }
  447.  
  448. sub AddTabs {
  449.     my $self = shift;
  450.     foreach (@_)
  451.     {
  452.     my ($type,$pos) = m/^\s*([rlcdv]?)(.*)/io;
  453.     $type = 'l' unless length $type;
  454.     my $value = convert( 'mm', $pos );
  455.     ${$self->{'__TABS'}}{$value} = "$type$pos";
  456.     }
  457. }
  458.  
  459. sub AddTabsComma {
  460.     my $self = shift;
  461.     $self->AddTabs( split (/,/, $_[0]) );
  462. }
  463.  
  464. sub Add {
  465.     my $self = shift;
  466.     my ($class, $value);
  467.  
  468.     while( $class = shift, $value = shift, defined $value )
  469.     {
  470.     if( defined $special{$class} )
  471.     {
  472.         &{$special{$class}->[0]} ( $self, $value );
  473.     }
  474.     else
  475.     {
  476.         $self->{$class} = $value;
  477.     }
  478.             
  479.     }
  480.  
  481.     $self;
  482. }
  483.  
  484. sub GetTabs {
  485.     my $self = shift;
  486.     my @result;
  487.  
  488.     foreach (sort { $a <=> $b } keys %{$self->{'__TABS'}})
  489.     {
  490.     my ($type, $pos) = ${$self->{'__TABS'}}{$_} =~ /(.)(.*)/;
  491.     push @result, "$tabs{$type} $pos";
  492.     }
  493.  
  494.     return @result if wantarray;
  495.  
  496.     return join( ',', @result );
  497. }
  498.  
  499. sub Get {
  500.     my $self = shift;
  501.     my (@result, $class);
  502.  
  503.     while( defined( $class = shift ) )
  504.     {
  505.     if( defined $special{$class} )
  506.     {
  507.         push @result, &{$special{$class}->[1]} ( $self );
  508.     }
  509.     else
  510.     {
  511.         push @result, $self->{$class};
  512.     }
  513.     }
  514.  
  515.     @result;
  516. }
  517.  
  518. sub Delete {
  519.     my $self = shift;
  520.     my $class;
  521.  
  522.     while( defined( $class = shift ) )
  523.     {
  524.     if( defined $special{$class} )
  525.     {
  526.         &{$special{$class}->[2]} ( $self );
  527.     }
  528.     else
  529.     {
  530.         delete $self->{$class};
  531.     }
  532.     }
  533. }
  534.  
  535. sub PrintTabs {
  536.     my $self = shift;
  537.     return $self->GetTabs() if wantarray;
  538.  
  539.     my $answer = scalar $self->GetTabs();
  540.     return length( $answer ) ? "tabs $answer" : '';
  541. }
  542.  
  543. sub Print {
  544.     my $self = shift;
  545.     my @result = ("style " . quote ($self->{'__NAME'}));
  546.     my $tabs = scalar $self->PrintTabs();
  547.     
  548.     push @result, $tabs unless $tabs eq '';
  549.     
  550.     my ($key, $value);
  551.     while (($key,$value) = each %$self) {
  552.     push @result, "$key $value" unless $key =~ /^__/o;
  553.     }
  554.  
  555.     return @result if( wantarray );
  556.  
  557.     return (join ";\n ", @result) . ";\n";
  558. }
  559.  
  560. sub Define {
  561.     my $self = shift;
  562.  
  563.     my $result = $self->Print;
  564.     $result =~ s/;\s$//m;
  565.     return "{\ndefine $result}";
  566. }
  567.  
  568. sub StyleOn {
  569.     my $self = shift;
  570.  
  571.     return '{' . quote ($self->{'__NAME'}) . ' on}';
  572. }
  573.  
  574. sub StyleOff {
  575.     my $self = shift;
  576.  
  577.     return '{' . quote ($self->{'__NAME'}) . ' off}';
  578. }
  579.  
  580. ################################################################################
  581. package Text::DDF::Style;
  582.  
  583. use strict;
  584. use vars qw($VERSION @ISA);
  585.  
  586. @ISA = 'Text::DDF::Ruler';
  587. $VERSION = 0.03;
  588.  
  589.  
  590. sub new ($;) {
  591.     goto &Text::DDF::Ruler::__new
  592. }
  593.  
  594. 1;
  595.