Index: test/unit/WysiwygPlugin/TranslatorTests.pm =================================================================== --- test/unit/WysiwygPlugin/TranslatorTests.pm (revision 8812) +++ test/unit/WysiwygPlugin/TranslatorTests.pm (working copy) @@ -93,9 +93,6 @@ Move !ItTest/site/ToWeb5 leaving web5 as !MySQL host

HERE - finaltml => <<'HERE', -Move !ItTest/site/ToWeb5 leaving web5 as !MySQL host -HERE }, { exec => $ROUNDTRIP, @@ -114,7 +111,6 @@ name => 'currentWebLinkAtStart', tml => 'Current.LinkAtStart', html => $linkon . 'Current.LinkAtStart' . $linkoff, - finaltml => 'Current.LinkAtStart', }, { exec => $ROUNDTRIP, @@ -151,7 +147,6 @@ reminded about${linkon}http://www.koders.com${linkoff} HERE tml => '*reminded about http://www.koders.com*', - finaltml => '*reminded about http://www.koders.com*', }, { exec => $ROUNDTRIP, @@ -418,7 +413,7 @@ { exec => $ROUNDTRIP | $TML2HTML, name => 'simpleHR', - html => '

--

', + html => '

--

', tml => <<'HERE', --- ------- @@ -427,7 +422,7 @@ HERE finaltml => <<'HERE', --- ---- +------- -- HERE @@ -783,8 +778,20 @@ 'WebPreferences %MAINWEB%.WikiUsers CompleteAndUtterNothing', }, { + exec => $HTML2TML, + name => 'squabsWithVars1', + html => < <<'EVERYWHERE', +[[wiki syntax]][[%MAINWEB%.TWiki users]] escaped: [[wiki syntax]] +EVERYWHERE + }, + { exec => $ROUNDTRIP, - name => 'squabsWithVars', + name => 'squabsWithVars2', html => < <<'EVERYWHERE', -[[wiki syntax]][[%MAINWEB%.TWiki users]] escaped: ![[wiki syntax]] -EVERYWHERE }, { exec => $ROUNDTRIP, @@ -825,14 +829,12 @@ name => 'plingedVarOne', html => '!%MAINWEB%nowt', tml => '!%MAINWEB%nowt', - finaltml => '!%MAINWEB%nowt', }, { exec => $ROUNDTRIP, name => 'plingedVarTwo', html => 'nowt!%MAINWEB%', tml => 'nowt!%MAINWEB%', - finaltml => 'nowt!%MAINWEB%', }, { exec => $ROUNDTRIP, @@ -1006,8 +1008,17 @@ tml => '%SCRIPTNAME%', }, { + exec => $HTML2TML, + name => 'nestedVerbatim1', + html => 'Outside +
 Inside
 
Outside', + tml => 'Outside + Inside + Outside', + }, + { exec => $ROUNDTRIP, - name => 'nestedVerbatim', + name => 'nestedVerbatim2', html => 'Outside
Inside
Outside', tml => 'Outside @@ -1015,9 +1026,6 @@ Inside Outside', - finaltml => 'Outside - Inside - Outside', }, { exec => $TML2HTML | $ROUNDTRIP, @@ -1077,13 +1085,15 @@ Outside ', - finaltml => 'Outside + finaltml => 'Outside + Inside - Outside', + + Outside', }, { - exec => $ROUNDTRIP | $HTML2TML, - name => 'nestedIndentedPre', + exec => $ROUNDTRIP, + name => 'nestedIndentedPre1', html => 'Outside
  Inside
@@ -1098,10 +1108,21 @@
 Snide
  
Outside', - finaltml => 'Outside
+    },
+    {
+        exec => $HTML2TML,
+        name => 'nestedIndentedPre2',
+        html => 'Outside
+ 
  Inside
 
 Snide
+ 
+ Outside', + tml => 'Outside
+ Inside
+
+Snide
  
Outside', }, { @@ -1117,8 +1138,18 @@
Outside', }, { + exec => $HTML2TML, + name => 'indentedPre1', + html => 'Outside
+ Inside
+    
Outside', + tml => 'Outside
+ Inside
+    
Outside', + }, + { exec => $ROUNDTRIP, - name => 'indentedPre', + name => 'indentedPre2', html => 'Outside
 Inside
 
Outside', @@ -1127,28 +1158,38 @@ Inside Outside', - finaltml => 'Outside
- Inside
-    
Outside', }, { exec => $TML2HTML | $ROUNDTRIP, - name => 'NAL', + name => 'NAL1', html => '

Outside <noautolink> Inside </noautolink> Outside

', + tml => 'Outside Inside Outside', + }, + { + exec => $TML2HTML | $ROUNDTRIP, + name => 'NAL2', + html => '

Outside' +. encodedWhitespace('ns1') +. '<noautolink>' +. encodedWhitespace('ns1') +. 'Inside' +. encodedWhitespace('ns1') +. '</noautolink>' +. encodedWhitespace('ns1') +. 'Outside

', tml => 'Outside Inside Outside', - finaltml => 'Outside Inside Outside', }, { - exec => $TML2HTML | $ROUNDTRIP, - name => 'classifiedNAL', + exec => $HTML2TML, + name => 'classifiedNAL1', html => '

Outside <noautolink class="foswikiAlert">

    @@ -1157,31 +1198,54 @@

    </noautolink> Outside

    ', + tml => 'Outside + * Inside + Outside', + }, + { + exec => $TML2HTML | $ROUNDTRIP, + name => 'classifiedNAL2', + html => '

    Outside' +. encodedWhitespace('n') +. '<noautolink class="foswikiAlert">

    +
      +
    • Inside
    • +
    +

    </noautolink>' +. encodedWhitespace('ns1') +. 'Outside +

    ', tml => 'Outside * Inside Outside', - finaltml => 'Outside - * Inside - Outside', }, { - exec => $ROUNDTRIP, - name => 'indentedNAL', + exec => $HTML2TML, + name => 'indentedNAL1', html => 'Outside <noautolink> Inside </noautolink> Outside ', + tml => 'Outside Inside Outside', + }, + { + exec => $ROUNDTRIP, + name => 'indentedNAL2', tml => 'Outside Inside Outside ', - finaltml => 'Outside Inside Outside', + finaltml => 'Outside + + Inside + + Outside', }, { exec => $ROUNDTRIP, @@ -1194,7 +1258,7 @@ exec => $HTML2TML, name => 'inlineBreaks', html => 'Zadoc
    The
    Priest', - finaltml => 'Zadoc
    The
    Priest', + tml => 'Zadoc
    The
    Priest', }, { exec => $HTML2TML, @@ -1409,9 +1473,6 @@ ${linkon}\[[Current.TestTopic]]${linkoff} ${linkon}\[[Sandbox.TestTopic]]${linkoff} HERE - finaltml => < $ROUNDTRIP, @@ -1426,7 +1487,6 @@ tml => '[[WebCTPasswords][Resetting a WebCT Password]]', html => "${linkon}[[WebCTPasswords][Resetting a WebCT Password]]${linkoff}", - finaltml => '[[WebCTPasswords][Resetting a WebCT Password]]', }, { exec => $ROUNDTRIP, @@ -1463,9 +1523,9 @@
  • x
  • y
', - finaltml => <<'HERE', + finaltml => <<"HERE", * x - * + *$trailingSpace * y HERE }, @@ -1502,7 +1562,7 @@ tml => "what\n\nthef", }, { - exec => $HTML2TML, # | $ROUNDTRIP, + exec => $HTML2TML | $ROUNDTRIP, name => 'Item4435', html => < @@ -1543,8 +1603,9 @@ name => 'paraConversions1', exec => $TML2HTML | $HTML2TML | $ROUNDTRIP, html => '

-Paraone -Paratwo +Paraone' +. encodedWhitespace('n') +. 'Paratwo

Parathree @@ -1560,7 +1621,8 @@ Parafour', - finaltml => 'Paraone Paratwo + finaltml => 'Paraone +Paratwo Parathree @@ -1706,9 +1768,11 @@ html => '

A

<section>

B

-

C -</section> -X

+

C' +. encodedWhitespace('n') +. '</section>' +. encodedWhitespace('n') +. 'X

', finaltml => < ---++ B -C X +C + +X FGFG }, { @@ -1738,8 +1804,6 @@ E hereE F hereF XWYZ - final_tml => < $ROUNDTRIP, @@ -2285,14 +2349,18 @@ with one space No more SPACED - html => < -
  • One item spanning several lines + html => +'
      +
    1. One item' +. encodedWhitespace('ns5') +. 'spanning several lines -
    2. And another item with one space +
    3. And another item' +. encodedWhitespace('ns1') +. 'with one space

    No more

    -DECAPS +', }, { exec => $ROUNDTRIP, @@ -2365,14 +2433,16 @@ edit Blah BLAH - html => <<'BLAH', -

    -Blah -<a href="%SCRIPTURLPATH{"edit"}%/%WEB%/%TOPIC%?t=%GM%NOP%TIME{"$epoch"}%">edit</a> -Blah + html => +'

    +Blah' +. encodedWhitespace('n') +. '<a href="%SCRIPTURLPATH{"edit"}%/%WEB%/%TOPIC%?t=%GM%NOP%TIME{"$epoch"}%">edit</a>' +. encodedWhitespace('n') +. 'Blah

    -BLAH +', }, { name => 'Item4903', @@ -2525,18 +2595,15 @@ &><" HERE - html => < -<smeg> -

    <img src="ball&co<ck>s">&><"
    -&><" + html => +'

    +<smeg>' +. encodedWhitespace('n') +. '


    <img src="ball&co<ck>s">&><"
    ' +. encodedWhitespace('n') +. '&><"

    -HERE - finaltml => < -&><" - &><" -HERE +', }, { name => "Item5337", @@ -2547,12 +2614,6 @@ there HERE - finaltml => < -hello -there - -HERE html => <
    @@ -2574,7 +2635,6 @@
         {
             name => "Item5961",
             exec => $HTML2TML | $ROUNDTRIP,
    -        html => 'one',
             html =>
     ' zero one two tre',
             tml =>
    @@ -2608,11 +2668,6 @@
     
     %SEARCH{search="Sven"}%
     HERE
    -        finaltml => <<'HERE',
    ----
    -
    -%SEARCH{search="Sven"}%
    -HERE
             html => <<'HERE',
     

    @@ -2691,6 +2746,32 @@ ''. '

    ' }, + { + name => "whitespaceEncoding", + exec => $TML2HTML | $ROUNDTRIP, + tml => <<'HERE', +a a + b + * c + d +e +HERE + html => '

    ' + . 'a' + . encodedWhitespace('s2') + . 'a' + . encodedWhitespace('ns1') + . 'b' + . '

    ' + . '
    • ' + . 'c' + . encodedWhitespace('ns5') + . 'd' + . '
    ' + . '

    ' + . 'e' + . '

    ', + }, { name => "failsTML2HTML", exec => 0,#$TML2HTML | $HTML2TML | $ROUNDTRIP, @@ -2704,6 +2785,13 @@ }, ]; +sub encodedWhitespace { + my $encoded = shift; + return ' '; +} + # Run from BEGIN sub gen_file_tests { foreach my $d (@INC) { @@ -2828,6 +2916,7 @@ my $html = eval { $txer->convert( $tml, $this->TML_HTMLconverterOptions() ); }; $html = $@ if $@; + #print STDERR "'$html'\n"; $txer = new Foswiki::Plugins::WysiwygPlugin::HTML2TML(); my $tx = $txer->convert( $html, $this->HTML_TMLconverterOptions() ); Index: test/unit/WysiwygPlugin/ExtendedTranslatorTests.pm =================================================================== --- test/unit/WysiwygPlugin/ExtendedTranslatorTests.pm (revision 8810) +++ test/unit/WysiwygPlugin/ExtendedTranslatorTests.pm (working copy) @@ -100,12 +100,14 @@ . $protecton . '<customtag>' . $protectoff - . 'some > text' + . 'some >' + . TranslatorTests::encodedWhitespace('s2') + . 'text' . $protecton . '</customtag>' . $protectoff . '

    ', tml => 'some > text', - finaltml => 'some > text', + finaltml => 'some > text', }, { exec => $TML2HTML | $ROUNDTRIP, @@ -120,12 +122,14 @@ . $protecton . '<customtag>' . $protectoff - . 'some > text' + . 'some >' + . TranslatorTests::encodedWhitespace('s2') + . 'text' . $protecton . '</customtag>' . $protectoff . '

    ', tml => 'some > text', - finaltml => 'some > text', + finaltml => 'some > text', }, { exec => $TML2HTML | $ROUNDTRIP, Index: lib/Foswiki/Plugins/WysiwygPlugin/HTML2TML/Node.pm =================================================================== --- lib/Foswiki/Plugins/WysiwygPlugin/HTML2TML/Node.pm (revision 8812) +++ lib/Foswiki/Plugins/WysiwygPlugin/HTML2TML/Node.pm (working copy) @@ -37,7 +37,8 @@ lotusscript php-f php sql tml ); my %tml2htmlClass = map { $_ => 1 } - qw( WYSIWYG_PROTECTED WYSIWYG_STICKY TMLverbatim WYSIWYG_LINK ); + qw( WYSIWYG_PROTECTED WYSIWYG_STICKY TMLverbatim WYSIWYG_LINK + TMLwhitespace foswikiDeleteMe ); =pod @@ -1508,7 +1513,15 @@ my ( $f, $kids ) = $this->_flatten($options); return ( $f, '
    ' . $kids ) if ( $options & $WC::NO_BLOCK_TML ); - return ( $f | $WC::BLOCK_TML, $WC::CHECKn . '---' . $WC::CHECKn . $kids ); + + my $dashes = 3; + if ( $this->{attrs}->{style} and + $this->{attrs}->{style} =~ s/\bnumDashes\s*:\s*(\d+)\b// ) { + $dashes = $1; + $dashes = 3 if $dashes < 3; + $dashes = 160 if $dashes > 160; # Filter out probably-bad data + } + return ( $f | $WC::BLOCK_TML, $WC::CHECKn . ('-' x $dashes) . $WC::CHECKn . $kids ); } sub _handleHTML { return _flatten(@_); } @@ -1673,6 +1686,46 @@ } } + if ( _removeClass( \%atts, 'TMLwhitespace' ) ) { + # This regular expression ensures the encoded whitespace is valid. + # The limit on the number of digits will ensure that the numbers are reasonable. + if ( $atts{style} and $atts{style} =~ s/\bencoded\s*:\s*(['"])((?:b|n|t\d{1,2}|s\d{1,3})+)\1;?// ) { + my $whitespace = $2; + #print STDERR "'$whitespace' -> "; + $whitespace =~ s/b/\\/g; + $whitespace =~ s/n/$WC::NBBR/g; + $whitespace =~ s/t(\d+)/'\t' x $1/ge; + $whitespace =~ s/s(\d+)/$WC::NBSP x $1/ge; + #print STDERR "'$whitespace'\n"; + #require Data::Dumper; + my ( $f, $kids ) = $this->_flatten($options | $WC::KEEP_WS | $WC::KEEP_ENTITIES); + #die Data::Dumper::Dumper($kids); + if ( $kids eq ' ' ) { + # The space was not changed + # So restore the encoded whitespace + return ( $f, $whitespace ); + } + elsif ( length($kids) == 0 ) { + # The user deleted the space + # So return blank + return ( 0, '' ); + } + #else {die "'".ord($kids)."'";}if(1){} + elsif ( 0 and ($kids eq ' ' or $kids eq chr(160)) ) { # SMELL: Firefox-specific + # This was probably inserted by Firefox after the user deleted the space. + # So return blank + return ( 0, '' ); + } + else { + # The user entered some new text + # Return the combination. + # Assume that a leading space corresponds to the encoded whitespace + $kids =~ s/^ //; + return ( $f, $whitespace . $kids ); + } + } + } + # Remove all other (non foswiki) classes if ( defined $atts{class} && $atts{class} !~ /foswiki/ ) { delete $atts{class}; Index: lib/Foswiki/Plugins/WysiwygPlugin/Constants.pm =================================================================== --- lib/Foswiki/Plugins/WysiwygPlugin/Constants.pm (revision 8810) +++ lib/Foswiki/Plugins/WysiwygPlugin/Constants.pm (working copy) @@ -15,8 +15,8 @@ HR ISINDEX MENU NOFRAMES NOSCRIPT OL P PRE TABLE UL ); our $ALWAYS_BLOCK_S = join( '|', keys %ALWAYS_BLOCK ); -our $STARTWW = qr/^|(?<=[ \t\n\(\!])/om; -our $ENDWW = qr/$|(?=[ \t\n\,\.\;\:\!\?\)])/om; +our $STARTWW = qr/^|(?<=[ \t\n\(\!])|(?<=

    )|(?<= <\/span>)/om; +our $ENDWW = qr/$|(?=[ \t\n\,\.\;\:\!\?\)])|(?=<\/p>)|(?=]*> )/om; our $PROTOCOL = qr/^(file|ftp|gopher|https?|irc|news|nntp|telnet|mailto):/; # Colours with colour settings in DefaultPreferences. Index: lib/Foswiki/Plugins/WysiwygPlugin/TML2HTML.pm =================================================================== --- lib/Foswiki/Plugins/WysiwygPlugin/TML2HTML.pm (revision 8810) +++ lib/Foswiki/Plugins/WysiwygPlugin/TML2HTML.pm (working copy) @@ -372,8 +372,8 @@ $text = $this->_takeOutCustomTags($text); - $text =~ s/\\\n/ /g; $text =~ s/\t/ /g; + $text =~ s/( +\\\n)/$this->_hideWhitespace($1)/ge; # Remove PRE to prevent TML interpretation of text inside it $text = $this->_liftOutBlocks( $text, 'pre', {} ); @@ -438,8 +438,7 @@ $text =~ s/$TT0([$Foswiki::regex{mixedAlphaNum}]+;)/&$1/go; # Horizontal rule - my $hr = CGI::hr( { class => 'TMLhr' } ); - $text =~ s/^---+$/$hr/gm; + $text =~ s/^(---+)$/_encodeHr($1)/gme; # Wrap tables with macros before or after them in a

    , # together with the macros, @@ -616,12 +615,14 @@ $line =~ s/^()\s*$/$1 /; } - elsif ( $inList && $line =~ /^[ \t]/ ) { + elsif ( $inList && $line =~ s/^([ \t]+)/$this->_hideWhitespace("\n$1")/e ) { # Extend text of previous list item by dropping through + $result[-1] .= $line; + $line = ''; } - elsif ( $line eq $hr ) { + elsif ( $line =~ /^
    ' ) if $inParagraph; $inParagraph = 0; } @@ -646,13 +647,25 @@ # Other line $this->_addListItem( \@result, '', '', '' ) if $inList; $inList = 0; + if ( $inParagraph and @result and $result[-1] !~ /

    $/ ) { + # This is the second (or later) line of a paragraph + + my $whitespace = "\n"; + if ($line =~ s/^(\s+)//) { + $whitespace .= $1; + } + $line = $this->_hideWhitespace($whitespace) . $line; + } unless ( $inParagraph or $inDiv ) { push( @result, '

    ' ); $inParagraph = 1; } + $line =~ s/(\s\s+)/$this->_hideWhitespace($1)/ge; + $result[-1] .= $line; + $line = ''; } - push( @result, $line ); + push( @result, $line ) if length($line) > 0; } if ($inTable) { @@ -688,7 +701,7 @@ # Handle [[][]] and [[]] links - # We _not_ support [[http://link text]] syntax + # We do _not_ support [[http://link text]] syntax # [[][]] $text =~ s/(\[\[[^\]]*\](\[[^\]]*\])?\])/$this->_liftOut($1, 'LINK')/ge; @@ -713,6 +726,42 @@ return $text; } +sub _encodeHr { + my $dashes = shift; + my $style = ''; + if ( length($dashes) > 3 ) { + $style = ' style="{numDashes:' . length($dashes) . '}"'; + } + return '


    '; +} + +sub _hideWhitespace { + my $this = shift; + my $whitespace = shift; + + $whitespace =~ s/\\/b/g; + $whitespace =~ s/\n/n/g; + $whitespace =~ s/(\t+)/'t' . length($1)/ge; + $whitespace =~ s/( +)/'s' . length($1)/ge; + + return $this->_liftOutGeneral( + " ", + { + tag => 'span', + class => "TMLwhitespace", + params => "style=\"{encoded:'$whitespace'}\"", + } + ); +} + sub _processTableRow { my ( $theRow, $inTable, $state ) = @_;