This test validate Template Toolkit (TT) Koha files.
For the time being an unique validation is done: Test if TT files
contain TT directive within HTML tag. For example:
<li[% IF
This kind of constuction MUST be avoided because it break Koha
translation process.
This patch transform also translation specific modules into C4 modules
in order to be able to use them in test case:
C4::TTPaser
C4::TmplToken
C4::TmplTokenType
This patch is a Perl adaptation of a Haskell script from Frère Sébastien
Marie.
Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Notes on testing:
- translate install de-DE - worked ok
- translate update de-DE > translate install de-DE - worked ok
- running the test xt/tt_valid.t - worked ok and pointed out lots of problems.
Found no problems.
Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Signed-off-by: Chris Cormack <chrisc@catalyst.net.nz>
#!/usr/bin/env perl
#simple parser for HTML with Template Toolkit directives. Tokens are put into @tokens and are accesible via next_token and peep_token
-package TTParser;
+package C4::TTParser;
use base qw(HTML::Parser);
-use TmplToken;
+use C4::TmplToken;
use strict;
use warnings;
$self->handler(declaration => "declaration", "self, line, text, is_cdata"); # declaration
$self->handler(comment => "comment", "self, line, text, is_cdata"); # comments
# $self->handler(default => "default", "self, line, text, is_cdata"); # anything else
- $self->marked_sections(1); #treat anything inside CDATA tags as text, should really make it a TmplTokenType::CDATA
+ $self->marked_sections(1); #treat anything inside CDATA tags as text, should really make it a C4::TmplTokenType::CDATA
$self->unbroken_text(1); #make contiguous whitespace into a single token (can span multiple lines)
$self->parse_file($filename);
return $self;
if( $work =~ m/\[%.*?\]/ ){
#everything before this tag is text (or possibly CDATA), add a text token to tokens if $`
if( $` ){
- my $t = TmplToken->new( $`, ($is_cdata? TmplTokenType::CDATA : TmplTokenType::TEXT), $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $`, ($is_cdata? C4::TmplTokenType::CDATA : C4::TmplTokenType::TEXT), $line, $self->{filename} );
push @tokens, $t;
}
#the match itself is a DIRECTIVE $&
- my $t = TmplToken->new( $&, TmplTokenType::DIRECTIVE, $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $&, C4::TmplTokenType::DIRECTIVE, $line, $self->{filename} );
push @tokens, $t;
# put work still to do back into work
$work = $' ? $' : 0;
} else {
# If there is some left over work, treat it as text token
- my $t = TmplToken->new( $work, ($is_cdata? TmplTokenType::CDATA : TmplTokenType::TEXT), $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $work, ($is_cdata? C4::TmplTokenType::CDATA : C4::TmplTokenType::TEXT), $line, $self->{filename} );
push @tokens, $t;
last;
my $line = shift;
my $work = shift; #original text
my $is_cdata = shift;
- my $t = TmplToken->new( $work, ($is_cdata? TmplTokenType::CDATA : TmplTokenType::TEXT), $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $work, ($is_cdata? C4::TmplTokenType::CDATA : C4::TmplTokenType::TEXT), $line, $self->{filename} );
push @tokens, $t;
}
my $line = shift;
my $work = shift; #original text
my $is_cdata = shift;
- my $t = TmplToken->new( $work, ($is_cdata? TmplTokenType::CDATA : TmplTokenType::TEXT), $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $work, ($is_cdata? C4::TmplTokenType::CDATA : C4::TmplTokenType::TEXT), $line, $self->{filename} );
push @tokens, $t;
}
my $line = shift;
my $work = shift; #original text
my $is_cdata = shift;
- my $t = TmplToken->new( $work, ($is_cdata? TmplTokenType::CDATA : TmplTokenType::TEXT), $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $work, ($is_cdata? C4::TmplTokenType::CDATA : C4::TmplTokenType::TEXT), $line, $self->{filename} );
push @tokens, $t;
}
my $tag = shift;
my $hash = shift; #hash of attr/value pairs
my $text = shift; #origional text
- my $t = TmplToken->new( $text, TmplTokenType::TAG, $line, $self->{filename});
+ my $t = C4::TmplToken->new( $text, C4::TmplTokenType::TAG, $line, $self->{filename});
my %attr;
# tags seem to be uses in an 'interesting' way elsewhere..
for my $key( %$hash ) {
my $hash = shift;
my $text = shift;
# what format should this be in?
- my $t = TmplToken->new( $text, TmplTokenType::TAG, $line, $self->{filename} );
+ my $t = C4::TmplToken->new( $text, C4::TmplTokenType::TAG, $line, $self->{filename} );
my %attr;
# tags seem to be uses in an 'interesting' way elsewhere..
for my $key( %$hash ) {
-package TmplToken;
+package C4::TmplToken;
use strict;
#use warnings; FIXME - Bug 2505
-use TmplTokenType;
+use C4::TmplTokenType;
require Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
# FIXME: DIRECTIVE is not necessarily TMPL_VAR !!
sub parameters_and_fields {
my $this = shift;
- return map { $_->type == TmplTokenType::DIRECTIVE? $_:
- ($_->type == TmplTokenType::TAG
+ return map { $_->type == C4::TmplTokenType::DIRECTIVE? $_:
+ ($_->type == C4::TmplTokenType::TAG
&& $_->string =~ /^<input\b/is)? $_: ()}
@{$this->{'_kids'}};
}
# only meaningful for TEXT_PARAMETRIZED tokens
sub anchors {
my $this = shift;
- return map { $_->type == TmplTokenType::TAG && $_->string =~ /^<a\b/is? $_: ()} @{$this->{'_kids'}};
+ return map { $_->type == C4::TmplTokenType::TAG && $_->string =~ /^<a\b/is? $_: ()} @{$this->{'_kids'}};
}
# only meaningful for TEXT_PARAMETRIZED tokens
sub tag_p {
my $this = shift;
- return $this->type == TmplTokenType::TAG;
+ return $this->type == C4::TmplTokenType::TAG;
}
sub cdata_p {
my $this = shift;
- return $this->type == TmplTokenType::CDATA;
+ return $this->type == C4::TmplTokenType::CDATA;
}
sub text_p {
my $this = shift;
- return $this->type == TmplTokenType::TEXT;
+ return $this->type == C4::TmplTokenType::TEXT;
}
sub text_parametrized_p {
my $this = shift;
- return $this->type == TmplTokenType::TEXT_PARAMETRIZED;
+ return $this->type == C4::TmplTokenType::TEXT_PARAMETRIZED;
}
sub directive_p {
my $this = shift;
- return $this->type == TmplTokenType::DIRECTIVE;
+ return $this->type == C4::TmplTokenType::DIRECTIVE;
}
###############################################################################
-package TmplTokenType;
+package C4::TmplTokenType;
use strict;
#use warnings; FIXME - Bug 2505
=head1 NAME
-TmplTokenType.pm - Types of TmplToken objects
+C4::TmplTokenType.pm - Types of TmplToken objects
=head1 DESCRIPTION
BEGIN {
my $new = sub {
- my $this = 'TmplTokenType';#shift;
+ my $this = 'C4::TmplTokenType';#shift;
my $class = ref($this) || $this;
my $self = {};
bless $self, $class;
use strict;
#use warnings; FIXME - Bug 2505
-use TmplTokenType;
-use TmplToken;
-use TTParser;
+use C4::TmplTokenType;
+use C4::TmplToken;
+use C4::TTParser;
use VerboseWarnings qw( pedantic_p error_normal warn_normal warn_pedantic );
require Exporter;
shift;
my ($filename) = @_;
#open my $handle,$filename or die "can't open $filename";
- my $parser = TTParser->new;
+ my $parser = C4::TTParser->new;
$parser->build_tokens( $filename );
bless {
filename => $filename,
sub _formalize{
my $t = shift;
- if( $t->type == TmplTokenType::DIRECTIVE ){
+ if( $t->type == C4::TmplTokenType::DIRECTIVE ){
return '%s';
- } elsif( $t->type == TmplTokenType::TEXT ){
+ } elsif( $t->type == C4::TmplTokenType::TEXT ){
return _formalize_string_cformat( $t->string );
- } elsif( $t->type == TmplTokenType::TAG ){
+ } elsif( $t->type == C4::TmplTokenType::TAG ){
if( $t->string =~ m/^a\b/is ){
return '<a>';
} elsif( $t->string =~ m/^input\b/is ){
}
# internal parametization, used within next_token
-# method that takes in an array of TEXT and DIRECTIVE tokens (DIRECTIVEs must be GET) and return a TmplTokenType::TEXT_PARAMETRIZED
+# method that takes in an array of TEXT and DIRECTIVE tokens (DIRECTIVEs must be GET) and return a C4::TmplTokenType::TEXT_PARAMETRIZED
sub _parametrize_internal{
my $this = shift;
my @parts = @_;
# my $s = "";
# for my $item (@parts){
- # if( $item->type == TmplTokenType::TEXT ){
+ # if( $item->type == C4::TmplTokenType::TEXT ){
# $s .= $item->string;
# } else {
# #must be a variable directive
my $s = join( "", map { _formalize $_ } @parts );
# should both the string and form be $s? maybe only the later? posibly the former....
# used line number from first token, should suffice
- my $t = TmplToken->new( $s, TmplTokenType::TEXT_PARAMETRIZED, $parts[0]->line_number, $this->filename );
+ my $t = C4::TmplToken->new( $s, C4::TmplTokenType::TEXT_PARAMETRIZED, $parts[0]->line_number, $this->filename );
$t->set_children(@parts);
$t->set_form($s);
return $t;
}
# if cformat mode is off, dont bother parametrizing, just return them as they come
return $next unless $self->allow_cformat_p;
- if( $next->type == TmplTokenType::TEXT ){
+ if( $next->type == C4::TmplTokenType::TEXT ){
push @parts, $next;
}
-# elsif( $next->type == TmplTokenType::DIRECTIVE && $next->string =~ m/\[%\s*\w+\s*%\]/ ){
- elsif( $next->type == TmplTokenType::DIRECTIVE ){
+# elsif( $next->type == C4::TmplTokenType::DIRECTIVE && $next->string =~ m/\[%\s*\w+\s*%\]/ ){
+ elsif( $next->type == C4::TmplTokenType::DIRECTIVE ){
push @parts, $next;
}
- elsif ( $next->type == TmplTokenType::CDATA){
+ elsif ( $next->type == C4::TmplTokenType::CDATA){
$self->_set_js_mode(1);
my $s0 = $next->string;
my @head = ();
my $param = $params[$i - 1];
warn_normal "$fmt_0: $&: Expected a TMPL_VAR, but found a "
. $param->type->to_string . "\n", undef
- if $param->type != TmplTokenType::DIRECTIVE;
+ if $param->type != C4::TmplTokenType::DIRECTIVE;
warn_normal "$fmt_0: $&: Unsupported "
. "field width or precision\n", undef
if defined $width || defined $prec;
if (!defined $param) {
warn_normal "$fmt_0: $&: Parameter $i not known", undef;
} else {
- if ($param->type == TmplTokenType::TAG
+ if ($param->type == C4::TmplTokenType::TAG
&& $param->string =~ /^<input\b/is) {
my $type = defined $param->attributes?
lc($param->attributes->{'type'}->[1]): undef;
my $s = TmplTokenizer::next_token $h;
last unless defined $s;
my($kind, $t, $attr) = ($s->type, $s->string, $s->attributes);
- if ($kind eq TmplTokenType::TEXT) {
+ if ($kind eq C4::TmplTokenType::TEXT) {
print $output find_translation($t);
- } elsif ($kind eq TmplTokenType::TEXT_PARAMETRIZED) {
+ } elsif ($kind eq C4::TmplTokenType::TEXT_PARAMETRIZED) {
my $fmt = find_translation($s->form);
print $output TmplTokenizer::parametrize($fmt, 1, $s, sub {
$_ = $_[0];
my($kind, $t, $attr) = ($_->type, $_->string, $_->attributes);
- $kind == TmplTokenType::TAG && %$attr?
+ $kind == C4::TmplTokenType::TAG && %$attr?
text_replace_tag($t, $attr): $t });
- } elsif ($kind eq TmplTokenType::TAG && %$attr) {
+ } elsif ($kind eq C4::TmplTokenType::TAG && %$attr) {
print $output text_replace_tag($t, $attr);
} elsif ($s->has_js_data) {
for my $t (@{$s->js_data}) {
my($x) = @_;
my $t = $x->type;
return !$extract_all_p && (
- $t == TmplTokenType::TEXT? string_negligible_p( $x->string ):
- $t == TmplTokenType::DIRECTIVE? 1:
- $t == TmplTokenType::TEXT_PARAMETRIZED
+ $t == C4::TmplTokenType::TEXT? string_negligible_p( $x->string ):
+ $t == C4::TmplTokenType::DIRECTIVE? 1:
+ $t == C4::TmplTokenType::TEXT_PARAMETRIZED
&& join( '', map { my $t = $_->type;
- $t == TmplTokenType::DIRECTIVE?
- '1': $t == TmplTokenType::TAG?
+ $t == C4::TmplTokenType::DIRECTIVE?
+ '1': $t == C4::TmplTokenType::TAG?
'': token_negligible_p( $_ )?
'': '1' } @{$x->children} ) eq '' );
}
my $s = TmplTokenizer::next_token $h;
last unless defined $s;
my($kind, $t, $attr) = ($s->type, $s->string, $s->attributes);
- if ($kind eq TmplTokenType::TEXT) {
+ if ($kind eq C4::TmplTokenType::TEXT) {
if ($t =~ /\S/s && $t !~ /<!/){
remember( $s, $t );
}
- } elsif ($kind eq TmplTokenType::TEXT_PARAMETRIZED) {
+ } elsif ($kind eq C4::TmplTokenType::TEXT_PARAMETRIZED) {
if ($s->form =~ /\S/s && $s->form !~ /<!/){
remember( $s, $s->form );
}
- } elsif ($kind eq TmplTokenType::TAG && %$attr) {
+ } elsif ($kind eq C4::TmplTokenType::TAG && %$attr) {
# value [tag=input], meta
my $tag = lc($1) if $t =~ /^<(\S+)/s;
for my $a ('alt', 'content', 'title', 'value','label') {
EOF
my $directory_re = quotemeta("$directory/");
for my $t (string_list) {
- if ($text{$t}->[0]->type == TmplTokenType::TEXT_PARAMETRIZED) {
+ if ($text{$t}->[0]->type == C4::TmplTokenType::TEXT_PARAMETRIZED) {
my($token, $n) = ($text{$t}->[0], 0);
printf OUTPUT "#. For the first occurrence,\n"
if @{$text{$t}} > 1 && $token->parameters_and_fields > 0;
for my $param ($token->parameters_and_fields) {
$n += 1;
my $type = $param->type;
- my $subtype = ($type == TmplTokenType::TAG
+ my $subtype = ($type == C4::TmplTokenType::TAG
&& $param->string =~ /^<input\b/is?
$param->attributes->{'type'}->[1]: undef);
my $fmt = TmplTokenizer::_formalize( $param );
$fmt =~ s/^%/%$n\$/;
- if ($type == TmplTokenType::DIRECTIVE) {
+ if ($type == C4::TmplTokenType::DIRECTIVE) {
# $type = "Template::Toolkit Directive";
$type = $param->string =~ /\[%(.*?)%\]/is? $1: 'ERROR';
my $name = $param->string =~ /\bname=(["']?)([^\s"']+)\1/is?
. (defined $value? " value=$value->[1]": '');
}
}
- } elsif ($text{$t}->[0]->type == TmplTokenType::TAG) {
+ } elsif ($text{$t}->[0]->type == C4::TmplTokenType::TAG) {
my($token) = ($text{$t}->[0]);
printf OUTPUT "#. For the first occurrence,\n"
if @{$text{$t}} > 1 && $token->parameters_and_fields > 0;
$pathname =~ s/^.*\/koha-tmpl\/(.*)$/$1/;
printf OUTPUT "#: %s:%d\n", $pathname, $token->line_number
if defined $pathname && defined $token->line_number;
- $cformat_p = 1 if $token->type == TmplTokenType::TEXT_PARAMETRIZED;
+ $cformat_p = 1 if $token->type == C4::TmplTokenType::TEXT_PARAMETRIZED;
}
printf OUTPUT "#, c-format\n" if $cformat_p;
printf OUTPUT "msgid %s\n", TmplTokenizer::quote_po
$msgid =~ s/^SELECTED>//;
# Create dummy token
- my $token = TmplToken->new( $msgid, TmplTokenType::UNKNOWN, undef, undef );
+ my $token = TmplToken->new( $msgid, C4::TmplTokenType::UNKNOWN, undef, undef );
remember( $token, $msgid );
$msgstr =~ s/^(?:LIMIT;|LIMITED;)//g; # unneeded for tmpl_process3
$translation{$msgid} = $msgstr unless $msgstr eq '*****';
--- /dev/null
+#!/usr/bin/perl
+
+# Copyright (C) 2011 Tamil s.a.r.l.
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use warnings;
+use strict;
+use Test::More tests => 1;
+use File::Find;
+use Cwd;
+use C4::TTParser;
+
+
+my @files_with_directive_in_tag = do {
+ my @files;
+ find( sub {
+ my $dir = getcwd();
+ return if $dir =~ /blib/;
+ return unless /\.(tt|inc)$/;
+ my $name = $_;
+ my $parser = C4::TTParser->new;
+ $parser->build_tokens( $name );
+ my @lines;
+ while ( my $token = $parser->next_token ) {
+ my $attr = $token->{_attr};
+ next unless $attr;
+ push @lines, $token->{_lc} if $attr->{'[%'};
+ }
+ ($dir) = $dir =~ /koha-tmpl\/(.*)$/;
+ push @files, { name => "$dir/$name", lines => \@lines } if @lines;
+ }, ( "./koha-tmpl/opac-tmpl/prog/en",
+ "./koha-tmpl/intranet-tmpl/prog/en" )
+ );
+ @files;
+};
+
+
+ok( !@files_with_directive_in_tag, "TT syntax: not using TT directive within HTML tag" )
+ or diag(
+ "Files list: \n",
+ join( "\n", map { $_->{name} . ': ' . join(', ', @{$_->{lines}})
+ } @files_with_directive_in_tag )
+ );
+
+
+
+=head1 NAME
+
+tt_valid.t
+
+=head1 DESCRIPTION
+
+This test validate Template Toolkit (TT) Koha files.
+
+For the time being an unique validation is done: Test if TT files contain TT
+directive within HTML tag. For example:
+
+ <li[% IF
+
+This kind of constuction MUST be avoided because it break Koha translation
+process.
+
+=head1 USAGE
+
+From Koha root directory:
+
+prove -v xt/tt_valid.t
+
+=cut
+