Note
+This is the first line inside the box
+Did you know?
+Another line here.
+Note
` + title = klass.capitalize() + elif title == '': + # an explicit blank title should not be rendered + # e.g.: `!!! warning ""` will *not* render `p` with a title + title = None + return klass, title + + +def makeExtension(configs={}): + return AdmonitionExtension(configs=configs) diff --git a/markdown_python/extensions/attr_list.py b/markdown_python/extensions/attr_list.py old mode 100644 new mode 100755 index 60287fe..c98aa85 --- a/markdown_python/extensions/attr_list.py +++ b/markdown_python/extensions/attr_list.py @@ -10,17 +10,20 @@ Contact: markdown@freewisdom.org -License: BSD (see ../../LICENSE for details) +License: BSD (see ../LICENSE.md for details) Dependencies: * [Python 2.4+](http://python.org) -* [Markdown 2.1+](http://www.freewisdom.org/projects/python-markdown/) +* [Markdown 2.1+](http://packages.python.org/Markdown/) """ -import markdown +from __future__ import absolute_import +from __future__ import unicode_literals +from . import Extension +from ..treeprocessors import Treeprocessor +from ..util import isBlockLevel import re -from markdown.util import isBlockLevel try: Scanner = re.Scanner @@ -41,9 +44,9 @@ def _handle_key_value(s, t): def _handle_word(s, t): if t.startswith('.'): - return u'.', t[1:] + return '.', t[1:] if t.startswith('#'): - return u'id', t[1:] + return 'id', t[1:] return t, t _scanner = Scanner([ @@ -61,16 +64,19 @@ def get_attrs(str): def isheader(elem): return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] -class AttrListTreeprocessor(markdown.treeprocessors.Treeprocessor): +class AttrListTreeprocessor(Treeprocessor): BASE_RE = r'\{\:?([^\}]*)\}' HEADER_RE = re.compile(r'[ ]*%s[ ]*$' % BASE_RE) BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE) INLINE_RE = re.compile(r'^%s' % BASE_RE) + NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d' + r'\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef' + r'\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd' + r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') def run(self, doc): for elem in doc.getiterator(): - #import pdb; pdb.set_trace() if isBlockLevel(elem.tag): # Block level: check for attrs on last line of text RE = self.BLOCK_RE @@ -114,14 +120,20 @@ def assign_attrs(self, elem, attrs): else: elem.set('class', v) else: - # assing attr k with v - elem.set(k, v) + # assign attr k with v + elem.set(self.sanitize_name(k), v) + def sanitize_name(self, name): + """ + Sanitize name as 'an XML Name, minus the ":"'. + See http://www.w3.org/TR/REC-xml-names/#NT-NCName + """ + return self.NAME_RE.sub('_', name) -class AttrListExtension(markdown.extensions.Extension): + +class AttrListExtension(Extension): def extendMarkdown(self, md, md_globals): - # insert after 'inline' treeprocessor - md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>inline') + md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify') def makeExtension(configs={}): diff --git a/markdown_python/extensions/codehilite.py b/markdown_python/extensions/codehilite.py old mode 100644 new mode 100755 index 5df820f..72b40fd --- a/markdown_python/extensions/codehilite.py +++ b/markdown_python/extensions/codehilite.py @@ -1,5 +1,3 @@ -#!/usr/bin/python - """ CodeHilite Extension for Python-Markdown ======================================== @@ -8,19 +6,23 @@ Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). -Project website:.*?)(?<=\n)(?P=fence)[ ]*$',
+ r'(?P^(?:~{3,}|`{3,}))[ ]*(\{?\.?(?P[a-zA-Z0-9_+-]*)\}?)?[ ]*\n(?P.*?)(?<=\n)(?P=fence)[ ]*$',
re.MULTILINE|re.DOTALL
)
CODE_WRAP = '%s
'
LANG_TAG = ' class="%s"'
-class FencedCodeExtension(markdown.Extension):
+class FencedCodeExtension(Extension):
def extendMarkdown(self, md, md_globals):
""" Add FencedBlockPreprocessor to the Markdown instance. """
@@ -95,13 +96,13 @@ def extendMarkdown(self, md, md_globals):
md.preprocessors.add('fenced_code_block',
FencedBlockPreprocessor(md),
- "_begin")
+ ">normalize_whitespace")
-class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor):
+class FencedBlockPreprocessor(Preprocessor):
def __init__(self, md):
- markdown.preprocessors.Preprocessor.__init__(self, md)
+ super(FencedBlockPreprocessor, self).__init__(md)
self.checked_for_codehilite = False
self.codehilite_conf = {}
@@ -130,7 +131,7 @@ def run(self, lines):
# is enabled, so we call it to highlite the code
if self.codehilite_conf:
highliter = CodeHilite(m.group('code'),
- linenos=self.codehilite_conf['force_linenos'][0],
+ linenums=self.codehilite_conf['linenums'][0],
guess_lang=self.codehilite_conf['guess_lang'][0],
css_class=self.codehilite_conf['css_class'][0],
style=self.codehilite_conf['pygments_style'][0],
@@ -158,8 +159,3 @@ def _escape(self, txt):
def makeExtension(configs=None):
return FencedCodeExtension(configs=configs)
-
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/markdown_python/extensions/footnotes.py b/markdown_python/extensions/footnotes.py
old mode 100644
new mode 100755
index 3d83807..65ed597
--- a/markdown_python/extensions/footnotes.py
+++ b/markdown_python/extensions/footnotes.py
@@ -23,16 +23,23 @@
"""
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..preprocessors import Preprocessor
+from ..inlinepatterns import Pattern
+from ..treeprocessors import Treeprocessor
+from ..postprocessors import Postprocessor
+from ..util import etree, text_type
+from ..odict import OrderedDict
import re
-import markdown
-from markdown.util import etree
FN_BACKLINK_TEXT = "zz1337820767766393qq"
NBSP_PLACEHOLDER = "qq3936677670287331zz"
DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
TABBED_RE = re.compile(r'((\t)|( ))(.*)')
-class FootnoteExtension(markdown.Extension):
+class FootnoteExtension(Extension):
""" Footnote Extension. """
def __init__ (self, configs):
@@ -61,6 +68,10 @@ def extendMarkdown(self, md, md_globals):
""" Add pieces to Markdown. """
md.registerExtension(self)
self.parser = md.parser
+ self.md = md
+ self.sep = ':'
+ if self.md.output_format in ['html5', 'xhtml5']:
+ self.sep = '-'
# Insert a preprocessor before ReferencePreprocessor
md.preprocessors.add("footnote", FootnotePreprocessor(self),
"
+Project website:
Contact: markdown@freewisdom.org
License: BSD (see ../docs/LICENSE for details)
Dependencies:
* [Python 2.3+](http://python.org)
-* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+* [Markdown 2.0+](http://packages.python.org/Markdown/)
"""
-import markdown
-from markdown.util import etree
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..treeprocessors import Treeprocessor
import re
-from string import ascii_lowercase, digits, punctuation
import logging
import unicodedata
@@ -97,13 +96,13 @@ def slugify(value, separator):
def unique(id, ids):
""" Ensure id is unique in set of ids. Append '_1', '_2'... if not """
- while id in ids:
+ while id in ids or not id:
m = IDCOUNT_RE.match(id)
if m:
id = '%s_%d'% (m.group(1), int(m.group(2))+1)
else:
id = '%s_%d'% (id, 1)
- ids.append(id)
+ ids.add(id)
return id
@@ -122,7 +121,7 @@ def itertext(elem):
yield e.tail
-class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor):
+class HeaderIdTreeprocessor(Treeprocessor):
""" Assign IDs to headers. """
IDs = set()
@@ -135,7 +134,7 @@ def run(self, doc):
if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
if force_id:
if "id" in elem.attrib:
- id = elem.id
+ id = elem.get('id')
else:
id = slugify(''.join(itertext(elem)), sep)
elem.set('id', unique(id, self.IDs))
@@ -151,9 +150,9 @@ def _get_meta(self):
level = int(self.config['level']) - 1
force = self._str2bool(self.config['forceid'])
if hasattr(self.md, 'Meta'):
- if self.md.Meta.has_key('header_level'):
+ if 'header_level' in self.md.Meta:
level = int(self.md.Meta['header_level'][0]) - 1
- if self.md.Meta.has_key('header_forceid'):
+ if 'header_forceid' in self.md.Meta:
force = self._str2bool(self.md.Meta['header_forceid'][0])
return level, force
@@ -167,7 +166,7 @@ def _str2bool(self, s, default=False):
return default
-class HeaderIdExtension (markdown.Extension):
+class HeaderIdExtension(Extension):
def __init__(self, configs):
# set defaults
self.config = {
@@ -185,17 +184,16 @@ def extendMarkdown(self, md, md_globals):
self.processor = HeaderIdTreeprocessor()
self.processor.md = md
self.processor.config = self.getConfigs()
- # Replace existing hasheader in place.
- md.treeprocessors.add('headerid', self.processor, '>inline')
+ if 'attr_list' in md.treeprocessors.keys():
+ # insert after attr_list treeprocessor
+ md.treeprocessors.add('headerid', self.processor, '>attr_list')
+ else:
+ # insert after 'prettify' treeprocessor.
+ md.treeprocessors.add('headerid', self.processor, '>prettify')
def reset(self):
- self.processor.IDs = []
+ self.processor.IDs = set()
def makeExtension(configs=None):
return HeaderIdExtension(configs=configs)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
-
diff --git a/markdown_python/extensions/html_tidy.py b/markdown_python/extensions/html_tidy.py
deleted file mode 100644
index 6aee083..0000000
--- a/markdown_python/extensions/html_tidy.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-
-"""
-HTML Tidy Extension for Python-Markdown
-=======================================
-
-Runs [HTML Tidy][] on the output of Python-Markdown using the [uTidylib][]
-Python wrapper. Both libtidy and uTidylib must be installed on your system.
-
-Note than any Tidy [options][] can be passed in as extension configs. So,
-for example, to output HTML rather than XHTML, set ``output_xhtml=0``. To
-indent the output, set ``indent=auto`` and to have Tidy wrap the output in
-```` and ```` tags, set ``show_body_only=0``.
-
-[HTML Tidy]: http://tidy.sourceforge.net/
-[uTidylib]: http://utidylib.berlios.de/
-[options]: http://tidy.sourceforge.net/docs/quickref.html
-
-Copyright (c)2008 [Waylan Limberg](http://achinghead.com)
-
-License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
-
-Dependencies:
-* [Python2.3+](http://python.org)
-* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
-* [HTML Tidy](http://utidylib.berlios.de/)
-* [uTidylib](http://utidylib.berlios.de/)
-
-"""
-
-import markdown
-try:
- import tidy
-except ImportError:
- tidy = None
-
-class TidyExtension(markdown.Extension):
-
- def __init__(self, configs):
- # Set defaults to match typical markdown behavior.
- self.config = dict(output_xhtml=1,
- show_body_only=1,
- char_encoding='utf8'
- )
- # Merge in user defined configs overriding any present if nessecary.
- for c in configs:
- self.config[c[0]] = c[1]
-
- def extendMarkdown(self, md, md_globals):
- # Save options to markdown instance
- md.tidy_options = self.config
- # Add TidyProcessor to postprocessors
- if tidy:
- md.postprocessors['tidy'] = TidyProcessor(md)
-
-
-class TidyProcessor(markdown.postprocessors.Postprocessor):
-
- def run(self, text):
- # Pass text to Tidy. As Tidy does not accept unicode we need to encode
- # it and decode its return value.
- enc = self.markdown.tidy_options.get('char_encoding', 'utf8')
- return unicode(tidy.parseString(text.encode(enc),
- **self.markdown.tidy_options),
- encoding=enc)
-
-
-def makeExtension(configs=None):
- return TidyExtension(configs=configs)
diff --git a/markdown_python/extensions/meta.py b/markdown_python/extensions/meta.py
old mode 100644
new mode 100755
index a3407da..aaff436
--- a/markdown_python/extensions/meta.py
+++ b/markdown_python/extensions/meta.py
@@ -1,5 +1,3 @@
-#!usr/bin/python
-
"""
Meta Data Extension for Python-Markdown
=======================================
@@ -34,21 +32,24 @@
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
-Project website:
+Project website:
Contact: markdown@freewisdom.org
-License: BSD (see ../docs/LICENSE for details)
+License: BSD (see ../LICENSE.md for details)
"""
-import re
-import markdown
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..preprocessors import Preprocessor
+import re
# Global Vars
META_RE = re.compile(r'^[ ]{0,3}(?P[A-Za-z0-9_-]+):\s*(?P.*)')
META_MORE_RE = re.compile(r'^[ ]{4,}(?P.*)')
-class MetaExtension (markdown.Extension):
+class MetaExtension (Extension):
""" Meta-Data extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals):
@@ -57,7 +58,7 @@ def extendMarkdown(self, md, md_globals):
md.preprocessors.add("meta", MetaPreprocessor(md), "_begin")
-class MetaPreprocessor(markdown.preprocessors.Preprocessor):
+class MetaPreprocessor(Preprocessor):
""" Get Meta-Data. """
def run(self, lines):
@@ -90,7 +91,3 @@ def run(self, lines):
def makeExtension(configs={}):
return MetaExtension(configs=configs)
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/markdown_python/extensions/nl2br.py b/markdown_python/extensions/nl2br.py
old mode 100644
new mode 100755
index 5ba08a9..da4b339
--- a/markdown_python/extensions/nl2br.py
+++ b/markdown_python/extensions/nl2br.py
@@ -3,7 +3,7 @@
===============
A Python-Markdown extension to treat newlines as hard breaks; like
-StackOverflow and GitHub flavored Markdown do.
+GitHub-flavored Markdown does.
Usage:
@@ -16,21 +16,23 @@
Dependencies:
* [Python 2.4+](http://python.org)
-* [Markdown 2.1+](http://www.freewisdom.org/projects/python-markdown/)
+* [Markdown 2.1+](http://packages.python.org/Markdown/)
"""
-import markdown
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..inlinepatterns import SubstituteTagPattern
BR_RE = r'\n'
-class Nl2BrExtension(markdown.Extension):
+class Nl2BrExtension(Extension):
def extendMarkdown(self, md, md_globals):
- br_tag = markdown.inlinepatterns.SubstituteTagPattern(BR_RE, 'br')
+ br_tag = SubstituteTagPattern(BR_RE, 'br')
md.inlinePatterns.add('nl', br_tag, '_end')
def makeExtension(configs=None):
return Nl2BrExtension(configs)
-
diff --git a/markdown_python/extensions/rss.py b/markdown_python/extensions/rss.py
deleted file mode 100644
index ae43220..0000000
--- a/markdown_python/extensions/rss.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import markdown
-from markdown.util import etree
-
-DEFAULT_URL = "http://www.freewisdom.org/projects/python-markdown/"
-DEFAULT_CREATOR = "Yuri Takhteyev"
-DEFAULT_TITLE = "Markdown in Python"
-GENERATOR = "http://www.freewisdom.org/projects/python-markdown/markdown2rss"
-
-month_map = { "Jan" : "01",
- "Feb" : "02",
- "March" : "03",
- "April" : "04",
- "May" : "05",
- "June" : "06",
- "July" : "07",
- "August" : "08",
- "September" : "09",
- "October" : "10",
- "November" : "11",
- "December" : "12" }
-
-def get_time(heading):
-
- heading = heading.split("-")[0]
- heading = heading.strip().replace(",", " ").replace(".", " ")
-
- month, date, year = heading.split()
- month = month_map[month]
-
- return rdftime(" ".join((month, date, year, "12:00:00 AM")))
-
-def rdftime(time):
-
- time = time.replace(":", " ")
- time = time.replace("/", " ")
- time = time.split()
- return "%s-%s-%sT%s:%s:%s-08:00" % (time[0], time[1], time[2],
- time[3], time[4], time[5])
-
-
-def get_date(text):
- return "date"
-
-class RssExtension (markdown.Extension):
-
- def extendMarkdown(self, md, md_globals):
-
- self.config = { 'URL' : [DEFAULT_URL, "Main URL"],
- 'CREATOR' : [DEFAULT_CREATOR, "Feed creator's name"],
- 'TITLE' : [DEFAULT_TITLE, "Feed title"] }
-
- md.xml_mode = True
-
- # Insert a tree-processor that would actually add the title tag
- treeprocessor = RssTreeProcessor(md)
- treeprocessor.ext = self
- md.treeprocessors['rss'] = treeprocessor
- md.stripTopLevelTags = 0
- md.docType = '\n'
-
-class RssTreeProcessor(markdown.treeprocessors.Treeprocessor):
-
- def run (self, root):
-
- rss = etree.Element("rss")
- rss.set("version", "2.0")
-
- channel = etree.SubElement(rss, "channel")
-
- for tag, text in (("title", self.ext.getConfig("TITLE")),
- ("link", self.ext.getConfig("URL")),
- ("description", None)):
-
- element = etree.SubElement(channel, tag)
- element.text = text
-
- for child in root:
-
- if child.tag in ["h1", "h2", "h3", "h4", "h5"]:
-
- heading = child.text.strip()
- item = etree.SubElement(channel, "item")
- link = etree.SubElement(item, "link")
- link.text = self.ext.getConfig("URL")
- title = etree.SubElement(item, "title")
- title.text = heading
-
- guid = ''.join([x for x in heading if x.isalnum()])
- guidElem = etree.SubElement(item, "guid")
- guidElem.text = guid
- guidElem.set("isPermaLink", "false")
-
- elif child.tag in ["p"]:
- try:
- description = etree.SubElement(item, "description")
- except UnboundLocalError:
- # Item not defined - moving on
- pass
- else:
- if len(child):
- content = "\n".join([etree.tostring(node)
- for node in child])
- else:
- content = child.text
- pholder = self.markdown.htmlStash.store(
- "" % content)
- description.text = pholder
-
- return rss
-
-
-def makeExtension(configs):
-
- return RssExtension(configs)
diff --git a/markdown_python/extensions/sane_lists.py b/markdown_python/extensions/sane_lists.py
old mode 100644
new mode 100755
index dce04ea..23e9a7f
--- a/markdown_python/extensions/sane_lists.py
+++ b/markdown_python/extensions/sane_lists.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
"""
Sane List Extension for Python-Markdown
=======================================
@@ -19,23 +18,26 @@
"""
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..blockprocessors import OListProcessor, UListProcessor
import re
-import markdown
-class SaneOListProcessor(markdown.blockprocessors.OListProcessor):
+class SaneOListProcessor(OListProcessor):
CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.))[ ]+(.*)')
SIBLING_TAGS = ['ol']
-class SaneUListProcessor(markdown.blockprocessors.UListProcessor):
+class SaneUListProcessor(UListProcessor):
CHILD_RE = re.compile(r'^[ ]{0,3}(([*+-]))[ ]+(.*)')
SIBLING_TAGS = ['ul']
-class SaneListExtension(markdown.Extension):
+class SaneListExtension(Extension):
""" Add sane lists to Markdown. """
def extendMarkdown(self, md, md_globals):
diff --git a/markdown_python/extensions/smart_strong.py b/markdown_python/extensions/smart_strong.py
old mode 100644
new mode 100755
index 3ed3560..4818cf9
--- a/markdown_python/extensions/smart_strong.py
+++ b/markdown_python/extensions/smart_strong.py
@@ -22,14 +22,15 @@
'''
-import re
-import markdown
-from markdown.inlinepatterns import SimpleTagPattern
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import Extension
+from ..inlinepatterns import SimpleTagPattern
SMART_STRONG_RE = r'(?
+ [{'level': 1, 'children': [{'level': 2, 'children': []}]}]
+
+ A wrong list is also converted:
+ [{'level': 2}, {'level': 1}]
+ =>
+ [{'level': 2, 'children': []}, {'level': 1, 'children': []}]
+ """
+
+ def build_correct(remaining_list, prev_elements=[{'level': 1000}]):
+
+ if not remaining_list:
+ return [], []
+
+ current = remaining_list.pop(0)
+ if not 'children' in current.keys():
+ current['children'] = []
+
+ if not prev_elements:
+ # This happens for instance with [8, 1, 1], ie. when some
+ # header level is outside a scope. We treat it as a
+ # top-level
+ next_elements, children = build_correct(remaining_list, [current])
+ current['children'].append(children)
+ return [current] + next_elements, []
+
+ prev_element = prev_elements.pop()
+ children = []
+ next_elements = []
+ # Is current part of the child list or next list?
+ if current['level'] > prev_element['level']:
+ #print "%d is a child of %d" % (current['level'], prev_element['level'])
+ prev_elements.append(prev_element)
+ prev_elements.append(current)
+ prev_element['children'].append(current)
+ next_elements2, children2 = build_correct(remaining_list, prev_elements)
+ children += children2
+ next_elements += next_elements2
+ else:
+ #print "%d is ancestor of %d" % (current['level'], prev_element['level'])
+ if not prev_elements:
+ #print "No previous elements, so appending to the next set"
+ next_elements.append(current)
+ prev_elements = [current]
+ next_elements2, children2 = build_correct(remaining_list, prev_elements)
+ current['children'].extend(children2)
+ else:
+ #print "Previous elements, comparing to those first"
+ remaining_list.insert(0, current)
+ next_elements2, children2 = build_correct(remaining_list, prev_elements)
+ children.extend(children2)
+ next_elements += next_elements2
+
+ return next_elements, children
+
+ ordered_list, __ = build_correct(toc_list)
+ return ordered_list
+
+
+class TocTreeprocessor(Treeprocessor):
+
# Iterator wrapper to get parent and child all at once
def iterparent(self, root):
for parent in root.getiterator():
for child in parent:
yield parent, child
-
- def run(self, doc):
- marker_found = False
-
- div = etree.Element("div")
- div.attrib["class"] = "toc"
- last_li = None
-
+
+ def add_anchor(self, c, elem_id): #@ReservedAssignment
+ if self.use_anchors:
+ anchor = etree.Element("a")
+ anchor.text = c.text
+ anchor.attrib["href"] = "#" + elem_id
+ anchor.attrib["class"] = "toclink"
+ c.text = ""
+ for elem in c.getchildren():
+ anchor.append(elem)
+ c.remove(elem)
+ c.append(anchor)
+
+ def build_toc_etree(self, div, toc_list):
# Add title to the div
if self.config["title"]:
header = etree.SubElement(div, "span")
header.attrib["class"] = "toctitle"
header.text = self.config["title"]
- level = 0
- list_stack=[div]
- header_rgx = re.compile("[Hh][123456]")
+ def build_etree_ul(toc_list, parent):
+ ul = etree.SubElement(parent, "ul")
+ for item in toc_list:
+ # List item link, to be inserted into the toc div
+ li = etree.SubElement(ul, "li")
+ link = etree.SubElement(li, "a")
+ link.text = item.get('name', '')
+ link.attrib["href"] = '#' + item.get('id', '')
+ if item['children']:
+ build_etree_ul(item['children'], li)
+ return ul
+
+ return build_etree_ul(toc_list, div)
+
+ def run(self, doc):
+ div = etree.Element("div")
+ div.attrib["class"] = "toc"
+ header_rgx = re.compile("[Hh][123456]")
+
+ self.use_anchors = self.config["anchorlink"] in [1, '1', True, 'True', 'true']
+
# Get a list of id attributes
- used_ids = []
+ used_ids = set()
for c in doc.getiterator():
if "id" in c.attrib:
- used_ids.append(c.attrib["id"])
+ used_ids.add(c.attrib["id"])
+ toc_list = []
+ marker_found = False
for (p, c) in self.iterparent(doc):
text = ''.join(itertext(c)).strip()
if not text:
@@ -56,7 +148,6 @@ def run(self, doc):
# We do not allow the marker inside a header as that
# would causes an enless loop of placing a new TOC
# inside previously generated TOC.
-
if c.text and c.text.strip() == self.config["marker"] and \
not header_rgx.match(c.tag) and c.tag not in ['pre', 'code']:
for i in range(len(p)):
@@ -64,66 +155,41 @@ def run(self, doc):
p[i] = div
break
marker_found = True
-
+
if header_rgx.match(c.tag):
- try:
- tag_level = int(c.tag[-1])
-
- while tag_level < level:
- list_stack.pop()
- level -= 1
-
- if tag_level > level:
- newlist = etree.Element("ul")
- if last_li:
- last_li.append(newlist)
- else:
- list_stack[-1].append(newlist)
- list_stack.append(newlist)
- if level == 0:
- level = tag_level
- else:
- level += 1
-
- # Do not override pre-existing ids
- if not "id" in c.attrib:
- id = unique(self.config["slugify"](text, '-'), used_ids)
- c.attrib["id"] = id
- else:
- id = c.attrib["id"]
-
- # List item link, to be inserted into the toc div
- last_li = etree.Element("li")
- link = etree.SubElement(last_li, "a")
- link.text = text
- link.attrib["href"] = '#' + id
-
- if self.config["anchorlink"] in [1, '1', True, 'True', 'true']:
- anchor = etree.Element("a")
- anchor.text = c.text
- anchor.attrib["href"] = "#" + id
- anchor.attrib["class"] = "toclink"
- c.text = ""
- for elem in c.getchildren():
- anchor.append(elem)
- c.remove(elem)
- c.append(anchor)
-
- list_stack[-1].append(last_li)
- except IndexError:
- # We have bad ordering of headers. Just move on.
- pass
+
+ # Do not override pre-existing ids
+ if not "id" in c.attrib:
+ elem_id = unique(self.config["slugify"](text, '-'), used_ids)
+ c.attrib["id"] = elem_id
+ else:
+ elem_id = c.attrib["id"]
+
+ tag_level = int(c.tag[-1])
+
+ toc_list.append({'level': tag_level,
+ 'id': elem_id,
+ 'name': text})
+
+ self.add_anchor(c, elem_id)
+
+ toc_list_nested = order_toc_list(toc_list)
+ self.build_toc_etree(div, toc_list_nested)
+ prettify = self.markdown.treeprocessors.get('prettify')
+ if prettify: prettify.run(div)
if not marker_found:
- # searialize and attach to markdown instance.
- prettify = self.markdown.treeprocessors.get('prettify')
- if prettify: prettify.run(div)
+ # serialize and attach to markdown instance.
toc = self.markdown.serializer(div)
for pp in self.markdown.postprocessors.values():
toc = pp.run(toc)
self.markdown.toc = toc
-class TocExtension(markdown.Extension):
- def __init__(self, configs):
+
+class TocExtension(Extension):
+
+ TreeProcessorClass = TocTreeprocessor
+
+ def __init__(self, configs=[]):
self.config = { "marker" : ["[TOC]",
"Text to find and replace with Table of Contents -"
"Defaults to \"[TOC]\""],
@@ -141,14 +207,15 @@ def __init__(self, configs):
self.setConfig(key, value)
def extendMarkdown(self, md, md_globals):
- tocext = TocTreeprocessor(md)
+ tocext = self.TreeProcessorClass(md)
tocext.config = self.getConfigs()
- # Headerid ext is set to '>inline'. With this set to 'prettify'. With this set to '_end',
# it should always come after headerid ext (and honor ids assinged
# by the header id extension) if both are used. Same goes for
# attr_list extension. This must come last because we don't want
# to redefine ids after toc is created. But we do want toc prettified.
- md.treeprocessors.add("toc", tocext, "]*?|\!--.*?--)\>)' # <...>
ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # &
LINE_BREAK_RE = r' \n' # two spaces at end of line
-LINE_BREAK_2_RE = r' $' # two spaces at end of text
def dequote(string):
@@ -144,7 +143,7 @@ def attributeCallback(match):
-----------------------------------------------------------------------------
"""
-class Pattern:
+class Pattern(object):
"""Base class that inline patterns subclass. """
def __init__(self, pattern, markdown_instance=None):
@@ -191,10 +190,27 @@ def unescape(self, text):
stash = self.markdown.treeprocessors['inline'].stashed_nodes
except KeyError:
return text
+ def itertext(el):
+ ' Reimplement Element.itertext for older python versions '
+ tag = el.tag
+ if not isinstance(tag, util.string_type) and tag is not None:
+ return
+ if el.text:
+ yield el.text
+ for e in el:
+ for s in itertext(e):
+ yield s
+ if e.tail:
+ yield e.tail
def get_stash(m):
id = m.group(1)
if id in stash:
- return stash.get(id)
+ value = stash.get(id)
+ if isinstance(value, util.string_type):
+ return value
+ else:
+ # An etree Element - return text content only
+ return ''.join(itertext(value))
return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
@@ -235,7 +251,7 @@ def handleMatch(self, m):
class SubstituteTagPattern(SimpleTagPattern):
- """ Return a eLement of type `tag` with no children. """
+ """ Return an element of type `tag` with no children. """
def handleMatch (self, m):
return util.etree.Element(self.tag)
@@ -328,6 +344,7 @@ def sanitize_url(self, url):
`username:password@host:port`.
"""
+ url = url.replace(' ', '%20')
if not self.markdown.safeMode:
# Return immediately bipassing parsing.
return url
@@ -339,14 +356,18 @@ def sanitize_url(self, url):
return ''
locless_schemes = ['', 'mailto', 'news']
+ allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps']
+ if scheme not in allowed_schemes:
+ # Not a known (allowed) scheme. Not safe.
+ return ''
+
if netloc == '' and scheme not in locless_schemes:
- # This fails regardless of anything else.
- # Return immediately to save additional proccessing
+ # This should not happen. Treat as suspect.
return ''
for part in url[2:]:
if ":" in part:
- # Not a safe url
+ # A colon in "path", "parameters", "query" or "fragment" is suspect.
return ''
# Url passes all tests. Return url as-is.
@@ -372,7 +393,7 @@ def handleMatch(self, m):
else:
truealt = m.group(2)
- el.set('alt', truealt)
+ el.set('alt', self.unescape(truealt))
return el
class ReferencePattern(LinkPattern):
@@ -417,7 +438,11 @@ def makeTag(self, href, title, text):
el.set("src", self.sanitize_url(href))
if title:
el.set("title", title)
- el.set("alt", text)
+
+ if self.markdown.enable_attributes:
+ text = handleAttributes(text, el)
+
+ el.set("alt", self.unescape(text))
return el
@@ -441,7 +466,7 @@ def handleMatch(self, m):
def codepoint2name(code):
"""Return entity definition by code, or the code if not defined."""
- entity = htmlentitydefs.codepoint2name.get(code)
+ entity = entities.codepoint2name.get(code)
if entity:
return "%s%s;" % (util.AMP_SUBSTITUTE, entity)
else:
diff --git a/markdown_python/odict.py b/markdown_python/odict.py
old mode 100644
new mode 100755
index bf3ef07..8089ece
--- a/markdown_python/odict.py
+++ b/markdown_python/odict.py
@@ -1,3 +1,14 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+from . import util
+
+from copy import deepcopy
+
+def iteritems_compat(d):
+ """Return an iterator over the (key, value) pairs of a dictionary.
+ Copied from `six` module."""
+ return iter(getattr(d, _iteritems)())
+
class OrderedDict(dict):
"""
A dictionary that keeps its keys in the order in which they're inserted.
@@ -11,34 +22,44 @@ def __new__(cls, *args, **kwargs):
return instance
def __init__(self, data=None):
- if data is None:
- data = {}
- super(OrderedDict, self).__init__(data)
- if isinstance(data, dict):
- self.keyOrder = data.keys()
+ if data is None or isinstance(data, dict):
+ data = data or []
+ super(OrderedDict, self).__init__(data)
+ self.keyOrder = list(data) if data else []
else:
- self.keyOrder = []
+ super(OrderedDict, self).__init__()
+ super_set = super(OrderedDict, self).__setitem__
for key, value in data:
- if key not in self.keyOrder:
+ # Take the ordering from first key
+ if key not in self:
self.keyOrder.append(key)
+ # But override with last value in data (dict() does this)
+ super_set(key, value)
def __deepcopy__(self, memo):
- from copy import deepcopy
return self.__class__([(key, deepcopy(value, memo))
- for key, value in self.iteritems()])
+ for key, value in self.items()])
+
+ def __copy__(self):
+ # The Python's default copy implementation will alter the state
+ # of self. The reason for this seems complex but is likely related to
+ # subclassing dict.
+ return self.copy()
def __setitem__(self, key, value):
- super(OrderedDict, self).__setitem__(key, value)
- if key not in self.keyOrder:
+ if key not in self:
self.keyOrder.append(key)
+ super(OrderedDict, self).__setitem__(key, value)
def __delitem__(self, key):
super(OrderedDict, self).__delitem__(key)
self.keyOrder.remove(key)
def __iter__(self):
- for k in self.keyOrder:
- yield k
+ return iter(self.keyOrder)
+
+ def __reversed__(self):
+ return reversed(self.keyOrder)
def pop(self, k, *args):
result = super(OrderedDict, self).pop(k, *args)
@@ -54,41 +75,51 @@ def popitem(self):
self.keyOrder.remove(result[0])
return result
- def items(self):
- return zip(self.keyOrder, self.values())
+ def _iteritems(self):
+ for key in self.keyOrder:
+ yield key, self[key]
- def iteritems(self):
+ def _iterkeys(self):
for key in self.keyOrder:
- yield key, super(OrderedDict, self).__getitem__(key)
+ yield key
- def keys(self):
- return self.keyOrder[:]
+ def _itervalues(self):
+ for key in self.keyOrder:
+ yield self[key]
- def iterkeys(self):
- return iter(self.keyOrder)
+ if util.PY3:
+ items = _iteritems
+ keys = _iterkeys
+ values = _itervalues
+ else:
+ iteritems = _iteritems
+ iterkeys = _iterkeys
+ itervalues = _itervalues
- def values(self):
- return [super(OrderedDict, self).__getitem__(k) for k in self.keyOrder]
+ def items(self):
+ return [(k, self[k]) for k in self.keyOrder]
- def itervalues(self):
- for key in self.keyOrder:
- yield super(OrderedDict, self).__getitem__(key)
+ def keys(self):
+ return self.keyOrder[:]
+
+ def values(self):
+ return [self[k] for k in self.keyOrder]
def update(self, dict_):
- for k, v in dict_.items():
- self.__setitem__(k, v)
+ for k, v in iteritems_compat(dict_):
+ self[k] = v
def setdefault(self, key, default):
- if key not in self.keyOrder:
+ if key not in self:
self.keyOrder.append(key)
return super(OrderedDict, self).setdefault(key, default)
def value_for_index(self, index):
- """Return the value of the item at the given zero-based index."""
+ """Returns the value of the item at the given zero-based index."""
return self[self.keyOrder[index]]
def insert(self, index, key, value):
- """Insert the key, value pair before the item with the given index."""
+ """Inserts the key, value pair before the item with the given index."""
if key in self.keyOrder:
n = self.keyOrder.index(key)
del self.keyOrder[n]
@@ -98,18 +129,16 @@ def insert(self, index, key, value):
super(OrderedDict, self).__setitem__(key, value)
def copy(self):
- """Return a copy of this object."""
+ """Returns a copy of this object."""
# This way of initializing the copy means it works for subclasses, too.
- obj = self.__class__(self)
- obj.keyOrder = self.keyOrder[:]
- return obj
+ return self.__class__(self)
def __repr__(self):
"""
- Replace the normal dict.__repr__ with a version that returns the keys
- in their sorted order.
+ Replaces the normal dict.__repr__ with a version that returns the keys
+ in their Ordered order.
"""
- return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
+ return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in iteritems_compat(self)])
def clear(self):
super(OrderedDict, self).clear()
@@ -117,7 +146,10 @@ def clear(self):
def index(self, key):
""" Return the index of a given key. """
- return self.keyOrder.index(key)
+ try:
+ return self.keyOrder.index(key)
+ except ValueError:
+ raise ValueError("Element '%s' was not found in OrderedDict" % key)
def index_for_location(self, location):
""" Return index or None for a given location. """
@@ -150,13 +182,13 @@ def link(self, key, location):
""" Change location of an existing item. """
n = self.keyOrder.index(key)
del self.keyOrder[n]
- i = self.index_for_location(location)
try:
+ i = self.index_for_location(location)
if i is not None:
self.keyOrder.insert(i, key)
else:
self.keyOrder.append(key)
- except Error:
+ except Exception as e:
# restore to prevent data loss and reraise
self.keyOrder.insert(n, key)
- raise Error
+ raise e
diff --git a/markdown_python/postprocessors.py b/markdown_python/postprocessors.py
old mode 100644
new mode 100755
index 071791a..5f3f032
--- a/markdown_python/postprocessors.py
+++ b/markdown_python/postprocessors.py
@@ -8,9 +8,12 @@
"""
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import util
+from . import odict
import re
-import util
-import odict
+
def build_postprocessors(md_instance, **kwargs):
""" Build the default postprocessors for Markdown. """
@@ -95,7 +98,7 @@ class UnescapePostprocessor(Postprocessor):
RE = re.compile('%s(\d+)%s' % (util.STX, util.ETX))
def unescape(self, m):
- return unichr(int(m.group(1)))
+ return util.int2str(int(m.group(1)))
def run(self, text):
return self.RE.sub(self.unescape, text)
diff --git a/markdown_python/preprocessors.py b/markdown_python/preprocessors.py
old mode 100644
new mode 100755
index 55dd9ab..72b2ed6
--- a/markdown_python/preprocessors.py
+++ b/markdown_python/preprocessors.py
@@ -6,14 +6,17 @@
complicated.
"""
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import util
+from . import odict
import re
-import util
-import odict
def build_preprocessors(md_instance, **kwargs):
""" Build the default set of preprocessors used by Markdown. """
preprocessors = odict.OrderedDict()
+ preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance)
if md_instance.safeMode != 'escape':
preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
preprocessors["reference"] = ReferencePreprocessor(md_instance)
@@ -41,6 +44,18 @@ def run(self, lines):
pass
+class NormalizeWhitespace(Preprocessor):
+ """ Normalize whitespace for consistant parsing. """
+
+ def run(self, lines):
+ source = '\n'.join(lines)
+ source = source.replace(util.STX, "").replace(util.ETX, "")
+ source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n"
+ source = source.expandtabs(self.markdown.tab_length)
+ source = re.sub(r'(?<=\n) +\n', '\n', source)
+ return source.split('\n')
+
+
class HtmlBlockPreprocessor(Preprocessor):
"""Remove html blocks from the text and store them for later retrieval."""
@@ -127,7 +142,7 @@ def _is_oneliner(self, tag):
def run(self, lines):
text = "\n".join(lines)
new_blocks = []
- text = text.split("\n\n")
+ text = text.rsplit("\n\n")
items = []
left_tag = ''
right_tag = ''
@@ -257,25 +272,26 @@ def run(self, lines):
class ReferencePreprocessor(Preprocessor):
""" Remove reference definitions from text and store for later use. """
- RE = re.compile(r'^(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)$', re.DOTALL)
+ TITLE = r'[ ]*(\"(.*)\"|\'(.*)\'|\((.*)\))[ ]*'
+ RE = re.compile(r'^[ ]{0,3}\[([^\]]*)\]:\s*([^ ]*)[ ]*(%s)?$' % TITLE, re.DOTALL)
+ TITLE_RE = re.compile(r'^%s$' % TITLE)
def run (self, lines):
new_text = [];
- for line in lines:
+ while lines:
+ line = lines.pop(0)
m = self.RE.match(line)
if m:
- id = m.group(2).strip().lower()
- link = m.group(3).lstrip('<').rstrip('>')
- t = m.group(4).strip() # potential title
+ id = m.group(1).strip().lower()
+ link = m.group(2).lstrip('<').rstrip('>')
+ t = m.group(5) or m.group(6) or m.group(7)
if not t:
- self.markdown.references[id] = (link, t)
- elif (len(t) >= 2
- and (t[0] == t[-1] == "\""
- or t[0] == t[-1] == "\'"
- or (t[0] == "(" and t[-1] == ")") ) ):
- self.markdown.references[id] = (link, t[1:-1])
- else:
- new_text.append(line)
+ # Check next line for title
+ tm = self.TITLE_RE.match(lines[0])
+ if tm:
+ lines.pop(0)
+ t = tm.group(2) or tm.group(3) or tm.group(4)
+ self.markdown.references[id] = (link, t)
else:
new_text.append(line)
diff --git a/markdown_python/serializers.py b/markdown_python/serializers.py
old mode 100644
new mode 100755
index 22a83d4..b19d61c
--- a/markdown_python/serializers.py
+++ b/markdown_python/serializers.py
@@ -37,7 +37,9 @@
# --------------------------------------------------------------------
-import util
+from __future__ import absolute_import
+from __future__ import unicode_literals
+from . import util
ElementTree = util.etree.ElementTree
QName = util.etree.QName
if hasattr(util.etree, 'test_comment'):
@@ -251,7 +253,7 @@ def add_qname(qname):
tag = elem.tag
if isinstance(tag, QName) and tag.text not in qnames:
add_qname(tag.text)
- elif isinstance(tag, basestring):
+ elif isinstance(tag, util.string_type):
if tag not in qnames:
add_qname(tag)
elif tag is not None and tag is not Comment and tag is not PI:
diff --git a/markdown_python/treeprocessors.py b/markdown_python/treeprocessors.py
old mode 100644
new mode 100755
index 3340554..e6d3dc9
--- a/markdown_python/treeprocessors.py
+++ b/markdown_python/treeprocessors.py
@@ -1,7 +1,8 @@
-import re
-import inlinepatterns
-import util
-import odict
+from __future__ import unicode_literals
+from __future__ import absolute_import
+from . import util
+from . import odict
+from . import inlinepatterns
def build_treeprocessors(md_instance, **kwargs):
@@ -15,17 +16,11 @@ def build_treeprocessors(md_instance, **kwargs):
def isString(s):
""" Check if it's string """
if not isinstance(s, util.AtomicString):
- return isinstance(s, basestring)
+ return isinstance(s, util.string_type)
return False
-class Processor:
- def __init__(self, markdown_instance=None):
- if markdown_instance:
- self.markdown = markdown_instance
-
-
-class Treeprocessor(Processor):
+class Treeprocessor(util.Processor):
"""
Treeprocessors are run on the ElementTree object before serialization.
@@ -304,25 +299,26 @@ def run(self, tree):
if child.getchildren():
stack.append(child)
- if self.markdown.enable_attributes:
- for element, lst in insertQueue:
- if element.text:
+ for element, lst in insertQueue:
+ if self.markdown.enable_attributes:
+ if element.text and isString(element.text):
element.text = \
inlinepatterns.handleAttributes(element.text,
element)
- i = 0
- for newChild in lst:
+ i = 0
+ for newChild in lst:
+ if self.markdown.enable_attributes:
# Processing attributes
- if newChild.tail:
+ if newChild.tail and isString(newChild.tail):
newChild.tail = \
inlinepatterns.handleAttributes(newChild.tail,
element)
- if newChild.text:
+ if newChild.text and isString(newChild.text):
newChild.text = \
inlinepatterns.handleAttributes(newChild.text,
newChild)
- element.insert(i, newChild)
- i += 1
+ element.insert(i, newChild)
+ i += 1
return tree
@@ -357,3 +353,8 @@ def run(self, root):
br.tail = '\n'
else:
br.tail = '\n%s' % br.tail
+ # Clean up extra empty lines at end of code blocks.
+ pres = root.getiterator('pre')
+ for pre in pres:
+ if len(pre) and pre[0].tag == 'code':
+ pre[0].text = pre[0].text.rstrip() + '\n'
diff --git a/markdown_python/util.py b/markdown_python/util.py
old mode 100644
new mode 100755
index 998211e..1036197
--- a/markdown_python/util.py
+++ b/markdown_python/util.py
@@ -1,14 +1,24 @@
# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
import re
-from logging import CRITICAL
-
-import etree_loader
+import sys
"""
-CONSTANTS
+Python 3 Stuff
=============================================================================
"""
+PY3 = sys.version_info[0] == 3
+
+if PY3:
+ string_type = str
+ text_type = str
+ int2str = chr
+else:
+ string_type = basestring
+ text_type = unicode
+ int2str = unichr
+
"""
Constants you might want to modify
@@ -17,13 +27,13 @@
BLOCK_LEVEL_ELEMENTS = re.compile("^(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul"
"|script|noscript|form|fieldset|iframe|math"
- "|ins|del|hr|hr/|style|li|dt|dd|thead|tbody"
+ "|hr|hr/|style|li|dt|dd|thead|tbody"
"|tr|th|td|section|footer|header|group|figure"
"|figcaption|aside|article|canvas|output"
- "|progress|video)$")
+ "|progress|video)$", re.IGNORECASE)
# Placeholders
-STX = u'\u0002' # Use STX ("Start of text") for start-of-placeholder
-ETX = u'\u0003' # Use ETX ("End of text") for end-of-placeholder
+STX = '\u0002' # Use STX ("Start of text") for start-of-placeholder
+ETX = '\u0003' # Use ETX ("End of text") for end-of-placeholder
INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:"
INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX
INLINE_PLACEHOLDER_RE = re.compile(INLINE_PLACEHOLDER % r'([0-9]{4})')
@@ -34,17 +44,29 @@
-----------------------------------------------------------------------------
"""
-RTL_BIDI_RANGES = ( (u'\u0590', u'\u07FF'),
+RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'),
# Hebrew (0590-05FF), Arabic (0600-06FF),
# Syriac (0700-074F), Arabic supplement (0750-077F),
# Thaana (0780-07BF), Nko (07C0-07FF).
- (u'\u2D30', u'\u2D7F'), # Tifinagh
+ ('\u2D30', '\u2D7F'), # Tifinagh
)
# Extensions should use "markdown.util.etree" instead of "etree" (or do `from
# markdown.util import etree`). Do not import it by yourself.
-etree = etree_loader.importETree()
+try: # Is the C implemenation of ElementTree available?
+ import xml.etree.cElementTree as etree
+ from xml.etree.ElementTree import Comment
+ # Serializers (including ours) test with non-c Comment
+ etree.test_comment = Comment
+ if etree.VERSION < "1.0.5":
+ raise RuntimeError("cElementTree version 1.0.5 or higher is required.")
+except (ImportError, RuntimeError):
+ # Use the Python implementation of ElementTree?
+ import xml.etree.ElementTree as etree
+ if etree.VERSION < "1.1":
+ raise RuntimeError("ElementTree version 1.1 or higher is required")
+
"""
AUXILIARY GLOBAL FUNCTIONS
@@ -54,7 +76,7 @@
def isBlockLevel(tag):
"""Check if the tag is a block level HTML tag."""
- if isinstance(tag, basestring):
+ if isinstance(tag, string_type):
return BLOCK_LEVEL_ELEMENTS.match(tag)
# Some ElementTree tags are not strings, so return False.
return False
@@ -64,18 +86,18 @@ def isBlockLevel(tag):
=============================================================================
"""
-class AtomicString(unicode):
+class AtomicString(text_type):
"""A string which should not be further processed."""
pass
-class Processor:
+class Processor(object):
def __init__(self, markdown_instance=None):
if markdown_instance:
self.markdown = markdown_instance
-class HtmlStash:
+class HtmlStash(object):
"""
This class is used for stashing HTML objects that we extract
in the beginning and replace with place-holders.