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
2 changes: 1 addition & 1 deletion confluence-mdx/bin/converter/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ def convert_li(self, node, list_type, counter=None):
logging.debug(f'li_itself={li_itself}')
logging.debug(f'child_markdown={child_markdown}')

itself = ' '.join(li_itself)
itself = ''.join(li_itself)
self.markdown_lines.append(f'{prefix}{itself}\n')
for line in child_markdown:
self.markdown_lines.append(prefix_for_children + line)
Expand Down
128 changes: 128 additions & 0 deletions confluence-mdx/docs/fc-whitespace-preservation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Forward Converter 공백 보존 원칙

## 핵심 원칙

Forward Converter(FC)는 XHTML 원본의 공백, 단락 구조를 **있는 그대로** MDX로 변환해야 한다.
FC 출력의 형태는 XHTML 원문이 결정한다. XHTML 원문과 무관하게 "이렇게 생겨야 한다"는 고정된 출력 형태는 없다.

### 하지 말아야 할 것

- XHTML에 없는 공백을 추가하지 않는다.
- XHTML에 있는 공백을 임의로 제거하지 않는다.
- 빈 `<p>` 요소의 콘텐츠를 필터링하지 않는다 (XHTML에 존재하는 구조이다).
- FC 출력 형태를 미리 정해놓고 그에 맞추지 않는다.

### 해야 할 것

- XHTML 텍스트 노드의 공백을 그대로 MDX로 옮긴다.
- `<p>` 요소의 콘텐츠(빈 단락 포함)를 보존한다.
- FC가 삽입하는 구조적 마커(`<br/>` 등)와 XHTML 원본 공백을 구분한다.

## 구체적 사례: `convert_li`의 `join`

### 문제 상황

`convert_li()`에서 `<li>` 내 여러 `<p>` 요소를 MDX 한 줄로 합칠 때,
`<p>` 경계를 `<br/>`로 표현하고 `' '.join(li_itself)`로 연결했다.

이 `' '.join()`은 **모든 항목 사이에 공백 하나를 삽입**하는데,
이 공백은 XHTML 원문에 존재하지 않는다.

### XHTML → FC 출력 추적 예시

#### 예시 1: `<br/>` 뒤 빈 `<p>`

```xml
<li>
<p>경우<br/> </p>
<ac:image>...</ac:image>
<p> </p>
</li>
```

`<p>` 경계에서 `<br/>` 구분자가 삽입되므로 `li_itself` 구성은:

| 인덱스 | 출처 | 값 | 설명 |
|--------|------|----|------|
| 0 | 첫 번째 `<p>` | `'경우<br/> '` | XHTML 텍스트 노드의 trailing space 보존 |
| 1 | `<br/>` 구분자 | `'<br/>'` | FC가 `<p>` 경계를 표현하기 위해 삽입 |
| 2 | `<p> </p>` | `' '` | 빈 단락의 공백 콘텐츠 보존 |

**`' '.join()` (잘못됨):**

```
'경우<br/> ' + ' ' + '<br/>' + ' ' + ' '
→ '경우<br/> <br/> '
```

join이 삽입한 공백(` `)이 XHTML 원본 공백과 합쳐져 이중 공백이 된다.

**`''.join()` (올바름):**

```
'경우<br/> ' + '<br/>' + ' '
→ '경우<br/> <br/> '
```

XHTML 원본의 공백만 존재한다.

#### 예시 2: `<br/>` 없는 `<p>` + 빈 `<p>`

```xml
<li>
<p>클릭합니다</p>
<ac:image>...</ac:image>
<p></p>
</li>
```

| 인덱스 | 출처 | 값 |
|--------|------|----|
| 0 | 첫 번째 `<p>` | `'클릭합니다'` |
| 1 | `<br/>` 구분자 | `'<br/>'` |
| 2 | `<p></p>` | `''` |

**`' '.join()` (잘못됨):** `'클릭합니다 <br/> '` — `<br/>` 앞뒤 공백은 XHTML에 없음

**`''.join()` (올바름):** `'클릭합니다<br/>'` — XHTML 그대로

#### 예시 3: 연속 `<p>` 텍스트

```xml
<li>
<p>Text A</p>
<p>Text B</p>
</li>
```

| 인덱스 | 출처 | 값 |
|--------|------|----|
| 0 | 첫 번째 `<p>` | `'Text A'` |
| 1 | `<br/>` 구분자 | `'<br/>'` |
| 2 | 두 번째 `<p>` | `'Text B'` |

**`' '.join()` (잘못됨):** `'Text A <br/> Text B'` — 경계 공백은 XHTML에 없음

**`''.join()` (올바름):** `'Text A<br/>Text B'` — `<br/>`가 줄바꿈 역할, 추가 공백 불필요

## 수정 방법

```python
# 변경 전 (XHTML에 없는 공백 삽입)
itself = ' '.join(li_itself)

# 변경 후 (XHTML 원본 공백만 보존)
itself = ''.join(li_itself)
```

한 줄 변경이다. 각 `<p>`의 SingleLineParser 출력에 이미 XHTML 원문의 공백이
보존되어 있으므로, join 시 추가 공백을 넣지 않으면 된다.

## 일반 원칙: FC 버그 수정 시 판단 기준

FC 출력이 "올바른지" 판단할 때:

1. **XHTML 원문을 확인**한다 — FC 출력을 보기 전에, 입력인 XHTML이 어떤 구조인지 파악
2. **XHTML의 공백/구조를 그대로 MDX에 반영하는지** 확인한다
3. FC가 삽입하는 구조적 요소(`<br/>` 구분자 등)는 XHTML 원본 공백과 **분리**해서 처리한다
4. "출력이 이렇게 생겨야 한다"는 고정관념 없이, XHTML 원문에서 출력을 **도출**한다
20 changes: 10 additions & 10 deletions confluence-mdx/tests/testcases/1454342158/expected.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ <h3>LDAP 연동</h3>

<li><strong>Server URL</strong> : <code>ldap://ldap.example.com:389</code> 과 같은 형식으로 LDAP server의 주소를 입력합니다. LDAPS의 경우 scheme을 ldaps:// 로 입력합니다.</li>

<li><strong>BindDN</strong> : LDAP 서버에 접속(바인드)할 때 사용할 서비스 계정의 고유 이름(Distinguished Name, DN)을 입력합니다. 이 계정은 최소한 사용자 정보를 검색(Read)할 수 있는 권한이 필요합니다. <br/> 예시: cn=admin,ou=Services,dc=example,dc=com</li>
<li><strong>BindDN</strong> : LDAP 서버에 접속(바인드)할 때 사용할 서비스 계정의 고유 이름(Distinguished Name, DN)을 입력합니다. 이 계정은 최소한 사용자 정보를 검색(Read)할 수 있는 권한이 필요합니다.<br/>예시: cn=admin,ou=Services,dc=example,dc=com</li>

<li><strong>Bind Password</strong> : BindDN의 암호를 입력합니다.</li>

Expand Down Expand Up @@ -552,11 +552,11 @@ <h4>QueryPie에서 Okta 연동 및 동기화 설정</h4>

<li><strong>Additional Settings</strong>
<ul>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다. <br/> 동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.<br/>동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>

<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다. <br/> 특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요. <br/> Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>
<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.<br/>특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.<br/>Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>

<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다. <br/> IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다. <br/> 옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다. <br/> 단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>
<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.<br/>IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.<br/>옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.<br/>단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>

<li><strong>Allowed User Deletion Rate Threshold :</strong>
<ul>
Expand Down Expand Up @@ -663,11 +663,11 @@ <h3>One Login 연동</h3>
<ul>
<li><strong>Additional Settings</strong>
<ul>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다. <br/> 동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.<br/>동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>

<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다. <br/> 특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요. <br/> Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>
<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.<br/>특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.<br/>Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>

<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다. <br/> IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다. <br/> 옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다. <br/> 단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>
<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.<br/>IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.<br/>옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.<br/>단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>

<li><strong>Allowed User Deletion Rate Threshold :</strong>
<ul>
Expand Down Expand Up @@ -730,11 +730,11 @@ <h3>Custom Identity Provider</h3>
<ul>
<li><strong>Additional Settings</strong>
<ul>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다. <br/> 동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>
<li><strong>Make New Users Inactive by Default</strong>: 동기화 시 새로운 사용자를 비활성화 상태로 추가할지 여부를 선택합니다.<br/>동기화할 사용자 수가 많거나, 사용자의 인증을 통한 QueryPie 접근을 개별적으로 관리하고자 하는 경우 해당 옵션을 활성화하시기 바랍니다.</li>

<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다. <br/> 특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요. <br/> Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>
<li><strong>Use an Attribute for Privilege Revoke</strong> : 동기화 시 특정 Attribute에 따라 Privilege를 회수할지 여부를 선택합니다.<br/>특정 Attribute의 변경에 의해 자동으로 DAC Privilege를 회수하고자 하는 경우 이 옵션을 활성화하세요.<br/>Attribute 입력 필드에 활성화 변경을 감지하려는 Attribute 이름을 입력합니다.</li>

<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다. <br/> IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다. <br/> 옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다. <br/> 단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>
<li><strong>Enable Attribute Synchronization</strong> : IdP의 사용자 속성과 QueryPie 사용자 속성을 매핑하여 동기화할지 여부를 선택합니다.<br/>IdP에서 관리 중인 사용자 속성을 QueryPie 내 Attribute와 자동으로 연동하고자 하는 경우, 해당 옵션을 활성화하시기 바랍니다.<br/>옵션 활성화 시, 하단에 Attribute Mapping UI가 표시되며 매핑 작업을 통해 연동할 IdP Attribute와 QueryPie Attribute를 지정할 수 있습니다.<br/>단, 해당 기능은 Profile Editor(Admin&gt; General &gt; User Management &gt; Profile Editor)에서 Source Priority가 Inherit from profile source로 설정된 Attribute에 한해 적용됩니다.</li>

<li><strong>Allowed User Deletion Rate Threshold :</strong>
<ul>
Expand Down
Loading
Loading