Repository: iamjackg/md2cf

Issue:
Link parsing fails for document references
Hi,

I just noticed that it's not possible to use `md2cf` anymore with document references like table of contents, since the relative path detector detects the paths that start with `#`

```md
[Frequently Asked Questions](#frequently-asked-questions-osep)
```

Having something like this in the document results in 

```
Page test.md has a relative link to , which is not in the list of pages to be uploaded.  
```

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3614043..a26d632 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+### Fixed
+- Local section links are no longer rendered as broken relative links (e.g. `[this section](#section-header)`)
+
 ## 2.2.0 - 2023-07-08
 ### Added
 - Relative links support section headers (@jimstein3d)
diff --git a/md2cf/confluence_renderer.py b/md2cf/confluence_renderer.py
index 5829fa2..74b3496 100644
--- a/md2cf/confluence_renderer.py
+++ b/md2cf/confluence_renderer.py
@@ -105,8 +105,10 @@ class ConfluenceRenderer(mistune.Renderer):
 
     def link(self, link, title, text):
         parsed_link = urlparse(link)
-        if self.enable_relative_links and (
-            not parsed_link.scheme and not parsed_link.netloc
+        if (
+            self.enable_relative_links
+            and (not parsed_link.scheme and not parsed_link.netloc)
+            and parsed_link.path
         ):
             # relative link
             replacement_link = f"md2cf-internal-link-{uuid.uuid4()}"


Test patch:
diff --git a/test_package/unit/test_renderer.py b/test_package/unit/test_renderer.py
index 40c50fc..1ab9596 100644
--- a/test_package/unit/test_renderer.py
+++ b/test_package/unit/test_renderer.py
@@ -1,3 +1,7 @@
+import re
+
+import pytest
+
 from md2cf.confluence_renderer import ConfluenceRenderer, ConfluenceTag
 
 
@@ -263,3 +267,93 @@ def test_renderer_remove_text_newlines():
     renderer = ConfluenceRenderer(remove_text_newlines=True)
 
     assert renderer.text(test_text) == test_stripped_text
+
+
+@pytest.mark.parametrize("relative_links", [False, True])
+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)
+        == '<a href="https://example.com">example link</a>'
+    )
+
+
+@pytest.mark.parametrize("relative_links", [False, True])
+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)
+        == '<a href="#header-name">example link</a>'
+    )
+
+
+def test_renderer_relative_link_enabled():
+    renderer = ConfluenceRenderer(enable_relative_links=True)
+
+    relative_link_regex = re.compile(
+        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
+    )
+    assert relative_link_regex.match(temporary_link)
+    assert len(renderer.relative_links) == 1
+    relative_link = renderer.relative_links[0]
+
+    assert relative_link.path == "document/../path/page.md"
+    assert (
+        relative_link.replacement == f"md2cf-internal-link-"
+        f"{relative_link_regex.match(temporary_link).groups(1)[0]}"
+    )
+    assert relative_link.fragment == ""
+    assert relative_link.original == "document/../path/page.md"
+    assert relative_link.escaped_original == "document/../path/page.md"
+
+
+def test_renderer_relative_link_with_fragment_enabled():
+    renderer = ConfluenceRenderer(enable_relative_links=True)
+
+    relative_link_regex = re.compile(
+        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
+    )
+    assert relative_link_regex.match(temporary_link)
+    assert len(renderer.relative_links) == 1
+    relative_link = renderer.relative_links[0]
+
+    assert relative_link.path == "document/../path/page.md"
+    assert (
+        relative_link.replacement == f"md2cf-internal-link-"
+        f"{relative_link_regex.match(temporary_link).groups(1)[0]}"
+    )
+    assert relative_link.fragment == "header-name"
+    assert relative_link.original == "document/../path/page.md#header-name"
+    assert relative_link.escaped_original == "document/../path/page.md#header-name"
+
+
+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)
+        == '<a href="document/../path/page.md">relative link</a>'
+    )
+    assert renderer.relative_links == []
+
+
+def test_renderer_relative_link_with_fragment_disabled():
+    renderer = ConfluenceRenderer(enable_relative_links=False)
+
+    assert (
+        renderer.link(
+            link="document/../path/page.md#header-name",
+            text="relative link",
+            title=None,
+        )
+        == '<a href="document/../path/page.md#header-name">relative link</a>'
+    )
+    assert renderer.relative_links == []