package SL::Presenter::EscapedText;
use strict;
+use Exporter qw(import);
+use Scalar::Util qw(looks_like_number);
-use overload '""' => \&escaped;
+our @EXPORT_OK = qw(escape is_escaped escape_js escape_js_call);
+our %EXPORT_TAGS = (ALL => \@EXPORT_OK);
+use JSON ();
+
+use overload '""' => \&escaped_text;
+
+my %html_entities = (
+ '<' => '<',
+ '>' => '>',
+ '&' => '&',
+ '"' => '"',
+ "'" => ''',
+);
+
+# static constructors
sub new {
my ($class, %params) = @_;
return $params{text} if ref($params{text}) eq $class;
my $self = bless {}, $class;
- $self->{text} = $params{is_escaped} ? $params{text} : $::locale->quote_special_chars('HTML', $params{text});
+ $self->{text} = $params{is_escaped} ? $params{text} : quote_html($params{text});
return $self;
}
-sub escaped {
+sub quote_html {
+ return undef unless defined $_[0];
+ (my $x = $_[0]) =~ s/(["'<>&])/$html_entities{$1}/ge;
+ $x
+}
+
+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);
+}
+
+sub escape_js_call {
+ my ($func, @args) = @_;
+
+ escape(
+ sprintf "%s(%s)",
+ escape_js($func),
+ join ", ", map {
+ looks_like_number($_)
+ ? $_
+ : '"' . escape_js($_) . '"'
+ } @args
+ );
+}
+
+# internal magic
+sub escaped_text {
my ($self) = @_;
return $self->{text};
}
+sub TO_JSON {
+ goto &escaped_text;
+}
+
1;
__END__
=head1 NAME
-SL::Presenter::EscapedText - Thin proxy object around HTML-escaped strings
+SL::Presenter::EscapedText - Thin proxy object to invert the burden of escaping HTML output
=head1 SYNOPSIS
- use SL::Presenter::EscapedText;
+ use SL::Presenter::EscapedText qw(escape is_escaped escape_js);
sub blackbox {
my ($text) = @_;
return SL::Presenter::EscapedText->new(text => $text);
+
+ # or shorter:
+ # return escape($text);
}
sub build_output {
escaped -- they should be able to simply escape it again. Without
producing stuff like '&amp;'.
-Stringification is overloaded. It will return the same as L<escaped>.
+Stringification is overloaded. It will return the same as L<escaped_text>.
This works together with the template plugin
L<SL::Template::Plugin::P> and its C<escape> method.
can be overridden by setting C<$params{is_escaped}> to a trueish
value.
-=item C<escaped>
+=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.
+
+=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
+ )
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item C<escaped_text>
Returns the escaped string (not an instance of C<EscapedText> but an
actual string).