Upgrade markdown to 2.4.1

This commit is contained in:
Matthew Jones
2014-08-06 15:33:24 -04:00
parent 07b538e5c5
commit 7cb892a869
31 changed files with 5682 additions and 5688 deletions

View File

@@ -32,7 +32,7 @@ importlib==1.0.3 (importlib/*, needed for Python 2.6 support)
iso8601==0.1.10 (iso8601/*) iso8601==0.1.10 (iso8601/*)
keyring==4.0 (keyring/*, excluded bin/keyring) keyring==4.0 (keyring/*, excluded bin/keyring)
kombu==3.0.21 (kombu/*) kombu==3.0.21 (kombu/*)
Markdown==2.4 (markdown/*, excluded bin/markdown_py) Markdown==2.4.1 (markdown/*, excluded bin/markdown_py)
mock==1.0.1 (mock.py) mock==1.0.1 (mock.py)
ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support) ordereddict==1.1 (ordereddict.py, needed for Python 2.6 support)
os-diskconfig-python-novaclient-ext==0.1.2 (os_diskconfig_python_novaclient_ext/*) os-diskconfig-python-novaclient-ext==0.1.2 (os_diskconfig_python_novaclient_ext/*)

View File

@@ -1,442 +1,442 @@
""" """
Python Markdown Python Markdown
=============== ===============
Python Markdown converts Markdown to HTML and can be used as a library or Python Markdown converts Markdown to HTML and can be used as a library or
called from the command line. called from the command line.
## Basic usage as a module: ## Basic usage as a module:
import markdown import markdown
html = markdown.markdown(your_text_string) html = markdown.markdown(your_text_string)
See <http://packages.python.org/Markdown/> for more See <http://packages.python.org/Markdown/> for more
information and instructions on how to extend the functionality of information and instructions on how to extend the functionality of
Python Markdown. Read that before you try modifying this file. Python Markdown. Read that before you try modifying this file.
## Authors and License ## Authors and License
Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and
maintained by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan maintained by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan
Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com). Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com).
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
Copyright 2007-2013 The Python Markdown Project (v. 1.7 and later) Copyright 2007-2013 The Python Markdown Project (v. 1.7 and later)
Copyright 200? Django Software Foundation (OrderedDict implementation) Copyright 200? Django Software Foundation (OrderedDict implementation)
Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
Copyright 2004 Manfred Stienstra (the original version) Copyright 2004 Manfred Stienstra (the original version)
License: BSD (see LICENSE for details). License: BSD (see LICENSE for details).
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from .__version__ import version, version_info from .__version__ import version, version_info
import codecs import codecs
import sys import sys
import logging import logging
from . import util from . import util
from .preprocessors import build_preprocessors from .preprocessors import build_preprocessors
from .blockprocessors import build_block_parser from .blockprocessors import build_block_parser
from .treeprocessors import build_treeprocessors from .treeprocessors import build_treeprocessors
from .inlinepatterns import build_inlinepatterns from .inlinepatterns import build_inlinepatterns
from .postprocessors import build_postprocessors from .postprocessors import build_postprocessors
from .extensions import Extension from .extensions import Extension
from .serializers import to_html_string, to_xhtml_string from .serializers import to_html_string, to_xhtml_string
__all__ = ['Markdown', 'markdown', 'markdownFromFile'] __all__ = ['Markdown', 'markdown', 'markdownFromFile']
logger = logging.getLogger('MARKDOWN') logger = logging.getLogger('MARKDOWN')
class Markdown(object): class Markdown(object):
"""Convert Markdown to HTML.""" """Convert Markdown to HTML."""
doc_tag = "div" # Element used to wrap document - later removed doc_tag = "div" # Element used to wrap document - later removed
option_defaults = { option_defaults = {
'html_replacement_text' : '[HTML_REMOVED]', 'html_replacement_text' : '[HTML_REMOVED]',
'tab_length' : 4, 'tab_length' : 4,
'enable_attributes' : True, 'enable_attributes' : True,
'smart_emphasis' : True, 'smart_emphasis' : True,
'lazy_ol' : True, 'lazy_ol' : True,
} }
output_formats = { output_formats = {
'html' : to_html_string, 'html' : to_html_string,
'html4' : to_html_string, 'html4' : to_html_string,
'html5' : to_html_string, 'html5' : to_html_string,
'xhtml' : to_xhtml_string, 'xhtml' : to_xhtml_string,
'xhtml1': to_xhtml_string, 'xhtml1': to_xhtml_string,
'xhtml5': to_xhtml_string, 'xhtml5': to_xhtml_string,
} }
ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']', ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']',
'(', ')', '>', '#', '+', '-', '.', '!'] '(', ')', '>', '#', '+', '-', '.', '!']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
Creates a new Markdown instance. Creates a new Markdown instance.
Keyword arguments: Keyword arguments:
* extensions: A list of extensions. * extensions: A list of extensions.
If they are of type string, the module mdx_name.py will be loaded. If they are of type string, the module mdx_name.py will be loaded.
If they are a subclass of markdown.Extension, they will be used If they are a subclass of markdown.Extension, they will be used
as-is. as-is.
* extension_configs: Configuration settingis for extensions. * extension_configs: Configuration settingis for extensions.
* output_format: Format of output. Supported formats are: * output_format: Format of output. Supported formats are:
* "xhtml1": Outputs XHTML 1.x. Default. * "xhtml1": Outputs XHTML 1.x. Default.
* "xhtml5": Outputs XHTML style tags of HTML 5 * "xhtml5": Outputs XHTML style tags of HTML 5
* "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1). * "xhtml": Outputs latest supported version of XHTML (currently XHTML 1.1).
* "html4": Outputs HTML 4 * "html4": Outputs HTML 4
* "html5": Outputs HTML style tags of HTML 5 * "html5": Outputs HTML style tags of HTML 5
* "html": Outputs latest supported version of HTML (currently HTML 4). * "html": Outputs latest supported version of HTML (currently HTML 4).
Note that it is suggested that the more specific formats ("xhtml1" Note that it is suggested that the more specific formats ("xhtml1"
and "html4") be used as "xhtml" or "html" may change in the future and "html4") be used as "xhtml" or "html" may change in the future
if it makes sense at that time. if it makes sense at that time.
* safe_mode: Disallow raw html. One of "remove", "replace" or "escape". * safe_mode: Disallow raw html. One of "remove", "replace" or "escape".
* html_replacement_text: Text used when safe_mode is set to "replace". * html_replacement_text: Text used when safe_mode is set to "replace".
* tab_length: Length of tabs in the source. Default: 4 * tab_length: Length of tabs in the source. Default: 4
* enable_attributes: Enable the conversion of attributes. Default: True * enable_attributes: Enable the conversion of attributes. Default: True
* smart_emphasis: Treat `_connected_words_` intelligently Default: True * smart_emphasis: Treat `_connected_words_` intelligently Default: True
* lazy_ol: Ignore number of first item of ordered lists. Default: True * lazy_ol: Ignore number of first item of ordered lists. Default: True
""" """
# For backward compatibility, loop through old positional args # For backward compatibility, loop through old positional args
pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format'] pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format']
c = 0 c = 0
for arg in args: for arg in args:
if pos[c] not in kwargs: if pos[c] not in kwargs:
kwargs[pos[c]] = arg kwargs[pos[c]] = arg
c += 1 c += 1
if c == len(pos): if c == len(pos):
# ignore any additional args # ignore any additional args
break break
# Loop through kwargs and assign defaults # Loop through kwargs and assign defaults
for option, default in self.option_defaults.items(): for option, default in self.option_defaults.items():
setattr(self, option, kwargs.get(option, default)) setattr(self, option, kwargs.get(option, default))
self.safeMode = kwargs.get('safe_mode', False) self.safeMode = kwargs.get('safe_mode', False)
if self.safeMode and 'enable_attributes' not in kwargs: if self.safeMode and 'enable_attributes' not in kwargs:
# Disable attributes in safeMode when not explicitly set # Disable attributes in safeMode when not explicitly set
self.enable_attributes = False self.enable_attributes = False
self.registeredExtensions = [] self.registeredExtensions = []
self.docType = "" self.docType = ""
self.stripTopLevelTags = True self.stripTopLevelTags = True
self.build_parser() self.build_parser()
self.references = {} self.references = {}
self.htmlStash = util.HtmlStash() self.htmlStash = util.HtmlStash()
self.registerExtensions(extensions=kwargs.get('extensions', []), self.registerExtensions(extensions=kwargs.get('extensions', []),
configs=kwargs.get('extension_configs', {})) configs=kwargs.get('extension_configs', {}))
self.set_output_format(kwargs.get('output_format', 'xhtml1')) self.set_output_format(kwargs.get('output_format', 'xhtml1'))
self.reset() self.reset()
def build_parser(self): def build_parser(self):
""" Build the parser from the various parts. """ """ Build the parser from the various parts. """
self.preprocessors = build_preprocessors(self) self.preprocessors = build_preprocessors(self)
self.parser = build_block_parser(self) self.parser = build_block_parser(self)
self.inlinePatterns = build_inlinepatterns(self) self.inlinePatterns = build_inlinepatterns(self)
self.treeprocessors = build_treeprocessors(self) self.treeprocessors = build_treeprocessors(self)
self.postprocessors = build_postprocessors(self) self.postprocessors = build_postprocessors(self)
return self return self
def registerExtensions(self, extensions, configs): def registerExtensions(self, extensions, configs):
""" """
Register extensions with this instance of Markdown. Register extensions with this instance of Markdown.
Keyword arguments: Keyword arguments:
* extensions: A list of extensions, which can either * extensions: A list of extensions, which can either
be strings or objects. See the docstring on Markdown. be strings or objects. See the docstring on Markdown.
* configs: A dictionary mapping module names to config options. * configs: A dictionary mapping module names to config options.
""" """
for ext in extensions: for ext in extensions:
if isinstance(ext, util.string_type): if isinstance(ext, util.string_type):
ext = self.build_extension(ext, configs.get(ext, [])) ext = self.build_extension(ext, configs.get(ext, []))
if isinstance(ext, Extension): if isinstance(ext, Extension):
ext.extendMarkdown(self, globals()) ext.extendMarkdown(self, globals())
elif ext is not None: elif ext is not None:
raise TypeError( raise TypeError(
'Extension "%s.%s" must be of type: "markdown.Extension"' 'Extension "%s.%s" must be of type: "markdown.Extension"'
% (ext.__class__.__module__, ext.__class__.__name__)) % (ext.__class__.__module__, ext.__class__.__name__))
return self return self
def build_extension(self, ext_name, configs = []): def build_extension(self, ext_name, configs = []):
"""Build extension by name, then return the module. """Build extension by name, then return the module.
The extension name may contain arguments as part of the string in the The extension name may contain arguments as part of the string in the
following format: "extname(key1=value1,key2=value2)" following format: "extname(key1=value1,key2=value2)"
""" """
# Parse extensions config params (ignore the order) # Parse extensions config params (ignore the order)
configs = dict(configs) configs = dict(configs)
pos = ext_name.find("(") # find the first "(" pos = ext_name.find("(") # find the first "("
if pos > 0: if pos > 0:
ext_args = ext_name[pos+1:-1] ext_args = ext_name[pos+1:-1]
ext_name = ext_name[:pos] ext_name = ext_name[:pos]
pairs = [x.split("=") for x in ext_args.split(",")] pairs = [x.split("=") for x in ext_args.split(",")]
configs.update([(x.strip(), y.strip()) for (x, y) in pairs]) configs.update([(x.strip(), y.strip()) for (x, y) in pairs])
# Setup the module name # Setup the module name
module_name = ext_name module_name = ext_name
if '.' not in ext_name: if '.' not in ext_name:
module_name = '.'.join(['markdown.extensions', ext_name]) module_name = '.'.join(['markdown.extensions', ext_name])
# Try loading the extension first from one place, then another # Try loading the extension first from one place, then another
try: # New style (markdown.extensions.<extension>) try: # New style (markdown.extensions.<extension>)
module = __import__(module_name, {}, {}, [module_name.rpartition('.')[0]]) module = __import__(module_name, {}, {}, [module_name.rpartition('.')[0]])
except ImportError: except ImportError:
module_name_old_style = '_'.join(['mdx', ext_name]) module_name_old_style = '_'.join(['mdx', ext_name])
try: # Old style (mdx_<extension>) try: # Old style (mdx_<extension>)
module = __import__(module_name_old_style) module = __import__(module_name_old_style)
except ImportError as e: except ImportError as e:
message = "Failed loading extension '%s' from '%s' or '%s'" \ message = "Failed loading extension '%s' from '%s' or '%s'" \
% (ext_name, module_name, module_name_old_style) % (ext_name, module_name, module_name_old_style)
e.args = (message,) + e.args[1:] e.args = (message,) + e.args[1:]
raise raise
# If the module is loaded successfully, we expect it to define a # If the module is loaded successfully, we expect it to define a
# function called makeExtension() # function called makeExtension()
try: try:
return module.makeExtension(configs.items()) return module.makeExtension(configs.items())
except AttributeError as e: except AttributeError as e:
message = e.args[0] message = e.args[0]
message = "Failed to initiate extension " \ message = "Failed to initiate extension " \
"'%s': %s" % (ext_name, message) "'%s': %s" % (ext_name, message)
e.args = (message,) + e.args[1:] e.args = (message,) + e.args[1:]
raise raise
def registerExtension(self, extension): def registerExtension(self, extension):
""" This gets called by the extension """ """ This gets called by the extension """
self.registeredExtensions.append(extension) self.registeredExtensions.append(extension)
return self return self
def reset(self): def reset(self):
""" """
Resets all state variables so that we can start with a new text. Resets all state variables so that we can start with a new text.
""" """
self.htmlStash.reset() self.htmlStash.reset()
self.references.clear() self.references.clear()
for extension in self.registeredExtensions: for extension in self.registeredExtensions:
if hasattr(extension, 'reset'): if hasattr(extension, 'reset'):
extension.reset() extension.reset()
return self return self
def set_output_format(self, format): def set_output_format(self, format):
""" Set the output format for the class instance. """ """ Set the output format for the class instance. """
self.output_format = format.lower() self.output_format = format.lower()
try: try:
self.serializer = self.output_formats[self.output_format] self.serializer = self.output_formats[self.output_format]
except KeyError as e: except KeyError as e:
valid_formats = list(self.output_formats.keys()) valid_formats = list(self.output_formats.keys())
valid_formats.sort() valid_formats.sort()
message = 'Invalid Output Format: "%s". Use one of %s.' \ message = 'Invalid Output Format: "%s". Use one of %s.' \
% (self.output_format, % (self.output_format,
'"' + '", "'.join(valid_formats) + '"') '"' + '", "'.join(valid_formats) + '"')
e.args = (message,) + e.args[1:] e.args = (message,) + e.args[1:]
raise raise
return self return self
def convert(self, source): def convert(self, source):
""" """
Convert markdown to serialized XHTML or HTML. Convert markdown to serialized XHTML or HTML.
Keyword arguments: Keyword arguments:
* source: Source text as a Unicode string. * source: Source text as a Unicode string.
Markdown processing takes place in five steps: Markdown processing takes place in five steps:
1. A bunch of "preprocessors" munge the input text. 1. A bunch of "preprocessors" munge the input text.
2. BlockParser() parses the high-level structural elements of the 2. BlockParser() parses the high-level structural elements of the
pre-processed text into an ElementTree. pre-processed text into an ElementTree.
3. A bunch of "treeprocessors" are run against the ElementTree. One 3. A bunch of "treeprocessors" are run against the ElementTree. One
such treeprocessor runs InlinePatterns against the ElementTree, such treeprocessor runs InlinePatterns against the ElementTree,
detecting inline markup. detecting inline markup.
4. Some post-processors are run against the text after the ElementTree 4. Some post-processors are run against the text after the ElementTree
has been serialized into text. has been serialized into text.
5. The output is written to a string. 5. The output is written to a string.
""" """
# Fixup the source text # Fixup the source text
if not source.strip(): if not source.strip():
return '' # a blank unicode string return '' # a blank unicode string
try: try:
source = util.text_type(source) source = util.text_type(source)
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
# Customise error message while maintaining original trackback # Customise error message while maintaining original trackback
e.reason += '. -- Note: Markdown only accepts unicode input!' e.reason += '. -- Note: Markdown only accepts unicode input!'
raise raise
# Split into lines and run the line preprocessors. # Split into lines and run the line preprocessors.
self.lines = source.split("\n") self.lines = source.split("\n")
for prep in self.preprocessors.values(): for prep in self.preprocessors.values():
self.lines = prep.run(self.lines) self.lines = prep.run(self.lines)
# Parse the high-level elements. # Parse the high-level elements.
root = self.parser.parseDocument(self.lines).getroot() root = self.parser.parseDocument(self.lines).getroot()
# Run the tree-processors # Run the tree-processors
for treeprocessor in self.treeprocessors.values(): for treeprocessor in self.treeprocessors.values():
newRoot = treeprocessor.run(root) newRoot = treeprocessor.run(root)
if newRoot is not None: if newRoot is not None:
root = newRoot root = newRoot
# Serialize _properly_. Strip top-level tags. # Serialize _properly_. Strip top-level tags.
output = self.serializer(root) output = self.serializer(root)
if self.stripTopLevelTags: if self.stripTopLevelTags:
try: try:
start = output.index('<%s>'%self.doc_tag)+len(self.doc_tag)+2 start = output.index('<%s>'%self.doc_tag)+len(self.doc_tag)+2
end = output.rindex('</%s>'%self.doc_tag) end = output.rindex('</%s>'%self.doc_tag)
output = output[start:end].strip() output = output[start:end].strip()
except ValueError: except ValueError:
if output.strip().endswith('<%s />'%self.doc_tag): if output.strip().endswith('<%s />'%self.doc_tag):
# We have an empty document # We have an empty document
output = '' output = ''
else: else:
# We have a serious problem # We have a serious problem
raise ValueError('Markdown failed to strip top-level tags. Document=%r' % output.strip()) raise ValueError('Markdown failed to strip top-level tags. Document=%r' % output.strip())
# Run the text post-processors # Run the text post-processors
for pp in self.postprocessors.values(): for pp in self.postprocessors.values():
output = pp.run(output) output = pp.run(output)
return output.strip() return output.strip()
def convertFile(self, input=None, output=None, encoding=None): def convertFile(self, input=None, output=None, encoding=None):
"""Converts a markdown file and returns the HTML as a unicode string. """Converts a markdown file and returns the HTML as a unicode string.
Decodes the file using the provided encoding (defaults to utf-8), Decodes the file using the provided encoding (defaults to utf-8),
passes the file content to markdown, and outputs the html to either passes the file content to markdown, and outputs the html to either
the provided stream or the file with provided name, using the same the provided stream or the file with provided name, using the same
encoding as the source file. The 'xmlcharrefreplace' error handler is encoding as the source file. The 'xmlcharrefreplace' error handler is
used when encoding the output. used when encoding the output.
**Note:** This is the only place that decoding and encoding of unicode **Note:** This is the only place that decoding and encoding of unicode
takes place in Python-Markdown. (All other code is unicode-in / takes place in Python-Markdown. (All other code is unicode-in /
unicode-out.) unicode-out.)
Keyword arguments: Keyword arguments:
* input: File object or path. Reads from stdin if `None`. * input: File object or path. Reads from stdin if `None`.
* output: File object or path. Writes to stdout if `None`. * output: File object or path. Writes to stdout if `None`.
* encoding: Encoding of input and output files. Defaults to utf-8. * encoding: Encoding of input and output files. Defaults to utf-8.
""" """
encoding = encoding or "utf-8" encoding = encoding or "utf-8"
# Read the source # Read the source
if input: if input:
if isinstance(input, util.string_type): if isinstance(input, util.string_type):
input_file = codecs.open(input, mode="r", encoding=encoding) input_file = codecs.open(input, mode="r", encoding=encoding)
else: else:
input_file = codecs.getreader(encoding)(input) input_file = codecs.getreader(encoding)(input)
text = input_file.read() text = input_file.read()
input_file.close() input_file.close()
else: else:
text = sys.stdin.read() text = sys.stdin.read()
if not isinstance(text, util.text_type): if not isinstance(text, util.text_type):
text = text.decode(encoding) text = text.decode(encoding)
text = text.lstrip('\ufeff') # remove the byte-order mark text = text.lstrip('\ufeff') # remove the byte-order mark
# Convert # Convert
html = self.convert(text) html = self.convert(text)
# Write to file or stdout # Write to file or stdout
if output: if output:
if isinstance(output, util.string_type): if isinstance(output, util.string_type):
output_file = codecs.open(output, "w", output_file = codecs.open(output, "w",
encoding=encoding, encoding=encoding,
errors="xmlcharrefreplace") errors="xmlcharrefreplace")
output_file.write(html) output_file.write(html)
output_file.close() output_file.close()
else: else:
writer = codecs.getwriter(encoding) writer = codecs.getwriter(encoding)
output_file = writer(output, errors="xmlcharrefreplace") output_file = writer(output, errors="xmlcharrefreplace")
output_file.write(html) output_file.write(html)
# Don't close here. User may want to write more. # Don't close here. User may want to write more.
else: else:
# Encode manually and write bytes to stdout. # Encode manually and write bytes to stdout.
html = html.encode(encoding, "xmlcharrefreplace") html = html.encode(encoding, "xmlcharrefreplace")
try: try:
# Write bytes directly to buffer (Python 3). # Write bytes directly to buffer (Python 3).
sys.stdout.buffer.write(html) sys.stdout.buffer.write(html)
except AttributeError: except AttributeError:
# Probably Python 2, which works with bytes by default. # Probably Python 2, which works with bytes by default.
sys.stdout.write(html) sys.stdout.write(html)
return self return self
""" """
EXPORTED FUNCTIONS EXPORTED FUNCTIONS
============================================================================= =============================================================================
Those are the two functions we really mean to export: markdown() and Those are the two functions we really mean to export: markdown() and
markdownFromFile(). markdownFromFile().
""" """
def markdown(text, *args, **kwargs): def markdown(text, *args, **kwargs):
"""Convert a markdown string to HTML and return HTML as a unicode string. """Convert a markdown string to HTML and return HTML as a unicode string.
This is a shortcut function for `Markdown` class to cover the most This is a shortcut function for `Markdown` class to cover the most
basic use case. It initializes an instance of Markdown, loads the basic use case. It initializes an instance of Markdown, loads the
necessary extensions and runs the parser on the given text. necessary extensions and runs the parser on the given text.
Keyword arguments: Keyword arguments:
* text: Markdown formatted text as Unicode or ASCII string. * text: Markdown formatted text as Unicode or ASCII string.
* Any arguments accepted by the Markdown class. * Any arguments accepted by the Markdown class.
Returns: An HTML document as a string. Returns: An HTML document as a string.
""" """
md = Markdown(*args, **kwargs) md = Markdown(*args, **kwargs)
return md.convert(text) return md.convert(text)
def markdownFromFile(*args, **kwargs): def markdownFromFile(*args, **kwargs):
"""Read markdown code from a file and write it to a file or a stream. """Read markdown code from a file and write it to a file or a stream.
This is a shortcut function which initializes an instance of Markdown, This is a shortcut function which initializes an instance of Markdown,
and calls the convertFile method rather than convert. and calls the convertFile method rather than convert.
Keyword arguments: Keyword arguments:
* input: a file name or readable object. * input: a file name or readable object.
* output: a file name or writable object. * output: a file name or writable object.
* encoding: Encoding of input and output. * encoding: Encoding of input and output.
* Any arguments accepted by the Markdown class. * Any arguments accepted by the Markdown class.
""" """
# For backward compatibility loop through positional args # For backward compatibility loop through positional args
pos = ['input', 'output', 'extensions', 'encoding'] pos = ['input', 'output', 'extensions', 'encoding']
c = 0 c = 0
for arg in args: for arg in args:
if pos[c] not in kwargs: if pos[c] not in kwargs:
kwargs[pos[c]] = arg kwargs[pos[c]] = arg
c += 1 c += 1
if c == len(pos): if c == len(pos):
break break
md = Markdown(**kwargs) md = Markdown(**kwargs)
md.convertFile(kwargs.get('input', None), md.convertFile(kwargs.get('input', None),
kwargs.get('output', None), kwargs.get('output', None),
kwargs.get('encoding', None)) kwargs.get('encoding', None))

View File

@@ -1,87 +1,87 @@
""" """
COMMAND-LINE SPECIFIC STUFF COMMAND-LINE SPECIFIC STUFF
============================================================================= =============================================================================
""" """
import markdown import markdown
import sys import sys
import optparse import optparse
import logging import logging
from logging import DEBUG, INFO, CRITICAL from logging import DEBUG, INFO, CRITICAL
logger = logging.getLogger('MARKDOWN') logger = logging.getLogger('MARKDOWN')
def parse_options(): def parse_options():
""" """
Define and parse `optparse` options for command-line usage. Define and parse `optparse` options for command-line usage.
""" """
usage = """%prog [options] [INPUTFILE] usage = """%prog [options] [INPUTFILE]
(STDIN is assumed if no INPUTFILE is given)""" (STDIN is assumed if no INPUTFILE is given)"""
desc = "A Python implementation of John Gruber's Markdown. " \ desc = "A Python implementation of John Gruber's Markdown. " \
"http://packages.python.org/Markdown/" "http://packages.python.org/Markdown/"
ver = "%%prog %s" % markdown.version ver = "%%prog %s" % markdown.version
parser = optparse.OptionParser(usage=usage, description=desc, version=ver) parser = optparse.OptionParser(usage=usage, description=desc, version=ver)
parser.add_option("-f", "--file", dest="filename", default=None, parser.add_option("-f", "--file", dest="filename", default=None,
help="Write output to OUTPUT_FILE. Defaults to STDOUT.", help="Write output to OUTPUT_FILE. Defaults to STDOUT.",
metavar="OUTPUT_FILE") metavar="OUTPUT_FILE")
parser.add_option("-e", "--encoding", dest="encoding", parser.add_option("-e", "--encoding", dest="encoding",
help="Encoding for input and output files.",) help="Encoding for input and output files.",)
parser.add_option("-q", "--quiet", default = CRITICAL, parser.add_option("-q", "--quiet", default = CRITICAL,
action="store_const", const=CRITICAL+10, dest="verbose", action="store_const", const=CRITICAL+10, dest="verbose",
help="Suppress all warnings.") help="Suppress all warnings.")
parser.add_option("-v", "--verbose", parser.add_option("-v", "--verbose",
action="store_const", const=INFO, dest="verbose", action="store_const", const=INFO, dest="verbose",
help="Print all warnings.") help="Print all warnings.")
parser.add_option("-s", "--safe", dest="safe", default=False, parser.add_option("-s", "--safe", dest="safe", default=False,
metavar="SAFE_MODE", metavar="SAFE_MODE",
help="'replace', 'remove' or 'escape' HTML tags in input") help="'replace', 'remove' or 'escape' HTML tags in input")
parser.add_option("-o", "--output_format", dest="output_format", parser.add_option("-o", "--output_format", dest="output_format",
default='xhtml1', metavar="OUTPUT_FORMAT", default='xhtml1', metavar="OUTPUT_FORMAT",
help="'xhtml1' (default), 'html4' or 'html5'.") help="'xhtml1' (default), 'html4' or 'html5'.")
parser.add_option("--noisy", parser.add_option("--noisy",
action="store_const", const=DEBUG, dest="verbose", action="store_const", const=DEBUG, dest="verbose",
help="Print debug messages.") help="Print debug messages.")
parser.add_option("-x", "--extension", action="append", dest="extensions", parser.add_option("-x", "--extension", action="append", dest="extensions",
help = "Load extension EXTENSION.", metavar="EXTENSION") help = "Load extension EXTENSION.", metavar="EXTENSION")
parser.add_option("-n", "--no_lazy_ol", dest="lazy_ol", parser.add_option("-n", "--no_lazy_ol", dest="lazy_ol",
action='store_false', default=True, action='store_false', default=True,
help="Observe number of first item of ordered lists.") help="Observe number of first item of ordered lists.")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if len(args) == 0: if len(args) == 0:
input_file = None input_file = None
else: else:
input_file = args[0] input_file = args[0]
if not options.extensions: if not options.extensions:
options.extensions = [] options.extensions = []
return {'input': input_file, return {'input': input_file,
'output': options.filename, 'output': options.filename,
'safe_mode': options.safe, 'safe_mode': options.safe,
'extensions': options.extensions, 'extensions': options.extensions,
'encoding': options.encoding, 'encoding': options.encoding,
'output_format': options.output_format, 'output_format': options.output_format,
'lazy_ol': options.lazy_ol}, options.verbose 'lazy_ol': options.lazy_ol}, options.verbose
def run(): def run():
"""Run Markdown from the command line.""" """Run Markdown from the command line."""
# Parse options and adjust logging level if necessary # Parse options and adjust logging level if necessary
options, logging_level = parse_options() options, logging_level = parse_options()
if not options: sys.exit(2) if not options: sys.exit(2)
logger.setLevel(logging_level) logger.setLevel(logging_level)
logger.addHandler(logging.StreamHandler()) logger.addHandler(logging.StreamHandler())
# Run # Run
markdown.markdownFromFile(**options) markdown.markdownFromFile(**options)
if __name__ == '__main__': if __name__ == '__main__':
# Support running module as a commandline command. # Support running module as a commandline command.
# Python 2.5 & 2.6 do: `python -m markdown.__main__ [options] [args]`. # Python 2.5 & 2.6 do: `python -m markdown.__main__ [options] [args]`.
# Python 2.7 & 3.x do: `python -m markdown [options] [args]`. # Python 2.7 & 3.x do: `python -m markdown [options] [args]`.
run() run()

View File

@@ -1,28 +1,28 @@
# #
# markdown/__version__.py # markdown/__version__.py
# #
# version_info should conform to PEP 386 # version_info should conform to PEP 386
# (major, minor, micro, alpha/beta/rc/final, #) # (major, minor, micro, alpha/beta/rc/final, #)
# (1, 1, 2, 'alpha', 0) => "1.1.2.dev" # (1, 1, 2, 'alpha', 0) => "1.1.2.dev"
# (1, 2, 0, 'beta', 2) => "1.2b2" # (1, 2, 0, 'beta', 2) => "1.2b2"
version_info = (2, 4, 0, 'final', 0) version_info = (2, 4, 1, 'final', 0)
def _get_version(): def _get_version():
" Returns a PEP 386-compliant version number from version_info. " " Returns a PEP 386-compliant version number from version_info. "
assert len(version_info) == 5 assert len(version_info) == 5
assert version_info[3] in ('alpha', 'beta', 'rc', 'final') assert version_info[3] in ('alpha', 'beta', 'rc', 'final')
parts = 2 if version_info[2] == 0 else 3 parts = 2 if version_info[2] == 0 else 3
main = '.'.join(map(str, version_info[:parts])) main = '.'.join(map(str, version_info[:parts]))
sub = '' sub = ''
if version_info[3] == 'alpha' and version_info[4] == 0: if version_info[3] == 'alpha' and version_info[4] == 0:
# TODO: maybe append some sort of git info here?? # TODO: maybe append some sort of git info here??
sub = '.dev' sub = '.dev'
elif version_info[3] != 'final': elif version_info[3] != 'final':
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'} mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
sub = mapping[version_info[3]] + str(version_info[4]) sub = mapping[version_info[3]] + str(version_info[4])
return str(main + sub) return str(main + sub)
version = _get_version() version = _get_version()

View File

@@ -1,99 +1,99 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from . import util from . import util
from . import odict from . import odict
class State(list): class State(list):
""" Track the current and nested state of the parser. """ Track the current and nested state of the parser.
This utility class is used to track the state of the BlockParser and This utility class is used to track the state of the BlockParser and
support multiple levels if nesting. It's just a simple API wrapped around support multiple levels if nesting. It's just a simple API wrapped around
a list. Each time a state is set, that state is appended to the end of the a list. Each time a state is set, that state is appended to the end of the
list. Each time a state is reset, that state is removed from the end of list. Each time a state is reset, that state is removed from the end of
the list. the list.
Therefore, each time a state is set for a nested block, that state must be Therefore, each time a state is set for a nested block, that state must be
reset when we back out of that level of nesting or the state could be reset when we back out of that level of nesting or the state could be
corrupted. corrupted.
While all the methods of a list object are available, only the three While all the methods of a list object are available, only the three
defined below need be used. defined below need be used.
""" """
def set(self, state): def set(self, state):
""" Set a new state. """ """ Set a new state. """
self.append(state) self.append(state)
def reset(self): def reset(self):
""" Step back one step in nested state. """ """ Step back one step in nested state. """
self.pop() self.pop()
def isstate(self, state): def isstate(self, state):
""" Test that top (current) level is of given state. """ """ Test that top (current) level is of given state. """
if len(self): if len(self):
return self[-1] == state return self[-1] == state
else: else:
return False return False
class BlockParser: class BlockParser:
""" Parse Markdown blocks into an ElementTree object. """ Parse Markdown blocks into an ElementTree object.
A wrapper class that stitches the various BlockProcessors together, A wrapper class that stitches the various BlockProcessors together,
looping through them and creating an ElementTree object. looping through them and creating an ElementTree object.
""" """
def __init__(self, markdown): def __init__(self, markdown):
self.blockprocessors = odict.OrderedDict() self.blockprocessors = odict.OrderedDict()
self.state = State() self.state = State()
self.markdown = markdown self.markdown = markdown
def parseDocument(self, lines): def parseDocument(self, lines):
""" Parse a markdown document into an ElementTree. """ Parse a markdown document into an ElementTree.
Given a list of lines, an ElementTree object (not just a parent Element) Given a list of lines, an ElementTree object (not just a parent Element)
is created and the root element is passed to the parser as the parent. is created and the root element is passed to the parser as the parent.
The ElementTree object is returned. The ElementTree object is returned.
This should only be called on an entire document, not pieces. This should only be called on an entire document, not pieces.
""" """
# Create a ElementTree from the lines # Create a ElementTree from the lines
self.root = util.etree.Element(self.markdown.doc_tag) self.root = util.etree.Element(self.markdown.doc_tag)
self.parseChunk(self.root, '\n'.join(lines)) self.parseChunk(self.root, '\n'.join(lines))
return util.etree.ElementTree(self.root) return util.etree.ElementTree(self.root)
def parseChunk(self, parent, text): def parseChunk(self, parent, text):
""" Parse a chunk of markdown text and attach to given etree node. """ Parse a chunk of markdown text and attach to given etree node.
While the ``text`` argument is generally assumed to contain multiple While the ``text`` argument is generally assumed to contain multiple
blocks which will be split on blank lines, it could contain only one blocks which will be split on blank lines, it could contain only one
block. Generally, this method would be called by extensions when block. Generally, this method would be called by extensions when
block parsing is required. block parsing is required.
The ``parent`` etree Element passed in is altered in place. The ``parent`` etree Element passed in is altered in place.
Nothing is returned. Nothing is returned.
""" """
self.parseBlocks(parent, text.split('\n\n')) self.parseBlocks(parent, text.split('\n\n'))
def parseBlocks(self, parent, blocks): def parseBlocks(self, parent, blocks):
""" Process blocks of markdown text and attach to given etree node. """ Process blocks of markdown text and attach to given etree node.
Given a list of ``blocks``, each blockprocessor is stepped through Given a list of ``blocks``, each blockprocessor is stepped through
until there are no blocks left. While an extension could potentially until there are no blocks left. While an extension could potentially
call this method directly, it's generally expected to be used internally. call this method directly, it's generally expected to be used internally.
This is a public method as an extension may need to add/alter additional This is a public method as an extension may need to add/alter additional
BlockProcessors which call this method to recursively parse a nested BlockProcessors which call this method to recursively parse a nested
block. block.
""" """
while blocks: while blocks:
for processor in self.blockprocessors.values(): for processor in self.blockprocessors.values():
if processor.test(parent, blocks[0]): if processor.test(parent, blocks[0]):
if processor.run(parent, blocks) is not False: if processor.run(parent, blocks) is not False:
# run returns True or None # run returns True or None
break break

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +1,53 @@
""" """
Extensions Extensions
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
class Extension(object): class Extension(object):
""" Base class for extensions to subclass. """ """ Base class for extensions to subclass. """
def __init__(self, configs = {}): def __init__(self, configs = {}):
"""Create an instance of an Extention. """Create an instance of an Extention.
Keyword arguments: Keyword arguments:
* configs: A dict of configuration setting used by an Extension. * configs: A dict of configuration setting used by an Extension.
""" """
self.config = configs self.config = configs
def getConfig(self, key, default=''): def getConfig(self, key, default=''):
""" Return a setting for the given key or an empty string. """ """ Return a setting for the given key or an empty string. """
if key in self.config: if key in self.config:
return self.config[key][0] return self.config[key][0]
else: else:
return default return default
def getConfigs(self): def getConfigs(self):
""" Return all configs settings as a dict. """ """ Return all configs settings as a dict. """
return dict([(key, self.getConfig(key)) for key in self.config.keys()]) return dict([(key, self.getConfig(key)) for key in self.config.keys()])
def getConfigInfo(self): def getConfigInfo(self):
""" Return all config descriptions as a list of tuples. """ """ Return all config descriptions as a list of tuples. """
return [(key, self.config[key][1]) for key in self.config.keys()] return [(key, self.config[key][1]) for key in self.config.keys()]
def setConfig(self, key, value): def setConfig(self, key, value):
""" Set a config setting for `key` with the given `value`. """ """ Set a config setting for `key` with the given `value`. """
self.config[key][0] = value self.config[key][0] = value
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" """
Add the various proccesors and patterns to the Markdown Instance. Add the various proccesors and patterns to the Markdown Instance.
This method must be overriden by every extension. This method must be overriden by every extension.
Keyword arguments: Keyword arguments:
* md: The Markdown instance. * md: The Markdown instance.
* md_globals: Global variables in the markdown module namespace. * md_globals: Global variables in the markdown module namespace.
""" """
raise NotImplementedError('Extension "%s.%s" must define an "extendMarkdown"' \ raise NotImplementedError('Extension "%s.%s" must define an "extendMarkdown"' \
'method.' % (self.__class__.__module__, self.__class__.__name__)) 'method.' % (self.__class__.__module__, self.__class__.__name__))

View File

@@ -1,96 +1,96 @@
''' '''
Abbreviation Extension for Python-Markdown Abbreviation Extension for Python-Markdown
========================================== ==========================================
This extension adds abbreviation handling to Python-Markdown. This extension adds abbreviation handling to Python-Markdown.
Simple Usage: Simple Usage:
>>> import markdown >>> import markdown
>>> text = """ >>> text = """
... Some text with an ABBR and a REF. Ignore REFERENCE and ref. ... Some text with an ABBR and a REF. Ignore REFERENCE and ref.
... ...
... *[ABBR]: Abbreviation ... *[ABBR]: Abbreviation
... *[REF]: Abbreviation Reference ... *[REF]: Abbreviation Reference
... """ ... """
>>> print markdown.markdown(text, ['abbr']) >>> print markdown.markdown(text, ['abbr'])
<p>Some text with an <abbr title="Abbreviation">ABBR</abbr> and a <abbr title="Abbreviation Reference">REF</abbr>. Ignore REFERENCE and ref.</p> <p>Some text with an <abbr title="Abbreviation">ABBR</abbr> and a <abbr title="Abbreviation Reference">REF</abbr>. Ignore REFERENCE and ref.</p>
Copyright 2007-2008 Copyright 2007-2008
* [Waylan Limberg](http://achinghead.com/) * [Waylan Limberg](http://achinghead.com/)
* [Seemant Kulleen](http://www.kulleen.org/) * [Seemant Kulleen](http://www.kulleen.org/)
''' '''
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..preprocessors import Preprocessor from ..preprocessors import Preprocessor
from ..inlinepatterns import Pattern from ..inlinepatterns import Pattern
from ..util import etree, AtomicString from ..util import etree, AtomicString
import re import re
# Global Vars # Global Vars
ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)') ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)')
class AbbrExtension(Extension): class AbbrExtension(Extension):
""" Abbreviation Extension for Python-Markdown. """ """ Abbreviation Extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Insert AbbrPreprocessor before ReferencePreprocessor. """ """ Insert AbbrPreprocessor before ReferencePreprocessor. """
md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference') md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference')
class AbbrPreprocessor(Preprocessor): class AbbrPreprocessor(Preprocessor):
""" Abbreviation Preprocessor - parse text for abbr references. """ """ Abbreviation Preprocessor - parse text for abbr references. """
def run(self, lines): def run(self, lines):
''' '''
Find and remove all Abbreviation references from the text. Find and remove all Abbreviation references from the text.
Each reference is set as a new AbbrPattern in the markdown instance. Each reference is set as a new AbbrPattern in the markdown instance.
''' '''
new_text = [] new_text = []
for line in lines: for line in lines:
m = ABBR_REF_RE.match(line) m = ABBR_REF_RE.match(line)
if m: if m:
abbr = m.group('abbr').strip() abbr = m.group('abbr').strip()
title = m.group('title').strip() title = m.group('title').strip()
self.markdown.inlinePatterns['abbr-%s'%abbr] = \ self.markdown.inlinePatterns['abbr-%s'%abbr] = \
AbbrPattern(self._generate_pattern(abbr), title) AbbrPattern(self._generate_pattern(abbr), title)
else: else:
new_text.append(line) new_text.append(line)
return new_text return new_text
def _generate_pattern(self, text): def _generate_pattern(self, text):
''' '''
Given a string, returns an regex pattern to match that string. Given a string, returns an regex pattern to match that string.
'HTML' -> r'(?P<abbr>[H][T][M][L])' 'HTML' -> r'(?P<abbr>[H][T][M][L])'
Note: we force each char as a literal match (in brackets) as we don't Note: we force each char as a literal match (in brackets) as we don't
know what they will be beforehand. know what they will be beforehand.
''' '''
chars = list(text) chars = list(text)
for i in range(len(chars)): for i in range(len(chars)):
chars[i] = r'[%s]' % chars[i] chars[i] = r'[%s]' % chars[i]
return r'(?P<abbr>\b%s\b)' % (r''.join(chars)) return r'(?P<abbr>\b%s\b)' % (r''.join(chars))
class AbbrPattern(Pattern): class AbbrPattern(Pattern):
""" Abbreviation inline pattern. """ """ Abbreviation inline pattern. """
def __init__(self, pattern, title): def __init__(self, pattern, title):
super(AbbrPattern, self).__init__(pattern) super(AbbrPattern, self).__init__(pattern)
self.title = title self.title = title
def handleMatch(self, m): def handleMatch(self, m):
abbr = etree.Element('abbr') abbr = etree.Element('abbr')
abbr.text = AtomicString(m.group('abbr')) abbr.text = AtomicString(m.group('abbr'))
abbr.set('title', self.title) abbr.set('title', self.title)
return abbr return abbr
def makeExtension(configs=None): def makeExtension(configs=None):
return AbbrExtension(configs=configs) return AbbrExtension(configs=configs)

View File

@@ -1,118 +1,118 @@
""" """
Admonition extension for Python-Markdown Admonition extension for Python-Markdown
======================================== ========================================
Adds rST-style admonitions. Inspired by [rST][] feature with the same name. Adds rST-style admonitions. Inspired by [rST][] feature with the same name.
The syntax is (followed by an indented block with the contents): The syntax is (followed by an indented block with the contents):
!!! [type] [optional explicit title] !!! [type] [optional explicit title]
Where `type` is used as a CSS class name of the div. If not present, `title` Where `type` is used as a CSS class name of the div. If not present, `title`
defaults to the capitalized `type`, so "note" -> "Note". defaults to the capitalized `type`, so "note" -> "Note".
rST suggests the following `types`, but you're free to use whatever you want: rST suggests the following `types`, but you're free to use whatever you want:
attention, caution, danger, error, hint, important, note, tip, warning attention, caution, danger, error, hint, important, note, tip, warning
A simple example: A simple example:
!!! note !!! note
This is the first line inside the box. This is the first line inside the box.
Outputs: Outputs:
<div class="admonition note"> <div class="admonition note">
<p class="admonition-title">Note</p> <p class="admonition-title">Note</p>
<p>This is the first line inside the box</p> <p>This is the first line inside the box</p>
</div> </div>
You can also specify the title and CSS class of the admonition: You can also specify the title and CSS class of the admonition:
!!! custom "Did you know?" !!! custom "Did you know?"
Another line here. Another line here.
Outputs: Outputs:
<div class="admonition custom"> <div class="admonition custom">
<p class="admonition-title">Did you know?</p> <p class="admonition-title">Did you know?</p>
<p>Another line here.</p> <p>Another line here.</p>
</div> </div>
[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions [rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
By [Tiago Serafim](http://www.tiagoserafim.com/). By [Tiago Serafim](http://www.tiagoserafim.com/).
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..blockprocessors import BlockProcessor from ..blockprocessors import BlockProcessor
from ..util import etree from ..util import etree
import re import re
class AdmonitionExtension(Extension): class AdmonitionExtension(Extension):
""" Admonition extension for Python-Markdown. """ """ Admonition extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add Admonition to Markdown instance. """ """ Add Admonition to Markdown instance. """
md.registerExtension(self) md.registerExtension(self)
md.parser.blockprocessors.add('admonition', md.parser.blockprocessors.add('admonition',
AdmonitionProcessor(md.parser), AdmonitionProcessor(md.parser),
'_begin') '_begin')
class AdmonitionProcessor(BlockProcessor): class AdmonitionProcessor(BlockProcessor):
CLASSNAME = 'admonition' CLASSNAME = 'admonition'
CLASSNAME_TITLE = 'admonition-title' CLASSNAME_TITLE = 'admonition-title'
RE = re.compile(r'(?:^|\n)!!!\ ?([\w\-]+)(?:\ "(.*?)")?') RE = re.compile(r'(?:^|\n)!!!\ ?([\w\-]+)(?:\ "(.*?)")?')
def test(self, parent, block): def test(self, parent, block):
sibling = self.lastChild(parent) sibling = self.lastChild(parent)
return self.RE.search(block) or \ return self.RE.search(block) or \
(block.startswith(' ' * self.tab_length) and sibling and \ (block.startswith(' ' * self.tab_length) and sibling and \
sibling.get('class', '').find(self.CLASSNAME) != -1) sibling.get('class', '').find(self.CLASSNAME) != -1)
def run(self, parent, blocks): def run(self, parent, blocks):
sibling = self.lastChild(parent) sibling = self.lastChild(parent)
block = blocks.pop(0) block = blocks.pop(0)
m = self.RE.search(block) m = self.RE.search(block)
if m: if m:
block = block[m.end() + 1:] # removes the first line block = block[m.end() + 1:] # removes the first line
block, theRest = self.detab(block) block, theRest = self.detab(block)
if m: if m:
klass, title = self.get_class_and_title(m) klass, title = self.get_class_and_title(m)
div = etree.SubElement(parent, 'div') div = etree.SubElement(parent, 'div')
div.set('class', '%s %s' % (self.CLASSNAME, klass)) div.set('class', '%s %s' % (self.CLASSNAME, klass))
if title: if title:
p = etree.SubElement(div, 'p') p = etree.SubElement(div, 'p')
p.text = title p.text = title
p.set('class', self.CLASSNAME_TITLE) p.set('class', self.CLASSNAME_TITLE)
else: else:
div = sibling div = sibling
self.parser.parseChunk(div, block) self.parser.parseChunk(div, block)
if theRest: if theRest:
# This block contained unindented line(s) after the first indented # This block contained unindented line(s) after the first indented
# line. Insert these lines as the first block of the master blocks # line. Insert these lines as the first block of the master blocks
# list for future processing. # list for future processing.
blocks.insert(0, theRest) blocks.insert(0, theRest)
def get_class_and_title(self, match): def get_class_and_title(self, match):
klass, title = match.group(1).lower(), match.group(2) klass, title = match.group(1).lower(), match.group(2)
if title is None: if title is None:
# no title was provided, use the capitalized classname as title # no title was provided, use the capitalized classname as title
# e.g.: `!!! note` will render `<p class="admonition-title">Note</p>` # e.g.: `!!! note` will render `<p class="admonition-title">Note</p>`
title = klass.capitalize() title = klass.capitalize()
elif title == '': elif title == '':
# an explicit blank title should not be rendered # an explicit blank title should not be rendered
# e.g.: `!!! warning ""` will *not* render `p` with a title # e.g.: `!!! warning ""` will *not* render `p` with a title
title = None title = None
return klass, title return klass, title
def makeExtension(configs={}): def makeExtension(configs={}):
return AdmonitionExtension(configs=configs) return AdmonitionExtension(configs=configs)

View File

@@ -1,168 +1,168 @@
""" """
Attribute List Extension for Python-Markdown Attribute List Extension for Python-Markdown
============================================ ============================================
Adds attribute list syntax. Inspired by Adds attribute list syntax. Inspired by
[maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s [maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s
feature of the same name. feature of the same name.
Copyright 2011 [Waylan Limberg](http://achinghead.com/). Copyright 2011 [Waylan Limberg](http://achinghead.com/).
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
License: BSD (see ../LICENSE.md for details) License: BSD (see ../LICENSE.md for details)
Dependencies: Dependencies:
* [Python 2.4+](http://python.org) * [Python 2.4+](http://python.org)
* [Markdown 2.1+](http://packages.python.org/Markdown/) * [Markdown 2.1+](http://packages.python.org/Markdown/)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..treeprocessors import Treeprocessor from ..treeprocessors import Treeprocessor
from ..util import isBlockLevel from ..util import isBlockLevel
import re import re
try: try:
Scanner = re.Scanner Scanner = re.Scanner
except AttributeError: except AttributeError:
# must be on Python 2.4 # must be on Python 2.4
from sre import Scanner from sre import Scanner
def _handle_double_quote(s, t): def _handle_double_quote(s, t):
k, v = t.split('=') k, v = t.split('=')
return k, v.strip('"') return k, v.strip('"')
def _handle_single_quote(s, t): def _handle_single_quote(s, t):
k, v = t.split('=') k, v = t.split('=')
return k, v.strip("'") return k, v.strip("'")
def _handle_key_value(s, t): def _handle_key_value(s, t):
return t.split('=') return t.split('=')
def _handle_word(s, t): def _handle_word(s, t):
if t.startswith('.'): if t.startswith('.'):
return '.', t[1:] return '.', t[1:]
if t.startswith('#'): if t.startswith('#'):
return 'id', t[1:] return 'id', t[1:]
return t, t return t, t
_scanner = Scanner([ _scanner = Scanner([
(r'[^ ]+=".*?"', _handle_double_quote), (r'[^ ]+=".*?"', _handle_double_quote),
(r"[^ ]+='.*?'", _handle_single_quote), (r"[^ ]+='.*?'", _handle_single_quote),
(r'[^ ]+=[^ ]*', _handle_key_value), (r'[^ ]+=[^ ]*', _handle_key_value),
(r'[^ ]+', _handle_word), (r'[^ ]+', _handle_word),
(r' ', None) (r' ', None)
]) ])
def get_attrs(str): def get_attrs(str):
""" Parse attribute list and return a list of attribute tuples. """ """ Parse attribute list and return a list of attribute tuples. """
return _scanner.scan(str)[0] return _scanner.scan(str)[0]
def isheader(elem): def isheader(elem):
return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
class AttrListTreeprocessor(Treeprocessor): class AttrListTreeprocessor(Treeprocessor):
BASE_RE = r'\{\:?([^\}]*)\}' BASE_RE = r'\{\:?([^\}]*)\}'
HEADER_RE = re.compile(r'[ ]+%s[ ]*$' % BASE_RE) HEADER_RE = re.compile(r'[ ]+%s[ ]*$' % BASE_RE)
BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE) BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE)
INLINE_RE = re.compile(r'^%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' 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'\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef'
r'\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd' r'\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd'
r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+')
def run(self, doc): def run(self, doc):
for elem in doc.getiterator(): for elem in doc.getiterator():
if isBlockLevel(elem.tag): if isBlockLevel(elem.tag):
# Block level: check for attrs on last line of text # Block level: check for attrs on last line of text
RE = self.BLOCK_RE RE = self.BLOCK_RE
if isheader(elem) or elem.tag == 'dt': if isheader(elem) or elem.tag == 'dt':
# header or def-term: check for attrs at end of line # header or def-term: check for attrs at end of line
RE = self.HEADER_RE RE = self.HEADER_RE
if len(elem) and elem.tag == 'li': if len(elem) and elem.tag == 'li':
# special case list items. children may include a ul or ol. # special case list items. children may include a ul or ol.
pos = None pos = None
# find the ul or ol position # find the ul or ol position
for i, child in enumerate(elem): for i, child in enumerate(elem):
if child.tag in ['ul', 'ol']: if child.tag in ['ul', 'ol']:
pos = i pos = i
break break
if pos is None and elem[-1].tail: if pos is None and elem[-1].tail:
# use tail of last child. no ul or ol. # use tail of last child. no ul or ol.
m = RE.search(elem[-1].tail) m = RE.search(elem[-1].tail)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem[-1].tail = elem[-1].tail[:m.start()] elem[-1].tail = elem[-1].tail[:m.start()]
elif pos is not None and pos > 0 and elem[pos-1].tail: elif pos is not None and pos > 0 and elem[pos-1].tail:
# use tail of last child before ul or ol # use tail of last child before ul or ol
m = RE.search(elem[pos-1].tail) m = RE.search(elem[pos-1].tail)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem[pos-1].tail = elem[pos-1].tail[:m.start()] elem[pos-1].tail = elem[pos-1].tail[:m.start()]
elif elem.text: elif elem.text:
# use text. ul is first child. # use text. ul is first child.
m = RE.search(elem.text) m = RE.search(elem.text)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem.text = elem.text[:m.start()] elem.text = elem.text[:m.start()]
elif len(elem) and elem[-1].tail: elif len(elem) and elem[-1].tail:
# has children. Get from tail of last child # has children. Get from tail of last child
m = RE.search(elem[-1].tail) m = RE.search(elem[-1].tail)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem[-1].tail = elem[-1].tail[:m.start()] elem[-1].tail = elem[-1].tail[:m.start()]
if isheader(elem): if isheader(elem):
# clean up trailing #s # clean up trailing #s
elem[-1].tail = elem[-1].tail.rstrip('#').rstrip() elem[-1].tail = elem[-1].tail.rstrip('#').rstrip()
elif elem.text: elif elem.text:
# no children. Get from text. # no children. Get from text.
m = RE.search(elem.text) m = RE.search(elem.text)
if not m and elem.tag == 'td': if not m and elem.tag == 'td':
m = re.search(self.BASE_RE, elem.text) m = re.search(self.BASE_RE, elem.text)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem.text = elem.text[:m.start()] elem.text = elem.text[:m.start()]
if isheader(elem): if isheader(elem):
# clean up trailing #s # clean up trailing #s
elem.text = elem.text.rstrip('#').rstrip() elem.text = elem.text.rstrip('#').rstrip()
else: else:
# inline: check for attrs at start of tail # inline: check for attrs at start of tail
if elem.tail: if elem.tail:
m = self.INLINE_RE.match(elem.tail) m = self.INLINE_RE.match(elem.tail)
if m: if m:
self.assign_attrs(elem, m.group(1)) self.assign_attrs(elem, m.group(1))
elem.tail = elem.tail[m.end():] elem.tail = elem.tail[m.end():]
def assign_attrs(self, elem, attrs): def assign_attrs(self, elem, attrs):
""" Assign attrs to element. """ """ Assign attrs to element. """
for k, v in get_attrs(attrs): for k, v in get_attrs(attrs):
if k == '.': if k == '.':
# add to class # add to class
cls = elem.get('class') cls = elem.get('class')
if cls: if cls:
elem.set('class', '%s %s' % (cls, v)) elem.set('class', '%s %s' % (cls, v))
else: else:
elem.set('class', v) elem.set('class', v)
else: else:
# assign attr k with v # assign attr k with v
elem.set(self.sanitize_name(k), v) elem.set(self.sanitize_name(k), v)
def sanitize_name(self, name): def sanitize_name(self, name):
""" """
Sanitize name as 'an XML Name, minus the ":"'. Sanitize name as 'an XML Name, minus the ":"'.
See http://www.w3.org/TR/REC-xml-names/#NT-NCName See http://www.w3.org/TR/REC-xml-names/#NT-NCName
""" """
return self.NAME_RE.sub('_', name) return self.NAME_RE.sub('_', name)
class AttrListExtension(Extension): class AttrListExtension(Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify') md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify')
def makeExtension(configs={}): def makeExtension(configs={}):
return AttrListExtension(configs=configs) return AttrListExtension(configs=configs)

View File

@@ -1,268 +1,268 @@
""" """
CodeHilite Extension for Python-Markdown CodeHilite Extension for Python-Markdown
======================================== ========================================
Adds code/syntax highlighting to standard Python-Markdown code blocks. Adds code/syntax highlighting to standard Python-Markdown code blocks.
Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/). Copyright 2006-2008 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/code_hilite.html> Project website: <http://packages.python.org/Markdown/extensions/code_hilite.html>
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
License: BSD (see ../LICENSE.md for details) License: BSD (see ../LICENSE.md for details)
Dependencies: Dependencies:
* [Python 2.3+](http://python.org/) * [Python 2.3+](http://python.org/)
* [Markdown 2.0+](http://packages.python.org/Markdown/) * [Markdown 2.0+](http://packages.python.org/Markdown/)
* [Pygments](http://pygments.org/) * [Pygments](http://pygments.org/)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..treeprocessors import Treeprocessor from ..treeprocessors import Treeprocessor
import warnings import warnings
try: try:
from pygments import highlight from pygments import highlight
from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer from pygments.lexers import get_lexer_by_name, guess_lexer, TextLexer
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
pygments = True pygments = True
except ImportError: except ImportError:
pygments = False pygments = False
def parse_hl_lines(expr): def parse_hl_lines(expr):
"""Support our syntax for emphasizing certain lines of code. """Support our syntax for emphasizing certain lines of code.
expr should be like '1 2' to emphasize lines 1 and 2 of a code block. expr should be like '1 2' to emphasize lines 1 and 2 of a code block.
Returns a list of ints, the line numbers to emphasize. Returns a list of ints, the line numbers to emphasize.
""" """
if not expr: if not expr:
return [] return []
try: try:
return list(map(int, expr.split())) return list(map(int, expr.split()))
except ValueError: except ValueError:
return [] return []
# ------------------ The Main CodeHilite Class ---------------------- # ------------------ The Main CodeHilite Class ----------------------
class CodeHilite(object): class CodeHilite(object):
""" """
Determine language of source code, and pass it into the pygments hilighter. Determine language of source code, and pass it into the pygments hilighter.
Basic Usage: Basic Usage:
>>> code = CodeHilite(src = 'some text') >>> code = CodeHilite(src = 'some text')
>>> html = code.hilite() >>> html = code.hilite()
* src: Source string or any object with a .readline attribute. * src: Source string or any object with a .readline attribute.
* linenums: (Boolean) Set line numbering to 'on' (True), 'off' (False) or 'auto'(None). * linenums: (Boolean) Set line numbering to 'on' (True), 'off' (False) or 'auto'(None).
Set to 'auto' by default. Set to 'auto' by default.
* guess_lang: (Boolean) Turn language auto-detection 'on' or 'off' (on by default). * guess_lang: (Boolean) Turn language auto-detection 'on' or 'off' (on by default).
* css_class: Set class name of wrapper div ('codehilite' by default). * css_class: Set class name of wrapper div ('codehilite' by default).
* hl_lines: (List of integers) Lines to emphasize, 1-indexed. * hl_lines: (List of integers) Lines to emphasize, 1-indexed.
Low Level Usage: Low Level Usage:
>>> code = CodeHilite() >>> code = CodeHilite()
>>> code.src = 'some text' # String or anything with a .readline attr. >>> code.src = 'some text' # String or anything with a .readline attr.
>>> code.linenos = True # True or False; Turns line numbering on or of. >>> code.linenos = True # True or False; Turns line numbering on or of.
>>> html = code.hilite() >>> html = code.hilite()
""" """
def __init__(self, src=None, linenums=None, guess_lang=True, def __init__(self, src=None, linenums=None, guess_lang=True,
css_class="codehilite", lang=None, style='default', css_class="codehilite", lang=None, style='default',
noclasses=False, tab_length=4, hl_lines=None): noclasses=False, tab_length=4, hl_lines=None):
self.src = src self.src = src
self.lang = lang self.lang = lang
self.linenums = linenums self.linenums = linenums
self.guess_lang = guess_lang self.guess_lang = guess_lang
self.css_class = css_class self.css_class = css_class
self.style = style self.style = style
self.noclasses = noclasses self.noclasses = noclasses
self.tab_length = tab_length self.tab_length = tab_length
self.hl_lines = hl_lines or [] self.hl_lines = hl_lines or []
def hilite(self): def hilite(self):
""" """
Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with Pass code to the [Pygments](http://pygments.pocoo.org/) highliter with
optional line numbers. The output should then be styled with css to optional line numbers. The output should then be styled with css to
your liking. No styles are applied by default - only styling hooks your liking. No styles are applied by default - only styling hooks
(i.e.: <span class="k">). (i.e.: <span class="k">).
returns : A string of html. returns : A string of html.
""" """
self.src = self.src.strip('\n') self.src = self.src.strip('\n')
if self.lang is None: if self.lang is None:
self._parseHeader() self._parseHeader()
if pygments: if pygments:
try: try:
lexer = get_lexer_by_name(self.lang) lexer = get_lexer_by_name(self.lang)
except ValueError: except ValueError:
try: try:
if self.guess_lang: if self.guess_lang:
lexer = guess_lexer(self.src) lexer = guess_lexer(self.src)
else: else:
lexer = TextLexer() lexer = TextLexer()
except ValueError: except ValueError:
lexer = TextLexer() lexer = TextLexer()
formatter = HtmlFormatter(linenos=self.linenums, formatter = HtmlFormatter(linenos=self.linenums,
cssclass=self.css_class, cssclass=self.css_class,
style=self.style, style=self.style,
noclasses=self.noclasses, noclasses=self.noclasses,
hl_lines=self.hl_lines) hl_lines=self.hl_lines)
return highlight(self.src, lexer, formatter) return highlight(self.src, lexer, formatter)
else: else:
# just escape and build markup usable by JS highlighting libs # just escape and build markup usable by JS highlighting libs
txt = self.src.replace('&', '&amp;') txt = self.src.replace('&', '&amp;')
txt = txt.replace('<', '&lt;') txt = txt.replace('<', '&lt;')
txt = txt.replace('>', '&gt;') txt = txt.replace('>', '&gt;')
txt = txt.replace('"', '&quot;') txt = txt.replace('"', '&quot;')
classes = [] classes = []
if self.lang: if self.lang:
classes.append('language-%s' % self.lang) classes.append('language-%s' % self.lang)
if self.linenums: if self.linenums:
classes.append('linenums') classes.append('linenums')
class_str = '' class_str = ''
if classes: if classes:
class_str = ' class="%s"' % ' '.join(classes) class_str = ' class="%s"' % ' '.join(classes)
return '<pre class="%s"><code%s>%s</code></pre>\n'% \ return '<pre class="%s"><code%s>%s</code></pre>\n'% \
(self.css_class, class_str, txt) (self.css_class, class_str, txt)
def _parseHeader(self): def _parseHeader(self):
""" """
Determines language of a code block from shebang line and whether said Determines language of a code block from shebang line and whether said
line should be removed or left in place. If the sheband line contains a line should be removed or left in place. If the sheband line contains a
path (even a single /) then it is assumed to be a real shebang line and path (even a single /) then it is assumed to be a real shebang line and
left alone. However, if no path is given (e.i.: #!python or :::python) left alone. However, if no path is given (e.i.: #!python or :::python)
then it is assumed to be a mock shebang for language identifitation of a then it is assumed to be a mock shebang for language identifitation of a
code fragment and removed from the code block prior to processing for code fragment and removed from the code block prior to processing for
code highlighting. When a mock shebang (e.i: #!python) is found, line code highlighting. When a mock shebang (e.i: #!python) is found, line
numbering is turned on. When colons are found in place of a shebang numbering is turned on. When colons are found in place of a shebang
(e.i.: :::python), line numbering is left in the current state - off (e.i.: :::python), line numbering is left in the current state - off
by default. by default.
Also parses optional list of highlight lines, like: Also parses optional list of highlight lines, like:
:::python hl_lines="1 3" :::python hl_lines="1 3"
""" """
import re import re
#split text into lines #split text into lines
lines = self.src.split("\n") lines = self.src.split("\n")
#pull first line to examine #pull first line to examine
fl = lines.pop(0) fl = lines.pop(0)
c = re.compile(r''' c = re.compile(r'''
(?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons (?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons
(?P<path>(?:/\w+)*[/ ])? # Zero or 1 path (?P<path>(?:/\w+)*[/ ])? # Zero or 1 path
(?P<lang>[\w+-]*) # The language (?P<lang>[\w+-]*) # The language
\s* # Arbitrary whitespace \s* # Arbitrary whitespace
# Optional highlight lines, single- or double-quote-delimited # Optional highlight lines, single- or double-quote-delimited
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))? (hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?
''', re.VERBOSE) ''', re.VERBOSE)
# search first line for shebang # search first line for shebang
m = c.search(fl) m = c.search(fl)
if m: if m:
# we have a match # we have a match
try: try:
self.lang = m.group('lang').lower() self.lang = m.group('lang').lower()
except IndexError: except IndexError:
self.lang = None self.lang = None
if m.group('path'): if m.group('path'):
# path exists - restore first line # path exists - restore first line
lines.insert(0, fl) lines.insert(0, fl)
if self.linenums is None and m.group('shebang'): if self.linenums is None and m.group('shebang'):
# Overridable and Shebang exists - use line numbers # Overridable and Shebang exists - use line numbers
self.linenums = True self.linenums = True
self.hl_lines = parse_hl_lines(m.group('hl_lines')) self.hl_lines = parse_hl_lines(m.group('hl_lines'))
else: else:
# No match # No match
lines.insert(0, fl) lines.insert(0, fl)
self.src = "\n".join(lines).strip("\n") self.src = "\n".join(lines).strip("\n")
# ------------------ The Markdown Extension ------------------------------- # ------------------ The Markdown Extension -------------------------------
class HiliteTreeprocessor(Treeprocessor): class HiliteTreeprocessor(Treeprocessor):
""" Hilight source code in code blocks. """ """ Hilight source code in code blocks. """
def run(self, root): def run(self, root):
""" Find code blocks and store in htmlStash. """ """ Find code blocks and store in htmlStash. """
blocks = root.getiterator('pre') blocks = root.getiterator('pre')
for block in blocks: for block in blocks:
children = block.getchildren() children = block.getchildren()
if len(children) == 1 and children[0].tag == 'code': if len(children) == 1 and children[0].tag == 'code':
code = CodeHilite(children[0].text, code = CodeHilite(children[0].text,
linenums=self.config['linenums'], linenums=self.config['linenums'],
guess_lang=self.config['guess_lang'], guess_lang=self.config['guess_lang'],
css_class=self.config['css_class'], css_class=self.config['css_class'],
style=self.config['pygments_style'], style=self.config['pygments_style'],
noclasses=self.config['noclasses'], noclasses=self.config['noclasses'],
tab_length=self.markdown.tab_length) tab_length=self.markdown.tab_length)
placeholder = self.markdown.htmlStash.store(code.hilite(), placeholder = self.markdown.htmlStash.store(code.hilite(),
safe=True) safe=True)
# Clear codeblock in etree instance # Clear codeblock in etree instance
block.clear() block.clear()
# Change to p element which will later # Change to p element which will later
# be removed when inserting raw html # be removed when inserting raw html
block.tag = 'p' block.tag = 'p'
block.text = placeholder block.text = placeholder
class CodeHiliteExtension(Extension): class CodeHiliteExtension(Extension):
""" Add source code hilighting to markdown codeblocks. """ """ Add source code hilighting to markdown codeblocks. """
def __init__(self, configs): def __init__(self, configs):
# define default configs # define default configs
self.config = { self.config = {
'linenums': [None, "Use lines numbers. True=yes, False=no, None=auto"], 'linenums': [None, "Use lines numbers. True=yes, False=no, None=auto"],
'force_linenos' : [False, "Depreciated! Use 'linenums' instead. Force line numbers - Default: False"], 'force_linenos' : [False, "Depreciated! Use 'linenums' instead. Force line numbers - Default: False"],
'guess_lang' : [True, "Automatic language detection - Default: True"], 'guess_lang' : [True, "Automatic language detection - Default: True"],
'css_class' : ["codehilite", 'css_class' : ["codehilite",
"Set class name for wrapper <div> - Default: codehilite"], "Set class name for wrapper <div> - Default: codehilite"],
'pygments_style' : ['default', 'Pygments HTML Formatter Style (Colorscheme) - Default: default'], 'pygments_style' : ['default', 'Pygments HTML Formatter Style (Colorscheme) - Default: default'],
'noclasses': [False, 'Use inline styles instead of CSS classes - Default false'] 'noclasses': [False, 'Use inline styles instead of CSS classes - Default false']
} }
# Override defaults with user settings # Override defaults with user settings
for key, value in configs: for key, value in configs:
# convert strings to booleans # convert strings to booleans
if value == 'True': value = True if value == 'True': value = True
if value == 'False': value = False if value == 'False': value = False
if value == 'None': value = None if value == 'None': value = None
if key == 'force_linenos': if key == 'force_linenos':
warnings.warn('The "force_linenos" config setting' warnings.warn('The "force_linenos" config setting'
' to the CodeHilite extension is deprecrecated.' ' to the CodeHilite extension is deprecrecated.'
' Use "linenums" instead.', DeprecationWarning) ' Use "linenums" instead.', DeprecationWarning)
if value: if value:
# Carry 'force_linenos' over to new 'linenos'. # Carry 'force_linenos' over to new 'linenos'.
self.setConfig('linenums', True) self.setConfig('linenums', True)
self.setConfig(key, value) self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add HilitePostprocessor to Markdown instance. """ """ Add HilitePostprocessor to Markdown instance. """
hiliter = HiliteTreeprocessor(md) hiliter = HiliteTreeprocessor(md)
hiliter.config = self.getConfigs() hiliter.config = self.getConfigs()
md.treeprocessors.add("hilite", hiliter, "<inline") md.treeprocessors.add("hilite", hiliter, "<inline")
md.registerExtension(self) md.registerExtension(self)
def makeExtension(configs={}): def makeExtension(configs={}):
return CodeHiliteExtension(configs=configs) return CodeHiliteExtension(configs=configs)

View File

@@ -1,118 +1,118 @@
""" """
Definition List Extension for Python-Markdown Definition List Extension for Python-Markdown
============================================= =============================================
Added parsing of Definition Lists to Python-Markdown. Added parsing of Definition Lists to Python-Markdown.
A simple example: A simple example:
Apple Apple
: Pomaceous fruit of plants of the genus Malus in : Pomaceous fruit of plants of the genus Malus in
the family Rosaceae. the family Rosaceae.
: An american computer company. : An american computer company.
Orange Orange
: The fruit of an evergreen tree of the genus Citrus. : The fruit of an evergreen tree of the genus Citrus.
Copyright 2008 - [Waylan Limberg](http://achinghead.com) Copyright 2008 - [Waylan Limberg](http://achinghead.com)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..blockprocessors import BlockProcessor, ListIndentProcessor from ..blockprocessors import BlockProcessor, ListIndentProcessor
from ..util import etree from ..util import etree
import re import re
class DefListProcessor(BlockProcessor): class DefListProcessor(BlockProcessor):
""" Process Definition Lists. """ """ Process Definition Lists. """
RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)') RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)')
NO_INDENT_RE = re.compile(r'^[ ]{0,3}[^ :]') NO_INDENT_RE = re.compile(r'^[ ]{0,3}[^ :]')
def test(self, parent, block): def test(self, parent, block):
return bool(self.RE.search(block)) return bool(self.RE.search(block))
def run(self, parent, blocks): def run(self, parent, blocks):
raw_block = blocks.pop(0) raw_block = blocks.pop(0)
m = self.RE.search(raw_block) m = self.RE.search(raw_block)
terms = [l.strip() for l in raw_block[:m.start()].split('\n') if l.strip()] terms = [l.strip() for l in raw_block[:m.start()].split('\n') if l.strip()]
block = raw_block[m.end():] block = raw_block[m.end():]
no_indent = self.NO_INDENT_RE.match(block) no_indent = self.NO_INDENT_RE.match(block)
if no_indent: if no_indent:
d, theRest = (block, None) d, theRest = (block, None)
else: else:
d, theRest = self.detab(block) d, theRest = self.detab(block)
if d: if d:
d = '%s\n%s' % (m.group(2), d) d = '%s\n%s' % (m.group(2), d)
else: else:
d = m.group(2) d = m.group(2)
sibling = self.lastChild(parent) sibling = self.lastChild(parent)
if not terms and sibling is None: if not terms and sibling is None:
# This is not a definition item. Most likely a paragraph that # This is not a definition item. Most likely a paragraph that
# starts with a colon at the begining of a document or list. # starts with a colon at the begining of a document or list.
blocks.insert(0, raw_block) blocks.insert(0, raw_block)
return False return False
if not terms and sibling.tag == 'p': if not terms and sibling.tag == 'p':
# The previous paragraph contains the terms # The previous paragraph contains the terms
state = 'looselist' state = 'looselist'
terms = sibling.text.split('\n') terms = sibling.text.split('\n')
parent.remove(sibling) parent.remove(sibling)
# Aquire new sibling # Aquire new sibling
sibling = self.lastChild(parent) sibling = self.lastChild(parent)
else: else:
state = 'list' state = 'list'
if sibling and sibling.tag == 'dl': if sibling and sibling.tag == 'dl':
# This is another item on an existing list # This is another item on an existing list
dl = sibling dl = sibling
if not terms and len(dl) and dl[-1].tag == 'dd' and len(dl[-1]): if not terms and len(dl) and dl[-1].tag == 'dd' and len(dl[-1]):
state = 'looselist' state = 'looselist'
else: else:
# This is a new list # This is a new list
dl = etree.SubElement(parent, 'dl') dl = etree.SubElement(parent, 'dl')
# Add terms # Add terms
for term in terms: for term in terms:
dt = etree.SubElement(dl, 'dt') dt = etree.SubElement(dl, 'dt')
dt.text = term dt.text = term
# Add definition # Add definition
self.parser.state.set(state) self.parser.state.set(state)
dd = etree.SubElement(dl, 'dd') dd = etree.SubElement(dl, 'dd')
self.parser.parseBlocks(dd, [d]) self.parser.parseBlocks(dd, [d])
self.parser.state.reset() self.parser.state.reset()
if theRest: if theRest:
blocks.insert(0, theRest) blocks.insert(0, theRest)
class DefListIndentProcessor(ListIndentProcessor): class DefListIndentProcessor(ListIndentProcessor):
""" Process indented children of definition list items. """ """ Process indented children of definition list items. """
ITEM_TYPES = ['dd'] ITEM_TYPES = ['dd']
LIST_TYPES = ['dl'] LIST_TYPES = ['dl']
def create_item(self, parent, block): def create_item(self, parent, block):
""" Create a new dd and parse the block with it as the parent. """ """ Create a new dd and parse the block with it as the parent. """
dd = etree.SubElement(parent, 'dd') dd = etree.SubElement(parent, 'dd')
self.parser.parseBlocks(dd, [block]) self.parser.parseBlocks(dd, [block])
class DefListExtension(Extension): class DefListExtension(Extension):
""" Add definition lists to Markdown. """ """ Add definition lists to Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add an instance of DefListProcessor to BlockParser. """ """ Add an instance of DefListProcessor to BlockParser. """
md.parser.blockprocessors.add('defindent', md.parser.blockprocessors.add('defindent',
DefListIndentProcessor(md.parser), DefListIndentProcessor(md.parser),
'>indent') '>indent')
md.parser.blockprocessors.add('deflist', md.parser.blockprocessors.add('deflist',
DefListProcessor(md.parser), DefListProcessor(md.parser),
'>ulist') '>ulist')
def makeExtension(configs={}): def makeExtension(configs={}):
return DefListExtension(configs=configs) return DefListExtension(configs=configs)

View File

@@ -1,129 +1,123 @@
""" """
Python-Markdown Extra Extension Python-Markdown Extra Extension
=============================== ===============================
A compilation of various Python-Markdown extensions that imitates A compilation of various Python-Markdown extensions that imitates
[PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/). [PHP Markdown Extra](http://michelf.com/projects/php-markdown/extra/).
Note that each of the individual extensions still need to be available Note that each of the individual extensions still need to be available
on your PYTHONPATH. This extension simply wraps them all up as a on your PYTHONPATH. This extension simply wraps them all up as a
convenience so that only one extension needs to be listed when convenience so that only one extension needs to be listed when
initiating Markdown. See the documentation for each individual initiating Markdown. See the documentation for each individual
extension for specifics about that extension. extension for specifics about that extension.
In the event that one or more of the supported extensions are not In the event that one or more of the supported extensions are not
available for import, Markdown will issue a warning and simply continue available for import, Markdown will issue a warning and simply continue
without that extension. without that extension.
There may be additional extensions that are distributed with There may be additional extensions that are distributed with
Python-Markdown that are not included here in Extra. Those extensions Python-Markdown that are not included here in Extra. Those extensions
are not part of PHP Markdown Extra, and therefore, not part of are not part of PHP Markdown Extra, and therefore, not part of
Python-Markdown Extra. If you really would like Extra to include Python-Markdown Extra. If you really would like Extra to include
additional extensions, we suggest creating your own clone of Extra additional extensions, we suggest creating your own clone of Extra
under a differant name. You could also edit the `extensions` global under a differant name. You could also edit the `extensions` global
variable defined below, but be aware that such changes may be lost variable defined below, but be aware that such changes may be lost
when you upgrade to any future version of Python-Markdown. when you upgrade to any future version of Python-Markdown.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..blockprocessors import BlockProcessor from ..blockprocessors import BlockProcessor
from .. import util from .. import util
import re import re
extensions = ['smart_strong', extensions = ['smart_strong',
'fenced_code', 'fenced_code',
'footnotes', 'footnotes',
'attr_list', 'attr_list',
'def_list', 'def_list',
'tables', 'tables',
'abbr', 'abbr',
] ]
class ExtraExtension(Extension): class ExtraExtension(Extension):
""" Add various extensions to Markdown class.""" """ Add various extensions to Markdown class."""
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Register extension instances. """ """ Register extension instances. """
md.registerExtensions(extensions, self.config) md.registerExtensions(extensions, self.config)
if not md.safeMode: if not md.safeMode:
# Turn on processing of markdown text within raw html # Turn on processing of markdown text within raw html
md.preprocessors['html_block'].markdown_in_raw = True md.preprocessors['html_block'].markdown_in_raw = True
md.parser.blockprocessors.add('markdown_block', md.parser.blockprocessors.add('markdown_block',
MarkdownInHtmlProcessor(md.parser), MarkdownInHtmlProcessor(md.parser),
'_begin') '_begin')
md.parser.blockprocessors.tag_counter = -1 md.parser.blockprocessors.tag_counter = -1
md.parser.blockprocessors.contain_span_tags = re.compile( md.parser.blockprocessors.contain_span_tags = re.compile(
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE) r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
def makeExtension(configs={}): def makeExtension(configs={}):
return ExtraExtension(configs=dict(configs)) return ExtraExtension(configs=dict(configs))
class MarkdownInHtmlProcessor(BlockProcessor): class MarkdownInHtmlProcessor(BlockProcessor):
"""Process Markdown Inside HTML Blocks.""" """Process Markdown Inside HTML Blocks."""
def test(self, parent, block): def test(self, parent, block):
return block == util.TAG_PLACEHOLDER % \ return block == util.TAG_PLACEHOLDER % \
str(self.parser.blockprocessors.tag_counter + 1) str(self.parser.blockprocessors.tag_counter + 1)
def _process_nests(self, element, block): def _process_nests(self, element, block):
"""Process the element's child elements in self.run.""" """Process the element's child elements in self.run."""
# Build list of indexes of each nest within the parent element. # Build list of indexes of each nest within the parent element.
nest_index = [] # a list of tuples: (left index, right index) nest_index = [] # a list of tuples: (left index, right index)
i = self.parser.blockprocessors.tag_counter + 1 i = self.parser.blockprocessors.tag_counter + 1
is_nest = self.parser.markdown.htmlStash.tag_data[i]['left_index'] while len(self._tag_data) > i and self._tag_data[i]['left_index']:
while len(self.parser.markdown.htmlStash.tag_data) > i and is_nest: left_child_index = self._tag_data[i]['left_index']
left_child_index = \ right_child_index = self._tag_data[i]['right_index']
self.parser.markdown.htmlStash.tag_data[i]['left_index'] nest_index.append((left_child_index - 1, right_child_index))
right_child_index = \ i += 1
self.parser.markdown.htmlStash.tag_data[i]['right_index']
nest_index.append((left_child_index - 1, right_child_index)) # Create each nest subelement.
i += 1 for i, (left_index, right_index) in enumerate(nest_index[:-1]):
self.run(element, block[left_index:right_index],
# Create each nest subelement. block[right_index:nest_index[i + 1][0]], True)
for i, (left_index, right_index) in enumerate(nest_index[:-1]): self.run(element, block[nest_index[-1][0]:nest_index[-1][1]], # last
self.run(element, block[left_index:right_index], block[nest_index[-1][1]:], True) # nest
block[right_index:nest_index[i + 1][0]], True)
self.run(element, block[nest_index[-1][0]:nest_index[-1][1]], # last def run(self, parent, blocks, tail=None, nest=False):
block[nest_index[-1][1]:], True) # nest self._tag_data = self.parser.markdown.htmlStash.tag_data
def run(self, parent, blocks, tail=None, nest=False): self.parser.blockprocessors.tag_counter += 1
self.parser.blockprocessors.tag_counter += 1 tag = self._tag_data[self.parser.blockprocessors.tag_counter]
tag_data = self.parser.markdown.htmlStash.tag_data[
self.parser.blockprocessors.tag_counter] # Create Element
markdown_value = tag['attrs'].pop('markdown')
# Create Element element = util.etree.SubElement(parent, tag['tag'], tag['attrs'])
markdown_value = tag_data['attrs'].pop('markdown')
element = util.etree.SubElement(parent, tag_data['tag'], # Slice Off Block
tag_data['attrs']) if nest:
self.parser.parseBlocks(parent, tail) # Process Tail
# Slice Off Block block = blocks[1:]
if nest: else: # includes nests since a third level of nesting isn't supported
self.parser.parseBlocks(parent, tail) # Process Tail block = blocks[tag['left_index'] + 1: tag['right_index']]
block = blocks[1:] del blocks[:tag['right_index']]
else: # includes nests since a third level of nesting isn't supported
block = blocks[tag_data['left_index'] + 1: # Process Text
tag_data['right_index']] if (self.parser.blockprocessors.contain_span_tags.match( # Span Mode
del blocks[:tag_data['right_index']] tag['tag']) and markdown_value != 'block') or \
markdown_value == 'span':
# Process Text element.text = '\n'.join(block)
if (self.parser.blockprocessors.contain_span_tags.match( # Span Mode else: # Block Mode
tag_data['tag']) and markdown_value != 'block') or \ i = self.parser.blockprocessors.tag_counter + 1
markdown_value == 'span': if len(self._tag_data) > i and self._tag_data[i]['left_index']:
element.text = '\n'.join(block) first_subelement_index = self._tag_data[i]['left_index'] - 1
else: # Block Mode self.parser.parseBlocks(
i = self.parser.blockprocessors.tag_counter + 1 element, block[:first_subelement_index])
if len(self.parser.markdown.htmlStash.tag_data) > i and self.\ if not nest:
parser.markdown.htmlStash.tag_data[i]['left_index']: block = self._process_nests(element, block)
first_subelement_index = self.parser.markdown.htmlStash.\ else:
tag_data[i]['left_index'] - 1 self.parser.parseBlocks(element, block)
self.parser.parseBlocks(
element, block[:first_subelement_index])
if not nest:
block = self._process_nests(element, block)
else:
self.parser.parseBlocks(element, block)

View File

@@ -1,179 +1,179 @@
""" """
Fenced Code Extension for Python Markdown Fenced Code Extension for Python Markdown
========================================= =========================================
This extension adds Fenced Code Blocks to Python-Markdown. This extension adds Fenced Code Blocks to Python-Markdown.
>>> import markdown >>> import markdown
>>> text = ''' >>> text = '''
... A paragraph before a fenced code block: ... A paragraph before a fenced code block:
... ...
... ~~~ ... ~~~
... Fenced code block ... Fenced code block
... ~~~ ... ~~~
... ''' ... '''
>>> html = markdown.markdown(text, extensions=['fenced_code']) >>> html = markdown.markdown(text, extensions=['fenced_code'])
>>> print html >>> print html
<p>A paragraph before a fenced code block:</p> <p>A paragraph before a fenced code block:</p>
<pre><code>Fenced code block <pre><code>Fenced code block
</code></pre> </code></pre>
Works with safe_mode also (we check this because we are using the HtmlStash): Works with safe_mode also (we check this because we are using the HtmlStash):
>>> print markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace') >>> print markdown.markdown(text, extensions=['fenced_code'], safe_mode='replace')
<p>A paragraph before a fenced code block:</p> <p>A paragraph before a fenced code block:</p>
<pre><code>Fenced code block <pre><code>Fenced code block
</code></pre> </code></pre>
Include tilde's in a code block and wrap with blank lines: Include tilde's in a code block and wrap with blank lines:
>>> text = ''' >>> text = '''
... ~~~~~~~~ ... ~~~~~~~~
... ...
... ~~~~ ... ~~~~
... ~~~~~~~~''' ... ~~~~~~~~'''
>>> print markdown.markdown(text, extensions=['fenced_code']) >>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code> <pre><code>
~~~~ ~~~~
</code></pre> </code></pre>
Language tags: Language tags:
>>> text = ''' >>> text = '''
... ~~~~{.python} ... ~~~~{.python}
... # Some python code ... # Some python code
... ~~~~''' ... ~~~~'''
>>> print markdown.markdown(text, extensions=['fenced_code']) >>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code class="python"># Some python code <pre><code class="python"># Some python code
</code></pre> </code></pre>
Optionally backticks instead of tildes as per how github's code block markdown is identified: Optionally backticks instead of tildes as per how github's code block markdown is identified:
>>> text = ''' >>> text = '''
... ````` ... `````
... # Arbitrary code ... # Arbitrary code
... ~~~~~ # these tildes will not close the block ... ~~~~~ # these tildes will not close the block
... `````''' ... `````'''
>>> print markdown.markdown(text, extensions=['fenced_code']) >>> print markdown.markdown(text, extensions=['fenced_code'])
<pre><code># Arbitrary code <pre><code># Arbitrary code
~~~~~ # these tildes will not close the block ~~~~~ # these tildes will not close the block
</code></pre> </code></pre>
If the codehighlite extension and Pygments are installed, lines can be highlighted: If the codehighlite extension and Pygments are installed, lines can be highlighted:
>>> text = ''' >>> text = '''
... ```hl_lines="1 3" ... ```hl_lines="1 3"
... line 1 ... line 1
... line 2 ... line 2
... line 3 ... line 3
... ```''' ... ```'''
>>> print markdown.markdown(text, extensions=['codehilite', 'fenced_code']) >>> print markdown.markdown(text, extensions=['codehilite', 'fenced_code'])
<pre><code><span class="hilight">line 1</span> <pre><code><span class="hilight">line 1</span>
line 2 line 2
<span class="hilight">line 3</span> <span class="hilight">line 3</span>
</code></pre> </code></pre>
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/). Copyright 2007-2008 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html> Project website: <http://packages.python.org/Markdown/extensions/fenced_code_blocks.html>
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
License: BSD (see ../docs/LICENSE for details) License: BSD (see ../docs/LICENSE for details)
Dependencies: Dependencies:
* [Python 2.4+](http://python.org) * [Python 2.4+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/) * [Markdown 2.0+](http://packages.python.org/Markdown/)
* [Pygments (optional)](http://pygments.org) * [Pygments (optional)](http://pygments.org)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..preprocessors import Preprocessor from ..preprocessors import Preprocessor
from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
import re import re
class FencedCodeExtension(Extension): class FencedCodeExtension(Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add FencedBlockPreprocessor to the Markdown instance. """ """ Add FencedBlockPreprocessor to the Markdown instance. """
md.registerExtension(self) md.registerExtension(self)
md.preprocessors.add('fenced_code_block', md.preprocessors.add('fenced_code_block',
FencedBlockPreprocessor(md), FencedBlockPreprocessor(md),
">normalize_whitespace") ">normalize_whitespace")
class FencedBlockPreprocessor(Preprocessor): class FencedBlockPreprocessor(Preprocessor):
FENCED_BLOCK_RE = re.compile(r''' FENCED_BLOCK_RE = re.compile(r'''
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # Opening ``` or ~~~ (?P<fence>^(?:~{3,}|`{3,}))[ ]* # Opening ``` or ~~~
(\{?\.?(?P<lang>[a-zA-Z0-9_+-]*))?[ ]* # Optional {, and lang (\{?\.?(?P<lang>[a-zA-Z0-9_+-]*))?[ ]* # Optional {, and lang
# Optional highlight lines, single- or double-quote-delimited # Optional highlight lines, single- or double-quote-delimited
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?[ ]* (hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?[ ]*
}?[ ]*\n # Optional closing } }?[ ]*\n # Optional closing }
(?P<code>.*?)(?<=\n) (?P<code>.*?)(?<=\n)
(?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE) (?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE)
CODE_WRAP = '<pre><code%s>%s</code></pre>' CODE_WRAP = '<pre><code%s>%s</code></pre>'
LANG_TAG = ' class="%s"' LANG_TAG = ' class="%s"'
def __init__(self, md): def __init__(self, md):
super(FencedBlockPreprocessor, self).__init__(md) super(FencedBlockPreprocessor, self).__init__(md)
self.checked_for_codehilite = False self.checked_for_codehilite = False
self.codehilite_conf = {} self.codehilite_conf = {}
def run(self, lines): def run(self, lines):
""" Match and store Fenced Code Blocks in the HtmlStash. """ """ Match and store Fenced Code Blocks in the HtmlStash. """
# Check for code hilite extension # Check for code hilite extension
if not self.checked_for_codehilite: if not self.checked_for_codehilite:
for ext in self.markdown.registeredExtensions: for ext in self.markdown.registeredExtensions:
if isinstance(ext, CodeHiliteExtension): if isinstance(ext, CodeHiliteExtension):
self.codehilite_conf = ext.config self.codehilite_conf = ext.config
break break
self.checked_for_codehilite = True self.checked_for_codehilite = True
text = "\n".join(lines) text = "\n".join(lines)
while 1: while 1:
m = self.FENCED_BLOCK_RE.search(text) m = self.FENCED_BLOCK_RE.search(text)
if m: if m:
lang = '' lang = ''
if m.group('lang'): if m.group('lang'):
lang = self.LANG_TAG % m.group('lang') lang = self.LANG_TAG % m.group('lang')
# If config is not empty, then the codehighlite extension # If config is not empty, then the codehighlite extension
# is enabled, so we call it to highlight the code # is enabled, so we call it to highlight the code
if self.codehilite_conf: if self.codehilite_conf:
highliter = CodeHilite(m.group('code'), highliter = CodeHilite(m.group('code'),
linenums=self.codehilite_conf['linenums'][0], linenums=self.codehilite_conf['linenums'][0],
guess_lang=self.codehilite_conf['guess_lang'][0], guess_lang=self.codehilite_conf['guess_lang'][0],
css_class=self.codehilite_conf['css_class'][0], css_class=self.codehilite_conf['css_class'][0],
style=self.codehilite_conf['pygments_style'][0], style=self.codehilite_conf['pygments_style'][0],
lang=(m.group('lang') or None), lang=(m.group('lang') or None),
noclasses=self.codehilite_conf['noclasses'][0], noclasses=self.codehilite_conf['noclasses'][0],
hl_lines=parse_hl_lines(m.group('hl_lines'))) hl_lines=parse_hl_lines(m.group('hl_lines')))
code = highliter.hilite() code = highliter.hilite()
else: else:
code = self.CODE_WRAP % (lang, self._escape(m.group('code'))) code = self.CODE_WRAP % (lang, self._escape(m.group('code')))
placeholder = self.markdown.htmlStash.store(code, safe=True) placeholder = self.markdown.htmlStash.store(code, safe=True)
text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():]) text = '%s\n%s\n%s'% (text[:m.start()], placeholder, text[m.end():])
else: else:
break break
return text.split("\n") return text.split("\n")
def _escape(self, txt): def _escape(self, txt):
""" basic html escaping """ """ basic html escaping """
txt = txt.replace('&', '&amp;') txt = txt.replace('&', '&amp;')
txt = txt.replace('<', '&lt;') txt = txt.replace('<', '&lt;')
txt = txt.replace('>', '&gt;') txt = txt.replace('>', '&gt;')
txt = txt.replace('"', '&quot;') txt = txt.replace('"', '&quot;')
return txt return txt
def makeExtension(configs=None): def makeExtension(configs=None):
return FencedCodeExtension(configs=configs) return FencedCodeExtension(configs=configs)

View File

@@ -1,315 +1,315 @@
""" """
========================= FOOTNOTES ================================= ========================= FOOTNOTES =================================
This section adds footnote handling to markdown. It can be used as This section adds footnote handling to markdown. It can be used as
an example for extending python-markdown with relatively complex an example for extending python-markdown with relatively complex
functionality. While in this case the extension is included inside functionality. While in this case the extension is included inside
the module itself, it could just as easily be added from outside the the module itself, it could just as easily be added from outside the
module. Not that all markdown classes above are ignorant about module. Not that all markdown classes above are ignorant about
footnotes. All footnote functionality is provided separately and footnotes. All footnote functionality is provided separately and
then added to the markdown instance at the run time. then added to the markdown instance at the run time.
Footnote functionality is attached by calling extendMarkdown() Footnote functionality is attached by calling extendMarkdown()
method of FootnoteExtension. The method also registers the method of FootnoteExtension. The method also registers the
extension to allow it's state to be reset by a call to reset() extension to allow it's state to be reset by a call to reset()
method. method.
Example: Example:
Footnotes[^1] have a label[^label] and a definition[^!DEF]. Footnotes[^1] have a label[^label] and a definition[^!DEF].
[^1]: This is a footnote [^1]: This is a footnote
[^label]: A footnote on "label" [^label]: A footnote on "label"
[^!DEF]: The footnote for definition [^!DEF]: The footnote for definition
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..preprocessors import Preprocessor from ..preprocessors import Preprocessor
from ..inlinepatterns import Pattern from ..inlinepatterns import Pattern
from ..treeprocessors import Treeprocessor from ..treeprocessors import Treeprocessor
from ..postprocessors import Postprocessor from ..postprocessors import Postprocessor
from ..util import etree, text_type from ..util import etree, text_type
from ..odict import OrderedDict from ..odict import OrderedDict
import re import re
FN_BACKLINK_TEXT = "zz1337820767766393qq" FN_BACKLINK_TEXT = "zz1337820767766393qq"
NBSP_PLACEHOLDER = "qq3936677670287331zz" NBSP_PLACEHOLDER = "qq3936677670287331zz"
DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)') DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
TABBED_RE = re.compile(r'((\t)|( ))(.*)') TABBED_RE = re.compile(r'((\t)|( ))(.*)')
class FootnoteExtension(Extension): class FootnoteExtension(Extension):
""" Footnote Extension. """ """ Footnote Extension. """
def __init__ (self, configs): def __init__ (self, configs):
""" Setup configs. """ """ Setup configs. """
self.config = {'PLACE_MARKER': self.config = {'PLACE_MARKER':
["///Footnotes Go Here///", ["///Footnotes Go Here///",
"The text string that marks where the footnotes go"], "The text string that marks where the footnotes go"],
'UNIQUE_IDS': 'UNIQUE_IDS':
[False, [False,
"Avoid name collisions across " "Avoid name collisions across "
"multiple calls to reset()."], "multiple calls to reset()."],
"BACKLINK_TEXT": "BACKLINK_TEXT":
["&#8617;", ["&#8617;",
"The text string that links from the footnote to the reader's place."] "The text string that links from the footnote to the reader's place."]
} }
for key, value in configs: for key, value in configs:
self.config[key][0] = value self.config[key][0] = value
# In multiple invocations, emit links that don't get tangled. # In multiple invocations, emit links that don't get tangled.
self.unique_prefix = 0 self.unique_prefix = 0
self.reset() self.reset()
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add pieces to Markdown. """ """ Add pieces to Markdown. """
md.registerExtension(self) md.registerExtension(self)
self.parser = md.parser self.parser = md.parser
self.md = md self.md = md
# Insert a preprocessor before ReferencePreprocessor # Insert a preprocessor before ReferencePreprocessor
md.preprocessors.add("footnote", FootnotePreprocessor(self), md.preprocessors.add("footnote", FootnotePreprocessor(self),
"<reference") "<reference")
# Insert an inline pattern before ImageReferencePattern # Insert an inline pattern before ImageReferencePattern
FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self), md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self),
"<reference") "<reference")
# Insert a tree-processor that would actually add the footnote div # Insert a tree-processor that would actually add the footnote div
# This must be before all other treeprocessors (i.e., inline and # This must be before all other treeprocessors (i.e., inline and
# codehilite) so they can run on the the contents of the div. # codehilite) so they can run on the the contents of the div.
md.treeprocessors.add("footnote", FootnoteTreeprocessor(self), md.treeprocessors.add("footnote", FootnoteTreeprocessor(self),
"_begin") "_begin")
# Insert a postprocessor after amp_substitute oricessor # Insert a postprocessor after amp_substitute oricessor
md.postprocessors.add("footnote", FootnotePostprocessor(self), md.postprocessors.add("footnote", FootnotePostprocessor(self),
">amp_substitute") ">amp_substitute")
def reset(self): def reset(self):
""" Clear the footnotes on reset, and prepare for a distinct document. """ """ Clear the footnotes on reset, and prepare for a distinct document. """
self.footnotes = OrderedDict() self.footnotes = OrderedDict()
self.unique_prefix += 1 self.unique_prefix += 1
def findFootnotesPlaceholder(self, root): def findFootnotesPlaceholder(self, root):
""" Return ElementTree Element that contains Footnote placeholder. """ """ Return ElementTree Element that contains Footnote placeholder. """
def finder(element): def finder(element):
for child in element: for child in element:
if child.text: if child.text:
if child.text.find(self.getConfig("PLACE_MARKER")) > -1: if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
return child, element, True return child, element, True
if child.tail: if child.tail:
if child.tail.find(self.getConfig("PLACE_MARKER")) > -1: if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
return child, element, False return child, element, False
finder(child) finder(child)
return None return None
res = finder(root) res = finder(root)
return res return res
def setFootnote(self, id, text): def setFootnote(self, id, text):
""" Store a footnote for later retrieval. """ """ Store a footnote for later retrieval. """
self.footnotes[id] = text self.footnotes[id] = text
def get_separator(self): def get_separator(self):
if self.md.output_format in ['html5', 'xhtml5']: if self.md.output_format in ['html5', 'xhtml5']:
return '-' return '-'
return ':' return ':'
def makeFootnoteId(self, id): def makeFootnoteId(self, id):
""" Return footnote link id. """ """ Return footnote link id. """
if self.getConfig("UNIQUE_IDS"): if self.getConfig("UNIQUE_IDS"):
return 'fn%s%d-%s' % (self.get_separator(), self.unique_prefix, id) return 'fn%s%d-%s' % (self.get_separator(), self.unique_prefix, id)
else: else:
return 'fn%s%s' % (self.get_separator(), id) return 'fn%s%s' % (self.get_separator(), id)
def makeFootnoteRefId(self, id): def makeFootnoteRefId(self, id):
""" Return footnote back-link id. """ """ Return footnote back-link id. """
if self.getConfig("UNIQUE_IDS"): if self.getConfig("UNIQUE_IDS"):
return 'fnref%s%d-%s' % (self.get_separator(), self.unique_prefix, id) return 'fnref%s%d-%s' % (self.get_separator(), self.unique_prefix, id)
else: else:
return 'fnref%s%s' % (self.get_separator(), id) return 'fnref%s%s' % (self.get_separator(), id)
def makeFootnotesDiv(self, root): def makeFootnotesDiv(self, root):
""" Return div of footnotes as et Element. """ """ Return div of footnotes as et Element. """
if not list(self.footnotes.keys()): if not list(self.footnotes.keys()):
return None return None
div = etree.Element("div") div = etree.Element("div")
div.set('class', 'footnote') div.set('class', 'footnote')
etree.SubElement(div, "hr") etree.SubElement(div, "hr")
ol = etree.SubElement(div, "ol") ol = etree.SubElement(div, "ol")
for id in self.footnotes.keys(): for id in self.footnotes.keys():
li = etree.SubElement(ol, "li") li = etree.SubElement(ol, "li")
li.set("id", self.makeFootnoteId(id)) li.set("id", self.makeFootnoteId(id))
self.parser.parseChunk(li, self.footnotes[id]) self.parser.parseChunk(li, self.footnotes[id])
backlink = etree.Element("a") backlink = etree.Element("a")
backlink.set("href", "#" + self.makeFootnoteRefId(id)) backlink.set("href", "#" + self.makeFootnoteRefId(id))
if self.md.output_format not in ['html5', 'xhtml5']: if self.md.output_format not in ['html5', 'xhtml5']:
backlink.set("rev", "footnote") # Invalid in HTML5 backlink.set("rev", "footnote") # Invalid in HTML5
backlink.set("class", "footnote-backref") backlink.set("class", "footnote-backref")
backlink.set("title", "Jump back to footnote %d in the text" % \ backlink.set("title", "Jump back to footnote %d in the text" % \
(self.footnotes.index(id)+1)) (self.footnotes.index(id)+1))
backlink.text = FN_BACKLINK_TEXT backlink.text = FN_BACKLINK_TEXT
if li.getchildren(): if li.getchildren():
node = li[-1] node = li[-1]
if node.tag == "p": if node.tag == "p":
node.text = node.text + NBSP_PLACEHOLDER node.text = node.text + NBSP_PLACEHOLDER
node.append(backlink) node.append(backlink)
else: else:
p = etree.SubElement(li, "p") p = etree.SubElement(li, "p")
p.append(backlink) p.append(backlink)
return div return div
class FootnotePreprocessor(Preprocessor): class FootnotePreprocessor(Preprocessor):
""" Find all footnote references and store for later use. """ """ Find all footnote references and store for later use. """
def __init__ (self, footnotes): def __init__ (self, footnotes):
self.footnotes = footnotes self.footnotes = footnotes
def run(self, lines): def run(self, lines):
""" """
Loop through lines and find, set, and remove footnote definitions. Loop through lines and find, set, and remove footnote definitions.
Keywords: Keywords:
* lines: A list of lines of text * lines: A list of lines of text
Return: A list of lines of text with footnote definitions removed. Return: A list of lines of text with footnote definitions removed.
""" """
newlines = [] newlines = []
i = 0 i = 0
while True: while True:
m = DEF_RE.match(lines[i]) m = DEF_RE.match(lines[i])
if m: if m:
fn, _i = self.detectTabbed(lines[i+1:]) fn, _i = self.detectTabbed(lines[i+1:])
fn.insert(0, m.group(2)) fn.insert(0, m.group(2))
i += _i-1 # skip past footnote i += _i-1 # skip past footnote
self.footnotes.setFootnote(m.group(1), "\n".join(fn)) self.footnotes.setFootnote(m.group(1), "\n".join(fn))
else: else:
newlines.append(lines[i]) newlines.append(lines[i])
if len(lines) > i+1: if len(lines) > i+1:
i += 1 i += 1
else: else:
break break
return newlines return newlines
def detectTabbed(self, lines): def detectTabbed(self, lines):
""" Find indented text and remove indent before further proccesing. """ Find indented text and remove indent before further proccesing.
Keyword arguments: Keyword arguments:
* lines: an array of strings * lines: an array of strings
Returns: a list of post processed items and the index of last line. Returns: a list of post processed items and the index of last line.
""" """
items = [] items = []
blank_line = False # have we encountered a blank line yet? blank_line = False # have we encountered a blank line yet?
i = 0 # to keep track of where we are i = 0 # to keep track of where we are
def detab(line): def detab(line):
match = TABBED_RE.match(line) match = TABBED_RE.match(line)
if match: if match:
return match.group(4) return match.group(4)
for line in lines: for line in lines:
if line.strip(): # Non-blank line if line.strip(): # Non-blank line
detabbed_line = detab(line) detabbed_line = detab(line)
if detabbed_line: if detabbed_line:
items.append(detabbed_line) items.append(detabbed_line)
i += 1 i += 1
continue continue
elif not blank_line and not DEF_RE.match(line): elif not blank_line and not DEF_RE.match(line):
# not tabbed but still part of first par. # not tabbed but still part of first par.
items.append(line) items.append(line)
i += 1 i += 1
continue continue
else: else:
return items, i+1 return items, i+1
else: # Blank line: _maybe_ we are done. else: # Blank line: _maybe_ we are done.
blank_line = True blank_line = True
i += 1 # advance i += 1 # advance
# Find the next non-blank line # Find the next non-blank line
for j in range(i, len(lines)): for j in range(i, len(lines)):
if lines[j].strip(): if lines[j].strip():
next_line = lines[j]; break next_line = lines[j]; break
else: else:
break # There is no more text; we are done. break # There is no more text; we are done.
# Check if the next non-blank line is tabbed # Check if the next non-blank line is tabbed
if detab(next_line): # Yes, more work to do. if detab(next_line): # Yes, more work to do.
items.append("") items.append("")
continue continue
else: else:
break # No, we are done. break # No, we are done.
else: else:
i += 1 i += 1
return items, i return items, i
class FootnotePattern(Pattern): class FootnotePattern(Pattern):
""" InlinePattern for footnote markers in a document's body text. """ """ InlinePattern for footnote markers in a document's body text. """
def __init__(self, pattern, footnotes): def __init__(self, pattern, footnotes):
super(FootnotePattern, self).__init__(pattern) super(FootnotePattern, self).__init__(pattern)
self.footnotes = footnotes self.footnotes = footnotes
def handleMatch(self, m): def handleMatch(self, m):
id = m.group(2) id = m.group(2)
if id in self.footnotes.footnotes.keys(): if id in self.footnotes.footnotes.keys():
sup = etree.Element("sup") sup = etree.Element("sup")
a = etree.SubElement(sup, "a") a = etree.SubElement(sup, "a")
sup.set('id', self.footnotes.makeFootnoteRefId(id)) sup.set('id', self.footnotes.makeFootnoteRefId(id))
a.set('href', '#' + self.footnotes.makeFootnoteId(id)) a.set('href', '#' + self.footnotes.makeFootnoteId(id))
if self.footnotes.md.output_format not in ['html5', 'xhtml5']: if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
a.set('rel', 'footnote') # invalid in HTML5 a.set('rel', 'footnote') # invalid in HTML5
a.set('class', 'footnote-ref') a.set('class', 'footnote-ref')
a.text = text_type(self.footnotes.footnotes.index(id) + 1) a.text = text_type(self.footnotes.footnotes.index(id) + 1)
return sup return sup
else: else:
return None return None
class FootnoteTreeprocessor(Treeprocessor): class FootnoteTreeprocessor(Treeprocessor):
""" Build and append footnote div to end of document. """ """ Build and append footnote div to end of document. """
def __init__ (self, footnotes): def __init__ (self, footnotes):
self.footnotes = footnotes self.footnotes = footnotes
def run(self, root): def run(self, root):
footnotesDiv = self.footnotes.makeFootnotesDiv(root) footnotesDiv = self.footnotes.makeFootnotesDiv(root)
if footnotesDiv: if footnotesDiv:
result = self.footnotes.findFootnotesPlaceholder(root) result = self.footnotes.findFootnotesPlaceholder(root)
if result: if result:
child, parent, isText = result child, parent, isText = result
ind = parent.getchildren().index(child) ind = parent.getchildren().index(child)
if isText: if isText:
parent.remove(child) parent.remove(child)
parent.insert(ind, footnotesDiv) parent.insert(ind, footnotesDiv)
else: else:
parent.insert(ind + 1, footnotesDiv) parent.insert(ind + 1, footnotesDiv)
child.tail = None child.tail = None
else: else:
root.append(footnotesDiv) root.append(footnotesDiv)
class FootnotePostprocessor(Postprocessor): class FootnotePostprocessor(Postprocessor):
""" Replace placeholders with html entities. """ """ Replace placeholders with html entities. """
def __init__(self, footnotes): def __init__(self, footnotes):
self.footnotes = footnotes self.footnotes = footnotes
def run(self, text): def run(self, text):
text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")) text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT"))
return text.replace(NBSP_PLACEHOLDER, "&#160;") return text.replace(NBSP_PLACEHOLDER, "&#160;")
def makeExtension(configs=[]): def makeExtension(configs=[]):
""" Return an instance of the FootnoteExtension """ """ Return an instance of the FootnoteExtension """
return FootnoteExtension(configs=configs) return FootnoteExtension(configs=configs)

View File

@@ -1,208 +1,208 @@
""" """
HeaderID Extension for Python-Markdown HeaderID Extension for Python-Markdown
====================================== ======================================
Auto-generate id attributes for HTML headers. Auto-generate id attributes for HTML headers.
Basic usage: Basic usage:
>>> import markdown >>> import markdown
>>> text = "# Some Header #" >>> text = "# Some Header #"
>>> md = markdown.markdown(text, ['headerid']) >>> md = markdown.markdown(text, ['headerid'])
>>> print md >>> print md
<h1 id="some-header">Some Header</h1> <h1 id="some-header">Some Header</h1>
All header IDs are unique: All header IDs are unique:
>>> text = ''' >>> text = '''
... #Header ... #Header
... #Header ... #Header
... #Header''' ... #Header'''
>>> md = markdown.markdown(text, ['headerid']) >>> md = markdown.markdown(text, ['headerid'])
>>> print md >>> print md
<h1 id="header">Header</h1> <h1 id="header">Header</h1>
<h1 id="header_1">Header</h1> <h1 id="header_1">Header</h1>
<h1 id="header_2">Header</h1> <h1 id="header_2">Header</h1>
To fit within a html template's hierarchy, set the header base level: To fit within a html template's hierarchy, set the header base level:
>>> text = ''' >>> text = '''
... #Some Header ... #Some Header
... ## Next Level''' ... ## Next Level'''
>>> md = markdown.markdown(text, ['headerid(level=3)']) >>> md = markdown.markdown(text, ['headerid(level=3)'])
>>> print md >>> print md
<h3 id="some-header">Some Header</h3> <h3 id="some-header">Some Header</h3>
<h4 id="next-level">Next Level</h4> <h4 id="next-level">Next Level</h4>
Works with inline markup. Works with inline markup.
>>> text = '#Some *Header* with [markup](http://example.com).' >>> text = '#Some *Header* with [markup](http://example.com).'
>>> md = markdown.markdown(text, ['headerid']) >>> md = markdown.markdown(text, ['headerid'])
>>> print md >>> print md
<h1 id="some-header-with-markup">Some <em>Header</em> with <a href="http://example.com">markup</a>.</h1> <h1 id="some-header-with-markup">Some <em>Header</em> with <a href="http://example.com">markup</a>.</h1>
Turn off auto generated IDs: Turn off auto generated IDs:
>>> text = ''' >>> text = '''
... # Some Header ... # Some Header
... # Another Header''' ... # Another Header'''
>>> md = markdown.markdown(text, ['headerid(forceid=False)']) >>> md = markdown.markdown(text, ['headerid(forceid=False)'])
>>> print md >>> print md
<h1>Some Header</h1> <h1>Some Header</h1>
<h1>Another Header</h1> <h1>Another Header</h1>
Use with MetaData extension: Use with MetaData extension:
>>> text = '''header_level: 2 >>> text = '''header_level: 2
... header_forceid: Off ... header_forceid: Off
... ...
... # A Header''' ... # A Header'''
>>> md = markdown.markdown(text, ['headerid', 'meta']) >>> md = markdown.markdown(text, ['headerid', 'meta'])
>>> print md >>> print md
<h2>A Header</h2> <h2>A Header</h2>
Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/). Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/).
Project website: <http://packages.python.org/Markdown/extensions/header_id.html> Project website: <http://packages.python.org/Markdown/extensions/header_id.html>
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
License: BSD (see ../docs/LICENSE for details) License: BSD (see ../docs/LICENSE for details)
Dependencies: Dependencies:
* [Python 2.3+](http://python.org) * [Python 2.3+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/) * [Markdown 2.0+](http://packages.python.org/Markdown/)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..treeprocessors import Treeprocessor from ..treeprocessors import Treeprocessor
from ..util import HTML_PLACEHOLDER_RE, parseBoolValue from ..util import HTML_PLACEHOLDER_RE, parseBoolValue
import re import re
import logging import logging
import unicodedata import unicodedata
logger = logging.getLogger('MARKDOWN') logger = logging.getLogger('MARKDOWN')
IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$') IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
def slugify(value, separator): def slugify(value, separator):
""" Slugify a string, to make it URL friendly. """ """ Slugify a string, to make it URL friendly. """
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = re.sub('[^\w\s-]', '', value.decode('ascii')).strip().lower() value = re.sub('[^\w\s-]', '', value.decode('ascii')).strip().lower()
return re.sub('[%s\s]+' % separator, separator, value) return re.sub('[%s\s]+' % separator, separator, value)
def unique(id, ids): def unique(id, ids):
""" Ensure id is unique in set of ids. Append '_1', '_2'... if not """ """ Ensure id is unique in set of ids. Append '_1', '_2'... if not """
while id in ids or not id: while id in ids or not id:
m = IDCOUNT_RE.match(id) m = IDCOUNT_RE.match(id)
if m: if m:
id = '%s_%d'% (m.group(1), int(m.group(2))+1) id = '%s_%d'% (m.group(1), int(m.group(2))+1)
else: else:
id = '%s_%d'% (id, 1) id = '%s_%d'% (id, 1)
ids.add(id) ids.add(id)
return id return id
def itertext(elem): def itertext(elem):
""" Loop through all children and return text only. """ Loop through all children and return text only.
Reimplements method of same name added to ElementTree in Python 2.7 Reimplements method of same name added to ElementTree in Python 2.7
""" """
if elem.text: if elem.text:
yield elem.text yield elem.text
for e in elem: for e in elem:
for s in itertext(e): for s in itertext(e):
yield s yield s
if e.tail: if e.tail:
yield e.tail yield e.tail
def stashedHTML2text(text, md): def stashedHTML2text(text, md):
""" Extract raw HTML, reduce to plain text and swap with placeholder. """ """ Extract raw HTML, reduce to plain text and swap with placeholder. """
def _html_sub(m): def _html_sub(m):
""" Substitute raw html with plain text. """ """ Substitute raw html with plain text. """
try: try:
raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))] raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
except (IndexError, TypeError): except (IndexError, TypeError):
return m.group(0) return m.group(0)
if md.safeMode and not safe: if md.safeMode and not safe:
return '' return ''
# Strip out tags and entities - leaveing text # Strip out tags and entities - leaveing text
return re.sub(r'(<[^>]+>)|(&[\#a-zA-Z0-9]+;)', '', raw) return re.sub(r'(<[^>]+>)|(&[\#a-zA-Z0-9]+;)', '', raw)
return HTML_PLACEHOLDER_RE.sub(_html_sub, text) return HTML_PLACEHOLDER_RE.sub(_html_sub, text)
class HeaderIdTreeprocessor(Treeprocessor): class HeaderIdTreeprocessor(Treeprocessor):
""" Assign IDs to headers. """ """ Assign IDs to headers. """
IDs = set() IDs = set()
def run(self, doc): def run(self, doc):
start_level, force_id = self._get_meta() start_level, force_id = self._get_meta()
slugify = self.config['slugify'] slugify = self.config['slugify']
sep = self.config['separator'] sep = self.config['separator']
for elem in doc.getiterator(): for elem in doc.getiterator():
if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']: if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
if force_id: if force_id:
if "id" in elem.attrib: if "id" in elem.attrib:
id = elem.get('id') id = elem.get('id')
else: else:
id = stashedHTML2text(''.join(itertext(elem)), self.md) id = stashedHTML2text(''.join(itertext(elem)), self.md)
id = slugify(id, sep) id = slugify(id, sep)
elem.set('id', unique(id, self.IDs)) elem.set('id', unique(id, self.IDs))
if start_level: if start_level:
level = int(elem.tag[-1]) + start_level level = int(elem.tag[-1]) + start_level
if level > 6: if level > 6:
level = 6 level = 6
elem.tag = 'h%d' % level elem.tag = 'h%d' % level
def _get_meta(self): def _get_meta(self):
""" Return meta data suported by this ext as a tuple """ """ Return meta data suported by this ext as a tuple """
level = int(self.config['level']) - 1 level = int(self.config['level']) - 1
force = parseBoolValue(self.config['forceid']) force = parseBoolValue(self.config['forceid'])
if hasattr(self.md, 'Meta'): if hasattr(self.md, 'Meta'):
if 'header_level' in self.md.Meta: if 'header_level' in self.md.Meta:
level = int(self.md.Meta['header_level'][0]) - 1 level = int(self.md.Meta['header_level'][0]) - 1
if 'header_forceid' in self.md.Meta: if 'header_forceid' in self.md.Meta:
force = parseBoolValue(self.md.Meta['header_forceid'][0]) force = parseBoolValue(self.md.Meta['header_forceid'][0])
return level, force return level, force
class HeaderIdExtension(Extension): class HeaderIdExtension(Extension):
def __init__(self, configs): def __init__(self, configs):
# set defaults # set defaults
self.config = { self.config = {
'level' : ['1', 'Base level for headers.'], 'level' : ['1', 'Base level for headers.'],
'forceid' : ['True', 'Force all headers to have an id.'], 'forceid' : ['True', 'Force all headers to have an id.'],
'separator' : ['-', 'Word separator.'], 'separator' : ['-', 'Word separator.'],
'slugify' : [slugify, 'Callable to generate anchors'], 'slugify' : [slugify, 'Callable to generate anchors'],
} }
for key, value in configs: for key, value in configs:
self.setConfig(key, value) self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
md.registerExtension(self) md.registerExtension(self)
self.processor = HeaderIdTreeprocessor() self.processor = HeaderIdTreeprocessor()
self.processor.md = md self.processor.md = md
self.processor.config = self.getConfigs() self.processor.config = self.getConfigs()
if 'attr_list' in md.treeprocessors.keys(): if 'attr_list' in md.treeprocessors.keys():
# insert after attr_list treeprocessor # insert after attr_list treeprocessor
md.treeprocessors.add('headerid', self.processor, '>attr_list') md.treeprocessors.add('headerid', self.processor, '>attr_list')
else: else:
# insert after 'prettify' treeprocessor. # insert after 'prettify' treeprocessor.
md.treeprocessors.add('headerid', self.processor, '>prettify') md.treeprocessors.add('headerid', self.processor, '>prettify')
def reset(self): def reset(self):
self.processor.IDs = set() self.processor.IDs = set()
def makeExtension(configs=None): def makeExtension(configs=None):
return HeaderIdExtension(configs=configs) return HeaderIdExtension(configs=configs)

View File

@@ -1,93 +1,93 @@
""" """
Meta Data Extension for Python-Markdown Meta Data Extension for Python-Markdown
======================================= =======================================
This extension adds Meta Data handling to markdown. This extension adds Meta Data handling to markdown.
Basic Usage: Basic Usage:
>>> import markdown >>> import markdown
>>> text = '''Title: A Test Doc. >>> text = '''Title: A Test Doc.
... Author: Waylan Limberg ... Author: Waylan Limberg
... John Doe ... John Doe
... Blank_Data: ... Blank_Data:
... ...
... The body. This is paragraph one. ... The body. This is paragraph one.
... ''' ... '''
>>> md = markdown.Markdown(['meta']) >>> md = markdown.Markdown(['meta'])
>>> print md.convert(text) >>> print md.convert(text)
<p>The body. This is paragraph one.</p> <p>The body. This is paragraph one.</p>
>>> print md.Meta >>> print md.Meta
{u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']} {u'blank_data': [u''], u'author': [u'Waylan Limberg', u'John Doe'], u'title': [u'A Test Doc.']}
Make sure text without Meta Data still works (markdown < 1.6b returns a <p>). Make sure text without Meta Data still works (markdown < 1.6b returns a <p>).
>>> text = ' Some Code - not extra lines of meta data.' >>> text = ' Some Code - not extra lines of meta data.'
>>> md = markdown.Markdown(['meta']) >>> md = markdown.Markdown(['meta'])
>>> print md.convert(text) >>> print md.convert(text)
<pre><code>Some Code - not extra lines of meta data. <pre><code>Some Code - not extra lines of meta data.
</code></pre> </code></pre>
>>> md.Meta >>> md.Meta
{} {}
Copyright 2007-2008 [Waylan Limberg](http://achinghead.com). Copyright 2007-2008 [Waylan Limberg](http://achinghead.com).
Project website: <http://packages.python.org/Markdown/meta_data.html> Project website: <http://packages.python.org/Markdown/meta_data.html>
Contact: markdown@freewisdom.org Contact: markdown@freewisdom.org
License: BSD (see ../LICENSE.md for details) License: BSD (see ../LICENSE.md for details)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..preprocessors import Preprocessor from ..preprocessors import Preprocessor
import re import re
# Global Vars # Global Vars
META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)') META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)')
META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)') META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)')
class MetaExtension (Extension): class MetaExtension (Extension):
""" Meta-Data extension for Python-Markdown. """ """ Meta-Data extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add MetaPreprocessor to Markdown instance. """ """ Add MetaPreprocessor to Markdown instance. """
md.preprocessors.add("meta", MetaPreprocessor(md), "_begin") md.preprocessors.add("meta", MetaPreprocessor(md), "_begin")
class MetaPreprocessor(Preprocessor): class MetaPreprocessor(Preprocessor):
""" Get Meta-Data. """ """ Get Meta-Data. """
def run(self, lines): def run(self, lines):
""" Parse Meta-Data and store in Markdown.Meta. """ """ Parse Meta-Data and store in Markdown.Meta. """
meta = {} meta = {}
key = None key = None
while lines: while lines:
line = lines.pop(0) line = lines.pop(0)
if line.strip() == '': if line.strip() == '':
break # blank line - done break # blank line - done
m1 = META_RE.match(line) m1 = META_RE.match(line)
if m1: if m1:
key = m1.group('key').lower().strip() key = m1.group('key').lower().strip()
value = m1.group('value').strip() value = m1.group('value').strip()
try: try:
meta[key].append(value) meta[key].append(value)
except KeyError: except KeyError:
meta[key] = [value] meta[key] = [value]
else: else:
m2 = META_MORE_RE.match(line) m2 = META_MORE_RE.match(line)
if m2 and key: if m2 and key:
# Add another line to existing key # Add another line to existing key
meta[key].append(m2.group('value').strip()) meta[key].append(m2.group('value').strip())
else: else:
lines.insert(0, line) lines.insert(0, line)
break # no meta data - done break # no meta data - done
self.markdown.Meta = meta self.markdown.Meta = meta
return lines return lines
def makeExtension(configs={}): def makeExtension(configs={}):
return MetaExtension(configs=configs) return MetaExtension(configs=configs)

View File

@@ -1,38 +1,38 @@
""" """
NL2BR Extension NL2BR Extension
=============== ===============
A Python-Markdown extension to treat newlines as hard breaks; like A Python-Markdown extension to treat newlines as hard breaks; like
GitHub-flavored Markdown does. GitHub-flavored Markdown does.
Usage: Usage:
>>> import markdown >>> import markdown
>>> print markdown.markdown('line 1\\nline 2', extensions=['nl2br']) >>> print markdown.markdown('line 1\\nline 2', extensions=['nl2br'])
<p>line 1<br /> <p>line 1<br />
line 2</p> line 2</p>
Copyright 2011 [Brian Neal](http://deathofagremmie.com/) Copyright 2011 [Brian Neal](http://deathofagremmie.com/)
Dependencies: Dependencies:
* [Python 2.4+](http://python.org) * [Python 2.4+](http://python.org)
* [Markdown 2.1+](http://packages.python.org/Markdown/) * [Markdown 2.1+](http://packages.python.org/Markdown/)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..inlinepatterns import SubstituteTagPattern from ..inlinepatterns import SubstituteTagPattern
BR_RE = r'\n' BR_RE = r'\n'
class Nl2BrExtension(Extension): class Nl2BrExtension(Extension):
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
br_tag = SubstituteTagPattern(BR_RE, 'br') br_tag = SubstituteTagPattern(BR_RE, 'br')
md.inlinePatterns.add('nl', br_tag, '_end') md.inlinePatterns.add('nl', br_tag, '_end')
def makeExtension(configs=None): def makeExtension(configs=None):
return Nl2BrExtension(configs) return Nl2BrExtension(configs)

View File

@@ -1,51 +1,51 @@
""" """
Sane List Extension for Python-Markdown Sane List Extension for Python-Markdown
======================================= =======================================
Modify the behavior of Lists in Python-Markdown t act in a sane manor. Modify the behavior of Lists in Python-Markdown t act in a sane manor.
In standard Markdown syntax, the following would constitute a single In standard Markdown syntax, the following would constitute a single
ordered list. However, with this extension, the output would include ordered list. However, with this extension, the output would include
two lists, the first an ordered list and the second and unordered list. two lists, the first an ordered list and the second and unordered list.
1. ordered 1. ordered
2. list 2. list
* unordered * unordered
* list * list
Copyright 2011 - [Waylan Limberg](http://achinghead.com) Copyright 2011 - [Waylan Limberg](http://achinghead.com)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..blockprocessors import OListProcessor, UListProcessor from ..blockprocessors import OListProcessor, UListProcessor
import re import re
class SaneOListProcessor(OListProcessor): class SaneOListProcessor(OListProcessor):
CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.))[ ]+(.*)') CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.))[ ]+(.*)')
SIBLING_TAGS = ['ol'] SIBLING_TAGS = ['ol']
class SaneUListProcessor(UListProcessor): class SaneUListProcessor(UListProcessor):
CHILD_RE = re.compile(r'^[ ]{0,3}(([*+-]))[ ]+(.*)') CHILD_RE = re.compile(r'^[ ]{0,3}(([*+-]))[ ]+(.*)')
SIBLING_TAGS = ['ul'] SIBLING_TAGS = ['ul']
class SaneListExtension(Extension): class SaneListExtension(Extension):
""" Add sane lists to Markdown. """ """ Add sane lists to Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Override existing Processors. """ """ Override existing Processors. """
md.parser.blockprocessors['olist'] = SaneOListProcessor(md.parser) md.parser.blockprocessors['olist'] = SaneOListProcessor(md.parser)
md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser) md.parser.blockprocessors['ulist'] = SaneUListProcessor(md.parser)
def makeExtension(configs={}): def makeExtension(configs={}):
return SaneListExtension(configs=configs) return SaneListExtension(configs=configs)

View File

@@ -1,42 +1,42 @@
''' '''
Smart_Strong Extension for Python-Markdown Smart_Strong Extension for Python-Markdown
========================================== ==========================================
This extention adds smarter handling of double underscores within words. This extention adds smarter handling of double underscores within words.
Simple Usage: Simple Usage:
>>> import markdown >>> import markdown
>>> print markdown.markdown('Text with double__underscore__words.', >>> print markdown.markdown('Text with double__underscore__words.',
... extensions=['smart_strong']) ... extensions=['smart_strong'])
<p>Text with double__underscore__words.</p> <p>Text with double__underscore__words.</p>
>>> print markdown.markdown('__Strong__ still works.', >>> print markdown.markdown('__Strong__ still works.',
... extensions=['smart_strong']) ... extensions=['smart_strong'])
<p><strong>Strong</strong> still works.</p> <p><strong>Strong</strong> still works.</p>
>>> print markdown.markdown('__this__works__too__.', >>> print markdown.markdown('__this__works__too__.',
... extensions=['smart_strong']) ... extensions=['smart_strong'])
<p><strong>this__works__too</strong>.</p> <p><strong>this__works__too</strong>.</p>
Copyright 2011 Copyright 2011
[Waylan Limberg](http://achinghead.com) [Waylan Limberg](http://achinghead.com)
''' '''
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..inlinepatterns import SimpleTagPattern from ..inlinepatterns import SimpleTagPattern
SMART_STRONG_RE = r'(?<!\w)(_{2})(?!_)(.+?)(?<!_)\2(?!\w)' SMART_STRONG_RE = r'(?<!\w)(_{2})(?!_)(.+?)(?<!_)\2(?!\w)'
STRONG_RE = r'(\*{2})(.+?)\2' STRONG_RE = r'(\*{2})(.+?)\2'
class SmartEmphasisExtension(Extension): class SmartEmphasisExtension(Extension):
""" Add smart_emphasis extension to Markdown class.""" """ Add smart_emphasis extension to Markdown class."""
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Modify inline patterns. """ """ Modify inline patterns. """
md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong') md.inlinePatterns['strong'] = SimpleTagPattern(STRONG_RE, 'strong')
md.inlinePatterns.add('strong2', SimpleTagPattern(SMART_STRONG_RE, 'strong'), '>emphasis2') md.inlinePatterns.add('strong2', SimpleTagPattern(SMART_STRONG_RE, 'strong'), '>emphasis2')
def makeExtension(configs={}): def makeExtension(configs={}):
return SmartEmphasisExtension(configs=dict(configs)) return SmartEmphasisExtension(configs=dict(configs))

View File

@@ -1,191 +1,191 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Smarty extension for Python-Markdown # Smarty extension for Python-Markdown
# Author: 2013, Dmitry Shachnev <mitya57@gmail.com> # Author: 2013, Dmitry Shachnev <mitya57@gmail.com>
# SmartyPants license: # SmartyPants license:
# #
# Copyright (c) 2003 John Gruber <http://daringfireball.net/> # Copyright (c) 2003 John Gruber <http://daringfireball.net/>
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are # modification, are permitted provided that the following conditions are
# met: # met:
# #
# * Redistributions of source code must retain the above copyright # * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer. # notice, this list of conditions and the following disclaimer.
# #
# * Redistributions in binary form must reproduce the above copyright # * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in # notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the # the documentation and/or other materials provided with the
# distribution. # distribution.
# #
# * Neither the name "SmartyPants" nor the names of its contributors # * Neither the name "SmartyPants" nor the names of its contributors
# may be used to endorse or promote products derived from this # may be used to endorse or promote products derived from this
# software without specific prior written permission. # software without specific prior written permission.
# #
# This software is provided by the copyright holders and contributors "as # This software is provided by the copyright holders and contributors "as
# is" and any express or implied warranties, including, but not limited # is" and any express or implied warranties, including, but not limited
# to, the implied warranties of merchantability and fitness for a # to, the implied warranties of merchantability and fitness for a
# particular purpose are disclaimed. In no event shall the copyright # particular purpose are disclaimed. In no event shall the copyright
# owner or contributors be liable for any direct, indirect, incidental, # owner or contributors be liable for any direct, indirect, incidental,
# special, exemplary, or consequential damages (including, but not # special, exemplary, or consequential damages (including, but not
# limited to, procurement of substitute goods or services; loss of use, # limited to, procurement of substitute goods or services; loss of use,
# data, or profits; or business interruption) however caused and on any # data, or profits; or business interruption) however caused and on any
# theory of liability, whether in contract, strict liability, or tort # theory of liability, whether in contract, strict liability, or tort
# (including negligence or otherwise) arising in any way out of the use # (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage. # of this software, even if advised of the possibility of such damage.
# #
# #
# smartypants.py license: # smartypants.py license:
# #
# smartypants.py is a derivative work of SmartyPants. # smartypants.py is a derivative work of SmartyPants.
# Copyright (c) 2004, 2007 Chad Miller <http://web.chad.org/> # Copyright (c) 2004, 2007 Chad Miller <http://web.chad.org/>
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are # modification, are permitted provided that the following conditions are
# met: # met:
# #
# * Redistributions of source code must retain the above copyright # * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer. # notice, this list of conditions and the following disclaimer.
# #
# * Redistributions in binary form must reproduce the above copyright # * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in # notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the # the documentation and/or other materials provided with the
# distribution. # distribution.
# #
# This software is provided by the copyright holders and contributors "as # This software is provided by the copyright holders and contributors "as
# is" and any express or implied warranties, including, but not limited # is" and any express or implied warranties, including, but not limited
# to, the implied warranties of merchantability and fitness for a # to, the implied warranties of merchantability and fitness for a
# particular purpose are disclaimed. In no event shall the copyright # particular purpose are disclaimed. In no event shall the copyright
# owner or contributors be liable for any direct, indirect, incidental, # owner or contributors be liable for any direct, indirect, incidental,
# special, exemplary, or consequential damages (including, but not # special, exemplary, or consequential damages (including, but not
# limited to, procurement of substitute goods or services; loss of use, # limited to, procurement of substitute goods or services; loss of use,
# data, or profits; or business interruption) however caused and on any # data, or profits; or business interruption) however caused and on any
# theory of liability, whether in contract, strict liability, or tort # theory of liability, whether in contract, strict liability, or tort
# (including negligence or otherwise) arising in any way out of the use # (including negligence or otherwise) arising in any way out of the use
# of this software, even if advised of the possibility of such damage. # of this software, even if advised of the possibility of such damage.
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..inlinepatterns import HtmlPattern from ..inlinepatterns import HtmlPattern
from ..util import parseBoolValue from ..util import parseBoolValue
# Constants for quote education. # Constants for quote education.
punctClass = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]""" punctClass = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]"""
endOfWordClass = r"[\s.,;:!?)]" endOfWordClass = r"[\s.,;:!?)]"
closeClass = r"[^\ \t\r\n\[\{\(\-\u0002\u0003]" closeClass = "[^\ \t\r\n\[\{\(\-\u0002\u0003]"
openingQuotesBase = ( openingQuotesBase = (
'(\s' # a whitespace char '(\s' # a whitespace char
'|&nbsp;' # or a non-breaking space entity '|&nbsp;' # or a non-breaking space entity
'|--' # or dashes '|--' # or dashes
'||—' # or unicode '||—' # or unicode
'|&[mn]dash;' # or named dash entities '|&[mn]dash;' # or named dash entities
'|&#8211;|&#8212;' # or decimal entities '|&#8211;|&#8212;' # or decimal entities
')' ')'
) )
# Special case if the very first character is a quote # Special case if the very first character is a quote
# followed by punctuation at a non-word-break. Close the quotes by brute force: # followed by punctuation at a non-word-break. Close the quotes by brute force:
singleQuoteStartRe = r"^'(?=%s\\B)" % punctClass singleQuoteStartRe = r"^'(?=%s\\B)" % punctClass
doubleQuoteStartRe = r'^"(?=%s\\B)' % punctClass doubleQuoteStartRe = r'^"(?=%s\\B)' % punctClass
# Special case for double sets of quotes, e.g.: # Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p> # <p>He said, "'Quoted' words in a larger quote."</p>
doubleQuoteSetsRe = r""""'(?=\w)""" doubleQuoteSetsRe = r""""'(?=\w)"""
singleQuoteSetsRe = r"""'"(?=\w)""" singleQuoteSetsRe = r"""'"(?=\w)"""
# Get most opening double quotes: # Get most opening double quotes:
openingDoubleQuotesRegex = r'%s"(?=\w)' % openingQuotesBase openingDoubleQuotesRegex = r'%s"(?=\w)' % openingQuotesBase
# Double closing quotes: # Double closing quotes:
closingDoubleQuotesRegex = r'"(?=\s)' closingDoubleQuotesRegex = r'"(?=\s)'
closingDoubleQuotesRegex2 = '(?<=%s)"' % closeClass closingDoubleQuotesRegex2 = '(?<=%s)"' % closeClass
# Get most opening single quotes: # Get most opening single quotes:
openingSingleQuotesRegex = r"%s'(?=\w)" % openingQuotesBase openingSingleQuotesRegex = r"%s'(?=\w)" % openingQuotesBase
# Single closing quotes: # Single closing quotes:
closingSingleQuotesRegex = r"(?<=%s)'(?!\s|s\b|\d)" % closeClass closingSingleQuotesRegex = r"(?<=%s)'(?!\s|s\b|\d)" % closeClass
closingSingleQuotesRegex2 = r"(?<=%s)'(\s|s\b)" % closeClass closingSingleQuotesRegex2 = r"(?<=%s)'(\s|s\b)" % closeClass
# All remaining quotes should be opening ones # All remaining quotes should be opening ones
remainingSingleQuotesRegex = "'" remainingSingleQuotesRegex = "'"
remainingDoubleQuotesRegex = '"' remainingDoubleQuotesRegex = '"'
lsquo, rsquo, ldquo, rdquo = '&lsquo;', '&rsquo;', '&ldquo;', '&rdquo;' lsquo, rsquo, ldquo, rdquo = '&lsquo;', '&rsquo;', '&ldquo;', '&rdquo;'
class SubstituteTextPattern(HtmlPattern): class SubstituteTextPattern(HtmlPattern):
def __init__(self, pattern, replace, markdown_instance): def __init__(self, pattern, replace, markdown_instance):
""" Replaces matches with some text. """ """ Replaces matches with some text. """
HtmlPattern.__init__(self, pattern) HtmlPattern.__init__(self, pattern)
self.replace = replace self.replace = replace
self.markdown = markdown_instance self.markdown = markdown_instance
def handleMatch(self, m): def handleMatch(self, m):
result = '' result = ''
for part in self.replace: for part in self.replace:
if isinstance(part, int): if isinstance(part, int):
result += m.group(part) result += m.group(part)
else: else:
result += self.markdown.htmlStash.store(part, safe=True) result += self.markdown.htmlStash.store(part, safe=True)
return result return result
class SmartyExtension(Extension): class SmartyExtension(Extension):
def __init__(self, configs): def __init__(self, configs):
self.config = { self.config = {
'smart_quotes': [True, 'Educate quotes'], 'smart_quotes': [True, 'Educate quotes'],
'smart_dashes': [True, 'Educate dashes'], 'smart_dashes': [True, 'Educate dashes'],
'smart_ellipses': [True, 'Educate ellipses'] 'smart_ellipses': [True, 'Educate ellipses']
} }
for key, value in configs: for key, value in configs:
self.setConfig(key, parseBoolValue(value)) self.setConfig(key, parseBoolValue(value))
def _addPatterns(self, md, patterns, serie): def _addPatterns(self, md, patterns, serie):
for ind, pattern in enumerate(patterns): for ind, pattern in enumerate(patterns):
pattern += (md,) pattern += (md,)
pattern = SubstituteTextPattern(*pattern) pattern = SubstituteTextPattern(*pattern)
after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '>entity') after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '>entity')
name = 'smarty-%s-%d' % (serie, ind) name = 'smarty-%s-%d' % (serie, ind)
md.inlinePatterns.add(name, pattern, after) md.inlinePatterns.add(name, pattern, after)
def educateDashes(self, md): def educateDashes(self, md):
emDashesPattern = SubstituteTextPattern(r'(?<!-)---(?!-)', '&mdash;', md) emDashesPattern = SubstituteTextPattern(r'(?<!-)---(?!-)', ('&mdash;',), md)
enDashesPattern = SubstituteTextPattern(r'(?<!-)--(?!-)', '&ndash;', md) enDashesPattern = SubstituteTextPattern(r'(?<!-)--(?!-)', ('&ndash;',), md)
md.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '>entity') md.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '>entity')
md.inlinePatterns.add('smarty-en-dashes', enDashesPattern, md.inlinePatterns.add('smarty-en-dashes', enDashesPattern,
'>smarty-em-dashes') '>smarty-em-dashes')
def educateEllipses(self, md): def educateEllipses(self, md):
ellipsesPattern = SubstituteTextPattern(r'(?<!\.)\.{3}(?!\.)', '&hellip;', md) ellipsesPattern = SubstituteTextPattern(r'(?<!\.)\.{3}(?!\.)', ('&hellip;',), md)
md.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '>entity') md.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '>entity')
def educateQuotes(self, md): def educateQuotes(self, md):
patterns = ( patterns = (
(singleQuoteStartRe, (rsquo,)), (singleQuoteStartRe, (rsquo,)),
(doubleQuoteStartRe, (rdquo,)), (doubleQuoteStartRe, (rdquo,)),
(doubleQuoteSetsRe, (ldquo + lsquo,)), (doubleQuoteSetsRe, (ldquo + lsquo,)),
(singleQuoteSetsRe, (lsquo + ldquo,)), (singleQuoteSetsRe, (lsquo + ldquo,)),
(openingSingleQuotesRegex, (2, lsquo)), (openingSingleQuotesRegex, (2, lsquo)),
(closingSingleQuotesRegex, (rsquo,)), (closingSingleQuotesRegex, (rsquo,)),
(closingSingleQuotesRegex2, (rsquo, 2)), (closingSingleQuotesRegex2, (rsquo, 2)),
(remainingSingleQuotesRegex, (lsquo,)), (remainingSingleQuotesRegex, (lsquo,)),
(openingDoubleQuotesRegex, (2, ldquo)), (openingDoubleQuotesRegex, (2, ldquo)),
(closingDoubleQuotesRegex, (rdquo,)), (closingDoubleQuotesRegex, (rdquo,)),
(closingDoubleQuotesRegex2, (rdquo,)), (closingDoubleQuotesRegex2, (rdquo,)),
(remainingDoubleQuotesRegex, (ldquo,)) (remainingDoubleQuotesRegex, (ldquo,))
) )
self._addPatterns(md, patterns, 'quotes') self._addPatterns(md, patterns, 'quotes')
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
configs = self.getConfigs() configs = self.getConfigs()
if configs['smart_quotes']: if configs['smart_quotes']:
self.educateQuotes(md) self.educateQuotes(md)
if configs['smart_dashes']: if configs['smart_dashes']:
self.educateDashes(md) self.educateDashes(md)
if configs['smart_ellipses']: if configs['smart_ellipses']:
self.educateEllipses(md) self.educateEllipses(md)
md.ESCAPED_CHARS.extend(['"', "'"]) md.ESCAPED_CHARS.extend(['"', "'"])
def makeExtension(configs=None): def makeExtension(configs=None):
return SmartyExtension(configs) return SmartyExtension(configs)

View File

@@ -1,100 +1,100 @@
""" """
Tables Extension for Python-Markdown Tables Extension for Python-Markdown
==================================== ====================================
Added parsing of tables to Python-Markdown. Added parsing of tables to Python-Markdown.
A simple example: A simple example:
First Header | Second Header First Header | Second Header
------------- | ------------- ------------- | -------------
Content Cell | Content Cell Content Cell | Content Cell
Content Cell | Content Cell Content Cell | Content Cell
Copyright 2009 - [Waylan Limberg](http://achinghead.com) Copyright 2009 - [Waylan Limberg](http://achinghead.com)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..blockprocessors import BlockProcessor from ..blockprocessors import BlockProcessor
from ..util import etree from ..util import etree
class TableProcessor(BlockProcessor): class TableProcessor(BlockProcessor):
""" Process Tables. """ """ Process Tables. """
def test(self, parent, block): def test(self, parent, block):
rows = block.split('\n') rows = block.split('\n')
return (len(rows) > 2 and '|' in rows[0] and return (len(rows) > 2 and '|' in rows[0] and
'|' in rows[1] and '-' in rows[1] and '|' in rows[1] and '-' in rows[1] and
rows[1].strip()[0] in ['|', ':', '-']) rows[1].strip()[0] in ['|', ':', '-'])
def run(self, parent, blocks): def run(self, parent, blocks):
""" Parse a table block and build table. """ """ Parse a table block and build table. """
block = blocks.pop(0).split('\n') block = blocks.pop(0).split('\n')
header = block[0].strip() header = block[0].strip()
seperator = block[1].strip() seperator = block[1].strip()
rows = block[2:] rows = block[2:]
# Get format type (bordered by pipes or not) # Get format type (bordered by pipes or not)
border = False border = False
if header.startswith('|'): if header.startswith('|'):
border = True border = True
# Get alignment of columns # Get alignment of columns
align = [] align = []
for c in self._split_row(seperator, border): for c in self._split_row(seperator, border):
if c.startswith(':') and c.endswith(':'): if c.startswith(':') and c.endswith(':'):
align.append('center') align.append('center')
elif c.startswith(':'): elif c.startswith(':'):
align.append('left') align.append('left')
elif c.endswith(':'): elif c.endswith(':'):
align.append('right') align.append('right')
else: else:
align.append(None) align.append(None)
# Build table # Build table
table = etree.SubElement(parent, 'table') table = etree.SubElement(parent, 'table')
thead = etree.SubElement(table, 'thead') thead = etree.SubElement(table, 'thead')
self._build_row(header, thead, align, border) self._build_row(header, thead, align, border)
tbody = etree.SubElement(table, 'tbody') tbody = etree.SubElement(table, 'tbody')
for row in rows: for row in rows:
self._build_row(row.strip(), tbody, align, border) self._build_row(row.strip(), tbody, align, border)
def _build_row(self, row, parent, align, border): def _build_row(self, row, parent, align, border):
""" Given a row of text, build table cells. """ """ Given a row of text, build table cells. """
tr = etree.SubElement(parent, 'tr') tr = etree.SubElement(parent, 'tr')
tag = 'td' tag = 'td'
if parent.tag == 'thead': if parent.tag == 'thead':
tag = 'th' tag = 'th'
cells = self._split_row(row, border) cells = self._split_row(row, border)
# We use align here rather than cells to ensure every row # We use align here rather than cells to ensure every row
# contains the same number of columns. # contains the same number of columns.
for i, a in enumerate(align): for i, a in enumerate(align):
c = etree.SubElement(tr, tag) c = etree.SubElement(tr, tag)
try: try:
c.text = cells[i].strip() c.text = cells[i].strip()
except IndexError: except IndexError:
c.text = "" c.text = ""
if a: if a:
c.set('align', a) c.set('align', a)
def _split_row(self, row, border): def _split_row(self, row, border):
""" split a row of text into list of cells. """ """ split a row of text into list of cells. """
if border: if border:
if row.startswith('|'): if row.startswith('|'):
row = row[1:] row = row[1:]
if row.endswith('|'): if row.endswith('|'):
row = row[:-1] row = row[:-1]
return row.split('|') return row.split('|')
class TableExtension(Extension): class TableExtension(Extension):
""" Add tables to Markdown. """ """ Add tables to Markdown. """
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
""" Add an instance of TableProcessor to BlockParser. """ """ Add an instance of TableProcessor to BlockParser. """
md.parser.blockprocessors.add('table', md.parser.blockprocessors.add('table',
TableProcessor(md.parser), TableProcessor(md.parser),
'<hashheader') '<hashheader')
def makeExtension(configs={}): def makeExtension(configs={}):
return TableExtension(configs=configs) return TableExtension(configs=configs)

View File

@@ -1,240 +1,240 @@
""" """
Table of Contents Extension for Python-Markdown Table of Contents Extension for Python-Markdown
* * * * * *
(c) 2008 [Jack Miller](http://codezen.org) (c) 2008 [Jack Miller](http://codezen.org)
Dependencies: Dependencies:
* [Markdown 2.1+](http://packages.python.org/Markdown/) * [Markdown 2.1+](http://packages.python.org/Markdown/)
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..treeprocessors import Treeprocessor from ..treeprocessors import Treeprocessor
from ..util import etree, parseBoolValue, AMP_SUBSTITUTE from ..util import etree, parseBoolValue, AMP_SUBSTITUTE
from .headerid import slugify, unique, itertext, stashedHTML2text from .headerid import slugify, unique, itertext, stashedHTML2text
import re import re
def order_toc_list(toc_list): def order_toc_list(toc_list):
"""Given an unsorted list with errors and skips, return a nested one. """Given an unsorted list with errors and skips, return a nested one.
[{'level': 1}, {'level': 2}] [{'level': 1}, {'level': 2}]
=> =>
[{'level': 1, 'children': [{'level': 2, 'children': []}]}] [{'level': 1, 'children': [{'level': 2, 'children': []}]}]
A wrong list is also converted: A wrong list is also converted:
[{'level': 2}, {'level': 1}] [{'level': 2}, {'level': 1}]
=> =>
[{'level': 2, 'children': []}, {'level': 1, 'children': []}] [{'level': 2, 'children': []}, {'level': 1, 'children': []}]
""" """
def build_correct(remaining_list, prev_elements=[{'level': 1000}]): def build_correct(remaining_list, prev_elements=[{'level': 1000}]):
if not remaining_list: if not remaining_list:
return [], [] return [], []
current = remaining_list.pop(0) current = remaining_list.pop(0)
if not 'children' in current.keys(): if not 'children' in current.keys():
current['children'] = [] current['children'] = []
if not prev_elements: if not prev_elements:
# This happens for instance with [8, 1, 1], ie. when some # This happens for instance with [8, 1, 1], ie. when some
# header level is outside a scope. We treat it as a # header level is outside a scope. We treat it as a
# top-level # top-level
next_elements, children = build_correct(remaining_list, [current]) next_elements, children = build_correct(remaining_list, [current])
current['children'].append(children) current['children'].append(children)
return [current] + next_elements, [] return [current] + next_elements, []
prev_element = prev_elements.pop() prev_element = prev_elements.pop()
children = [] children = []
next_elements = [] next_elements = []
# Is current part of the child list or next list? # Is current part of the child list or next list?
if current['level'] > prev_element['level']: if current['level'] > prev_element['level']:
#print "%d is a child of %d" % (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(prev_element)
prev_elements.append(current) prev_elements.append(current)
prev_element['children'].append(current) prev_element['children'].append(current)
next_elements2, children2 = build_correct(remaining_list, prev_elements) next_elements2, children2 = build_correct(remaining_list, prev_elements)
children += children2 children += children2
next_elements += next_elements2 next_elements += next_elements2
else: else:
#print "%d is ancestor of %d" % (current['level'], prev_element['level']) #print "%d is ancestor of %d" % (current['level'], prev_element['level'])
if not prev_elements: if not prev_elements:
#print "No previous elements, so appending to the next set" #print "No previous elements, so appending to the next set"
next_elements.append(current) next_elements.append(current)
prev_elements = [current] prev_elements = [current]
next_elements2, children2 = build_correct(remaining_list, prev_elements) next_elements2, children2 = build_correct(remaining_list, prev_elements)
current['children'].extend(children2) current['children'].extend(children2)
else: else:
#print "Previous elements, comparing to those first" #print "Previous elements, comparing to those first"
remaining_list.insert(0, current) remaining_list.insert(0, current)
next_elements2, children2 = build_correct(remaining_list, prev_elements) next_elements2, children2 = build_correct(remaining_list, prev_elements)
children.extend(children2) children.extend(children2)
next_elements += next_elements2 next_elements += next_elements2
return next_elements, children return next_elements, children
ordered_list, __ = build_correct(toc_list) ordered_list, __ = build_correct(toc_list)
return ordered_list return ordered_list
class TocTreeprocessor(Treeprocessor): class TocTreeprocessor(Treeprocessor):
# Iterator wrapper to get parent and child all at once # Iterator wrapper to get parent and child all at once
def iterparent(self, root): def iterparent(self, root):
for parent in root.getiterator(): for parent in root.getiterator():
for child in parent: for child in parent:
yield parent, child yield parent, child
def add_anchor(self, c, elem_id): #@ReservedAssignment def add_anchor(self, c, elem_id): #@ReservedAssignment
anchor = etree.Element("a") anchor = etree.Element("a")
anchor.text = c.text anchor.text = c.text
anchor.attrib["href"] = "#" + elem_id anchor.attrib["href"] = "#" + elem_id
anchor.attrib["class"] = "toclink" anchor.attrib["class"] = "toclink"
c.text = "" c.text = ""
for elem in c.getchildren(): for elem in c.getchildren():
anchor.append(elem) anchor.append(elem)
c.remove(elem) c.remove(elem)
c.append(anchor) c.append(anchor)
def add_permalink(self, c, elem_id): def add_permalink(self, c, elem_id):
permalink = etree.Element("a") permalink = etree.Element("a")
permalink.text = ("%spara;" % AMP_SUBSTITUTE permalink.text = ("%spara;" % AMP_SUBSTITUTE
if self.use_permalinks is True else self.use_permalinks) if self.use_permalinks is True else self.use_permalinks)
permalink.attrib["href"] = "#" + elem_id permalink.attrib["href"] = "#" + elem_id
permalink.attrib["class"] = "headerlink" permalink.attrib["class"] = "headerlink"
permalink.attrib["title"] = "Permanent link" permalink.attrib["title"] = "Permanent link"
c.append(permalink) c.append(permalink)
def build_toc_etree(self, div, toc_list): def build_toc_etree(self, div, toc_list):
# Add title to the div # Add title to the div
if self.config["title"]: if self.config["title"]:
header = etree.SubElement(div, "span") header = etree.SubElement(div, "span")
header.attrib["class"] = "toctitle" header.attrib["class"] = "toctitle"
header.text = self.config["title"] header.text = self.config["title"]
def build_etree_ul(toc_list, parent): def build_etree_ul(toc_list, parent):
ul = etree.SubElement(parent, "ul") ul = etree.SubElement(parent, "ul")
for item in toc_list: for item in toc_list:
# List item link, to be inserted into the toc div # List item link, to be inserted into the toc div
li = etree.SubElement(ul, "li") li = etree.SubElement(ul, "li")
link = etree.SubElement(li, "a") link = etree.SubElement(li, "a")
link.text = item.get('name', '') link.text = item.get('name', '')
link.attrib["href"] = '#' + item.get('id', '') link.attrib["href"] = '#' + item.get('id', '')
if item['children']: if item['children']:
build_etree_ul(item['children'], li) build_etree_ul(item['children'], li)
return ul return ul
return build_etree_ul(toc_list, div) return build_etree_ul(toc_list, div)
def run(self, doc): def run(self, doc):
div = etree.Element("div") div = etree.Element("div")
div.attrib["class"] = "toc" div.attrib["class"] = "toc"
header_rgx = re.compile("[Hh][123456]") header_rgx = re.compile("[Hh][123456]")
self.use_anchors = parseBoolValue(self.config["anchorlink"]) self.use_anchors = parseBoolValue(self.config["anchorlink"])
self.use_permalinks = parseBoolValue(self.config["permalink"], False) self.use_permalinks = parseBoolValue(self.config["permalink"], False)
if self.use_permalinks is None: if self.use_permalinks is None:
self.use_permalinks = self.config["permalink"] self.use_permalinks = self.config["permalink"]
# Get a list of id attributes # Get a list of id attributes
used_ids = set() used_ids = set()
for c in doc.getiterator(): for c in doc.getiterator():
if "id" in c.attrib: if "id" in c.attrib:
used_ids.add(c.attrib["id"]) used_ids.add(c.attrib["id"])
toc_list = [] toc_list = []
marker_found = False marker_found = False
for (p, c) in self.iterparent(doc): for (p, c) in self.iterparent(doc):
text = ''.join(itertext(c)).strip() text = ''.join(itertext(c)).strip()
if not text: if not text:
continue continue
# To keep the output from screwing up the # To keep the output from screwing up the
# validation by putting a <div> inside of a <p> # validation by putting a <div> inside of a <p>
# we actually replace the <p> in its entirety. # we actually replace the <p> in its entirety.
# We do not allow the marker inside a header as that # We do not allow the marker inside a header as that
# would causes an enless loop of placing a new TOC # would causes an enless loop of placing a new TOC
# inside previously generated TOC. # inside previously generated TOC.
if c.text and c.text.strip() == self.config["marker"] and \ if c.text and c.text.strip() == self.config["marker"] and \
not header_rgx.match(c.tag) and c.tag not in ['pre', 'code']: not header_rgx.match(c.tag) and c.tag not in ['pre', 'code']:
for i in range(len(p)): for i in range(len(p)):
if p[i] == c: if p[i] == c:
p[i] = div p[i] = div
break break
marker_found = True marker_found = True
if header_rgx.match(c.tag): if header_rgx.match(c.tag):
# Do not override pre-existing ids # Do not override pre-existing ids
if not "id" in c.attrib: if not "id" in c.attrib:
elem_id = stashedHTML2text(text, self.markdown) elem_id = stashedHTML2text(text, self.markdown)
elem_id = unique(self.config["slugify"](elem_id, '-'), used_ids) elem_id = unique(self.config["slugify"](elem_id, '-'), used_ids)
c.attrib["id"] = elem_id c.attrib["id"] = elem_id
else: else:
elem_id = c.attrib["id"] elem_id = c.attrib["id"]
tag_level = int(c.tag[-1]) tag_level = int(c.tag[-1])
toc_list.append({'level': tag_level, toc_list.append({'level': tag_level,
'id': elem_id, 'id': elem_id,
'name': text}) 'name': text})
if self.use_anchors: if self.use_anchors:
self.add_anchor(c, elem_id) self.add_anchor(c, elem_id)
if self.use_permalinks: if self.use_permalinks:
self.add_permalink(c, elem_id) self.add_permalink(c, elem_id)
toc_list_nested = order_toc_list(toc_list) toc_list_nested = order_toc_list(toc_list)
self.build_toc_etree(div, toc_list_nested) self.build_toc_etree(div, toc_list_nested)
prettify = self.markdown.treeprocessors.get('prettify') prettify = self.markdown.treeprocessors.get('prettify')
if prettify: prettify.run(div) if prettify: prettify.run(div)
if not marker_found: if not marker_found:
# serialize and attach to markdown instance. # serialize and attach to markdown instance.
toc = self.markdown.serializer(div) toc = self.markdown.serializer(div)
for pp in self.markdown.postprocessors.values(): for pp in self.markdown.postprocessors.values():
toc = pp.run(toc) toc = pp.run(toc)
self.markdown.toc = toc self.markdown.toc = toc
class TocExtension(Extension): class TocExtension(Extension):
TreeProcessorClass = TocTreeprocessor TreeProcessorClass = TocTreeprocessor
def __init__(self, configs=[]): def __init__(self, configs=[]):
self.config = { "marker" : ["[TOC]", self.config = { "marker" : ["[TOC]",
"Text to find and replace with Table of Contents -" "Text to find and replace with Table of Contents -"
"Defaults to \"[TOC]\""], "Defaults to \"[TOC]\""],
"slugify" : [slugify, "slugify" : [slugify,
"Function to generate anchors based on header text-" "Function to generate anchors based on header text-"
"Defaults to the headerid ext's slugify function."], "Defaults to the headerid ext's slugify function."],
"title" : [None, "title" : [None,
"Title to insert into TOC <div> - " "Title to insert into TOC <div> - "
"Defaults to None"], "Defaults to None"],
"anchorlink" : [0, "anchorlink" : [0,
"1 if header should be a self link" "1 if header should be a self link"
"Defaults to 0"], "Defaults to 0"],
"permalink" : [0, "permalink" : [0,
"1 or link text if a Sphinx-style permalink should be added", "1 or link text if a Sphinx-style permalink should be added",
"Defaults to 0"] "Defaults to 0"]
} }
for key, value in configs: for key, value in configs:
self.setConfig(key, value) self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
tocext = self.TreeProcessorClass(md) tocext = self.TreeProcessorClass(md)
tocext.config = self.getConfigs() tocext.config = self.getConfigs()
# Headerid ext is set to '>prettify'. With this set to '_end', # Headerid ext is set to '>prettify'. With this set to '_end',
# it should always come after headerid ext (and honor ids assinged # it should always come after headerid ext (and honor ids assinged
# by the header id extension) if both are used. Same goes for # by the header id extension) if both are used. Same goes for
# attr_list extension. This must come last because we don't want # 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. # to redefine ids after toc is created. But we do want toc prettified.
md.treeprocessors.add("toc", tocext, "_end") md.treeprocessors.add("toc", tocext, "_end")
def makeExtension(configs={}): def makeExtension(configs={}):
return TocExtension(configs=configs) return TocExtension(configs=configs)

View File

@@ -1,151 +1,151 @@
''' '''
WikiLinks Extension for Python-Markdown WikiLinks Extension for Python-Markdown
====================================== ======================================
Converts [[WikiLinks]] to relative links. Requires Python-Markdown 2.0+ Converts [[WikiLinks]] to relative links. Requires Python-Markdown 2.0+
Basic usage: Basic usage:
>>> import markdown >>> import markdown
>>> text = "Some text with a [[WikiLink]]." >>> text = "Some text with a [[WikiLink]]."
>>> html = markdown.markdown(text, ['wikilinks']) >>> html = markdown.markdown(text, ['wikilinks'])
>>> print html >>> print html
<p>Some text with a <a class="wikilink" href="/WikiLink/">WikiLink</a>.</p> <p>Some text with a <a class="wikilink" href="/WikiLink/">WikiLink</a>.</p>
Whitespace behavior: Whitespace behavior:
>>> print markdown.markdown('[[ foo bar_baz ]]', ['wikilinks']) >>> print markdown.markdown('[[ foo bar_baz ]]', ['wikilinks'])
<p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p> <p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p>
>>> print markdown.markdown('foo [[ ]] bar', ['wikilinks']) >>> print markdown.markdown('foo [[ ]] bar', ['wikilinks'])
<p>foo bar</p> <p>foo bar</p>
To define custom settings the simple way: To define custom settings the simple way:
>>> print markdown.markdown(text, >>> print markdown.markdown(text,
... ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)'] ... ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']
... ) ... )
<p>Some text with a <a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p> <p>Some text with a <a class="foo" href="/wiki/WikiLink.html">WikiLink</a>.</p>
Custom settings the complex way: Custom settings the complex way:
>>> md = markdown.Markdown( >>> md = markdown.Markdown(
... extensions = ['wikilinks'], ... extensions = ['wikilinks'],
... extension_configs = {'wikilinks': [ ... extension_configs = {'wikilinks': [
... ('base_url', 'http://example.com/'), ... ('base_url', 'http://example.com/'),
... ('end_url', '.html'), ... ('end_url', '.html'),
... ('html_class', '') ]}, ... ('html_class', '') ]},
... safe_mode = True) ... safe_mode = True)
>>> print md.convert(text) >>> print md.convert(text)
<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p> <p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>
Use MetaData with mdx_meta.py (Note the blank html_class in MetaData): Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
>>> text = """wiki_base_url: http://example.com/ >>> text = """wiki_base_url: http://example.com/
... wiki_end_url: .html ... wiki_end_url: .html
... wiki_html_class: ... wiki_html_class:
... ...
... Some text with a [[WikiLink]].""" ... Some text with a [[WikiLink]]."""
>>> md = markdown.Markdown(extensions=['meta', 'wikilinks']) >>> md = markdown.Markdown(extensions=['meta', 'wikilinks'])
>>> print md.convert(text) >>> print md.convert(text)
<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p> <p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>
MetaData should not carry over to next document: MetaData should not carry over to next document:
>>> print md.convert("No [[MetaData]] here.") >>> print md.convert("No [[MetaData]] here.")
<p>No <a class="wikilink" href="/MetaData/">MetaData</a> here.</p> <p>No <a class="wikilink" href="/MetaData/">MetaData</a> here.</p>
Define a custom URL builder: Define a custom URL builder:
>>> def my_url_builder(label, base, end): >>> def my_url_builder(label, base, end):
... return '/bar/' ... return '/bar/'
>>> md = markdown.Markdown(extensions=['wikilinks'], >>> md = markdown.Markdown(extensions=['wikilinks'],
... extension_configs={'wikilinks' : [('build_url', my_url_builder)]}) ... extension_configs={'wikilinks' : [('build_url', my_url_builder)]})
>>> print md.convert('[[foo]]') >>> print md.convert('[[foo]]')
<p><a class="wikilink" href="/bar/">foo</a></p> <p><a class="wikilink" href="/bar/">foo</a></p>
From the command line: From the command line:
python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt
By [Waylan Limberg](http://achinghead.com/). By [Waylan Limberg](http://achinghead.com/).
License: [BSD](http://www.opensource.org/licenses/bsd-license.php) License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
Dependencies: Dependencies:
* [Python 2.3+](http://python.org) * [Python 2.3+](http://python.org)
* [Markdown 2.0+](http://packages.python.org/Markdown/) * [Markdown 2.0+](http://packages.python.org/Markdown/)
''' '''
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import Extension from . import Extension
from ..inlinepatterns import Pattern from ..inlinepatterns import Pattern
from ..util import etree from ..util import etree
import re import re
def build_url(label, base, end): def build_url(label, base, end):
""" Build a url from the label, a base, and an end. """ """ Build a url from the label, a base, and an end. """
clean_label = re.sub(r'([ ]+_)|(_[ ]+)|([ ]+)', '_', label) clean_label = re.sub(r'([ ]+_)|(_[ ]+)|([ ]+)', '_', label)
return '%s%s%s'% (base, clean_label, end) return '%s%s%s'% (base, clean_label, end)
class WikiLinkExtension(Extension): class WikiLinkExtension(Extension):
def __init__(self, configs): def __init__(self, configs):
# set extension defaults # set extension defaults
self.config = { self.config = {
'base_url' : ['/', 'String to append to beginning or URL.'], 'base_url' : ['/', 'String to append to beginning or URL.'],
'end_url' : ['/', 'String to append to end of URL.'], 'end_url' : ['/', 'String to append to end of URL.'],
'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'], 'html_class' : ['wikilink', 'CSS hook. Leave blank for none.'],
'build_url' : [build_url, 'Callable formats URL from label.'], 'build_url' : [build_url, 'Callable formats URL from label.'],
} }
configs = dict(configs) or {} configs = dict(configs) or {}
# Override defaults with user settings # Override defaults with user settings
for key, value in configs.items(): for key, value in configs.items():
self.setConfig(key, value) self.setConfig(key, value)
def extendMarkdown(self, md, md_globals): def extendMarkdown(self, md, md_globals):
self.md = md self.md = md
# append to end of inline patterns # append to end of inline patterns
WIKILINK_RE = r'\[\[([\w0-9_ -]+)\]\]' WIKILINK_RE = r'\[\[([\w0-9_ -]+)\]\]'
wikilinkPattern = WikiLinks(WIKILINK_RE, self.getConfigs()) wikilinkPattern = WikiLinks(WIKILINK_RE, self.getConfigs())
wikilinkPattern.md = md wikilinkPattern.md = md
md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong") md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong")
class WikiLinks(Pattern): class WikiLinks(Pattern):
def __init__(self, pattern, config): def __init__(self, pattern, config):
super(WikiLinks, self).__init__(pattern) super(WikiLinks, self).__init__(pattern)
self.config = config self.config = config
def handleMatch(self, m): def handleMatch(self, m):
if m.group(2).strip(): if m.group(2).strip():
base_url, end_url, html_class = self._getMeta() base_url, end_url, html_class = self._getMeta()
label = m.group(2).strip() label = m.group(2).strip()
url = self.config['build_url'](label, base_url, end_url) url = self.config['build_url'](label, base_url, end_url)
a = etree.Element('a') a = etree.Element('a')
a.text = label a.text = label
a.set('href', url) a.set('href', url)
if html_class: if html_class:
a.set('class', html_class) a.set('class', html_class)
else: else:
a = '' a = ''
return a return a
def _getMeta(self): def _getMeta(self):
""" Return meta data or config data. """ """ Return meta data or config data. """
base_url = self.config['base_url'] base_url = self.config['base_url']
end_url = self.config['end_url'] end_url = self.config['end_url']
html_class = self.config['html_class'] html_class = self.config['html_class']
if hasattr(self.md, 'Meta'): if hasattr(self.md, 'Meta'):
if 'wiki_base_url' in self.md.Meta: if 'wiki_base_url' in self.md.Meta:
base_url = self.md.Meta['wiki_base_url'][0] base_url = self.md.Meta['wiki_base_url'][0]
if 'wiki_end_url' in self.md.Meta: if 'wiki_end_url' in self.md.Meta:
end_url = self.md.Meta['wiki_end_url'][0] end_url = self.md.Meta['wiki_end_url'][0]
if 'wiki_html_class' in self.md.Meta: if 'wiki_html_class' in self.md.Meta:
html_class = self.md.Meta['wiki_html_class'][0] html_class = self.md.Meta['wiki_html_class'][0]
return base_url, end_url, html_class return base_url, end_url, html_class
def makeExtension(configs=None) : def makeExtension(configs=None) :
return WikiLinkExtension(configs=configs) return WikiLinkExtension(configs=configs)

View File

@@ -1,482 +1,482 @@
""" """
INLINE PATTERNS INLINE PATTERNS
============================================================================= =============================================================================
Inline patterns such as *emphasis* are handled by means of auxiliary Inline patterns such as *emphasis* are handled by means of auxiliary
objects, one per pattern. Pattern objects must be instances of classes objects, one per pattern. Pattern objects must be instances of classes
that extend markdown.Pattern. Each pattern object uses a single regular that extend markdown.Pattern. Each pattern object uses a single regular
expression and needs support the following methods: expression and needs support the following methods:
pattern.getCompiledRegExp() # returns a regular expression pattern.getCompiledRegExp() # returns a regular expression
pattern.handleMatch(m) # takes a match object and returns pattern.handleMatch(m) # takes a match object and returns
# an ElementTree element or just plain text # an ElementTree element or just plain text
All of python markdown's built-in patterns subclass from Pattern, All of python markdown's built-in patterns subclass from Pattern,
but you can add additional patterns that don't. but you can add additional patterns that don't.
Also note that all the regular expressions used by inline must Also note that all the regular expressions used by inline must
capture the whole block. For this reason, they all start with capture the whole block. For this reason, they all start with
'^(.*)' and end with '(.*)!'. In case with built-in expression '^(.*)' and end with '(.*)!'. In case with built-in expression
Pattern takes care of adding the "^(.*)" and "(.*)!". Pattern takes care of adding the "^(.*)" and "(.*)!".
Finally, the order in which regular expressions are applied is very Finally, the order in which regular expressions are applied is very
important - e.g. if we first replace http://.../ links with <a> tags important - e.g. if we first replace http://.../ links with <a> tags
and _then_ try to replace inline html, we would end up with a mess. and _then_ try to replace inline html, we would end up with a mess.
So, we apply the expressions in the following order: So, we apply the expressions in the following order:
* escape and backticks have to go before everything else, so * escape and backticks have to go before everything else, so
that we can preempt any markdown patterns by escaping them. that we can preempt any markdown patterns by escaping them.
* then we handle auto-links (must be done before inline html) * then we handle auto-links (must be done before inline html)
* then we handle inline HTML. At this point we will simply * then we handle inline HTML. At this point we will simply
replace all inline HTML strings with a placeholder and add replace all inline HTML strings with a placeholder and add
the actual HTML to a hash. the actual HTML to a hash.
* then inline images (must be done before links) * then inline images (must be done before links)
* then bracketed links, first regular then reference-style * then bracketed links, first regular then reference-style
* finally we apply strong and emphasis * finally we apply strong and emphasis
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import util from . import util
from . import odict from . import odict
import re import re
try: try:
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
except ImportError: except ImportError:
from urlparse import urlparse, urlunparse from urlparse import urlparse, urlunparse
try: try:
from html import entities from html import entities
except ImportError: except ImportError:
import htmlentitydefs as entities import htmlentitydefs as entities
def build_inlinepatterns(md_instance, **kwargs): def build_inlinepatterns(md_instance, **kwargs):
""" Build the default set of inline patterns for Markdown. """ """ Build the default set of inline patterns for Markdown. """
inlinePatterns = odict.OrderedDict() inlinePatterns = odict.OrderedDict()
inlinePatterns["backtick"] = BacktickPattern(BACKTICK_RE) inlinePatterns["backtick"] = BacktickPattern(BACKTICK_RE)
inlinePatterns["escape"] = EscapePattern(ESCAPE_RE, md_instance) inlinePatterns["escape"] = EscapePattern(ESCAPE_RE, md_instance)
inlinePatterns["reference"] = ReferencePattern(REFERENCE_RE, md_instance) inlinePatterns["reference"] = ReferencePattern(REFERENCE_RE, md_instance)
inlinePatterns["link"] = LinkPattern(LINK_RE, md_instance) inlinePatterns["link"] = LinkPattern(LINK_RE, md_instance)
inlinePatterns["image_link"] = ImagePattern(IMAGE_LINK_RE, md_instance) inlinePatterns["image_link"] = ImagePattern(IMAGE_LINK_RE, md_instance)
inlinePatterns["image_reference"] = \ inlinePatterns["image_reference"] = \
ImageReferencePattern(IMAGE_REFERENCE_RE, md_instance) ImageReferencePattern(IMAGE_REFERENCE_RE, md_instance)
inlinePatterns["short_reference"] = \ inlinePatterns["short_reference"] = \
ReferencePattern(SHORT_REF_RE, md_instance) ReferencePattern(SHORT_REF_RE, md_instance)
inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance) inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance)
inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance) inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance)
inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br') inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br')
if md_instance.safeMode != 'escape': if md_instance.safeMode != 'escape':
inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance) inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance)
inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance) inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance)
inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE) inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE)
inlinePatterns["strong_em"] = DoubleTagPattern(STRONG_EM_RE, 'strong,em') inlinePatterns["strong_em"] = DoubleTagPattern(STRONG_EM_RE, 'strong,em')
inlinePatterns["strong"] = SimpleTagPattern(STRONG_RE, 'strong') inlinePatterns["strong"] = SimpleTagPattern(STRONG_RE, 'strong')
inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em') inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em')
if md_instance.smart_emphasis: if md_instance.smart_emphasis:
inlinePatterns["emphasis2"] = SimpleTagPattern(SMART_EMPHASIS_RE, 'em') inlinePatterns["emphasis2"] = SimpleTagPattern(SMART_EMPHASIS_RE, 'em')
else: else:
inlinePatterns["emphasis2"] = SimpleTagPattern(EMPHASIS_2_RE, 'em') inlinePatterns["emphasis2"] = SimpleTagPattern(EMPHASIS_2_RE, 'em')
return inlinePatterns return inlinePatterns
""" """
The actual regular expressions for patterns The actual regular expressions for patterns
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
""" """
NOBRACKET = r'[^\]\[]*' NOBRACKET = r'[^\]\[]*'
BRK = ( r'\[(' BRK = ( r'\[('
+ (NOBRACKET + r'(\[')*6 + (NOBRACKET + r'(\[')*6
+ (NOBRACKET+ r'\])*')*6 + (NOBRACKET+ r'\])*')*6
+ NOBRACKET + r')\]' ) + NOBRACKET + r')\]' )
NOIMG = r'(?<!\!)' NOIMG = r'(?<!\!)'
BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")`` BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")``
ESCAPE_RE = r'\\(.)' # \< ESCAPE_RE = r'\\(.)' # \<
EMPHASIS_RE = r'(\*)([^\*]+)\2' # *emphasis* EMPHASIS_RE = r'(\*)([^\*]+)\2' # *emphasis*
STRONG_RE = r'(\*{2}|_{2})(.+?)\2' # **strong** STRONG_RE = r'(\*{2}|_{2})(.+?)\2' # **strong**
STRONG_EM_RE = r'(\*{3}|_{3})(.+?)\2' # ***strong*** STRONG_EM_RE = r'(\*{3}|_{3})(.+?)\2' # ***strong***
SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)' # _smart_emphasis_ SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)' # _smart_emphasis_
EMPHASIS_2_RE = r'(_)(.+?)\2' # _emphasis_ EMPHASIS_2_RE = r'(_)(.+?)\2' # _emphasis_
LINK_RE = NOIMG + BRK + \ LINK_RE = NOIMG + BRK + \
r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
# [text](url) or [text](<url>) or [text](url "title") # [text](url) or [text](<url>) or [text](url "title")
IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^")]+"[^"]*"|[^\)]*))\)' IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^")]+"[^"]*"|[^\)]*))\)'
# ![alttxt](http://x.com/) or ![alttxt](<http://x.com/>) # ![alttxt](http://x.com/) or ![alttxt](<http://x.com/>)
REFERENCE_RE = NOIMG + BRK+ r'\s?\[([^\]]*)\]' # [Google][3] REFERENCE_RE = NOIMG + BRK+ r'\s?\[([^\]]*)\]' # [Google][3]
SHORT_REF_RE = NOIMG + r'\[([^\]]+)\]' # [Google] SHORT_REF_RE = NOIMG + r'\[([^\]]+)\]' # [Google]
IMAGE_REFERENCE_RE = r'\!' + BRK + '\s?\[([^\]]*)\]' # ![alt text][2] IMAGE_REFERENCE_RE = r'\!' + BRK + '\s?\[([^\]]*)\]' # ![alt text][2]
NOT_STRONG_RE = r'((^| )(\*|_)( |$))' # stand-alone * or _ NOT_STRONG_RE = r'((^| )(\*|_)( |$))' # stand-alone * or _
AUTOLINK_RE = r'<((?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^>]*)>' # <http://www.123.com> AUTOLINK_RE = r'<((?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^>]*)>' # <http://www.123.com>
AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com> AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com>
HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...> HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...>
ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # &amp; ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # &amp;
LINE_BREAK_RE = r' \n' # two spaces at end of line LINE_BREAK_RE = r' \n' # two spaces at end of line
def dequote(string): def dequote(string):
"""Remove quotes from around a string.""" """Remove quotes from around a string."""
if ( ( string.startswith('"') and string.endswith('"')) if ( ( string.startswith('"') and string.endswith('"'))
or (string.startswith("'") and string.endswith("'")) ): or (string.startswith("'") and string.endswith("'")) ):
return string[1:-1] return string[1:-1]
else: else:
return string return string
ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123} ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123}
def handleAttributes(text, parent): def handleAttributes(text, parent):
"""Set values of an element based on attribute definitions ({@id=123}).""" """Set values of an element based on attribute definitions ({@id=123})."""
def attributeCallback(match): def attributeCallback(match):
parent.set(match.group(1), match.group(2).replace('\n', ' ')) parent.set(match.group(1), match.group(2).replace('\n', ' '))
return ATTR_RE.sub(attributeCallback, text) return ATTR_RE.sub(attributeCallback, text)
""" """
The pattern classes The pattern classes
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
""" """
class Pattern(object): class Pattern(object):
"""Base class that inline patterns subclass. """ """Base class that inline patterns subclass. """
def __init__(self, pattern, markdown_instance=None): def __init__(self, pattern, markdown_instance=None):
""" """
Create an instant of an inline pattern. Create an instant of an inline pattern.
Keyword arguments: Keyword arguments:
* pattern: A regular expression that matches a pattern * pattern: A regular expression that matches a pattern
""" """
self.pattern = pattern self.pattern = pattern
self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern,
re.DOTALL | re.UNICODE) re.DOTALL | re.UNICODE)
# Api for Markdown to pass safe_mode into instance # Api for Markdown to pass safe_mode into instance
self.safe_mode = False self.safe_mode = False
if markdown_instance: if markdown_instance:
self.markdown = markdown_instance self.markdown = markdown_instance
def getCompiledRegExp(self): def getCompiledRegExp(self):
""" Return a compiled regular expression. """ """ Return a compiled regular expression. """
return self.compiled_re return self.compiled_re
def handleMatch(self, m): def handleMatch(self, m):
"""Return a ElementTree element from the given match. """Return a ElementTree element from the given match.
Subclasses should override this method. Subclasses should override this method.
Keyword arguments: Keyword arguments:
* m: A re match object containing a match of the pattern. * m: A re match object containing a match of the pattern.
""" """
pass pass
def type(self): def type(self):
""" Return class name, to define pattern type """ """ Return class name, to define pattern type """
return self.__class__.__name__ return self.__class__.__name__
def unescape(self, text): def unescape(self, text):
""" Return unescaped text given text with an inline placeholder. """ """ Return unescaped text given text with an inline placeholder. """
try: try:
stash = self.markdown.treeprocessors['inline'].stashed_nodes stash = self.markdown.treeprocessors['inline'].stashed_nodes
except KeyError: except KeyError:
return text return text
def itertext(el): def itertext(el):
' Reimplement Element.itertext for older python versions ' ' Reimplement Element.itertext for older python versions '
tag = el.tag tag = el.tag
if not isinstance(tag, util.string_type) and tag is not None: if not isinstance(tag, util.string_type) and tag is not None:
return return
if el.text: if el.text:
yield el.text yield el.text
for e in el: for e in el:
for s in itertext(e): for s in itertext(e):
yield s yield s
if e.tail: if e.tail:
yield e.tail yield e.tail
def get_stash(m): def get_stash(m):
id = m.group(1) id = m.group(1)
if id in stash: if id in stash:
value = stash.get(id) value = stash.get(id)
if isinstance(value, util.string_type): if isinstance(value, util.string_type):
return value return value
else: else:
# An etree Element - return text content only # An etree Element - return text content only
return ''.join(itertext(value)) return ''.join(itertext(value))
return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
class SimpleTextPattern(Pattern): class SimpleTextPattern(Pattern):
""" Return a simple text of group(2) of a Pattern. """ """ Return a simple text of group(2) of a Pattern. """
def handleMatch(self, m): def handleMatch(self, m):
text = m.group(2) text = m.group(2)
if text == util.INLINE_PLACEHOLDER_PREFIX: if text == util.INLINE_PLACEHOLDER_PREFIX:
return None return None
return text return text
class EscapePattern(Pattern): class EscapePattern(Pattern):
""" Return an escaped character. """ """ Return an escaped character. """
def handleMatch(self, m): def handleMatch(self, m):
char = m.group(2) char = m.group(2)
if char in self.markdown.ESCAPED_CHARS: if char in self.markdown.ESCAPED_CHARS:
return '%s%s%s' % (util.STX, ord(char), util.ETX) return '%s%s%s' % (util.STX, ord(char), util.ETX)
else: else:
return None return None
class SimpleTagPattern(Pattern): class SimpleTagPattern(Pattern):
""" """
Return element of type `tag` with a text attribute of group(3) Return element of type `tag` with a text attribute of group(3)
of a Pattern. of a Pattern.
""" """
def __init__ (self, pattern, tag): def __init__ (self, pattern, tag):
Pattern.__init__(self, pattern) Pattern.__init__(self, pattern)
self.tag = tag self.tag = tag
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element(self.tag) el = util.etree.Element(self.tag)
el.text = m.group(3) el.text = m.group(3)
return el return el
class SubstituteTagPattern(SimpleTagPattern): class SubstituteTagPattern(SimpleTagPattern):
""" Return an element of type `tag` with no children. """ """ Return an element of type `tag` with no children. """
def handleMatch (self, m): def handleMatch (self, m):
return util.etree.Element(self.tag) return util.etree.Element(self.tag)
class BacktickPattern(Pattern): class BacktickPattern(Pattern):
""" Return a `<code>` element containing the matching text. """ """ Return a `<code>` element containing the matching text. """
def __init__ (self, pattern): def __init__ (self, pattern):
Pattern.__init__(self, pattern) Pattern.__init__(self, pattern)
self.tag = "code" self.tag = "code"
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element(self.tag) el = util.etree.Element(self.tag)
el.text = util.AtomicString(m.group(3).strip()) el.text = util.AtomicString(m.group(3).strip())
return el return el
class DoubleTagPattern(SimpleTagPattern): class DoubleTagPattern(SimpleTagPattern):
"""Return a ElementTree element nested in tag2 nested in tag1. """Return a ElementTree element nested in tag2 nested in tag1.
Useful for strong emphasis etc. Useful for strong emphasis etc.
""" """
def handleMatch(self, m): def handleMatch(self, m):
tag1, tag2 = self.tag.split(",") tag1, tag2 = self.tag.split(",")
el1 = util.etree.Element(tag1) el1 = util.etree.Element(tag1)
el2 = util.etree.SubElement(el1, tag2) el2 = util.etree.SubElement(el1, tag2)
el2.text = m.group(3) el2.text = m.group(3)
return el1 return el1
class HtmlPattern(Pattern): class HtmlPattern(Pattern):
""" Store raw inline html and return a placeholder. """ """ Store raw inline html and return a placeholder. """
def handleMatch (self, m): def handleMatch (self, m):
rawhtml = self.unescape(m.group(2)) rawhtml = self.unescape(m.group(2))
place_holder = self.markdown.htmlStash.store(rawhtml) place_holder = self.markdown.htmlStash.store(rawhtml)
return place_holder return place_holder
def unescape(self, text): def unescape(self, text):
""" Return unescaped text given text with an inline placeholder. """ """ Return unescaped text given text with an inline placeholder. """
try: try:
stash = self.markdown.treeprocessors['inline'].stashed_nodes stash = self.markdown.treeprocessors['inline'].stashed_nodes
except KeyError: except KeyError:
return text return text
def get_stash(m): def get_stash(m):
id = m.group(1) id = m.group(1)
value = stash.get(id) value = stash.get(id)
if value is not None: if value is not None:
try: try:
return self.markdown.serializer(value) return self.markdown.serializer(value)
except: except:
return '\%s' % value return '\%s' % value
return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
class LinkPattern(Pattern): class LinkPattern(Pattern):
""" Return a link element from the given match. """ """ Return a link element from the given match. """
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element("a") el = util.etree.Element("a")
el.text = m.group(2) el.text = m.group(2)
title = m.group(13) title = m.group(13)
href = m.group(9) href = m.group(9)
if href: if href:
if href[0] == "<": if href[0] == "<":
href = href[1:-1] href = href[1:-1]
el.set("href", self.sanitize_url(self.unescape(href.strip()))) el.set("href", self.sanitize_url(self.unescape(href.strip())))
else: else:
el.set("href", "") el.set("href", "")
if title: if title:
title = dequote(self.unescape(title)) title = dequote(self.unescape(title))
el.set("title", title) el.set("title", title)
return el return el
def sanitize_url(self, url): def sanitize_url(self, url):
""" """
Sanitize a url against xss attacks in "safe_mode". Sanitize a url against xss attacks in "safe_mode".
Rather than specifically blacklisting `javascript:alert("XSS")` and all Rather than specifically blacklisting `javascript:alert("XSS")` and all
its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known
safe url formats. Most urls contain a network location, however some safe url formats. Most urls contain a network location, however some
are known not to (i.e.: mailto links). Script urls do not contain a are known not to (i.e.: mailto links). Script urls do not contain a
location. Additionally, for `javascript:...`, the scheme would be location. Additionally, for `javascript:...`, the scheme would be
"javascript" but some aliases will appear to `urlparse()` to have no "javascript" but some aliases will appear to `urlparse()` to have no
scheme. On top of that relative links (i.e.: "foo/bar.html") have no scheme. On top of that relative links (i.e.: "foo/bar.html") have no
scheme. Therefore we must check "path", "parameters", "query" and scheme. Therefore we must check "path", "parameters", "query" and
"fragment" for any literal colons. We don't check "scheme" for colons "fragment" for any literal colons. We don't check "scheme" for colons
because it *should* never have any and "netloc" must allow the form: because it *should* never have any and "netloc" must allow the form:
`username:password@host:port`. `username:password@host:port`.
""" """
if not self.markdown.safeMode: if not self.markdown.safeMode:
# Return immediately bipassing parsing. # Return immediately bipassing parsing.
return url return url
try: try:
scheme, netloc, path, params, query, fragment = url = urlparse(url) scheme, netloc, path, params, query, fragment = url = urlparse(url)
except ValueError: except ValueError:
# Bad url - so bad it couldn't be parsed. # Bad url - so bad it couldn't be parsed.
return '' return ''
locless_schemes = ['', 'mailto', 'news'] locless_schemes = ['', 'mailto', 'news']
allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps'] allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps']
if scheme not in allowed_schemes: if scheme not in allowed_schemes:
# Not a known (allowed) scheme. Not safe. # Not a known (allowed) scheme. Not safe.
return '' return ''
if netloc == '' and scheme not in locless_schemes: if netloc == '' and scheme not in locless_schemes:
# This should not happen. Treat as suspect. # This should not happen. Treat as suspect.
return '' return ''
for part in url[2:]: for part in url[2:]:
if ":" in part: if ":" in part:
# A colon in "path", "parameters", "query" or "fragment" is suspect. # A colon in "path", "parameters", "query" or "fragment" is suspect.
return '' return ''
# Url passes all tests. Return url as-is. # Url passes all tests. Return url as-is.
return urlunparse(url) return urlunparse(url)
class ImagePattern(LinkPattern): class ImagePattern(LinkPattern):
""" Return a img element from the given match. """ """ Return a img element from the given match. """
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element("img") el = util.etree.Element("img")
src_parts = m.group(9).split() src_parts = m.group(9).split()
if src_parts: if src_parts:
src = src_parts[0] src = src_parts[0]
if src[0] == "<" and src[-1] == ">": if src[0] == "<" and src[-1] == ">":
src = src[1:-1] src = src[1:-1]
el.set('src', self.sanitize_url(self.unescape(src))) el.set('src', self.sanitize_url(self.unescape(src)))
else: else:
el.set('src', "") el.set('src', "")
if len(src_parts) > 1: if len(src_parts) > 1:
el.set('title', dequote(self.unescape(" ".join(src_parts[1:])))) el.set('title', dequote(self.unescape(" ".join(src_parts[1:]))))
if self.markdown.enable_attributes: if self.markdown.enable_attributes:
truealt = handleAttributes(m.group(2), el) truealt = handleAttributes(m.group(2), el)
else: else:
truealt = m.group(2) truealt = m.group(2)
el.set('alt', self.unescape(truealt)) el.set('alt', self.unescape(truealt))
return el return el
class ReferencePattern(LinkPattern): class ReferencePattern(LinkPattern):
""" Match to a stored reference and return link element. """ """ Match to a stored reference and return link element. """
NEWLINE_CLEANUP_RE = re.compile(r'[ ]?\n', re.MULTILINE) NEWLINE_CLEANUP_RE = re.compile(r'[ ]?\n', re.MULTILINE)
def handleMatch(self, m): def handleMatch(self, m):
try: try:
id = m.group(9).lower() id = m.group(9).lower()
except IndexError: except IndexError:
id = None id = None
if not id: if not id:
# if we got something like "[Google][]" or "[Goggle]" # if we got something like "[Google][]" or "[Goggle]"
# we'll use "google" as the id # we'll use "google" as the id
id = m.group(2).lower() id = m.group(2).lower()
# Clean up linebreaks in id # Clean up linebreaks in id
id = self.NEWLINE_CLEANUP_RE.sub(' ', id) id = self.NEWLINE_CLEANUP_RE.sub(' ', id)
if not id in self.markdown.references: # ignore undefined refs if not id in self.markdown.references: # ignore undefined refs
return None return None
href, title = self.markdown.references[id] href, title = self.markdown.references[id]
text = m.group(2) text = m.group(2)
return self.makeTag(href, title, text) return self.makeTag(href, title, text)
def makeTag(self, href, title, text): def makeTag(self, href, title, text):
el = util.etree.Element('a') el = util.etree.Element('a')
el.set('href', self.sanitize_url(href)) el.set('href', self.sanitize_url(href))
if title: if title:
el.set('title', title) el.set('title', title)
el.text = text el.text = text
return el return el
class ImageReferencePattern(ReferencePattern): class ImageReferencePattern(ReferencePattern):
""" Match to a stored reference and return img element. """ """ Match to a stored reference and return img element. """
def makeTag(self, href, title, text): def makeTag(self, href, title, text):
el = util.etree.Element("img") el = util.etree.Element("img")
el.set("src", self.sanitize_url(href)) el.set("src", self.sanitize_url(href))
if title: if title:
el.set("title", title) el.set("title", title)
if self.markdown.enable_attributes: if self.markdown.enable_attributes:
text = handleAttributes(text, el) text = handleAttributes(text, el)
el.set("alt", self.unescape(text)) el.set("alt", self.unescape(text))
return el return el
class AutolinkPattern(Pattern): class AutolinkPattern(Pattern):
""" Return a link Element given an autolink (`<http://example/com>`). """ """ Return a link Element given an autolink (`<http://example/com>`). """
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element("a") el = util.etree.Element("a")
el.set('href', self.unescape(m.group(2))) el.set('href', self.unescape(m.group(2)))
el.text = util.AtomicString(m.group(2)) el.text = util.AtomicString(m.group(2))
return el return el
class AutomailPattern(Pattern): class AutomailPattern(Pattern):
""" """
Return a mailto link Element given an automail link (`<foo@example.com>`). Return a mailto link Element given an automail link (`<foo@example.com>`).
""" """
def handleMatch(self, m): def handleMatch(self, m):
el = util.etree.Element('a') el = util.etree.Element('a')
email = self.unescape(m.group(2)) email = self.unescape(m.group(2))
if email.startswith("mailto:"): if email.startswith("mailto:"):
email = email[len("mailto:"):] email = email[len("mailto:"):]
def codepoint2name(code): def codepoint2name(code):
"""Return entity definition by code, or the code if not defined.""" """Return entity definition by code, or the code if not defined."""
entity = entities.codepoint2name.get(code) entity = entities.codepoint2name.get(code)
if entity: if entity:
return "%s%s;" % (util.AMP_SUBSTITUTE, entity) return "%s%s;" % (util.AMP_SUBSTITUTE, entity)
else: else:
return "%s#%d;" % (util.AMP_SUBSTITUTE, code) return "%s#%d;" % (util.AMP_SUBSTITUTE, code)
letters = [codepoint2name(ord(letter)) for letter in email] letters = [codepoint2name(ord(letter)) for letter in email]
el.text = util.AtomicString(''.join(letters)) el.text = util.AtomicString(''.join(letters))
mailto = "mailto:" + email mailto = "mailto:" + email
mailto = "".join([util.AMP_SUBSTITUTE + '#%d;' % mailto = "".join([util.AMP_SUBSTITUTE + '#%d;' %
ord(letter) for letter in mailto]) ord(letter) for letter in mailto])
el.set('href', mailto) el.set('href', mailto)
return el return el

View File

@@ -1,189 +1,189 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from . import util from . import util
from copy import deepcopy from copy import deepcopy
class OrderedDict(dict): class OrderedDict(dict):
""" """
A dictionary that keeps its keys in the order in which they're inserted. A dictionary that keeps its keys in the order in which they're inserted.
Copied from Django's SortedDict with some modifications. Copied from Django's SortedDict with some modifications.
""" """
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs) instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs)
instance.keyOrder = [] instance.keyOrder = []
return instance return instance
def __init__(self, data=None): def __init__(self, data=None):
if data is None or isinstance(data, dict): if data is None or isinstance(data, dict):
data = data or [] data = data or []
super(OrderedDict, self).__init__(data) super(OrderedDict, self).__init__(data)
self.keyOrder = list(data) if data else [] self.keyOrder = list(data) if data else []
else: else:
super(OrderedDict, self).__init__() super(OrderedDict, self).__init__()
super_set = super(OrderedDict, self).__setitem__ super_set = super(OrderedDict, self).__setitem__
for key, value in data: for key, value in data:
# Take the ordering from first key # Take the ordering from first key
if key not in self: if key not in self:
self.keyOrder.append(key) self.keyOrder.append(key)
# But override with last value in data (dict() does this) # But override with last value in data (dict() does this)
super_set(key, value) super_set(key, value)
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self.__class__([(key, deepcopy(value, memo)) return self.__class__([(key, deepcopy(value, memo))
for key, value in self.items()]) for key, value in self.items()])
def __copy__(self): def __copy__(self):
# The Python's default copy implementation will alter the state # The Python's default copy implementation will alter the state
# of self. The reason for this seems complex but is likely related to # of self. The reason for this seems complex but is likely related to
# subclassing dict. # subclassing dict.
return self.copy() return self.copy()
def __setitem__(self, key, value): def __setitem__(self, key, value):
if key not in self: if key not in self:
self.keyOrder.append(key) self.keyOrder.append(key)
super(OrderedDict, self).__setitem__(key, value) super(OrderedDict, self).__setitem__(key, value)
def __delitem__(self, key): def __delitem__(self, key):
super(OrderedDict, self).__delitem__(key) super(OrderedDict, self).__delitem__(key)
self.keyOrder.remove(key) self.keyOrder.remove(key)
def __iter__(self): def __iter__(self):
return iter(self.keyOrder) return iter(self.keyOrder)
def __reversed__(self): def __reversed__(self):
return reversed(self.keyOrder) return reversed(self.keyOrder)
def pop(self, k, *args): def pop(self, k, *args):
result = super(OrderedDict, self).pop(k, *args) result = super(OrderedDict, self).pop(k, *args)
try: try:
self.keyOrder.remove(k) self.keyOrder.remove(k)
except ValueError: except ValueError:
# Key wasn't in the dictionary in the first place. No problem. # Key wasn't in the dictionary in the first place. No problem.
pass pass
return result return result
def popitem(self): def popitem(self):
result = super(OrderedDict, self).popitem() result = super(OrderedDict, self).popitem()
self.keyOrder.remove(result[0]) self.keyOrder.remove(result[0])
return result return result
def _iteritems(self): def _iteritems(self):
for key in self.keyOrder: for key in self.keyOrder:
yield key, self[key] yield key, self[key]
def _iterkeys(self): def _iterkeys(self):
for key in self.keyOrder: for key in self.keyOrder:
yield key yield key
def _itervalues(self): def _itervalues(self):
for key in self.keyOrder: for key in self.keyOrder:
yield self[key] yield self[key]
if util.PY3: if util.PY3:
items = _iteritems items = _iteritems
keys = _iterkeys keys = _iterkeys
values = _itervalues values = _itervalues
else: else:
iteritems = _iteritems iteritems = _iteritems
iterkeys = _iterkeys iterkeys = _iterkeys
itervalues = _itervalues itervalues = _itervalues
def items(self): def items(self):
return [(k, self[k]) for k in self.keyOrder] return [(k, self[k]) for k in self.keyOrder]
def keys(self): def keys(self):
return self.keyOrder[:] return self.keyOrder[:]
def values(self): def values(self):
return [self[k] for k in self.keyOrder] return [self[k] for k in self.keyOrder]
def update(self, dict_): def update(self, dict_):
for k in dict_: for k in dict_:
self[k] = dict_[k] self[k] = dict_[k]
def setdefault(self, key, default): def setdefault(self, key, default):
if key not in self: if key not in self:
self.keyOrder.append(key) self.keyOrder.append(key)
return super(OrderedDict, self).setdefault(key, default) return super(OrderedDict, self).setdefault(key, default)
def value_for_index(self, index): def value_for_index(self, index):
"""Returns 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]] return self[self.keyOrder[index]]
def insert(self, index, key, value): def insert(self, index, key, value):
"""Inserts 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: if key in self.keyOrder:
n = self.keyOrder.index(key) n = self.keyOrder.index(key)
del self.keyOrder[n] del self.keyOrder[n]
if n < index: if n < index:
index -= 1 index -= 1
self.keyOrder.insert(index, key) self.keyOrder.insert(index, key)
super(OrderedDict, self).__setitem__(key, value) super(OrderedDict, self).__setitem__(key, value)
def copy(self): def copy(self):
"""Returns a copy of this object.""" """Returns a copy of this object."""
# This way of initializing the copy means it works for subclasses, too. # This way of initializing the copy means it works for subclasses, too.
return self.__class__(self) return self.__class__(self)
def __repr__(self): def __repr__(self):
""" """
Replaces the normal dict.__repr__ with a version that returns the keys Replaces the normal dict.__repr__ with a version that returns the keys
in their Ordered order. in their Ordered order.
""" """
return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self._iteritems()]) return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self._iteritems()])
def clear(self): def clear(self):
super(OrderedDict, self).clear() super(OrderedDict, self).clear()
self.keyOrder = [] self.keyOrder = []
def index(self, key): def index(self, key):
""" Return the index of a given key. """ """ Return the index of a given key. """
try: try:
return self.keyOrder.index(key) return self.keyOrder.index(key)
except ValueError: except ValueError:
raise ValueError("Element '%s' was not found in OrderedDict" % key) raise ValueError("Element '%s' was not found in OrderedDict" % key)
def index_for_location(self, location): def index_for_location(self, location):
""" Return index or None for a given location. """ """ Return index or None for a given location. """
if location == '_begin': if location == '_begin':
i = 0 i = 0
elif location == '_end': elif location == '_end':
i = None i = None
elif location.startswith('<') or location.startswith('>'): elif location.startswith('<') or location.startswith('>'):
i = self.index(location[1:]) i = self.index(location[1:])
if location.startswith('>'): if location.startswith('>'):
if i >= len(self): if i >= len(self):
# last item # last item
i = None i = None
else: else:
i += 1 i += 1
else: else:
raise ValueError('Not a valid location: "%s". Location key ' raise ValueError('Not a valid location: "%s". Location key '
'must start with a ">" or "<".' % location) 'must start with a ">" or "<".' % location)
return i return i
def add(self, key, value, location): def add(self, key, value, location):
""" Insert by key location. """ """ Insert by key location. """
i = self.index_for_location(location) i = self.index_for_location(location)
if i is not None: if i is not None:
self.insert(i, key, value) self.insert(i, key, value)
else: else:
self.__setitem__(key, value) self.__setitem__(key, value)
def link(self, key, location): def link(self, key, location):
""" Change location of an existing item. """ """ Change location of an existing item. """
n = self.keyOrder.index(key) n = self.keyOrder.index(key)
del self.keyOrder[n] del self.keyOrder[n]
try: try:
i = self.index_for_location(location) i = self.index_for_location(location)
if i is not None: if i is not None:
self.keyOrder.insert(i, key) self.keyOrder.insert(i, key)
else: else:
self.keyOrder.append(key) self.keyOrder.append(key)
except Exception as e: except Exception as e:
# restore to prevent data loss and reraise # restore to prevent data loss and reraise
self.keyOrder.insert(n, key) self.keyOrder.insert(n, key)
raise e raise e

View File

@@ -1,104 +1,104 @@
""" """
POST-PROCESSORS POST-PROCESSORS
============================================================================= =============================================================================
Markdown also allows post-processors, which are similar to preprocessors in Markdown also allows post-processors, which are similar to preprocessors in
that they need to implement a "run" method. However, they are run after core that they need to implement a "run" method. However, they are run after core
processing. processing.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import util from . import util
from . import odict from . import odict
import re import re
def build_postprocessors(md_instance, **kwargs): def build_postprocessors(md_instance, **kwargs):
""" Build the default postprocessors for Markdown. """ """ Build the default postprocessors for Markdown. """
postprocessors = odict.OrderedDict() postprocessors = odict.OrderedDict()
postprocessors["raw_html"] = RawHtmlPostprocessor(md_instance) postprocessors["raw_html"] = RawHtmlPostprocessor(md_instance)
postprocessors["amp_substitute"] = AndSubstitutePostprocessor() postprocessors["amp_substitute"] = AndSubstitutePostprocessor()
postprocessors["unescape"] = UnescapePostprocessor() postprocessors["unescape"] = UnescapePostprocessor()
return postprocessors return postprocessors
class Postprocessor(util.Processor): class Postprocessor(util.Processor):
""" """
Postprocessors are run after the ElementTree it converted back into text. Postprocessors are run after the ElementTree it converted back into text.
Each Postprocessor implements a "run" method that takes a pointer to a Each Postprocessor implements a "run" method that takes a pointer to a
text string, modifies it as necessary and returns a text string. text string, modifies it as necessary and returns a text string.
Postprocessors must extend markdown.Postprocessor. Postprocessors must extend markdown.Postprocessor.
""" """
def run(self, text): def run(self, text):
""" """
Subclasses of Postprocessor should implement a `run` method, which Subclasses of Postprocessor should implement a `run` method, which
takes the html document as a single text string and returns a takes the html document as a single text string and returns a
(possibly modified) string. (possibly modified) string.
""" """
pass pass
class RawHtmlPostprocessor(Postprocessor): class RawHtmlPostprocessor(Postprocessor):
""" Restore raw html to the document. """ """ Restore raw html to the document. """
def run(self, text): def run(self, text):
""" Iterate over html stash and restore "safe" html. """ """ Iterate over html stash and restore "safe" html. """
for i in range(self.markdown.htmlStash.html_counter): for i in range(self.markdown.htmlStash.html_counter):
html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] html, safe = self.markdown.htmlStash.rawHtmlBlocks[i]
if self.markdown.safeMode and not safe: if self.markdown.safeMode and not safe:
if str(self.markdown.safeMode).lower() == 'escape': if str(self.markdown.safeMode).lower() == 'escape':
html = self.escape(html) html = self.escape(html)
elif str(self.markdown.safeMode).lower() == 'remove': elif str(self.markdown.safeMode).lower() == 'remove':
html = '' html = ''
else: else:
html = self.markdown.html_replacement_text html = self.markdown.html_replacement_text
if self.isblocklevel(html) and (safe or not self.markdown.safeMode): if self.isblocklevel(html) and (safe or not self.markdown.safeMode):
text = text.replace("<p>%s</p>" % text = text.replace("<p>%s</p>" %
(self.markdown.htmlStash.get_placeholder(i)), (self.markdown.htmlStash.get_placeholder(i)),
html + "\n") html + "\n")
text = text.replace(self.markdown.htmlStash.get_placeholder(i), text = text.replace(self.markdown.htmlStash.get_placeholder(i),
html) html)
return text return text
def escape(self, html): def escape(self, html):
""" Basic html escaping """ """ Basic html escaping """
html = html.replace('&', '&amp;') html = html.replace('&', '&amp;')
html = html.replace('<', '&lt;') html = html.replace('<', '&lt;')
html = html.replace('>', '&gt;') html = html.replace('>', '&gt;')
return html.replace('"', '&quot;') return html.replace('"', '&quot;')
def isblocklevel(self, html): def isblocklevel(self, html):
m = re.match(r'^\<\/?([^ >]+)', html) m = re.match(r'^\<\/?([^ >]+)', html)
if m: if m:
if m.group(1)[0] in ('!', '?', '@', '%'): if m.group(1)[0] in ('!', '?', '@', '%'):
# Comment, php etc... # Comment, php etc...
return True return True
return util.isBlockLevel(m.group(1)) return util.isBlockLevel(m.group(1))
return False return False
class AndSubstitutePostprocessor(Postprocessor): class AndSubstitutePostprocessor(Postprocessor):
""" Restore valid entities """ """ Restore valid entities """
def run(self, text): def run(self, text):
text = text.replace(util.AMP_SUBSTITUTE, "&") text = text.replace(util.AMP_SUBSTITUTE, "&")
return text return text
class UnescapePostprocessor(Postprocessor): class UnescapePostprocessor(Postprocessor):
""" Restore escaped chars """ """ Restore escaped chars """
RE = re.compile('%s(\d+)%s' % (util.STX, util.ETX)) RE = re.compile('%s(\d+)%s' % (util.STX, util.ETX))
def unescape(self, m): def unescape(self, m):
return util.int2str(int(m.group(1))) return util.int2str(int(m.group(1)))
def run(self, text): def run(self, text):
return self.RE.sub(self.unescape, text) return self.RE.sub(self.unescape, text)

View File

@@ -1,341 +1,341 @@
""" """
PRE-PROCESSORS PRE-PROCESSORS
============================================================================= =============================================================================
Preprocessors work on source text before we start doing anything too Preprocessors work on source text before we start doing anything too
complicated. complicated.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import util from . import util
from . import odict from . import odict
import re import re
def build_preprocessors(md_instance, **kwargs): def build_preprocessors(md_instance, **kwargs):
""" Build the default set of preprocessors used by Markdown. """ """ Build the default set of preprocessors used by Markdown. """
preprocessors = odict.OrderedDict() preprocessors = odict.OrderedDict()
preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance) preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance)
if md_instance.safeMode != 'escape': if md_instance.safeMode != 'escape':
preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance) preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
preprocessors["reference"] = ReferencePreprocessor(md_instance) preprocessors["reference"] = ReferencePreprocessor(md_instance)
return preprocessors return preprocessors
class Preprocessor(util.Processor): class Preprocessor(util.Processor):
""" """
Preprocessors are run after the text is broken into lines. Preprocessors are run after the text is broken into lines.
Each preprocessor implements a "run" method that takes a pointer to a Each preprocessor implements a "run" method that takes a pointer to a
list of lines of the document, modifies it as necessary and returns list of lines of the document, modifies it as necessary and returns
either the same pointer or a pointer to a new list. either the same pointer or a pointer to a new list.
Preprocessors must extend markdown.Preprocessor. Preprocessors must extend markdown.Preprocessor.
""" """
def run(self, lines): def run(self, lines):
""" """
Each subclass of Preprocessor should override the `run` method, which Each subclass of Preprocessor should override the `run` method, which
takes the document as a list of strings split by newlines and returns takes the document as a list of strings split by newlines and returns
the (possibly modified) list of lines. the (possibly modified) list of lines.
""" """
pass pass
class NormalizeWhitespace(Preprocessor): class NormalizeWhitespace(Preprocessor):
""" Normalize whitespace for consistant parsing. """ """ Normalize whitespace for consistant parsing. """
def run(self, lines): def run(self, lines):
source = '\n'.join(lines) source = '\n'.join(lines)
source = source.replace(util.STX, "").replace(util.ETX, "") source = source.replace(util.STX, "").replace(util.ETX, "")
source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n" source = source.replace("\r\n", "\n").replace("\r", "\n") + "\n\n"
source = source.expandtabs(self.markdown.tab_length) source = source.expandtabs(self.markdown.tab_length)
source = re.sub(r'(?<=\n) +\n', '\n', source) source = re.sub(r'(?<=\n) +\n', '\n', source)
return source.split('\n') return source.split('\n')
class HtmlBlockPreprocessor(Preprocessor): class HtmlBlockPreprocessor(Preprocessor):
"""Remove html blocks from the text and store them for later retrieval.""" """Remove html blocks from the text and store them for later retrieval."""
right_tag_patterns = ["</%s>", "%s>"] right_tag_patterns = ["</%s>", "%s>"]
attrs_pattern = r""" attrs_pattern = r"""
\s+(?P<attr>[^>"'/= ]+)=(?P<q>['"])(?P<value>.*?)(?P=q) # attr="value" \s+(?P<attr>[^>"'/= ]+)=(?P<q>['"])(?P<value>.*?)(?P=q) # attr="value"
| # OR | # OR
\s+(?P<attr1>[^>"'/= ]+)=(?P<value1>[^> ]+) # attr=value \s+(?P<attr1>[^>"'/= ]+)=(?P<value1>[^> ]+) # attr=value
| # OR | # OR
\s+(?P<attr2>[^>"'/= ]+) # attr \s+(?P<attr2>[^>"'/= ]+) # attr
""" """
left_tag_pattern = r'^\<(?P<tag>[^> ]+)(?P<attrs>(%s)*)\s*\/?\>?' % attrs_pattern left_tag_pattern = r'^\<(?P<tag>[^> ]+)(?P<attrs>(%s)*)\s*\/?\>?' % attrs_pattern
attrs_re = re.compile(attrs_pattern, re.VERBOSE) attrs_re = re.compile(attrs_pattern, re.VERBOSE)
left_tag_re = re.compile(left_tag_pattern, re.VERBOSE) left_tag_re = re.compile(left_tag_pattern, re.VERBOSE)
markdown_in_raw = False markdown_in_raw = False
def _get_left_tag(self, block): def _get_left_tag(self, block):
m = self.left_tag_re.match(block) m = self.left_tag_re.match(block)
if m: if m:
tag = m.group('tag') tag = m.group('tag')
raw_attrs = m.group('attrs') raw_attrs = m.group('attrs')
attrs = {} attrs = {}
if raw_attrs: if raw_attrs:
for ma in self.attrs_re.finditer(raw_attrs): for ma in self.attrs_re.finditer(raw_attrs):
if ma.group('attr'): if ma.group('attr'):
if ma.group('value'): if ma.group('value'):
attrs[ma.group('attr').strip()] = ma.group('value') attrs[ma.group('attr').strip()] = ma.group('value')
else: else:
attrs[ma.group('attr').strip()] = "" attrs[ma.group('attr').strip()] = ""
elif ma.group('attr1'): elif ma.group('attr1'):
if ma.group('value1'): if ma.group('value1'):
attrs[ma.group('attr1').strip()] = ma.group('value1') attrs[ma.group('attr1').strip()] = ma.group('value1')
else: else:
attrs[ma.group('attr1').strip()] = "" attrs[ma.group('attr1').strip()] = ""
elif ma.group('attr2'): elif ma.group('attr2'):
attrs[ma.group('attr2').strip()] = "" attrs[ma.group('attr2').strip()] = ""
return tag, len(m.group(0)), attrs return tag, len(m.group(0)), attrs
else: else:
tag = block[1:].split(">", 1)[0].lower() tag = block[1:].split(">", 1)[0].lower()
return tag, len(tag)+2, {} return tag, len(tag)+2, {}
def _recursive_tagfind(self, ltag, rtag, start_index, block): def _recursive_tagfind(self, ltag, rtag, start_index, block):
while 1: while 1:
i = block.find(rtag, start_index) i = block.find(rtag, start_index)
if i == -1: if i == -1:
return -1 return -1
j = block.find(ltag, start_index) j = block.find(ltag, start_index)
# if no ltag, or rtag found before another ltag, return index # if no ltag, or rtag found before another ltag, return index
if (j > i or j == -1): if (j > i or j == -1):
return i + len(rtag) return i + len(rtag)
# another ltag found before rtag, use end of ltag as starting # another ltag found before rtag, use end of ltag as starting
# point and search again # point and search again
j = block.find('>', j) j = block.find('>', j)
start_index = self._recursive_tagfind(ltag, rtag, j + 1, block) start_index = self._recursive_tagfind(ltag, rtag, j + 1, block)
if start_index == -1: if start_index == -1:
# HTML potentially malformed- ltag has no corresponding # HTML potentially malformed- ltag has no corresponding
# rtag # rtag
return -1 return -1
def _get_right_tag(self, left_tag, left_index, block): def _get_right_tag(self, left_tag, left_index, block):
for p in self.right_tag_patterns: for p in self.right_tag_patterns:
tag = p % left_tag tag = p % left_tag
i = self._recursive_tagfind("<%s" % left_tag, tag, left_index, block) i = self._recursive_tagfind("<%s" % left_tag, tag, left_index, block)
if i > 2: if i > 2:
return tag.lstrip("<").rstrip(">"), i return tag.lstrip("<").rstrip(">"), i
return block.rstrip()[-left_index:-1].lower(), len(block) return block.rstrip()[-left_index:-1].lower(), len(block)
def _equal_tags(self, left_tag, right_tag): def _equal_tags(self, left_tag, right_tag):
if left_tag[0] in ['?', '@', '%']: # handle PHP, etc. if left_tag[0] in ['?', '@', '%']: # handle PHP, etc.
return True return True
if ("/" + left_tag) == right_tag: if ("/" + left_tag) == right_tag:
return True return True
if (right_tag == "--" and left_tag == "--"): if (right_tag == "--" and left_tag == "--"):
return True return True
elif left_tag == right_tag[1:] \ elif left_tag == right_tag[1:] \
and right_tag[0] == "/": and right_tag[0] == "/":
return True return True
else: else:
return False return False
def _is_oneliner(self, tag): def _is_oneliner(self, tag):
return (tag in ['hr', 'hr/']) return (tag in ['hr', 'hr/'])
def _stringindex_to_listindex(self, stringindex, items): def _stringindex_to_listindex(self, stringindex, items):
""" """
Same effect as concatenating the strings in items, Same effect as concatenating the strings in items,
finding the character to which stringindex refers in that string, finding the character to which stringindex refers in that string,
and returning the item in which that character resides. and returning the index of the item in which that character resides.
""" """
items.append('dummy') items.append('dummy')
i, count = 0, 0 i, count = 0, 0
while count <= stringindex: while count <= stringindex:
count += len(items[i]) count += len(items[i])
i += 1 i += 1
return i - 1 return i - 1
def _nested_markdown_in_html(self, items): def _nested_markdown_in_html(self, items):
"""Find and process html child elements of the given element block.""" """Find and process html child elements of the given element block."""
for i, item in enumerate(items): for i, item in enumerate(items):
if self.left_tag_re.match(item): if self.left_tag_re.match(item):
left_tag, left_index, attrs = \ left_tag, left_index, attrs = \
self._get_left_tag(''.join(items[i:])) self._get_left_tag(''.join(items[i:]))
right_tag, data_index = self._get_right_tag( right_tag, data_index = self._get_right_tag(
left_tag, left_index, ''.join(items[i:])) left_tag, left_index, ''.join(items[i:]))
right_listindex = \ right_listindex = \
self._stringindex_to_listindex(data_index, items[i:]) + i self._stringindex_to_listindex(data_index, items[i:]) + i
if 'markdown' in attrs.keys(): if 'markdown' in attrs.keys():
items[i] = items[i][left_index:] # remove opening tag items[i] = items[i][left_index:] # remove opening tag
placeholder = self.markdown.htmlStash.store_tag( placeholder = self.markdown.htmlStash.store_tag(
left_tag, attrs, i + 1, right_listindex + 1) left_tag, attrs, i + 1, right_listindex + 1)
items.insert(i, placeholder) items.insert(i, placeholder)
if len(items) - right_listindex <= 1: # last nest, no tail if len(items) - right_listindex <= 1: # last nest, no tail
right_listindex -= 1 right_listindex -= 1
items[right_listindex] = items[right_listindex][ items[right_listindex] = items[right_listindex][
:-len(right_tag) - 2] # remove closing tag :-len(right_tag) - 2] # remove closing tag
else: # raw html else: # raw html
if len(items) - right_listindex <= 1: # last element if len(items) - right_listindex <= 1: # last element
right_listindex -= 1 right_listindex -= 1
placeholder = self.markdown.htmlStash.store('\n\n'.join( placeholder = self.markdown.htmlStash.store('\n\n'.join(
items[i:right_listindex])) items[i:right_listindex + 1]))
del items[i:right_listindex] del items[i:right_listindex + 1]
items.insert(i, placeholder) items.insert(i, placeholder)
return items return items
def run(self, lines): def run(self, lines):
text = "\n".join(lines) text = "\n".join(lines)
new_blocks = [] new_blocks = []
text = text.rsplit("\n\n") text = text.rsplit("\n\n")
items = [] items = []
left_tag = '' left_tag = ''
right_tag = '' right_tag = ''
in_tag = False # flag in_tag = False # flag
while text: while text:
block = text[0] block = text[0]
if block.startswith("\n"): if block.startswith("\n"):
block = block[1:] block = block[1:]
text = text[1:] text = text[1:]
if block.startswith("\n"): if block.startswith("\n"):
block = block[1:] block = block[1:]
if not in_tag: if not in_tag:
if block.startswith("<") and len(block.strip()) > 1: if block.startswith("<") and len(block.strip()) > 1:
if block[1:4] == "!--": if block[1:4] == "!--":
# is a comment block # is a comment block
left_tag, left_index, attrs = "--", 2, {} left_tag, left_index, attrs = "--", 2, {}
else: else:
left_tag, left_index, attrs = self._get_left_tag(block) left_tag, left_index, attrs = self._get_left_tag(block)
right_tag, data_index = self._get_right_tag(left_tag, right_tag, data_index = self._get_right_tag(left_tag,
left_index, left_index,
block) block)
# keep checking conditions below and maybe just append # keep checking conditions below and maybe just append
if data_index < len(block) \ if data_index < len(block) \
and (util.isBlockLevel(left_tag) and (util.isBlockLevel(left_tag)
or left_tag == '--'): or left_tag == '--'):
text.insert(0, block[data_index:]) text.insert(0, block[data_index:])
block = block[:data_index] block = block[:data_index]
if not (util.isBlockLevel(left_tag) \ if not (util.isBlockLevel(left_tag) \
or block[1] in ["!", "?", "@", "%"]): or block[1] in ["!", "?", "@", "%"]):
new_blocks.append(block) new_blocks.append(block)
continue continue
if self._is_oneliner(left_tag): if self._is_oneliner(left_tag):
new_blocks.append(block.strip()) new_blocks.append(block.strip())
continue continue
if block.rstrip().endswith(">") \ if block.rstrip().endswith(">") \
and self._equal_tags(left_tag, right_tag): and self._equal_tags(left_tag, right_tag):
if self.markdown_in_raw and 'markdown' in attrs.keys(): if self.markdown_in_raw and 'markdown' in attrs.keys():
block = block[left_index:-len(right_tag) - 2] block = block[left_index:-len(right_tag) - 2]
new_blocks.append(self.markdown.htmlStash. new_blocks.append(self.markdown.htmlStash.
store_tag(left_tag, attrs, 0, 2)) store_tag(left_tag, attrs, 0, 2))
new_blocks.extend([block]) new_blocks.extend([block])
else: else:
new_blocks.append( new_blocks.append(
self.markdown.htmlStash.store(block.strip())) self.markdown.htmlStash.store(block.strip()))
continue continue
else: else:
# if is block level tag and is not complete # if is block level tag and is not complete
if (not self._equal_tags(left_tag, right_tag)) and \ if (not self._equal_tags(left_tag, right_tag)) and \
(util.isBlockLevel(left_tag) or left_tag == "--"): (util.isBlockLevel(left_tag) or left_tag == "--"):
items.append(block.strip()) items.append(block.strip())
in_tag = True in_tag = True
else: else:
new_blocks.append( new_blocks.append(
self.markdown.htmlStash.store(block.strip())) self.markdown.htmlStash.store(block.strip()))
continue continue
else: else:
new_blocks.append(block) new_blocks.append(block)
else: else:
items.append(block) items.append(block)
right_tag, data_index = self._get_right_tag(left_tag, 0, block) right_tag, data_index = self._get_right_tag(left_tag, 0, block)
if self._equal_tags(left_tag, right_tag): if self._equal_tags(left_tag, right_tag):
# if find closing tag # if find closing tag
if data_index < len(block): if data_index < len(block):
# we have more text after right_tag # we have more text after right_tag
items[-1] = block[:data_index] items[-1] = block[:data_index]
text.insert(0, block[data_index:]) text.insert(0, block[data_index:])
in_tag = False in_tag = False
if self.markdown_in_raw and 'markdown' in attrs.keys(): if self.markdown_in_raw and 'markdown' in attrs.keys():
items[0] = items[0][left_index:] items[0] = items[0][left_index:]
items[-1] = items[-1][:-len(right_tag) - 2] items[-1] = items[-1][:-len(right_tag) - 2]
if items[len(items) - 1]: # not a newline/empty string if items[len(items) - 1]: # not a newline/empty string
right_index = len(items) + 3 right_index = len(items) + 3
else: else:
right_index = len(items) + 2 right_index = len(items) + 2
new_blocks.append(self.markdown.htmlStash.store_tag( new_blocks.append(self.markdown.htmlStash.store_tag(
left_tag, attrs, 0, right_index)) left_tag, attrs, 0, right_index))
placeholderslen = len(self.markdown.htmlStash.tag_data) placeholderslen = len(self.markdown.htmlStash.tag_data)
new_blocks.extend( new_blocks.extend(
self._nested_markdown_in_html(items)) self._nested_markdown_in_html(items))
nests = len(self.markdown.htmlStash.tag_data) - \ nests = len(self.markdown.htmlStash.tag_data) - \
placeholderslen placeholderslen
self.markdown.htmlStash.tag_data[-1 - nests][ self.markdown.htmlStash.tag_data[-1 - nests][
'right_index'] += nests - 2 'right_index'] += nests - 2
else: else:
new_blocks.append( new_blocks.append(
self.markdown.htmlStash.store('\n\n'.join(items))) self.markdown.htmlStash.store('\n\n'.join(items)))
items = [] items = []
if items: if items:
if self.markdown_in_raw and 'markdown' in attrs.keys(): if self.markdown_in_raw and 'markdown' in attrs.keys():
items[0] = items[0][left_index:] items[0] = items[0][left_index:]
items[-1] = items[-1][:-len(right_tag) - 2] items[-1] = items[-1][:-len(right_tag) - 2]
if items[len(items) - 1]: # not a newline/empty string if items[len(items) - 1]: # not a newline/empty string
right_index = len(items) + 3 right_index = len(items) + 3
else: else:
right_index = len(items) + 2 right_index = len(items) + 2
new_blocks.append( new_blocks.append(
self.markdown.htmlStash.store_tag( self.markdown.htmlStash.store_tag(
left_tag, attrs, 0, right_index)) left_tag, attrs, 0, right_index))
placeholderslen = len(self.markdown.htmlStash.tag_data) placeholderslen = len(self.markdown.htmlStash.tag_data)
new_blocks.extend(self._nested_markdown_in_html(items)) new_blocks.extend(self._nested_markdown_in_html(items))
nests = len(self.markdown.htmlStash.tag_data) - placeholderslen nests = len(self.markdown.htmlStash.tag_data) - placeholderslen
self.markdown.htmlStash.tag_data[-1 - nests][ self.markdown.htmlStash.tag_data[-1 - nests][
'right_index'] += nests - 2 'right_index'] += nests - 2
else: else:
new_blocks.append( new_blocks.append(
self.markdown.htmlStash.store('\n\n'.join(items))) self.markdown.htmlStash.store('\n\n'.join(items)))
new_blocks.append('\n') new_blocks.append('\n')
new_text = "\n\n".join(new_blocks) new_text = "\n\n".join(new_blocks)
return new_text.split("\n") return new_text.split("\n")
class ReferencePreprocessor(Preprocessor): class ReferencePreprocessor(Preprocessor):
""" Remove reference definitions from text and store for later use. """ """ Remove reference definitions from text and store for later use. """
TITLE = r'[ ]*(\"(.*)\"|\'(.*)\'|\((.*)\))[ ]*' TITLE = r'[ ]*(\"(.*)\"|\'(.*)\'|\((.*)\))[ ]*'
RE = re.compile(r'^[ ]{0,3}\[([^\]]*)\]:\s*([^ ]*)[ ]*(%s)?$' % TITLE, re.DOTALL) RE = re.compile(r'^[ ]{0,3}\[([^\]]*)\]:\s*([^ ]*)[ ]*(%s)?$' % TITLE, re.DOTALL)
TITLE_RE = re.compile(r'^%s$' % TITLE) TITLE_RE = re.compile(r'^%s$' % TITLE)
def run (self, lines): def run (self, lines):
new_text = []; new_text = [];
while lines: while lines:
line = lines.pop(0) line = lines.pop(0)
m = self.RE.match(line) m = self.RE.match(line)
if m: if m:
id = m.group(1).strip().lower() id = m.group(1).strip().lower()
link = m.group(2).lstrip('<').rstrip('>') link = m.group(2).lstrip('<').rstrip('>')
t = m.group(5) or m.group(6) or m.group(7) t = m.group(5) or m.group(6) or m.group(7)
if not t: if not t:
# Check next line for title # Check next line for title
tm = self.TITLE_RE.match(lines[0]) tm = self.TITLE_RE.match(lines[0])
if tm: if tm:
lines.pop(0) lines.pop(0)
t = tm.group(2) or tm.group(3) or tm.group(4) t = tm.group(2) or tm.group(3) or tm.group(4)
self.markdown.references[id] = (link, t) self.markdown.references[id] = (link, t)
else: else:
new_text.append(line) new_text.append(line)
return new_text #+ "\n" return new_text #+ "\n"

View File

@@ -1,276 +1,276 @@
# markdown/searializers.py # markdown/searializers.py
# #
# Add x/html serialization to Elementree # Add x/html serialization to Elementree
# Taken from ElementTree 1.3 preview with slight modifications # Taken from ElementTree 1.3 preview with slight modifications
# #
# Copyright (c) 1999-2007 by Fredrik Lundh. All rights reserved. # Copyright (c) 1999-2007 by Fredrik Lundh. All rights reserved.
# #
# fredrik@pythonware.com # fredrik@pythonware.com
# http://www.pythonware.com # http://www.pythonware.com
# #
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# The ElementTree toolkit is # The ElementTree toolkit is
# #
# Copyright (c) 1999-2007 by Fredrik Lundh # Copyright (c) 1999-2007 by Fredrik Lundh
# #
# By obtaining, using, and/or copying this software and/or its # By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood, # associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions: # and will comply with the following terms and conditions:
# #
# Permission to use, copy, modify, and distribute this software and # Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is # its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in # hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission # all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of # notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity # Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written # pertaining to distribution of the software without specific, written
# prior permission. # prior permission.
# #
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD # SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- # TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR # ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY # BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, # DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE. # OF THIS SOFTWARE.
# -------------------------------------------------------------------- # --------------------------------------------------------------------
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from . import util from . import util
ElementTree = util.etree.ElementTree ElementTree = util.etree.ElementTree
QName = util.etree.QName QName = util.etree.QName
if hasattr(util.etree, 'test_comment'): if hasattr(util.etree, 'test_comment'):
Comment = util.etree.test_comment Comment = util.etree.test_comment
else: else:
Comment = util.etree.Comment Comment = util.etree.Comment
PI = util.etree.PI PI = util.etree.PI
ProcessingInstruction = util.etree.ProcessingInstruction ProcessingInstruction = util.etree.ProcessingInstruction
__all__ = ['to_html_string', 'to_xhtml_string'] __all__ = ['to_html_string', 'to_xhtml_string']
HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr", HTML_EMPTY = ("area", "base", "basefont", "br", "col", "frame", "hr",
"img", "input", "isindex", "link", "meta" "param") "img", "input", "isindex", "link", "meta" "param")
try: try:
HTML_EMPTY = set(HTML_EMPTY) HTML_EMPTY = set(HTML_EMPTY)
except NameError: except NameError:
pass pass
_namespace_map = { _namespace_map = {
# "well-known" namespace prefixes # "well-known" namespace prefixes
"http://www.w3.org/XML/1998/namespace": "xml", "http://www.w3.org/XML/1998/namespace": "xml",
"http://www.w3.org/1999/xhtml": "html", "http://www.w3.org/1999/xhtml": "html",
"http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
"http://schemas.xmlsoap.org/wsdl/": "wsdl", "http://schemas.xmlsoap.org/wsdl/": "wsdl",
# xml schema # xml schema
"http://www.w3.org/2001/XMLSchema": "xs", "http://www.w3.org/2001/XMLSchema": "xs",
"http://www.w3.org/2001/XMLSchema-instance": "xsi", "http://www.w3.org/2001/XMLSchema-instance": "xsi",
# dublic core # dublic core
"http://purl.org/dc/elements/1.1/": "dc", "http://purl.org/dc/elements/1.1/": "dc",
} }
def _raise_serialization_error(text): def _raise_serialization_error(text):
raise TypeError( raise TypeError(
"cannot serialize %r (type %s)" % (text, type(text).__name__) "cannot serialize %r (type %s)" % (text, type(text).__name__)
) )
def _encode(text, encoding): def _encode(text, encoding):
try: try:
return text.encode(encoding, "xmlcharrefreplace") return text.encode(encoding, "xmlcharrefreplace")
except (TypeError, AttributeError): except (TypeError, AttributeError):
_raise_serialization_error(text) _raise_serialization_error(text)
def _escape_cdata(text): def _escape_cdata(text):
# escape character data # escape character data
try: try:
# it's worth avoiding do-nothing calls for strings that are # it's worth avoiding do-nothing calls for strings that are
# shorter than 500 character, or so. assume that's, by far, # shorter than 500 character, or so. assume that's, by far,
# the most common case in most applications. # the most common case in most applications.
if "&" in text: if "&" in text:
text = text.replace("&", "&amp;") text = text.replace("&", "&amp;")
if "<" in text: if "<" in text:
text = text.replace("<", "&lt;") text = text.replace("<", "&lt;")
if ">" in text: if ">" in text:
text = text.replace(">", "&gt;") text = text.replace(">", "&gt;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError):
_raise_serialization_error(text) _raise_serialization_error(text)
def _escape_attrib(text): def _escape_attrib(text):
# escape attribute value # escape attribute value
try: try:
if "&" in text: if "&" in text:
text = text.replace("&", "&amp;") text = text.replace("&", "&amp;")
if "<" in text: if "<" in text:
text = text.replace("<", "&lt;") text = text.replace("<", "&lt;")
if ">" in text: if ">" in text:
text = text.replace(">", "&gt;") text = text.replace(">", "&gt;")
if "\"" in text: if "\"" in text:
text = text.replace("\"", "&quot;") text = text.replace("\"", "&quot;")
if "\n" in text: if "\n" in text:
text = text.replace("\n", "&#10;") text = text.replace("\n", "&#10;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError):
_raise_serialization_error(text) _raise_serialization_error(text)
def _escape_attrib_html(text): def _escape_attrib_html(text):
# escape attribute value # escape attribute value
try: try:
if "&" in text: if "&" in text:
text = text.replace("&", "&amp;") text = text.replace("&", "&amp;")
if "<" in text: if "<" in text:
text = text.replace("<", "&lt;") text = text.replace("<", "&lt;")
if ">" in text: if ">" in text:
text = text.replace(">", "&gt;") text = text.replace(">", "&gt;")
if "\"" in text: if "\"" in text:
text = text.replace("\"", "&quot;") text = text.replace("\"", "&quot;")
return text return text
except (TypeError, AttributeError): except (TypeError, AttributeError):
_raise_serialization_error(text) _raise_serialization_error(text)
def _serialize_html(write, elem, qnames, namespaces, format): def _serialize_html(write, elem, qnames, namespaces, format):
tag = elem.tag tag = elem.tag
text = elem.text text = elem.text
if tag is Comment: if tag is Comment:
write("<!--%s-->" % _escape_cdata(text)) write("<!--%s-->" % _escape_cdata(text))
elif tag is ProcessingInstruction: elif tag is ProcessingInstruction:
write("<?%s?>" % _escape_cdata(text)) write("<?%s?>" % _escape_cdata(text))
else: else:
tag = qnames[tag] tag = qnames[tag]
if tag is None: if tag is None:
if text: if text:
write(_escape_cdata(text)) write(_escape_cdata(text))
for e in elem: for e in elem:
_serialize_html(write, e, qnames, None, format) _serialize_html(write, e, qnames, None, format)
else: else:
write("<" + tag) write("<" + tag)
items = elem.items() items = elem.items()
if items or namespaces: if items or namespaces:
items = sorted(items) # lexical order items = sorted(items) # lexical order
for k, v in items: for k, v in items:
if isinstance(k, QName): if isinstance(k, QName):
k = k.text k = k.text
if isinstance(v, QName): if isinstance(v, QName):
v = qnames[v.text] v = qnames[v.text]
else: else:
v = _escape_attrib_html(v) v = _escape_attrib_html(v)
if qnames[k] == v and format == 'html': if qnames[k] == v and format == 'html':
# handle boolean attributes # handle boolean attributes
write(" %s" % v) write(" %s" % v)
else: else:
write(" %s=\"%s\"" % (qnames[k], v)) write(" %s=\"%s\"" % (qnames[k], v))
if namespaces: if namespaces:
items = namespaces.items() items = namespaces.items()
items.sort(key=lambda x: x[1]) # sort on prefix items.sort(key=lambda x: x[1]) # sort on prefix
for v, k in items: for v, k in items:
if k: if k:
k = ":" + k k = ":" + k
write(" xmlns%s=\"%s\"" % (k, _escape_attrib(v))) write(" xmlns%s=\"%s\"" % (k, _escape_attrib(v)))
if format == "xhtml" and tag.lower() in HTML_EMPTY: if format == "xhtml" and tag.lower() in HTML_EMPTY:
write(" />") write(" />")
else: else:
write(">") write(">")
if text: if text:
if tag.lower() in ["script", "style"]: if tag.lower() in ["script", "style"]:
write(text) write(text)
else: else:
write(_escape_cdata(text)) write(_escape_cdata(text))
for e in elem: for e in elem:
_serialize_html(write, e, qnames, None, format) _serialize_html(write, e, qnames, None, format)
if tag.lower() not in HTML_EMPTY: if tag.lower() not in HTML_EMPTY:
write("</" + tag + ">") write("</" + tag + ">")
if elem.tail: if elem.tail:
write(_escape_cdata(elem.tail)) write(_escape_cdata(elem.tail))
def _write_html(root, def _write_html(root,
encoding=None, encoding=None,
default_namespace=None, default_namespace=None,
format="html"): format="html"):
assert root is not None assert root is not None
data = [] data = []
write = data.append write = data.append
qnames, namespaces = _namespaces(root, default_namespace) qnames, namespaces = _namespaces(root, default_namespace)
_serialize_html(write, root, qnames, namespaces, format) _serialize_html(write, root, qnames, namespaces, format)
if encoding is None: if encoding is None:
return "".join(data) return "".join(data)
else: else:
return _encode("".join(data)) return _encode("".join(data))
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# serialization support # serialization support
def _namespaces(elem, default_namespace=None): def _namespaces(elem, default_namespace=None):
# identify namespaces used in this tree # identify namespaces used in this tree
# maps qnames to *encoded* prefix:local names # maps qnames to *encoded* prefix:local names
qnames = {None: None} qnames = {None: None}
# maps uri:s to prefixes # maps uri:s to prefixes
namespaces = {} namespaces = {}
if default_namespace: if default_namespace:
namespaces[default_namespace] = "" namespaces[default_namespace] = ""
def add_qname(qname): def add_qname(qname):
# calculate serialized qname representation # calculate serialized qname representation
try: try:
if qname[:1] == "{": if qname[:1] == "{":
uri, tag = qname[1:].split("}", 1) uri, tag = qname[1:].split("}", 1)
prefix = namespaces.get(uri) prefix = namespaces.get(uri)
if prefix is None: if prefix is None:
prefix = _namespace_map.get(uri) prefix = _namespace_map.get(uri)
if prefix is None: if prefix is None:
prefix = "ns%d" % len(namespaces) prefix = "ns%d" % len(namespaces)
if prefix != "xml": if prefix != "xml":
namespaces[uri] = prefix namespaces[uri] = prefix
if prefix: if prefix:
qnames[qname] = "%s:%s" % (prefix, tag) qnames[qname] = "%s:%s" % (prefix, tag)
else: else:
qnames[qname] = tag # default element qnames[qname] = tag # default element
else: else:
if default_namespace: if default_namespace:
raise ValueError( raise ValueError(
"cannot use non-qualified names with " "cannot use non-qualified names with "
"default_namespace option" "default_namespace option"
) )
qnames[qname] = qname qnames[qname] = qname
except TypeError: except TypeError:
_raise_serialization_error(qname) _raise_serialization_error(qname)
# populate qname and namespaces table # populate qname and namespaces table
try: try:
iterate = elem.iter iterate = elem.iter
except AttributeError: except AttributeError:
iterate = elem.getiterator # cET compatibility iterate = elem.getiterator # cET compatibility
for elem in iterate(): for elem in iterate():
tag = elem.tag tag = elem.tag
if isinstance(tag, QName) and tag.text not in qnames: if isinstance(tag, QName) and tag.text not in qnames:
add_qname(tag.text) add_qname(tag.text)
elif isinstance(tag, util.string_type): elif isinstance(tag, util.string_type):
if tag not in qnames: if tag not in qnames:
add_qname(tag) add_qname(tag)
elif tag is not None and tag is not Comment and tag is not PI: elif tag is not None and tag is not Comment and tag is not PI:
_raise_serialization_error(tag) _raise_serialization_error(tag)
for key, value in elem.items(): for key, value in elem.items():
if isinstance(key, QName): if isinstance(key, QName):
key = key.text key = key.text
if key not in qnames: if key not in qnames:
add_qname(key) add_qname(key)
if isinstance(value, QName) and value.text not in qnames: if isinstance(value, QName) and value.text not in qnames:
add_qname(value.text) add_qname(value.text)
text = elem.text text = elem.text
if isinstance(text, QName) and text.text not in qnames: if isinstance(text, QName) and text.text not in qnames:
add_qname(text.text) add_qname(text.text)
return qnames, namespaces return qnames, namespaces
def to_html_string(element): def to_html_string(element):
return _write_html(ElementTree(element).getroot(), format="html") return _write_html(ElementTree(element).getroot(), format="html")
def to_xhtml_string(element): def to_xhtml_string(element):
return _write_html(ElementTree(element).getroot(), format="xhtml") return _write_html(ElementTree(element).getroot(), format="xhtml")

View File

@@ -1,360 +1,360 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from __future__ import absolute_import from __future__ import absolute_import
from . import util from . import util
from . import odict from . import odict
from . import inlinepatterns from . import inlinepatterns
def build_treeprocessors(md_instance, **kwargs): def build_treeprocessors(md_instance, **kwargs):
""" Build the default treeprocessors for Markdown. """ """ Build the default treeprocessors for Markdown. """
treeprocessors = odict.OrderedDict() treeprocessors = odict.OrderedDict()
treeprocessors["inline"] = InlineProcessor(md_instance) treeprocessors["inline"] = InlineProcessor(md_instance)
treeprocessors["prettify"] = PrettifyTreeprocessor(md_instance) treeprocessors["prettify"] = PrettifyTreeprocessor(md_instance)
return treeprocessors return treeprocessors
def isString(s): def isString(s):
""" Check if it's string """ """ Check if it's string """
if not isinstance(s, util.AtomicString): if not isinstance(s, util.AtomicString):
return isinstance(s, util.string_type) return isinstance(s, util.string_type)
return False return False
class Treeprocessor(util.Processor): class Treeprocessor(util.Processor):
""" """
Treeprocessors are run on the ElementTree object before serialization. Treeprocessors are run on the ElementTree object before serialization.
Each Treeprocessor implements a "run" method that takes a pointer to an Each Treeprocessor implements a "run" method that takes a pointer to an
ElementTree, modifies it as necessary and returns an ElementTree ElementTree, modifies it as necessary and returns an ElementTree
object. object.
Treeprocessors must extend markdown.Treeprocessor. Treeprocessors must extend markdown.Treeprocessor.
""" """
def run(self, root): def run(self, root):
""" """
Subclasses of Treeprocessor should implement a `run` method, which Subclasses of Treeprocessor should implement a `run` method, which
takes a root ElementTree. This method can return another ElementTree takes a root ElementTree. This method can return another ElementTree
object, and the existing root ElementTree will be replaced, or it can object, and the existing root ElementTree will be replaced, or it can
modify the current tree and return None. modify the current tree and return None.
""" """
pass pass
class InlineProcessor(Treeprocessor): class InlineProcessor(Treeprocessor):
""" """
A Treeprocessor that traverses a tree, applying inline patterns. A Treeprocessor that traverses a tree, applying inline patterns.
""" """
def __init__(self, md): def __init__(self, md):
self.__placeholder_prefix = util.INLINE_PLACEHOLDER_PREFIX self.__placeholder_prefix = util.INLINE_PLACEHOLDER_PREFIX
self.__placeholder_suffix = util.ETX self.__placeholder_suffix = util.ETX
self.__placeholder_length = 4 + len(self.__placeholder_prefix) \ self.__placeholder_length = 4 + len(self.__placeholder_prefix) \
+ len(self.__placeholder_suffix) + len(self.__placeholder_suffix)
self.__placeholder_re = util.INLINE_PLACEHOLDER_RE self.__placeholder_re = util.INLINE_PLACEHOLDER_RE
self.markdown = md self.markdown = md
def __makePlaceholder(self, type): def __makePlaceholder(self, type):
""" Generate a placeholder """ """ Generate a placeholder """
id = "%04d" % len(self.stashed_nodes) id = "%04d" % len(self.stashed_nodes)
hash = util.INLINE_PLACEHOLDER % id hash = util.INLINE_PLACEHOLDER % id
return hash, id return hash, id
def __findPlaceholder(self, data, index): def __findPlaceholder(self, data, index):
""" """
Extract id from data string, start from index Extract id from data string, start from index
Keyword arguments: Keyword arguments:
* data: string * data: string
* index: index, from which we start search * index: index, from which we start search
Returns: placeholder id and string index, after the found placeholder. Returns: placeholder id and string index, after the found placeholder.
""" """
m = self.__placeholder_re.search(data, index) m = self.__placeholder_re.search(data, index)
if m: if m:
return m.group(1), m.end() return m.group(1), m.end()
else: else:
return None, index + 1 return None, index + 1
def __stashNode(self, node, type): def __stashNode(self, node, type):
""" Add node to stash """ """ Add node to stash """
placeholder, id = self.__makePlaceholder(type) placeholder, id = self.__makePlaceholder(type)
self.stashed_nodes[id] = node self.stashed_nodes[id] = node
return placeholder return placeholder
def __handleInline(self, data, patternIndex=0): def __handleInline(self, data, patternIndex=0):
""" """
Process string with inline patterns and replace it Process string with inline patterns and replace it
with placeholders with placeholders
Keyword arguments: Keyword arguments:
* data: A line of Markdown text * data: A line of Markdown text
* patternIndex: The index of the inlinePattern to start with * patternIndex: The index of the inlinePattern to start with
Returns: String with placeholders. Returns: String with placeholders.
""" """
if not isinstance(data, util.AtomicString): if not isinstance(data, util.AtomicString):
startIndex = 0 startIndex = 0
while patternIndex < len(self.markdown.inlinePatterns): while patternIndex < len(self.markdown.inlinePatterns):
data, matched, startIndex = self.__applyPattern( data, matched, startIndex = self.__applyPattern(
self.markdown.inlinePatterns.value_for_index(patternIndex), self.markdown.inlinePatterns.value_for_index(patternIndex),
data, patternIndex, startIndex) data, patternIndex, startIndex)
if not matched: if not matched:
patternIndex += 1 patternIndex += 1
return data return data
def __processElementText(self, node, subnode, isText=True): def __processElementText(self, node, subnode, isText=True):
""" """
Process placeholders in Element.text or Element.tail Process placeholders in Element.text or Element.tail
of Elements popped from self.stashed_nodes. of Elements popped from self.stashed_nodes.
Keywords arguments: Keywords arguments:
* node: parent node * node: parent node
* subnode: processing node * subnode: processing node
* isText: bool variable, True - it's text, False - it's tail * isText: bool variable, True - it's text, False - it's tail
Returns: None Returns: None
""" """
if isText: if isText:
text = subnode.text text = subnode.text
subnode.text = None subnode.text = None
else: else:
text = subnode.tail text = subnode.tail
subnode.tail = None subnode.tail = None
childResult = self.__processPlaceholders(text, subnode) childResult = self.__processPlaceholders(text, subnode)
if not isText and node is not subnode: if not isText and node is not subnode:
pos = list(node).index(subnode) pos = list(node).index(subnode)
node.remove(subnode) node.remove(subnode)
else: else:
pos = 0 pos = 0
childResult.reverse() childResult.reverse()
for newChild in childResult: for newChild in childResult:
node.insert(pos, newChild) node.insert(pos, newChild)
def __processPlaceholders(self, data, parent): def __processPlaceholders(self, data, parent):
""" """
Process string with placeholders and generate ElementTree tree. Process string with placeholders and generate ElementTree tree.
Keyword arguments: Keyword arguments:
* data: string with placeholders instead of ElementTree elements. * data: string with placeholders instead of ElementTree elements.
* parent: Element, which contains processing inline data * parent: Element, which contains processing inline data
Returns: list with ElementTree elements with applied inline patterns. Returns: list with ElementTree elements with applied inline patterns.
""" """
def linkText(text): def linkText(text):
if text: if text:
if result: if result:
if result[-1].tail: if result[-1].tail:
result[-1].tail += text result[-1].tail += text
else: else:
result[-1].tail = text result[-1].tail = text
else: else:
if parent.text: if parent.text:
parent.text += text parent.text += text
else: else:
parent.text = text parent.text = text
result = [] result = []
strartIndex = 0 strartIndex = 0
while data: while data:
index = data.find(self.__placeholder_prefix, strartIndex) index = data.find(self.__placeholder_prefix, strartIndex)
if index != -1: if index != -1:
id, phEndIndex = self.__findPlaceholder(data, index) id, phEndIndex = self.__findPlaceholder(data, index)
if id in self.stashed_nodes: if id in self.stashed_nodes:
node = self.stashed_nodes.get(id) node = self.stashed_nodes.get(id)
if index > 0: if index > 0:
text = data[strartIndex:index] text = data[strartIndex:index]
linkText(text) linkText(text)
if not isString(node): # it's Element if not isString(node): # it's Element
for child in [node] + list(node): for child in [node] + list(node):
if child.tail: if child.tail:
if child.tail.strip(): if child.tail.strip():
self.__processElementText(node, child,False) self.__processElementText(node, child,False)
if child.text: if child.text:
if child.text.strip(): if child.text.strip():
self.__processElementText(child, child) self.__processElementText(child, child)
else: # it's just a string else: # it's just a string
linkText(node) linkText(node)
strartIndex = phEndIndex strartIndex = phEndIndex
continue continue
strartIndex = phEndIndex strartIndex = phEndIndex
result.append(node) result.append(node)
else: # wrong placeholder else: # wrong placeholder
end = index + len(self.__placeholder_prefix) end = index + len(self.__placeholder_prefix)
linkText(data[strartIndex:end]) linkText(data[strartIndex:end])
strartIndex = end strartIndex = end
else: else:
text = data[strartIndex:] text = data[strartIndex:]
if isinstance(data, util.AtomicString): if isinstance(data, util.AtomicString):
# We don't want to loose the AtomicString # We don't want to loose the AtomicString
text = util.AtomicString(text) text = util.AtomicString(text)
linkText(text) linkText(text)
data = "" data = ""
return result return result
def __applyPattern(self, pattern, data, patternIndex, startIndex=0): def __applyPattern(self, pattern, data, patternIndex, startIndex=0):
""" """
Check if the line fits the pattern, create the necessary Check if the line fits the pattern, create the necessary
elements, add it to stashed_nodes. elements, add it to stashed_nodes.
Keyword arguments: Keyword arguments:
* data: the text to be processed * data: the text to be processed
* pattern: the pattern to be checked * pattern: the pattern to be checked
* patternIndex: index of current pattern * patternIndex: index of current pattern
* startIndex: string index, from which we start searching * startIndex: string index, from which we start searching
Returns: String with placeholders instead of ElementTree elements. Returns: String with placeholders instead of ElementTree elements.
""" """
match = pattern.getCompiledRegExp().match(data[startIndex:]) match = pattern.getCompiledRegExp().match(data[startIndex:])
leftData = data[:startIndex] leftData = data[:startIndex]
if not match: if not match:
return data, False, 0 return data, False, 0
node = pattern.handleMatch(match) node = pattern.handleMatch(match)
if node is None: if node is None:
return data, True, len(leftData)+match.span(len(match.groups()))[0] return data, True, len(leftData)+match.span(len(match.groups()))[0]
if not isString(node): if not isString(node):
if not isinstance(node.text, util.AtomicString): if not isinstance(node.text, util.AtomicString):
# We need to process current node too # We need to process current node too
for child in [node] + list(node): for child in [node] + list(node):
if not isString(node): if not isString(node):
if child.text: if child.text:
child.text = self.__handleInline(child.text, child.text = self.__handleInline(child.text,
patternIndex + 1) patternIndex + 1)
if child.tail: if child.tail:
child.tail = self.__handleInline(child.tail, child.tail = self.__handleInline(child.tail,
patternIndex) patternIndex)
placeholder = self.__stashNode(node, pattern.type()) placeholder = self.__stashNode(node, pattern.type())
return "%s%s%s%s" % (leftData, return "%s%s%s%s" % (leftData,
match.group(1), match.group(1),
placeholder, match.groups()[-1]), True, 0 placeholder, match.groups()[-1]), True, 0
def run(self, tree): def run(self, tree):
"""Apply inline patterns to a parsed Markdown tree. """Apply inline patterns to a parsed Markdown tree.
Iterate over ElementTree, find elements with inline tag, apply inline Iterate over ElementTree, find elements with inline tag, apply inline
patterns and append newly created Elements to tree. If you don't patterns and append newly created Elements to tree. If you don't
want to process your data with inline paterns, instead of normal string, want to process your data with inline paterns, instead of normal string,
use subclass AtomicString: use subclass AtomicString:
node.text = markdown.AtomicString("This will not be processed.") node.text = markdown.AtomicString("This will not be processed.")
Arguments: Arguments:
* tree: ElementTree object, representing Markdown tree. * tree: ElementTree object, representing Markdown tree.
Returns: ElementTree object with applied inline patterns. Returns: ElementTree object with applied inline patterns.
""" """
self.stashed_nodes = {} self.stashed_nodes = {}
stack = [tree] stack = [tree]
while stack: while stack:
currElement = stack.pop() currElement = stack.pop()
insertQueue = [] insertQueue = []
for child in currElement: for child in currElement:
if child.text and not isinstance(child.text, util.AtomicString): if child.text and not isinstance(child.text, util.AtomicString):
text = child.text text = child.text
child.text = None child.text = None
lst = self.__processPlaceholders(self.__handleInline( lst = self.__processPlaceholders(self.__handleInline(
text), child) text), child)
stack += lst stack += lst
insertQueue.append((child, lst)) insertQueue.append((child, lst))
if child.tail: if child.tail:
tail = self.__handleInline(child.tail) tail = self.__handleInline(child.tail)
dumby = util.etree.Element('d') dumby = util.etree.Element('d')
tailResult = self.__processPlaceholders(tail, dumby) tailResult = self.__processPlaceholders(tail, dumby)
if dumby.text: if dumby.text:
child.tail = dumby.text child.tail = dumby.text
else: else:
child.tail = None child.tail = None
pos = list(currElement).index(child) + 1 pos = list(currElement).index(child) + 1
tailResult.reverse() tailResult.reverse()
for newChild in tailResult: for newChild in tailResult:
currElement.insert(pos, newChild) currElement.insert(pos, newChild)
if len(child): if len(child):
stack.append(child) stack.append(child)
for element, lst in insertQueue: for element, lst in insertQueue:
if self.markdown.enable_attributes: if self.markdown.enable_attributes:
if element.text and isString(element.text): if element.text and isString(element.text):
element.text = \ element.text = \
inlinepatterns.handleAttributes(element.text, inlinepatterns.handleAttributes(element.text,
element) element)
i = 0 i = 0
for newChild in lst: for newChild in lst:
if self.markdown.enable_attributes: if self.markdown.enable_attributes:
# Processing attributes # Processing attributes
if newChild.tail and isString(newChild.tail): if newChild.tail and isString(newChild.tail):
newChild.tail = \ newChild.tail = \
inlinepatterns.handleAttributes(newChild.tail, inlinepatterns.handleAttributes(newChild.tail,
element) element)
if newChild.text and isString(newChild.text): if newChild.text and isString(newChild.text):
newChild.text = \ newChild.text = \
inlinepatterns.handleAttributes(newChild.text, inlinepatterns.handleAttributes(newChild.text,
newChild) newChild)
element.insert(i, newChild) element.insert(i, newChild)
i += 1 i += 1
return tree return tree
class PrettifyTreeprocessor(Treeprocessor): class PrettifyTreeprocessor(Treeprocessor):
""" Add linebreaks to the html document. """ """ Add linebreaks to the html document. """
def _prettifyETree(self, elem): def _prettifyETree(self, elem):
""" Recursively add linebreaks to ElementTree children. """ """ Recursively add linebreaks to ElementTree children. """
i = "\n" i = "\n"
if util.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']: if util.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']:
if (not elem.text or not elem.text.strip()) \ if (not elem.text or not elem.text.strip()) \
and len(elem) and util.isBlockLevel(elem[0].tag): and len(elem) and util.isBlockLevel(elem[0].tag):
elem.text = i elem.text = i
for e in elem: for e in elem:
if util.isBlockLevel(e.tag): if util.isBlockLevel(e.tag):
self._prettifyETree(e) self._prettifyETree(e)
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
if not elem.tail or not elem.tail.strip(): if not elem.tail or not elem.tail.strip():
elem.tail = i elem.tail = i
def run(self, root): def run(self, root):
""" Add linebreaks to ElementTree root object. """ """ Add linebreaks to ElementTree root object. """
self._prettifyETree(root) self._prettifyETree(root)
# Do <br />'s seperately as they are often in the middle of # Do <br />'s seperately as they are often in the middle of
# inline content and missed by _prettifyETree. # inline content and missed by _prettifyETree.
brs = root.getiterator('br') brs = root.getiterator('br')
for br in brs: for br in brs:
if not br.tail or not br.tail.strip(): if not br.tail or not br.tail.strip():
br.tail = '\n' br.tail = '\n'
else: else:
br.tail = '\n%s' % br.tail br.tail = '\n%s' % br.tail
# Clean up extra empty lines at end of code blocks. # Clean up extra empty lines at end of code blocks.
pres = root.getiterator('pre') pres = root.getiterator('pre')
for pre in pres: for pre in pres:
if len(pre) and pre[0].tag == 'code': if len(pre) and pre[0].tag == 'code':
pre[0].text = pre[0].text.rstrip() + '\n' pre[0].text = pre[0].text.rstrip() + '\n'

View File

@@ -1,163 +1,163 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import re import re
import sys import sys
""" """
Python 3 Stuff Python 3 Stuff
============================================================================= =============================================================================
""" """
PY3 = sys.version_info[0] == 3 PY3 = sys.version_info[0] == 3
if PY3: if PY3:
string_type = str string_type = str
text_type = str text_type = str
int2str = chr int2str = chr
else: else:
string_type = basestring string_type = basestring
text_type = unicode text_type = unicode
int2str = unichr int2str = unichr
""" """
Constants you might want to modify Constants you might want to modify
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
""" """
BLOCK_LEVEL_ELEMENTS = re.compile("^(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul" BLOCK_LEVEL_ELEMENTS = re.compile("^(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul"
"|script|noscript|form|fieldset|iframe|math" "|script|noscript|form|fieldset|iframe|math"
"|hr|hr/|style|li|dt|dd|thead|tbody" "|hr|hr/|style|li|dt|dd|thead|tbody"
"|tr|th|td|section|footer|header|group|figure" "|tr|th|td|section|footer|header|group|figure"
"|figcaption|aside|article|canvas|output" "|figcaption|aside|article|canvas|output"
"|progress|video)$", re.IGNORECASE) "|progress|video|nav)$", re.IGNORECASE)
# Placeholders # Placeholders
STX = '\u0002' # Use STX ("Start of text") for start-of-placeholder STX = '\u0002' # Use STX ("Start of text") for start-of-placeholder
ETX = '\u0003' # Use ETX ("End of text") for end-of-placeholder ETX = '\u0003' # Use ETX ("End of text") for end-of-placeholder
INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:" INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:"
INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX
INLINE_PLACEHOLDER_RE = re.compile(INLINE_PLACEHOLDER % r'([0-9]+)') INLINE_PLACEHOLDER_RE = re.compile(INLINE_PLACEHOLDER % r'([0-9]+)')
AMP_SUBSTITUTE = STX+"amp"+ETX AMP_SUBSTITUTE = STX+"amp"+ETX
HTML_PLACEHOLDER = STX + "wzxhzdk:%s" + ETX HTML_PLACEHOLDER = STX + "wzxhzdk:%s" + ETX
HTML_PLACEHOLDER_RE = re.compile(HTML_PLACEHOLDER % r'([0-9]+)') HTML_PLACEHOLDER_RE = re.compile(HTML_PLACEHOLDER % r'([0-9]+)')
TAG_PLACEHOLDER = STX + "hzzhzkh:%s" + ETX TAG_PLACEHOLDER = STX + "hzzhzkh:%s" + ETX
""" """
Constants you probably do not need to change Constants you probably do not need to change
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
""" """
RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'), RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'),
# Hebrew (0590-05FF), Arabic (0600-06FF), # Hebrew (0590-05FF), Arabic (0600-06FF),
# Syriac (0700-074F), Arabic supplement (0750-077F), # Syriac (0700-074F), Arabic supplement (0750-077F),
# Thaana (0780-07BF), Nko (07C0-07FF). # Thaana (0780-07BF), Nko (07C0-07FF).
('\u2D30', '\u2D7F'), # Tifinagh ('\u2D30', '\u2D7F'), # Tifinagh
) )
# Extensions should use "markdown.util.etree" instead of "etree" (or do `from # Extensions should use "markdown.util.etree" instead of "etree" (or do `from
# markdown.util import etree`). Do not import it by yourself. # markdown.util import etree`). Do not import it by yourself.
try: # Is the C implementation of ElementTree available? try: # Is the C implementation of ElementTree available?
import xml.etree.cElementTree as etree import xml.etree.cElementTree as etree
from xml.etree.ElementTree import Comment from xml.etree.ElementTree import Comment
# Serializers (including ours) test with non-c Comment # Serializers (including ours) test with non-c Comment
etree.test_comment = Comment etree.test_comment = Comment
if etree.VERSION < "1.0.5": if etree.VERSION < "1.0.5":
raise RuntimeError("cElementTree version 1.0.5 or higher is required.") raise RuntimeError("cElementTree version 1.0.5 or higher is required.")
except (ImportError, RuntimeError): except (ImportError, RuntimeError):
# Use the Python implementation of ElementTree? # Use the Python implementation of ElementTree?
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
if etree.VERSION < "1.1": if etree.VERSION < "1.1":
raise RuntimeError("ElementTree version 1.1 or higher is required") raise RuntimeError("ElementTree version 1.1 or higher is required")
""" """
AUXILIARY GLOBAL FUNCTIONS AUXILIARY GLOBAL FUNCTIONS
============================================================================= =============================================================================
""" """
def isBlockLevel(tag): def isBlockLevel(tag):
"""Check if the tag is a block level HTML tag.""" """Check if the tag is a block level HTML tag."""
if isinstance(tag, string_type): if isinstance(tag, string_type):
return BLOCK_LEVEL_ELEMENTS.match(tag) return BLOCK_LEVEL_ELEMENTS.match(tag)
# Some ElementTree tags are not strings, so return False. # Some ElementTree tags are not strings, so return False.
return False return False
def parseBoolValue(value, fail_on_errors=True): def parseBoolValue(value, fail_on_errors=True):
"""Parses a string representing bool value. If parsing was successful, """Parses a string representing bool value. If parsing was successful,
returns True or False. If parsing was not successful, raises returns True or False. If parsing was not successful, raises
ValueError, or, if fail_on_errors=False, returns None.""" ValueError, or, if fail_on_errors=False, returns None."""
if not isinstance(value, string_type): if not isinstance(value, string_type):
return bool(value) return bool(value)
elif value.lower() in ('true', 'yes', 'y', 'on', '1'): elif value.lower() in ('true', 'yes', 'y', 'on', '1'):
return True return True
elif value.lower() in ('false', 'no', 'n', 'off', '0'): elif value.lower() in ('false', 'no', 'n', 'off', '0'):
return False return False
elif fail_on_errors: elif fail_on_errors:
raise ValueError('Cannot parse bool value: %r' % value) raise ValueError('Cannot parse bool value: %r' % value)
""" """
MISC AUXILIARY CLASSES MISC AUXILIARY CLASSES
============================================================================= =============================================================================
""" """
class AtomicString(text_type): class AtomicString(text_type):
"""A string which should not be further processed.""" """A string which should not be further processed."""
pass pass
class Processor(object): class Processor(object):
def __init__(self, markdown_instance=None): def __init__(self, markdown_instance=None):
if markdown_instance: if markdown_instance:
self.markdown = markdown_instance self.markdown = markdown_instance
class HtmlStash(object): class HtmlStash(object):
""" """
This class is used for stashing HTML objects that we extract This class is used for stashing HTML objects that we extract
in the beginning and replace with place-holders. in the beginning and replace with place-holders.
""" """
def __init__(self): def __init__(self):
""" Create a HtmlStash. """ """ Create a HtmlStash. """
self.html_counter = 0 # for counting inline html segments self.html_counter = 0 # for counting inline html segments
self.rawHtmlBlocks = [] self.rawHtmlBlocks = []
self.tag_counter = 0 self.tag_counter = 0
self.tag_data = [] # list of dictionaries in the order tags appear self.tag_data = [] # list of dictionaries in the order tags appear
def store(self, html, safe=False): def store(self, html, safe=False):
""" """
Saves an HTML segment for later reinsertion. Returns a Saves an HTML segment for later reinsertion. Returns a
placeholder string that needs to be inserted into the placeholder string that needs to be inserted into the
document. document.
Keyword arguments: Keyword arguments:
* html: an html segment * html: an html segment
* safe: label an html segment as safe for safemode * safe: label an html segment as safe for safemode
Returns : a placeholder string Returns : a placeholder string
""" """
self.rawHtmlBlocks.append((html, safe)) self.rawHtmlBlocks.append((html, safe))
placeholder = self.get_placeholder(self.html_counter) placeholder = self.get_placeholder(self.html_counter)
self.html_counter += 1 self.html_counter += 1
return placeholder return placeholder
def reset(self): def reset(self):
self.html_counter = 0 self.html_counter = 0
self.rawHtmlBlocks = [] self.rawHtmlBlocks = []
def get_placeholder(self, key): def get_placeholder(self, key):
return HTML_PLACEHOLDER % key return HTML_PLACEHOLDER % key
def store_tag(self, tag, attrs, left_index, right_index): def store_tag(self, tag, attrs, left_index, right_index):
"""Store tag data and return a placeholder.""" """Store tag data and return a placeholder."""
self.tag_data.append({'tag': tag, 'attrs': attrs, self.tag_data.append({'tag': tag, 'attrs': attrs,
'left_index': left_index, 'left_index': left_index,
'right_index': right_index}) 'right_index': right_index})
placeholder = TAG_PLACEHOLDER % str(self.tag_counter) placeholder = TAG_PLACEHOLDER % str(self.tag_counter)
self.tag_counter += 1 # equal to the tag's index in self.tag_data self.tag_counter += 1 # equal to the tag's index in self.tag_data
return placeholder return placeholder