848
## ----------------------------------------------------------------------
849
## HTML aware string manipulation
850
851
+# Try to chop given string on a word boundary between position
852
+# $len and $len+$add_len. If there is no word boundary there,
853
+# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
854
+# (marking chopped part) would be longer than given string.
855
sub chop_str {
856
my $str = shift;
857
my $len = shift;
858
my $add_len = shift || 10;
859
+ my $where = shift || 'right'; # 'left' | 'center' | 'right'
860
861
# allow only $len chars, but don't cut a word if it would fit in $add_len
862
# if it doesn't fit, cut it if it's still longer than the dots we would add
858
- $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
859
- my $body = $1;
860
- my $tail = $2;
861
- if (length($tail) > 4) {
862
- $tail = " ...";
863
- $body =~ s/&[^;]*$//; # remove chopped character entities
863
+ # remove chopped character entities entirely
864
+
865
+ # when chopping in the middle, distribute $len into left and right part
866
+ # return early if chopping wouldn't make string shorter
867
+ if ($where eq 'center') {
868
+ return $str if ($len + 5 >= length($str)); # filler is length 5
869
+ $len = int($len/2);
870
+ } else {
871
+ return $str if ($len + 4 >= length($str)); # filler is length 4
872
+ }
873
+
874
+ # regexps: ending and beginning with word part up to $add_len
875
+ my $endre = qr/.{$len}\w{0,$add_len}/;
876
+ my $begre = qr/\w{0,$add_len}.{$len}/;
877
+
878
+ if ($where eq 'left') {
879
+ $str =~ m/^(.*?)($begre)$/;
880
+ my ($lead, $body) = ($1, $2);
881
+ if (length($lead) > 4) {
882
+ $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
883
+ $lead = " ...";
884
+ }
885
+ return "$lead$body";
886
+
887
+ } elsif ($where eq 'center') {
888
+ $str =~ m/^($endre)(.*)$/;
889
+ my ($left, $str) = ($1, $2);
890
+ $str =~ m/^(.*?)($begre)$/;
891
+ my ($mid, $right) = ($1, $2);
892
+ if (length($mid) > 5) {
893
+ $left =~ s/&[^;]*$//;
894
+ $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
895
+ $mid = " ... ";
896
+ }
897
+ return "$left$mid$right";
898
+
899
+ } else {
900
+ $str =~ m/^($endre)(.*)$/;
901
+ my $body = $1;
902
+ my $tail = $2;
903
+ if (length($tail) > 4) {
904
+ $body =~ s/&[^;]*$//;
905
+ $tail = "... ";
906
+ }
907
+ return "$body$tail";
908
}
865
- return "$body$tail";
909
}
910
911
# takes the same arguments as chop_str, but also wraps a <span> around the
912
# result with a title attribute if it does get chopped. Additionally, the
913
# string is HTML-escaped.
914
sub chop_and_escape_str {
872
- my $str = shift;
873
- my $len = shift;
874
- my $add_len = shift || 10;
915
+ my ($str) = @_;
916
876
- my $chopped = chop_str($str, $len, $add_len);
917
+ my $chopped = chop_str(@_);
918
if ($chopped eq $str) {
919
return esc_html($chopped);
920
} else {
3832
foreach my $line (@$comment) {
3833
if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
3834
my ($lead, $match, $trail) = ($1, $2, $3);
3794
- $match = chop_str($match, 70, 5); # in case match is very long
3795
- my $contextlen = int((80 - length($match))/2); # for the remainder
3796
- $contextlen = 30 if ($contextlen > 30); # but not too much
3797
- $lead = chop_str($lead, $contextlen, 10);
3798
- $trail = chop_str($trail, $contextlen, 10);
3835
+ $match = chop_str($match, 70, 5, 'center');
3836
+ my $contextlen = int((80 - length($match))/2);
3837
+ $contextlen = 30 if ($contextlen > 30);
3838
+ $lead = chop_str($lead, $contextlen, 10, 'left');
3839
+ $trail = chop_str($trail, $contextlen, 10, 'right');
3840
3841
$lead = esc_html($lead);
3842
$match = esc_html($match);