Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions confluence-mdx/bin/reverse_sync/roundtrip_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,26 +106,50 @@ def _normalize_empty_bold(text: str) -> str:


def _normalize_empty_list_items(text: str) -> str:
"""내용 없는 번호 리스트 항목(예: `` 12.``)을 빈 줄로 치환한다.
"""내용 없는 번호 리스트 항목(예: `` 12.``)을 줄째 제거한다.

Forward converter가 XHTML의 텍스트 없는 ``<li>`` (이미지만 포함)를
번호만 있는 항목(``12.``)으로 변환한다. 이 항목은 시각적으로 무의미하므로
improved.mdx에서 제거하더라도 XHTML 패치로 ``<li>`` 구조를 삭제할 수 없다.
양쪽을 빈 줄로 정규화하여 이 차이를 무시한다.
줄 전체(newline 포함)를 제거하여 이 차이를 무시한다.
"""
return re.sub(r'^([ \t]+)\d+\.\s*$', '', text, flags=re.MULTILINE)
return re.sub(r'^[ \t]+\d+\.\s*\n', '', text, flags=re.MULTILINE)


def _normalize_consecutive_blank_lines(text: str) -> str:
"""연속 빈 줄(3개 이상의 개행)을 단일 빈 줄(2개 개행)로 정규화한다.

Forward converter가 블록 요소 사이에 추가 빈 줄을 삽입하거나,
_normalize_empty_list_items가 줄을 제거한 뒤 남은 연속 빈 줄을
정규화한다. MDX에서 빈 줄 수는 시각적으로 동일하다.
"""
return re.sub(r'\n{3,}', '\n\n', text)


def _normalize_blank_line_after_br(text: str) -> str:
"""<br/> 로 끝나는 줄 뒤의 빈 줄을 제거한다.

<br/> 자체가 줄바꿈을 생성하므로, 뒤따르는 빈 줄은 시각적으로
무의미하다. improved.mdx에서 빈 리스트 번호(12.)를 제거한 뒤
남은 빈 줄과 FC가 빈 줄 없이 출력하는 차이를 정규화한다.
"""
return re.sub(r'(<br\s*/>\n)\n+', r'\1', text)


def _apply_minimal_normalizations(text: str) -> str:
"""항상 적용하는 최소 정규화 (strict/lenient 모드 공통).

forward converter의 체계적 출력 특성에 의한 차이만 처리한다:
- 인라인 이중 공백 → 단일 공백 (_normalize_consecutive_spaces_in_text)
- <br/> 앞 공백 제거 (_normalize_br_space)
- 링크 텍스트 앞뒤 공백 제거 (_normalize_link_text_spacing)
- 빈 bold 마커(****) 정규화 (_normalize_empty_bold)
- 내용 없는 번호 리스트 항목 제거 (_normalize_empty_list_items)
- 문장 경계 줄바꿈 결합 (_normalize_sentence_breaks)
- 인라인 이중 공백 → 단일 공백
- <br/> 앞 공백 제거
- 링크 텍스트 앞뒤 공백 제거
- 빈 bold 마커(****) 정규화
- 내용 없는 번호 리스트 항목 제거
- 연속 빈 줄 정규화
- 테이블 셀 패딩 정규화
- 문장 경계 줄바꿈 결합
- 첫 번째 h1 heading 제거
- trailing 빈 줄 정규화

lenient 모드에서는 이 정규화 이후 _apply_normalizations가 추가로 적용된다.
"""
Expand All @@ -134,6 +158,8 @@ def _apply_minimal_normalizations(text: str) -> str:
text = _normalize_link_text_spacing(text)
text = _normalize_empty_bold(text)
text = _normalize_empty_list_items(text)
text = _normalize_consecutive_blank_lines(text)
text = _normalize_blank_line_after_br(text)
text = _normalize_table_cell_padding(text)
text = _normalize_sentence_breaks(text)
text = _strip_first_heading(text)
Expand Down
115 changes: 114 additions & 1 deletion confluence-mdx/tests/test_reverse_sync_roundtrip_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
_normalize_link_text_spacing,
_normalize_sentence_breaks,
_normalize_table_cell_padding,
_normalize_empty_list_items,
_normalize_consecutive_blank_lines,
_normalize_blank_line_after_br,
)


Expand Down Expand Up @@ -190,6 +193,78 @@ def test_multiple_links_all_normalized(self):
assert result == "* [**A**](a) and [**B**](b)"


# --- _normalize_empty_list_items 단위 테스트 ---


class TestNormalizeEmptyListItems:
def test_removes_number_only_line(self):
"""번호만 있는 리스트 항목(예: ' 12.')을 줄째 제거한다.

재현: integrating-with-email.mdx — FC가 이미지만 포함된 <li>를
' 12.'로 변환하지만, improved.mdx에서는 제거됨.
"""
text = " 11. item\n 12.\n 13. next\n"
assert _normalize_empty_list_items(text) == " 11. item\n 13. next\n"

def test_preserves_item_with_content(self):
"""내용이 있는 리스트 항목은 제거하지 않는다."""
text = " 1. first item\n 2. second item\n"
assert _normalize_empty_list_items(text) == text

def test_removes_with_tab_indent(self):
"""탭 들여쓰기인 경우에도 제거한다."""
text = "\t5.\n\t6. content\n"
assert _normalize_empty_list_items(text) == "\t6. content\n"


# --- _normalize_consecutive_blank_lines 단위 테스트 ---


class TestNormalizeConsecutiveBlankLines:
def test_triple_newline_collapsed(self):
"""3개 이상 연속 개행을 2개로 정규화한다.

재현: identity-providers.mdx, user-management.mdx — FC가 블록 사이에
추가 빈 줄을 삽입하거나, empty list item 제거 후 연속 빈 줄이 남음.
"""
text = "para1\n\n\npara2\n"
assert _normalize_consecutive_blank_lines(text) == "para1\n\npara2\n"

def test_double_newline_unchanged(self):
"""2개 개행(단일 빈 줄)은 변경하지 않는다."""
text = "para1\n\npara2\n"
assert _normalize_consecutive_blank_lines(text) == text

def test_quadruple_newline_collapsed(self):
"""4개 이상 연속 개행도 2개로 정규화한다."""
text = "para1\n\n\n\npara2\n"
assert _normalize_consecutive_blank_lines(text) == "para1\n\npara2\n"


# --- _normalize_blank_line_after_br 단위 테스트 ---


class TestNormalizeBlankLineAfterBr:
def test_removes_blank_after_br(self):
"""<br/> 뒤의 빈 줄을 제거한다.

재현: integrating-with-email.mdx — improved.mdx에서 빈 리스트 번호
제거 후 <br/> 바로 뒤에 빈 줄이 남음. FC는 빈 줄 없이 출력.
"""
text = "text<br/>\n\nnext\n"
assert _normalize_blank_line_after_br(text) == "text<br/>\nnext\n"

def test_preserves_br_without_blank(self):
"""<br/> 뒤에 빈 줄이 없으면 변경하지 않는다."""
text = "text<br/>\nnext\n"
assert _normalize_blank_line_after_br(text) == text

def test_handles_self_closing_variants(self):
"""<br /> 형태도 처리한다."""
text = "text<br />\n\nnext\n"
assert _normalize_blank_line_after_br(text) == "text<br />\nnext\n"


# --- _normalize_table_cell_padding 단위 테스트 ---


Expand Down Expand Up @@ -238,7 +313,7 @@ def test_minimal_norm_double_space_passes():


def test_minimal_norm_br_space_passes():
"""<br/> 앞 공백 차이는 strict 모드에서도 정규화된다."""
"""<br/> 앞 공백 차이는 최소 정규화로 통과한다."""
result = verify_roundtrip(
expected_mdx="item <br/>next\n",
actual_mdx="item<br/>next\n",
Expand Down Expand Up @@ -274,6 +349,44 @@ def test_minimal_norm_link_spacing_multiple_items():
assert result.passed is True


def test_minimal_norm_empty_list_item_passes():
"""빈 리스트 번호 차이는 최소 정규화로 통과한다.

재현: integrating-with-email.mdx — improved.mdx에서 '12.' 제거,
FC verify.mdx에서 '12.' 출력. 정규화로 양쪽 모두 해당 줄 제거.
"""
result = verify_roundtrip(
expected_mdx=" 11. item\n 13. next\n",
actual_mdx=" 11. item\n 12.\n 13. next\n",
)
assert result.passed is True


def test_minimal_norm_consecutive_blank_lines_passes():
"""연속 빈 줄 차이는 최소 정규화로 통과한다.

재현: identity-providers.mdx — FC가 블록 사이에 빈 줄을 추가 삽입.
"""
result = verify_roundtrip(
expected_mdx="para1\n\npara2\n",
actual_mdx="para1\n\n\npara2\n",
)
assert result.passed is True


def test_minimal_norm_blank_line_after_br_passes():
"""<br/> 뒤 빈 줄 차이는 최소 정규화로 통과한다.

재현: integrating-with-email.mdx — empty list item 제거 후
<br/> 뒤에 빈 줄이 남는 차이.
"""
result = verify_roundtrip(
expected_mdx="text<br/>\n\nnext\n",
actual_mdx="text<br/>\nnext\n",
)
assert result.passed is True


def test_strict_mode_still_fails_on_trailing_ws():
"""strict 모드: trailing whitespace 차이는 여전히 실패한다 (최소 정규화 대상 아님)."""
result = verify_roundtrip(
Expand Down
Loading