OLD | NEW |
1 # markdown is released under the BSD license | |
2 # Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) | |
3 # Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) | |
4 # Copyright 2004 Manfred Stienstra (the original version) | |
5 # | |
6 # All rights reserved. | |
7 # | |
8 # Redistribution and use in source and binary forms, with or without | |
9 # modification, are permitted provided that the following conditions are met: | |
10 # | |
11 # * Redistributions of source code must retain the above copyright | |
12 # notice, this list of conditions and the following disclaimer. | |
13 # * Redistributions in binary form must reproduce the above copyright | |
14 # notice, this list of conditions and the following disclaimer in the | |
15 # documentation and/or other materials provided with the distribution. | |
16 # * Neither the name of the <organization> nor the | |
17 # names of its contributors may be used to endorse or promote products | |
18 # derived from this software without specific prior written permission. | |
19 # | |
20 # THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY | |
21 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
22 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
23 # DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT | |
24 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
25 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
26 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
27 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
28 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
29 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
30 # POSSIBILITY OF SUCH DAMAGE. | |
31 | |
32 | |
33 """ | 1 """ |
34 Attribute List Extension for Python-Markdown | 2 Attribute List Extension for Python-Markdown |
35 ============================================ | 3 ============================================ |
36 | 4 |
37 Adds attribute list syntax. Inspired by | 5 Adds attribute list syntax. Inspired by |
38 [maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s | 6 [maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s |
39 feature of the same name. | 7 feature of the same name. |
40 | 8 |
41 Copyright 2011 [Waylan Limberg](http://achinghead.com/). | 9 See <https://pythonhosted.org/Markdown/extensions/attr_list.html> |
| 10 for documentation. |
42 | 11 |
43 Contact: markdown@freewisdom.org | 12 Original code Copyright 2011 [Waylan Limberg](http://achinghead.com/). |
44 | 13 |
45 License: BSD (see ../LICENSE.md for details) | 14 All changes Copyright 2011-2014 The Python Markdown Project |
46 | 15 |
47 Dependencies: | 16 License: [BSD](http://www.opensource.org/licenses/bsd-license.php) |
48 * [Python 2.4+](http://python.org) | |
49 * [Markdown 2.1+](http://packages.python.org/Markdown/) | |
50 | 17 |
51 """ | 18 """ |
52 | 19 |
53 from __future__ import absolute_import | 20 from __future__ import absolute_import |
54 from __future__ import unicode_literals | 21 from __future__ import unicode_literals |
55 from . import Extension | 22 from . import Extension |
56 from ..treeprocessors import Treeprocessor | 23 from ..treeprocessors import Treeprocessor |
57 from ..util import isBlockLevel | 24 from ..util import isBlockLevel |
58 import re | 25 import re |
59 | 26 |
60 try: | 27 try: |
61 Scanner = re.Scanner | 28 Scanner = re.Scanner |
62 except AttributeError: | 29 except AttributeError: # pragma: no cover |
63 # must be on Python 2.4 | 30 # must be on Python 2.4 |
64 from sre import Scanner | 31 from sre import Scanner |
65 | 32 |
| 33 |
66 def _handle_double_quote(s, t): | 34 def _handle_double_quote(s, t): |
67 k, v = t.split('=') | 35 k, v = t.split('=') |
68 return k, v.strip('"') | 36 return k, v.strip('"') |
69 | 37 |
| 38 |
70 def _handle_single_quote(s, t): | 39 def _handle_single_quote(s, t): |
71 k, v = t.split('=') | 40 k, v = t.split('=') |
72 return k, v.strip("'") | 41 return k, v.strip("'") |
73 | 42 |
74 def _handle_key_value(s, t): | 43 |
| 44 def _handle_key_value(s, t): |
75 return t.split('=') | 45 return t.split('=') |
76 | 46 |
| 47 |
77 def _handle_word(s, t): | 48 def _handle_word(s, t): |
78 if t.startswith('.'): | 49 if t.startswith('.'): |
79 return '.', t[1:] | 50 return '.', t[1:] |
80 if t.startswith('#'): | 51 if t.startswith('#'): |
81 return 'id', t[1:] | 52 return 'id', t[1:] |
82 return t, t | 53 return t, t |
83 | 54 |
84 _scanner = Scanner([ | 55 _scanner = Scanner([ |
85 (r'[^ ]+=".*?"', _handle_double_quote), | 56 (r'[^ ]+=".*?"', _handle_double_quote), |
86 (r"[^ ]+='.*?'", _handle_single_quote), | 57 (r"[^ ]+='.*?'", _handle_single_quote), |
87 (r'[^ ]+=[^ ]*', _handle_key_value), | 58 (r'[^ ]+=[^ =]+', _handle_key_value), |
88 (r'[^ ]+', _handle_word), | 59 (r'[^ =]+', _handle_word), |
89 (r' ', None) | 60 (r' ', None) |
90 ]) | 61 ]) |
91 | 62 |
| 63 |
92 def get_attrs(str): | 64 def get_attrs(str): |
93 """ Parse attribute list and return a list of attribute tuples. """ | 65 """ Parse attribute list and return a list of attribute tuples. """ |
94 return _scanner.scan(str)[0] | 66 return _scanner.scan(str)[0] |
95 | 67 |
| 68 |
96 def isheader(elem): | 69 def isheader(elem): |
97 return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] | 70 return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] |
98 | 71 |
| 72 |
99 class AttrListTreeprocessor(Treeprocessor): | 73 class AttrListTreeprocessor(Treeprocessor): |
100 | 74 |
101 BASE_RE = r'\{\:?([^\}]*)\}' | 75 BASE_RE = r'\{\:?([^\}]*)\}' |
102 HEADER_RE = re.compile(r'[ ]*%s[ ]*$' % BASE_RE) | 76 HEADER_RE = re.compile(r'[ ]+%s[ ]*$' % BASE_RE) |
103 BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE) | 77 BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE) |
104 INLINE_RE = re.compile(r'^%s' % BASE_RE) | 78 INLINE_RE = re.compile(r'^%s' % BASE_RE) |
105 NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u037
0-\u037d' | 79 NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff' |
106 r'\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef' | 80 r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d' |
107 r'\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd' | 81 r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff' |
| 82 r'\uf900-\ufdcf\ufdf0-\ufffd' |
108 r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') | 83 r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+') |
109 | 84 |
110 def run(self, doc): | 85 def run(self, doc): |
111 for elem in doc.getiterator(): | 86 for elem in doc.getiterator(): |
112 if isBlockLevel(elem.tag): | 87 if isBlockLevel(elem.tag): |
113 # Block level: check for attrs on last line of text | 88 # Block level: check for attrs on last line of text |
114 RE = self.BLOCK_RE | 89 RE = self.BLOCK_RE |
115 if isheader(elem): | 90 if isheader(elem) or elem.tag == 'dt': |
116 # header: check for attrs at end of line | 91 # header or def-term: check for attrs at end of line |
117 RE = self.HEADER_RE | 92 RE = self.HEADER_RE |
118 if len(elem) and elem[-1].tail: | 93 if len(elem) and elem.tag == 'li': |
| 94 # special case list items. children may include a ul or ol. |
| 95 pos = None |
| 96 # find the ul or ol position |
| 97 for i, child in enumerate(elem): |
| 98 if child.tag in ['ul', 'ol']: |
| 99 pos = i |
| 100 break |
| 101 if pos is None and elem[-1].tail: |
| 102 # use tail of last child. no ul or ol. |
| 103 m = RE.search(elem[-1].tail) |
| 104 if m: |
| 105 self.assign_attrs(elem, m.group(1)) |
| 106 elem[-1].tail = elem[-1].tail[:m.start()] |
| 107 elif pos is not None and pos > 0 and elem[pos-1].tail: |
| 108 # use tail of last child before ul or ol |
| 109 m = RE.search(elem[pos-1].tail) |
| 110 if m: |
| 111 self.assign_attrs(elem, m.group(1)) |
| 112 elem[pos-1].tail = elem[pos-1].tail[:m.start()] |
| 113 elif elem.text: |
| 114 # use text. ul is first child. |
| 115 m = RE.search(elem.text) |
| 116 if m: |
| 117 self.assign_attrs(elem, m.group(1)) |
| 118 elem.text = elem.text[:m.start()] |
| 119 elif len(elem) and elem[-1].tail: |
119 # has children. Get from tail of last child | 120 # has children. Get from tail of last child |
120 m = RE.search(elem[-1].tail) | 121 m = RE.search(elem[-1].tail) |
121 if m: | 122 if m: |
122 self.assign_attrs(elem, m.group(1)) | 123 self.assign_attrs(elem, m.group(1)) |
123 elem[-1].tail = elem[-1].tail[:m.start()] | 124 elem[-1].tail = elem[-1].tail[:m.start()] |
124 if isheader(elem): | 125 if isheader(elem): |
125 # clean up trailing #s | 126 # clean up trailing #s |
126 elem[-1].tail = elem[-1].tail.rstrip('#').rstrip() | 127 elem[-1].tail = elem[-1].tail.rstrip('#').rstrip() |
127 elif elem.text: | 128 elif elem.text: |
128 # no children. Get from text. | 129 # no children. Get from text. |
129 m = RE.search(elem.text) | 130 m = RE.search(elem.text) |
| 131 if not m and elem.tag == 'td': |
| 132 m = re.search(self.BASE_RE, elem.text) |
130 if m: | 133 if m: |
131 self.assign_attrs(elem, m.group(1)) | 134 self.assign_attrs(elem, m.group(1)) |
132 elem.text = elem.text[:m.start()] | 135 elem.text = elem.text[:m.start()] |
133 if isheader(elem): | 136 if isheader(elem): |
134 # clean up trailing #s | 137 # clean up trailing #s |
135 elem.text = elem.text.rstrip('#').rstrip() | 138 elem.text = elem.text.rstrip('#').rstrip() |
136 else: | 139 else: |
137 # inline: check for attrs at start of tail | 140 # inline: check for attrs at start of tail |
138 if elem.tail: | 141 if elem.tail: |
139 m = self.INLINE_RE.match(elem.tail) | 142 m = self.INLINE_RE.match(elem.tail) |
(...skipping 18 matching lines...) Expand all Loading... |
158 def sanitize_name(self, name): | 161 def sanitize_name(self, name): |
159 """ | 162 """ |
160 Sanitize name as 'an XML Name, minus the ":"'. | 163 Sanitize name as 'an XML Name, minus the ":"'. |
161 See http://www.w3.org/TR/REC-xml-names/#NT-NCName | 164 See http://www.w3.org/TR/REC-xml-names/#NT-NCName |
162 """ | 165 """ |
163 return self.NAME_RE.sub('_', name) | 166 return self.NAME_RE.sub('_', name) |
164 | 167 |
165 | 168 |
166 class AttrListExtension(Extension): | 169 class AttrListExtension(Extension): |
167 def extendMarkdown(self, md, md_globals): | 170 def extendMarkdown(self, md, md_globals): |
168 md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify
') | 171 md.treeprocessors.add( |
| 172 'attr_list', AttrListTreeprocessor(md), '>prettify' |
| 173 ) |
169 | 174 |
170 | 175 |
171 def makeExtension(configs={}): | 176 def makeExtension(*args, **kwargs): |
172 return AttrListExtension(configs=configs) | 177 return AttrListExtension(*args, **kwargs) |
OLD | NEW |