def citationGlue(s):
def DOI(s):
def headerIndent(s, depth=1, minLevel=1, maxLevel=6):
def quote(s): def highlight(md):
def arrows(md):
def autoItalics(md):
def magicVars(md):
def pageBreak(md):
def quote(md): def wordCount(s, excludeYAML=True, clean=True):
def addLineNumbersToPDF(inFile, outFile, color='#d3d7cf'): class ZoteroParser(BaseParser):

	cachePath = u'.zoteromarkdown.cache'

	def __init__(self, libraryId, apiKey, libraryType=u'user',
		clearCache=False, headerText=u'References', headerLevel=1,
		odtStyle=None, fixDOI=True, fixAuthorNames=True, verbose=False,
		removeURL=True):

		"""
		Constructor.

		Arguments:
		libraryId		--	The libraryId, available from your Zotero profile.
		apiKey			--	The API key, available from your Zotero profile.

		Keyword arguments:
		libraryType		--	The library type. Can be 'user' or 'group'.
							(default=u'user')
		clearCache		--	Indicates whether the cache should be cleared.
							(default=False)
		headerText		--	Indicates the text to be used for the header.
							(default=u'References')
		headerLevel		--	Indicates the header level for the references.
							(default=1)
		odtStyle		--	Indicates the style to be used for ODT output. This
							style is indicated as an HTML comment, so that it
							does not affect HTML output. (default=None)
		fixDOI			--	Indicates that the DOI field should be remapped and
							cleaned up for proper rendering. (default=True)
		fixAuthorNames	--	Indicates that first names of authors should be
							converted to clean initials, to avoid one author
							appearing as multiple. (default=True)
		removeURL		--	Removes the URL from the references, because some
							styles insist on adding it. The URL is only removed
							when a journal (i.e. `container-title` field) is
							available. (default=True)
		verbose			--	Indicates whether verbose output should be printed.
							(default=False)
		"""

		if zotero == None:
			raise Exception(u'pyzotero is not available!')
		super(ZoteroParser, self).__init__(verbose=verbose)
		self.zotero = None
		self.libraryId = libraryId
		self.apiKey = apiKey
		self.libraryType = libraryType
		self.headerText = headerText
		self.headerLevel = headerLevel
		self.odtStyle = odtStyle
		self.fixDOI = fixDOI
		self.fixAuthorNames = fixAuthorNames
		self.removeURL = removeURL
		self.refCount = 0
		if not os.path.exists(self.cachePath) or clearCache:
			self.cache = {}
		else:
			fd = open(self.cachePath, 'rb')
			try:
				self.cache = pickle.load(fd)
			except:
				self.msg(u'Failed to open cache.')
				self.cache = {}
			fd.close()

	def connect(self):

		"""Connects to the Zotero API."""

		self.msg(u'Connecting to Zotero server.')
		self.zotero = zotero.Zotero(self.libraryId, self.libraryType, \
			self.apiKey)

	def getYear(self, s):

		"""
		Extracts the year from a string in a clever way.

		Arguments:
		s	--	A string.

		Returns:
		A best guess of the year.
		"""

		try:
			from dateutil import parser
		except:
			self.msg(u'dateutil is not available to guess the year.')
			return s
		try:
			return parser.parse(s).year
		except:
			self.msg(u'failed to parse date %s' % s)
			return s

	def parse(self, md):

		"""
		Parses pandoc-style citations from the documents and adds a
		corresponding bibliography as YAML to the documents.

		Arguments:
		md		--	A string containing MarkDown text.

		Returns:
		The Markdown text with bibliography added.
		"""

		items = []
		oldQueries = []
		regexp =  r'@([^ ?!,.\t\n\r\f\v\]\[;]+)'
		for r in re.finditer(regexp, md):
			queryString = r.groups()[0]
			self.msg(u'Found citation (#%d) "%s"' % (self.refCount,
				queryString))
			if queryString in oldQueries:
				continue
			self.refCount += 1
			matches = self.bestMatch(queryString)
			if len(matches) == 0:
				self.msg(u'No matches for "%s"!' % queryString)
				continue
			if len(matches) > 1:
				for match in matches:
					print(match)
				raise Exception( \
					u'Multiple Zotero matches (%d) for "@%s". Be more specific!' % \
					(len(matches), queryString))
			match = matches[0]
			if match in items and queryString not in oldQueries:
				for _queryString in sorted(oldQueries):
					print(u'Ref: %s' % _queryString)
				raise Exception(
					('"%s" refers to an existent reference with a different name. Please use consistent references (see list above)!' \
					% queryString))
			match[u'id'] = queryString
			if self.odtStyle != None:
				match[u'title'] += u'<!--odt-style="%s"-->' % self.odtStyle
			items.append(match)
			oldQueries.append(queryString)
		# TODO Placing the citation info in the YAML block doesn't appear to
		# work. So for now save it as a JSON file.
		fd = open(u'.bibliography.json', u'w')
		json.dump(items, fd, indent=1)
		fd.close()
		if self.headerText == None or self.headerLevel == None:
			return md
		md = md.replace(u'%rc%', str(self.refCount))
		return md + u'\n\n%s %s\n\n' % (u'#' * self.headerLevel, \
			self.headerText)

	def bestMatch(self, queryString):

		"""
		Retrieves a matching item for a given query. Queries

		Arguments:
		queryString		--	A query string.


		Returns:
		A csljson-style dictionary for the matching item.
		"""

		query = self.splitCitation(queryString)
		if query[0] in self.cache:
			self.msg(u'Retrieving "%s" from cache.' % query[0])
			items = self.cache[query[0]]
		else:
			self.msg(u'Retrieving "%s" from Zotero API.' % query[0])
			if self.zotero == None:
				self.connect()
			try:
				items = self.zotero.top(q=safe_encode(query[0]),
					limit=100, content=u'csljson')
			except:
				self.msg(u'Failed to query Zotero server!')
				return []
			if len(items) == 0:
				return []
			self.cache[query[0]] = items
			fd = open(self.cachePath, u'wb')
			pickle.dump(self.cache, fd)
			fd.close()
		matches = []
		for item in items:
			match = True
			matchPhase = 0
			for i in range(len(query)):
				# Determine whether we are matching a year or an author name
				term = query[i].lower()
				try:
					term = int(term)
					matchPhase += 1
				except:
					pass
				# Check authors
				if matchPhase == 0:
					if 'author' not in item:
						break
					if i >= len(item[u'author']):
						match = False
						break
					if term not in item[u'author'][i][u'family'].lower():
						match = False
						break
				# Check year
				elif matchPhase == 1:
					if u'issued' not in item:
						match = False
						break
					else:
						if u'year' in item[u'issued'] and not item[u'issued']['year'] == u'date unknown':
							year = item[u'issued'][u'year']
						elif u'raw' in item[u'issued']:
							year = item[u'issued'][u'raw']
						elif u'date-parts' in item[u'issued']:
							year = item[u'issued'][u'date-parts'][0][0]
						elif u'literal' in item[u'issued']:
							year = item[u'issued'][u'literal']
						else:
							raise Exception(u'Invalid issued field: %s' % item)
						try:
							year = int(year)
						except:
							pass
						if type(year) == int:
							if term != year:
								match = False
								break
						else:
							if term != 0:
								match = False
								break
					matchPhase += 1
				# Check title or publication
				elif matchPhase == 2:
					if not (u'title' in item and term in \
						item[u'title'].lower()) and not (u'container-title' in \
						item and term in item[u'container-title'].lower()):
						match = False
						break
			# Sometimes, the year of publication is stored as issued.raw,
			# instead of issued.year. Fix this, if this is the case. We need to
			# explictly remove the 'raw' entry as well.
			if u'issued' in item and u'year' not in item[u'issued']:
				if u'raw' in item[u'issued']:
					item[u'issued'][u'year'] = self.getYear(
						item[u'issued'][u'raw']
					)
				elif u'date-parts' in item[u'issued']:
					item[u'issued'][u'year'] = self.getYear(
						item[u'issued'][u'date-parts'][0][0]
					)
				else:
					item[u'issued'][u'year'] = u'date unknown'
			# Make sure that both a lowercase (doi) and uppercase (DOI) key is
			# present, remove prefixed 'doi:' strings, and lowercase the url.
			if self.fixDOI:
				if u'DOI' in item:
					doi = item[u'DOI'][:]
				elif u'doi' in item:
					doi = item[u'doi'][:]
				else:
					doi = None
					self.msg('Missing DOI: %s' % item[u'title'])
				if doi is not None:
					if doi.startswith(u'doi:'):
						doi = doi[4:]
					doi = doi.lower()
					item[u'doi'] = item[u'DOI'] = doi
			# Remove URL field
			if self.removeURL:
				if u'URL' in item.keys() and (u'container-title' in \
					item.keys() or u'publisher' in item.keys()):
					del item[u'URL']
			# Convert initials to 'A.B.C.' style to avoid mixups.
			if self.fixAuthorNames and u'author' in item:
				_author = []
				for author in item[u'author']:
					if u'given' not in author or u'family' not in author:
						continue
					given = author[u'given']
					family = author[u'family']
					# First replace dots by spaces
					given = given.replace(u'.', u' ')
					# Concatenate a list of capitalized initials
					given = u''.join([i[0].upper() for i in given.split()])
					# Add dots after each initial
					given = u'. '.join(given) + u'.'
					_author.append({u'family' : family, u'given': given})
				item[u'author'] = _author
			# Remove empty fields
			for field in item:
				if isinstance(item[field], str) and \
					item[field].strip() == u'':
					self.msg(u'Removing empty field: %s' % field)
					del item[field]
			if match:
				matches.append(item)
		return matches

	def splitCitation(self, s):

		"""
		Splits a citation string, like Land1999WhyAnimals, and returns each
		element of the string in a list.

		Arguments:
		s		--	The citation string, e.g. 'Land1999WhyAnimals'.

		Returns:
		A list of citation elements, e.g. ['land', '1999', 'why', 'animals'].
		"""

		# First, split underscore-style citations, like '@land_1999_why_animals'
		if u'_' in s:
			return s.split(u'_')
		regexp = r'([A-Z][^ 0-9A-Z?!,.\t\n\r\f\v\]\[;]*)'
		# Otherwise, split camelcase-style citations, like @Land1999WhyAnimals.
		l = []
		for t in re.split(regexp, s):
			if t != u'':
				l.append(t.lower().replace(u'+', u' '))
		return l class FigureParser(YAMLParser):

	"""
	The `figure` block embeds a Figure in the text. Figures are numbered
	automatically. The ID can be used to refer to the Figure in the text, using
	a `%` character. So the following figure would be referred to as `%FigFA`.

		%--
		figure:
		 id: FigFA
		 source: foveal_acuity.svg
		 caption: "Visual acuity drops of rapidly with distance from the fovea."
		 width: 100
		--%

	The `caption` and `width` attributes are optional.
	"""

	def __init__(self, style=u'inline', template=u'html5', convertSVG=True, \
			  margins=(30, 20, 30, 20), verbose=False):

		"""
		Constructor.

		Keyword arguments:
		style		--	Can be u'inline' or u'below' to indicate whether figures
						should be placed in or below the text.
						(default=u'inline')
		template	--	Indicates the output format, which can be 'odt' or
						'html5'. (default=u'html5')
		convertSVG	--	Indicates whether .svg files should be converted to .png
						for better embedding. (default=True)
		margins		--	Page margins. (default=30,20,30,20)
		verbose		--	Indicates whether verbose output should be generated.
						(default=False)
		"""

		self.style = style
		self.template = template
		self.convertSVG = convertSVG
		self.margins = margins
		super(FigureParser, self).__init__(_object=u'figure', required=['id', \
			'source'], verbose=verbose)

	def parse(self, md):

		"""See BaseParser.parse()."""

		self.nFig = 0
		return super(FigureParser, self).parse(md)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		self.nFig += 1
		d['nFig'] = self.nFig
		self.msg(u'Found figure: %s (%d)' % (d['id'], self.nFig))

		d[u'source'] = self.getPath(d[u'source'])

		if d[u'source'].lower().endswith(u'.svg') and self.convertSVG:
			dest = d[u'source'] + u'.png'
			self.msg(u'Converting from SVG')
			A4WidthMm = 210
			A4WidthPx = 744.09
			pxPerMm = A4WidthPx / A4WidthMm
			realWidthMm = A4WidthMm - self.margins[1] - self.margins[3]
			realWidthPx = realWidthMm * pxPerMm
			cmd = [u'inkscape', d[u'source'], '-W']
			figWidthPx = float(subprocess.check_output(cmd))
			d[u'width'] = min(100, 100. * figWidthPx / realWidthPx)
			self.msg(u'Width: %.2f (%.2f%%)' % (figWidthPx, d[u'width']))
			if not os.path.exists(dest) or '--clear-svg' in sys.argv:
				cmd = [u'inkscape', u'-f', d[u'source'], u'-e', dest, u'-d', \
					'150', u'-b', u'white', u'-y', u'1.0']
				subprocess.call(cmd)
			else:
				self.msg('"%s" exists, not regenerating' % dest)
			d[u'source'] = dest

		if u'caption' not in d:
			d[u'caption'] = u''
		replaceList = [
			(u'"', u'&quot;'),
			(u'\'', u'&#39;'),
			(u'<', u'&lt;'),
			(u'>', u'&gt;')
			]
		for _from, _to in replaceList:
			d[u'caption'] = d[u'caption'].replace(_from, _to)
		d[u'caption'] = d[u'caption'].strip()
		if u'width' not in d:
			d[u'width'] = 100
		img = figureTemplate[self.template] % d
		if self.style == u'inline':
			md = md.replace(_yaml, img)
		else:
			md = md.replace(_yaml, u'')
			md += img
		# Replace both %MyFigure::a and %MyFigure, to allow for suffixes
		md = md.replace(u'%%%s::' % d[u'id'], u'[Figure %d](#%s)' % (self.nFig, \
			d[u'id']))
		md = md.replace(u'%%%s' % d[u'id'], u'[Figure %d](#%s)' % (self.nFig, \
			d[u'id']))
		return md class CodeParser(YAMLParser):

	"""
	The `code` blocks embeds a code listing in the text, quite to similar to the
	`figure` block.

		%--
		code:
		 id: CodeA caption: |
  Test!

		 source: my_script.py
		 syntax: python
		 caption: "A simple Python script"
		--%

	The `caption` and `syntax` attributes are optional.
	"""

	def __init__(self, style=u'inline', template=u'kramdown', verbose=False):

		"""
		Constructor.

		Keyword arguments:
		style		--	Can be u'inline' or u'below' to indicate whether code
						should be placed in or below the text.
						(default=u'inline')
		template	--	Indicates the output format, which can be 'kramdown' or
						'liquid'. (default=u'kramdown')
		verbose		--	Indicates whether verbose output should be generated.
						(default=False)
		"""

		self.style = style
		self.template = template
		super(CodeParser, self).__init__(_object=u'code', required=['id', \
			'source', 'syntax'], verbose=verbose)

	def parse(self, md):

		"""See BaseParser.parse()."""

		self.nCode = 0
		return super(CodeParser, self).parse(md)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		self.nCode += 1
		d['nCode'] = self.nCode
		self.msg(u'Found code: %s (%d)' % (d['id'], self.nCode))
		d[u'source'] = self.getPath(d[u'source'])
		if u'caption' not in d:
			d[u'caption'] = u''
		if self.template == u'kramdown':
			d[u'syntax'] = u'.' + d[u'syntax']
		with open(self.getPath(d[u'source'])) as fd:
			d[u'code'] = fd.read().strip()
		code = codeTemplate[self.template] % d
		if self.style == u'inline':
			md = md.replace(_yaml, code)
		else:
			md = md.replace(_yaml, u'')
			md += code
		md = md.replace(u'%%%s' % d[u'id'], u'[Listing %d](#%s)' % (self.nCode, \
			d[u'id']))
		return md class ConstantParser(YAMLParser):

	"""
	The `constant` block allows you to define constants. For example, if you
	define MyConstant1 (as below), all occurrences of "%MyConstant1" in the text
	will be replcated by "You can refer to this as %MyConstant1".

		%--
		constant:
			MyConstant1:	"You can refer to this as %MyConstant1"
			MyConstant2:	"You can refer to this as %MyConstant2"
		--%
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(ConstantParser, self).__init__(_object=u'constant',
			verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		# Remove the YAML block
		md = md.replace(_yaml, u'')
		for key, val in d.items():
			self.msg(key)
			md = md.replace(u'%%%s' % key, val.strip())
		return md class ExecParser(YAMLParser):

	"""
	The `exec` block inserts the return value of an external command in the
	text. For example, the following block embeds something like
	'Generated on 10/18/2013':

		%-- exec: "date +'Generated on %x'" --%
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(ExecParser, self).__init__(_object=u'exec', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		if not isinstance(d, basestring):
			return u'Expecting a string, not "%s"' % d
		self.msg(u'Command: %s' % d)
		if not py3:
			d = safe_encode(d)
		output = safe_decode(subprocess.check_output(shlex.split(d)))
		self.msg(u'Returns: %s' % output)
		return md.replace(_yaml, output) class PythonParser(YAMLParser):

	"""
	The `python` block embeds the output (i.e. whatever is printed to stdout)
	of a Python script into your document. For example, the following block
	embeds the docstring of the `PythonParser` class (i.e. what you're reading
	now):

		%--
		python: |
		 import inspect
		 from academicmarkdown import PythonParser
		 print inspect.getdoc(PythonParser)
		--%

	Note that the `|` symbol is YAML syntax, and allows you to have a multiline
	string.
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(PythonParser, self).__init__(_object=u'python', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""
		
		global img_nr

		if not isinstance(d, basestring):
			return u'Expecting a string, not "%s"' % d

		import sys
		try:
			from io import StringIO # Py 3
		except ImportError:
			from StringIO import StringIO # Py 2
		img = False
		code = d
		if d.endswith('\nplt.show()\n'):
			d = d[:-len('\nplt.show()\n')]
			img_nr += 1
			d = 'from matplotlib import pyplot as plt\nplt.clf()\n{}\nplt.savefig("img/{}.png")\n'.format(
				d,
				img_nr
			)
			img = True
		self.msg(u'Python: %s' % d)
		buffer = StringIO()
		sys.stdout = buffer
		if safe_str(d).startswith('# should-raise\n'):
			code = code.replace('# should-raise\n', '')
			try:
				exec('#-*- coding:utf-8 -*-\n%s' % safe_str(d), _globals)
			except Exception as e:
				print('{0}: {1}'.format(e.__class__.__name__, e))
				# import traceback
				# traceback.print_exc(file=buffer, limit=1)
			else:
				raise ValueError('Code should raise exception but didn\t')
		else:
			exec('#-*- coding:utf-8 -*-\n%s' % safe_str(d), _globals)
		sys.stdout = sys.__stdout__
		output = buffer.getvalue()
		self.msg(u'Returns: %s' % output)
		s = u"""
~~~ .python
%s
~~~
""" % safe_str(code)

		if output:
			s += """
__Output:__

~~~ .text
%s
~~~
""" % output
		if img:
			s += '\n![](/img/{}.png)\n'.format(img_nr)
		return md.replace(_yaml, s) class IncludeParser(YAMLParser):

	"""
	The `include` block includes an other Markdown file. For example:

		%-- include: example/intro.md --%
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(IncludeParser, self).__init__(_object=u'include', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		if not isinstance(d, basestring):
			return u'Expecting a string, not "%s"' % d
		d = self.getPath(d)
		self.msg('Include: %s' % d)
		_md = safe_decode(open(d).read())
		# Apply pre-processing Markdown Filters
		for flt in preMarkdownFilters:
			fltFunc = getattr(MDFilter, flt)
			_md = fltFunc(_md)
		ip = IncludeParser(verbose=self.verbose)
		_md = ip.parse(_md)
		return md.replace(_yaml, _md) class TOCParser(YAMLParser):

	"""
	The `toc` block automatically generates a table of contents from the
	headings, assuming that headings are indicated using the `#` style and not
	the underlining style. You can indicate headings to be excluded from the
	table of contents as well.

		%--
		toc:
		 mindepth: 1
		 maxdepth: 2
		 exclude: [Contents, Contact]
		--%

	All attributes are optional.
	"""

	def __init__(self, anchorHeaders=False, appendHeaderRefs=False,
		verbose=False):

		"""
		Constructor.

		Keyword arguments:
		anchorHeaders		--	Indicates whether headers should be turned into
								clickable anchors, mostly useful for HTML pages.
								(default=False)
		appendHeaderRefs 	--	Indicates whether headers references should be
								appended to the document, so that you can
								directly refer to header links in the text. This
								is only necessary when using a Markdown parser
								that doesn't do this automatically.
								(default=False)
		verbose			--	Indicates whether verbose output should be
							generated. (default=False)
		"""

		self.anchorHeaders = anchorHeaders
		self.appendHeaderRefs = appendHeaderRefs
		self._uniqueId = u''
		super(TOCParser, self).__init__(_object=u'toc', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		if u'exclude' not in d:
			d[u'exclude'] = []
		if u'maxdepth' not in d:
			d[u'maxdepth'] = 3
		if u'mindepth' not in d:
			d[u'mindepth'] = 1
		headers = []
		appends = []
		# Because script can have hashtags as code comments, we should ignore
		# script when searching for headers.
		# - Remove standard ~~~ style script blocks
		mdNoScript = re.sub(r'~~~(.*?)~~~', u'DUMMY', md, re.M, re.S)
		# - Remove jekyll style script blocks
		mdNoScript = re.sub(r'{% highlight (\w*) %}(.*?){% endhighlight %}',
			u'DUMMY', mdNoScript, re.M, re.S)
		for i in re.finditer(r'^#(.*)', mdNoScript, re.M):
			h = i.group()
			for level in range(100, -1, -1):
				if h.startswith(u'#' * level):
					break
			if level not in range(d[u'mindepth'], d[u'maxdepth']+1):
				self.msg(u'Header level not in range: %s' % h)
				continue
			label = h[level:].strip()
			_id = self.labelId(label)
			if label not in d[u'exclude']:
				headers.append( (level, h, label, _id) )
				self.msg(u'%s {#%s} (%d)' % (h, _id, level))
			if self.appendHeaderRefs:
				appends.append(u'[%s]: #%s' % (label, _id))
		_md = u'\n'
		lRep = []
		for level, h, label, _id in headers:
			print(h)
			_md += u'\t' * (level-d[u'mindepth']) # Indent
			_md += u'- [%s](#%s)\n' % (label, _id)
			if self.anchorHeaders:
				md = md.replace(h, u'%s [%s](#%s) {#%s}' % (u'#'*level, label, \
					_id, _id))
		_md += u'\n'
		return md.replace(_yaml, _md) + u'\n' + u'\n'.join(appends)

	def labelId(self, label):

		"""
		Generates and ID for a label, simulating the IDs generated by Pandoc.

		IDs should match the IDs that generated by Pandoc. The basic
		algorithm appears to be that spaces are converted to dashes, dashes are
		left in and all non-alphanumeric characters are ignored. Avoid dashes
		at the beginning and end of the ID and also avoid double dashes.

		BUG: Pandoc doesn't properly parse (at least) Chinese characters,
		so links in Chinese TOCs will be broken.

		Arguments:
		label		--	A label.

		Returns:
		An ID.
		"""

		_id = u''
		for ch in label:
			if ch in string.ascii_letters + string.digits + u'-_':
				_id += ch.lower()
			elif ch.isspace() and len(_id) > 0 and _id[-1] != u'-':
				_id += u'-'
		_id = _id.strip(u'-.')
		# Make sure that the ID is not empty and starts with a letter
		if len(_id) == 0 or not _id[0].isalpha():
			_id = self.uniqueId() + _id
		return _id

	def uniqueId(self):

		"""
		Returns:
		A unique letter id.
		"""

		self._uniqueId += u'a'
		return self._uniqueId class VideoParser(YAMLParser):

	"""
	Embeds a video. Currently, YouTube and Vimeo sources are supported. The
	keywords `width`, `height`, and `caption` are optional.

	%--
	video:
	 id: VidRefresh
	 source: vimeo
	 videoid: 24216910
	 width: 640
	 height: 240
	 caption: "A figure caption"
	--%

	"""

	def __init__(self, verbose=False):

		"""
		Constructor.

		Keyword arguments:
		verbose		--	Indicates whether verbose output should be generated.
						(default=False)
		"""

		super(VideoParser, self).__init__(_object=u'video', required=['id', \
			'source', 'videoid'], verbose=verbose)

	def parse(self, md):

		"""See BaseParser.parse()."""

		self.nVid = 0
		return super(VideoParser, self).parse(md)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		self.nVid += 1
		d['nVid'] = self.nVid
		self.msg(u'Found video: %s (%d)' % (d['id'], self.nVid))
		if u'caption' not in d:
			d[u'caption'] = u''
		if u'width' not in d:
			d[u'width'] = 640
		if u'height' not in d:
			d[u'height'] = 320
		vid = videoTemplate[d[u'source']] % d
		md = md.replace(_yaml, vid)
		md = md.replace(u'%%%s' % d[u'id'], u'[Video %d](#%s)' % (self.nVid, \
			d[u'id']))
		return md class TableParser(YAMLParser):

	"""
	The `table` block reads a table from a `.csv` file and embed it into the
	document. The source file needs to be a utf-8 encoded file that is
	comma separated and double quoted.

		%--
		table:
		 id: MyTable
		 source: my_table.csv
		 caption: "My table caption."
		 ndigits: 4
		--%
	"""

	def __init__(self, style=u'inline', template=u'kramdown', verbose= \
		False):

		"""
		Constructor.

		Keyword arguments:
		style		--	Can be u'inline' or u'below' to indicate whether figures
						should be placed in or below the text.
						(default=u'inline')
		template	--	Indicates the output format, which can be 'odt' or
						'html5'. (default=u'html5')
		verbose		--	Indicates whether verbose output should be generated.
						(default=False)
		"""

		self.style = style
		self.template = template
		super(TableParser, self).__init__(_object=u'table', required=['id', \
			'source'], verbose=verbose)

	def parse(self, md):

		"""See BaseParser.parse()."""

		self.nTbl = 0
		return super(TableParser, self).parse(md)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		self.nTbl += 1
		d['nTbl'] = self.nTbl
		self.msg(u'Found table: %s (%d)' % (d['id'], self.nTbl))
		d[u'source'] = self.getPath(d[u'source'])
		if u'caption' not in d:
			d[u'caption'] = u''
		if u'ndigits' not in d:
			d[u'ndigits'] = 4
		# Read table and turn it into a kramdown-style table
		s = u''
		import csv
		i = 0
		with open(d[u'source'], u'r') as csvFile:
			csvReader = csv.reader(csvFile, delimiter=',', quotechar='"')
			for row in csvReader:
				# Pandoc requires a row of alignment indicators below the
				# header. See also:
				# - <http://johnmacfarlane.net/pandoc/README.html#tables>
				if self.template in (u'pandoc', u'kramdown'):
					if i == 1:
						alignList = []
						for col in row:
							try:
								float(col)
								alignList.append(u'--:')
							except:
								alignList.append(u':--')
						s += (u'| ' + u' | '.join(alignList) + u' |\n')
				_row = []
				for col in row:
					try:
						# If a value is numeric, we need to round it. If the
						# rounde value is 0, we indicated this with a smaller
						# than sign.
						float(col)
						col = round(float(col), d[u'ndigits'])
						if col == 0:
							col = u'< 0.' + u'0'*(d[u'ndigits']-1) + u'1'
						else:
							# Use a somethat convoluted string formatting
							# operation to make sure that we don't lose trailing
							# zeros.
							col = (u'%%.%df' % d[u'ndigits']) % col
						_row.append(col)
					except:
						if not col.strip():
							col = u'&nbsp;'
						_row.append(safe_decode(col))
				s += (u'| ' + u' | '.join(_row) + u' |\n')
				i += 1
		d[u'table'] = s
		tbl = tableTemplate[self.template] % d
		# Insert/ append table into document
		if self.style == u'inline':
			md = md.replace(_yaml, tbl)
		else:
			md = md.replace(_yaml, u'')
			md += tbl
		# Replace reference to table
		md = md.replace(u'%%%s' % d[u'id'], u'[Table %d](#%s)' % (self.nTbl, \
			d[u'id']))
		return md class Pandoc(BaseParser):

	def __init__(self, css=None, csl=None, template=None, standalone=True, \
		verbose=False):

		"""
		Constructor.

		Keyword arguments:
		css			--	A path to a `.css` file or None for no stylesheet.
						(default=None)
		csl			--	A path to a `.csl` file to specify a citation format
						or None for a default citation format. (default=None)
		template	--	The HTML template to be used. (default=None)
		standalone	--	Indicates whether the --standalone and --self-contained
						arguments should be passed to pandoc. (default=True)
		verbose		--	Indicates whether verbose output should be generated.
							(default=False)
		"""

		self.css = css
		self.csl = csl
		self.template = template
		self.standalone = standalone
		super(Pandoc, self).__init__(verbose=verbose)

	def docx(self, md, output, docxRef=None):

		"""
		Generates a .docx document.

		Arguments:
		md		--	A Markdown string.
		output	--	The name of the output file.
		"""

		self.odt(md, output, odtRef=docxRef)

	def epub(self, md, output):

		"""
		Generates an .epub document.

		Arguments:
		md		--	A Markdown string.
		output	--	The name of the output file.
		"""

		self.msg(u'Invoking pandoc')
		cmd = u'pandoc --smart -t epub --toc -o %s' % output
		if os.path.exists(u'.bibliography.json'):
			cmd += u' --bibliography .bibliography.json'
			if self.csl != None:
				cmd += u' --csl %s' % self.csl
		ps = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, \
			stdout=subprocess.PIPE)
		print(safe_decode(ps.communicate(safe_encode(md)[0])))

	def html(self, md, output):

		"""
		Generates an .html document.

		Argument:
		md		--	A Markdown string.
		output	--	The name of the output file.
		"""

		open(output, 'w').write(safe_encode(self.parse(md)))

	def odt(self, md, output, odtRef=None):

		"""
		Generates an .odt document.

		Arguments:
		md		--	A Markdown string.
		output	--	The name of the output file.

		Keyword arguments:
		odtRef	--	A reference ODT for styling. (default=None)
		"""

		self.msg(u'Invoking pandoc')
		cmd = u'pandoc --standalone'
		if os.path.exists(u'.bibliography.json'):
			cmd += u' --bibliography .bibliography.json'
			if self.csl != None:
				cmd += u' --csl %s' % self.csl
		if odtRef != None:
			# Since this function is also used to render docx, we should make
			# sure that we pass the correct reference argument.
			if odtRef.endswith(u'.docx'):
				cmd += u' --reference-docx=%s' % odtRef
			else:
				cmd += u' --reference-odt=%s' % odtRef
		cmd += u' -o'
		ps = subprocess.Popen(cmd.split() + [output], stdin=subprocess.PIPE, \
			stdout=subprocess.PIPE)
		print(safe_decode(ps.communicate(safe_encode(md))[0]))

	def parse(self, md):

		"""See BaseParser.parse()."""

		self.msg(u'Invoking pandoc')
		cmd = u'pandoc -f markdown+header_attributes+markdown_attribute -t html5'
		if self.standalone:
			# cmd += u' --standalone --self-contained'
			cmd += u' --standalone --self-contained'
		if self.css != None:
			cmd += u' --css %s' % self.css
		if self.template != None:
			cmd += u' --template %s' % self.template
		if os.path.exists(u'.bibliography.json'):
			cmd += u' --bibliography .bibliography.json'
			if self.csl != None:
				cmd += u' --csl %s' % self.csl
		print(cmd)
		ps = subprocess.Popen(cmd.split(), stdin=subprocess.PIPE, \
			stdout=subprocess.PIPE)
		return safe_decode(ps.communicate(safe_encode(md))[0]) class ODTFixer(BaseParser):
	
	def __init__(self, verbose=False):
		
		super(ODTFixer, self).__init__(verbose=verbose)
		
	def fix(self, path):
		
		self.msg(u'Fixing %s' % path)		
		self.msg(u'Reading ...')
		archive = zipfile.ZipFile(path, 'a')
		content = safe_decode(archive.read(u'content.xml'))
		# Style information is embedded as HTML comments, like so:
		# <!--odt-style="Style"-->. The style needs to be extracted and placed
		# into the <text:p text:style-name="Style"> tags that open a paragraph.
		# We also need to take into account that '<', '>'. and '"' characters
		# have been HTML-ized by pandoc.
		lines = []
		for line in content.split('\n'):
			for toStyle in re.findall( \
				r'&lt;!--odt-style=&quot;(\w+)&quot;--&gt;', line):
				for fromStyle in re.findall( \
					r'<text:p text:style-name="(\w+)">', line):
					line = line.replace(fromStyle, toStyle)
				line = line.replace(u'&lt;!--odt-style=&quot;%s&quot;--&gt;' \
					% toStyle, u'')
				self.msg(u'Changing style "%s" to "%s"' % (fromStyle, toStyle))
			lines.append(line)
		content = u'\n'.join(lines)
		
		#print content
		self.msg(u'Writing ...')
		archive.writestr('content.xml', safe_encode(content))
		archive.close()
		self.msg(u'Done') class WkHtmlToPdf(BaseParser):

	def __init__(self, css=None, fix00=False, margins=(20, 20, 30, 20),
		spacing=(10, 10), header=None, footer=u'%page% / %topage%',
		verbose=False, args=''):

		"""
		Constructor.

		Keyword arguments:
		css			--	A path to a css stylesheet or None. (default=None)
		fix00		--	Indicates whether #00 corruptions should be fixed.
						(default=True)
		margins		--	A T,R,B,L tuple of page margins. (default=20,20,30,20)
		spacing		--	A (header spacing, footer spacing) tuple.
						(default=10,10)
		header		--	A header text, or None. (default=None)
		footer		--	A footer text, or None. (default=u'%page% / %topage%')
		verbose		--	Indicates whether verbose output should be generated.
						(default=False)
		args		--
		"""

		self.css = css
		self.args = args
		self.fix00 = fix00
		self.margins = margins
		self.spacing = spacing
		self.header = header
		self.footer = footer
		super(WkHtmlToPdf, self).__init__(verbose=verbose)

	def createFeader(self, s, cssClass):

		"""
		Builds a header or footer html file.

		Arguments:
		s			--	The contents for the header/ footer.
		cssClass	--	The css class to be used for the div containing the
						contents.

		Returns:
		An HTML string containing the header/ footer.
		"""

		regEx = r'%(?P<var>[a-z]+)%'
		for r in re.finditer(regEx, s):
			s = s.replace(u'%%%s%%' % r.group('var'), \
				u'<span class="%s"></span>' % r.group('var'))
		feader = feaderTmpl.replace(u'%feader%', s)
		if self.css != None:
			feader = feader.replace(u'%css%', \
				u'<link rel="stylesheet" href="%s" type="text/css" />' % \
				self.css)
		else:
			feader = feader.replace(u'%css%', u'')
		feader = feader.replace(u'%class%', cssClass)
		return feader

	def parse(self, html, target):

		"""See BaseParser.parse()."""

		self.msg(u'Invoking wkhtmltopdf')
		cmd = u'wkhtmltopdf -T %s -R %s -B %s -L %s --dpi 96 --disable-smart-shrinking' % self.margins
		if self.header != None:
			open('.header.html', 'wb').write(safe_encode(self.createFeader(
				self.header, u'header')))
			cmd += u' --header-html .header.html --header-spacing %s' % \
				self.spacing[0]
		if self.footer != None:
			open('.footer.html', 'wb').write(safe_encode(
				self.createFeader(self.footer, u'footer')))
			cmd += u' --footer-html .footer.html --footer-spacing %s' % \
				self.spacing[1]
		cmd += u' ' + self.args
		cmd += u' %s "%s"' % (html, target)
		self.msg(cmd)
		if not py3:
			cmd = safe_encode(cmd)
		subprocess.call(shlex.split(cmd))
		if self.fix00:
			# Due to a bug in wkhtmltopdf, the PDF may contain #00 strings,
			# which cause Acrobat Reader to choke (but not other PDF readers).
			# This happens mostly when filenames are very long, in which case
			# anchors are hashed, and the resulting hashes sometimes contain #00
			# values. Here we simply replace all #00 strings, which seems to
			# work.
			self.msg(u'Checking for #00')
			pdf = open(target, 'rb').read()
			if safe_encode('#00') in pdf:
				self.msg(u'Fixing #00!')
				pdf = pdf.replace('#00', '#01')
				open(target, u'wb').write(pdf) class WcParser(YAMLParser):

	"""
	The `wc` block insert the word count for a particular document. This is
	convenient if you have split the text across multiple documents, and want to
	have a separate word count for each document.

		%-- wc: method-section.md --%
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(WcParser, self).__init__(_object=u'wc', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		if not isinstance(d, str):
			return u'Expecting a string, not "%s"' % d
		s = safe_decode(open(self.getPath(d)).read())
		wc = str(len(s.split()))
		self.msg(u'Word count: %s words in %s' % (wc, d))
		return md.replace(_yaml, wc) class GitHubParser(YAMLParser):

	"""
	The `github` block includes references to GitHub issues or user profiles.
	For example:

		%-- github: { user: smathot } --%
		%-- github: { repo: "smathot/academicmarkdown", issue: 1 } --%
	"""

	def __init__(self, verbose=False):

		"""See YAMLParser.__init__()."""

		super(GitHubParser, self).__init__(_object=u'github', verbose=verbose)

	def parseObject(self, md, _yaml, d):

		"""See YAMLParser.parseObject()."""

		if not isinstance(d, dict):
			raise Exception(u'Expecting a dict')
		if u'user' in d:
			username = d['user']
			return md.replace(_yaml, \
				u'[@%s](https://github.com/%s)' % (username, username))
		if u'issue' in d:			
			url = u'https://api.github.com/repos/%(repo)s/issues/%(issue)d' % d
			s = urlget(url)
			d = yaml.load(s)
			summary = u'[Issue #%(number)s](%(url)s): %(title)s' % d
			for label in d[u'labels']:
				summary += u' (*%(name)s*) ' % label
			return md.replace(_yaml, summary)
		raise Exception(u'Invalid GitHub block')
import os
import sys
import subprocess
from academicmarkdown import FigureParser, Pandoc, ZoteroParser, ODTFixer, \
	ExecParser, IncludeParser, TOCParser, HTMLFilter, MDFilter, WkHtmlToPdf, \
	CodeParser, WcParser, VideoParser, TableParser, PythonParser, \
	tools, ConstantParser, GitHubParser
from academicmarkdown.constants import *
from academicmarkdown.py3compat import *
---
"""


def HTML(src, target=None, standalone=True):

	"""
	desc: |
		Builds an HTML file from a Markdown source.

		%--
		constant:
			arg_src:
				The Markdown source. If it is a path to an existing file, the
				contents of this file are read. Otherwise, the string itself
				it used. Should be in utf-8 encoding.
		--%

	arguments:
		src:
			desc:	"%arg_src"
			type:	[str, unicode]

	keywords:
		target:
			desc:	The name of an HTML target file or None to skip saving.
			type:	[str, unicode, NoneType]
		standalone:
			desc:	Indicates whether a full HTML5 document should be generated,
					which embeds all content, or whether the document should
					be rendered without `<head>` and `<body>` tags, etc.
			type:	bool

	returns:
		desc:		The HTML file as a unicode string.
		type:		unicode
	"""

	md = MD(src)
	# Count words
	print(u'Document statistics:')
	print(u'Word count: %d' % len(md.split()))
	print(u'Character count: %d' % len(md))
	# And finally convert the Markdown to HTML
	pd = Pandoc(css=css, csl=csl, template=html5Ref, standalone=standalone, \
		verbose=True)
	html = pd.parse(md)
	for flt in htmlFilters:
		fltFunc = getattr(HTMLFilter, flt)
		html = fltFunc(html)
	if target != None:
		open(target, u'wb').write(safe_encode(html))
	print(u'Done!')
	return html

def MD(src, target=None):

	"""
	desc:
		Builds a Markdown file from a Markdown source.

	arguments:
		src:
			desc:	"%arg_src"
			type:	[str, unicode]

	keywords:
		target:
			desc: 	The name of a Markdown target file or None to skip saving.
			type:	[str, unicode, NoneType]

	returns:
		desc:		The compiled Markdown file as a unicode string.
		type:		unicode
	"""

	if os.path.exists(src):
		md = safe_decode(open(src).read())
		print(u'Building %s from %s ...' % (target, src))
	else:
		md = src
		print(u'Building from string ...')
	# Apply pre-processing Markdown Filters
	for flt in preMarkdownFilters:
		fltFunc = getattr(MDFilter, flt)
		md = fltFunc(md)
	# Apply all extensions
	for ext in extensions:
		print(u'Parsing with %s extension ...' % ext)
		if u'include' == ext:
			md = IncludeParser(verbose=True).parse(md)
		elif u'toc' == ext:
			md = TOCParser(anchorHeaders=TOCAnchorHeaders, appendHeaderRefs= \
				TOCAppendHeaderRefs, verbose=True).parse(md)
		elif u'figure' == ext:
			md = FigureParser(verbose=True, style=figureStyle, template= \
				figureTemplate, margins=pdfMargins).parse(md)
		elif u'video' == ext:
			md = VideoParser(verbose=True).parse(md)
		elif u'table' == ext:
			md = TableParser(style=tableStyle, template=tableTemplate, verbose= \
				True).parse(md)
		elif u'code' == ext:
			md = CodeParser(verbose=True, style=codeStyle, template=codeTemplate) \
				.parse(md)
		elif u'wc' == ext:
			md = WcParser(verbose=True).parse(md)
		elif u'exec' == ext:
			md = ExecParser(verbose=True).parse(md)
		elif u'python' == ext:
			md = PythonParser(verbose=True).parse(md)
		elif u'constant' == ext:
			md = ConstantParser(verbose=True).parse(md)
		elif u'github' == ext:
			md = GitHubParser(verbose=True).parse(md)
		else:
			raise Exception(u'Unknown Academic Markdown extension: %s' % ext)
	# Parse Zotero references
	if zoteroApiKey != None and zoteroLibraryId != None:
		clearCache = '--clear-cache' in sys.argv

		md = ZoteroParser(verbose=True, apiKey=zoteroApiKey, libraryId= \