Skip to content
Merged
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
10 changes: 8 additions & 2 deletions confluence-mdx/bin/reverse_sync/xhtml_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,17 +617,23 @@ def _apply_text_changes(element: Tag, old_text: str, new_text: str):
exclude_insert_at_start=exclude_at_start,
)

# 직전 노드 범위와 현재 노드 범위 사이의 gap이 diff로 삭제된 경우,
# 직전 노드 범위와 현재 노드 범위 사이의 gap이 diff로 삭제/축소된 경우,
# leading whitespace를 제거한다.
# 예: <strong>IDENTIFIER</strong> 조사 → IDENTIFIER조사 (공백 교정)
# 예1: <strong>IDENTIFIER</strong> 조사 → IDENTIFIER조사 (공백 삭제)
# 예2: <p> Admin → <p>Admin (공백 축소: gap 2→1이면 leading 제거)
if leading and i > 0:
prev_end = node_ranges[i - 1][1]
if prev_end < node_start:
gap_old = old_stripped[prev_end:node_start]
gap_new = _map_text_range(
old_stripped, new_stripped, opcodes, prev_end, node_start
)
if not gap_new:
leading = ''
elif (gap_new.isspace() and gap_old.isspace()
and len(gap_new) < len(gap_old)
and len(gap_new) <= len(leading)):
leading = ''

if trailing_in_range:
# diff가 trailing whitespace를 처리했으므로 별도 보존 불필요
Expand Down
63 changes: 63 additions & 0 deletions confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,66 @@ def test_only_one_of_duplicate_shortcodes_is_replaced(self):

assert len(soup.find_all('ac:emoticon')) == 1
assert 'A 확인' in result


class TestGapWhitespaceReduction:
"""텍스트 노드 사이 gap 공백이 축소될 때 leading whitespace 처리 테스트."""

def test_li_p_leading_space_removed_when_gap_reduced(self):
"""<p> trailing space + 내부 <p> leading space → 2공백 gap 축소 시 leading 제거.

XHTML 구조: <p>...정의합니다. </p><ul><li><p> Admin ...</p></li></ul>
old_plain_text에서 gap이 2공백(" "), new에서 1공백(" ")으로 축소될 때
내부 <p>의 leading space가 제거되어야 한다.
(FC가 leading space를 보존하면 "* ·Admin" → "*··Admin" 이중 공백이 됨)
"""
xhtml = (
'<ol>'
'<li><p><strong>Allowed Zones</strong> : 정의합니다. </p>'
'<ul><li><p> Admin 매핑합니다.</p></li></ul>'
'</li>'
'</ol>'
)
patches = [{
'xhtml_xpath': 'ol[1]',
# trailing " " + leading " " = gap 2
'old_plain_text': 'Allowed Zones : 정의합니다. Admin 매핑합니다.',
# gap 2 → 1
'new_plain_text': 'Allowed Zones : 정의합니다. Admin 매핑합니다.',
}]
result = patch_xhtml(xhtml, patches)
assert '<p>Admin' in result, (
f"leading space not removed when gap reduced: {result}"
)

def test_gap_fully_deleted(self):
"""gap이 완전히 삭제되면 기존 동작대로 leading을 제거한다."""
xhtml = '<p><strong>IDENTIFIER</strong> 조사</p>'
patches = [{
'xhtml_xpath': 'p[1]',
'old_plain_text': 'IDENTIFIER 조사',
'new_plain_text': 'IDENTIFIER조사',
}]
result = patch_xhtml(xhtml, patches)
assert 'IDENTIFIER</strong>조사' in result

def test_gap_not_reduced_preserves_leading(self):
"""gap이 축소되지 않으면 leading whitespace를 보존한다."""
xhtml = (
'<ol>'
'<li><p>텍스트. </p>'
'<ul><li><p> 내용입니다.</p></li></ul>'
'</li>'
'</ol>'
)
patches = [{
'xhtml_xpath': 'ol[1]',
'old_plain_text': '텍스트. 내용입니다.',
# gap 크기 동일 (2→2), 텍스트만 변경
'new_plain_text': '텍스트. 내용변경.',
}]
result = patch_xhtml(xhtml, patches)
# gap이 축소되지 않았으므로 leading space 보존
assert '<p> 내용변경.' in result, (
f"leading space should be preserved when gap not reduced: {result}"
)
Loading