Bivio::UI::Widget::Prose
# Copyright (c) 2000-2006 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::UI::Widget::Prose; use strict; use Bivio::Base 'Bivio::UI::Widget'; use Bivio::IO::Trace; use Bivio::UI::ViewLanguage; use Bivio::UI::Widget::Join; use Bivio::UI::Widget; # C<Bivio::UI::Widget::Prose> defines a language of text comingled with widgets, # widget values, and view values. The text can be in any output language, # e.g. XML, HTML, or rfc822. The intent is for the text and dynamic values to be # free-form. Prose widgets are probably not appropriate for displaying program # source. # # The text looks like whatever output language you are using. You can # insert a simple L<Bivio::UI::ViewLanguage|Bivio::UI::ViewLanguage> # function call right in the text: # # Here is some text. Here is a dynamic vs_any_value();. # # The function C<vs_any_value> is an application specific shortcut. You # can insert widgets the same way: # # Here is an Image('my_image', 'my alt text'); in the middle. # # Any ViewLanguage function call can be inserted as long as it does not # contain the code sequence C<);> (close parethesis followed immediately # by a semicolon). # # You can escape a word followed by an open parethesis as follows: # # My text with escape<(>s) # # This sequence is a bit cumbersome to type, but is unlikely to occur # in any of the common text formatting languages or in source text. # Ideally, you would be able to insert a space between the word (C<escape> # in this case) and the open parenthesis, e.g. # # My text with escape (s) # # However, this is cumbersome in certain languages, hence the escape # mechanism. # # You can enter more complex ViewLanguage programs by bracketing the # programs as follows: # # Here is a complex <{ # if (vs_some_condition()) { # vs_do_this(); # else { # vs_do_that(); # } # } # }> and some more text here. # # Currently, nested bracketing is not supported. You can escape a # E<lt>{ or }E<gt> sequence using the same bracketing technique around # the angle brackets, e.g. # # This is my escaped opening program bracket <<>{ # and my escaped closing bracket }<>>. # You can also escape a closing()<;> # # Note that any E<lt>E<lt>E<gt> and E<lt>E<gt><gt> sequences in the # text will be unescaped when processing. # # # # value : string (required) # # value : string_ref (required) # # I<value> is parsed as described above and the result is put # on I<self> as I<values> for the Join widget (superclass). our($_TRACE); sub initialize { my($self) = @_; # Initializes widget state and children. if (ref(my $v = $self->get('value'))) { $self->initialize_attr('value'); } else { $self->put(_join => Bivio::UI::Widget::Join->new(_parse($v))); } return; } sub internal_new_args { my(undef, $value, $attributes) = @_; # Implements positional argument parsing for L<new|"new">. return "'value' must be defined" unless defined($value); return { value => $value, ($attributes ? %$attributes : ()), }; } sub render { my($self, $source, $buffer) = @_; ($self->unsafe_get('_join') || $self->initialize_value( 'value', Bivio::UI::Widget::Join->new( _parse($self->render_simple_attr('value', $source)), ), ))->render($source, $buffer); return; } sub _parse { my($value) = @_; # Parses the string and returns an array_ref for the join. my($res) = [ map($_ =~ s/^\<\{// ? _parse_code($_) : _parse_text($_), split(/(?=\<\{)|(?<=\}\>)/, ref($value) ? $$value : $value)), ]; _trace($res) if $_TRACE; return $res; } sub _parse_code { my($code) = @_; # Parses the code and returns the result of the eval. Bivio::Die->die($code, ': missing Prose program terminator "}>"') unless $code =~ s/\}\>$//; return Bivio::UI::ViewLanguage->eval(\$code); } sub _parse_text { my($text) = @_; # Called for text with embedded function calls. my(@res, $bit); while (length($text)) { unless ($text =~ /^(?:[a-zA-Z]\w+|[A-Z])\(/) { ($bit, $text) = split(/(?=\b(?:[a-zA-Z]\w+|[A-Z])\()/, $text, 2); # Unescape any specials in <>. $bit =~ s/\<([\<\>\{\}\(\);])\>/$1/g if $bit; push(@res, $bit); last unless defined($text) && length($text); } # We have a function at the start of $text. Strip it off and # leave in $bit. $text will contain the rest $bit = $text; Bivio::Die->die($bit, ': missing Prose function terminator ");"') unless $bit =~ s/\);(.*)//s; $text = $1; # Unescape any escaped values in perl code $bit =~ s/\<([\<\>\{\}\(\);])\>/$1/g; push(@res, map { Bivio::Die->die($_, ': invalid value in Prose function: ', $bit) unless defined($_) && (UNIVERSAL::isa($_, 'Bivio::UI::Widget') || ref($_) eq 'ARRAY' || !ref($_)); $_; } _parse_code($bit.');}>')); } return @res; } 1;