Skip to content
Open
20 changes: 10 additions & 10 deletions md2cf/confluence_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def append(self, child):
self.children.append(child)


class ConfluenceRenderer(mistune.Renderer):
class ConfluenceRenderer(mistune.HTMLRenderer):
def __init__(
self,
strip_header=False,
Expand All @@ -81,14 +81,14 @@ def reinit(self):
self.relative_links = list()
self.title = None

def header(self, text, level, raw=None):
def heading(self, text, level, raw=None):
if self.title is None and level == 1:
self.title = text
# Don't duplicate page title as a header
if self.strip_header:
return ""

return super(ConfluenceRenderer, self).header(text, level, raw=raw)
return super(ConfluenceRenderer, self).heading(text, level, raw=raw)

def structured_macro(self, name):
return ConfluenceTag("structured-macro", attrib={"name": name})
Expand All @@ -103,8 +103,8 @@ def plain_text_body(self, text):
body_tag.text = text
return body_tag

def link(self, link, title, text):
parsed_link = urlparse(link)
def link(self, text, url, title=None):
parsed_link = urlparse(url)
if (
self.enable_relative_links
and (not parsed_link.scheme and not parsed_link.netloc)
Expand All @@ -119,18 +119,18 @@ def link(self, link, title, text):
path=unquote(parsed_link.path),
replacement=replacement_link,
fragment=parsed_link.fragment,
original=link,
escaped_original=mistune.escape_link(link),
original=url,
escaped_original=mistune.escape_url(url),
)
)
link = replacement_link
return super(ConfluenceRenderer, self).link(link, title, text)
url = replacement_link
return super(ConfluenceRenderer, self).link(text, url, title)

def text(self, text):
if self.remove_text_newlines:
text = text.replace("\n", " ")

return super().text(text)
return text

def block_code(self, code, lang=None):
root_element = self.structured_macro("code")
Expand Down
5 changes: 4 additions & 1 deletion md2cf/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,15 @@ def parse_page(
strip_header: bool = False,
remove_text_newlines: bool = False,
enable_relative_links: bool = False,
escape: bool = False,
allow_harmful_protocols: bool = False,
) -> Page:
renderer = ConfluenceRenderer(
use_xhtml=True,
strip_header=strip_header,
remove_text_newlines=remove_text_newlines,
enable_relative_links=enable_relative_links,
escape=escape,
allow_harmful_protocols=allow_harmful_protocols,
)
confluence_mistune = mistune.Markdown(renderer=renderer)
confluence_content = confluence_mistune("".join(markdown_lines))
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
install_requires=[
"rich-argparse==1.0.0",
"rich==13.0.1",
"mistune==0.8.4",
"mistune==3.0.1",
"chardet==5.1.0",
"requests==2.31.0",
"PyYAML==6.0.1",
Expand Down
61 changes: 32 additions & 29 deletions test_package/functional/result.xml
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
<h1>Markdown: Syntax</h1>
<ul>
<li><a href="#markdown-syntax">Markdown: Syntax</a><ul>
<li><a href="#overview">Overview</a><ul>
<li><a href="#philosophy">Philosophy</a></li>
<li><a href="#html">Inline HTML</a></li>
<li><a href="#autoescape">Automatic Escaping for Special Characters</a></li>
</ul>
</li>
<li><a href="#block">Block Elements</a><ul>
<li><a href="#p">Paragraphs and Line Breaks</a></li>
<li><a href="#header">Headers</a></li>
<li><a href="#blockquote">Blockquotes</a></li>
<li><a href="#list">Lists</a></li>
<li><a href="#precode">Code Blocks</a></li>
<li><a href="#hr">Horizontal Rules</a></li>
<li><a href="#block-elements">Block Elements</a><ul>
<li><a href="#paragraphs-and-line-breaks">Paragraphs and Line Breaks</a></li>
<li><a href="#headers">Headers</a></li>
<li><a href="#blockquotes">Blockquotes</a></li>
<li><a href="#lists">Lists</a></li>
<li><a href="#code-blocks">Code Blocks</a></li>
</ul>
</li>
<li><a href="#span">Span Elements</a><ul>
<li><a href="#link">Links</a></li>
<li><a href="#em">Emphasis</a></li>
<li><a href="#span-elements">Span Elements</a><ul>
<li><a href="#links">Links</a></li>
<li><a href="#emphasis">Emphasis</a></li>
<li><a href="#code">Code</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>Note:</strong> This document is itself written using Markdown; you
can <a href="/projects/markdown/syntax.text">see the source for it by adding '.text' to the URL</a>.</p>
<hr />
Expand Down Expand Up @@ -60,30 +60,37 @@ determines the header level.)</p>
familiar with quoting passages of text in an email message, then you
know how to create a blockquote in Markdown. It looks best if you hard
wrap the text and put a <code>&gt;</code> before every line:</p>
<blockquote><p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
<blockquote>
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.</p>
</blockquote>
<p>Markdown allows you to be lazy and only put the <code>&gt;</code> before the first
line of a hard-wrapped paragraph:</p>
<blockquote><p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
<blockquote>
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
</blockquote>
<blockquote>
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.</p>
</blockquote>
<p>Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
adding additional levels of <code>&gt;</code>:</p>
<blockquote><p>This is the first level of quoting.</p>
<blockquote><p>This is nested blockquote.</p>
<blockquote>
<p>This is the first level of quoting.</p>
<blockquote>
<p>This is nested blockquote.</p>
</blockquote>
<p>Back to the first level.</p>
</blockquote>
<p>Blockquotes can contain other Markdown elements, including headers, lists,
and code blocks:</p>
<blockquote><h2>This is a header.</h2>
<blockquote>
<h2>This is a header.</h2>
<ol>
<li>This is the first list item.</li>
<li>This is the second list item.</li>
Expand Down Expand Up @@ -133,7 +140,7 @@ Markdown produces from the above list is:</p>
<li>Parish</li>
</ol>
<p>or even:</p>
<ol>
<ol start="3">
<li>Bird</li>
<li>McHale</li>
<li>Parish</li>
Expand Down Expand Up @@ -188,7 +195,8 @@ sit amet, consectetuer adipiscing elit.</p>
delimiters need to be indented:</p>
<ul>
<li><p>A list item with a blockquote:</p>
<blockquote><p>This is a blockquote
<blockquote>
<p>This is a blockquote
inside a list item.</p>
</blockquote>
</li>
Expand All @@ -211,17 +219,13 @@ in both <code>&lt;pre&gt;</code> and <code>&lt;code&gt;</code> tags.</p>
block by at least 4 spaces or 1 tab.</p>
<p>This is a normal paragraph:</p>
<ac:structured-macro ac:name="code"><ac:parameter ac:name="linenumbers">true</ac:parameter>
<ac:plain-text-body><![CDATA[This is a code block.

]]></ac:plain-text-body>
<ac:plain-text-body><![CDATA[This is a code block.]]></ac:plain-text-body>
</ac:structured-macro>
<p>Here is an example of AppleScript:</p>
<ac:structured-macro ac:name="code"><ac:parameter ac:name="linenumbers">true</ac:parameter>
<ac:plain-text-body><![CDATA[tell application "Foo"
beep
end tell

]]></ac:plain-text-body>
end tell]]></ac:plain-text-body>
</ac:structured-macro>
<p>A code block continues until it reaches a line that is not indented
(or the end of the article).</p>
Expand All @@ -233,17 +237,16 @@ ampersands and angle brackets. For example, this:</p>
<ac:structured-macro ac:name="code"><ac:parameter ac:name="linenumbers">true</ac:parameter>
<ac:plain-text-body><![CDATA[<div class="footer">
&copy; 2004 Foo Corporation
</div>

]]></ac:plain-text-body>
</div>]]></ac:plain-text-body>
</ac:structured-macro>
<p>Regular Markdown syntax is not processed within code blocks. E.g.,
asterisks are just literal asterisks within a code block. This means
it's also easy to use Markdown to write about Markdown's own syntax.</p>
<ac:structured-macro ac:name="code"><ac:parameter ac:name="linenumbers">true</ac:parameter>
<ac:plain-text-body><![CDATA[tell application "Foo"
beep
end tell]]></ac:plain-text-body>
end tell
]]></ac:plain-text-body>
</ac:structured-macro>
<h2>Span Elements</h2>
<h3>Links</h3>
Expand Down
28 changes: 13 additions & 15 deletions test_package/functional/test.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# Markdown: Syntax

* [Overview](#overview)
* [Philosophy](#philosophy)
* [Inline HTML](#html)
* [Automatic Escaping for Special Characters](#autoescape)
* [Block Elements](#block)
* [Paragraphs and Line Breaks](#p)
* [Headers](#header)
* [Blockquotes](#blockquote)
* [Lists](#list)
* [Code Blocks](#precode)
* [Horizontal Rules](#hr)
* [Span Elements](#span)
* [Links](#link)
* [Emphasis](#em)
* [Code](#code)
- [Markdown: Syntax](#markdown-syntax)
- [Overview](#overview)
- [Philosophy](#philosophy)
- [Block Elements](#block-elements)
- [Paragraphs and Line Breaks](#paragraphs-and-line-breaks)
- [Headers](#headers)
- [Blockquotes](#blockquotes)
- [Lists](#lists)
- [Code Blocks](#code-blocks)
- [Span Elements](#span-elements)
- [Links](#links)
- [Emphasis](#emphasis)
- [Code](#code)


**Note:** This document is itself written using Markdown; you
Expand Down
28 changes: 14 additions & 14 deletions test_package/unit/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_tag_render_with_child_and_text():

def test_renderer_reinit():
renderer = ConfluenceRenderer()
renderer.header("this is a title", 1)
renderer.heading("this is a title", 1)
assert renderer.title is not None

renderer.reinit()
Expand Down Expand Up @@ -157,7 +157,7 @@ def test_renderer_header_sets_title():
test_header = "this is a header"
renderer = ConfluenceRenderer()

renderer.header(test_header, 1)
renderer.heading(test_header, 1)

assert renderer.title == test_header

Expand All @@ -166,7 +166,7 @@ def test_renderer_strips_header():
test_header = "this is a header"
renderer = ConfluenceRenderer(strip_header=True)

result = renderer.header(test_header, 1)
result = renderer.heading(test_header, 1)

assert result == ""

Expand All @@ -175,7 +175,7 @@ def test_renderer_header_lower_level_does_not_set_title():
test_header = "this is a header"
renderer = ConfluenceRenderer()

renderer.header(test_header, 2)
renderer.heading(test_header, 2)

assert renderer.title is None

Expand All @@ -185,8 +185,8 @@ def test_renderer_header_later_level_sets_title():
test_header = "this is a header"
renderer = ConfluenceRenderer()

renderer.header(test_lower_header, 2)
renderer.header(test_header, 1)
renderer.heading(test_lower_header, 2)
renderer.heading(test_header, 1)

assert renderer.title is test_header

Expand All @@ -196,8 +196,8 @@ def test_renderer_header_only_sets_first_title():
test_second_header = "this is another header"
renderer = ConfluenceRenderer()

renderer.header(test_header, 1)
renderer.header(test_second_header, 1)
renderer.heading(test_header, 1)
renderer.heading(test_second_header, 1)

assert renderer.title is test_header

Expand Down Expand Up @@ -274,7 +274,7 @@ def test_renderer_normal_link(relative_links):
renderer = ConfluenceRenderer(enable_relative_links=relative_links)

assert (
renderer.link(link="https://example.com", text="example link", title=None)
renderer.link(url="https://example.com", text="example link", title=None)
== '<a href="https://example.com">example link</a>'
)

Expand All @@ -284,7 +284,7 @@ def test_renderer_local_header_link(relative_links):
renderer = ConfluenceRenderer(enable_relative_links=relative_links)

assert (
renderer.link(link="#header-name", text="example link", title=None)
renderer.link(url="#header-name", text="example link", title=None)
== '<a href="#header-name">example link</a>'
)

Expand All @@ -296,7 +296,7 @@ def test_renderer_relative_link_enabled():
r"<a href=\"md2cf-internal-link-([-a-z0-9]+)\">relative link</a>"
)
temporary_link = renderer.link(
link="document/../path/page.md", text="relative link", title=None
url="document/../path/page.md", text="relative link", title=None
)
assert relative_link_regex.match(temporary_link)
assert len(renderer.relative_links) == 1
Expand All @@ -319,7 +319,7 @@ def test_renderer_relative_link_with_fragment_enabled():
r"<a href=\"md2cf-internal-link-([-a-z0-9]+)\">relative link</a>"
)
temporary_link = renderer.link(
link="document/../path/page.md#header-name", text="relative link", title=None
url="document/../path/page.md#header-name", text="relative link", title=None
)
assert relative_link_regex.match(temporary_link)
assert len(renderer.relative_links) == 1
Expand All @@ -339,7 +339,7 @@ def test_renderer_relative_link_disabled():
renderer = ConfluenceRenderer(enable_relative_links=False)

assert (
renderer.link(link="document/../path/page.md", text="relative link", title=None)
renderer.link(url="document/../path/page.md", text="relative link", title=None)
== '<a href="document/../path/page.md">relative link</a>'
)
assert renderer.relative_links == []
Expand All @@ -350,7 +350,7 @@ def test_renderer_relative_link_with_fragment_disabled():

assert (
renderer.link(
link="document/../path/page.md#header-name",
url="document/../path/page.md#header-name",
text="relative link",
title=None,
)
Expand Down