Skip to main content

hwpforge_core/control/
fields.rs

1//! Field-control payload types: path/file-name commands, cross-reference
2//! targets, and inline page-number kinds.
3//!
4//! Houses [`PathFieldCommand`], [`RefTarget`], and [`InlinePageKind`] — the
5//! typed payloads carried by the field variants of [`Control`](super::Control).
6
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10/// Typed variant of the HWP5 `%pat` Command string (Wave 12n).
11///
12/// Observed forms are `$F` (file name only), `$P` (path only), and `$P$F`
13/// (full path). Anything else is preserved as [`PathFieldCommand::Unknown`].
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
15#[non_exhaustive]
16pub enum PathFieldCommand {
17    /// `$F` — file name only.
18    FileName,
19    /// `$P` — folder path only (no file name).
20    Path,
21    /// `$P$F` — full path including file name.
22    PathAndFileName,
23    /// Any other Command form, preserved verbatim.
24    Unknown(String),
25}
26
27impl PathFieldCommand {
28    /// Returns the canonical wire Command string for typed variants.
29    /// For [`Self::Unknown`], returns the carried raw string.
30    pub fn wire_command(&self) -> &str {
31        match self {
32            Self::FileName => "$F",
33            Self::Path => "$P",
34            Self::PathAndFileName => "$P$F",
35            Self::Unknown(s) => s.as_str(),
36        }
37    }
38
39    /// Parses a wire Command string into a typed variant. Unknown forms
40    /// are preserved as [`Self::Unknown`].
41    pub fn from_wire(cmd: &str) -> Self {
42        match cmd {
43            "$F" => Self::FileName,
44            "$P" => Self::Path,
45            "$P$F" => Self::PathAndFileName,
46            other => Self::Unknown(other.to_string()),
47        }
48    }
49}
50
51/// Cross-reference target identifier (Wave 12m Phase 2).
52///
53/// Replaces the previous `target_name: String` field on
54/// [`Control::CrossRef`] with a type-safe enum that distinguishes the
55/// three observed wire forms in HWP5 `%xrf` Command strings:
56///
57/// - **`Name(String)`** — 사용자 지정 책갈피 이름 (Bookmark 전용).
58///   한컴 fixture 의 `?target1;6;0;0;0;` 형식에서 `target1` 부분.
59/// - **`SystemId(u64)`** — 한컴 자동 생성 정수 ID (Footnote / Endnote /
60///   Caption / Outline). 한컴 fixture 의 `?#1108165575;3;0;0;0;` 형식에서
61///   `#` 접두사를 떼고 정수로 파싱한 값.
62/// - **`Raw(String)`** — 위 두 형식 어느쪽으로도 파싱이 안 된 wire 값.
63///   silent fallback 위조 금지 원칙 (Codex(architect)) — 의미를
64///   모르면 raw 보존.
65///
66/// 변환은 `smithy-hwp5/src/projection.rs::decode_hwp5_target` 의
67/// boundary 함수에서 수행 (Core 는 wire-agnostic).
68#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
69#[non_exhaustive]
70pub enum RefTarget {
71    /// 사용자 지정 책갈피 이름 (Bookmark 전용).
72    Name(String),
73    /// 한컴 자동 생성 정수 ID (Footnote / Endnote / Caption / Outline).
74    SystemId(u64),
75    /// 위 형식 어느쪽으로도 정규화되지 않은 raw 값 — fallback 위조 금지.
76    Raw(String),
77}
78
79impl RefTarget {
80    /// Returns the displayable name for this target.
81    ///
82    /// - `Name(s)` → `s`
83    /// - `SystemId(id)` → `"#{id}"` 형식 (한컴 wire 와 동일)
84    /// - `Raw(s)` → `s`
85    pub fn as_display(&self) -> String {
86        match self {
87            Self::Name(s) => s.clone(),
88            Self::SystemId(id) => format!("#{id}"),
89            Self::Raw(s) => s.clone(),
90        }
91    }
92}
93
94/// Typed variant of the HWP5 `atno` inline page-number `flag` byte
95/// (Wave 12n).
96///
97/// Observed values: `0x00` = current page, `0x06` = total page count.
98/// Other values surface as [`InlinePageKind::Unknown`].
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
100#[non_exhaustive]
101pub enum InlinePageKind {
102    /// Flag `0x00` — current page number.
103    CurrentPage,
104    /// Flag `0x06` — total page count.
105    TotalPages,
106    /// Any other flag value, carried verbatim via
107    /// [`Control::InlinePageNumber::raw_flag`].
108    Unknown,
109}
110
111impl InlinePageKind {
112    /// Returns the canonical raw flag value for typed variants.
113    /// For [`Self::Unknown`], callers must use the
114    /// [`Control::InlinePageNumber::raw_flag`] field directly.
115    pub fn raw_flag(self) -> Option<u32> {
116        match self {
117            Self::CurrentPage => Some(0x00),
118            Self::TotalPages => Some(0x06),
119            Self::Unknown => None,
120        }
121    }
122
123    /// Maps an observed raw flag value to a typed variant.
124    pub fn from_raw_flag(flag: u32) -> Self {
125        match flag {
126            0x00 => Self::CurrentPage,
127            0x06 => Self::TotalPages,
128            _ => Self::Unknown,
129        }
130    }
131}