Smarty Forum Index Smarty
The discussions here are for Smarty, a template engine for the PHP programming language.
Better control of outputting of newlines / line breaks

 
Post new topic   Reply to topic    Smarty Forum Index -> Feature Requests
View previous topic :: View next topic  
Author Message
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Thu Oct 06, 2011 12:50 pm    Post subject: Better control of outputting of newlines / line breaks Reply with quote

There has been some recurring discussion on how to handle the extra newlines that are introduced by function blocks (for example here: http://groups.google.com/group/smarty-developers/browse_thread/thread/d1b244df61e407b7) In that thread, it is said "This discussion comes up every few years, and we always circle around to the same conclusion. " but IMNSHO that is simply because not enough options are considered Wink

I'm porting an application from Python to PHP. The Python version used the Jinja2 template engine. It uses a construct that IMO would be perfect for Smarty to adopt.

To have something concrete to illustrate, consider the following:

Code:

            PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS
{if isset($preprocessor_flags.dll)};{$preprocessor_flags.dll|implode:';'}{/if}
{if isset($preprocessor_flags.all)};{$preprocessor_flags.all|implode:';'}{/if}
"


Space at start of first line is important. This is a snippet from an XML file I generate. When the .dll and .all array of $preprocessor_flags are empty, I want the output to be

Code:

            PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS"


If one has contents, I want the output to be (e.g)

Code:

            PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;SOME_FLAG;ANOTHER"


At the moment (using Smarty 3.1.2, latest release), with the given code, this won't work - there will be extra newlines, causing the final quote to be on its own line, and if the arrays have content them to be on a different line too. The only way not to get that, is to put it all on one big line, which makes it very hard to read.

The way Jinja solved this is by allowing a modifier on the tag opener, that removed a preceding or following newline. For example:

Code:

  <an_element attr_1="
{-if isset($somevar)-}hello{/if-}
">


would generate, if $somevar is not set

Code:

  <an_element attr_1="">


and if it is set

Code:

  <an_element attr_1="hello">


The minus sign at the begin or end of a resp. open- or close tag makes the engine remove the newline immediately before resp after it.

This is very useful when generating things that are whitespace sensitive or need to be human-readable, which I find myself doing a lot (I'm not in web dev anymore and now use Smarty mostly to generate source code). Additionally, it could be extended to also eat whitespace up until the first preceding newline. That way, you could write

Code:

  <an_element attr_1="
           {-if isset($somevar)-}hello{/if-}
">


and it would still generate the same output. This is useful often to keep your smarty code indentation consistent, otherwise you have to left-align all smarty code, making it hard to read. Maybe this whitespace-eating could be optional by using a different modifier - not {-if} but {--if} or whatever.

Thanks for reading this far Wink
Back to top
View user's profile Send private message
rodneyrehm
Administrator


Joined: 30 Mar 2007
Posts: 698
Location: Germany, border to Switzerland

PostPosted: Thu Oct 06, 2011 1:30 pm    Post subject: Reply with quote

Code:
{strip}
  so much  space as you
want
     on as
        {if true} many {/if} lines
{if true}
  as you
      {/if}
 like
{strip}

would produce
Code:
so much  space as youwanton as many  linesas youlike

(yeah, newlines are not replaced by a space…)

you can easily roll your own plugin to remove linebreaks, duplicate spaces, whatever:
Code:
$smarty->registerPlugin('block', 'stripws' 'smarty_block_stripws');
function smarty_block_stripws($params, $content, $template, &$repeat) {
  return preg_replace( '#\s+#', ' ', $content);
}

_________________
Twitter
Back to top
View user's profile Send private message Visit poster's website
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Tue Mar 06, 2012 10:51 am    Post subject: Reply with quote

I have run into this issue a few times more since I posted my original question, but the situation I have now is egregious enough to compel me to post about this again. I may just be missing something very obvious, but in my current understanding of things, white space control remains a weak point in Smarty.

I have the following, which is a snippet from a template that generates member variables of a C++ class declaration:

Code:

    {foreach $element->children as $c}
        {if $c->max_count == 1}
    {$c->getClassName()} {$c->getMemberVariableName()};
        {else}
    {$c->getClassName()} {$c->getMemberVariableName()};
    std::vector<{$c->getClassName()}> {$c->getMemberVariableName()}s;
        {/if}
    {/foreach}


This will output spurious white space, namely the spaces in front of the {foreach} and {if} blocks. Using {strip}, it seems I would have to fix it thusly:

Code:

{strip}
    {foreach $element->children as $c}
        {if $c->max_count == 1}
{/strip}
    {$c->getClassName()} {$c->getMemberVariableName()};
{strip}
        {else}
{/strip}
    {$c->getClassName()} {$c->getMemberVariableName()};
    std::vector<{$c->getClassName()}> {$c->getMemberVariableName()}s;
{strip}
        {/if}
    {/foreach}
{/strip}


Causing the template to become unwieldy and unreadable, especially since I'm basically writing C++ code in the template and all the {strip} tags add a whole new dimension of reading obstacles.
The obvious proper solution is again to add a way to ignore white space before and/or after Smarty constructs, because that's the fundamental problem here: to keep the template readable I add spacing to indent Smarty constructs, but that spacing is not part of the template output itself.

For generating HTML, there are easy solutions to simply strip ALL white space, since it doesn't matter for browser rendering engines anyway. In other use cases, a more fine grained control of generated white space is required.

I hope this example illustrates the shortcomings of {trim} or a trim_whitespace output filter. Of course if there is something I'm not understanding about Smarty that provides a way to solve this problem, I'd be happy to hear. Thanks.
Back to top
View user's profile Send private message
rodneyrehm
Administrator


Joined: 30 Mar 2007
Posts: 698
Location: Germany, border to Switzerland

PostPosted: Tue Mar 06, 2012 11:15 am    Post subject: Reply with quote

How about using a pre-filter to remove the indentation?
Code:
function trimLines($string, $template) {
    return preg_replace('#^\s*(\{/?(foreach|foreachelse|if|else|elseif))#m', "\\1", $string);
}
$smarty->registerFilter('pre', 'trimLines');


above filter would turn
Code:
{foreach $element->children as $c}
        {if $c->max_count == 1}
    {$c->getClassName()} {$c->getMemberVariableName()};
        {else}
    {$c->getClassName()} {$c->getMemberVariableName()};
    std::vector<{$c->getClassName()}> {$c->getMemberVariableName()}s;
        {/if}
    {/foreach}
into
Code:
{foreach $element->children as $c}
{if $c->max_count == 1}
    {$c->getClassName()} {$c->getMemberVariableName()};
{else}
    {$c->getClassName()} {$c->getMemberVariableName()};
    std::vector<{$c->getClassName()}> {$c->getMemberVariableName()}s;
{/if}
{/foreach}

_________________
Twitter
Back to top
View user's profile Send private message Visit poster's website
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Wed Mar 07, 2012 9:24 am    Post subject: Reply with quote

Thanks for taking the time to reflect about my issue.

Your suggestion works (after some modifications) and makes my template look a lot tidier already, so thanks for that too. The multiline regex strips empty lines before Smarty constructs too, so I split the template content into lines and then apply the regex on each line separately. While it feels a bit brittle, on my current test data set it seems to work, and if I discover corner cases it's not that big of a deal to modify the regex to cope.

This said, from a library design point of view, I still feel like this is a workaround for something that is a deeper issue, an issue I hope will be considered in a next round of updating. If I run into more situations where I struggle with white space that can't be elegantly solved with {strip} or the prefilter, I will post in this thread to keep a complete record of use cases where a more fine-grained build-it control of white space in Smarty could be of use.
Back to top
View user's profile Send private message
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Wed Mar 07, 2012 9:28 am    Post subject: Reply with quote

To follow up further, looking over my comments on this forum I see that I posted about another use case back in 2010: http://www.smarty.net/forums/viewtopic.php?p=68606&highlight=#68606 .

This is disguised as an issue with 'indent', but it boils down to the same issue. In this case, the prefilter approach wouldn't work, and the block in question would have to be surrounded by {strip} tags (I think - or would the {strip} remove the spaces put in by {indent}? I'm not sure, I'd have to test).

Either way, in that case too, having a built-in way to remove the leading space of the text tag would be the fix to the root cause.
Back to top
View user's profile Send private message
rodneyrehm
Administrator


Joined: 30 Mar 2007
Posts: 698
Location: Germany, border to Switzerland

PostPosted: Wed Mar 07, 2012 9:44 am    Post subject: Reply with quote

There is no generic solution to the "whitespace problem". Consider the following snippen:
Code:
<pre>
    {foreach $foo as $bar}{$bar}, {/foreach}
</pre>


You'd like the spaces before {foreach} removed, while I'd want them preserved. How do you think this could be handled?
_________________
Twitter
Back to top
View user's profile Send private message Visit poster's website
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Wed Mar 07, 2012 10:14 am    Post subject: Reply with quote

In the way other template engines (such as Jinja for Python) do, by having a modifier in the tag format that would indicate whether to remove leading white space or not. My first post in this thread contains an example, basically it could be something like

Code:

    {foreach $foo as $bar}{$bar}, {/foreach}


where no stripping is done (the default), and

Code:

    {-foreach $foo as $bar}{$bar}, {/foreach}


(notice the minus sign on the opening brace of the foreach) where it is stripped. The '-' basically means 'strip all whitespace before this'. One could debate if it should only work on whitespace at the beginning of a line, or if

Code:

    {-foreach $foo as $bar}{$bar}, {-/foreach}


should remove the white space after the commas. Also for consistency, having {-foreach-} strip trailing white space would be the logical thing to do, although I'm not sure of in which cases that would be useful.

Either way, my point is that other template engines have features to address this, and I won't claim to know everything about every template engine, so there may be other ways or syntaxes to do this. I'm also not claiming that Smarty should copy every feature from every other template engine. It's just that I run into this quite regularly (as evidenced by my posts spanning at least 3 years), and I imagine that if a casual Smarty user like myself runs into it, there must be others, too. Or maybe not and I'm just an outlier, I don't have data. Still with my posts here I'm just trying to illustrate the issue, and hopefully I can make a solid point about it, and maybe contribute to a discussion on other use cases where an addition like the proposed one would cause problems; since I won't claim that I have thought through every possible angle of adding new syntax. I just hope that my examples can convince you/the smarty devs/people in general that this would be worthwhile issue to address, or alternatively, that I can be convinced that other solutions are superior to introducing new syntax. As you can tell from my rambling, that last thing hasn't happened yet Wink[/quote]
Back to top
View user's profile Send private message
roel_v
Smarty Rookie


Joined: 17 Nov 2010
Posts: 11

PostPosted: Wed Mar 07, 2012 10:17 am    Post subject: Reply with quote

For reference, here is the Jinja documentation on the white space stripping syntax: http://jinja.pocoo.org/docs/templates/#whitespace-control

It also has something called 'line statements' (http://jinja.pocoo.org/docs/templates/#line-statements), which solves the same problem in a different way, but I'm no fan of that syntax.
Back to top
View user's profile Send private message
rodneyrehm
Administrator


Joined: 30 Mar 2007
Posts: 698
Location: Germany, border to Switzerland

PostPosted: Wed Mar 07, 2012 11:18 am    Post subject: Reply with quote

Looks like it might be handled with a prefilter as well. So (for now) you could implement that "whitespace control" plugin yourself. I'm not sure if this can be done directly in the compiler without some serious changes... so a regex-based prefilter seems like the thing to do.

put this in …/plugins/prefilter.whitespace_control.php:
Code:
<?php
function smarty_prefilter_whitespace_control($string, Smarty_Internal_Template $template) {
    $ldelim = $template->smarty->left_delimiter;
    $rdelim = $template->smarty->right_delimiter;
    $ldelim = '{';
    $rdelim = '}';
    $_ldelim = preg_quote($ldelim);
    $_rdelim = preg_quote($rdelim);
   
    // strip whitespace to previous non-space (including line breaks)
    $string =  preg_replace('#\s*'. $_ldelim .'--#', $ldelim, $string);
    // strip whitespace to next non-space (including line breaks)
    $string =  preg_replace('#--'. $_rdelim .'\s*#', $rdelim, $string);
   
    // strip whitespace to previous non-space (excluding line breaks)
    $string =  preg_replace('#[^\S\r\n]*'. $_ldelim .'-#', $ldelim, $string);
    // strip whitespace to next non-space (excluding line breaks)
    $string =  preg_replace('#-'. $_rdelim .'[^\S\r\n]*#', $rdelim, $string);

    return $string;
}


and make your smarty autoload it:
Code:
$smarty->autoload_filters['pre'] => array('whitespace_control');


now you can pre/append - to any tag (variable-output as well) to remove the whitespace to the next non-whitespace character or line-break. pre/append -- to also remove line-breaks.

about what you had in mind?
_________________
Twitter
Back to top
View user's profile Send private message Visit poster's website
rodneyrehm
Administrator


Joined: 30 Mar 2007
Posts: 698
Location: Germany, border to Switzerland

PostPosted: Thu Jun 28, 2012 9:17 pm    Post subject: Reply with quote

-> Blogged Smarty - Whitespace Control
_________________
Twitter
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    Smarty Forum Index -> Feature Requests All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group
Protected by Anti-Spam ACP