how to use py:attrs to add additional attributes?

454 views
Skip to first unread message

Andreas Zeidler

unread,
May 7, 2009, 4:33:37 AM5/7/09
to gen...@googlegroups.com
hi,

recently i've tried to modify our trac setup a bit and started playing
with genshi. however, even after quite a bit of investigation i
couldn't figure out how to simply add an additional attribute to an
existing tag using `py:match` and `py:attrs`: simply setting
`py:attrs` to a dict removes the already existing attributes from the
tag, and trying to find information about how to manipulate the stream
object provided by "select('@*')" didn't lead anywhere either.

any help on this would be greatly appreciated! :)

best regards,


andi

--
zeidler it consulting - http://zitc.de/ - in...@zitc.de
friedelstraße 31 - 12047 berlin - telefon +49 30 25563779
pgp key at http://zitc.de/pgp - http://wwwkeys.de.pgp.net/
plone 3.2.2 released! -- http://plone.org/products/plone/

PGP.sig

kindy

unread,
May 7, 2009, 5:22:46 AM5/7/09
to gen...@googlegroups.com
could you give some sample code?
--
Regards,

Lin Qing

Andreas Zeidler

unread,
May 7, 2009, 6:46:28 AM5/7/09
to gen...@googlegroups.com
On May 7, 2009, at 11:22 AM, kindy wrote:
> could you give some sample code?

what i'm trying to do is add an accesskey attribute to an existing link:

<a class="next" href="/changeset/25997" title="Changeset
25997">Next Changeset</a>

so that it becomes:

<a class="next" href="/changeset/25997" title="Changeset 25997"
accesskey="n">Next Changeset</a>

trac uses a genshi template that is applied to every rendered page.
currently mine looks like:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/" py:strip="">
<a py:match="a[@class='next']" py:attrs="select('@*')">$
{select('*|text()')}</a>
</html>

this matches the link but otherwise effectively does nothing (since
all existing attributes are replaced with all existing
attributes :)). like i said, a `py:attrs="{'accesskey': 'n'}"`
replaces _all_ attributes, i.e. ending up in:

<a accesskey="n">Next Changeset</a>

what i'd like to know is how to add the extra attribute here (without
using extra python code — since i don't think i can wrt the way trac
is set up).

PGP.sig

kindy

unread,
May 7, 2009, 12:09:36 PM5/7/09
to gen...@googlegroups.com
On Thu, May 7, 2009 at 6:46 PM, Andreas Zeidler <a...@zitc.de> wrote:
On May 7, 2009, at 11:22 AM, kindy wrote:
could you give some sample code?

what i'm trying to do is add an accesskey attribute to an existing link:

 <a class="next" href="/changeset/25997" title="Changeset 25997">Next Changeset</a>

so that it becomes:

 <a class="next" href="/changeset/25997" title="Changeset 25997" accesskey="n">Next Changeset</a>

trac uses a genshi template that is applied to every rendered page.  currently mine looks like:

 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/" py:strip="">
   <a py:match="a[@class='next']" py:attrs="select('@*')">${select('*|text()')}</a>


<a py:match="a[@class='next']" accesskey="n" py:attrs="select('@*')">${select('*|text()')}</a>

will this OK?
 
 </html>

this matches the link but otherwise effectively does nothing (since all existing attributes are replaced with all existing attributes :)).  like i said, a `py:attrs="{'accesskey': 'n'}"` replaces _all_ attributes, i.e. ending up in:

 <a accesskey="n">Next Changeset</a>

what i'd like to know is how to add the extra attribute here (without using extra python code — since i don't think i can wrt the way trac is set up).


best regards,


andi

--
zeidler it consulting - http://zitc.de/ - in...@zitc.de
friedelstraße 31 - 12047 berlin - telefon +49 30 25563779
pgp key at http://zitc.de/pgp - http://wwwkeys.de.pgp.net/
plone 3.2.2 released! -- http://plone.org/products/plone/




--
Regards,

Lin Qing

Andreas Zeidler

unread,
May 8, 2009, 5:23:44 AM5/8/09
to gen...@googlegroups.com
On May 7, 2009, at 6:09 PM, kindy wrote:
On Thu, May 7, 2009 at 6:46 PM, Andreas Zeidler <a...@zitc.de> wrote:
On May 7, 2009, at 11:22 AM, kindy wrote:
could you give some sample code?

what i'm trying to do is add an accesskey attribute to an existing link:

 <a class="next" href="/changeset/25997" title="Changeset 25997">Next Changeset</a>

so that it becomes:

 <a class="next" href="/changeset/25997" title="Changeset 25997" accesskey="n">Next Changeset</a>

trac uses a genshi template that is applied to every rendered page.  currently mine looks like:

 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/" py:strip="">
   <a py:match="a[@class='next']" py:attrs="select('@*')">${select('*|text()')}</a>

<a py:match="a[@class='next']" accesskey="n" py:attrs="select('@*')">${select('*|text()')}</a>

will this OK?

no, this doesn't work.  the `py:attrs` still replaces all attributes including the one given here...
PGP.sig

Kyle Schaffrick

unread,
May 8, 2009, 2:46:00 PM5/8/09
to gen...@googlegroups.com
On Fri, 8 May 2009 11:23:44 +0200
Andreas Zeidler <a...@zitc.de> wrote:

> On May 7, 2009, at 6:09 PM, kindy wrote:
> > On Thu, May 7, 2009 at 6:46 PM, Andreas Zeidler <a...@zitc.de> wrote:
> > On May 7, 2009, at 11:22 AM, kindy wrote:
> > could you give some sample code?
> >
> > what i'm trying to do is add an accesskey attribute to an existing
> > link:
> >
> > <a class="next" href="/changeset/25997" title="Changeset
> > 25997">Next Changeset</a>
> >
> > so that it becomes:
> >
> > <a class="next" href="/changeset/25997" title="Changeset 25997"
> > accesskey="n">Next Changeset</a>
> >
> > trac uses a genshi template that is applied to every rendered
> > page. currently mine looks like:
> >
> > <html xmlns="http://www.w3.org/1999/xhtml"
> > xmlns:py="http://genshi.edgewall.org/" py:strip="">
> > <a py:match="a[@class='next']" py:attrs="select('@*')">$
> > {select('*|text()')}</a>
> >
> > <a py:match="a[@class='next']" accesskey="n"
> > py:attrs="select('@*')"> ${select('*|text()')}</a>
> >
> > will this OK?
>
> no, this doesn't work. the `py:attrs` still replaces all attributes
> including the one given here...
>

My first reaction was that py:attrs accepts any expression, so just
write an expression that merges the manually-added attribute with the
ones from the matched element. However, this won't be straightforward at
all because select() returns a Stream: it would seem to require you to
manually insert stream events into it.

If there were a way to get the attributes from the matched element in a
more useful structure (like a dict, which you can update and dump right
back into py:attrs) then this would be trivial, but so far I can't find
anything that does this out of the box...

An analogue of xsl:attribute would make this trivial as well, but I
doubt that can be done with the stream-based design of Genshi.

-Kyle

Andreas Zeidler

unread,
May 8, 2009, 6:02:30 PM5/8/09
to gen...@googlegroups.com
On May 8, 2009, at 8:46 PM, Kyle Schaffrick wrote:
> My first reaction was that py:attrs accepts any expression, so just
> write an expression that merges the manually-added attribute with the
> ones from the matched element. However, this won't be
> straightforward at
> all because select() returns a Stream: it would seem to require you to
> manually insert stream events into it.

exactly. i made it to this point and was expecting the API of the
stream object to be of help, actually. having figured out that the
stream iterator returns triplets similar to what i knew from
elementtree, i decided not to try insert a few more inside a single
expression. at this point i was kinda assuming there must be a far
easier way which i had simply failed to see so far...

> If there were a way to get the attributes from the matched element
> in a
> more useful structure (like a dict, which you can update and dump
> right
> back into py:attrs) then this would be trivial, but so far I can't
> find
> anything that does this out of the box...

that would indeed be more like what one would expect. however, i
couldn't find any other examples either.

> An analogue of xsl:attribute would make this trivial as well, but I
> doubt that can be done with the stream-based design of Genshi.

not sure, but perhaps someone else knows the missing piece here... :)
thanks for looking into this, though!

best regards,

PGP.sig

Nicholas Dudfield

unread,
May 8, 2009, 9:01:10 PM5/8/09
to gen...@googlegroups.com
Here's a horrible hack (which does work) I did for rewriting relative
image paths to absolutes

<py:match path="img">
<img py:attrs="dict((k, '/%s'%v if k == 'src' else v) for a in
select('@*') for (k,v) in a)" />
</py:match

Based on the above I'm guessing something like this could work (untested) :

dict([kv for a in select('@*') for kv in a] + [(newk, newv)])

Good luck

kindy

unread,
May 10, 2009, 9:59:55 AM5/10/09
to gen...@googlegroups.com
On Sat, May 9, 2009 at 6:02 AM, Andreas Zeidler <a...@zitc.de> wrote:
On May 8, 2009, at 8:46 PM, Kyle Schaffrick wrote:
My first reaction was that py:attrs accepts any expression, so just
write an expression that merges the manually-added attribute with the
ones from the matched element. However, this won't be straightforward at
all because select() returns a Stream: it would seem to require you to
manually insert stream events into it.

exactly.  i made it to this point and was expecting the API of the stream object to be of help, actually.  having figured out that the stream iterator returns triplets similar to what i knew from elementtree, i decided not to try insert a few more inside a single expression.  at this point i was kinda assuming there must be a far easier way which i had simply failed to see so far...


If there were a way to get the attributes from the matched element in a
more useful structure (like a dict, which you can update and dump right
back into py:attrs) then this would be trivial, but so far I can't find
anything that does this out of the box...

that would indeed be more like what one would expect.  however, i couldn't find any other examples either.

<a py:match="a[@class='next']" py:attrs="dict(list(list(select('@*'))[0]) + [('accesskey', 'n')])">
 
attrs = select('@*') # is a stream
attrs = list(attrs)    # convert to a list
attrs = attrs[0]      # the first one is the attributes (Attr -< Tuple)
attrs = list(attrs)   # convert the tuple to list, because 
#                           we need to add our custom attr
attrs += [('accesskey', 'n')]  # add out attr

attrs = dict(attrs)


i hope this can do some help.
:)



An analogue of xsl:attribute would make this trivial as well, but I
doubt that can be done with the stream-based design of Genshi.

not sure, but perhaps someone else knows the missing piece here... :)  thanks for looking into this, though!

best regards,



andi

--
zeidler it consulting - http://zitc.de/ - in...@zitc.de
friedelstraße 31 - 12047 berlin - telefon +49 30 25563779
pgp key at http://zitc.de/pgp - http://wwwkeys.de.pgp.net/
plone 3.2.2 released! -- http://plone.org/products/plone/




--
Regards,

Lin Qing

fluence

unread,
May 11, 2009, 7:22:01 AM5/11/09
to Genshi
Lin,

Cheers for clearing that up.

Andreas Zeidler

unread,
May 13, 2009, 4:54:44 AM5/13/09
to gen...@googlegroups.com
hi nicholas, hi kindy!

many thanks for your help! both of your versions:

<a py:match="a[@class='next']"

py:attrs="dict(list(list(select('@*'))[0]) + [('foo', 'p')])">...</a>
<a py:match="a[@class='next']" py:attrs="dict([i for a in
select('@*') for i in a] + [('foo', 'n')])">...</a>

do work indeed. however, after wondering for a bit why the accesskey
attribute still didn't show up, it turned out that it somehow gets
filtered: adding an attribute "foo" works fine, but as soon as i name
it "accesskey" it gets dropped again. i have no idea why that is, but
suppose it's specific to trac...

but anyway, thanks again for looking into this!

regards,


andi


On May 9, 2009, at 3:01 AM, Nicholas Dudfield wrote:
> Here's a horrible hack (which does work) I did for rewriting relative
> image paths to absolutes
>
> <py:match path="img">
> <img py:attrs="dict((k, '/%s'%v if k == 'src' else v) for a in
> select('@*') for (k,v) in a)" />
> </py:match
>
> Based on the above I'm guessing something like this could work
> (untested) :
>
> dict([kv for a in select('@*') for kv in a] + [(newk, newv)])
>
> Good luck


On May 10, 2009, at 3:59 PM, kindy wrote:
> <a py:match="a[@class='next']" py:attrs="dict(list(list(select('@*'))
> [0]) + [('accesskey', 'n')])">
>
> attrs = select('@*') # is a stream
> attrs = list(attrs) # convert to a list
> attrs = attrs[0] # the first one is the attributes (Attr -<
> Tuple)
> attrs = list(attrs) # convert the tuple to list, because
> # we need to add our custom attr
> attrs += [('accesskey', 'n')] # add out attr
>
> attrs = dict(attrs)
>
> i hope this can do some help.
> :)

--

PGP.sig

kindy

unread,
May 13, 2009, 5:14:14 AM5/13/09
to gen...@googlegroups.com
in trac.web.chrome

Chrome.render_template,
there has some code like this:

if not req.session or not int(req.session.get('accesskeys', 0)):
    stream |= self._strip_accesskeys


this place strip the accesskey attribute.

:)
--
Regards,

Lin Qing

Andreas Zeidler

unread,
May 13, 2009, 5:22:32 AM5/13/09
to gen...@googlegroups.com
On May 13, 2009, at 11:14 AM, kindy wrote:
> this place strip the accesskey attribute.
> :)

aha! :) and they can be re-enabled via the "preferences", .../prefs/
keybindings

many thanks again!


andi

PGP.sig

Leho Kraav

unread,
Sep 14, 2014, 7:13:52 PM9/14/14
to gen...@googlegroups.com, a...@zitc.de


On Wednesday, May 13, 2009 11:54:44 AM UTC+3, Andreas Zeidler wrote:
hi nicholas, hi kindy!

many thanks for your help!  both of your versions:

   <a py:match="a[@class='next']"  
py:attrs="dict(list(list(select('@*'))[0]) + [('foo', 'p')])">...</a>
   <a py:match="a[@class='next']" py:attrs="dict([i for a in  
select('@*') for i in a] + [('foo', 'n')])">...</a>

do work indeed.  however, after wondering for a bit why the accesskey  
attribute still didn't show up, it turned out that it somehow gets  
filtered:  adding an attribute "foo" works fine, but as soon as i name  
it "accesskey" it gets dropped again.  i have no idea why that is, but  
suppose it's specific to trac...


How would one add classes to an existing list based on the above examples?

Simon Cross

unread,
Sep 20, 2014, 5:57:33 AM9/20/14
to gen...@googlegroups.com, a...@zitc.de
On Mon, Sep 15, 2014 at 1:13 AM, Leho Kraav <le...@kraav.com> wrote:
> How would one add classes to an existing list based on the above examples?

This is a bit separate to the question asked in this thread, but a
possible solution is something like:

<a py:attrs="{'class': ['foo', 'bar'] + extra_classes}">...</a>

Schiavo
Simon

Leho Kraav

unread,
Sep 21, 2014, 12:51:39 PM9/21/14
to gen...@googlegroups.com, a...@zitc.de

I'm looking for something like merging the attribute values.

http://stackoverflow.com/a/17604869/35946

class="ticket" would want to become class="ticket foo bar"

This feels like the dict(list(list(select('@*'))[0]) + [('foo', 'p')]) magic would need to become some kind of a macro. It's not feasible to repeat that conversion 20 times in a match template. Can this be done with py:def?

Simon Cross

unread,
Sep 21, 2014, 1:46:08 PM9/21/14
to gen...@googlegroups.com, a...@zitc.de
Ah, so you're wanting to do this inside a py:match?

You might be able to make this work with py:def. Because Genshi
attempts to give guarantees on the correctness of the XML produced,
py:def produces a single block of XML/HTML. This may either be exactly
what you want or not. :)

If py:def doesn't work, you can always add a suitable helper to your
template context or define it in a code block [1].

[1] http://genshi.edgewall.org/wiki/Documentation/templates.html#id1

Aside: As a general rule, I would prefer using py:def over py:match
where possible. py:def is explicit and easy to understand. Lots of
py:match rules can make templates slow to render and hard to
understand.

Schiavo
Simon

Leho Kraav

unread,
Sep 21, 2014, 1:47:09 PM9/21/14
to gen...@googlegroups.com, a...@zitc.de
This is what I came up with:

<!-- early in the template -->
<?python
# https://groups.google.com/forum/#!topic/genshi/BS-KypqIHVQ
# http://stackoverflow.com/a/17604869/35946
def merge_attrs(attrs, moar_attrs):
if not isinstance(moar_attrs, dict) or not moar_attrs:
return attrs

attrs = dict(list(list(attrs)[0]))
keys = attrs.viewkeys() | moar_attrs.viewkeys()

return dict((k, attrs.get(k,"") + " " + moar_attrs.get(k,"")) for
k in keys)

# Exception: Unhandled node type <class '_ast.DictComp'>
#return {k: attrs.get(k,"") + " " + moar_attrs.get(k,"") for k
in keys}
?>

<!-- later -->

<div py:match="div[@id='content']" py:attrs="merge_attrs(select('@*'),
{'class': 'foo bar baz'})">${select("*")}</div>

Result:

<div class="ticket foo bar baz" id="content">


Looks good to me. Except I'd like to generalize this to be usable and
maintainable in a more centralized way, not just copy-paste into each
template everywhere. Could this be a core Genshi function of some sort?

Simon Cross

unread,
Sep 21, 2014, 1:51:18 PM9/21/14
to gen...@googlegroups.com, a...@zitc.de
On Sun, Sep 21, 2014 at 7:32 PM, Leho Kraav <le...@kraav.com> wrote:
> Looks good to me. Except I'd like to generalize this to be usable and
> maintainable in a more centralized way, not just copy-paste into each
> template everywhere. Could this be a core Genshi function of some sort?

I'm not sure that this should be part of core Genshi. I can imagine a
module of utilities and passing the module into your template context.
E.g.

"""utils.py"""

def merge_attrs(attrs, moar_attrs):
....

def other_things(...):
....

And then .generate(utils=utils).

I'm not completely closed to suggestions to add such a utility module
to Genshi itself (or incorporate it into Genshi in some other way),
but I think for now it would make more sense to develop the utilities
outside of Genshi and then incorporate them once we have a set that
works nicely?

Schiavo
Simon
Reply all
Reply to author
Forward
0 new messages