| OLD | NEW |
| (Empty) |
| 1 # -*- coding: utf-8 -*- | |
| 2 ''' | |
| 3 Smarty extension for Python-Markdown | |
| 4 ==================================== | |
| 5 | |
| 6 Adds conversion of ASCII dashes, quotes and ellipses to their HTML | |
| 7 entity equivalents. | |
| 8 | |
| 9 See <https://pythonhosted.org/Markdown/extensions/smarty.html> | |
| 10 for documentation. | |
| 11 | |
| 12 Author: 2013, Dmitry Shachnev <mitya57@gmail.com> | |
| 13 | |
| 14 All changes Copyright 2013-2014 The Python Markdown Project | |
| 15 | |
| 16 License: [BSD](http://www.opensource.org/licenses/bsd-license.php) | |
| 17 | |
| 18 SmartyPants license: | |
| 19 | |
| 20 Copyright (c) 2003 John Gruber <http://daringfireball.net/> | |
| 21 All rights reserved. | |
| 22 | |
| 23 Redistribution and use in source and binary forms, with or without | |
| 24 modification, are permitted provided that the following conditions are | |
| 25 met: | |
| 26 | |
| 27 * Redistributions of source code must retain the above copyright | |
| 28 notice, this list of conditions and the following disclaimer. | |
| 29 | |
| 30 * Redistributions in binary form must reproduce the above copyright | |
| 31 notice, this list of conditions and the following disclaimer in | |
| 32 the documentation and/or other materials provided with the | |
| 33 distribution. | |
| 34 | |
| 35 * Neither the name "SmartyPants" nor the names of its contributors | |
| 36 may be used to endorse or promote products derived from this | |
| 37 software without specific prior written permission. | |
| 38 | |
| 39 This software is provided by the copyright holders and contributors "as | |
| 40 is" and any express or implied warranties, including, but not limited | |
| 41 to, the implied warranties of merchantability and fitness for a | |
| 42 particular purpose are disclaimed. In no event shall the copyright | |
| 43 owner or contributors be liable for any direct, indirect, incidental, | |
| 44 special, exemplary, or consequential damages (including, but not | |
| 45 limited to, procurement of substitute goods or services; loss of use, | |
| 46 data, or profits; or business interruption) however caused and on any | |
| 47 theory of liability, whether in contract, strict liability, or tort | |
| 48 (including negligence or otherwise) arising in any way out of the use | |
| 49 of this software, even if advised of the possibility of such damage. | |
| 50 | |
| 51 | |
| 52 smartypants.py license: | |
| 53 | |
| 54 smartypants.py is a derivative work of SmartyPants. | |
| 55 Copyright (c) 2004, 2007 Chad Miller <http://web.chad.org/> | |
| 56 | |
| 57 Redistribution and use in source and binary forms, with or without | |
| 58 modification, are permitted provided that the following conditions are | |
| 59 met: | |
| 60 | |
| 61 * Redistributions of source code must retain the above copyright | |
| 62 notice, this list of conditions and the following disclaimer. | |
| 63 | |
| 64 * Redistributions in binary form must reproduce the above copyright | |
| 65 notice, this list of conditions and the following disclaimer in | |
| 66 the documentation and/or other materials provided with the | |
| 67 distribution. | |
| 68 | |
| 69 This software is provided by the copyright holders and contributors "as | |
| 70 is" and any express or implied warranties, including, but not limited | |
| 71 to, the implied warranties of merchantability and fitness for a | |
| 72 particular purpose are disclaimed. In no event shall the copyright | |
| 73 owner or contributors be liable for any direct, indirect, incidental, | |
| 74 special, exemplary, or consequential damages (including, but not | |
| 75 limited to, procurement of substitute goods or services; loss of use, | |
| 76 data, or profits; or business interruption) however caused and on any | |
| 77 theory of liability, whether in contract, strict liability, or tort | |
| 78 (including negligence or otherwise) arising in any way out of the use | |
| 79 of this software, even if advised of the possibility of such damage. | |
| 80 | |
| 81 ''' | |
| 82 | |
| 83 | |
| 84 from __future__ import unicode_literals | |
| 85 from . import Extension | |
| 86 from ..inlinepatterns import HtmlPattern | |
| 87 from ..odict import OrderedDict | |
| 88 from ..treeprocessors import InlineProcessor | |
| 89 | |
| 90 | |
| 91 # Constants for quote education. | |
| 92 punctClass = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]""" | |
| 93 endOfWordClass = r"[\s.,;:!?)]" | |
| 94 closeClass = "[^\ \t\r\n\[\{\(\-\u0002\u0003]" | |
| 95 | |
| 96 openingQuotesBase = ( | |
| 97 '(\s' # a whitespace char | |
| 98 '| ' # or a non-breaking space entity | |
| 99 '|--' # or dashes | |
| 100 '|–|—' # or unicode | |
| 101 '|&[mn]dash;' # or named dash entities | |
| 102 '|–|—' # or decimal entities | |
| 103 ')' | |
| 104 ) | |
| 105 | |
| 106 substitutions = { | |
| 107 'mdash': '—', | |
| 108 'ndash': '–', | |
| 109 'ellipsis': '…', | |
| 110 'left-angle-quote': '«', | |
| 111 'right-angle-quote': '»', | |
| 112 'left-single-quote': '‘', | |
| 113 'right-single-quote': '’', | |
| 114 'left-double-quote': '“', | |
| 115 'right-double-quote': '”', | |
| 116 } | |
| 117 | |
| 118 | |
| 119 # Special case if the very first character is a quote | |
| 120 # followed by punctuation at a non-word-break. Close the quotes by brute force: | |
| 121 singleQuoteStartRe = r"^'(?=%s\B)" % punctClass | |
| 122 doubleQuoteStartRe = r'^"(?=%s\B)' % punctClass | |
| 123 | |
| 124 # Special case for double sets of quotes, e.g.: | |
| 125 # <p>He said, "'Quoted' words in a larger quote."</p> | |
| 126 doubleQuoteSetsRe = r""""'(?=\w)""" | |
| 127 singleQuoteSetsRe = r"""'"(?=\w)""" | |
| 128 | |
| 129 # Special case for decade abbreviations (the '80s): | |
| 130 decadeAbbrRe = r"(?<!\w)'(?=\d{2}s)" | |
| 131 | |
| 132 # Get most opening double quotes: | |
| 133 openingDoubleQuotesRegex = r'%s"(?=\w)' % openingQuotesBase | |
| 134 | |
| 135 # Double closing quotes: | |
| 136 closingDoubleQuotesRegex = r'"(?=\s)' | |
| 137 closingDoubleQuotesRegex2 = '(?<=%s)"' % closeClass | |
| 138 | |
| 139 # Get most opening single quotes: | |
| 140 openingSingleQuotesRegex = r"%s'(?=\w)" % openingQuotesBase | |
| 141 | |
| 142 # Single closing quotes: | |
| 143 closingSingleQuotesRegex = r"(?<=%s)'(?!\s|s\b|\d)" % closeClass | |
| 144 closingSingleQuotesRegex2 = r"(?<=%s)'(\s|s\b)" % closeClass | |
| 145 | |
| 146 # All remaining quotes should be opening ones | |
| 147 remainingSingleQuotesRegex = "'" | |
| 148 remainingDoubleQuotesRegex = '"' | |
| 149 | |
| 150 | |
| 151 class SubstituteTextPattern(HtmlPattern): | |
| 152 def __init__(self, pattern, replace, markdown_instance): | |
| 153 """ Replaces matches with some text. """ | |
| 154 HtmlPattern.__init__(self, pattern) | |
| 155 self.replace = replace | |
| 156 self.markdown = markdown_instance | |
| 157 | |
| 158 def handleMatch(self, m): | |
| 159 result = '' | |
| 160 for part in self.replace: | |
| 161 if isinstance(part, int): | |
| 162 result += m.group(part) | |
| 163 else: | |
| 164 result += self.markdown.htmlStash.store(part, safe=True) | |
| 165 return result | |
| 166 | |
| 167 | |
| 168 class SmartyExtension(Extension): | |
| 169 def __init__(self, *args, **kwargs): | |
| 170 self.config = { | |
| 171 'smart_quotes': [True, 'Educate quotes'], | |
| 172 'smart_angled_quotes': [False, 'Educate angled quotes'], | |
| 173 'smart_dashes': [True, 'Educate dashes'], | |
| 174 'smart_ellipses': [True, 'Educate ellipses'], | |
| 175 'substitutions': [{}, 'Overwrite default substitutions'], | |
| 176 } | |
| 177 super(SmartyExtension, self).__init__(*args, **kwargs) | |
| 178 self.substitutions = dict(substitutions) | |
| 179 self.substitutions.update(self.getConfig('substitutions', default={})) | |
| 180 | |
| 181 def _addPatterns(self, md, patterns, serie): | |
| 182 for ind, pattern in enumerate(patterns): | |
| 183 pattern += (md,) | |
| 184 pattern = SubstituteTextPattern(*pattern) | |
| 185 after = ('>smarty-%s-%d' % (serie, ind - 1) if ind else '_begin') | |
| 186 name = 'smarty-%s-%d' % (serie, ind) | |
| 187 self.inlinePatterns.add(name, pattern, after) | |
| 188 | |
| 189 def educateDashes(self, md): | |
| 190 emDashesPattern = SubstituteTextPattern( | |
| 191 r'(?<!-)---(?!-)', (self.substitutions['mdash'],), md | |
| 192 ) | |
| 193 enDashesPattern = SubstituteTextPattern( | |
| 194 r'(?<!-)--(?!-)', (self.substitutions['ndash'],), md | |
| 195 ) | |
| 196 self.inlinePatterns.add('smarty-em-dashes', emDashesPattern, '_begin') | |
| 197 self.inlinePatterns.add( | |
| 198 'smarty-en-dashes', enDashesPattern, '>smarty-em-dashes' | |
| 199 ) | |
| 200 | |
| 201 def educateEllipses(self, md): | |
| 202 ellipsesPattern = SubstituteTextPattern( | |
| 203 r'(?<!\.)\.{3}(?!\.)', (self.substitutions['ellipsis'],), md | |
| 204 ) | |
| 205 self.inlinePatterns.add('smarty-ellipses', ellipsesPattern, '_begin') | |
| 206 | |
| 207 def educateAngledQuotes(self, md): | |
| 208 leftAngledQuotePattern = SubstituteTextPattern( | |
| 209 r'\<\<', (self.substitutions['left-angle-quote'],), md | |
| 210 ) | |
| 211 rightAngledQuotePattern = SubstituteTextPattern( | |
| 212 r'\>\>', (self.substitutions['right-angle-quote'],), md | |
| 213 ) | |
| 214 self.inlinePatterns.add( | |
| 215 'smarty-left-angle-quotes', leftAngledQuotePattern, '_begin' | |
| 216 ) | |
| 217 self.inlinePatterns.add( | |
| 218 'smarty-right-angle-quotes', | |
| 219 rightAngledQuotePattern, | |
| 220 '>smarty-left-angle-quotes' | |
| 221 ) | |
| 222 | |
| 223 def educateQuotes(self, md): | |
| 224 lsquo = self.substitutions['left-single-quote'] | |
| 225 rsquo = self.substitutions['right-single-quote'] | |
| 226 ldquo = self.substitutions['left-double-quote'] | |
| 227 rdquo = self.substitutions['right-double-quote'] | |
| 228 patterns = ( | |
| 229 (singleQuoteStartRe, (rsquo,)), | |
| 230 (doubleQuoteStartRe, (rdquo,)), | |
| 231 (doubleQuoteSetsRe, (ldquo + lsquo,)), | |
| 232 (singleQuoteSetsRe, (lsquo + ldquo,)), | |
| 233 (decadeAbbrRe, (rsquo,)), | |
| 234 (openingSingleQuotesRegex, (2, lsquo)), | |
| 235 (closingSingleQuotesRegex, (rsquo,)), | |
| 236 (closingSingleQuotesRegex2, (rsquo, 2)), | |
| 237 (remainingSingleQuotesRegex, (lsquo,)), | |
| 238 (openingDoubleQuotesRegex, (2, ldquo)), | |
| 239 (closingDoubleQuotesRegex, (rdquo,)), | |
| 240 (closingDoubleQuotesRegex2, (rdquo,)), | |
| 241 (remainingDoubleQuotesRegex, (ldquo,)) | |
| 242 ) | |
| 243 self._addPatterns(md, patterns, 'quotes') | |
| 244 | |
| 245 def extendMarkdown(self, md, md_globals): | |
| 246 configs = self.getConfigs() | |
| 247 self.inlinePatterns = OrderedDict() | |
| 248 if configs['smart_ellipses']: | |
| 249 self.educateEllipses(md) | |
| 250 if configs['smart_quotes']: | |
| 251 self.educateQuotes(md) | |
| 252 if configs['smart_angled_quotes']: | |
| 253 self.educateAngledQuotes(md) | |
| 254 if configs['smart_dashes']: | |
| 255 self.educateDashes(md) | |
| 256 inlineProcessor = InlineProcessor(md) | |
| 257 inlineProcessor.inlinePatterns = self.inlinePatterns | |
| 258 md.treeprocessors.add('smarty', inlineProcessor, '_end') | |
| 259 md.ESCAPED_CHARS.extend(['"', "'"]) | |
| 260 | |
| 261 | |
| 262 def makeExtension(*args, **kwargs): | |
| 263 return SmartyExtension(*args, **kwargs) | |
| OLD | NEW |