iOfficeAI/OfficeCLI · error · ArgumentException

{propName} contains an unpaired high surrogate U+{(int)c:X4}

Error message

{propName} contains an unpaired high surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.

What it means

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.

Source

Thrown at src/officecli/Core/ParseHelpers.cs:860

            if (c == '\t' || c == '\n' || c == '\r') continue;
            // '\v' (0x0B) is XML-illegal as character data. It is allowed ONLY
            // when the caller consumes it into a break ELEMENT before
            // serialization (NEWLINE-SEMANTICS-V2: AppendTextWithBreaks turns
            // '\v' into <w:br/>). Callers that write validated text verbatim
            // into XML (chart titles, xlsx cell values, headers, ...) keep the
            // strict default so '\v' can never reach raw character data.
            if (c == '\v' && allowSoftBreakChar) continue;
            if (c < 0x20)
                throw new ArgumentException(
                    $"{propName} contains XML-illegal control character U+{(int)c:X4} at position {i}. " +
                    "Allowed control chars: \\t, \\n, \\r" +
                    (allowSoftBreakChar ? ", \\v." : "."));
            // UTF-16 surrogates only valid in pairs (high then low). A lone
            // half is illegal in XML 1.0 character data.
            if (char.IsHighSurrogate(c))
            {
                if (i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1]))
                    throw new ArgumentException(
                        $"{propName} contains an unpaired high surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.");
                i++; // skip the matched low surrogate
                continue;
            }
            if (char.IsLowSurrogate(c))
                throw new ArgumentException(
                    $"{propName} contains an unpaired low surrogate U+{(int)c:X4} at position {i}. Use a complete UTF-16 surrogate pair.");
            if (c == 0xFFFE || c == 0xFFFF)
                throw new ArgumentException(
                    $"{propName} contains the XML-illegal noncharacter U+{(int)c:X4} at position {i}.");
        }
    }
}

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Avoid splitting strings at arbitrary indices when they may contain supplementary-plane characters; use text-element or grapheme-aware boundaries (StringInfo).
  2. Ensure the source bytes are fully and correctly decoded to UTF-16 before setting the value.
  3. If truncation is needed, use StringInfo.SubstringByTextElements to avoid breaking surrogate pairs.

Example fix

// before
var s = "\uD83D";            // lone high surrogate (smiley missing its low half)
// after
var s = "\uD83D\uDE00";      // complete surrogate pair
Defensive patterns

Strategy: validation

Validate before calling

// Reject strings containing a lone high surrogate (no following low surrogate):
static bool HasNoLoneSurrogates(string s)
{
    for (int i = 0; i < s.Length; i++)
    {
        if (char.IsHighSurrogate(s, i) && (i + 1 >= s.Length || !char.IsLowSurrogate(s[i + 1]))) return false;
        if (char.IsLowSurrogate(s, i) && (i == 0 || !char.IsHighSurrogate(s[i - 1]))) return false;
    }
    return true;
}

Prevention

When it happens

Trigger: Thrown at src/officecli/Core/ParseHelpers.cs:860 when the library encounters an invalid state.

Common situations: 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.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/f196de170e0b5a79. Report an issue: GitHub.