-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmd.py
More file actions
executable file
·327 lines (276 loc) · 11.3 KB
/
md.py
File metadata and controls
executable file
·327 lines (276 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/python
import argparse
import os
import re
import markdown
from markdown.util import etree
import lxml
import lxml.etree
from lxml.cssselect import CSSSelector
from mdx_tolatex import laTeXRenderer
from mdx_defs import build_headings
from postprocess import build_sections
from utils import get_by_id
import logging
import mdx_macros
template_system = None
try:
import django.conf
import django.template
logging.basicConfig()
django.conf.settings.configure()
template_system = "DJANGO"
except:
pass
if not template_system:
try:
import jinja2
jinja_env=jinja2.Environment(extensions=['jinja2.ext.autoescape'])
template_system = "JINJA2"
except:
pass
logger = logging.getLogger("md")
root_logger = logging.getLogger()
def render_template(tpl, context):
if template_system == "DJANGO":
dtpl = django.template.Template(tpl)
return dtpl.render(django.template.Context(context))
elif template_system == "JINJA2":
print(jinja_env.extensions)
dtpl = jinja_env.from_string(tpl)
return dtpl.render(context)
else:
logging.getLogger("md").warn("No template library found!")
for (key,value) in context.items():
exp = re.compile(r"{{\s*"+key+"\s*}}",re.UNICODE | re.IGNORECASE)
tpl=exp.sub(value.replace("\\", "\\\\"),tpl)
exp = re.compile("{%[^%]*%}")
tpl = exp.sub("",tpl)
return tpl
def text_content(element):
""" Returns the contents of the element @element
converted to plain text """
ret = ''
if element.text:
ret += element.text
for c in element:
ret += ' '+ text_content(c)
if element.tail:
ret += element.tail
return ret
def parse_html( html, tree ):
""" Parses html as an
- lxml.etree (@tree='lxml')
- markdown.util.etree (@tree='md')
"""
try:
if tree == 'lxml':
return lxml.etree.fromstring((u'<html><head></head><body>'+html+u'</body></html>').encode('utf-8'), lxml.etree.HTMLParser(encoding='utf-8'))
else:
return etree.fromstring((u'<html><head></head><body>'+html+u'</body></html>').encode('utf-8'))
except Exception as e:
ln = e.position[0]-1
col = e.position[1]
lns = html.split('\n')
logger.warn("XML PARSE ERROR ("+tree+"): Line: "+str(ln)+", Col: "+str(col))
logger.warn("----------------")
for i in range(-3,4):
if i == 0:
logger.warn(str(i)+' ***: '+lns[ln+i][:col]+'->'+lns[ln+i][col]+'<-'+lns[ln+i][col+1:])
else:
logger.warn(str(i)+' : '+lns[ln+i])
logger.warn("----------------")
raise e
def render_md( md_text, tree = None, ext_config = {} ):
""" Converts the (unicode) markdown @md_text to html.
And returns the pair (md,html) where
md is the resulting parser instance
and the html is
- html string (@tree = None)
- parsed lxml.etree (@tree='lxml')
- parsed markdown.util.etree (@tree='md')
"""
md = markdown.Markdown(extensions=['extra','defs','mymathjax','wikilinks','headerid','references','bibliography','meta'],extension_configs=ext_config)
doc = mdx_macros.pre_process(md_text)
html = md.convert(doc)
if tree:
return md, parse_html(html, tree)
else:
return md, html
def filter(css_selector, lxmltree, include_references = True):
""" Returns an iterable over the elements from @lxmltree matching
the css selector @css_selector. If @include_references is True,
the iterable will contain an additional <references> element
which will contain all referenced elements not already present.
"""
try:
selector = CSSSelector(css_selector)
if include_references:
refs_selector = CSSSelector('ref')
ref_ids = set([])
ret = []
# Find all references in the selected elements
# and store the referenced urls (should be of the form #id)
# in ref_ids
for ch in selector(lxmltree):
ret.append(ch)
for ref_node in refs_selector(ch):
a_nodes = ref_node.findall('a')
if len(a_nodes) > 0:
ref_ids.add(a_nodes[-1].get('href',None))
if len(ref_ids) > 0:
# Create the <references> element and an appropriate heading for it
ref_parent = lxml.etree.Element('references')
ref_h = lxml.etree.SubElement(ref_parent,'h1')
ref_h.set('class','do_not_number')
ref_h.text = 'References'
references_found = False
# Iterate over the reference ids and check that they exist
# and are not already present in the selected elements
for id in sorted(ref_ids):
if id:
logger.debug("Checking reference "+id)
# Only include the reference if it is not already present in the selected elements
if get_by_id(selector(lxmltree),id[1:]) is None:
logger.debug("Adding reference "+id)
ref = get_by_id(lxmltree,id[1:])
if ref is None:
logger.warn("Reference "+id+" not found")
else:
references_found = True
ref_parent.append(ref)
# Do not include the <references> element if there were no references
if references_found:
ret.append(ref_parent)
return ret
return selector(lxmltree)
except Exception as e:
logger.critical("Error parsing filter: "+css_selector+" ("+str(e)+")")
return []
def query( css_selector, lxmltree ):
""" Returns a list of maps containing the properties of elements
from @lxmltree matching the @css_selector. Each map moreover
contains a 'tag' key (with the respective element tagname) and
a 'text_content' key (with the text content of the element).
"""
ret = []
for e in filter(css_selector,lxmltree):
e_map = {
'tag':e.tag,
'text_content':text_content(e)
}
e_map.update(e.attrib)
ret.append( e_map )
return ret
def load_template(tpl, fmt):
format_exts = {
'html':'html',
'latex':'tex'
}
base_name = tpl or 'default-template'
search_dirs = [ '.', os.environ['HOME'], os.path.dirname(os.path.realpath(__file__)) ]
for dirname in search_dirs:
try:
fname = os.path.join(dirname,base_name+'.'+format_exts[fmt])
return unicode(file(fname,'r').read(),encoding='utf-8',errors='ignore')
except:
pass
if tpl:
logger.critical('Could not open template file ' + tpl)
exit(-1)
else:
logger.warn('Could not open default template')
template = '{{ content }}'
def main():
parser = argparse.ArgumentParser(description='A markdown processor')
parser.add_argument('-t', '--template', help='document template')
parser.add_argument('-ao', '--autooutput', help='automatically create the output file with appropriate extension', action='store_true')
parser.add_argument('-o', '--output', type=argparse.FileType('w'),help='output file')
parser.add_argument('-f', '--format',help='output format', choices=['html','latex'],default='html')
parser.add_argument('-s', '--subs',help='template substitutions',default='')
parser.add_argument('--filter',help='process only elements matching a given css selector',default=None)
parser.add_argument('-q', '--query',help='print out elements matching a given css selector')
parser.add_argument('--attrs', help='a comma separated list of attribute names to print (in conjunction with -q)', default='text_content')
parser.add_argument('--norefs',help='do not include referenced elements when filtering',action='store_true')
parser.add_argument('--numberreferencedonly',help='only number blocks which are actually referenced',action='store_true')
parser.add_argument('--verbose', '-v', action='count',help='be verbose',default=0)
parser.add_argument('--renderoptions', help='a comma separated list of key=value pairs which will be passed as options to the renderer',default=None)
parser.add_argument('document',type=argparse.FileType('r'),help='filename of the document to transform')
args = parser.parse_args()
root_logger.setLevel(logging.FATAL-args.verbose*10)
template = load_template(args.template, args.format)
doc_source = unicode(args.document.read(),encoding='utf-8',errors='ignore');
comments_re = re.compile('^\/\/.*$', re.MULTILINE)
doc_source = comments_re.sub('',doc_source)
md, lxml_tree = render_md(doc_source,tree='lxml',ext_config={'references':{'number_referenced_only':args.numberreferencedonly}})
lxml_tree = build_sections(lxml_tree)
if args.query:
attrs = args.attrs.split(',')
for e in query(args.query, lxml_tree):
print(','.join([ attr+'='+e[attr] for attr in attrs if attr in e ]))
return
if args.filter:
html = '\n'.join([lxml.etree.tostring(e,method='html') for e in filter(args.filter, lxml_tree, include_references=not args.norefs)])
else:
html = lxml.etree.tostring(lxml_tree,method='html')
html_tree = parse_html(html,tree='lxml')
dct = {}
render_options = {}
if args.renderoptions:
try:
for opt in args.renderoptions.split(','):
key,value = opt.split('=')
render_options[key]=eval(value)
except Exception as e:
logger.warn('Bad render options: '+args.renderoptions+' ('+str(e)+')')
if args.format == 'html':
output = html
dct['headings_css'] = build_headings(html_tree,position='after',format='css')
elif args.format == 'latex':
dct['headings'] = build_headings(html_tree,position='after',format='latex')
latex = laTeXRenderer(render_options)
output = latex.render_from_dom(html_tree)
dct['content']=output
if hasattr(md, 'Meta'):
for (k,v) in md.Meta.items():
dct[k] = ' '.join(v)
dct['toc']=etree.tostring(md.TOC.to_element())
try:
for l in open('.md-substitutions','r').readlines():
if l.strip().startswith('#'):
continue
try:
k,v = l.strip().split('=')
dct[k.strip()] = v.strip()
except:
pass
except:
pass
if args.subs:
for ts in args.subs.split(','):
k,v = ts.split('=')
dct[k] = v
if 'image_path' in dct:
exp = re.compile(r'(<img\s*[^>]*)\s*src=[\'"]([^\'"]*)[\'"]([^>]*>)')
dct['content']=exp.sub(r'\1src="'+dct['image_path']+r'\2"\3',dct['content'])
if args.document.name.endswith('.md'):
dct['basename'] = args.document.name[:-3]
else:
dct['basename'] = args.document.name
output = render_template(template, dct)
if args.output:
args.output.write(output.encode('utf-8'))
elif args.autooutput:
extensions = {
'html':'.html',
'latex':'.tex'
}
base = args.document.name;
if base.endswith('.md'):
base = base[:-3]
out_fname=base+extensions[args.format]
open(out_fname,'w').write(output.encode('utf-8'))
else:
print(output.encode('utf-8'))
if __name__ == "__main__":
main()