On 10/27/2011 9:27 AM, Franz wrote:
Hi,

when I edit some Wiki page or ticket in Trac like this:
= The [[span(Sandbox, style=background-color: yellow)]] =

the output is as following:
The [[span(...)]]

When trying
[[span(= The Sandbox =, style=background-color: yellow)]]
it outputs (with yellow background)
= The Sandbox =

It should however print the text "Sandbox" with yellow background
(which works with other wiki formats).

Is this behavior a (known) bug? Or was the span macro not intended for
headers?

It's a known limitation, yes. We currently have no way to express that we expect inline content ''only'' from a macro. So I think that we need to expand the API a bit, for example by adding an `is_inline` method on the macro providers. That way we could only expand the macros which give us the guarantee to produce inline content.

Ok, so here's a patch that implements this. It also illustrates how a macro can sometimes produce inline content, sometimes not (here the TicketQueryMacro, producing inline content for the count and compact format). In addition, if a macro wants to adapt its generated content to the current expected output, it could look at the type of the formatter. But in general it should be the other way round: the macro generates some content, and the formatter adapts to it.

-- Christian

--
You received this message because you are subscribed to the Google Groups "Trac 
Development" group.
To post to this group, send email to trac-dev@googlegroups.com.
To unsubscribe from this group, send email to 
trac-dev+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/trac-dev?hl=en.

>From 65014816db2a69bcf511b51c38d88aa7d9e5d155 Mon Sep 17 00:00:00 2001
From: Christian Boos <cb...@edgewall.org>
Date: Thu, 27 Oct 2011 23:07:00 +0200
Subject: [PATCH] wiki macros can declare that they produce inline content.

Added a new `is_inline(content)` method and based on this,
allow some actual content from a macro returning True to be
included in context where inline only content is expected
(OneLineFormatter, OutlineFormatter).

This is in particular the case for the span macro, for the html
macro if the toplevel element is an inline element, and for some
formats of the TicketQuery.
---
 trac/ticket/query.py           |    4 ++
 trac/wiki/api.py               |    6 ++
 trac/wiki/formatter.py         |  101 ++++++++++++++++++++++++++--------------
 trac/wiki/tests/wiki-tests.txt |    2 +-
 4 files changed, 77 insertions(+), 36 deletions(-)

diff --git a/trac/ticket/query.py b/trac/ticket/query.py
index f60a277..a1f9049 100644
--- a/trac/ticket/query.py
+++ b/trac/ticket/query.py
@@ -1422,3 +1422,7 @@ class TicketQueryMacro(WikiMacroBase):
                                         tag.dd(ticket['summary']))
                                        for ticket in tickets],
                                       class_='wiki compact'))
+
+    def is_inline(self, content):
+        query_string, kwargs, format = self.parse_args(content)
+        return format in ('count', 'compact')
diff --git a/trac/wiki/api.py b/trac/wiki/api.py
index 85b52c3..0496541 100644
--- a/trac/wiki/api.py
+++ b/trac/wiki/api.py
@@ -107,6 +107,12 @@ class IWikiMacroProvider(Interface):
     def render_macro(req, name, content):
         """Return the HTML output of the macro :deprecated:"""
 
+    def is_inline(content):
+        """Return `True` if the content generated is an inline XHTML element.
+
+        .. versionadded :: 0.13
+        """
+
     def expand_macro(formatter, name, content, args=None):
         """Called by the formatter when rendering the parsed wiki text.
 
diff --git a/trac/wiki/formatter.py b/trac/wiki/formatter.py
index 4218550..1816893 100644
--- a/trac/wiki/formatter.py
+++ b/trac/wiki/formatter.py
@@ -134,6 +134,7 @@ class WikiProcessor(object):
         self.error = None
         self.macro_provider = None
 
+        # FIXME: move these tables outside of __init__
         builtin_processors = {'html': self._html_processor,
                               'htmlcomment': self._htmlcomment_processor,
                               'default': self._default_processor,
@@ -148,6 +149,11 @@ class WikiProcessor(object):
                               'table': self._table_processor,
                               }
 
+        self.inline_check = {'html': self._html_is_inline,
+                                'htmlcomment': True, 'comment': True,
+                                'span': True, 'Span': True,
+                                }.get(name)
+
         self._sanitizer = TracHTMLSanitizer(formatter.wiki.safe_schemes)
         
         self.processor = builtin_processors.get(name)
@@ -161,6 +167,8 @@ class WikiProcessor(object):
                         else:
                             self.processor = self._legacy_macro_processor
                         self.macro_provider = macro_provider
+                        self.inline_check = getattr(macro_provider, 
'is_inline',
+                                                    False)
                         break
         if not self.processor:
             # Find a matching mimeview renderer
@@ -179,6 +187,20 @@ class WikiProcessor(object):
             self.processor = self._default_processor
             self.error = "No macro or processor named '%s' found" % name
 
+    # inline checks
+
+    def _html_is_inline(self, text):
+        if text:
+            tag = text[1:].lstrip()
+            idx = tag.find(' ')
+            if idx > -1:
+                tag = tag[:idx]
+            return tag.lower() in ('a', 'span', 'bdo', 'img',
+                                   'big', 'small', 'font',
+                                   'tt', 'i', 'b', 'u', 's', 'strike',
+                                   'em', 'strong', 'dfn', 'code', 'q',
+                                   'samp', 'kbd', 'var', 'cite', 'abbr',
+                                   'acronym', 'sub', 'sup')
     # builtin processors
 
     def _comment_processor(self, text):
@@ -332,36 +354,42 @@ class WikiProcessor(object):
                                   self.error)
         else:
             text = self.processor(text)
-        if not text:
-            return ''
-        if in_paragraph:
-            content_for_span = None
-            interrupt_paragraph = False
-            if isinstance(text, Element):
-                tagname = text.tag.lower()
-                if tagname == 'div':
-                    class_ = text.attrib.get('class', '')
-                    if class_ and 'code' in class_:
-                        content_for_span = text.children
-                    else:
-                        interrupt_paragraph = True
-                elif tagname == 'table':
+        return text or ''
+
+    def is_inline(self, text):
+        if callable(self.inline_check):
+            return self.inline_check(text)
+        else:
+            return self.inline_check
+
+    def ensure_inline(self, text):
+        content_for_span = None
+        interrupt_paragraph = False
+        if isinstance(text, Element):
+            tagname = text.tag.lower()
+            if tagname == 'div':
+                class_ = text.attrib.get('class', '')
+                if class_ and 'code' in class_:
+                    content_for_span = text.children
+                else:
                     interrupt_paragraph = True
-            else:
-                # FIXME: do something smarter for Streams
-                text = _markup_to_unicode(text)
-                match = re.match(self._code_block_re, text)
-                if match:
-                    if match.group(1) and 'code' in match.group(1):
-                        content_for_span = match.group(2)
-                    else:
-                        interrupt_paragraph = True
-                elif re.match(self._block_elem_re, text):
+            elif tagname == 'table':
+                interrupt_paragraph = True
+        else:
+            # FIXME: do something smarter for Streams
+            text = _markup_to_unicode(text)
+            match = re.match(self._code_block_re, text)
+            if match:
+                if match.group(1) and 'code' in match.group(1):
+                    content_for_span = match.group(2)
+                else:
                     interrupt_paragraph = True
-            if content_for_span:
-                text = tag.span(class_='code-block')(*content_for_span)
-            elif interrupt_paragraph:
-                text = "</p>%s<p>" % _markup_to_unicode(text)
+            elif re.match(self._block_elem_re, text):
+                interrupt_paragraph = True
+        if content_for_span:
+            text = tag.span(class_='code-block')(*content_for_span)
+        elif interrupt_paragraph:
+            text = "</p>%s<p>" % _markup_to_unicode(text)
         return text
 
 
@@ -722,7 +750,7 @@ class Formatter(object):
         fullmatch = WikiParser._creolelink_re.match(macro_or_link)
         return self._lhref_formatter(match, fullmatch)
     
-    def _macro_formatter(self, match, fullmatch, macro=None):
+    def _macro_formatter(self, match, fullmatch, macro, only_inline=False):
         name = fullmatch.group('macroname')
         if name.lower() == 'br':
             return '<br />'
@@ -731,7 +759,7 @@ class Formatter(object):
         else:
             args = fullmatch.group('macroargs')
         try:
-            return macro.process(args, in_paragraph=True)
+            return macro.ensure_inline(macro.process(args))
         except Exception, e:
             self.env.log.error('Macro %s(%s) failed: %s' % 
                     (name, args, exception_to_unicode(e, traceback=True)))
@@ -1310,14 +1338,14 @@ class OneLinerFormatter(Formatter):
     def _linebreak_wc_formatter(self, match, fullmatch):
         return ' '
 
-    def _macro_formatter(self, match, fullmatch, macro=None):
+    def _macro_formatter(self, match, fullmatch, macro):
         name = fullmatch.group('macroname')
         if name.lower() == 'br':
             return ' '
-        elif name == 'comment':
-            return ''
+        args = fullmatch.group('macroargs')
+        if macro.is_inline(args):
+            return Formatter._macro_formatter(self, match, fullmatch, macro)
         else:
-            args = fullmatch.group('macroargs')
             return '[[%s%s]]' % (name, '(...)' if args else '')
 
     def format(self, text, out, shorten=False):
@@ -1370,7 +1398,10 @@ class OutlineFormatter(Formatter):
     
     # Avoid the possible side-effects of rendering WikiProcessors
 
-    def _macro_formatter(self, match, fullmatch, macro=None):
+    def _macro_formatter(self, match, fullmatch, macro):
+        args = fullmatch.group('macroargs')
+        if macro.is_inline(args):
+            return Formatter._macro_formatter(self, match, fullmatch, macro)
         return ''
 
     def handle_code_block(self, line, startmatch=None):
diff --git a/trac/wiki/tests/wiki-tests.txt b/trac/wiki/tests/wiki-tests.txt
index d8cb761..6d927a2 100644
--- a/trac/wiki/tests/wiki-tests.txt
+++ b/trac/wiki/tests/wiki-tests.txt
@@ -936,7 +936,7 @@ with an additional class
 Done.
 </p>
 ------------------------------
-And now it's [[span(...)]]. Really.
+And now it's <span class="important"><strong>TIME FOR BED!</strong></span>. 
Really.
  […]
 and
  […]
-- 
1.7.7.msysgit.0

Reply via email to