Projekt

Allgemein

Profil

Herunterladen (3,87 KB) Statistiken
| Zweig: | Markierung: | Revision:
d4458803 Moritz Bunkus
package SL::Presenter::EscapedText;

use strict;
0e5e3501 Sven Schöling
use Exporter qw(import);
1e0c2679 Sven Schöling
use Scalar::Util qw(looks_like_number);
0e5e3501 Sven Schöling
1e0c2679 Sven Schöling
our @EXPORT_OK = qw(escape is_escaped escape_js escape_js_call);
0e5e3501 Sven Schöling
our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
d4458803 Moritz Bunkus
7647d46a Moritz Bunkus
use JSON ();

0e5e3501 Sven Schöling
use overload '""' => \&escaped_text;
d4458803 Moritz Bunkus
be5cae09 Sven Schöling
my %html_entities = (
'<' => '&lt;',
'>' => '&gt;',
'&' => '&amp;',
'"' => '&quot;',
"'" => '&apos;',
);

0e5e3501 Sven Schöling
# static constructors
d4458803 Moritz Bunkus
sub new {
my ($class, %params) = @_;

return $params{text} if ref($params{text}) eq $class;

my $self = bless {}, $class;
be5cae09 Sven Schöling
$self->{text} = $params{is_escaped} ? $params{text} : quote_html($params{text});
d4458803 Moritz Bunkus
return $self;
}

be5cae09 Sven Schöling
sub quote_html {
return undef unless defined $_[0];
(my $x = $_[0]) =~ s/(["'<>&])/$html_entities{$1}/ge;
$x
}

0e5e3501 Sven Schöling
sub escape {
__PACKAGE__->new(text => $_[0]);
}

sub is_escaped {
__PACKAGE__->new(text => $_[0], is_escaped => 1);
}

sub escape_js {
my ($text) = @_;

$text =~ s|\\|\\\\|g;
$text =~ s|\"|\\\"|g;
$text =~ s|\n|\\n|g;

__PACKAGE__->new(text => $text, is_escaped => 1);
}

1e0c2679 Sven Schöling
sub escape_js_call {
my ($func, @args) = @_;

escape(
sprintf "%s(%s)",
escape_js($func),
join ", ", map {
looks_like_number($_)
? $_
: '"' . escape_js($_) . '"'
} @args
);
}

0e5e3501 Sven Schöling
# internal magic
sub escaped_text {
d4458803 Moritz Bunkus
my ($self) = @_;
return $self->{text};
}

7647d46a Moritz Bunkus
sub TO_JSON {
0e5e3501 Sven Schöling
goto &escaped_text;
7647d46a Moritz Bunkus
}

d4458803 Moritz Bunkus
1;
__END__

=pod

=encoding utf8

=head1 NAME

0e5e3501 Sven Schöling
SL::Presenter::EscapedText - Thin proxy object to invert the burden of escaping HTML output
d4458803 Moritz Bunkus
=head1 SYNOPSIS

0e5e3501 Sven Schöling
use SL::Presenter::EscapedText qw(escape is_escaped escape_js);
d4458803 Moritz Bunkus
sub blackbox {
my ($text) = @_;
return SL::Presenter::EscapedText->new(text => $text);
0e5e3501 Sven Schöling
# or shorter:
# return escape($text);
d4458803 Moritz Bunkus
}

sub build_output {
my $output_of_other_component = blackbox('Hello & Goodbye');

# The following is safe, text will not be escaped twice:
return SL::Presenter::EscapedText->new(text => $output_of_other_component);
}

my $output = build_output();
print "Yeah: $output\n";

=head1 OVERVIEW

Sometimes it's nice to let a sub-component build its own
representation. However, you always have to be very careful about
whose responsibility escaping is. Only the building function knows
enough about the structure to be able to HTML escape properly.

But higher functions should not have to care if the output is already
escaped -- they should be able to simply escape it again. Without
producing stuff like '&amp;amp;'.

0e5e3501 Sven Schöling
Stringification is overloaded. It will return the same as L<escaped_text>.
d4458803 Moritz Bunkus
This works together with the template plugin
L<SL::Template::Plugin::P> and its C<escape> method.

=head1 FUNCTIONS

=over 4

=item C<new %params>

Creates an instance of C<EscapedText>.

The parameter C<text> is the text to escape. If it is already an
instance of C<EscapedText> then C<$params{text}> is returned
unmodified.

Otherwise C<text> is HTML-escaped and stored in the new instance. This
can be overridden by setting C<$params{is_escaped}> to a trueish
value.

0e5e3501 Sven Schöling
=item C<escape $text>

Static constructor, can be exported. Equivalent to calling C<< new(text => $text) >>.

=item C<is_escaped $text>

Static constructor, can be exported. Equivalent to calling C<< new(text => $text, escaped => 1) >>.

=item C<escape_js $text>

Static constructor, can be exported. Like C<escape> but also escapes Javascript.

1e0c2679 Sven Schöling
=item C<escape_js_call $func_name, @args>

Static constructor, can be exported. Used to construct a javascript call than
can be used for onclick handlers in other Presenter functions.

For example:

L.button_tag(
P.escape_js_call("kivi.Package.some_func", arg_one, arg_two, arg_three)
title
)

0e5e3501 Sven Schöling
=back

=head1 METHODS

=over 4

=item C<escaped_text>
d4458803 Moritz Bunkus
Returns the escaped string (not an instance of C<EscapedText> but an
actual string).

=back

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut