Skip to content

Commit 3c34267

Browse files
committed
confluence-mdx: implement Phase 4 container reconstruction
1 parent 13694b5 commit 3c34267

10 files changed

Lines changed: 786 additions & 42 deletions

confluence-mdx/bin/reverse_sync/mapping_recorder.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class BlockMapping:
1818
HEADING_TAGS = {'h1', 'h2', 'h3', 'h4', 'h5', 'h6'}
1919

2020
CALLOUT_MACRO_NAMES = frozenset({'tip', 'info', 'note', 'warning', 'panel'})
21+
CONTAINER_MACRO_NAMES = CALLOUT_MACRO_NAMES | frozenset({'expand'})
2122

2223
# backward-compat aliases
2324
_CALLOUT_MACRO_NAMES = CALLOUT_MACRO_NAMES
@@ -102,15 +103,15 @@ def record_mapping(xhtml: str) -> List[BlockMapping]:
102103
block_type='code')
103104
else:
104105
# Callout 매크로: body 텍스트만 추출 (파라미터 메타데이터 제외)
105-
if macro_name in CALLOUT_MACRO_NAMES:
106+
if macro_name in CONTAINER_MACRO_NAMES:
106107
rich_body = child.find('ac:rich-text-body')
107108
plain = get_text_with_emoticons(rich_body) if rich_body else child.get_text()
108109
else:
109110
plain = child.get_text()
110111
_add_mapping(mappings, counters, f'macro-{macro_name}', str(child), plain,
111112
block_type='html_block')
112-
# Callout 매크로: 자식 요소 개별 매핑 추가
113-
if macro_name in CALLOUT_MACRO_NAMES:
113+
# callout/details 매크로: 자식 요소 개별 매핑 추가
114+
if macro_name in CONTAINER_MACRO_NAMES:
114115
parent_mapping = mappings[-1]
115116
_add_rich_text_body_children(
116117
child, parent_mapping, mappings, counters)

confluence-mdx/bin/reverse_sync/patch_builder.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,24 @@ def _mapping_block_family(mapping: BlockMapping) -> str:
192192
return _xpath_block_family(mapping.xhtml_xpath)
193193

194194

195+
def _strategy_for_resolved_mapping(
196+
change: BlockChange,
197+
mapping: BlockMapping,
198+
) -> str:
199+
"""이미 찾은 mapping에 대해 처리 전략을 다시 계산한다."""
200+
if change.old_block.type == 'callout':
201+
return 'containing'
202+
if mapping.children:
203+
if change.old_block.type == 'list':
204+
return 'list'
205+
return 'containing'
206+
if change.old_block.type == 'list':
207+
return 'list'
208+
if is_markdown_table(change.old_block.content):
209+
return 'table'
210+
return 'direct'
211+
212+
195213
def _flush_containing_changes(
196214
containing_changes: dict,
197215
used_ids: 'set | None' = None,
@@ -330,7 +348,9 @@ def _mark_used(block_id: str, m: BlockMapping):
330348
idx, mdx_to_sidecar, xpath_to_mapping)
331349
if mapping is None:
332350
continue
333-
sidecar_block = xpath_to_sidecar_block.get(mapping.xhtml_xpath)
351+
sidecar_block = _find_roundtrip_sidecar_block(
352+
del_change, mapping, roundtrip_sidecar, xpath_to_sidecar_block,
353+
)
334354
if _is_clean_block(
335355
add_change.new_block.type,
336356
mapping,
@@ -346,6 +366,18 @@ def _mark_used(block_id: str, m: BlockMapping):
346366
_paired_indices.add(idx)
347367
_mark_used(mapping.block_id, mapping)
348368
continue
369+
if sidecar_block_requires_reconstruction(sidecar_block):
370+
patches.append(
371+
_build_replace_fragment_patch(
372+
mapping,
373+
add_change.new_block,
374+
sidecar_block=sidecar_block,
375+
mapping_lost_info=mapping_lost_info,
376+
)
377+
)
378+
_paired_indices.add(idx)
379+
_mark_used(mapping.block_id, mapping)
380+
continue
349381
old_plain = normalize_mdx_to_plain(
350382
del_change.old_block.content, del_change.old_block.type)
351383
new_plain = normalize_mdx_to_plain(
@@ -391,13 +423,37 @@ def _mark_used(block_id: str, m: BlockMapping):
391423
change, old_plain, mappings, used_ids,
392424
mdx_to_sidecar, xpath_to_mapping)
393425

426+
if strategy == 'skip' and roundtrip_sidecar is not None and change.old_block.content:
427+
identity_sidecar = find_sidecar_block_by_identity(
428+
roundtrip_sidecar.blocks,
429+
sha256_text(change.old_block.content),
430+
(change.old_block.line_start, change.old_block.line_end),
431+
)
432+
if identity_sidecar is not None:
433+
mapping = xpath_to_mapping.get(identity_sidecar.xhtml_xpath)
434+
if mapping is not None:
435+
strategy = _strategy_for_resolved_mapping(change, mapping)
436+
437+
if strategy == 'list' and mapping is None and roundtrip_sidecar is not None and change.old_block.content:
438+
identity_sidecar = find_sidecar_block_by_identity(
439+
roundtrip_sidecar.blocks,
440+
sha256_text(change.old_block.content),
441+
(change.old_block.line_start, change.old_block.line_end),
442+
)
443+
if identity_sidecar is not None:
444+
mapping = xpath_to_mapping.get(identity_sidecar.xhtml_xpath)
445+
394446
if strategy == 'skip':
395447
continue
396448

397-
if strategy == 'list':
398-
list_sidecar = _find_roundtrip_sidecar_block(
449+
sidecar_block = None
450+
if mapping is not None:
451+
sidecar_block = _find_roundtrip_sidecar_block(
399452
change, mapping, roundtrip_sidecar, xpath_to_sidecar_block,
400453
)
454+
455+
if strategy == 'list':
456+
list_sidecar = sidecar_block
401457
# roundtrip sidecar가 있지만 이 list에 매칭되는 block이 없을 때
402458
# (cross-type 거부 또는 mapping drift) clean list는 whole-fragment 재생성으로 처리
403459
should_replace_clean_list = (
@@ -409,7 +465,8 @@ def _mark_used(block_id: str, m: BlockMapping):
409465
if (mapping is not None
410466
and not _contains_preserved_anchor_markup(mapping.xhtml_text)
411467
and (
412-
sidecar_block_requires_reconstruction(list_sidecar)
468+
list_sidecar is not None
469+
or sidecar_block_requires_reconstruction(list_sidecar)
413470
or should_replace_clean_list
414471
)):
415472
_mark_used(mapping.block_id, mapping)
@@ -446,6 +503,18 @@ def _mark_used(block_id: str, m: BlockMapping):
446503
mdx_to_sidecar, xpath_to_mapping))
447504
continue
448505

506+
if strategy == 'containing' and sidecar_block_requires_reconstruction(sidecar_block):
507+
_mark_used(mapping.block_id, mapping)
508+
patches.append(
509+
_build_replace_fragment_patch(
510+
mapping,
511+
change.new_block,
512+
sidecar_block=sidecar_block,
513+
mapping_lost_info=mapping_lost_info,
514+
)
515+
)
516+
continue
517+
449518
new_plain = normalize_mdx_to_plain(
450519
change.new_block.content, change.new_block.type)
451520

@@ -465,9 +534,6 @@ def _mark_used(block_id: str, m: BlockMapping):
465534
and collapse_ws(new_plain) == collapse_ws(mapping.xhtml_plain_text)):
466535
continue
467536

468-
sidecar_block = _find_roundtrip_sidecar_block(
469-
change, mapping, roundtrip_sidecar, xpath_to_sidecar_block,
470-
)
471537
if _can_replace_table_fragment(change, mapping, roundtrip_sidecar):
472538
patches.append(
473539
_build_replace_fragment_patch(

0 commit comments

Comments
 (0)