Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add support to repeat items with no new line inserted #110

Open
anthonygerrard opened this Issue · 22 comments

5 participants

@anthonygerrard

tal:repeat always inserts a new line inbetween repeating elements. Whitespace is sometimes important in HTML and unnecessary whitespace can cause styling problems in some older browsers.

It would be great if Chameleon could support repeating elements, or text, all on the same line.

The equivalent bug in zope.tal project is https://bugs.launchpad.net/zope3/+bug/229931

It seems tricky to do this in a backwards compatible way. A rule that would make sense to me would be that if the repeating element is the first non white space element on its line then add a new line otherwise don't.

e.g.

    <ul><li tal:repeat="item items" tal:content="item"></li></ul>

would result in

    <ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>

but

    <ul>
        <li tal:repeat="item items" tal:content="item"></li>
    </ul>

would result in

    <ul>
        <li>item 1</li>
        <li>item 2</li>
        <li>item 3</li>
    </ul>
@malthe
Owner

I have actually experimented quite a bit with this, but got stuck in details and issues.

It's not impossible to pull it off, and your syntax is pretty good I think.

@anthonygerrard

An alternative to the above syntax might be:

  • if the start tag of the repeating element is the first non white space element on its line then prefix each repeated element with the same amount of white space that proceeds the start tag
  • if the end tag of the element is the last non white space element on its line then suffix each element with the same amount of white space that succeeds the element plus a new line
@goodwillcoding

@malthe @anthonygerrard
Is there any movement on this. The workaround above does not seem to work as of 2.13-1

When I do

<span tal:repeat="item ('h', 'e', 'l', 'l', 'o')" tal:content="item"></span>

I always get:

<span>h</span>
<span>e</span>
<span>l</span>
<span>l</span>
<span>o</span>

I need to generate a continuos line with spans adjustent and I can not seem to do that in anyway in Chameleon

@malthe
Owner

What happens if you do e.g. <tal:block><span tal:repeat="..." /></tal:block>?

@goodwillcoding

So this:

<tal:block><span tal:repeat="n range(3)" tal:content="n"></span></tal:block>

Produces this (with newlines):

  <span>0</span>
  <span>1</span>
  <span>2</span>
@goodwillcoding

@malthe
Also I know this is marked as a feature but techinicall its a bug, since there is absolutely no way to generate consecutive spans right now and have continuous text.

In short, insteat if "hello" I always get "h e l l o"

@goodwillcoding

@malthe

Also based on this https://mail.zope.org/pipermail/zpt/2002-November/004078.html it appears that zpt supported that as functionality way back

@goodwillcoding

@malthe
Okie, so I managed to test "tal:block" enclose work around in zope.tal and Chameleon and results are different.

pip install zope.pagetemplates chameleon

test.pt

<tal:block><span tal:repeat="n python:range(3)" tal:content="n"></span></tal:block>

test.py

#!/usr/bin/env python

from zope.pagetemplate.pagetemplatefile import PageTemplateFile
zpt_pt = PageTemplateFile('./test.pt')
print "==== Zope.TAL ===="
print zpt_pt()


from chameleon import PageTemplateFile
chameleon_pt = PageTemplateFile('./test.pt')
print "==== Chameleon ===="
print chameleon_pt()

output:

==== Zope.TAL ====
<span>0</span><span>1</span><span>2</span>

==== Chameleon ====
<span>0</span>
<span>1</span>
<span>2</span>
@goodwillcoding

@malthe

so the question is whether to implement "tal:whitespace=false" fix described in the ubuntu bug or to fix the Chameleon tal:block behaviour.

Btw, I've looked over the block code and I have no clue how to do the latter. Meanwhile I have a patched code chameleon, but I am really at a loss how what to do next.

Do you think this is something you can address?

@malthe
Owner
@malthe malthe closed this
@goodwillcoding

@malthe

The fix does work for "tal" namespace. However I did miss a scenario in the original test. As it appears zope.tal might be doing this for any "tal:repeat"

test.pt:

<span tal:repeat="n python:range(3)" tal:content="n" />

test.py:

#!/usr/bin/env python

from zope.pagetemplate.pagetemplatefile import PageTemplateFile
zpt_pt = PageTemplateFile('./test.pt')
print "==== Zope.TAL ===="
print zpt_pt()

from chameleon import PageTemplateFile
chameleon_pt = PageTemplateFile('./test.pt')
print "==== Chameleon ===="
print chameleon_pt()

output:

==== Zope.TAL ====
<span>0</span><span>1</span><span>2</span>

==== Chameleon ====
<span>0</span>
<span>1</span>
<span>2</span>
@goodwillcoding

Expanding on the test a little more, seem the logic is conditional:

test.pt

<span tal:repeat="n python:range(3)" tal:content="n" />
<div tal:repeat="n python:range(3)" tal:content="n" />
<tal:block tal:repeat="n python:range(3)" tal:content="n" />

output:

==== Zope.TAL ====
<span>0</span><span>1</span><span>2</span>
<div>0</div>
<div>1</div>
<div>2</div>
0
1
2

==== Chameleon ====
<span>0</span>
<span>1</span>
<span>2</span>
<div>0</div>
<div>1</div>
<div>2</div>
012
@goodwillcoding

As you said this was tricky as shown by more tests:

test.pt:

<tal:block tal:repeat="n python:range(3)"><span tal:content="n" /></tal:block>
<span tal:repeat="n python:range(3)" tal:content="n" />
<div tal:repeat="n python:range(3)" tal:content="n" />
<li tal:repeat="n python:range(3)" tal:content="n" />
<table>
<tr tal:repeat="n python:range(3)" tal:content="n" />
</table>
<tal:block tal:repeat="n python:range(3)" tal:content="n" />
<tal:item tal:repeat="n python:range(3)" tal:content="n" /

output:

==== Zope.TAL ====
<span>0</span><span>1</span><span>2</span>
<span>0</span>
<span>1</span>
<span>2</span>
<div>0</div>
<div>1</div>
<div>2</div>
<li>0</li>
<li>1</li>
<li>2</li>
<table>
<tr>0</tr>
<tr>1</tr>
<tr>2</tr>
</table>
0
1
2
0
1
2

==== Chameleon ====
<span>0</span><span>1</span><span>2</span>
<span>0</span>
<span>1</span>
<span>2</span>
<div>0</div>
<div>1</div>
<div>2</div>
<li>0</li>
<li>1</li>
<li>2</li>
<table>
<tr>0</tr>
<tr>1</tr>
<tr>2</tr>
</table>
012
012
@malthe
Owner

Zope also has this HTML mode where it behaves differently depending on the tag. We do support some of that via configuration options but obviously not all of it.

@malthe malthe reopened this
@malthe
Owner

The question is whether the current fix is enough to let you do what you want. It doesn't always make sense to replicate every last Zopeism out there.

@goodwillcoding

@malthe

Ok the commonality seems to be presence of tal:content so far in every use case I have thought off.

Basically the conditionally the conditional here : https://github.com/malthe/chameleon/blob/8d2e415bc2425a4de63c2eba92d885e36dc55be3/src/chameleon/zpt/program.py#L398 should be:

if start['namespace'] == TAL and (not ns.has_key((TAL, 'content'))):

This also passes all the current tests, though I am sure if this is the fix a few more tests can be added. If you are ok with the fix I'll try to do a PR with proper tests.

@goodwillcoding

@malthe

That said its possible the bug wih zope.tal because the fix you did actually has greater flexibility IMHO, so I am ok with keeping it.

@malthe
Owner

I feel that we do need a few more tests, but yes, what I like about "my" fix is that it's fairly easy to explain the rule, i.e. if it's the TAL namespace, then there's no repeat space. That kind of makes sense I think.

@sdouche

Hi Malthe,
since the upgrade to 2.14, I have this:

Traceback (most recent call last):
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.publisher-3.13.0-py2.7.egg/zope/publisher/publish.py", line 132, in publish
    result = publication.callObject(request, obj)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.app.publication-3.14.0-py2.7.egg/zope/app/publication/zopepublication.py", line 205, in callObject
    return mapply(ob, request.getPositionalArguments(), request)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.publisher-3.13.0-py2.7.egg/zope/publisher/publish.py", line 107, in mapply
    return debug_call(obj, args)
   - __traceback_info__: <security proxied z3c.pagelet.zcml.WidgetsPagelet instance at 0x7f1c287d7b10>
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.publisher-3.13.0-py2.7.egg/zope/publisher/publish.py", line 113, in debug_call
    return obj(*args)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pagelet-2.0.0a1-py2.7.egg/z3c/pagelet/browser.py", line 61, in __call__
    return layout(self)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 271, in __call__
    return bound_pt(**kwargs)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 295, in __call__
    return self.im_func(**kw)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 124, in render
    return self.render(**context)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 163, in render
    return base_renderer(**context)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/zpt/template.py", line 258, in render
    return super(PageTemplate, self).render(**vars)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/template.py", line 170, in render
    self._render(stream, econtext, rcontext)
  File "layout_1cf42383ed54e51738e4eb58077ae3f5.py", line 531, in render
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/expressions.py", line 74, in render_content_provider
    return cp.render()
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pagelet-2.0.0a1-py2.7.egg/z3c/pagelet/provider.py", line 41, in render
    return self.__parent__.render()
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pagelet-2.0.0a1-py2.7.egg/z3c/pagelet/browser.py", line 45, in render
    return self.template()
  File "/home/sdouche/src/sact/main/sact.nova/eggs/sact.nevrax-0.35.0.1dev_r201312041517-py2.7.egg/sact/nevrax/gui/view.py", line 73, in get_template_from_context
    return view_instance.context.html_template(view_instance)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.browserpage-3.12.2-py2.7.egg/zope/browserpage/viewpagetemplatefile.py", line 83, in __call__
    return self.im_func(im_self, *args, **kw)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.browserpage-3.12.2-py2.7.egg/zope/browserpage/viewpagetemplatefile.py", line 51, in __call__
    sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0),
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.pagetemplate-3.6.3-py2.7.egg/zope/pagetemplate/pagetemplate.py", line 132, in pt_render
    strictinsert=0, sourceAnnotations=sourceAnnotations
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.ptcompat-2.0.0a1-py2.7.egg/z3c/ptcompat/engine.py", line 47, in __call__
    return self.template.render(**context.vars)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 163, in render
    return base_renderer(**context)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/zpt/template.py", line 258, in render
    return super(PageTemplate, self).render(**vars)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/template.py", line 188, in render
    raise_with_traceback(exc, tb)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/template.py", line 170, in render
    self._render(stream, econtext, rcontext)
  File "e1ac401b3335be57fe35402e66f931fb.py", line 164, in render
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/expressions.py", line 101, in path_traverse
    next = getattr(base, name, _marker)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/sact.nevrax-0.35.0.1dev_r201312041517-py2.7.egg/sact/nevrax/form/view.py", line 56, in form
    name=self.context.form_descriptor.view_name)()
  File "/home/sdouche/src/sact/main/sact.nova/eggs/sact.nevrax-0.35.0.1dev_r201312041517-py2.7.egg/sact/nevrax/form/form.py", line 234, in __call__
    return self._template()
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.browserpage-3.12.2-py2.7.egg/zope/browserpage/viewpagetemplatefile.py", line 83, in __call__
    return self.im_func(im_self, *args, **kw)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.browserpage-3.12.2-py2.7.egg/zope/browserpage/viewpagetemplatefile.py", line 51, in __call__
    sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0),
  File "/home/sdouche/src/sact/main/sact.nova/eggs/zope.pagetemplate-3.6.3-py2.7.egg/zope/pagetemplate/pagetemplate.py", line 132, in pt_render
    strictinsert=0, sourceAnnotations=sourceAnnotations
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.ptcompat-2.0.0a1-py2.7.egg/z3c/ptcompat/engine.py", line 47, in __call__
    return self.template.render(**context.vars)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/z3c.pt-3.0.0a1-py2.7.egg/z3c/pt/pagetemplate.py", line 163, in render
    return base_renderer(**context)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/zpt/template.py", line 258, in render
    return super(PageTemplate, self).render(**vars)
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/template.py", line 170, in render
    self._render(stream, econtext, rcontext)
  File "54abc3247b63a1adbb9cc429bb27bfe1.py", line 1072, in render
  File "/home/sdouche/src/sact/main/sact.nova/eggs/Chameleon-2.14-py2.7.egg/chameleon/tal.py", line 462, in __call__
    iterable = tuple(iterable)
TypeError: 'NoneType' object is not iterable

 - Expression: "provider:pagelet"
 - Filename:   ... .1dev_r201312041517-py2.7.egg/sact/nevrax/skin/layout.pt
 - Location:   (line 79: col 50)
 - Source:     ... " tal:content="structure provider:pagelet">
                                            ^^^^^^^^^^^^^^^^
 - Expression: "view/form"
 - Filename:   ... .7.egg/sact/nevrax/gui/templates/default_widgets_page.pt
 - Location:   (line 8: col 30)
 - Source:     <div tal:content="structure view/form" />
                                           ^^^^^^^^^
 - Arguments:  repeat: {...} (0)
               context: <type BcaDashboard at 0x618fd30>
               views: <ViewMapper - at 0x7f1c28405890>
               modules: <TraversableModuleImporter - at 0x35217d0>
               args: <tuple - at 0x7f1c44c81050>
               nothing: <NoneType - at 0x7ac070>
               target_language: <NoneType - at 0x7ac070>
               default: <object - at 0x7f1c44c44580>
               request: <BrowserRequest - at 0x7f1c0003bd10>
               loop: {...} (0)
               template: <ViewPageTemplateFile - at 0x6112cd0>
               translate: <function translate at 0x7f1c286825f0>
               options: {...} (0)
               view: <WidgetsPagelet default_view at 0x7f1c287d7b10>
@ampsport

@malthe @sdouche I just saw the same issue. This might actually break quite a bit of code in Plone, since I know many of us rely on it to err nicely when the loop is empty. 2.14 release has the issue. I'll open a new ticket.

@malthe
Owner

@sdouche, can you move that traceback to the issue that @eleddy added just now – #172.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.