Skip to content

Commit fb307bc

Browse files
jk-kim0claude
andauthored
fix(reverse_sync): 리스트 항목 선행 공백 축소가 XHTML에 반영되지 않는 문제를 수정합니다 (#977)
## Summary - `_apply_text_changes`에서 텍스트 노드 사이 gap이 축소될 때(예: 2공백→1공백) leading whitespace가 보존되는 버그를 수정합니다 - 이로 인해 `<p> Admin` → FC → `* Admin` (이중 공백)이 발생하던 문제가 해결됩니다 - gap이 완전 삭제(`not gap_new`)뿐 아니라 공백으로만 축소된 경우에도 leading을 제거합니다 ## Test plan - [x] `make test-reverse-sync` 전체 통과 (42 passed, 0 failed) - [x] `make test-reverse-sync-bugs-one TEST_ID=544377652` 통과 - [x] convert 테스트 전체 통과 (21 passed) - [x] pytest 단위 테스트 통과 (929 passed, e2e 2건은 worktree 환경 문제로 제외) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 14aa2cf commit fb307bc

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

confluence-mdx/bin/reverse_sync/xhtml_patcher.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,17 +617,23 @@ def _apply_text_changes(element: Tag, old_text: str, new_text: str):
617617
exclude_insert_at_start=exclude_at_start,
618618
)
619619

620-
# 직전 노드 범위와 현재 노드 범위 사이의 gap이 diff로 삭제된 경우,
620+
# 직전 노드 범위와 현재 노드 범위 사이의 gap이 diff로 삭제/축소된 경우,
621621
# leading whitespace를 제거한다.
622-
# 예: <strong>IDENTIFIER</strong> 조사 → IDENTIFIER조사 (공백 교정)
622+
# 예1: <strong>IDENTIFIER</strong> 조사 → IDENTIFIER조사 (공백 삭제)
623+
# 예2: <p> Admin → <p>Admin (공백 축소: gap 2→1이면 leading 제거)
623624
if leading and i > 0:
624625
prev_end = node_ranges[i - 1][1]
625626
if prev_end < node_start:
627+
gap_old = old_stripped[prev_end:node_start]
626628
gap_new = _map_text_range(
627629
old_stripped, new_stripped, opcodes, prev_end, node_start
628630
)
629631
if not gap_new:
630632
leading = ''
633+
elif (gap_new.isspace() and gap_old.isspace()
634+
and len(gap_new) < len(gap_old)
635+
and len(gap_new) <= len(leading)):
636+
leading = ''
631637

632638
if trailing_in_range:
633639
# diff가 trailing whitespace를 처리했으므로 별도 보존 불필요

confluence-mdx/tests/test_reverse_sync_xhtml_patcher.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,3 +596,66 @@ def test_only_one_of_duplicate_shortcodes_is_replaced(self):
596596

597597
assert len(soup.find_all('ac:emoticon')) == 1
598598
assert 'A 확인' in result
599+
600+
601+
class TestGapWhitespaceReduction:
602+
"""텍스트 노드 사이 gap 공백이 축소될 때 leading whitespace 처리 테스트."""
603+
604+
def test_li_p_leading_space_removed_when_gap_reduced(self):
605+
"""<p> trailing space + 내부 <p> leading space → 2공백 gap 축소 시 leading 제거.
606+
607+
XHTML 구조: <p>...정의합니다. </p><ul><li><p> Admin ...</p></li></ul>
608+
old_plain_text에서 gap이 2공백(" "), new에서 1공백(" ")으로 축소될 때
609+
내부 <p>의 leading space가 제거되어야 한다.
610+
(FC가 leading space를 보존하면 "* ·Admin" → "*··Admin" 이중 공백이 됨)
611+
"""
612+
xhtml = (
613+
'<ol>'
614+
'<li><p><strong>Allowed Zones</strong> : 정의합니다. </p>'
615+
'<ul><li><p> Admin 매핑합니다.</p></li></ul>'
616+
'</li>'
617+
'</ol>'
618+
)
619+
patches = [{
620+
'xhtml_xpath': 'ol[1]',
621+
# trailing " " + leading " " = gap 2
622+
'old_plain_text': 'Allowed Zones : 정의합니다. Admin 매핑합니다.',
623+
# gap 2 → 1
624+
'new_plain_text': 'Allowed Zones : 정의합니다. Admin 매핑합니다.',
625+
}]
626+
result = patch_xhtml(xhtml, patches)
627+
assert '<p>Admin' in result, (
628+
f"leading space not removed when gap reduced: {result}"
629+
)
630+
631+
def test_gap_fully_deleted(self):
632+
"""gap이 완전히 삭제되면 기존 동작대로 leading을 제거한다."""
633+
xhtml = '<p><strong>IDENTIFIER</strong> 조사</p>'
634+
patches = [{
635+
'xhtml_xpath': 'p[1]',
636+
'old_plain_text': 'IDENTIFIER 조사',
637+
'new_plain_text': 'IDENTIFIER조사',
638+
}]
639+
result = patch_xhtml(xhtml, patches)
640+
assert 'IDENTIFIER</strong>조사' in result
641+
642+
def test_gap_not_reduced_preserves_leading(self):
643+
"""gap이 축소되지 않으면 leading whitespace를 보존한다."""
644+
xhtml = (
645+
'<ol>'
646+
'<li><p>텍스트. </p>'
647+
'<ul><li><p> 내용입니다.</p></li></ul>'
648+
'</li>'
649+
'</ol>'
650+
)
651+
patches = [{
652+
'xhtml_xpath': 'ol[1]',
653+
'old_plain_text': '텍스트. 내용입니다.',
654+
# gap 크기 동일 (2→2), 텍스트만 변경
655+
'new_plain_text': '텍스트. 내용변경.',
656+
}]
657+
result = patch_xhtml(xhtml, patches)
658+
# gap이 축소되지 않았으므로 leading space 보존
659+
assert '<p> 내용변경.' in result, (
660+
f"leading space should be preserved when gap not reduced: {result}"
661+
)

0 commit comments

Comments
 (0)