| # markdown is released under the BSD license |
| # Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) |
| # Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) |
| # Copyright 2004 Manfred Stienstra (the original version) |
| # |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution. |
| # * Neither the name of the <organization> nor the |
| # names of its contributors may be used to endorse or promote products |
| # derived from this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY |
| # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| # DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT |
| # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| # POSSIBILITY OF SUCH DAMAGE. |
| |
| |
| """ |
| 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 <a> 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 |
| """ |
| |
| from __future__ import absolute_import |
| from __future__ import unicode_literals |
| from . import util |
| from . import odict |
| import re |
| try: |
| from urllib.parse import urlparse, urlunparse |
| except ImportError: |
| from urlparse import urlparse, urlunparse |
| try: |
| from html import entities |
| except ImportError: |
| import htmlentitydefs as entities |
| |
| |
| def build_inlinepatterns(md_instance, **kwargs): |
| """ Build the default set of inline patterns for Markdown. """ |
| inlinePatterns = odict.OrderedDict() |
| inlinePatterns["backtick"] = BacktickPattern(BACKTICK_RE) |
| inlinePatterns["escape"] = EscapePattern(ESCAPE_RE, md_instance) |
| inlinePatterns["reference"] = ReferencePattern(REFERENCE_RE, md_instance) |
| inlinePatterns["link"] = LinkPattern(LINK_RE, md_instance) |
| inlinePatterns["image_link"] = ImagePattern(IMAGE_LINK_RE, md_instance) |
| inlinePatterns["image_reference"] = \ |
| ImageReferencePattern(IMAGE_REFERENCE_RE, md_instance) |
| inlinePatterns["short_reference"] = \ |
| ReferencePattern(SHORT_REF_RE, md_instance) |
| inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance) |
| inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance) |
| inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br') |
| if md_instance.safeMode != 'escape': |
| inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance) |
| inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance) |
| inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE) |
| inlinePatterns["strong_em"] = DoubleTagPattern(STRONG_EM_RE, 'strong,em') |
| inlinePatterns["strong"] = SimpleTagPattern(STRONG_RE, 'strong') |
| inlinePatterns["emphasis"] = SimpleTagPattern(EMPHASIS_RE, 'em') |
| if md_instance.smart_emphasis: |
| inlinePatterns["emphasis2"] = SimpleTagPattern(SMART_EMPHASIS_RE, 'em') |
| else: |
| inlinePatterns["emphasis2"] = SimpleTagPattern(EMPHASIS_2_RE, 'em') |
| return inlinePatterns |
| |
| """ |
| The actual regular expressions for patterns |
| ----------------------------------------------------------------------------- |
| """ |
| |
| NOBRACKET = r'[^\]\[]*' |
| BRK = ( r'\[(' |
| + (NOBRACKET + r'(\[')*6 |
| + (NOBRACKET+ r'\])*')*6 |
| + NOBRACKET + r')\]' ) |
| NOIMG = r'(?<!\!)' |
| |
| BACKTICK_RE = r'(?<!\\)(`+)(.+?)(?<!`)\2(?!`)' # `e=f()` or ``e=f("`")`` |
| ESCAPE_RE = r'\\(.)' # \< |
| EMPHASIS_RE = r'(\*)([^\*]+)\2' # *emphasis* |
| STRONG_RE = r'(\*{2}|_{2})(.+?)\2' # **strong** |
| STRONG_EM_RE = r'(\*{3}|_{3})(.+?)\2' # ***strong*** |
| SMART_EMPHASIS_RE = r'(?<!\w)(_)(?!_)(.+?)(?<!_)\2(?!\w)' # _smart_emphasis_ |
| EMPHASIS_2_RE = r'(_)(.+?)\2' # _emphasis_ |
| LINK_RE = NOIMG + BRK + \ |
| r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' |
| # [text](url) or [text](<url>) or [text](url "title") |
| |
| IMAGE_LINK_RE = r'\!' + BRK + r'\s*\((<.*?>|([^\)]*))\)' |
| # ![alttxt](http://x.com/) or ![alttxt](<http://x.com/>) |
| REFERENCE_RE = NOIMG + BRK+ r'\s?\[([^\]]*)\]' # [Google][3] |
| SHORT_REF_RE = NOIMG + r'\[([^\]]+)\]' # [Google] |
| IMAGE_REFERENCE_RE = r'\!' + BRK + '\s?\[([^\]]*)\]' # ![alt text][2] |
| NOT_STRONG_RE = r'((^| )(\*|_)( |$))' # stand-alone * or _ |
| AUTOLINK_RE = r'<((?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^>]*)>' # <http://www.123.com> |
| AUTOMAIL_RE = r'<([^> \!]*@[^> ]*)>' # <me@example.com> |
| |
| HTML_RE = r'(\<([a-zA-Z/][^\>]*?|\!--.*?--)\>)' # <...> |
| ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # & |
| LINE_BREAK_RE = r' \n' # two spaces at end of line |
| |
| |
| 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(object): |
| """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 | re.UNICODE) |
| |
| # 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__ |
| |
| def unescape(self, text): |
| """ Return unescaped text given text with an inline placeholder. """ |
| try: |
| stash = self.markdown.treeprocessors['inline'].stashed_nodes |
| except KeyError: |
| return text |
| def itertext(el): |
| ' Reimplement Element.itertext for older python versions ' |
| tag = el.tag |
| if not isinstance(tag, util.string_type) and tag is not None: |
| return |
| if el.text: |
| yield el.text |
| for e in el: |
| for s in itertext(e): |
| yield s |
| if e.tail: |
| yield e.tail |
| def get_stash(m): |
| id = m.group(1) |
| if id in stash: |
| value = stash.get(id) |
| if isinstance(value, util.string_type): |
| return value |
| else: |
| # An etree Element - return text content only |
| return ''.join(itertext(value)) |
| return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) |
| |
| |
| class SimpleTextPattern(Pattern): |
| """ Return a simple text of group(2) of a Pattern. """ |
| def handleMatch(self, m): |
| text = m.group(2) |
| if text == util.INLINE_PLACEHOLDER_PREFIX: |
| return None |
| return text |
| |
| |
| class EscapePattern(Pattern): |
| """ Return an escaped character. """ |
| |
| def handleMatch(self, m): |
| char = m.group(2) |
| if char in self.markdown.ESCAPED_CHARS: |
| return '%s%s%s' % (util.STX, ord(char), util.ETX) |
| else: |
| return '\\%s' % char |
| |
| |
| 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 = util.etree.Element(self.tag) |
| el.text = m.group(3) |
| return el |
| |
| |
| class SubstituteTagPattern(SimpleTagPattern): |
| """ Return an element of type `tag` with no children. """ |
| def handleMatch (self, m): |
| return util.etree.Element(self.tag) |
| |
| |
| class BacktickPattern(Pattern): |
| """ Return a `<code>` element containing the matching text. """ |
| def __init__ (self, pattern): |
| Pattern.__init__(self, pattern) |
| self.tag = "code" |
| |
| def handleMatch(self, m): |
| el = util.etree.Element(self.tag) |
| el.text = util.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 = util.etree.Element(tag1) |
| el2 = util.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 = self.unescape(m.group(2)) |
| place_holder = self.markdown.htmlStash.store(rawhtml) |
| return place_holder |
| |
| def unescape(self, text): |
| """ Return unescaped text given text with an inline placeholder. """ |
| try: |
| stash = self.markdown.treeprocessors['inline'].stashed_nodes |
| except KeyError: |
| return text |
| def get_stash(m): |
| id = m.group(1) |
| value = stash.get(id) |
| if value is not None: |
| try: |
| return self.markdown.serializer(value) |
| except: |
| return '\%s' % value |
| |
| return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text) |
| |
| |
| class LinkPattern(Pattern): |
| """ Return a link element from the given match. """ |
| def handleMatch(self, m): |
| el = util.etree.Element("a") |
| el.text = m.group(2) |
| title = m.group(13) |
| href = m.group(9) |
| |
| if href: |
| if href[0] == "<": |
| href = href[1:-1] |
| el.set("href", self.sanitize_url(self.unescape(href.strip()))) |
| else: |
| el.set("href", "") |
| |
| if title: |
| title = dequote(self.unescape(title)) |
| 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 <http://ha.ckers.org/xss.html>), 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`. |
| |
| """ |
| url = url.replace(' ', '%20') |
| if not self.markdown.safeMode: |
| # Return immediately bipassing parsing. |
| return url |
| |
| try: |
| scheme, netloc, path, params, query, fragment = url = urlparse(url) |
| except ValueError: |
| # Bad url - so bad it couldn't be parsed. |
| return '' |
| |
| locless_schemes = ['', 'mailto', 'news'] |
| allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps'] |
| if scheme not in allowed_schemes: |
| # Not a known (allowed) scheme. Not safe. |
| return '' |
| |
| if netloc == '' and scheme not in locless_schemes: |
| # This should not happen. Treat as suspect. |
| return '' |
| |
| for part in url[2:]: |
| if ":" in part: |
| # A colon in "path", "parameters", "query" or "fragment" is suspect. |
| return '' |
| |
| # Url passes all tests. Return url as-is. |
| return urlunparse(url) |
| |
| class ImagePattern(LinkPattern): |
| """ Return a img element from the given match. """ |
| def handleMatch(self, m): |
| el = util.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(self.unescape(src))) |
| else: |
| el.set('src', "") |
| if len(src_parts) > 1: |
| el.set('title', dequote(self.unescape(" ".join(src_parts[1:])))) |
| |
| if self.markdown.enable_attributes: |
| truealt = handleAttributes(m.group(2), el) |
| else: |
| truealt = m.group(2) |
| |
| el.set('alt', self.unescape(truealt)) |
| return el |
| |
| class ReferencePattern(LinkPattern): |
| """ Match to a stored reference and return link element. """ |
| |
| NEWLINE_CLEANUP_RE = re.compile(r'[ ]?\n', re.MULTILINE) |
| |
| def handleMatch(self, m): |
| try: |
| id = m.group(9).lower() |
| except IndexError: |
| id = None |
| if not id: |
| # if we got something like "[Google][]" or "[Goggle]" |
| # we'll use "google" as the id |
| id = m.group(2).lower() |
| |
| # Clean up linebreaks in id |
| id = self.NEWLINE_CLEANUP_RE.sub(' ', id) |
| 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 = util.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 = util.etree.Element("img") |
| el.set("src", self.sanitize_url(href)) |
| if title: |
| el.set("title", title) |
| |
| if self.markdown.enable_attributes: |
| text = handleAttributes(text, el) |
| |
| el.set("alt", self.unescape(text)) |
| return el |
| |
| |
| class AutolinkPattern(Pattern): |
| """ Return a link Element given an autolink (`<http://example/com>`). """ |
| def handleMatch(self, m): |
| el = util.etree.Element("a") |
| el.set('href', self.unescape(m.group(2))) |
| el.text = util.AtomicString(m.group(2)) |
| return el |
| |
| class AutomailPattern(Pattern): |
| """ |
| Return a mailto link Element given an automail link (`<foo@example.com>`). |
| """ |
| def handleMatch(self, m): |
| el = util.etree.Element('a') |
| email = self.unescape(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 = entities.codepoint2name.get(code) |
| if entity: |
| return "%s%s;" % (util.AMP_SUBSTITUTE, entity) |
| else: |
| return "%s#%d;" % (util.AMP_SUBSTITUTE, code) |
| |
| letters = [codepoint2name(ord(letter)) for letter in email] |
| el.text = util.AtomicString(''.join(letters)) |
| |
| mailto = "mailto:" + email |
| mailto = "".join([util.AMP_SUBSTITUTE + '#%d;' % |
| ord(letter) for letter in mailto]) |
| el.set('href', mailto) |
| return el |
| |