changeset 2599:636f439f2bbf

add class attr to moinwiki, rst; improve alt attr code in moinwiki, rst, creole, markdown; fix #7
author RogerHaase <haaserd@gmail.com>
date Sun, 11 May 2014 13:39:38 -0700
parents b975a8b1f41c
children d62370bd12e1
files MoinMoin/converter/_tests/test_creole_in.py MoinMoin/converter/_tests/test_image.py MoinMoin/converter/_tests/test_include.py MoinMoin/converter/_tests/test_markdown_in.py MoinMoin/converter/_tests/test_moinwiki_in.py MoinMoin/converter/_tests/test_moinwiki_in_out.py MoinMoin/converter/_tests/test_moinwiki_out.py MoinMoin/converter/creole_in.py MoinMoin/converter/docbook_in.py MoinMoin/converter/html_out.py MoinMoin/converter/include.py MoinMoin/converter/markdown_in.py MoinMoin/converter/moinwiki_in.py MoinMoin/converter/moinwiki_out.py MoinMoin/converter/rst_in.py MoinMoin/static/js/common.js MoinMoin/themes/foobar/static/css/common.css MoinMoin/themes/foobar/static/css/stylus/userstyles.css MoinMoin/themes/modernized/static/css/common.css MoinMoin/themes/modernized/static/css/stylus/userstyles.css
diffstat 20 files changed, 192 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/converter/_tests/test_creole_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_creole_in.py	Sun May 11 13:39:38 2014 -0700
@@ -8,7 +8,7 @@
 
 import re
 
-from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink, html
 
 from ..creole_in import Converter
 
@@ -17,6 +17,7 @@
     namespaces = {
         moin_page: '',
         xlink: 'xlink',
+        html: 'xhtml'
     }
 
     output_re = re.compile(r'\s+xmlns(:\S+)?="[^"]+"')
@@ -47,9 +48,9 @@
             (u'[[MoinMoin]]',
                 '<page><body><p><a xlink:href="wiki.local:MoinMoin">MoinMoin</a></p></body></page>'),
             (u'{{http://moinmo.in/}}',
-                '<page><body><p><object xlink:href="http://moinmo.in/">http://moinmo.in/</object></p></body></page>'),
+                '<page><body><p><object xlink:href="http://moinmo.in/" /></p></body></page>'),
             (u'{{http://moinmo.in/|MoinMoin}}',
-                '<page><body><p><object xlink:href="http://moinmo.in/">MoinMoin</object></p></body></page>'),
+                '<page><body><p><object xhtml:alt="MoinMoin" xlink:href="http://moinmo.in/" /></p></body></page>'),
             (u'----',
                 '<page><body><separator /></body></page>'),
         ]
--- a/MoinMoin/converter/_tests/test_image.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_image.py	Sun May 11 13:39:38 2014 -0700
@@ -40,7 +40,7 @@
         ]
 
         output = ('<div xmlns="http://www.w3.org/1999/xhtml"><p data-lineno="1"><span class="moin-transclusion" '
-                  'data-href="/imagetest"><img src="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest">'
+                  'data-href="/imagetest"><img alt="imagetest" src="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest">'
                   '</span></p></div>')
 
         for imagetype in tests:
@@ -55,7 +55,7 @@
                         'ns2:height="10" ns2:width="10" ns0:type="image/jpeg" />'
                         '</ns0:body></ns0:page></ns0:p></ns0:body></ns0:page>')
 
-        image_resize_out = ('<div xmlns="http://www.w3.org/1999/xhtml"><p><div><img height="10" '
+        image_resize_out = ('<div xmlns="http://www.w3.org/1999/xhtml"><p><div><img alt="imagetest" height="10" '
                             'src="/+get/+2882c905b2ab409fbf79cd05637a112d/imagetest" width="10">'
                             '</div></p></div>')
 
--- a/MoinMoin/converter/_tests/test_include.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_include.py	Sun May 11 13:39:38 2014 -0700
@@ -170,6 +170,11 @@
         rendered = Item.create(u'page1').content._render_data()
         assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="logo.png" src=' in rendered
         assert '/logo.png" /></span></p>' in rendered
+        # simple transclusion with alt text and width
+        update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'{{logo.png|my alt text|width="100"}}')
+        rendered = Item.create(u'page1').content._render_data()
+        assert '<p><span class="moin-transclusion" data-href="/logo.png"><img alt="my alt text" src=' in rendered
+        assert 'logo.png" width="100" /></span></p>' in rendered
         # within paragraph
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki;charset=utf-8'}, u'text {{logo.png}} text')
         rendered = Item.create(u'page1').content._render_data()
--- a/MoinMoin/converter/_tests/test_markdown_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_markdown_in.py	Sun May 11 13:39:38 2014 -0700
@@ -135,11 +135,11 @@
             (u'![Alt text](png "Optional title")',
                 '<p><xinclude:include html:alt="Alt text" xinclude:href="wiki.local:png" /></p>'),
             (u'![](png "Optional title")',
-                '<p><xinclude:include html:alt="" xinclude:href="wiki.local:png" /></p>'),
+                '<p><xinclude:include xinclude:href="wiki.local:png" /></p>'),
             (u'![remote image](http://static.moinmo.in/logos/moinmoin.png)',
-                '<p><object xlink:href="http://static.moinmo.in/logos/moinmoin.png">remote image</object></p>'),
+                '<p><object html:alt="remote image" xlink:href="http://static.moinmo.in/logos/moinmoin.png" /></p>'),
             (u'![Alt text](http://test.moinmo.in/png)',
-                '<p><object xlink:href="http://test.moinmo.in/png">Alt text</object></p>'),
+                '<p><object html:alt="Alt text" xlink:href="http://test.moinmo.in/png" /></p>'),
             (u'![transclude local wiki item](someitem)',
                 '<p><xinclude:include html:alt="transclude local wiki item" xinclude:href="wiki.local:someitem" /></p>'),
         ]
--- a/MoinMoin/converter/_tests/test_moinwiki_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_moinwiki_in.py	Sun May 11 13:39:38 2014 -0700
@@ -46,13 +46,18 @@
                 '<page><body><p><a xlink:href="wiki.local:MoinMoin">MoinMoin</a></p></body></page>'),
             (u'{{somelocalimage|my alt text|width=10, height=10}}',
                 '<page><body><p><xinclude:include xhtml:alt="my alt text" xhtml:height="10" xhtml:width="10" xinclude:href="wiki.local:somelocalimage?" /></p></body></page>'),
+            # html5 requires img tags to have an alt attribute, html_out.py will add any required attributes that are missing
             (u'{{somelocalimage||width=10, height=10}}',
                 '<page><body><p><xinclude:include xhtml:height="10" xhtml:width="10" xinclude:href="wiki.local:somelocalimage?" /></p></body></page>'),
             (u'{{somelocalimage||width=10, &h=10}}',
                 '<page><body><p><xinclude:include xhtml:width="10" xinclude:href="wiki.local:somelocalimage?h=10" /></p></body></page>'),
-            # object tags do not have alt attributes, instead it is placed before the </object>
+            (u'before {{somelocalimage}} middle {{somelocalimage}} after',
+                '<page><body><p>before <xinclude:include xinclude:href="wiki.local:somelocalimage?" /> middle <xinclude:include xinclude:href="wiki.local:somelocalimage?" /> after</p></body></page>'),
+            (u'before {{http://moinmo.in}} middle {{http://moinmo.in}} after',
+                '<page><body><p>before <object xlink:href="http://moinmo.in" /> middle <object xlink:href="http://moinmo.in" /> after</p></body></page>'),
+            # in html5, object tags must not have alt attributes, html_out.py will adjust this so alt text is placed before the </object>
             (u'{{http://moinmo.in/|test|width=10, height=10}}',
-                '<page><body><p><object xhtml:height="10" xhtml:width="10" xlink:href="http://moinmo.in/">test</object></p></body></page>'),
+                '<page><body><p><object xhtml:alt="test" xhtml:height="10" xhtml:width="10" xlink:href="http://moinmo.in/" /></p></body></page>'),
             (u'{{http://moinmo.in/}}',
                 '<page><body><p><object xlink:href="http://moinmo.in/" /></p></body></page>', None, 'unknown'),
             (u'{{http://moinmo.in/|MoinMoin}}',
--- a/MoinMoin/converter/_tests/test_moinwiki_in_out.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_moinwiki_in_out.py	Sun May 11 13:39:38 2014 -0700
@@ -15,18 +15,21 @@
 import re
 
 from emeraldtree import ElementTree as ET
-from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink, xinclude, html
 from MoinMoin.converter.moinwiki_in import Converter as conv_in
 from MoinMoin.converter.moinwiki_out import Converter as conv_out
 
 
 class TestConverter(object):
 
-    input_namespaces = 'xmlns="{0}" xmlns:page="{1}" xmlns:xlink="{2}"'.format(moin_page.namespace, moin_page.namespace, xlink.namespace)
+    input_namespaces = 'xmlns="{0}" xmlns:page="{1}" xmlns:xlink="{2}" xmlns:xinclude="{3}" xmlns:html="{4}"'.format(
+        moin_page.namespace, moin_page.namespace, xlink.namespace, xinclude.namespace, html.namespace)
 
     namespaces = {
         moin_page.namespace: 'page',
         xlink.namespace: 'xlink',
+        xinclude.namespace: 'xinclude',
+        html.namespace: 'html',
     }
     input_re = re.compile(r'^(<[a-z:]+)')
     output_re = re.compile(r'\s+xmlns(:\S+)?="[^"]+"')
@@ -87,7 +90,8 @@
             # (u'[[http://moinmo.in/|MoinMoin Wiki|class=green dotted, accesskey=1]]', '[[http://moinmo.in/|MoinMoin Wiki|class=green dotted,accesskey=1]]\n'),
             # (u'[[MoinMoin:MoinMoinWiki|MoinMoin Wiki|&action=diff,&rev1=1,&rev2=2]]', '[[MoinMoin:MoinMoinWiki|MoinMoin Wiki|&action=diff,&rev1=1,&rev2=2]]\n'),
             # (u'[[attachment:HelpOnImages/pineapple.jpg|a pineapple|&do=get]]', '[[attachment:HelpOnImages/pineapple.jpg|a pineapple|&do=get]]\n'),
-            # (u'[[attachment:filename.txt]]', '[[attachment:filename.txt]]\n')
+            # Note: old style attachments are converted to new style sub-item syntax
+            (u'[[attachment:filename.txt]]', '[[/filename.txt]]\n')
         ]
         for i in data:
             yield (self.do, ) + i
@@ -115,20 +119,24 @@
 
     def test_object(self):
         data = [
-            # (u'{{png}}', '{{png}}\n'),
-            # (u'{{png|png}}', '{{png|png}}\n'),
-            # (u'{{png|my png}}', '{{png|my png}}\n'),
-            # (u'{{png|my png|width=100}}', '{{png|my png|width=100}}\n'),
-            # (u'{{png||width=100}}', '{{png||width=100}}\n'),
-            # (u"{{drawing:anywikitest.adraw}}", '{{drawing:anywikitest.adraw}}\n'),
+            (u'{{png}}', '{{png}}\n'),
+            (u'{{png|png}}', '{{png|png}}\n'),  # alt text same as default test
+            (u'{{png|my png}}', '{{png|my png}}\n'),
+            # output attributes will always be quoted, even if input is not quoted
+            (u'{{png|my png|width=100}}', '{{png|my png|width="100"}}\n'),
+            (u'{{png|my png|&w=100"}}', '{{png|my png|&w=100}}\n'),
+            (u'{{png||width="100"}}', '{{png||width="100"}}\n'),
+            (u"{{drawing:anywikitest.adraw}}", '{{drawing:anywikitest.adraw}}\n'),
             (u"{{http://static.moinmo.in/logos/moinmoin.png}}\n", '{{http://static.moinmo.in/logos/moinmoin.png}}\n'),
-            # (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text}}\n'),
-            # (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100 height=150 align=right}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100 height=150 align=right}}\n'),
-            # (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100}}\n'),
-            # (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100 height=150 style="float: right"}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|width=100 height=150 style="float: right"}}\n'),
-            # (u'{{attachment:image.png}}', '{{attachment:image.png}}\n'),
-            # (u'{{attachment:image.png|alt text}}', '{{attachment:image.png|alt text}}\n'),
-            # (u'{{attachment:image.png|alt text|width=100 align=left height=150}}', '{{attachment:image.png|alt text|width=100 align=left height=150}}\n'),
+            (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text}}\n', '{{http://static.moinmo.in/logos/moinmoin.png|alt text}}\n'),
+            # output sequence of height, width, class may not be the same as input, so here we test only one attribute at a time to avoid random test failures
+            (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|height="150"}}\n', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|height="150"}}\n'),
+            (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|width="100"}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|width="100"}}\n'),
+            (u'{{http://static.moinmo.in/logos/moinmoin.png|alt text|class="right"}}', '{{http://static.moinmo.in/logos/moinmoin.png|alt text|class="right"}}\n'),
+            # Note: old style attachments are converted to new style sub-item syntax
+            (u'{{attachment:image.png}}', '{{/image.png}}\n'),
+            (u'{{attachment:image.png|alt text}}', '{{/image.png|alt text}}\n'),
+            (u'{{attachment:image.png|alt text|height="150"}}', '{{/image.png|alt text|height="150"}}\n'),
 
         ]
         for i in data:
--- a/MoinMoin/converter/_tests/test_moinwiki_out.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/_tests/test_moinwiki_out.py	Sun May 11 13:39:38 2014 -0700
@@ -13,7 +13,8 @@
 
 
 class Base(object):
-    input_namespaces = ns_all = 'xmlns="{0}" xmlns:page="{1}" xmlns:xlink="{2}"'.format(moin_page.namespace, moin_page.namespace, xlink.namespace)
+    input_namespaces = ns_all = 'xmlns="{0}" xmlns:page="{1}" xmlns:xlink="{2}" xmlns:html="{3}"'.format(
+        moin_page.namespace, moin_page.namespace, xlink.namespace, html.namespace)
     output_namespaces = {
         moin_page.namespace: 'page'
     }
@@ -74,13 +75,12 @@
 
     def test_object(self):
         data = [
-            (u"<page:object xlink:href=\"drawing:anywikitest.adraw\">drawing:anywikitest.adraw</page:object>", '{{drawing:anywikitest.adraw}}'),
+            (u"<page:object xlink:href=\"drawing:anywikitest.adraw\"></page:object>", '{{drawing:anywikitest.adraw}}'),
             (u"<page:object xlink:href=\"http://static.moinmo.in/logos/moinmoin.png\" />", '{{http://static.moinmo.in/logos/moinmoin.png}}'),
             (u'<page:object page:alt="alt text" xlink:href="http://static.moinmo.in/logos/moinmoin.png">alt text</page:object>', '{{http://static.moinmo.in/logos/moinmoin.png|alt text}}'),
             (u'<page:object xlink:href="attachment:image.png" />', '{{attachment:image.png}}'),
-            # TODO: review following 2 lines, alt is invalid within object tag, align is not valid in html5
             (u'<page:object page:alt="alt text" xlink:href="attachment:image.png">alt text</page:object>', '{{attachment:image.png|alt text}}'),
-            (u'<page:object page:alt="alt text" xlink:href="attachment:image.png?width=100&amp;height=150&amp;align=left" />', '{{attachment:image.png|alt text|width=100 height=150 align=left}}'),
+            (u'<page:object xlink:href="attachment:image.png" html:width="100" html:height="150" html:class="left">alt text</page:object>', '{{attachment:image.png|alt text|height="150" width="100" class="left"}}'),
         ]
         for i in data:
             yield (self.do, ) + i
--- a/MoinMoin/converter/creole_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/creole_in.py	Sun May 11 13:39:38 2014 -0700
@@ -29,7 +29,7 @@
 
 from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
-from MoinMoin.util.tree import moin_page, xlink, xinclude
+from MoinMoin.util.tree import moin_page, xlink, xinclude, html
 
 from ._args_wiki import parse as parse_arguments
 from ._wiki_macro import ConverterMacro
@@ -402,26 +402,20 @@
 
     def inline_object_repl(self, stack, object, object_page=None, object_url=None, object_text=None):
         """Handles objects included in the page."""
-
+        attrib = {}
+        if object_text:
+            attrib[html.alt] = object_text
         if object_page is not None:
             att = 'attachment:'  # moin 1.9 needed this for an attached file
             if object_page.startswith(att):
                 object_page = '/' + object_page[len(att):]  # now we have a subitem
             target = Iri(scheme='wiki.local', path=object_page)
-            text = object_page
-
-            attrib = {xinclude.href: target}
+            attrib[xinclude.href] = target
             element = xinclude.include(attrib=attrib)
-            stack.top_append(element)
-
         else:
-            target = object_url
-            text = object_url
-
-            element = moin_page.object({xlink.href: target})
-            stack.push(element)
-            self.parse_inline(object_text or text, stack, self.link_desc_re)
-            stack.pop()
+            attrib[xlink.href] = object_url
+            element = moin_page.object(attrib)
+        stack.top_append(element)
 
     inline_url = r"""
         (?P<url>
--- a/MoinMoin/converter/docbook_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/docbook_in.py	Sun May 11 13:39:38 2014 -0700
@@ -564,8 +564,9 @@
         attrib[key] = "footnote"
         children = self.new(moin_page('note-body'), attrib={},
                             children=self.do_children(element, depth))
-        # must delete lineno because footnote will be placed near end of page and out of sequence
-        children._children[1].attrib.pop(html.data_lineno, None)
+        if len(children) > 1:
+            # must delete lineno because footnote will be placed near end of page and out of sequence
+            del children._children[1].attrib[html.data_lineno]
         return self.new(moin_page.note, attrib=attrib, children=[children])
 
     def visit_docbook_formalpara(self, element, depth):
--- a/MoinMoin/converter/html_out.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/html_out.py	Sun May 11 13:39:38 2014 -0700
@@ -19,8 +19,9 @@
 
 from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin.util.tree import html, moin_page, xlink, xml, Name
+from MoinMoin.util.tree import html, moin_page, xlink, xml, Name, xinclude
 from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
+from MoinMoin.util.iri import Iri
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -355,11 +356,13 @@
         """
         href = elem.get(xlink.href, None)
         attrib = {}
-        whitelist = ['width', 'height']
+        whitelist = ['width', 'height', 'alt', 'class']
         for key in elem.attrib:
             if key.name in whitelist:
                 attrib[key] = elem.attrib[key]
         mimetype = Type(_type=elem.get(moin_page.type_, CONTENTTYPE_NONEXISTENT))
+        if elem.get(moin_page.type_):
+            del elem.attrib[moin_page.type_]
         # Get the object type
         obj_type = self.eval_object_type(mimetype, href)
 
@@ -373,20 +376,28 @@
         if href is not None:
             # Set the attribute of the returned element appropriately
             attrib[attr] = href
+        alt = convert_getlink_to_showlink(unicode(href))
+        alt = re.sub('^\/', '', alt)
 
         if obj_type == "img":
-            # Images have alt text
+            # Images must have alt attribute in html5, but if user did not specify then default to url
             if not attrib.get(html.alt):
-                alt = ''.join(unicode(e) for e in elem)  # XXX handle non-text e
-                if alt:
-                    attrib[html.alt] = alt
+                attrib[html.alt] = alt
             new_elem = html.img(attrib=attrib)
 
         else:
             if obj_type != "object":
-                # Non-objects have the "controls" attribute
+                # Non-objects like video and audio have the "controls" attribute
                 attrib[html.controls] = 'controls'
-            new_elem = self.new_copy(getattr(html, obj_type), elem, attrib)
+                new_elem = self.new_copy(getattr(html, obj_type), elem, attrib)
+            else:
+                # is an object
+                new_elem = html.object(attrib=attrib)
+                if new_elem.attrib.get(html.alt):
+                    new_elem.append(new_elem.attrib.get(html.alt))
+                    del new_elem.attrib[html.alt]
+                else:
+                    new_elem.append(alt)
 
         if obj_type == "object" and getattr(href, 'scheme', None):
             # items similar to {{http://moinmo.in}} are marked here, other objects are marked in include.py
--- a/MoinMoin/converter/include.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/include.py	Sun May 11 13:39:38 2014 -0700
@@ -323,7 +323,7 @@
                         loop = self.stack[self.stack.index(p_href):]
                         loop = [u'{0}'.format(ref.path[1:]) for ref in loop if ref is not None] + [page.name]
                         msg = u'Error: Transclusion loop via: ' + u', '.join(loop)
-                        attrib = {getattr(moin_page, 'class'): 'moin-error'}
+                        attrib = {html.class_: 'moin-error'}
                         strong = ET.Element(moin_page.strong, attrib, (msg, ))
                         included_elements.append(strong)
                         continue
@@ -340,7 +340,6 @@
                         included_elements.append(elem_h)
 
                     page_doc = page.content.internal_representation(attributes=Arguments(keyword=elem.attrib))
-                    # page_doc.tag = self.tag_div # XXX why did we have this?
 
                     self.recurse(page_doc, page_href)
 
@@ -414,7 +413,10 @@
                                     # is usually replaced by container
                                     return [container, new_trans_ptr]
                             else:
-                                # default action for odd things like circular transclusion error messages
+                                # default action for inline transclusions or odd things like circular transclusion error messages
+                                classes = child.attrib.get(html.class_, '').split()
+                                classes += ret.attrib.get(html.class_, '').split()
+                                ret.attrib[html.class_] = ' '.join(classes)
                                 elem[i] = ret
                         elif isinstance(ret, types.ListType):
                             # a container has been returned.
@@ -435,20 +437,23 @@
                                 new_trans_ptr = len(container)
                                 # child may have classes like "comment" that must be added to transcluded element
                                 classes = child.attrib.get(moin_page.class_, '').split()
-                                # this must be html, not moin_page:
+                                # must use moin_page.class_ above, but use html.class below per html_out.py code
                                 classes += ret_container[trans_ptr].attrib.get(html.class_, '').split()
-                                # this must be html, not moin_page:
                                 ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes)
                                 container.append(ret_container[trans_ptr])  # the transclusion
                                 if len(after):
                                     container.append(after)
                                 return [container, new_trans_ptr]
                             else:
-                                # elem is a block element,
+                                # elem is a block element
+                                for grandchild in child:
+                                    if isinstance(grandchild, ET.Node) and grandchild.tag.name == u'include':
+                                        # the include may have classes that must be added to transcluded element
+                                        classes = grandchild.attrib.get(html.class_, '').split()
+                                        classes += ret_container[trans_ptr].attrib.get(html.class_, '').split()
+                                        ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes)
                                 # replace child element with the container generated in lower recursion
                                 elem[i:i + 1] = ret_container  # elem[i] is the child
-                                # avoid duplicate recursion over nodes already processed
-                                i += len(ret_container) - 1
                         else:
                             # default action for any ret not fitting special cases above,
                             # e.g. tranclusion is within a table cell
@@ -462,7 +467,6 @@
     def __call__(self, tree):
         self.stack = []
         self.recurse(tree, None)
-
         return tree
 
 
--- a/MoinMoin/converter/markdown_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/markdown_in.py	Sun May 11 13:39:38 2014 -0700
@@ -230,16 +230,17 @@
         """
         attrib = {}
         url = Iri(element.attrib.get('src'))
+        if element.attrib.get('alt'):
+            attrib[html.alt] = element.attrib.get('alt')
         if url.scheme is None:
             # img tag
-            attrib[html.alt] = element.attrib.get('alt', '')
             target = Iri(scheme='wiki.local', path=element.attrib.get("src"), fragment=None)
             attrib[xinclude.href] = target
             new_node = xinclude.include(attrib=attrib)
         else:
             # object tag
             attrib[xlink.href] = url
-            new_node = moin_page.object(attrib, children=[element.attrib.get('alt', '')])
+            new_node = moin_page.object(attrib)
         return new_node
 
     def visit_object(self, element):
--- a/MoinMoin/converter/moinwiki_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/moinwiki_in.py	Sun May 11 13:39:38 2014 -0700
@@ -835,13 +835,14 @@
 
         query_keys = {}
         attrib = {}
-        whitelist = ['width', 'height']
+        whitelist = ['width', 'height', 'class']
         for attr, value in args.iteritems():
             if attr.startswith('&'):
                 query_keys[attr[1:]] = value
             elif attr in whitelist:
                 attrib[html(attr)] = value
-
+        if object_text:
+            attrib[html.alt] = object_text
         if object_item is not None:
             # img tag
             query = url_encode(query_keys, charset=CHARSET, encode_keys=True)
@@ -850,22 +851,14 @@
                 object_item = '/' + object_item[len(att):]  # now we have a subitem
             target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None)
             attrib[xinclude.href] = target
-            if object_text:
-                attrib[html.alt] = object_text
             element = xinclude.include(attrib=attrib)
             stack.top_append(element)
         else:
             # object tag
             target = Iri(object_url)
-            text = object_url
             attrib[xlink.href] = target
             element = moin_page.object(attrib)
-            stack.push(element)
-            if object_text:
-                self.parse_inline(object_text, stack, self.inlinedesc_re)
-            else:
-                stack.top_append(text)
-            stack.pop()
+            stack.top_append(element)
 
     table = block_table
 
--- a/MoinMoin/converter/moinwiki_out.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/moinwiki_out.py	Sun May 11 13:39:38 2014 -0700
@@ -11,11 +11,12 @@
 
 from __future__ import absolute_import, division
 
-from MoinMoin.util.tree import moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink, xinclude, html
+from MoinMoin.util.iri import Iri
 
 from emeraldtree import ElementTree as ET
 
-from re import findall
+from re import findall, sub
 
 from werkzeug.utils import unescape
 
@@ -69,7 +70,9 @@
     Converter application/x.moin.document -> text/x.moin.wiki
     """
     namespaces = {
-        moin_page.namespace: 'moinpage'}
+        moin_page.namespace: 'moinpage',
+        xinclude: 'xinclude',
+    }
 
     supported_tag = {
         'moinpage': (
@@ -92,11 +95,14 @@
             'object',
             'table',
             'table_header',
-            'teble_footer',
+            'table_footer',
             'table_body',
             'table_row',
             'table_cell',
-        )
+        ),
+        'xinclude': (
+            'include',
+        ),
     }
 
     @classmethod
@@ -153,7 +159,8 @@
             f = getattr(self, n, None)
             if f is not None:
                 return f(elem)
-        return open_children(elem)
+        # process odd things like xinclude
+        return self.open_children(elem)
 
     def open_moinpage(self, elem):
         n = 'open_moinpage_' + elem.tag.name.replace('-', '_')
@@ -295,10 +302,8 @@
         return u''
 
     def open_moinpage_object(self, elem):
-        # TODO: this can be done with one regex:
-        href = elem.get(xlink.href, u'')
-        # XXX: We don't have Iri support for now
-        from MoinMoin.util.iri import Iri
+        """Process objects and xincludes."""
+        href = elem.get(xlink.href, elem.get(xinclude.href, u''))
         if isinstance(href, Iri):
             href = unicode(href)
         href = href.split(u'?')
@@ -306,16 +311,25 @@
         if len(href) > 1:
             args = u' '.join([s for s in findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$)', href[1]) if s[:3] != u'do='])
         href = href[0].split(u'wiki.local:')[-1]
-        # TODO: add '|' to Moinwiki class and rewrite this using % formatting
-        ret = Moinwiki.object_open
-        ret += href
-        alt = elem.get(moin_page.alt, u'')
-        if alt and alt != href:
-            # TODO: this will fail on: {{png||width=100}}
-            ret += u'|' + alt
-            if args:
-                ret += u'|' + args
-        ret += Moinwiki.object_close
+
+        if len(elem) and isinstance(elem[0], unicode):
+            # alt text for objects is enclosed within <object...>...</object>
+            alt = elem[0]
+        else:
+            alt = elem.attrib.get(html.alt, u'')
+
+        whitelist = {html.width: 'width', html.height: 'height', html.class_: 'class'}
+        options = []
+        for attr, value in elem.attrib.items():
+            if attr in whitelist.keys():
+                options.append('{0}="{1}"'.format(whitelist[attr], value))
+
+        if args:
+            args = u'&' + args
+        args += u' '.join(options)
+
+        ret = '{0}{1}|{2}|{3}{4}'.format(Moinwiki.object_open, href, alt, args, Moinwiki.object_close)
+        ret = sub(r"\|+}}", "}}", ret)
         return ret
 
     def open_moinpage_p(self, elem):
@@ -521,6 +535,11 @@
     def open_moinpage_table_of_content(self, elem):
         return u"<<TableOfContents({0})>>\n".format(elem.get(moin_page.outline_level, u""))
 
+    def open_xinclude(self, elem):
+        """Processing of transclusions is similar to objects."""
+        return self.open_moinpage_object(elem)
+
+
 from . import default_registry
 from MoinMoin.util.mime import Type, type_moin_document, type_moin_wiki
 default_registry.register(Converter.factory, type_moin_document, type_moin_wiki)
--- a/MoinMoin/converter/rst_in.py	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/converter/rst_in.py	Sun May 11 13:39:38 2014 -0700
@@ -372,10 +372,6 @@
         """
         Processes images and other transcluded objects.
         """
-        # TODO: ReST also defines "align" as a parameter, but it is invalid in HTML5.  Depending upon value
-        # of align, we could add a style attribute of either:
-        #   vertical-align: middle | top | bottom
-        #   float: left | right
         whitelist = ['width', 'height', 'alt', ]
         attrib = {}
         for key in whitelist:
@@ -389,6 +385,19 @@
                 if html(key) in attrib:
                     attrib[html(key)] = int(int(attrib[html(key)]) * scaling_factor)
 
+        # "align" parameter is invalid in HTML5. Convert it to a class defined in userstyles.css.
+        userstyles = {
+            'left': 'left',
+            'center': 'center',
+            'right': 'right',
+            'top': 'top',  # rst parser creates error messages for top, bottom, and middle
+            'bottom': 'bottom',
+            'middle': 'middle',
+        }
+        alignment = userstyles.get(node.get('align'))
+        if alignment:
+            attrib[html.class_] = alignment
+
         url = Iri(node['uri'])
         if url.scheme is None:
             # img
@@ -397,8 +406,6 @@
             new_node = xinclude.include(attrib=attrib)
         else:
             # obj
-            # TODO: alt is set above, OK on img tags, but invalid here on object tags, it needs to be a text child of object tag for present code in html_out.py
-            # this should be handled consistently for rest, moinwiki, markdown, mediawiki...
             new_node = moin_page.object(attrib)
             new_node.set(xlink.href, url)
 
--- a/MoinMoin/static/js/common.js	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/static/js/common.js	Sun May 11 13:39:38 2014 -0700
@@ -95,7 +95,7 @@
 // Transclusion initialization is executed once after document ready.
 MoinMoin.prototype.initTransclusionOverlays = function () {
     "use strict";
-    var elem, overlayUL, overlayLR, wrapper, wrappers, transclusions,
+    var elem, overlayUL, overlayLR, wrapper, wrappers, transclusions, classes,
         rightArrow = '\u2192';
     // get list of elements to be wrapped; must work in reverse order in case there are nested transclusions
     transclusions = $($('.moin-transclusion').get().reverse());
@@ -117,6 +117,18 @@
             if ($(elem).parent()[0].tagName === 'A') {
                 elem = $(elem).parent()[0];
             }
+            // copy user specified classes from img/object tag to wrapper
+            if (elem.tagName === 'OBJECT') {
+                // do not copy classes starting with moin-
+                classes = $(elem).attr('class');
+                classes = classes.split(" ").filter(function(c) {
+                    return c.lastIndexOf('moin-', 0) !== 0;
+                });
+                $(wrapper).addClass(classes.join(' '));
+            } else {
+                // copy all classes from img tags
+                $(wrapper).addClass($(elem).find(">:first-child").attr('class'));
+            }
             // insert wrapper after elem, append (move) elem, append overlays
             $(elem).after(wrapper);
             $(wrapper).append(elem);
--- a/MoinMoin/themes/foobar/static/css/common.css	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/themes/foobar/static/css/common.css	Sun May 11 13:39:38 2014 -0700
@@ -151,6 +151,13 @@
 .right { text-align: right; }
 .justify { text-align: justify; }
 .monospaced { font-family: monospace; }
+/* position image or object - the left, center, right rules above are either consistent with or harmless to the rules below */
+.moin-item-wrapper.left { float: left; }
+.moin-item-wrapper.center { width: 100%; text-align: center; }
+.moin-item-wrapper.right { float: right; }
+.moin-item-wrapper.middle { vertical-align: middle; }
+.moin-item-wrapper.top { vertical-align: top; }
+.moin-item-wrapper.bottom { vertical-align: bottom; }
 a.moin-www:before,a.moin-http:before,a.moin-https:before{content:url("../img/moin-www.png");margin:0 .2em;vertical-align:middle}
 a.moin-file:before,a.moin-ftp:before{content:url("../img/moin-ftp.png");margin:0 .2em;vertical-align:middle}
 a.moin-nntp:before,a.moin-news:before{content:url("../img/moin-news.png");margin:0 .2em;vertical-align:middle}
--- a/MoinMoin/themes/foobar/static/css/stylus/userstyles.css	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/themes/foobar/static/css/stylus/userstyles.css	Sun May 11 13:39:38 2014 -0700
@@ -13,3 +13,10 @@
 .right { text-align: right; }
 .justify { text-align: justify; }
 .monospaced { font-family: monospace; }
+/* position image or object - the left, center, right rules above are either consistent with or harmless to the rules below */
+.moin-item-wrapper.left { float: left; }
+.moin-item-wrapper.center { width: 100%; text-align: center; }
+.moin-item-wrapper.right { float: right; }
+.moin-item-wrapper.middle { vertical-align: middle; }
+.moin-item-wrapper.top { vertical-align: top; }
+.moin-item-wrapper.bottom { vertical-align: bottom; }
--- a/MoinMoin/themes/modernized/static/css/common.css	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/themes/modernized/static/css/common.css	Sun May 11 13:39:38 2014 -0700
@@ -97,6 +97,13 @@
 .right { text-align: right; }
 .justify { text-align: justify; }
 .monospaced { font-family: monospace; }
+/* position image or object - the left, center, right rules above are either consistent with or harmless to the rules below */
+.moin-item-wrapper.left { float: left; }
+.moin-item-wrapper.center { width: 100%; text-align: center; }
+.moin-item-wrapper.right { float: right; }
+.moin-item-wrapper.middle { vertical-align: middle; }
+.moin-item-wrapper.top { vertical-align: top; }
+.moin-item-wrapper.bottom { vertical-align: bottom; }
 a.moin-www:before,a.moin-http:before,a.moin-https:before{content:url("../img/moin-www.png");margin:0 .2em;vertical-align:middle}
 a.moin-file:before,a.moin-ftp:before{content:url("../img/moin-ftp.png");margin:0 .2em;vertical-align:middle}
 a.moin-nntp:before,a.moin-news:before{content:url("../img/moin-news.png");margin:0 .2em;vertical-align:middle}
--- a/MoinMoin/themes/modernized/static/css/stylus/userstyles.css	Sun May 11 09:33:30 2014 -0700
+++ b/MoinMoin/themes/modernized/static/css/stylus/userstyles.css	Sun May 11 13:39:38 2014 -0700
@@ -13,3 +13,10 @@
 .right { text-align: right; }
 .justify { text-align: justify; }
 .monospaced { font-family: monospace; }
+/* position image or object - the left, center, right rules above are either consistent with or harmless to the rules below */
+.moin-item-wrapper.left { float: left; }
+.moin-item-wrapper.center { width: 100%; text-align: center; }
+.moin-item-wrapper.right { float: right; }
+.moin-item-wrapper.middle { vertical-align: middle; }
+.moin-item-wrapper.top { vertical-align: top; }
+.moin-item-wrapper.bottom { vertical-align: bottom; }