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
41 changes: 29 additions & 12 deletions confluence-mdx/bin/reverse_sync/patch_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,21 +190,19 @@ def _normalize_list_for_content_compare(content: str) -> str:
내용 변경 판정에서는 무시한다. 대신 항목 경계와 항목 내부의 실제 공백 수 차이는
그대로 보존해 no-op reflow와 가시 공백 변경을 구분한다.

마커 뒤 공백 수도 보존한다. normalize_mdx_to_plain이 마커를 제거하므로
마커 뒤 공백 수를 별도로 접두어에 기록하여 ``* text``와 ``* text``를 구분한다.
마커 뒤 공백 변경은 이 함수가 아닌 ``_has_marker_ws_change``로 별도 감지한다.
"""
marker_re = re.compile(r'^(\s*(?:\d+\.|[-*+]))(\s+)')
lines = content.strip().split('\n')
item_chunks: List[str] = []
current_chunk: List[str] = []
current_marker_ws: str = ''

def _flush_current() -> None:
if not current_chunk:
return
plain = normalize_mdx_to_plain('\n'.join(current_chunk), 'list')
if plain:
item_chunks.append(current_marker_ws + plain.replace('\n', ' '))
item_chunks.append(plain.replace('\n', ' ').strip())

for line in lines:
if not line.strip():
Expand All @@ -213,23 +211,39 @@ def _flush_current() -> None:
if m:
_flush_current()
current_chunk = [line]
current_marker_ws = m.group(2)
continue
if re.match(r'^\s*(?:\d+\.(?:\s+|$)|[-*+]\s+)', line):
_flush_current()
current_chunk = [line]
current_marker_ws = ''
continue
if current_chunk:
current_chunk.append(line)
else:
current_chunk = [line]
current_marker_ws = ''

_flush_current()
return '\n'.join(item_chunks)


def _has_marker_ws_change(old_content: str, new_content: str) -> bool:
"""리스트 마커 뒤 공백 수가 변경되었는지 감지한다.

``_normalize_list_for_content_compare``는 텍스트 내용만 비교하므로
마커 뒤 공백 변경(``* text`` → ``* text``)은 여기서 별도로 감지한다.
"""
marker_re = re.compile(r'^(\s*(?:\d+\.|[-*+]))(\s+)')

def _extract_marker_ws(content: str) -> List[str]:
result: List[str] = []
for line in content.strip().split('\n'):
m = marker_re.match(line)
if m:
result.append(m.group(2))
return result

return _extract_marker_ws(old_content) != _extract_marker_ws(new_content)


def _build_inline_fixups(
old_content: str,
new_content: str,
Expand Down Expand Up @@ -1113,10 +1127,12 @@ def _mark_used(block_id: str, m: BlockMapping):
_old_plain_raw = _normalize_list_for_content_compare(change.old_block.content)
_new_plain_raw = _normalize_list_for_content_compare(change.new_block.content)
has_content_change = _old_plain_raw != _new_plain_raw
# _apply_mdx_diff_to_xhtml에 전달할 기본값은 collapse_ws 적용:
# XHTML plain text에는 줄바꿈이 없으므로 clean list 정렬에는 공백 축약본이 맞다.
_old_plain = collapse_ws(_old_plain_raw)
_new_plain = collapse_ws(_new_plain_raw)
has_marker_ws_change = _has_marker_ws_change(
change.old_block.content, change.new_block.content)
# _apply_mdx_diff_to_xhtml 전달용: 항목간 \n을 공백으로 변환하고
# 양쪽 공백을 제거하여 XHTML plain text와 정렬한다.
_old_plain = _old_plain_raw.replace('\n', ' ').strip()
_new_plain = _new_plain_raw.replace('\n', ' ').strip()
# ol start 변경 감지: 숫자 목록의 시작 번호가 달라진 경우
_old_start = re.match(r'^\s*(\d+)\.', change.old_block.content)
_new_start = re.match(r'^\s*(\d+)\.', change.new_block.content)
Expand All @@ -1132,7 +1148,8 @@ def _mark_used(block_id: str, m: BlockMapping):
block_type=change.old_block.type,
)
has_inline_boundary = bool(inline_fixups)
has_any_change = has_content_change or has_ol_start_change or has_inline_boundary
has_any_change = (has_content_change or has_ol_start_change
or has_inline_boundary or has_marker_ws_change)
should_replace_clean_list = (
mapping is not None
and not _contains_preserved_anchor_markup(mapping.xhtml_text)
Expand Down
44 changes: 33 additions & 11 deletions confluence-mdx/tests/test_reverse_sync_patch_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
_extract_inline_markers,
_find_roundtrip_sidecar_block,
_has_inline_boundary_change,
_has_marker_ws_change,
_normalize_list_for_content_compare,
_resolve_mapping_for_change,
build_patches,
Expand Down Expand Up @@ -2972,13 +2973,13 @@ def test_image_anchor_list_keeps_collapsed_text_diff(self):


class TestNormalizeListMarkerWhitespace:
"""_normalize_list_for_content_compare: 마커 뒤 공백 차이를 보존하여 변경 감지."""
"""_normalize_list_for_content_compare: 텍스트 내용 변경만 감지하고 마커 공백은 무시."""

def test_marker_ws_difference_detected(self):
"""마커 뒤 공백 수가 다르면 정규화 결과가 다르다."""
def test_marker_ws_difference_ignored(self):
"""마커 뒤 공백 수가 달라도 정규화 결과는 같다."""
old = _normalize_list_for_content_compare("* 항목")
new = _normalize_list_for_content_compare("* 항목")
assert old != new
assert old == new

def test_same_content_same_result(self):
"""마커 공백이 같으면 정규화 결과도 같다."""
Expand All @@ -2992,20 +2993,41 @@ def test_text_only_change_detected(self):
new = _normalize_list_for_content_compare("* 새것")
assert old != new

def test_numbered_list_marker_ws(self):
"""번호 리스트 마커 뒤 공백 차이."""
def test_numbered_list_marker_ws_ignored(self):
"""번호 리스트 마커 뒤 공백 차이는 무시한다."""
old = _normalize_list_for_content_compare("7. 생성이")
new = _normalize_list_for_content_compare("7. 생성이")
assert old != new
assert old == new

def test_nested_list_marker_ws(self):
"""중첩 리스트에서 하위 항목 마커 공백 차이."""
def test_nested_list_marker_ws_ignored(self):
"""중첩 리스트에서 하위 항목 마커 공백 차이는 무시한다."""
old = _normalize_list_for_content_compare("1. 상위\n * 하위")
new = _normalize_list_for_content_compare("1. 상위\n * 하위")
assert old != new
assert old == new

def test_text_and_marker_ws_change(self):
"""텍스트와 마커 공백이 동시에 변경."""
"""텍스트와 마커 공백이 동시에 변경되면 텍스트 차이를 감지한다."""
old = _normalize_list_for_content_compare("* 원래 텍스트")
new = _normalize_list_for_content_compare("* 새 텍스트")
assert old != new


class TestHasMarkerWsChange:
"""_has_marker_ws_change: 마커 뒤 공백 변경을 별도로 감지한다."""

def test_ws_difference_detected(self):
assert _has_marker_ws_change("* 항목", "* 항목") is True

def test_same_ws_no_change(self):
assert _has_marker_ws_change("* 항목", "* 항목") is False

def test_numbered_list_ws(self):
assert _has_marker_ws_change("7. 생성이", "7. 생성이") is True

def test_nested_list_ws(self):
assert _has_marker_ws_change(
"1. 상위\n * 하위", "1. 상위\n * 하위") is True

def test_text_change_same_ws(self):
"""텍스트만 변경되고 마커 공백은 같으면 False."""
assert _has_marker_ws_change("* 원래", "* 새것") is False
Loading