OLD | NEW |
| (Empty) |
1 """ | |
2 HeaderID Extension for Python-Markdown | |
3 ====================================== | |
4 | |
5 Auto-generate id attributes for HTML headers. | |
6 | |
7 Basic usage: | |
8 | |
9 >>> import markdown | |
10 >>> text = "# Some Header #" | |
11 >>> md = markdown.markdown(text, ['headerid']) | |
12 >>> print md | |
13 <h1 id="some-header">Some Header</h1> | |
14 | |
15 All header IDs are unique: | |
16 | |
17 >>> text = ''' | |
18 ... #Header | |
19 ... #Header | |
20 ... #Header''' | |
21 >>> md = markdown.markdown(text, ['headerid']) | |
22 >>> print md | |
23 <h1 id="header">Header</h1> | |
24 <h1 id="header_1">Header</h1> | |
25 <h1 id="header_2">Header</h1> | |
26 | |
27 To fit within a html template's hierarchy, set the header base level: | |
28 | |
29 >>> text = ''' | |
30 ... #Some Header | |
31 ... ## Next Level''' | |
32 >>> md = markdown.markdown(text, ['headerid(level=3)']) | |
33 >>> print md | |
34 <h3 id="some-header">Some Header</h3> | |
35 <h4 id="next-level">Next Level</h4> | |
36 | |
37 Works with inline markup. | |
38 | |
39 >>> text = '#Some *Header* with [markup](http://example.com).' | |
40 >>> md = markdown.markdown(text, ['headerid']) | |
41 >>> print md | |
42 <h1 id="some-header-with-markup">Some <em>Header</em> with <a href="http://e
xample.com">markup</a>.</h1> | |
43 | |
44 Turn off auto generated IDs: | |
45 | |
46 >>> text = ''' | |
47 ... # Some Header | |
48 ... # Another Header''' | |
49 >>> md = markdown.markdown(text, ['headerid(forceid=False)']) | |
50 >>> print md | |
51 <h1>Some Header</h1> | |
52 <h1>Another Header</h1> | |
53 | |
54 Use with MetaData extension: | |
55 | |
56 >>> text = '''header_level: 2 | |
57 ... header_forceid: Off | |
58 ... | |
59 ... # A Header''' | |
60 >>> md = markdown.markdown(text, ['headerid', 'meta']) | |
61 >>> print md | |
62 <h2>A Header</h2> | |
63 | |
64 Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/). | |
65 | |
66 Project website: <http://packages.python.org/Markdown/extensions/header_id.html> | |
67 Contact: markdown@freewisdom.org | |
68 | |
69 License: BSD (see ../docs/LICENSE for details) | |
70 | |
71 Dependencies: | |
72 * [Python 2.3+](http://python.org) | |
73 * [Markdown 2.0+](http://packages.python.org/Markdown/) | |
74 | |
75 """ | |
76 | |
77 from __future__ import absolute_import | |
78 from __future__ import unicode_literals | |
79 from . import Extension | |
80 from ..treeprocessors import Treeprocessor | |
81 import re | |
82 import logging | |
83 import unicodedata | |
84 | |
85 logger = logging.getLogger('MARKDOWN') | |
86 | |
87 IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$') | |
88 | |
89 | |
90 def slugify(value, separator): | |
91 """ Slugify a string, to make it URL friendly. """ | |
92 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') | |
93 value = re.sub('[^\w\s-]', '', value.decode('ascii')).strip().lower() | |
94 return re.sub('[%s\s]+' % separator, separator, value) | |
95 | |
96 | |
97 def unique(id, ids): | |
98 """ Ensure id is unique in set of ids. Append '_1', '_2'... if not """ | |
99 while id in ids or not id: | |
100 m = IDCOUNT_RE.match(id) | |
101 if m: | |
102 id = '%s_%d'% (m.group(1), int(m.group(2))+1) | |
103 else: | |
104 id = '%s_%d'% (id, 1) | |
105 ids.add(id) | |
106 return id | |
107 | |
108 | |
109 def itertext(elem): | |
110 """ Loop through all children and return text only. | |
111 | |
112 Reimplements method of same name added to ElementTree in Python 2.7 | |
113 | |
114 """ | |
115 if elem.text: | |
116 yield elem.text | |
117 for e in elem: | |
118 for s in itertext(e): | |
119 yield s | |
120 if e.tail: | |
121 yield e.tail | |
122 | |
123 | |
124 class HeaderIdTreeprocessor(Treeprocessor): | |
125 """ Assign IDs to headers. """ | |
126 | |
127 IDs = set() | |
128 | |
129 def run(self, doc): | |
130 start_level, force_id = self._get_meta() | |
131 slugify = self.config['slugify'] | |
132 sep = self.config['separator'] | |
133 for elem in doc.getiterator(): | |
134 if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']: | |
135 if force_id: | |
136 if "id" in elem.attrib: | |
137 id = elem.get('id') | |
138 else: | |
139 id = slugify(''.join(itertext(elem)), sep) | |
140 elem.set('id', unique(id, self.IDs)) | |
141 if start_level: | |
142 level = int(elem.tag[-1]) + start_level | |
143 if level > 6: | |
144 level = 6 | |
145 elem.tag = 'h%d' % level | |
146 | |
147 | |
148 def _get_meta(self): | |
149 """ Return meta data suported by this ext as a tuple """ | |
150 level = int(self.config['level']) - 1 | |
151 force = self._str2bool(self.config['forceid']) | |
152 if hasattr(self.md, 'Meta'): | |
153 if 'header_level' in self.md.Meta: | |
154 level = int(self.md.Meta['header_level'][0]) - 1 | |
155 if 'header_forceid' in self.md.Meta: | |
156 force = self._str2bool(self.md.Meta['header_forceid'][0]) | |
157 return level, force | |
158 | |
159 def _str2bool(self, s, default=False): | |
160 """ Convert a string to a booleen value. """ | |
161 s = str(s) | |
162 if s.lower() in ['0', 'f', 'false', 'off', 'no', 'n']: | |
163 return False | |
164 elif s.lower() in ['1', 't', 'true', 'on', 'yes', 'y']: | |
165 return True | |
166 return default | |
167 | |
168 | |
169 class HeaderIdExtension(Extension): | |
170 def __init__(self, configs): | |
171 # set defaults | |
172 self.config = { | |
173 'level' : ['1', 'Base level for headers.'], | |
174 'forceid' : ['True', 'Force all headers to have an id.'], | |
175 'separator' : ['-', 'Word separator.'], | |
176 'slugify' : [slugify, 'Callable to generate anchors'], | |
177 } | |
178 | |
179 for key, value in configs: | |
180 self.setConfig(key, value) | |
181 | |
182 def extendMarkdown(self, md, md_globals): | |
183 md.registerExtension(self) | |
184 self.processor = HeaderIdTreeprocessor() | |
185 self.processor.md = md | |
186 self.processor.config = self.getConfigs() | |
187 if 'attr_list' in md.treeprocessors.keys(): | |
188 # insert after attr_list treeprocessor | |
189 md.treeprocessors.add('headerid', self.processor, '>attr_list') | |
190 else: | |
191 # insert after 'prettify' treeprocessor. | |
192 md.treeprocessors.add('headerid', self.processor, '>prettify') | |
193 | |
194 def reset(self): | |
195 self.processor.IDs = set() | |
196 | |
197 | |
198 def makeExtension(configs=None): | |
199 return HeaderIdExtension(configs=configs) | |
OLD | NEW |