from __future__ import unicode_literals from .abc import Writer import re class _WriterOutput(object): def __init__(self, start, end=None, generate_end=None, anchor_position=None): if generate_end is None: generate_end = _constant(end) self.start = start self.generate_end = generate_end self.anchor_position = anchor_position def _constant(value): def get(): return value return get class _MarkdownState(object): def __init__(self): self._list_state_stack = [] self.list_state = None self.list_item_has_closed = False def update_list_state(self, list_state): self._list_state_stack.append(self.list_state) self.list_state = list_state def pop_list_state(self): self.list_state = self._list_state_stack.pop() class _MarkdownListState(object): def __init__(self, ordered, indentation): self.ordered = ordered self.count = 0 self.indentation = indentation def _symmetric_wrapped(end): return _Wrapped(end, end) class _Wrapped(object): def __init__(self, start, end): self._start = start self._end = end def __call__(self, attributes, markdown_state): return _WriterOutput(self._start, self._end) def _hyperlink(attributes, markdown_state): href = attributes.get("href", "") if href: return _WriterOutput( "[", "]({0})".format(href), anchor_position="before", ) else: return _default_output def _image(attributes, markdown_state): src = attributes.get("src", "") alt_text = attributes.get("alt", "") if src or alt_text: return _WriterOutput("![{0}]({1})".format(alt_text, src), "") else: return _default_output def _list(ordered): def call(attributes, markdown_state): if markdown_state.list_state is None: start = "" end_text = "\n" indentation = 0 else: start = "\n" end_text = "" indentation = markdown_state.list_state.indentation + 1 def generate_end(): markdown_state.pop_list_state() return end_text markdown_state.update_list_state(_MarkdownListState( ordered=ordered, indentation=indentation, )) return _WriterOutput(start, generate_end=generate_end) return call def _list_item(attributes, markdown_state): markdown_state.list_item_has_closed = False list_state = markdown_state.list_state or _MarkdownListState(ordered=False, indentation=0) list_state.count += 1 if list_state.ordered: bullet = "{0}.".format(list_state.count) else: bullet = "-" def generate_end(): if markdown_state.list_item_has_closed: return "" else: markdown_state.list_item_has_closed = True return "\n" return _WriterOutput( start=("\t" * list_state.indentation) + bullet + " ", generate_end=generate_end ) def _init_writers(): writers = { "p": _Wrapped("", "\n\n"), "br": _Wrapped("", " \n"), "strong": _symmetric_wrapped("__"), "em": _symmetric_wrapped("*"), "a": _hyperlink, "img": _image, "ol": _list(ordered=True), "ul": _list(ordered=False), "li": _list_item, } for level in range(1, 7): writers["h{0}".format(level)] = _Wrapped("#" * level + " ", "\n\n") return writers _writers = _init_writers() _default_output = _WriterOutput("", "") def _default_writer(attributes, markdown_state): return _default_output class MarkdownWriter(Writer): def __init__(self): self._fragments = [] self._element_stack = [] self._markdown_state = _MarkdownState() def text(self, text): self._fragments.append(_escape_markdown(text)) def start(self, name, attributes=None): if attributes is None: attributes = {} output = _writers.get(name, _default_writer)(attributes, self._markdown_state) self._element_stack.append(output.generate_end) anchor_before_start = output.anchor_position == "before" if anchor_before_start: self._write_anchor(attributes) self._fragments.append(output.start) if not anchor_before_start: self._write_anchor(attributes) def end(self, name): end = self._element_stack.pop() output = end() self._fragments.append(output) def self_closing(self, name, attributes=None): self.start(name, attributes) self.end(name) def append(self, other): self._fragments.append(other) def as_string(self): return "".join(self._fragments) def _write_anchor(self, attributes): html_id = attributes.get("id") if html_id: self._fragments.append(''.format(html_id)) def _escape_markdown(value): return re.sub(r"([\`\*_\{\}\[\]\(\)\#\+\-\.\!])", r"\\\1", re.sub("\\\\", "\\\\\\\\", value))