Loading

[异常处理笔记] Unicode 编码代理项错误处理

问题描述

在一个 XML 序列化与文件保存的业务中,出现了一个异常:“代理项对无效,缺少低代理项字符。”

直接原因:业务提供的字符串中,有非法编码的字符。具体而言,就是如果出现了需要用代理项表示的字符,则必须成对出现(代理项对),前一个为高代理项,后一个为低代理项。

关于 Unicode 的代理项,可以参看:Unicode | 代理项(Surrogate) - 云+社区 - 腾讯云

本质原因:业务层提供的字符串有问题,需要调查为什么会出现非法的代理项编码。

业务方可能的问题原因:
对于需要使用代理项字符来说,就自然含义来看,它是一个字符,但编码上由高代理+低代理组合成一个代理对来表示。在 C# 的代码下,如 string surrogateContent = "\ud835\udc01",此时的 surrogateContent.Length 为 2。
所以就可能出现,在某些场景下,将其分开成两个“字符”了,但这两个“字符”都是非法的,因为其编码是在代理项区域,必须成对出现,合起来表示一个字符。

解决:
1 修复业务,不再产生非法字符。
2 存储层对于非法字符过滤掉,之后再进行 XML 序列化或者保存(先正常处理,出现异常,再检查是否有非法字符,处理之后再重试)。这种方式会造成数据的丢失,需要根据实际业务场景来决定是否可以这样处理。

场景复现

这里使用的 C# 代码

public static string Test2()
{
    // 示例字符:高代理项 \ud835 低代理项 \udc01
    char invalidChar = '\ud835';
    string invalidStr = "\ud835";

    string surrogateContent = "\ud835"; // 代理项对无效,缺少低代理项字符。
    string surrogateContent = "\udc01"; // 无效的高代理项字符(0x{0})。高代理项字符必须具有范围(0xD800 - 0xDBFF)内的值。
    string surrogateContent = "\ud835\ud835"; // 代理项对 (0x{0}, 0x{1}) 无效。高代理项字符(0xD800 - 0xDBFF) 必须始终与低代理项字符(0xDC00 - 0xDFFF)成对。

    Console.WriteLine(Char.IsSurrogate(invalidChar));
    Console.WriteLine(Char.IsSurrogatePair(invalidStr, 0));
    Console.WriteLine(Char.IsHighSurrogate(invalidChar));
    Console.WriteLine(Char.IsLowSurrogate(invalidChar));

    XElement element = new XElement("node")
    {
        Value = surrogateContent
    };

    var document = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), element);

    using (var stringWriter = new StringWriter(CultureInfo.InvariantCulture))
    {
        var settings = new XmlWriterSettings
        {
            OmitXmlDeclaration = true,
            Indent = true,
            NewLineHandling = NewLineHandling.Entitize,
        };
        using (var writer = System.Xml.XmlWriter.Create(stringWriter, settings))
        {
            document.WriteTo(writer);
        }
        return stringWriter.ToString();
    }
}

因为代理项需要成对出现,前高后低,所以这里的错误一共有三种。

1 Xml_InvalidSurrogatePairWithArgs
  The surrogate pair (0x{0}, 0x{1}) is invalid. A high surrogate character (0xD800 - 0xDBFF) must always be paired with a low surrogate character (0xDC00 - 0xDFFF).
  代理项对 (0x{0}, 0x{1}) 无效。高代理项字符(0xD800 - 0xDBFF) 必须始终与低代理项字符(0xDC00 - 0xDFFF)成对。

2 Xml_InvalidSurrogateMissingLowChar
  The surrogate pair is invalid. Missing a low surrogate character.
  代理项对无效,缺少低代理项字符。

3 Xml_InvalidSurrogateHighChar
  Invalid high surrogate character (0x{0}). A high surrogate character must have a value from range (0xD800 - 0xDBFF).
  无效的高代理项字符(0x{0})。高代理项字符必须具有范围(0xD800 - 0xDBFF)内的值。

相关的源码:

XmlEncodedRawTextWriter.cs

        private static unsafe char* EncodeSurrogate( char* pSrc, char* pSrcEnd, char* pDst ) {
            Debug.Assert( XmlCharType.IsSurrogate( *pSrc ) );
 
            int ch = *pSrc;
            if ( ch <= XmlCharType.SurHighEnd ) {
                if ( pSrc + 1 < pSrcEnd ) {
                    int lowChar = pSrc[1];
                    if ( lowChar >= XmlCharType.SurLowStart &&
                        (LocalAppContextSwitches.DontThrowOnInvalidSurrogatePairs || lowChar <= XmlCharType.SurLowEnd)) {
 
                        pDst[0] = (char)ch;
                        pDst[1] = (char)lowChar;
                        pDst += 2;
 
                        return pDst;
                    }
                    throw XmlConvert.CreateInvalidSurrogatePairException( (char)lowChar, (char)ch );
                }
                throw new ArgumentException( Res.GetString( Res.Xml_InvalidSurrogateMissingLowChar ) );
            }
            throw XmlConvert.CreateInvalidHighSurrogateCharException( (char)ch );
        }

由于都是 ArgumentException,所以不是很好区分,不严谨的处理方式是,根据 Message 来判断。

/// <summary>
/// 是否字符代理项相关的异常
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
public static bool IsSurrogateException(ArgumentException ex)
{
    if (ex.Message.Contains("代理项字符") ||
        ex.Message.ToLower().Contains("surrogate"))
    {
        return true;
    }
    else
    {
        return false;
    }
}

相关内容

C# 的 char 类型是 UTF-16 编码的字符。
char type - C# reference | Microsoft Docs

C# Char 下有多个方法可以进行代理项相关的判断。
Char.IsSurrogate 方法 (System) | Microsoft Docs

.NET 中的字符编码
.NET 中的 character 编码简介 | Microsoft Docs

Unicode | 代理项(Surrogate)
Unicode | 代理项(Surrogate) - 云+社区 - 腾讯云

字符编码查询工具
汉字字符集编码查询;中文字符集编码:GB2312、BIG5、GBK、GB18030、Unicode

posted @ 2022-03-11 11:46  J.晒太阳的猫  阅读(699)  评论(0编辑  收藏  举报