from copy import deepcopy import re from typing import Any # Only numeric data placeholders are normalized. Decorative Korean placeholders such as <소수1+> # stay untouched and do not consume a value index. DATA_PLACEHOLDER_PATTERN = re.compile(r'<(?:int|float\d*)[^>]*>') DECORATIVE_KOREAN_PLACEHOLDER_PATTERN = re.compile(r'<[가-힣]+\d+\+>') VALUE_PATTERN = re.compile(r'\{value\d+\}') SKILL_DETAIL_NORMALIZE_VERSION = 'decorative-korean-placeholder-v1' def replace_placeholders(template: str) -> str: idx = 0 def repl(_: re.Match[str]) -> str: nonlocal idx idx += 1 return f'{{value{idx}}}' protected = DECORATIVE_KOREAN_PLACEHOLDER_PATTERN.sub(lambda match: f'@@DDECORATIVE_{len(match.group(0))}_{match.start()}@@', template) replacements = { f'@@DDECORATIVE_{len(match.group(0))}_{match.start()}@@': match.group(0) for match in DECORATIVE_KOREAN_PLACEHOLDER_PATTERN.finditer(template) } normalized = DATA_PLACEHOLDER_PATTERN.sub(repl, protected) for token, placeholder in replacements.items(): normalized = normalized.replace(token, placeholder) return normalized def normalize_skill_detail(skill_data: dict[str, Any]) -> dict[str, Any]: level_info = skill_data.get('levelInfo') if not isinstance(level_info, dict): return skill_data option_desc = level_info.get('optionDesc') if option_desc is not None: level_info['optionDesc'] = replace_placeholders(option_desc) return skill_data def skill_detail_for_level(skill_data: dict[str, Any], level: int | None) -> dict[str, Any]: result = deepcopy(skill_data) if level is None: return result level = max(0, min(level, result.get('maxLevel', 0))) level_info = result.get('levelInfo') if not isinstance(level_info, dict): return result rows = level_info.get('rows') if not isinstance(rows, list): return result detail = next((row for row in rows if row.get('level') == level), None) if not detail: return result attribute = {**level_info, **detail} option_desc = attribute.get('optionDesc') attribute['detail'] = option_desc if isinstance(option_desc, str) else '' for key, value in (attribute.get('optionValue') or {}).items(): attribute['detail'] = attribute['detail'].replace(f'{{{key}}}', str(value)) attribute['detail'] = VALUE_PATTERN.sub('-', attribute['detail']) attribute.pop('rows', None) result['attribute'] = attribute result.pop('levelInfo', None) return result