From a47beab54152119db652dd5b1945a198791bbade Mon Sep 17 00:00:00 2001 From: Jinkun Jang Date: Wed, 13 Mar 2013 02:21:26 +0900 Subject: [PATCH] Tizen 2.1 base Signed-off-by: Hyunjee Kim --- MANIFEST | 56 +++ PKG-INFO | 29 ++ bin/markdown | 42 ++ docs/AUTHORS | 44 ++ docs/CHANGE_LOG | 180 ++++++++ docs/INSTALL | 73 +++ docs/LICENSE | 30 ++ docs/README | 30 ++ docs/README.html | 12 + docs/command_line.txt | 98 ++++ docs/extensions/Abbreviations.txt | 53 +++ docs/extensions/CodeHilite.txt | 113 +++++ docs/extensions/Definition_Lists.txt | 55 +++ docs/extensions/Fenced_Code_Blocks.txt | 63 +++ docs/extensions/HTML_Tidy.txt | 27 ++ docs/extensions/HeaderId.txt | 104 +++++ docs/extensions/ImageLinks.txt | 27 ++ docs/extensions/Meta-Data.txt | 88 ++++ docs/extensions/RSS.txt | 35 ++ docs/extensions/Tables.txt | 53 +++ docs/extensions/Tables_of_Contents.txt | 50 ++ docs/extensions/WikiLinks.txt | 144 ++++++ docs/extensions/extra.txt | 43 ++ docs/extensions/footnotes.txt | 62 +++ docs/extensions/index.txt | 44 ++ docs/release-2.0.1.txt | 16 + docs/release-2.0.2.txt | 9 + docs/release-2.0.txt | 67 +++ docs/using_as_module.txt | 150 ++++++ docs/writing_extensions.txt | 594 ++++++++++++++++++++++++ markdown/__init__.py | 614 +++++++++++++++++++++++++ markdown/blockparser.py | 95 ++++ markdown/blockprocessors.py | 460 ++++++++++++++++++ markdown/commandline.py | 96 ++++ markdown/etree_loader.py | 33 ++ markdown/extensions/__init__.py | 0 markdown/extensions/abbr.py | 95 ++++ markdown/extensions/codehilite.py | 224 +++++++++ markdown/extensions/def_list.py | 104 +++++ markdown/extensions/extra.py | 49 ++ markdown/extensions/fenced_code.py | 117 +++++ markdown/extensions/footnotes.py | 307 +++++++++++++ markdown/extensions/headerid.py | 195 ++++++++ markdown/extensions/html_tidy.py | 62 +++ markdown/extensions/imagelinks.py | 119 +++++ markdown/extensions/meta.py | 90 ++++ markdown/extensions/rss.py | 114 +++++ markdown/extensions/tables.py | 97 ++++ markdown/extensions/toc.py | 136 ++++++ markdown/extensions/wikilinks.py | 155 +++++++ markdown/html4.py | 274 +++++++++++ markdown/inlinepatterns.py | 371 +++++++++++++++ markdown/odict.py | 162 +++++++ markdown/postprocessors.py | 77 ++++ markdown/preprocessors.py | 215 +++++++++ markdown/treeprocessors.py | 329 +++++++++++++ setup.py | 65 +++ 57 files changed, 7046 insertions(+) create mode 100644 MANIFEST create mode 100644 PKG-INFO create mode 100755 bin/markdown create mode 100644 docs/AUTHORS create mode 100644 docs/CHANGE_LOG create mode 100644 docs/INSTALL create mode 100644 docs/LICENSE create mode 100644 docs/README create mode 100644 docs/README.html create mode 100644 docs/command_line.txt create mode 100644 docs/extensions/Abbreviations.txt create mode 100644 docs/extensions/CodeHilite.txt create mode 100644 docs/extensions/Definition_Lists.txt create mode 100644 docs/extensions/Fenced_Code_Blocks.txt create mode 100644 docs/extensions/HTML_Tidy.txt create mode 100644 docs/extensions/HeaderId.txt create mode 100644 docs/extensions/ImageLinks.txt create mode 100644 docs/extensions/Meta-Data.txt create mode 100644 docs/extensions/RSS.txt create mode 100644 docs/extensions/Tables.txt create mode 100644 docs/extensions/Tables_of_Contents.txt create mode 100644 docs/extensions/WikiLinks.txt create mode 100644 docs/extensions/extra.txt create mode 100644 docs/extensions/footnotes.txt create mode 100644 docs/extensions/index.txt create mode 100644 docs/release-2.0.1.txt create mode 100644 docs/release-2.0.2.txt create mode 100644 docs/release-2.0.txt create mode 100644 docs/using_as_module.txt create mode 100644 docs/writing_extensions.txt create mode 100644 markdown/__init__.py create mode 100644 markdown/blockparser.py create mode 100644 markdown/blockprocessors.py create mode 100644 markdown/commandline.py create mode 100644 markdown/etree_loader.py create mode 100644 markdown/extensions/__init__.py create mode 100644 markdown/extensions/abbr.py create mode 100644 markdown/extensions/codehilite.py create mode 100644 markdown/extensions/def_list.py create mode 100644 markdown/extensions/extra.py create mode 100644 markdown/extensions/fenced_code.py create mode 100644 markdown/extensions/footnotes.py create mode 100644 markdown/extensions/headerid.py create mode 100644 markdown/extensions/html_tidy.py create mode 100644 markdown/extensions/imagelinks.py create mode 100644 markdown/extensions/meta.py create mode 100644 markdown/extensions/rss.py create mode 100644 markdown/extensions/tables.py create mode 100644 markdown/extensions/toc.py create mode 100644 markdown/extensions/wikilinks.py create mode 100644 markdown/html4.py create mode 100644 markdown/inlinepatterns.py create mode 100644 markdown/odict.py create mode 100644 markdown/postprocessors.py create mode 100644 markdown/preprocessors.py create mode 100644 markdown/treeprocessors.py create mode 100755 setup.py diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..b30a00c --- /dev/null +++ b/MANIFEST @@ -0,0 +1,56 @@ +MANIFEST +setup.py +bin/markdown +docs/AUTHORS +docs/CHANGE_LOG +docs/INSTALL +docs/LICENSE +docs/README +docs/README.html +docs/command_line.txt +docs/release-2.0.1.txt +docs/release-2.0.2.txt +docs/release-2.0.txt +docs/using_as_module.txt +docs/writing_extensions.txt +docs/extensions/Abbreviations.txt +docs/extensions/CodeHilite.txt +docs/extensions/Definition_Lists.txt +docs/extensions/Fenced_Code_Blocks.txt +docs/extensions/HTML_Tidy.txt +docs/extensions/HeaderId.txt +docs/extensions/ImageLinks.txt +docs/extensions/Meta-Data.txt +docs/extensions/RSS.txt +docs/extensions/Tables.txt +docs/extensions/Tables_of_Contents.txt +docs/extensions/WikiLinks.txt +docs/extensions/extra.txt +docs/extensions/footnotes.txt +docs/extensions/index.txt +markdown/__init__.py +markdown/blockparser.py +markdown/blockprocessors.py +markdown/commandline.py +markdown/etree_loader.py +markdown/html4.py +markdown/inlinepatterns.py +markdown/odict.py +markdown/postprocessors.py +markdown/preprocessors.py +markdown/treeprocessors.py +markdown/extensions/__init__.py +markdown/extensions/abbr.py +markdown/extensions/codehilite.py +markdown/extensions/def_list.py +markdown/extensions/extra.py +markdown/extensions/fenced_code.py +markdown/extensions/footnotes.py +markdown/extensions/headerid.py +markdown/extensions/html_tidy.py +markdown/extensions/imagelinks.py +markdown/extensions/meta.py +markdown/extensions/rss.py +markdown/extensions/tables.py +markdown/extensions/toc.py +markdown/extensions/wikilinks.py diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..0b8c489 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,29 @@ +Metadata-Version: 1.0 +Name: Markdown +Version: 2.0.3 +Summary: Python implementation of Markdown. +Home-page: http://www.freewisdom.org/projects/python-markdown +Author: Waylan Limberg +Author-email: waylan [at] gmail.com +License: BSD License +Download-URL: http://pypi.python.org/packages/source/M/Markdown/Markdown-2.0.3.tar.gz +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.3 +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.0 +Classifier: Topic :: Communications :: Email :: Filters +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries +Classifier: Topic :: Internet :: WWW/HTTP :: Site Management +Classifier: Topic :: Software Development :: Documentation +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Text Processing :: Markup :: HTML diff --git a/bin/markdown b/bin/markdown new file mode 100755 index 0000000..8d04cc9 --- /dev/null +++ b/bin/markdown @@ -0,0 +1,42 @@ +#!/usr/bin/env python +""" +Python Markdown, the Command Line Script +======================================== + +This is the command line script for Python Markdown. + +Basic use from the command line: + + markdown source.txt > destination.html + +Run "markdown --help" to see more options. + +See markdown/__init__.py for information on using Python Markdown as a module. + +## Authors and License + +Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and +maintained by [Yuri Takhteyev](http://www.freewisdom.org), [Waylan +Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com). + +Contact: markdown@freewisdom.org + +Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) +Copyright 200? Django Software Foundation (OrderedDict implementation) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see docs/LICENSE for details). +""" + +import logging +from markdown import COMMAND_LINE_LOGGING_LEVEL +from markdown import commandline + +# Setup a logger manually for compatibility with Python 2.3 +logger = logging.getLogger('MARKDOWN') +logger.setLevel(COMMAND_LINE_LOGGING_LEVEL) +logger.addHandler(logging.StreamHandler()) + +if __name__ == '__main__': + commandline.run() diff --git a/docs/AUTHORS b/docs/AUTHORS new file mode 100644 index 0000000..2843b56 --- /dev/null +++ b/docs/AUTHORS @@ -0,0 +1,44 @@ +Primary Authors +=============== + +Yuri Takteyev , who has written much of the current code +while procrastingating his Ph.D. + +Waylan Limberg , who has written most of the available +extensions and later was asked to join Yuri, fixing nummrious bugs, adding +documentation and making general improvements to the existing codebase, +included a complete refactor of the core. + +Artem Yunusov, who as part of a 2008 GSoC project, has refactored inline +patterns, replaced the NanoDOM with ElementTree support and made various other +improvements. + +Manfed Stienstra , who wrote the original version of +the script and is responsible for various parts of the existing codebase. + +David Wolever, who refactored the extension API and made other improvements +as he helped to integrate Markdown into Dr.Project. + +Other Contributors +================== + +The incomplete list of individuals below have provided patches or otherwise +contributed to the project in various ways. We would like to thank everyone +who has contributed to the progect in any way. + +Eric Abrahamsen +Jeff Balogh +Sergej Chodarev +Chris Clark +Tiago Cogumbreiro +Kjell Magne Fauske +G. Clark Haynes +Daniel Krech +Steward Midwinter +Jack Miller +Neale Pickett +Paul Stansifer +John Szakmeister +Malcolm Tredinnick +Ben Wilson +and many others who helped by reporting bugs diff --git a/docs/CHANGE_LOG b/docs/CHANGE_LOG new file mode 100644 index 0000000..e005ff8 --- /dev/null +++ b/docs/CHANGE_LOG @@ -0,0 +1,180 @@ +PYTHON MARKDOWN CHANGELOG +========================= + +Sept 28, 2009: Released version 2.0.2-Final. + +May 20, 2009: Released version 2.0.1-Final. + +Mar 30, 2009: Released version 2.0-Final. + +Mar 8, 2009: Release Candidate 2.0-rc-1. + +Feb 2009: Added support for multi-level lists to new Blockprocessors. + +Jan 2009: Added HTML 4 output as an option (thanks Eric Abrahamsen) + +Nov 2008: Added Definistion List ext. Replaced old core with BlockProcessors. +Broken up into multiple files. + +Oct 2008: Changed logging behavior to work better with other systems. +Refactored tree tarversing. Added treap implementation, then replaced with +OrderedDEict. Renamed various processors to better reflect what they actually +do. Refactored footnote ext to match php Extra's output. + +Sept 2008: Moved prettifyTree to a Postprocessor, replaced wikilink ext +with wikilinks (note the s) ext (uses bracketed links instead of CamelCase) +and various bug fixes. + +August 18 2008: Reorganized directory structure. Added a 'docs' dir +and moved all extensions into a 'markdown-extensions' package. +Added additional documentation and a few bug fixes. (v2.0-beta) + +August 4 2008: Updated included extensions to ElementTree. Added a +seperate commanline script. (v2.0-alpha) + +July 2008: Switched from home-grown NanoDOM to ElementTree and +various related bugs (thanks Artem Yunusov). + +June 2008: Fixed issues with nested inline patterns and cleaned +up testing framework (thanks Artem Yunusov). + +May 2008: Added a number of additional extensions to the +distribution and other minor changes. Moved repo to git from svn. + +Mar 2008: Refactored extension api to accept either an +extension name (as a string) or an instance of an extension +(Thanks David Wolever). Fixed various bugs and added doc strings. + +Feb 2008: Various bugfixes mostly regarding extensions. + +Feb 18, 2008: Version 1.7. + +Feb 13, 2008: A little code cleanup and better documentation +and inheritance for pre/post proccessors. + +Feb 9, 2008: Doublequotes no longer html escaped and rawhtml +honors , <@foo>, and <%foo> for those who run markdown on +template syntax. + +Dec 12, 2007: Updated docs. Removed encoding arg from Markdown +and markdown as per list discussion. Clean up in prep for 1.7. + +Nov 29, 2007: Added support for images inside links. Also fixed +a few bugs in the footnote extension. + +Nov 19, 2007: `message` now uses python's logging module. Also removed +limit imposed by recursion in _process_section(). You can now parse as +long of a document as your memory can handle. + +Nov 5, 2007: Moved safe_mode code to a textPostprocessor and added +escaping option. + +Nov 3, 2007: Fixed convert method to accept empty strings. + +Oct 30, 2007: Fixed BOM removal (thanks Malcolm Tredinnick). Fixed +infinite loop in bracket regex for inline links. + +Oct 11, 2007: LineBreaks is now an inlinePattern. Fixed HR in +blockquotes. Refactored _processSection method (see tracker #1793419). + +Oct 9, 2007: Added textPreprocessor (from 1.6b). + +Oct 8, 2008: Fixed Lazy Blockquote. Fixed code block on first line. +Fixed empty inline image link. + +Oct 7, 2007: Limit recursion on inlinePatterns. Added a 'safe' tag +to htmlStash. + +March 18, 2007: Fixed or merged a bunch of minor bugs, including +multi-line comments and markup inside links. (Tracker #s: 1683066, +1671153, 1661751, 1627935, 1544371, 1458139.) -> v. 1.6b + +Oct 10, 2006: Fixed a bug that caused some text to be lost after +comments. Added "safe mode" (user's html tags are removed). + +Sept 6, 2006: Added exception for PHP tags when handling html blocks. + +August 7, 2006: Incorporated Sergej Chodarev's patch to fix a problem +with ampersand normalization and html blocks. + +July 10, 2006: Switched to using optparse. Added proper support for +unicode. + +July 9, 2006: Fixed the " % _escape_cdata(text, encoding)) + elif tag is ProcessingInstruction: + write("" % _escape_cdata(text, encoding)) + else: + tag = qnames[tag] + if tag is None: + if text: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + else: + write("<" + tag) + items = elem.items() + if items or namespaces: + items.sort() # lexical order + for k, v in items: + if isinstance(k, QName): + k = k.text + if isinstance(v, QName): + v = qnames[v.text] + else: + v = _escape_attrib_html(v, encoding) + # FIXME: handle boolean attributes + write(" %s=\"%s\"" % (qnames[k], v)) + if namespaces: + items = namespaces.items() + items.sort(key=lambda x: x[1]) # sort on prefix + for v, k in items: + if k: + k = ":" + k + write(" xmlns%s=\"%s\"" % ( + k.encode(encoding), + _escape_attrib(v, encoding) + )) + write(">") + tag = tag.lower() + if text: + if tag == "script" or tag == "style": + write(_encode(text, encoding)) + else: + write(_escape_cdata(text, encoding)) + for e in elem: + _serialize_html(write, e, encoding, qnames, None) + if tag not in HTML_EMPTY: + write("") + if elem.tail: + write(_escape_cdata(elem.tail, encoding)) + +def write_html(root, f, + # keyword arguments + encoding="us-ascii", + default_namespace=None): + assert root is not None + if not hasattr(f, "write"): + f = open(f, "wb") + write = f.write + if not encoding: + encoding = "us-ascii" + qnames, namespaces = _namespaces( + root, encoding, default_namespace + ) + _serialize_html( + write, root, encoding, qnames, namespaces + ) + +# -------------------------------------------------------------------- +# serialization support + +def _namespaces(elem, encoding, default_namespace=None): + # identify namespaces used in this tree + + # maps qnames to *encoded* prefix:local names + qnames = {None: None} + + # maps uri:s to prefixes + namespaces = {} + if default_namespace: + namespaces[default_namespace] = "" + + def encode(text): + return text.encode(encoding) + + def add_qname(qname): + # calculate serialized qname representation + try: + if qname[:1] == "{": + uri, tag = qname[1:].split("}", 1) + prefix = namespaces.get(uri) + if prefix is None: + prefix = _namespace_map.get(uri) + if prefix is None: + prefix = "ns%d" % len(namespaces) + if prefix != "xml": + namespaces[uri] = prefix + if prefix: + qnames[qname] = encode("%s:%s" % (prefix, tag)) + else: + qnames[qname] = encode(tag) # default element + else: + if default_namespace: + # FIXME: can this be handled in XML 1.0? + raise ValueError( + "cannot use non-qualified names with " + "default_namespace option" + ) + qnames[qname] = encode(qname) + except TypeError: + _raise_serialization_error(qname) + + # populate qname and namespaces table + try: + iterate = elem.iter + except AttributeError: + iterate = elem.getiterator # cET compatibility + for elem in iterate(): + tag = elem.tag + if isinstance(tag, QName) and tag.text not in qnames: + add_qname(tag.text) + elif isinstance(tag, basestring): + if tag not in qnames: + add_qname(tag) + elif tag is not None and tag is not Comment and tag is not PI: + _raise_serialization_error(tag) + for key, value in elem.items(): + if isinstance(key, QName): + key = key.text + if key not in qnames: + add_qname(key) + if isinstance(value, QName) and value.text not in qnames: + add_qname(value.text) + text = elem.text + if isinstance(text, QName) and text.text not in qnames: + add_qname(text.text) + return qnames, namespaces + +def to_html_string(element, encoding=None): + class dummy: + pass + data = [] + file = dummy() + file.write = data.append + write_html(ElementTree(element).getroot(),file,encoding) + return "".join(data) diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py new file mode 100644 index 0000000..917a9d3 --- /dev/null +++ b/markdown/inlinepatterns.py @@ -0,0 +1,371 @@ +""" +INLINE PATTERNS +============================================================================= + +Inline patterns such as *emphasis* are handled by means of auxiliary +objects, one per pattern. Pattern objects must be instances of classes +that extend markdown.Pattern. Each pattern object uses a single regular +expression and needs support the following methods: + + pattern.getCompiledRegExp() # returns a regular expression + + pattern.handleMatch(m) # takes a match object and returns + # an ElementTree element or just plain text + +All of python markdown's built-in patterns subclass from Pattern, +but you can add additional patterns that don't. + +Also note that all the regular expressions used by inline must +capture the whole block. For this reason, they all start with +'^(.*)' and end with '(.*)!'. In case with built-in expression +Pattern takes care of adding the "^(.*)" and "(.*)!". + +Finally, the order in which regular expressions are applied is very +important - e.g. if we first replace http://.../ links with tags +and _then_ try to replace inline html, we would end up with a mess. +So, we apply the expressions in the following order: + +* escape and backticks have to go before everything else, so + that we can preempt any markdown patterns by escaping them. + +* then we handle auto-links (must be done before inline html) + +* then we handle inline HTML. At this point we will simply + replace all inline HTML strings with a placeholder and add + the actual HTML to a hash. + +* then inline images (must be done before links) + +* then bracketed links, first regular then reference-style + +* finally we apply strong and emphasis +""" + +import markdown +import re +from urlparse import urlparse, urlunparse +import sys +if sys.version >= "3.0": + from html import entities as htmlentitydefs +else: + import htmlentitydefs + +""" +The actual regular expressions for patterns +----------------------------------------------------------------------------- +""" + +NOBRACKET = r'[^\]\[]*' +BRK = ( r'\[(' + + (NOBRACKET + r'(\[')*6 + + (NOBRACKET+ r'\])*')*6 + + NOBRACKET + r')\]' ) +NOIMG = r'(?|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12)?\)''' +# [text](url) or [text]() + +IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^\)]*))\)' +# ![alttxt](http://x.com/) or ![alttxt]() +REFERENCE_RE = NOIMG + BRK+ r'\s*\[([^\]]*)\]' # [Google][3] +IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2] +NOT_STRONG_RE = r'((^| )(\*|_)( |$))' # stand-alone * or _ +AUTOLINK_RE = r'<((?:f|ht)tps?://[^>]*)>' # +AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # + +HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...> +ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # & +LINE_BREAK_RE = r' \n' # two spaces at end of line +LINE_BREAK_2_RE = r' $' # two spaces at end of text + + +def dequote(string): + """Remove quotes from around a string.""" + if ( ( string.startswith('"') and string.endswith('"')) + or (string.startswith("'") and string.endswith("'")) ): + return string[1:-1] + else: + return string + +ATTR_RE = re.compile("\{@([^\}]*)=([^\}]*)}") # {@id=123} + +def handleAttributes(text, parent): + """Set values of an element based on attribute definitions ({@id=123}).""" + def attributeCallback(match): + parent.set(match.group(1), match.group(2).replace('\n', ' ')) + return ATTR_RE.sub(attributeCallback, text) + + +""" +The pattern classes +----------------------------------------------------------------------------- +""" + +class Pattern: + """Base class that inline patterns subclass. """ + + def __init__ (self, pattern, markdown_instance=None): + """ + Create an instant of an inline pattern. + + Keyword arguments: + + * pattern: A regular expression that matches a pattern + + """ + self.pattern = pattern + self.compiled_re = re.compile("^(.*?)%s(.*?)$" % pattern, re.DOTALL) + + # Api for Markdown to pass safe_mode into instance + self.safe_mode = False + if markdown_instance: + self.markdown = markdown_instance + + def getCompiledRegExp (self): + """ Return a compiled regular expression. """ + return self.compiled_re + + def handleMatch(self, m): + """Return a ElementTree element from the given match. + + Subclasses should override this method. + + Keyword arguments: + + * m: A re match object containing a match of the pattern. + + """ + pass + + def type(self): + """ Return class name, to define pattern type """ + return self.__class__.__name__ + +BasePattern = Pattern # for backward compatibility + +class SimpleTextPattern (Pattern): + """ Return a simple text of group(2) of a Pattern. """ + def handleMatch(self, m): + text = m.group(2) + if text == markdown.INLINE_PLACEHOLDER_PREFIX: + return None + return text + +class SimpleTagPattern (Pattern): + """ + Return element of type `tag` with a text attribute of group(3) + of a Pattern. + + """ + def __init__ (self, pattern, tag): + Pattern.__init__(self, pattern) + self.tag = tag + + def handleMatch(self, m): + el = markdown.etree.Element(self.tag) + el.text = m.group(3) + return el + + +class SubstituteTagPattern (SimpleTagPattern): + """ Return a eLement of type `tag` with no children. """ + def handleMatch (self, m): + return markdown.etree.Element(self.tag) + + +class BacktickPattern (Pattern): + """ Return a `` element containing the matching text. """ + def __init__ (self, pattern): + Pattern.__init__(self, pattern) + self.tag = "code" + + def handleMatch(self, m): + el = markdown.etree.Element(self.tag) + el.text = markdown.AtomicString(m.group(3).strip()) + return el + + +class DoubleTagPattern (SimpleTagPattern): + """Return a ElementTree element nested in tag2 nested in tag1. + + Useful for strong emphasis etc. + + """ + def handleMatch(self, m): + tag1, tag2 = self.tag.split(",") + el1 = markdown.etree.Element(tag1) + el2 = markdown.etree.SubElement(el1, tag2) + el2.text = m.group(3) + return el1 + + +class HtmlPattern (Pattern): + """ Store raw inline html and return a placeholder. """ + def handleMatch (self, m): + rawhtml = m.group(2) + inline = True + place_holder = self.markdown.htmlStash.store(rawhtml) + return place_holder + + +class LinkPattern (Pattern): + """ Return a link element from the given match. """ + def handleMatch(self, m): + el = markdown.etree.Element("a") + el.text = m.group(2) + title = m.group(11) + href = m.group(9) + + if href: + if href[0] == "<": + href = href[1:-1] + el.set("href", self.sanitize_url(href.strip())) + else: + el.set("href", "") + + if title: + title = dequote(title) #.replace('"', """) + el.set("title", title) + return el + + def sanitize_url(self, url): + """ + Sanitize a url against xss attacks in "safe_mode". + + Rather than specifically blacklisting `javascript:alert("XSS")` and all + its aliases (see ), we whitelist known + 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 + location. Additionally, for `javascript:...`, the scheme would be + "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. Therefore we must check "path", "parameters", "query" and + "fragment" for any literal colons. We don't check "scheme" for colons + because it *should* never have any and "netloc" must allow the form: + `username:password@host:port`. + + """ + locless_schemes = ['', 'mailto', 'news'] + scheme, netloc, path, params, query, fragment = url = urlparse(url) + safe_url = False + if netloc != '' or scheme in locless_schemes: + safe_url = True + + for part in url[2:]: + if ":" in part: + safe_url = False + + if self.markdown.safeMode and not safe_url: + return '' + else: + return urlunparse(url) + +class ImagePattern(LinkPattern): + """ Return a img element from the given match. """ + def handleMatch(self, m): + el = markdown.etree.Element("img") + src_parts = m.group(9).split() + if src_parts: + src = src_parts[0] + if src[0] == "<" and src[-1] == ">": + src = src[1:-1] + el.set('src', self.sanitize_url(src)) + else: + el.set('src', "") + if len(src_parts) > 1: + el.set('title', dequote(" ".join(src_parts[1:]))) + + if markdown.ENABLE_ATTRIBUTES: + truealt = handleAttributes(m.group(2), el) + else: + truealt = m.group(2) + + el.set('alt', truealt) + return el + +class ReferencePattern(LinkPattern): + """ Match to a stored reference and return link element. """ + def handleMatch(self, m): + if m.group(9): + id = m.group(9).lower() + else: + # if we got something like "[Google][]" + # we'll use "google" as the id + id = m.group(2).lower() + + if not id in self.markdown.references: # ignore undefined refs + return None + href, title = self.markdown.references[id] + + text = m.group(2) + return self.makeTag(href, title, text) + + def makeTag(self, href, title, text): + el = markdown.etree.Element('a') + + el.set('href', self.sanitize_url(href)) + if title: + el.set('title', title) + + el.text = text + return el + + +class ImageReferencePattern (ReferencePattern): + """ Match to a stored reference and return img element. """ + def makeTag(self, href, title, text): + el = markdown.etree.Element("img") + el.set("src", self.sanitize_url(href)) + if title: + el.set("title", title) + el.set("alt", text) + return el + + +class AutolinkPattern (Pattern): + """ Return a link Element given an autolink (``). """ + def handleMatch(self, m): + el = markdown.etree.Element("a") + el.set('href', m.group(2)) + el.text = markdown.AtomicString(m.group(2)) + return el + +class AutomailPattern (Pattern): + """ + Return a mailto link Element given an automail link (``). + """ + def handleMatch(self, m): + el = markdown.etree.Element('a') + email = m.group(2) + if email.startswith("mailto:"): + email = email[len("mailto:"):] + + def codepoint2name(code): + """Return entity definition by code, or the code if not defined.""" + entity = htmlentitydefs.codepoint2name.get(code) + if entity: + return "%s%s;" % (markdown.AMP_SUBSTITUTE, entity) + else: + return "%s#%d;" % (markdown.AMP_SUBSTITUTE, code) + + letters = [codepoint2name(ord(letter)) for letter in email] + el.text = markdown.AtomicString(''.join(letters)) + + mailto = "mailto:" + email + mailto = "".join([markdown.AMP_SUBSTITUTE + '#%d;' % + ord(letter) for letter in mailto]) + el.set('href', mailto) + return el + diff --git a/markdown/odict.py b/markdown/odict.py new file mode 100644 index 0000000..bf3ef07 --- /dev/null +++ b/markdown/odict.py @@ -0,0 +1,162 @@ +class OrderedDict(dict): + """ + A dictionary that keeps its keys in the order in which they're inserted. + + Copied from Django's SortedDict with some modifications. + + """ + def __new__(cls, *args, **kwargs): + instance = super(OrderedDict, cls).__new__(cls, *args, **kwargs) + instance.keyOrder = [] + return instance + + def __init__(self, data=None): + if data is None: + data = {} + super(OrderedDict, self).__init__(data) + if isinstance(data, dict): + self.keyOrder = data.keys() + else: + self.keyOrder = [] + for key, value in data: + if key not in self.keyOrder: + self.keyOrder.append(key) + + def __deepcopy__(self, memo): + from copy import deepcopy + return self.__class__([(key, deepcopy(value, memo)) + for key, value in self.iteritems()]) + + def __setitem__(self, key, value): + super(OrderedDict, self).__setitem__(key, value) + if key not in self.keyOrder: + self.keyOrder.append(key) + + def __delitem__(self, key): + super(OrderedDict, self).__delitem__(key) + self.keyOrder.remove(key) + + def __iter__(self): + for k in self.keyOrder: + yield k + + def pop(self, k, *args): + result = super(OrderedDict, self).pop(k, *args) + try: + self.keyOrder.remove(k) + except ValueError: + # Key wasn't in the dictionary in the first place. No problem. + pass + return result + + def popitem(self): + result = super(OrderedDict, self).popitem() + self.keyOrder.remove(result[0]) + return result + + def items(self): + return zip(self.keyOrder, self.values()) + + def iteritems(self): + for key in self.keyOrder: + yield key, super(OrderedDict, self).__getitem__(key) + + def keys(self): + return self.keyOrder[:] + + def iterkeys(self): + return iter(self.keyOrder) + + def values(self): + return [super(OrderedDict, self).__getitem__(k) for k in self.keyOrder] + + def itervalues(self): + for key in self.keyOrder: + yield super(OrderedDict, self).__getitem__(key) + + def update(self, dict_): + for k, v in dict_.items(): + self.__setitem__(k, v) + + def setdefault(self, key, default): + if key not in self.keyOrder: + self.keyOrder.append(key) + return super(OrderedDict, self).setdefault(key, default) + + def value_for_index(self, index): + """Return the value of the item at the given zero-based index.""" + return self[self.keyOrder[index]] + + def insert(self, index, key, value): + """Insert the key, value pair before the item with the given index.""" + if key in self.keyOrder: + n = self.keyOrder.index(key) + del self.keyOrder[n] + if n < index: + index -= 1 + self.keyOrder.insert(index, key) + super(OrderedDict, self).__setitem__(key, value) + + def copy(self): + """Return a copy of this object.""" + # This way of initializing the copy means it works for subclasses, too. + obj = self.__class__(self) + obj.keyOrder = self.keyOrder[:] + return obj + + def __repr__(self): + """ + Replace the normal dict.__repr__ with a version that returns the keys + in their sorted order. + """ + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + + def clear(self): + super(OrderedDict, self).clear() + self.keyOrder = [] + + def index(self, key): + """ Return the index of a given key. """ + return self.keyOrder.index(key) + + def index_for_location(self, location): + """ Return index or None for a given location. """ + if location == '_begin': + i = 0 + elif location == '_end': + i = None + elif location.startswith('<') or location.startswith('>'): + i = self.index(location[1:]) + if location.startswith('>'): + if i >= len(self): + # last item + i = None + else: + i += 1 + else: + raise ValueError('Not a valid location: "%s". Location key ' + 'must start with a ">" or "<".' % location) + return i + + def add(self, key, value, location): + """ Insert by key location. """ + i = self.index_for_location(location) + if i is not None: + self.insert(i, key, value) + else: + self.__setitem__(key, value) + + def link(self, key, location): + """ Change location of an existing item. """ + n = self.keyOrder.index(key) + del self.keyOrder[n] + i = self.index_for_location(location) + try: + if i is not None: + self.keyOrder.insert(i, key) + else: + self.keyOrder.append(key) + except Error: + # restore to prevent data loss and reraise + self.keyOrder.insert(n, key) + raise Error diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py new file mode 100644 index 0000000..80227bb --- /dev/null +++ b/markdown/postprocessors.py @@ -0,0 +1,77 @@ +""" +POST-PROCESSORS +============================================================================= + +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 +processing. + +""" + + +import markdown + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Postprocessor(Processor): + """ + Postprocessors are run after the ElementTree it converted back into text. + + Each Postprocessor implements a "run" method that takes a pointer to a + text string, modifies it as necessary and returns a text string. + + Postprocessors must extend markdown.Postprocessor. + + """ + + def run(self, text): + """ + Subclasses of Postprocessor should implement a `run` method, which + takes the html document as a single text string and returns a + (possibly modified) string. + + """ + pass + + +class RawHtmlPostprocessor(Postprocessor): + """ Restore raw html to the document. """ + + def run(self, text): + """ Iterate over html stash and restore "safe" html. """ + for i in range(self.markdown.htmlStash.html_counter): + html, safe = self.markdown.htmlStash.rawHtmlBlocks[i] + if self.markdown.safeMode and not safe: + if str(self.markdown.safeMode).lower() == 'escape': + html = self.escape(html) + elif str(self.markdown.safeMode).lower() == 'remove': + html = '' + else: + html = markdown.HTML_REMOVED_TEXT + if safe or not self.markdown.safeMode: + text = text.replace("

%s

" % + (markdown.preprocessors.HTML_PLACEHOLDER % i), + html + "\n") + text = text.replace(markdown.preprocessors.HTML_PLACEHOLDER % i, + html) + return text + + def escape(self, html): + """ Basic html escaping """ + html = html.replace('&', '&') + html = html.replace('<', '<') + html = html.replace('>', '>') + return html.replace('"', '"') + + +class AndSubstitutePostprocessor(Postprocessor): + """ Restore valid entities """ + def __init__(self): + pass + + def run(self, text): + text = text.replace(markdown.AMP_SUBSTITUTE, "&") + return text diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py new file mode 100644 index 0000000..ef04cab --- /dev/null +++ b/markdown/preprocessors.py @@ -0,0 +1,215 @@ + +""" +PRE-PROCESSORS +============================================================================= + +Preprocessors work on source text before we start doing anything too +complicated. +""" + +import re +import markdown + +HTML_PLACEHOLDER_PREFIX = markdown.STX+"wzxhzdk:" +HTML_PLACEHOLDER = HTML_PLACEHOLDER_PREFIX + "%d" + markdown.ETX + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Preprocessor (Processor): + """ + Preprocessors are run after the text is broken into lines. + + Each preprocessor implements a "run" method that takes a pointer to a + list of lines of the document, modifies it as necessary and returns + either the same pointer or a pointer to a new list. + + Preprocessors must extend markdown.Preprocessor. + + """ + def run(self, lines): + """ + Each subclass of Preprocessor should override the `run` method, which + takes the document as a list of strings split by newlines and returns + the (possibly modified) list of lines. + + """ + pass + +class HtmlStash: + """ + This class is used for stashing HTML objects that we extract + in the beginning and replace with place-holders. + """ + + def __init__ (self): + """ Create a HtmlStash. """ + self.html_counter = 0 # for counting inline html segments + self.rawHtmlBlocks=[] + + def store(self, html, safe=False): + """ + Saves an HTML segment for later reinsertion. Returns a + placeholder string that needs to be inserted into the + document. + + Keyword arguments: + + * html: an html segment + * safe: label an html segment as safe for safemode + + Returns : a placeholder string + + """ + self.rawHtmlBlocks.append((html, safe)) + placeholder = HTML_PLACEHOLDER % self.html_counter + self.html_counter += 1 + return placeholder + + def reset(self): + self.html_counter = 0 + self.rawHtmlBlocks = [] + + +class HtmlBlockPreprocessor(Preprocessor): + """Remove html blocks from the text and store them for later retrieval.""" + + right_tag_patterns = ["", "%s>"] + + def _get_left_tag(self, block): + return block[1:].replace(">", " ", 1).split()[0].lower() + + def _get_right_tag(self, left_tag, block): + for p in self.right_tag_patterns: + tag = p % left_tag + i = block.rfind(tag) + if i > 2: + return tag.lstrip("<").rstrip(">"), i + len(p)-2 + len(left_tag) + return block.rstrip()[-len(left_tag)-2:-1].lower(), len(block) + + def _equal_tags(self, left_tag, right_tag): + if left_tag == 'div' or left_tag[0] in ['?', '@', '%']: # handle PHP, etc. + return True + if ("/" + left_tag) == right_tag: + return True + if (right_tag == "--" and left_tag == "--"): + return True + elif left_tag == right_tag[1:] \ + and right_tag[0] != "<": + return True + else: + return False + + def _is_oneliner(self, tag): + return (tag in ['hr', 'hr/']) + + def run(self, lines): + text = "\n".join(lines) + new_blocks = [] + text = text.split("\n\n") + items = [] + left_tag = '' + right_tag = '' + in_tag = False # flag + + while text: + block = text[0] + if block.startswith("\n"): + block = block[1:] + text = text[1:] + + if block.startswith("\n"): + block = block[1:] + + if not in_tag: + if block.startswith("<"): + left_tag = self._get_left_tag(block) + right_tag, data_index = self._get_right_tag(left_tag, block) + + if block[1] == "!": + # is a comment block + left_tag = "--" + right_tag, data_index = self._get_right_tag(left_tag, block) + # keep checking conditions below and maybe just append + + if data_index < len(block) \ + and markdown.isBlockLevel(left_tag): + text.insert(0, block[data_index:]) + block = block[:data_index] + + if not (markdown.isBlockLevel(left_tag) \ + or block[1] in ["!", "?", "@", "%"]): + new_blocks.append(block) + continue + + if self._is_oneliner(left_tag): + new_blocks.append(block.strip()) + continue + + if block.rstrip().endswith(">") \ + and self._equal_tags(left_tag, right_tag): + new_blocks.append( + self.markdown.htmlStash.store(block.strip())) + continue + else: #if not block[1] == "!": + # if is block level tag and is not complete + + if markdown.isBlockLevel(left_tag) or left_tag == "--" \ + and not block.rstrip().endswith(">"): + items.append(block.strip()) + in_tag = True + else: + new_blocks.append( + self.markdown.htmlStash.store(block.strip())) + + continue + + new_blocks.append(block) + + else: + items.append(block.strip()) + + right_tag, data_index = self._get_right_tag(left_tag, block) + + if self._equal_tags(left_tag, right_tag): + # if find closing tag + in_tag = False + new_blocks.append( + self.markdown.htmlStash.store('\n\n'.join(items))) + items = [] + + if items: + new_blocks.append(self.markdown.htmlStash.store('\n\n'.join(items))) + new_blocks.append('\n') + + new_text = "\n\n".join(new_blocks) + return new_text.split("\n") + + +class ReferencePreprocessor(Preprocessor): + """ Remove reference definitions from text and store for later use. """ + + RE = re.compile(r'^(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)$', re.DOTALL) + + def run (self, lines): + new_text = []; + for line in lines: + m = self.RE.match(line) + if m: + id = m.group(2).strip().lower() + t = m.group(4).strip() # potential title + if not t: + self.markdown.references[id] = (m.group(3), t) + elif (len(t) >= 2 + and (t[0] == t[-1] == "\"" + or t[0] == t[-1] == "\'" + or (t[0] == "(" and t[-1] == ")") ) ): + self.markdown.references[id] = (m.group(3), t[1:-1]) + else: + new_text.append(line) + else: + new_text.append(line) + + return new_text #+ "\n" diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py new file mode 100644 index 0000000..1dc612a --- /dev/null +++ b/markdown/treeprocessors.py @@ -0,0 +1,329 @@ +import markdown +import re + +def isString(s): + """ Check if it's string """ + return isinstance(s, unicode) or isinstance(s, str) + +class Processor: + def __init__(self, markdown_instance=None): + if markdown_instance: + self.markdown = markdown_instance + +class Treeprocessor(Processor): + """ + Treeprocessors are run on the ElementTree object before serialization. + + Each Treeprocessor implements a "run" method that takes a pointer to an + ElementTree, modifies it as necessary and returns an ElementTree + object. + + Treeprocessors must extend markdown.Treeprocessor. + + """ + def run(self, root): + """ + Subclasses of Treeprocessor should implement a `run` method, which + takes a root ElementTree. This method can return another ElementTree + object, and the existing root ElementTree will be replaced, or it can + modify the current tree and return None. + """ + pass + + +class InlineProcessor(Treeprocessor): + """ + A Treeprocessor that traverses a tree, applying inline patterns. + """ + + def __init__ (self, md): + self.__placeholder_prefix = markdown.INLINE_PLACEHOLDER_PREFIX + self.__placeholder_suffix = markdown.ETX + self.__placeholder_length = 4 + len(self.__placeholder_prefix) \ + + len(self.__placeholder_suffix) + self.__placeholder_re = re.compile(markdown.INLINE_PLACEHOLDER % r'([0-9]{4})') + self.markdown = md + + def __makePlaceholder(self, type): + """ Generate a placeholder """ + id = "%04d" % len(self.stashed_nodes) + hash = markdown.INLINE_PLACEHOLDER % id + return hash, id + + def __findPlaceholder(self, data, index): + """ + Extract id from data string, start from index + + Keyword arguments: + + * data: string + * index: index, from which we start search + + Returns: placeholder id and string index, after the found placeholder. + """ + + m = self.__placeholder_re.search(data, index) + if m: + return m.group(1), m.end() + else: + return None, index + 1 + + def __stashNode(self, node, type): + """ Add node to stash """ + placeholder, id = self.__makePlaceholder(type) + self.stashed_nodes[id] = node + return placeholder + + def __handleInline(self, data, patternIndex=0): + """ + Process string with inline patterns and replace it + with placeholders + + Keyword arguments: + + * data: A line of Markdown text + * patternIndex: The index of the inlinePattern to start with + + Returns: String with placeholders. + + """ + if not isinstance(data, markdown.AtomicString): + startIndex = 0 + while patternIndex < len(self.markdown.inlinePatterns): + data, matched, startIndex = self.__applyPattern( + self.markdown.inlinePatterns.value_for_index(patternIndex), + data, patternIndex, startIndex) + if not matched: + patternIndex += 1 + return data + + def __processElementText(self, node, subnode, isText=True): + """ + Process placeholders in Element.text or Element.tail + of Elements popped from self.stashed_nodes. + + Keywords arguments: + + * node: parent node + * subnode: processing node + * isText: bool variable, True - it's text, False - it's tail + + Returns: None + + """ + if isText: + text = subnode.text + subnode.text = None + else: + text = subnode.tail + subnode.tail = None + + childResult = self.__processPlaceholders(text, subnode) + + if not isText and node is not subnode: + pos = node.getchildren().index(subnode) + node.remove(subnode) + else: + pos = 0 + + childResult.reverse() + for newChild in childResult: + node.insert(pos, newChild) + + def __processPlaceholders(self, data, parent): + """ + Process string with placeholders and generate ElementTree tree. + + Keyword arguments: + + * data: string with placeholders instead of ElementTree elements. + * parent: Element, which contains processing inline data + + Returns: list with ElementTree elements with applied inline patterns. + """ + def linkText(text): + if text: + if result: + if result[-1].tail: + result[-1].tail += text + else: + result[-1].tail = text + else: + if parent.text: + parent.text += text + else: + parent.text = text + + result = [] + strartIndex = 0 + while data: + index = data.find(self.__placeholder_prefix, strartIndex) + if index != -1: + id, phEndIndex = self.__findPlaceholder(data, index) + + if id in self.stashed_nodes: + node = self.stashed_nodes.get(id) + + if index > 0: + text = data[strartIndex:index] + linkText(text) + + if not isString(node): # it's Element + for child in [node] + node.getchildren(): + if child.tail: + if child.tail.strip(): + self.__processElementText(node, child, False) + if child.text: + if child.text.strip(): + self.__processElementText(child, child) + else: # it's just a string + linkText(node) + strartIndex = phEndIndex + continue + + strartIndex = phEndIndex + result.append(node) + + else: # wrong placeholder + end = index + len(prefix) + linkText(data[strartIndex:end]) + strartIndex = end + else: + text = data[strartIndex:] + linkText(text) + data = "" + + return result + + def __applyPattern(self, pattern, data, patternIndex, startIndex=0): + """ + Check if the line fits the pattern, create the necessary + elements, add it to stashed_nodes. + + Keyword arguments: + + * data: the text to be processed + * pattern: the pattern to be checked + * patternIndex: index of current pattern + * startIndex: string index, from which we starting search + + Returns: String with placeholders instead of ElementTree elements. + + """ + match = pattern.getCompiledRegExp().match(data[startIndex:]) + leftData = data[:startIndex] + + if not match: + return data, False, 0 + + node = pattern.handleMatch(match) + + if node is None: + return data, True, len(leftData) + match.span(len(match.groups()))[0] + + if not isString(node): + if not isinstance(node.text, markdown.AtomicString): + # We need to process current node too + for child in [node] + node.getchildren(): + if not isString(node): + if child.text: + child.text = self.__handleInline(child.text, + patternIndex + 1) + if child.tail: + child.tail = self.__handleInline(child.tail, + patternIndex) + + placeholder = self.__stashNode(node, pattern.type()) + + return "%s%s%s%s" % (leftData, + match.group(1), + placeholder, match.groups()[-1]), True, 0 + + def run(self, tree): + """Apply inline patterns to a parsed Markdown tree. + + Iterate over ElementTree, find elements with inline tag, apply inline + patterns and append newly created Elements to tree. If you don't + want process your data with inline paterns, instead of normal string, + use subclass AtomicString: + + node.text = markdown.AtomicString("data won't be processed with inline patterns") + + Arguments: + + * markdownTree: ElementTree object, representing Markdown tree. + + Returns: ElementTree object with applied inline patterns. + + """ + self.stashed_nodes = {} + + stack = [tree] + + while stack: + currElement = stack.pop() + insertQueue = [] + for child in currElement.getchildren(): + if child.text and not isinstance(child.text, markdown.AtomicString): + text = child.text + child.text = None + lst = self.__processPlaceholders(self.__handleInline( + text), child) + stack += lst + insertQueue.append((child, lst)) + + if child.getchildren(): + stack.append(child) + + for element, lst in insertQueue: + if element.text: + element.text = \ + markdown.inlinepatterns.handleAttributes(element.text, + element) + i = 0 + for newChild in lst: + # Processing attributes + if newChild.tail: + newChild.tail = \ + markdown.inlinepatterns.handleAttributes(newChild.tail, + element) + if newChild.text: + newChild.text = \ + markdown.inlinepatterns.handleAttributes(newChild.text, + newChild) + element.insert(i, newChild) + i += 1 + return tree + + +class PrettifyTreeprocessor(Treeprocessor): + """ Add linebreaks to the html document. """ + + def _prettifyETree(self, elem): + """ Recursively add linebreaks to ElementTree children. """ + + i = "\n" + if markdown.isBlockLevel(elem.tag) and elem.tag not in ['code', 'pre']: + if (not elem.text or not elem.text.strip()) \ + and len(elem) and markdown.isBlockLevel(elem[0].tag): + elem.text = i + for e in elem: + if markdown.isBlockLevel(e.tag): + self._prettifyETree(e) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + if not elem.tail or not elem.tail.strip(): + elem.tail = i + + def run(self, root): + """ Add linebreaks to ElementTree root object. """ + + self._prettifyETree(root) + # Do
's seperately as they are often in the middle of + # inline content and missed by _prettifyETree. + brs = root.getiterator('br') + for br in brs: + if not br.tail or not br.tail.strip(): + br.tail = '\n' + else: + br.tail = '\n%s' % br.tail diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..42939d3 --- /dev/null +++ b/setup.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +import sys, os +from distutils.core import setup +from distutils.command.install_scripts import install_scripts + +version = '2.0.3' + +class md_install_scripts(install_scripts): + """ Customized install_scripts. Create markdown.bat for win32. """ + def run(self): + install_scripts.run(self) + + if sys.platform == 'win32': + try: + script_dir = os.path.join(sys.prefix, 'Scripts') + script_path = os.path.join(script_dir, 'markdown') + bat_str = '@"%s" "%s" %%*' % (sys.executable, script_path) + bat_path = os.path.join(self.install_dir, 'markdown.bat') + f = file(bat_path, 'w') + f.write(bat_str) + f.close() + print 'Created:', bat_path + except Exception, e: + print 'ERROR: Unable to create %s: %s' % (bat_path, e) + +data = dict( + name = 'Markdown', + version = version, + url = 'http://www.freewisdom.org/projects/python-markdown', + download_url = 'http://pypi.python.org/packages/source/M/Markdown/Markdown-%s.tar.gz' % version, + description = 'Python implementation of Markdown.', + author = 'Manfred Stienstra and Yuri takhteyev', + author_email = 'yuri [at] freewisdom.org', + maintainer = 'Waylan Limberg', + maintainer_email = 'waylan [at] gmail.com', + license = 'BSD License', + packages = ['markdown', 'markdown.extensions'], + scripts = ['bin/markdown'], + cmdclass = {'install_scripts': md_install_scripts}, + classifiers = ['Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.3', + 'Programming Language :: Python :: 2.4', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.0', + 'Topic :: Communications :: Email :: Filters', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries', + 'Topic :: Internet :: WWW/HTTP :: Site Management', + 'Topic :: Software Development :: Documentation', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Text Processing :: Filters', + 'Topic :: Text Processing :: Markup :: HTML', + ], + ) + +if sys.version[:3] < '2.5': + data['install_requires'] = ['elementtree'] + +setup(**data) -- 2.34.1