{"record":{"id":"f196de170e0b5a79","repo":"iOfficeAI/OfficeCLI","slug":"propname-contains-an-unpaired-high-surrogate-u","errorCode":null,"errorMessage":"{propName} contains an unpaired high surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.","messagePattern":"(.+?) contains an unpaired high surrogate U\\+(.+?) at position (.+?)\\. Use a complete UTF-16 surrogate pair\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/officecli/Core/ParseHelpers.cs","lineNumber":860,"sourceCode":"            if (c == '\\t' || c == '\\n' || c == '\\r') continue;\n            // '\\v' (0x0B) is XML-illegal as character data. It is allowed ONLY\n            // when the caller consumes it into a break ELEMENT before\n            // serialization (NEWLINE-SEMANTICS-V2: AppendTextWithBreaks turns\n            // '\\v' into <w:br/>). Callers that write validated text verbatim\n            // into XML (chart titles, xlsx cell values, headers, ...) keep the\n            // strict default so '\\v' can never reach raw character data.\n            if (c == '\\v' && allowSoftBreakChar) continue;\n            if (c < 0x20)\n                throw new ArgumentException(\n                    $\"{propName} contains XML-illegal control character U+{(int)c:X4} at position {i}. \" +\n                    \"Allowed control chars: \\\\t, \\\\n, \\\\r\" +\n                    (allowSoftBreakChar ? \", \\\\v.\" : \".\"));\n            // UTF-16 surrogates only valid in pairs (high then low). A lone\n            // half is illegal in XML 1.0 character data.\n            if (char.IsHighSurrogate(c))\n            {\n                if (i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1]))\n                    throw new ArgumentException(\n                        $\"{propName} contains an unpaired high surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.\");\n                i++; // skip the matched low surrogate\n                continue;\n            }\n            if (char.IsLowSurrogate(c))\n                throw new ArgumentException(\n                    $\"{propName} contains an unpaired low surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.\");\n            if (c == 0xFFFE || c == 0xFFFF)\n                throw new ArgumentException(\n                    $\"{propName} contains the XML-illegal noncharacter U+{(int)c:X4} at position {i}.\");\n        }\n    }\n}\n","sourceCodeStart":842,"sourceCodeEnd":874,"githubUrl":"https://github.com/iOfficeAI/OfficeCLI/blob/1ced45e900782c5083ed550ddf328ee974e425e7/src/officecli/Core/ParseHelpers.cs#L842-L874","documentation":"Thrown by ValidateXmlText when a high surrogate (U+D800–U+DBFF) is not followed by a low surrogate. XML 1.0 character data forbids lone UTF-16 surrogate halves; only a complete high+low pair encodes a valid supplementary-plane code point. The validator advances past a matched pair, so this fires only for an unmatched high surrogate.","triggerScenarios":"Thrown at src/officecli/Core/ParseHelpers.cs:860 when the library encounters an invalid state.","commonSituations":"Truncating user text at a fixed character count that lands between a surrogate pair; .Substring on a string containing emoji/supplementary-plane chars; decoding a byte stream as UTF-16 with a truncated final character.","solutions":["Avoid splitting strings at arbitrary indices when they may contain supplementary-plane characters; use text-element or grapheme-aware boundaries (StringInfo).","Ensure the source bytes are fully and correctly decoded to UTF-16 before setting the value.","If truncation is needed, use StringInfo.SubstringByTextElements to avoid breaking surrogate pairs."],"exampleFix":"// before\nvar s = \"\\uD83D\";            // lone high surrogate (smiley missing its low half)\n// after\nvar s = \"\\uD83D\\uDE00\";      // complete surrogate pair","handlingStrategy":"validation","validationCode":"// Reject strings containing a lone high surrogate (no following low surrogate):\nstatic bool HasNoLoneSurrogates(string s)\n{\n    for (int i = 0; i < s.Length; i++)\n    {\n        if (char.IsHighSurrogate(s, i) && (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1]))) return false;\n        if (char.IsLowSurrogate(s, i) && (i == 0 || !char.IsHighSurrogate(s[i - 1]))) return false;\n    }\n    return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use StringInfo for substring/truncate operations on text that may contain emoji.","Fully decode source bytes to UTF-16 before setting.","Avoid slicing strings at arbitrary indices across surrogate pairs."],"tags":["xml","input-validation","unicode","surrogate","ooxml","officecli"],"backgroundTag":null,"analyzedSha":"1ced45e900782c5083ed550ddf328ee974e425e7","analyzedAt":"2026-08-13T13:01:07.193Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}