diff --git a/confluence-mdx/bin/reverse_sync/xhtml_patcher.py b/confluence-mdx/bin/reverse_sync/xhtml_patcher.py index 2760128ce..e7663d740 100644 --- a/confluence-mdx/bin/reverse_sync/xhtml_patcher.py +++ b/confluence-mdx/bin/reverse_sync/xhtml_patcher.py @@ -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를 제거한다. - # 예: IDENTIFIER 조사 → IDENTIFIER조사 (공백 교정) + # 예1: IDENTIFIER 조사 → IDENTIFIER조사 (공백 삭제) + # 예2:
Admin →
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를 처리했으므로 별도 보존 불필요 diff --git a/confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py b/confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py index 697a6d58e..7cf4d09b1 100644 --- a/confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py +++ b/confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py @@ -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): + """
trailing space + 내부
leading space → 2공백 gap 축소 시 leading 제거. + + XHTML 구조:
...정의합니다.
Admin ...
의 leading space가 제거되어야 한다. + (FC가 leading space를 보존하면 "* ·Admin" → "*··Admin" 이중 공백이 됨) + """ + xhtml = ( + '
Allowed Zones : 정의합니다.
' + 'Admin 매핑합니다.
Admin' in result, ( + f"leading space not removed when gap reduced: {result}" + ) + + def test_gap_fully_deleted(self): + """gap이 완전히 삭제되면 기존 동작대로 leading을 제거한다.""" + xhtml = '
IDENTIFIER 조사
' + patches = [{ + 'xhtml_xpath': 'p[1]', + 'old_plain_text': 'IDENTIFIER 조사', + 'new_plain_text': 'IDENTIFIER조사', + }] + result = patch_xhtml(xhtml, patches) + assert 'IDENTIFIER조사' in result + + def test_gap_not_reduced_preserves_leading(self): + """gap이 축소되지 않으면 leading whitespace를 보존한다.""" + xhtml = ( + '텍스트.
' + '내용입니다.
내용변경.' in result, ( + f"leading space should be preserved when gap not reduced: {result}" + )