コード例 #1
0
def test_replace():
    """
    Includes testing link creation.
    """
    parts = {
        'index':
        trim("""
            Test document

            - The Simple Test
            - The More Complex Test
            """),
        'test':
        trim("""
            This is a @[simple] test.

            This is a @[-more complex] test.
            """),
    }

    outline = Outline(parts, default_counters())
    placeholders = CrossReferences(parts, outline)

    tokenized = placeholders.insert(parts)
    html_parts = placeholders.replace(tokenized)
    out = html_parts['test']

    assert 'The Simple Test' in out
    assert '&sect;2' in out  # <-- complex uses shorthand
    assert 'the-simple-test' in out
    assert 'the-more-complex-test' in out
コード例 #2
0
ファイル: test_footnotes.py プロジェクト: eukras/article-wiki
def test_constructor():
    parts = sample_doc()
    outline = Outline(parts, default_counters())
    footnotes = Footnotes(parts, outline, 'prefix_')
    assert footnotes.outline.errors == {
        'topic-four': [('', 'More <kbd>^[link]s</kbd> than footnotes! (+1)')],
        'topic-three': [('', 'More footnotes than <kbd>^[link]s</kbd>! (+1)')]
    }
コード例 #3
0
ファイル: test_outline.py プロジェクト: eukras/article-wiki
def test_find_numbering():
    arg = {
        'index':
        trim("""
            Title

            - Chapter One
            - - Chapter One Aye
            - Chapter Two
            """),
        'chapter-one':
        "Chapter One\n\n" + ('word ' * 998)
    }
    outline = Outline(arg, default_counters())
    assert outline.find_numbering('chapter-one') == ['1']
    assert outline.find_numbering('chapter-one-aye') == ['1', 'a']
    assert outline.find_numbering('chapter-two') == ['2']
コード例 #4
0
ファイル: test_outline.py プロジェクト: eukras/article-wiki
def test_outline():
    arg = {
        'index':
        trim("""
            Title

            - Chapter One
            - Chapter Two
            """),
        'chapter-one':
        "Chapter One\n\n" + ('word ' * 998)
    }
    outline = Outline(arg, default_counters())
    out = outline.html('/')  # <-- edit_base_uri
    # @todo: Check this.
    # dom = html.fragment_fromstring(out, create_parent='body')[0]
    # assert len(dom.cssselect('tr')) == 4
    rows = findall('<tr', out)
    assert len(rows) == 4
コード例 #5
0
ファイル: test_outline.py プロジェクト: eukras/article-wiki
def test_elements():
    arg = {
        'blog-1': "Blog One\n\n" + ('word ' * 500),
        'blog-2': "Blog Two\n\n" + ('word ' * 500)
    }
    outline = Outline(arg, default_counters())
    # Numbering, slug, title, title_slug, word count.
    expected = [(['1'], 'blog-1', 'Blog One', 'blog-one', 502),
                (['2'], 'blog-2', 'Blog Two', 'blog-two', 502)]
    assert outline.elements == expected
コード例 #6
0
ファイル: test_index.py プロジェクト: eukras/article-wiki
def test_get_count():
    parts = {
        'index': 'INDEX',
        'other': 'OTHER',
    }
    outline = Outline(parts, default_counters())
    index = Index(outline)
    assert index.get_count('index') == 'i'
    assert index.get_count('index') == 'ii'
    assert index.get_count('other') == 'i'
    assert index.get_count('other') == 'ii'
コード例 #7
0
ファイル: test_index.py プロジェクト: eukras/article-wiki
def test_tag():
    parts = {}
    outline = Outline(parts, default_counters())
    index = Index(outline)
    link = index.tag('alias', 'tag', 'subtag', '?', ['1', 'a'], 'i')

    assert '<a id="tag_subtag_1.a_i" href="#ref_tag_subtag_1.a_i">' in link
    assert 'alias?<sup>i</sup>' in link  # <-- (?)
    assert '</a>' in link

    back_link = '<a id="ref_tag_subtag_1.a_i" href="#tag_subtag_1.a_i">'
    assert back_link in index.tags['tag']['subtag']['1.a'][0]
コード例 #8
0
def test_footnotes():
    """
    Simple count that we have the right number of footnotes.
    """
    parts = sample_doc()
    footnotes = Footnotes(parts, Outline(parts, default_counters()), id_prefix)
    links = Links(footnotes)
    new_parts = links.insert(parts)
    end_parts = links.replace(new_parts)
    out = footnotes.html()
    dom = html.fragment_fromstring(out, create_parent='body')[0]
    assert len(dom.cssselect('sup')) == 6
コード例 #9
0
def test_get_count():
    parts = {
        'index': 'INDEX',
        'other': 'OTHER',
    }
    outline = Outline(parts, default_counters())
    bibliography = Bibliography(parts, outline, id_prefix)
    assert bibliography.get_count('index') == '*'
    assert bibliography.get_count('index') == '†'
    assert bibliography.get_count('index') == '‡'
    assert bibliography.get_count('other') == 'a'
    assert bibliography.get_count('other') == 'b'
    assert bibliography.get_count('other') == 'c'
コード例 #10
0
def test_match():
    parts = {
        'biblio':
        trim("""
            Author. 1999. Title #1. Publisher, City.
            Author. 2000. Title #1. Publisher, City.
            Author. 2000. Title #2. Publisher, City.
            """),
    }
    outline = Outline(parts, default_counters())
    bibliography = Bibliography(parts, outline, id_prefix)
    assert bibliography.match('author') == "Author. 1999."
    assert bibliography.match('author 2000') == "Author. 2000a."
コード例 #11
0
def test_html_simplest():
    parts = {
        'biblio':
        trim("""
            Author. 1999. Title #1. Publisher, City.
            Author. 2000. Title #1. Publisher, City.
            Author. 2000. Title #2. Publisher, City.
            """),
    }
    outline = Outline(parts, default_counters())
    bibliography = Bibliography(parts, outline, id_prefix)
    out = bibliography.html()
    dom = html.fragment_fromstring(out, create_parent='body')[0]
    assert len(dom.cssselect('div.indent-hanging')) == 3
コード例 #12
0
def test_citation():
    parts = {}
    outline = Outline(parts, default_counters())
    bibliography = Bibliography(parts, outline, id_prefix)
    link = bibliography.citation('Author /Title/', 'p.34', '.',
                                 'Author. 2000.', ['1', 'a'], 3)

    assert '<a id="PREFIX_author-2000_1.a_3" ' + \
           'href="#ref_PREFIX_author-2000_1.a_3">' in link
    assert '(Author <em>Title</em>, p.34).' in link  # <-- (?)
    assert '<sup>3</sup>' in link
    assert '</a>' in link

    back_link = '<a id="ref_PREFIX_author-2000_1.a_3" ' + \
                'href="#PREFIX_author-2000_1.a_3">'
    assert back_link in bibliography.citations['Author. 2000.']['1.a'][0]
コード例 #13
0
ファイル: test_citations.py プロジェクト: eukras/article-wiki
def test_constructor():
    parts = {
        'index':
        trim("""
            My Document!

            It contains a reference to ~[Chapman, /Conversation/, p.34].
        """),
        'biblio':
        trim("""
            Chapman, Nigel. 2014. Private conversation.
            Chapman, Nigel. 2014. Private conversation. #2.
        """),
    }

    outline = Outline(parts, default_counters())
    bibliography = Bibliography(parts, outline, id_prefix)
    citations = Citations(bibliography)

    assert bibliography.entries == SortedDict({
        "Chapman, Nigel. 2014a.":
        "Private conversation.",
        "Chapman, Nigel. 2014b.":
        "Private conversation. #2.",
    })

    new_parts = citations.insert(parts)

    assert new_parts == {
        'index':
        trim("""
            My Document!

            It contains a reference to %scitation:1%s
            """) % (DELIMITER, DELIMITER),  # ^ note the missing fullstop
        'biblio':
        trim("""
            Chapman, Nigel. 2014. Private conversation.
            Chapman, Nigel. 2014. Private conversation. #2.
            """),
    }

    end_parts = citations.replace(new_parts)
    assert '<em>Conversation</em>' in end_parts['index']
    assert 'p.34' in end_parts['index']
    dom = html.fragment_fromstring(end_parts['index'], create_parent='body')[0]
    assert len(dom.cssselect('a')) == 1
コード例 #14
0
def test_constructor():
    parts = {
        'index':
        trim("""
            My Document!

            It contains a #[Tag] and a %[Tag].

            ` Part One
        """),
        'part-one':
        trim("""
            Or an #[alias: Tag, subtag]?
        """),
    }

    outline = Outline(parts, default_counters())
    index = Index(outline)
    tags = Tags(index)

    new_parts = tags.insert(parts)

    assert new_parts == {
        'index':
        trim("""
            My Document!

            It contains a %stag:1%s and a %stag:2%s

            ` Part One
            """) % (DELIMITER, DELIMITER, DELIMITER, DELIMITER),
        'part-one':
        trim("""
            Or an %stag:1%s
            """) % (DELIMITER, DELIMITER),
    }

    end_parts = tags.replace(new_parts)

    assert 'Tag<sup>i</sup>' in end_parts['index']
    assert 'Tag.<sup>ii</sup>' in end_parts['index']
コード例 #15
0
ファイル: test_index.py プロジェクト: eukras/article-wiki
def test_html_simplest():
    parts = {
        'index':
        trim("""
            My Document!

            It contains a #[Tag] and a %[Tag].

            ` Part One
        """),
        'part-one':
        trim("""
            Or an #[alias: Tag, subtag]?
        """),
    }
    outline = Outline(parts, default_counters())
    index = Index(outline)
    index.tags = {'tag': {'subtag': {'1.1': ['LINK']}}}
    out = index.html()
    dom = html.fragment_fromstring(out, create_parent='body')[0]
    assert len(dom.cssselect('div.indent-first-line')) == 1
    assert 'LINK' in out
コード例 #16
0
ファイル: wiki.py プロジェクト: eukras/article-wiki
class Wiki(object):
    """
    The simple block-based Article Wiki parser, designed for academic
    writing.
    """

    __version__ = '0.1'

    def __init__(self, settings=None):
        """
        Settings hold all necessary context information.
        """
        assert isinstance(settings, Settings) or settings is None

        self.settings = settings
        self.html = Html(self.settings)

        self.id_prefix = self.settings.get(
            'config:document',
            random_slug('wiki_')  # <-- else
        )

        self.outline = None
        self.cross_references = None
        self.footnotes = None
        self.links = None
        self.index = None
        self.tags = None
        self.bibliography = None
        self.citations = None

    def process(self,
                user_slug,
                doc_slug,
                parts_dict,
                fragment=False,
                preview=False):
        """
        Generate a complete HTML document from a dictionary.

        - To process a directory, supply a dictionary with no 'index' key.
        - To process a document, supply a dictionary with an index entry.
        - To process an index _only_, supply {'index': text}.
        - To process a section, supply {slug: text} when slug != 'index'.
        - To process a fragment, without a headline, supply {slug: text} with
          fragment=true (used in DEMO blocks).
        """

        if len(parts_dict) == 0:
            return ValueError("Document is empty.")

        validate_document(parts_dict, fragment)
        parts, files = clean_document(parts_dict)

        # ------------------------------------------------------
        # Add placeholders for elements not processed by the wiki
        # -------------------------------------------------------

        self.demo = Demo()
        self.backslashes = Backslashes()
        self.entities = Entities()
        self.verbatim = Verbatim()

        # Demo is first: entities(self.backslashes(verbatim(demo)))
        parts = pipe(
            [self.entities, self.backslashes, self.verbatim, self.demo],
            'insert', parts)

        self.settings.extract(parts)

        # @todo:decide on file support.
        self.settings.set_config('files', files)

        # @[Cross Reference]
        self.outline = Outline(parts, default_counters())
        self.cross_references = CrossReferences(parts, self.outline)

        # ^[marker]
        # ^ Reference
        self.footnotes = Footnotes(parts, self.outline, self.id_prefix)
        self.links = Links(self.footnotes, self.id_prefix)

        # #[Topic, sub-topic]
        # self.index = Index(self.outline)
        # self.tags = Tags(self.index)

        # ~[Author 2000, p.34]
        self.bibliography = Bibliography(parts, self.outline, self.id_prefix)
        self.citations = Citations(self.bibliography)

        parts = pipe(
            [
                self.cross_references,  # <-- call 'insert(parts)'
                self.links,
                # self.tags,
                self.citations
            ],
            'insert',
            parts)

        # -------------
        # Generate HTML
        # -------------

        html_parts = {}

        # if len(parts) == 1 or fragment:
        # number = 1
        # for slug in sorted(parts):
        # section = self.make_section(
        # [str(number)], slug, parts[slug], fragment, preview
        # )
        # number = + 1
        # html_parts[slug] = section
        # else:
        if 'index' in parts:
            _, title, _, summary = get_title_data(parts['index'], 'index')
            self.settings.set('TITLE', title)
            self.settings.set('SUMMARY', summary)
            html_parts['index'] = self.make_index(parts['index'])
            html_parts['index'] += web_buttons(user_slug, doc_slug)
            if not self.outline.single_page():
                edit_base_uri = self.settings.get_base_uri('edit')
                html_parts['index'] += self.outline.html(edit_base_uri)
                html_parts['index'] += self.outline.html_spare_parts(
                    parts, edit_base_uri)
        else:
            self.settings.set('TITLE', '')

        for (numbering, slug, _, _, _) in self.outline:
            if slug in parts and slug not in ['index', 'biblio']:
                # print u"MAKE SECTION"
                section = self.make_section(numbering, slug, parts[slug],
                                            fragment, preview)
                html_parts[slug] = section

        # -------------------------------------
        # Replace placeholder content (as HTML)
        # -------------------------------------

        html_parts = pipe(
            [
                self.cross_references,
                # self.tags,
                self.links,
                self.citations
            ],
            'replace',
            html_parts)

        html_parts = pipe(
            [self.demo, self.verbatim, self.backslashes, self.entities],
            'replace', html_parts)

        footnote_parts = self.footnotes.html_parts()
        footnote_parts = pipe([self.verbatim, self.backslashes, self.entities],
                              'replace', footnote_parts)

        html = '<article>\n'
        html += html_parts['index'] if 'index' in html_parts else ''

        for (numbering, slug, _, _, _) in self.outline:
            if slug in parts and slug not in ['index', 'biblio']:
                html += html_parts[slug]
                footnote_html = footnote_parts.get(slug)
                if footnote_html:
                    html += "<footer>"
                    html += "<hr class=\"div-left div-solid\" />"
                    html += footnote_html
                    html += "</footer>"

        html += self.make_footer()

        html += '</article>'

        return html

    def make_footer(self):
        """
        <footer>
            <section id="prefix-footnotes">
            <section id="prefix-bibliography">
            <section id="prefix-index">
        """
        endmatter = [
            _ for _ in [
                # self.footnotes.html(),
                self.bibliography.html(),
                # self.index.html(),
            ] if _.strip() != ""
        ]
        env = Environment(autoescape=True)
        tpl = env.from_string(
            trim("""
                <footer>
                {% if endmatter|length > 0 %}
                    <hr class="div-left div-solid"/>
                    {% for html in endmatter %}
                        {{ html | safe }}
                    {% endfor %}
                {% endif %}
                </footer>
            """))
        return tpl.render(endmatter=endmatter,
                          biblio_link=self.settings.get_base_uri('edit') +
                          '/biblio')

    def dump(self):
        """
        Diagnostics 101
        """
        print(pformat(self.outline))

    def compile_metadata(self, tz_name, user_slug, doc_slug=None):
        """
        Return just the elements we want to store in the document
        metadata cache. Self.metadata is a copy of settings made after
        processing the index part, plus other items like count_words.
        """
        data = {}

        data['user'] = user_slug
        data['title'] = self.settings.get('TITLE', '')
        data['slug'] = doc_slug if doc_slug else slugify(data['title'])
        data['summary'] = self.settings.get('SUMMARY', '')
        data['license'] = self.settings.get('LICENSE', '')
        data['publish'] = self.settings.get('PUBLISH', 'YES')  # <-- Default

        data['author'] = self.settings.get('AUTHOR', '')
        data['email'] = self.settings.get('EMAIL', '')
        data['facebook'] = self.settings.get('FACEBOOK', '')
        data['twitter'] = self.settings.get('TWITTER', '')

        default = date.today().strftime("%d %b %Y")
        data['date'] = self.settings.get('DATE', default)
        utc = parse_date(data['date'], tz_name)
        data['published_time'] = format_date(utc, tz_name, DATE_FORMAT_ISO8601)

        data['todo'] = self.settings.get('TODO', '')
        data['word_count'] = self.outline.total_word_count()

        return data

    def make_index(self, text):
        """
        Front matter preceding index text.
        Multi-author blocks are disabled for now; v.0.1.0 is SINGLE_USER.
        """
        env = Environment(autoescape=True)
        tpl = env.from_string(
            trim("""
        <header>

            {% if title_html != "" %}
            <hgroup>
                <h1 class="balance-text">{{ title_html|safe }}</h1>
            {% if summary != "" %}
                <h2 class="balance-text">{{ summary_html|safe }}</h2>
            {% endif %}
            </hgroup>
            {% endif %}

            {% if author != "" or email != "" %}
            <div class="author-list">
                <address>
                {% if author != "" %}
                    <div>{{ author }}</div>
                {% endif %}
                {% if email != "" %}
                    <div><a href="mailto:{{ email }}">{{ email }}</a></div>
                {% endif %}
                </address>
            </div>
            {% endif %}

            {% if facebook != "" or twitter != "" %}
            <p class="space space-between">
                {% if facebook != "" %}
                <a href="https://facebook.com/{{ facebook }}" target="_blank">
                    FB: {{ facebook }}
                </a>
                {% endif %}
                {% if twitter != "" %}
                <a href="https://twitter.com/{{ twitter }}" target="_blank">
                    TW: {{ twitter }}
                </a>
                {% endif %}
            </p>
            {% endif %}

            {% if date %}
            <p class="space" rel="date">
                {% if parsed_date != None %}
                <time pubdate datetime="{{ parsed_date }}">{{ date }}</time>
                {% else %}
                {{ date }}
                {% endif %}
            </p>
            {% endif %}

        </header>
        <section class="depth-0">
            {{ content_html|safe }}
        </section>
        """))

        inline = Inline()
        content, _ = split_bibliography(text)
        blocks = BlockList(content)
        title, summary = blocks.pop_titles()
        content_html = blocks.html(['0'],
                                   'index',
                                   self.settings,
                                   fragment=True)

        date_string = inline.process(self.settings.get('DATE', ''))
        try:
            dt = parse(date_string)
            date_yyyymmdd = dt.date().isoformat()
        except ValueError:
            date_yyyymmdd = None

        # author = self.split_author()
        return tpl.render(
            title_html=inline.process(title),
            summary_html=inline.process(summary),
            author=self.settings.get('AUTHOR', ''),
            email=self.settings.get('EMAIL', ''),
            facebook=self.settings.get('FACEBOOK', ''),
            twitter=self.settings.get('TWITTER', ''),
            date=date_string,
            parsed_date=date_yyyymmdd,
            edit_link=self.settings.get_base_uri('edit') + '/index',
            content_html=content_html,
        )

    # Move to geometry?
    def split_author(self, author):
        """
        UNUSED FOR NOW. v.0.1.0 is SINGLE_USER.

        Split into authors and lines.

        $ AUTHOR = Author / Affiliation
                 + Author2 / Affiliation2

        No author should return an empty list.
        """
        inline = Inline()
        if author.strip() == '':
            return []
        else:
            return [[
                inline.process(line.strip()) for line in block.split(' / ')
            ] for block in author.split(' + ')]

    # Move to geometry?
    def author_cols(self, author):
        """
        Return twelve-column-grid spans based on numbers.

        @todo: Fix the hackish; better solution would find the numbers for each
        row, e.g. 5 would give 3 + 2.

        @todo: Should be solved with Flexbox now.
        """
        _ = len(author)  # list
        columns = {1: 12, 2: 6, 3: 4, 4: 6}
        return columns[_] if _ in columns else 4

    def make_section(self,
                     numbering,
                     slug,
                     text,
                     fragment=False,
                     preview=False):
        """
        Wrap section HTML in titles and nav.
        A fragment has no heading. A preview has a heading but no context,so no
        numbering.
        """
        env = Environment(autoescape=True)
        tpl = env.from_string(
            trim("""
        <section class="body depth-{{ depth }}">
        {% if not fragment %}
        <nav class="button-list button-list-edge no-preview no-print">
            <a class="button" href="{{ edit_link }}">
                <i class="fa fa-pencil"></i> Edit
            </a>
        </nav>
        {% endif %}
        {{ content_html|safe }}
        </section>
        """))

        content, _ = split_bibliography(text)
        blocks = BlockList(content)
        content_html = blocks.html(numbering, slug, self.settings, fragment,
                                   preview)

        return tpl.render(depth=len(numbering),
                          edit_link=self.settings.get_base_uri('edit') + '/' +
                          slug,
                          content_html=content_html,
                          fragment=fragment)
コード例 #17
0
ファイル: wiki.py プロジェクト: eukras/article-wiki
    def process(self,
                user_slug,
                doc_slug,
                parts_dict,
                fragment=False,
                preview=False):
        """
        Generate a complete HTML document from a dictionary.

        - To process a directory, supply a dictionary with no 'index' key.
        - To process a document, supply a dictionary with an index entry.
        - To process an index _only_, supply {'index': text}.
        - To process a section, supply {slug: text} when slug != 'index'.
        - To process a fragment, without a headline, supply {slug: text} with
          fragment=true (used in DEMO blocks).
        """

        if len(parts_dict) == 0:
            return ValueError("Document is empty.")

        validate_document(parts_dict, fragment)
        parts, files = clean_document(parts_dict)

        # ------------------------------------------------------
        # Add placeholders for elements not processed by the wiki
        # -------------------------------------------------------

        self.demo = Demo()
        self.backslashes = Backslashes()
        self.entities = Entities()
        self.verbatim = Verbatim()

        # Demo is first: entities(self.backslashes(verbatim(demo)))
        parts = pipe(
            [self.entities, self.backslashes, self.verbatim, self.demo],
            'insert', parts)

        self.settings.extract(parts)

        # @todo:decide on file support.
        self.settings.set_config('files', files)

        # @[Cross Reference]
        self.outline = Outline(parts, default_counters())
        self.cross_references = CrossReferences(parts, self.outline)

        # ^[marker]
        # ^ Reference
        self.footnotes = Footnotes(parts, self.outline, self.id_prefix)
        self.links = Links(self.footnotes, self.id_prefix)

        # #[Topic, sub-topic]
        # self.index = Index(self.outline)
        # self.tags = Tags(self.index)

        # ~[Author 2000, p.34]
        self.bibliography = Bibliography(parts, self.outline, self.id_prefix)
        self.citations = Citations(self.bibliography)

        parts = pipe(
            [
                self.cross_references,  # <-- call 'insert(parts)'
                self.links,
                # self.tags,
                self.citations
            ],
            'insert',
            parts)

        # -------------
        # Generate HTML
        # -------------

        html_parts = {}

        # if len(parts) == 1 or fragment:
        # number = 1
        # for slug in sorted(parts):
        # section = self.make_section(
        # [str(number)], slug, parts[slug], fragment, preview
        # )
        # number = + 1
        # html_parts[slug] = section
        # else:
        if 'index' in parts:
            _, title, _, summary = get_title_data(parts['index'], 'index')
            self.settings.set('TITLE', title)
            self.settings.set('SUMMARY', summary)
            html_parts['index'] = self.make_index(parts['index'])
            html_parts['index'] += web_buttons(user_slug, doc_slug)
            if not self.outline.single_page():
                edit_base_uri = self.settings.get_base_uri('edit')
                html_parts['index'] += self.outline.html(edit_base_uri)
                html_parts['index'] += self.outline.html_spare_parts(
                    parts, edit_base_uri)
        else:
            self.settings.set('TITLE', '')

        for (numbering, slug, _, _, _) in self.outline:
            if slug in parts and slug not in ['index', 'biblio']:
                # print u"MAKE SECTION"
                section = self.make_section(numbering, slug, parts[slug],
                                            fragment, preview)
                html_parts[slug] = section

        # -------------------------------------
        # Replace placeholder content (as HTML)
        # -------------------------------------

        html_parts = pipe(
            [
                self.cross_references,
                # self.tags,
                self.links,
                self.citations
            ],
            'replace',
            html_parts)

        html_parts = pipe(
            [self.demo, self.verbatim, self.backslashes, self.entities],
            'replace', html_parts)

        footnote_parts = self.footnotes.html_parts()
        footnote_parts = pipe([self.verbatim, self.backslashes, self.entities],
                              'replace', footnote_parts)

        html = '<article>\n'
        html += html_parts['index'] if 'index' in html_parts else ''

        for (numbering, slug, _, _, _) in self.outline:
            if slug in parts and slug not in ['index', 'biblio']:
                html += html_parts[slug]
                footnote_html = footnote_parts.get(slug)
                if footnote_html:
                    html += "<footer>"
                    html += "<hr class=\"div-left div-solid\" />"
                    html += footnote_html
                    html += "</footer>"

        html += self.make_footer()

        html += '</article>'

        return html