1.1 --- a/common.py Wed May 29 18:18:17 2013 +0200
1.2 +++ b/common.py Sat Jun 08 18:24:21 2013 +0200
1.3 @@ -35,4 +35,9 @@
1.4 "bq" : "{{{%s}}}",
1.5 }
1.6
1.7 +headings = blocktypes.keys(); headings.remove("bq")
1.8 +
1.9 +def quote_macro_argument(arg):
1.10 + return '"%s"' % arg.replace('"', '""')
1.11 +
1.12 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/tests/test_macros.txt Wed May 29 18:18:17 2013 +0200
2.2 +++ b/tests/test_macros.txt Sat Jun 08 18:24:21 2013 +0200
2.3 @@ -1,6 +1,8 @@
2.4 This is a test of {color:red}colours{color}.
2.5
2.6 -{code}sections featuring code{code}
2.7 +{code}sections featuring code{code}
2.8 +
2.9 +h2. {anchor:heading}Heading
2.10
2.11 {anchor:second}
2.12 This is the second paragraph.
3.1 --- a/tests/test_xml_macros.txt Wed May 29 18:18:17 2013 +0200
3.2 +++ b/tests/test_xml_macros.txt Sat Jun 08 18:24:21 2013 +0200
3.3 @@ -2,5 +2,7 @@
3.4
3.5 <ac:macro ac:name="code"><ac:plain-text-body>sections featuring code</ac:plain-text-body></ac:macro>
3.6
3.7 +<h2><ac:macro ac:name="anchor"><ac:default-parameter>heading</ac:default-parameter></ac:macro>Heading</h2>
3.8 +
3.9 <ac:macro ac:name="anchor"><ac:default-parameter>second</ac:default-parameter></ac:macro>
3.10 <p>This is the second paragraph.</p>
4.1 --- a/wikiparser.py Wed May 29 18:18:17 2013 +0200
4.2 +++ b/wikiparser.py Sat Jun 08 18:24:21 2013 +0200
4.3 @@ -166,6 +166,7 @@
4.4 monospace_regexp_str = r"{{(?P<monotext>.*?)}}"
4.5 link_regexp_str = r"[[](?P<linktext>.*?)]"
4.6 image_regexp_str = r"!(?P<imagetext>\w.*?)!"
4.7 +macro_regexp_str = r"{(?P<macro>.*?):(?P<options>.*?)}"
4.8
4.9 # Word-dependent patterns.
4.10 # Here, the unbracketed markers must test for the absence of surrounding word
4.11 @@ -184,6 +185,8 @@
4.12 "|"
4.13 "(" + image_regexp_str + ")"
4.14 "|"
4.15 + "(" + macro_regexp_str + ")"
4.16 + "|"
4.17 "(" + italic_regexp_str + ")"
4.18 "|"
4.19 "(" + bold_regexp_str + ")"
4.20 @@ -290,9 +293,13 @@
4.21
4.22 preformatted_sectiontypes = (None, "noformat")
4.23
4.24 +macroargs = {
4.25 + "color" : "col",
4.26 + }
4.27 +
4.28 macrotypes = {
4.29 - "anchor" : "<<Anchor(%s)>>",
4.30 - "color" : "<<Color(%s)>>",
4.31 + "anchor" : "<<Anchor(%(args)s)>>",
4.32 + "color" : "<<Color2(%(content)s, %(args)s)>>",
4.33 }
4.34
4.35 class ConfluenceParser:
4.36 @@ -301,6 +308,7 @@
4.37
4.38 def __init__(self):
4.39 self.max_level = self.level = 0
4.40 + self.in_heading = False
4.41
4.42 def translate_marker(self, marker):
4.43
4.44 @@ -394,6 +402,16 @@
4.45 else:
4.46 return "{{%s%s|%s}}" % (prefix, parts[0], parts[1])
4.47
4.48 + elif match.group("macro"):
4.49 + macro_name = match.group("macro")
4.50 + if macrotypes.has_key(macro_name) and not self.forbids_macros():
4.51 + argname = macroargs.get(macro_name)
4.52 + return macrotypes[macro_name] % {
4.53 + "args" : quote_macro_argument((argname and ("%s=" % argname) or "") + match.group("options"))
4.54 + }
4.55 + else:
4.56 + return ""
4.57 +
4.58 elif match.group("italictext"):
4.59 return "''%s''" % self.translate_content(match.group("italictext"))
4.60
4.61 @@ -452,12 +470,15 @@
4.62
4.63 "Translate the block with the given 'blocktype' and 'blocktext'."
4.64
4.65 + if blocktype in headings:
4.66 + self.in_heading = True
4.67 +
4.68 parts = []
4.69
4.70 # Translate headings and blockquotes.
4.71
4.72 if blocktypes.has_key(blocktype):
4.73 - parts.append(blocktypes[blocktype] % blocktext)
4.74 + parts.append(blocktypes[blocktype] % self.translate_content(blocktext))
4.75
4.76 # Translate list items.
4.77
4.78 @@ -501,6 +522,9 @@
4.79 else:
4.80 parts.append(self.translate_content(blocktext))
4.81
4.82 + if blocktype in headings:
4.83 + self.in_heading = False
4.84 +
4.85 return "\n".join(parts)
4.86
4.87 def translate_section(self, sectiontype, options, text):
4.88 @@ -597,8 +621,12 @@
4.89
4.90 # Translations of macros (which can look like sections).
4.91
4.92 - elif macrotypes.has_key(sectiontype):
4.93 - parts.append(macrotypes[sectiontype] % self.translate_content(text, sectiontype))
4.94 + elif macrotypes.has_key(sectiontype) and not self.forbids_macros():
4.95 + argname = macroargs.get(sectiontype)
4.96 + parts.append(macrotypes[sectiontype] % {
4.97 + "content" : quote_macro_argument(self.translate_content(text, sectiontype)),
4.98 + "args" : quote_macro_argument((argname and ("%s=" % argname) or "") + options)
4.99 + })
4.100 preceded_by_block = False
4.101
4.102 # Unrecognised sections.
4.103 @@ -609,6 +637,9 @@
4.104
4.105 return "".join(parts)
4.106
4.107 + def forbids_macros(self):
4.108 + return self.in_heading
4.109 +
4.110 def parse(s, out):
4.111
4.112 "Parse the content in the string 's', writing a translation to 'out'."
5.1 --- a/xmlparser.py Wed May 29 18:18:17 2013 +0200
5.2 +++ b/xmlparser.py Sat Jun 08 18:24:21 2013 +0200
5.3 @@ -120,6 +120,17 @@
5.4 "tip" : "tip",
5.5 }
5.6
5.7 +macroargs = {
5.8 + # Confluence macro Confluence and MoinMoin macro arguments
5.9 + "color" : ("color", "col"),
5.10 + }
5.11 +
5.12 +macrotypes = {
5.13 + # Confluence macro MoinMoin syntax
5.14 + "anchor" : "<<Anchor(%(anchor)s)>>",
5.15 + "color" : "<<Color2(%(content)s, %(args)s)>>",
5.16 + }
5.17 +
5.18 normalise_regexp_str = r"\s+"
5.19 normalise_regexp = re.compile(normalise_regexp_str)
5.20
5.21 @@ -365,9 +376,19 @@
5.22 self.target = self.target_type = self.label = None
5.23
5.24 # Macros require various kinds of information.
5.25 + # Some macros affect the formatting of their contents, whereas other
5.26 + # simpler macros are handled here.
5.27
5.28 - elif name == "ac:macro":
5.29 - macro_name = self.attributes[-1]["ac:name"]
5.30 + elif name == "ac:macro" and not self.forbids_macros():
5.31 + conversion = macrotypes.get(self.macro)
5.32 + if conversion:
5.33 + parameters = {"content" : text}
5.34 + parameters.update(self.macro_parameters)
5.35 + argnames = macroargs.get(self.macro)
5.36 + if argnames:
5.37 + confargname, moinargname = argnames
5.38 + parameters["args"] = quote_macro_argument("%s=%s" % (moinargname, self.macro_parameters[confargname]))
5.39 + text = conversion % parameters
5.40
5.41 # Handle the common cases for parameterised and unparameterised
5.42 # substitutions.
5.43 @@ -449,6 +470,9 @@
5.44 def is_preformatted(self):
5.45 return reduce(operator.or_, [self.states[tag] for tag in preformatted_tags], False)
5.46
5.47 + def forbids_macros(self):
5.48 + return reduce(operator.or_, [(tag in headings or tag == "a") for tag in self.elements], False)
5.49 +
5.50 # Whitespace normalisation.
5.51
5.52 def get_replacement(self, name):