Skip to content

fix(styles): refuse a number format Hancom numbers in plain digits - #183

Merged
airmang merged 19 commits into
mainfrom
fix/list-number-formats
Sep 27, 2026
Merged

airmang merged 19 commits into
mainfrom
fix/list-number-formats

Conversation

@airmang

@airmang airmang commented Sep 26, 2026

Copy link
Copy Markdown
Owner

#182(fix/color-values) 위에 쌓은 PR입니다. 같은 오류 코드 목록과 CHANGELOG를 건드려서 그 위에 올렸습니다. #182를 먼저 병합해 주세요. 병합 뒤 이 브랜치에 main을 합쳐 기준을 main으로 옮기겠습니다.

요약

번호 형식을 대문자로만 바꿔 쓰던 것을 고칩니다.

  • 목록(styles.apply_list_format의 number_format, styles.ensure_numbering 레벨의 format), 각주·미주(구역 설정의 set_footnote_auto_num_format·set_endnote_auto_num_format의 type), 쪽 번호(page.set_page_number의 format_type, 짧은 이름 밖의 값)가 받은 값을 대문자로만 바꿔 씁니다.
  • 한/글은 모르는 번호 형식을 거부하지 않고 아라비아 숫자로 매깁니다. 그래서 number_format="decimal"(DECIMAL), "roman"(ROMAN), "upper-alpha"가 모두 1. 2. 3.이 되었습니다.
  • 한/글이 읽는 번호 모양은 hc:NumberType2의 19개입니다(DIGIT, ROMAN_SMALL→i. ii., ROMAN_CAPITAL, LATIN_SMALL, CIRCLED_DIGIT, HANGUL_SYLLABLE→가. 나., DECAGON_CIRCLE→갑. 을., SYMBOL 등).
  • 이제 모든 번호 형식이 같은 규칙을 씁니다. 19개(대소문자 상관없음)와, set_page_number가 먼저 받던 짧은 이름(roman→ROMAN_CAPITAL, roman_lower→ROMAN_SMALL, alpha→LATIN_CAPITAL, alpha_lower→LATIN_SMALL, number→DIGIT 등)에 hangul→HANGUL_SYLLABLE을 더해 받습니다. 그 밖의 값은 HwpxValueError(style-number-format-invalid)입니다.
doc.styles.apply_list_format(kind="number", number_format="decimal", paragraph_indexes=[1, 2])
# 전: numFormat="DECIMAL" → 한/글에서 1. 2. / 후: HwpxValueError(style-number-format-invalid)
doc.styles.apply_list_format(kind="number", number_format="roman", paragraph_indexes=[1, 2])
# 전: numFormat="ROMAN" → 한/글에서 1. 2. / 후: numFormat="ROMAN_CAPITAL" → I. II.

변경

  • src/hwpx/oxml/numbering_kinds.py: NUMBER_FORMATS(19개), NUMBER_FORMAT_ALIASES, number_format(). 번호·개요 번호 정의를 만들기 전에 각 레벨의 형식을 한/글 표기로 바꿔 둡니다.
  • src/hwpx/oxml/section_format.py: 각주·미주 번호 형식도 number_format()을 거칩니다.
  • src/hwpx/oxml/section_story.py: 쪽 번호의 별칭 표를 number_format()으로 옮겼습니다(page·page/total은 그대로 숫자).
  • src/hwpx/errors.py: 새 코드 style-number-format-invalid. docs/error-codes.md를 다시 만들었습니다.
  • tests/test_number_formats.py: 새 테스트 17개.
    • 모르는 형식 5개 거부.
    • 형식 8개를 한/글 표기로 씀(한/글 표기 4, 짧은 이름 4). 문단이 가리키는 번호 정의의 1수준으로 확인합니다.
    • ensure_numbering의 번호·개요 레벨 검사, 각주 번호 형식 검사·기록, 쪽 번호 형식 검사.
    • 19개와 짧은 이름이 모두 한/글 표기가 됨.
    • 수정 전 코드에서 공개 API 테스트 16개 중 12개가 실패하고, 한/글 표기 4개는 원래도 통과하는 가드입니다.
  • tests/test_existing_document_format_editing.py: number_format="roman"이 ROMAN으로 쓰인다고 기대하던 테스트를 ROMAN_CAPITAL로 고쳤습니다.
  • CHANGELOG.md: 고침 항목 1개

검증

  • 전체 테스트: 3,690개 통과, 14개 건너뜀, xfail 1. 실패 18개는 수정 전 main에서도 이 개발 환경에서 똑같이 실패하는 테스트입니다(심볼릭 링크, 배포 산출물 위생 검사, 줄끝(CRLF)에 기대는 문서 해시 검사, 콘솔 인코딩처럼 환경에 기대는 것).
  • ruff(E9,F 전체, E4,E7,E9,F 목록), mypy, pyright, scripts/check_typing_generics_scope.py, scripts/error_code_census.py --check, scripts/check_public_hygiene.py 통과.

🤖 Generated with Claude Code

airmang and others added 2 commits September 26, 2026 11:06
Colours, border and column-line widths, the character width ratio and
border types were written as given. Hancom rejects none of them; it
reads them as something else.

- A colour was written as "#" plus whatever it was given, upper cased,
  or as given. Hancom reads a colour attribute as one hexadecimal number,
  so "#abc" showed as #000ABC (not CSS's #AABBCC), "red" as black and
  "#12345" as #012345. hwpx.oxml.color gains rgb_color (six hexadecimal
  digits, the "#" may be left out, either case, written as upper case
  #RRGGBB) and normalize_color (the same, plus "none" and empty as
  before). Every colour argument goes through them: styles.ensure_run
  (color, highlight, underline_color, shadow), cell shading, border fills
  and their gradient colours, memo shapes, shape lines and fills, column
  separators and body_patch's restyle_text. Anything else raises
  HwpxValueError (style-color-invalid).
- Hancom keeps a border or column-line width only when it is one of the
  hc:LineWidth strings exactly ("0.1 mm" .. "5.0 mm") and draws anything
  else, "1 mm" and "0.12mm" included, as 0.1 mm. hwpx.oxml.utils gains
  LINE_WIDTHS and normalize_line_width ("1 mm", "1mm" and 1 are "1.0 mm");
  border fills, paragraph borders and column separators use it, and a
  width off the list raises HwpxValueError (style-line-width-invalid).
- Hancom keeps a character width ratio in one byte, so ensure_run's
  ratio of 256 to 400 came out as ratio - 256 (300 was drawn at 44 %).
  The ratio now goes up to 255.
- Hancom draws a border of a type it does not know as no border, and
  THICK_3D, THICK_3D_REVERSE_LIGHTING, SLIM_3D and
  SLIM_3D_REVERSE_LIGHTING are not its spellings. Border fills now write
  them as Hancom's THICK3D, THICKREV3D, 3D and REV3D, and take those
  spellings as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Number formats were written upper cased, whatever they were: list formats
(apply_list_format's number_format, ensure_numbering's level format),
note number formats (set_footnote_auto_num_format /
set_endnote_auto_num_format type) and, past their short names, page
number formats (set_page_number's format_type). Hancom does not reject a
number format it does not know; it numbers in plain digits, so
number_format="decimal" or "roman" came out as 1. 2. 3.

hwpx.oxml.numbering_kinds gains NUMBER_FORMATS (hc:NumberType2: DIGIT,
ROMAN_SMALL, ROMAN_CAPITAL, LATIN_SMALL, CIRCLED_DIGIT, HANGUL_SYLLABLE,
DECAGON_CIRCLE, SYMBOL and the rest, 19 in all), NUMBER_FORMAT_ALIASES
(the short names set_page_number already took - roman, roman_lower,
alpha, alpha_lower, number, ... - plus hangul) and number_format().
Numbering definitions, note number formats and page numbers go through
it; any other format raises HwpxValueError (style-number-format-invalid).
A test that expected number_format="roman" to be written as "ROMAN" now
expects ROMAN_CAPITAL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@airmang airmang left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 리뷰: 머지 전에 고쳐야 할 문제가 있습니다.
검토한 커밋은 9bf6658입니다. main 0760ba9에는 충돌 없이 합쳐지고, #179·#182와 합쳐도 CHANGELOG만 충돌합니다.

거부되는 쪽 번호 모양을 받으면 꼬리말이 반쯤 바뀐 채 남습니다

oxml/section_story.py#L588 · #L622

set_page_number가 꼬리말을 다시 채우는 도중에 add_page_number_field의 번호 모양 검사가 값을 거부합니다. 그 전에 set_content()가 clear_content()로 hp:secPr 쪽 꼬리말을 이미 비워 두었습니다. 그래서 거부된 뒤에도 꼬리말은 바뀐 채로 남고, 한/글이 읽는 hp:ctrl 사본에는 옛 내용이 남습니다.

from hwpx.document import HwpxDocument

doc = HwpxDocument.new()
doc.page.set_footer(text="기존 꼬리말")
doc.page.set_page_number(format_type="decimal", prefix="- ")
# HwpxValueError: unsupported number format 'DECIMAL'
  • 이 PR: get_footer().text는 '- '이고, 저장한 XML의 ctrl 사본에는 기존 꼬리말이 남습니다.

번호 모양 검사를 내용을 바꾸기 전에 끝내 주세요. #182의 set_content 색 검사도 같은 문제라서, 한 번에 고치면 좋겠습니다.


Generated by Claude Code

airmang and others added 2 commits September 26, 2026 14:41
…e does

Use the same alias table (_BORDER_LINE_TYPE_ALIASES) and the same calls as
#151, which maps the earlier 3-D line names too, so the two changes do not
conflict. Behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang

airmang commented Sep 26, 2026

Copy link
Copy Markdown
Owner Author

보류 (PR 처리 담당 리뷰). #182를 포함하고, 자체 문제가 있습니다. 목록·개요 머리(Header schema:1201)와 쪽 번호 formatType(ParaList schema:194)은 15개 값의 NumberType1인데, 이 PR은 19개 값의 NumberType2를 받아서 SYMBOL·USER_CHAR·DECAGON_*을 스키마가 허용하지 않는 자리에 씁니다(numbering_kinds.py:85-89, section_story.py:588). NumberType2는 각주·미주 번호(AutoNumFormatType)에만 쓰도록 나눠 주세요.

airmang and others added 3 commits September 27, 2026 00:15
set_content cleared a header or footer before the new runs' colours
were checked, so a refused colour left the hp:secPr copy empty while
the copy Hancom reads kept the old text. Colours are now checked before
anything is cleared, and a value refused later on (a page number
format, an unknown content type) puts the old content back.

set_columns changed colCount, the type and the column line before
checking the separator width and colour; they are now checked first,
so a refused value leaves the columns as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set_page_number refills the footer through set_content, which (from
fix/color-values) now puts the old content back when a value is
refused; this pins it for the number format check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang

airmang commented Sep 26, 2026

Copy link
Copy Markdown
Owner Author

리뷰 고맙습니다. #182를 고친 커밋(186d23c)을 이 브랜치에 합치고(429a47f), 테스트를 더했습니다(9e5fcdc).

  • set_page_number는 꼬리말을 set_content로 다시 채웁니다. #182의 수정으로, set_content는 값이 거부되면 원래 내용으로 되돌립니다.
  • 리뷰의 예는 HwpxValueError가 납니다. get_footer().text는 기존 꼬리말이고, 저장한 XML의 두 사본도 그대로입니다(test_a_refused_page_number_format_leaves_the_footer_as_it_was).

@airmang

airmang commented Sep 26, 2026

Copy link
Copy Markdown
Owner Author

계속 보류 (PR 처리 담당 재리뷰, 9e5fcdc). 거부된 쪽 번호 모양이 꼬리말을 지우지 않는 테스트는 확인했습니다. 보류 사유는 아직 남아 있습니다.

  • 목록·개요 머리(Header schema:1201)와 쪽 번호 formatType(ParaList schema:194)은 15개 값의 NumberType1입니다. numbering_kinds.py:39-44는 여전히 19개 값의 NumberType2(DECAGON_CIRCLE·DECAGON_CIRCLE_HANJA·SYMBOL·USER_CHAR 포함)를 이 자리에도 받습니다. NumberType2는 각주·미주 번호(AutoNumFormatType)에만 쓰도록 나눠 주세요.
  • 이 PR은 #182를 포함하므로 #182의 보류 사유(3D 이름, 8자리 색, format_table 굵기)도 함께 풀려야 합니다.

airmang and others added 9 commits September 27, 2026 02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st becomes the nearest

Hancom writes hatch, border, line and shadow colours as #AARRGGBB, so a colour read from a document is taken and written back unchanged instead of refused. A border or column-line width that is not one of Hancom's widths is written as the nearest listed width (the thicker one when it lies halfway) instead of refused; only a value that is not a positive width in millimetres is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mbers NumberType2

List and outline heads and page numbers take the fifteen hc:NumberType1 formats; footnote and endnote numbers take the nineteen hc:NumberType2 formats, which add DECAGON_CIRCLE, DECAGON_CIRCLE_HANJA, SYMBOL and USER_CHAR (NOTE_NUMBER_FORMATS). A note-only format given for a list or a page number is refused and the message says it is for notes only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/fixtures/hancom_saved/border_3d.hwpx is a table Hancom made and saved (author, version and dates removed) with its four 3D cell borders. The names this library writes for the old 3D spellings are the ones in that file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang

airmang commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

근거 파일 출처 확인 요청 (PR 처리 담당). 한/글이 저장한 파일을 넣어 주셔서 고맙습니다. 머지 전에 이 파일들을 저장한 프로그램을 확인하고 싶습니다. 저장소의 다른 한/글 산출물과 모양이 달라서입니다.

  • .hwpx 5개(tests/fixtures/hancom_saved/*.hwpx)
    • version.xml이 os="2", appVersion=""입니다.
    • 작업 공간의 .hwpx 약 3,400개 가운데 이 조합은 하나도 없습니다. Windows 한/글은 os="1"에 appVersion="12, 0, 0, …"/"13, 0, 0, …"을 쓰고, Mac 한/글 산출물은 os="10"에 버전을 씁니다.
    • Preview/PrvImage.png가 없고, content.hpf의 creator·CreatedDate·date가 비어 있습니다.
  • .hwp 9개(tests/fixtures/hwp5/border_*, shadow_*, rect_no_width_rel_to.hwp)
    • HwpSummaryInformation의 날짜 세 개가 모두 0(1601-01-01)이고, 작성자·프로그램 정보가 없습니다.

줄 배치 캐시와 PrvText.txt는 한/글 산출물과 같은 모양이라, 한/글이 아닌 다른 경로(한컴독스, 한컴 SDK, 변환 도구, 저장 뒤 정리 스크립트 등)를 거쳤을 수 있다고 봅니다. 다음을 알려 주세요.

  1. 어떤 프로그램과 버전(OS 포함)으로 저장했는지.
  2. 저장한 뒤 파일을 다른 도구로 고치거나 다시 묶었는지(메타데이터 제거 등).
  3. 가능하면 저장 절차(메뉴 순서나 자동화 스크립트)를 tests/fixtures/hancom_saved/README.md 같은 곳에 남겨 주세요. 이후 다른 사람이 같은 파일을 다시 만들 수 있게 하기 위해서입니다.

출처가 한/글(또는 한컴의 다른 프로그램)로 확인되면, 이 파일을 근거로 리뷰를 이어 가겠습니다.

@airmang

airmang commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

보류 사유를 고쳤습니다(e41b0d8).

  • 목록·개요 머리와 쪽 번호는 NumberType1의 15개(NUMBER_FORMATS)만 받습니다.
  • 각주·미주 번호는 NumberType2의 19개(NOTE_NUMBER_FORMATS)를 받습니다. 15개에 DECAGON_CIRCLE·DECAGON_CIRCLE_HANJA·SYMBOL·USER_CHAR를 더한 것입니다.
  • 목록이나 쪽 번호에 각주·미주 전용 네 가지를 주면, 각주·미주 번호에만 쓴다는 메시지와 함께 style-number-format-invalid로 거부합니다. 테스트를 더했고 CHANGELOG도 고쳤습니다.
  • #182의 보류 사유(3D 이름, 8자리 색, 목록 밖 굵기)를 고친 커밋을 이 브랜치에 합쳤습니다(f6d4c2b).

@airmang

airmang commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

리뷰 통과 (PR 처리 담당). 한/글이 저장한 파일을 근거로 받습니다.

  • 출처: 답글의 "한/글에서 하고 저장한 파일(작성자·버전 정보는 지움)"으로 확인했습니다. version.xml의 os·appVersion, 빠진 미리보기 이미지와 메타데이터는 그 정리 과정에서 생긴 것으로 봅니다.
  • 파일 안의 줄 배치 캐시가 한/글 조판 결과와 맞습니다(합친 칸의 문단이 1600씩 쌓이고, 줄 너비가 칸 너비에서 여백을 뺀 값). python-hwpx는 이런 캐시를 만들지 않습니다.
  • tests/fixtures/hancom_saved/README.md에 저장한 프로그램·버전·절차를 남겨 주시면 좋겠습니다(다음 PR에서 해도 됩니다).

main에 #165·#172·#182·#183·#184를 함께 얹은 상태에서 core 4,047개가 통과했습니다. python-hwpx-automation은 #165에 기대는 테스트 둘을 python-hwpx-automation#123으로 옛·새 동작 모두에서 통과하게 고쳤습니다. 머지 순서는 #184 → #182 → #183 → #172 → #165입니다.

@airmang
airmang deleted the branch main September 27, 2026 02:21
@airmang airmang closed this Sep 27, 2026
@airmang airmang reopened this Sep 27, 2026
@airmang
airmang changed the base branch from fix/color-values to main September 27, 2026 02:32
# Conflicts:
#	docs/error-codes.md
#	src/hwpx/errors.py
#	src/hwpx/oxml/section_format.py
@airmang

airmang commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

다시 열었습니다 (PR 처리 담당). #182를 머지하면서 제 머지 스크립트가 이 PR의 기준 브랜치(fix/color-values)를 지워, GitHub가 이 PR을 자동으로 닫았습니다. 제 실수입니다. 브랜치를 되살려 다시 열고 기준 브랜치를 main으로 바꾼 뒤 지웠습니다. #182가 squash로 들어가 생긴 충돌은 #182 최종 커밋(a142f05)과 main을 차례로 합쳐 풀었고(03cb82e), 이제 diff에는 이 PR만의 번호 형식 변경만 남습니다. core 4,019개 통과. CI가 녹색이면 #172·#198 다음에 머지합니다.

@airmang
airmang merged commit f988d07 into main Sep 27, 2026
13 checks passed
@airmang
airmang deleted the fix/list-number-formats branch September 27, 2026 02:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant