From b53c4bfb7157349d8cf63690d0f530810f6b1e4a Mon Sep 17 00:00:00 2001 From: Brummel Date: Mon, 18 May 2026 18:06:34 +0200 Subject: [PATCH] fix: honor ModifiedBy all="true" (ignore ValidModifierClass restriction) --- src/claml.rs | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/claml.rs b/src/claml.rs index bb38d10..21a01d8 100644 --- a/src/claml.rs +++ b/src/claml.rs @@ -31,9 +31,12 @@ struct ModifierClassDef { struct ModifiedByRef { /// The modifier code (value of the `code` attribute). code: String, - /// When `all="false"` and `valid` is non-empty, only the listed + /// `true` when the `all` attribute was `"true"` (or absent/self-closing): + /// all ModifierClass codes apply regardless of any `` children. + /// `false` when `all="false"`: restrict to the listed `valid` codes. + unrestricted: bool, + /// When `unrestricted` is `false` and `valid` is non-empty, only the listed /// `` values are allowed for this parent. - /// When `all="true"` or `valid` is empty, all ModifierClass codes apply. valid: Vec, } @@ -141,7 +144,11 @@ pub fn load(path: &str) -> Result, AppError> { } } if !mb_code.is_empty() { - cur_refs.push(ModifiedByRef { code: mb_code, valid: Vec::new() }); + cur_refs.push(ModifiedByRef { + code: mb_code, + unrestricted: true, + valid: Vec::new(), + }); } } } @@ -271,24 +278,14 @@ pub fn load(path: &str) -> Result, AppError> { if !mb_code.is_empty() { // Store in cur_mb; ValidModifierClass children will populate valid. // On End(ModifiedBy) we'll push it to cur_refs. + // `unrestricted` is set from all="true"/"false"; when true, + // expand_modifiers ignores any ValidModifierClass children and + // uses the modifier's full ModifierClass list. cur_mb = Some(ModifiedByRef { code: mb_code, - // Pre-populate as empty; if all="false" the End handler - // will use whatever ValidModifierClass children collected. - // If all="true", valid stays empty = unrestricted. + unrestricted: all_true, valid: Vec::new(), }); - // If all="true", mark it as unrestricted even if no children. - // We track this implicitly: valid is empty ↔ unrestricted. - // But for all="true" we still want to keep it empty. - // For all="false", children will push codes into mb.valid. - // So no extra flag needed; empty valid = unrestricted. - // However, if all="false" but NO ValidModifierClass children - // appeared (unusual but possible), empty valid = unrestricted, - // which is the safest fallback (full set). - if all_true { - // Explicitly mark unrestricted; empty valid is the signal. - } // else: children will fill valid; if none appear → unrestricted. } } } @@ -422,10 +419,11 @@ pub fn load(path: &str) -> Result, AppError> { /// ICD code (e.g. `E11` + `.9` + `0` = `E11.90`, `I10.0` + `1` = `I10.01`), /// which exactly matches the Alpha-ID corpus code format. /// -/// When a `` ref carries a non-empty `valid` set (sourced from -/// `` children with `all="false"`), only those specific -/// `` codes are included for that level of the cartesian product; -/// the full modifier list is used when the set is empty (i.e. `all="true"`). +/// When a `` ref has `unrestricted = false` (i.e. `all="false"`), +/// only the `` codes listed in `valid` are included for that +/// level of the cartesian product. When `unrestricted = true` (`all="true"`), +/// the modifier's full `` list is always used regardless of any +/// `valid` entries that were parsed. /// /// Only the terminal leaf (every modifier in the chain applied) is emitted as /// a synthesized code. Its `Para295`/`Para301` values are inherited from the @@ -457,11 +455,12 @@ fn expand_modifiers( Some(list) if !list.is_empty() => { // Fix 1: if a non-empty valid set is given (all="false"), // restrict this level to only those ModifierClass codes. - let filtered: Vec<&ModifierClassDef> = if r.valid.is_empty() { - // all="true" or no restriction: use everything. + let filtered: Vec<&ModifierClassDef> = if r.unrestricted { + // all="true": use the modifier's full ModifierClass list, + // ignoring any ValidModifierClass children that were parsed. list.iter().collect() } else { - // all="false": keep only the listed valid codes. + // all="false": restrict to the listed valid codes. list.iter().filter(|mc| r.valid.contains(&mc.code)).collect() }; if filtered.is_empty() {