zoukankan      html  css  js  c++  java
  • 序列化,反序列化时低序位非打印 ASCII 字符的问题

    原因:System.Xml.XmlException: “”(十六进制值 0x03)是无效的字符。 行 4,位置 24。 在 System.Xml.XmlTextReaderImpl.Throw(Exception e) 在 决方法

    最近碰到一个问题,我的一个把数据库中记录的信息暴露出来的Web Service调用时候出问题了。报下面的错误信息:

    System.InvalidOperationException was unhandled
      Message="XML 文档(1, 823)中有错误。"
      Source="System.Xml"
        Message="“”(十六进制值 0x0E)是无效的字符。 行 1,位置 823。"
        Source="System.Xml"

    当这个错误发生时,Web Service 服务器端不会有任何错误,而调用这个 Web Service 的客户端则会报上述错误。
    是何原因导致的这个问题呢?
    答案很简单,是WEB Service 暴露的XML文档中存在低序位非打印 ASCII 字符所致。
    我们查看 Web Service 返回的XML 文档文档中,会有下面的XML文档节:其中的  就是低序位 ASCII 字符。对应的字符如后:

    <Value>&#xE;在神奇天地裏誰叱咤風雨</Value>

    会导致这些问题的 低序位非打印 ASCII 字符包含以下字符:
    #x0 - #x8 (ASCII 0 - 8)
    #xB - #xC (ASCII 11 - 12)
    #xE - #x1F (ASCII 14 - 31)

    下面就是一个简单演示这个问题的控制台程序,
    为了简单起见,这里没有建立 WebService, 而是把一个类XML序列化存储到文件,然后再把这个文件反序列化读取出来:
    其中的这个类的Value值中,放了一个低序位非打印 ASCII 字符。
    执行这个控制台程序,就会报异常。“XML 文档(3, 12)中有错误。”

    using System;
    using System.Xml.Serialization;
    using System.IO;
    using System.Text;
    using System.Globalization;

    namespace TextSerialize
    {
    [Serializable]
    public class MyClass
    {
    public string Value { get; set; }
    }

    class Program
    {
    static void Main(string[] args)
    {
    string fileName = "d:\\1.txt";

    MyClass c = new MyClass();
    c.Value = string.Format("在神奇{0}天地裏誰叱咤風雨", Convert.ToChar(14));

    SaveAsXML(c, fileName, Encoding.UTF8);

    object o = ConvertFileToObject(fileName, typeof(MyClass), Encoding.UTF8);
    MyClass d = o as MyClass;
    if (d != null) Console.WriteLine(d.Value);
    else Console.WriteLine("null");

    Console.ReadLine();
    }


    /// <summary>
    /// 序列化
    /// </summary>
    /// <param name="objectToConvert"></param>
    /// <param name="path"></param>
    /// <param name="encoding"></param>
    public static void SaveAsXML(object objectToConvert, string path, Encoding encoding)
    {
    if (objectToConvert != null)
    {
    Type t = objectToConvert.GetType();
    XmlSerializer ser = new XmlSerializer(t);
    using (StreamWriter writer = new StreamWriter(path, false, encoding))
    {
    ser.Serialize(writer, objectToConvert);
    writer.Close();
    }
    }
    }


    /// <summary>
    /// 反序列化
    /// </summary>
    /// <param name="path"></param>
    /// <param name="objectType"></param>
    /// <param name="encoding"></param>
    /// <returns></returns>
    public static object ConvertFileToObject(string path, Type objectType, Encoding encoding)
    {
    object convertedObject = null;
    if (!string.IsNullOrEmpty(path))
    {
    XmlSerializer ser = new XmlSerializer(objectType);
    using (StreamReader reader = new StreamReader(path, encoding))
    {
    convertedObject = ser.Deserialize(reader);
    reader.Close();
    }
    }
    return convertedObject;
    }
    }
    }

    上面提到的Web Service 的那个问题,跟这个演示程序是一样的。

    我们需要被序列化的内容中,存在 低序位非打印 ASCII 字符 时, .net 会给我们正常序列化, 会自动把 低序位非打印 ASCII 字符 转换成 &#x 编码的字符(这个XML规范中要求这么做的)。

    但是,反序列化时候,如果需要反序列化的内容如果存在 &#x 编码的字符(映射到低序位非打印 ASCII 字符),则反序列化就会出错。

    如果解决这个问题呢?

    当然,最彻底的解决方法是修改反序列化的代码,让这些字符不会出错。但这个东西很多时候不归我们控制。这个方案不可行。

    下一个方案就是剔除这些捣乱的字符。

    我这里要给出的方案,是对这些字符序列化时作一次预处理,反序列化时,作一次反向处理。
    这里为了演示的更有意义,我这里处理逻辑就是把 低序位非打印 ASCII 字符 转换成 &#x 编码的字符 ,和把&#x 编码的字符 转换成 低序位非打印 ASCII 字符。
    这样就可以使用我这里提供的函数,实现更多的处理逻辑。这两个函数的代码如下:

            /// <summary>
    /// 把一个字符串中的 低序位 ASCII 字符 替换成 &#x 字符
    /// 转换 ASCII 0 - 8 -> &#x0 - &#x8
    /// 转换 ASCII 11 - 12 -> &#xB - &#xC
    /// 转换 ASCII 14 - 31 -> &#xE - &#x1F
    /// </summary>
    /// <param name="tmp"></param>
    /// <returns></returns>
    public static string ReplaceLowOrderASCIICharacters(string tmp)
    {
    StringBuilder info = new StringBuilder();
    foreach (char cc in tmp)
    {
    int ss = (int)cc;
    if (((ss >= 0) && (ss <= 8)) || ((ss >= 11) && (ss <= 12)) || ((ss >= 14) && (ss <= 32)))
    info.AppendFormat("&#x{0:X};", ss);
    else info.Append(cc);
    }
    return info.ToString();
    }

    /// <summary>
    /// 把一个字符串中的下列字符替换成 低序位 ASCII 字符
    /// 转换 &#x0 - &#x8 -> ASCII 0 - 8
    /// 转换 &#xB - &#xC -> ASCII 11 - 12
    /// 转换 &#xE - &#x1F -> ASCII 14 - 31
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public static string GetLowOrderASCIICharacters(string input)
    {
    if (string.IsNullOrEmpty(input)) return string.Empty;
    int pos, startIndex = 0, len = input.Length;
    if (len <= 4) return input;

    StringBuilder result = new StringBuilder();
    while ((pos = input.IndexOf("&#x", startIndex)) >= 0)
    {
    bool needReplace = false;
    string rOldV = string.Empty, rNewV = string.Empty;

    int le = (len - pos < 6) ? len - pos : 6;
    int p = input.IndexOf(";", pos, le);

    if (p >= 0)
    {
    rOldV = input.Substring(pos, p - pos + 1);

    // 计算 对应的低位字符
    short ss;
    if (short.TryParse(rOldV.Substring(3, p - pos - 3), NumberStyles.AllowHexSpecifier, null, out ss))
    {
    if (((ss >= 0) && (ss <= 8)) || ((ss >= 11) && (ss <= 12)) || ((ss >= 14) && (ss <= 32)))
    {
    needReplace = true;
    rNewV = Convert.ToChar(ss).ToString();
    }
    }
    pos = p + 1;
    }
    else pos += le;

    string part = input.Substring(startIndex, pos - startIndex);
    if (needReplace) result.Append(part.Replace(rOldV, rNewV));
    else result.Append(part);

    startIndex = pos;
    }
    result.Append(input.Substring(startIndex));
    return result.ToString();
    }

    这样,我们这个演示程序的 Main 函数修改为下面的代码,也不会有任何错误发生。

            static void Main(string[] args)
    {
    Console.WriteLine(GetLowOrderASCIICharacters("123456&#x50000"));
    Console.WriteLine(GetLowOrderASCIICharacters("123456&#x5"));
    Console.WriteLine(GetLowOrderASCIICharacters("&#x5"));
    Console.WriteLine(GetLowOrderASCIICharacters("0123&#x1F;456789"));
    Console.WriteLine(GetLowOrderASCIICharacters("\f"));
    Console.WriteLine(GetLowOrderASCIICharacters("&#xE;=-1"));
    Console.WriteLine(GetLowOrderASCIICharacters("&#xF;"));
    Console.WriteLine(GetLowOrderASCIICharacters("&#x1F;"));

    string fileName = "d:\\1.txt";

    MyClass c = new MyClass();
    c.Value = string.Format("在神奇{0}天地裏誰叱咤風雨", Convert.ToChar(14));
    c.Value = ReplaceLowOrderASCIICharacters(c.Value);


    SaveAsXML(c, fileName, Encoding.UTF8);

    object o = ConvertFileToObject(fileName, typeof(MyClass), Encoding.UTF8);
    MyClass d = o as MyClass;
    if (d != null)
    {
    d.Value = GetLowOrderASCIICharacters(d.Value);
    Console.WriteLine(d.Value);
    }
    else Console.WriteLine("null");

    Console.ReadLine();
    }

    小结

    低序位非打印 ASCII 字符 在很多时候会给我们的系统带来问题,这部分字符必须作特殊处理。

  • 相关阅读:
    Android点击按钮退出程序并提醒
    Android项目如何打包成安装包.apk
    java之String字符串根据指定字符转化为字符串数组
    2021春季开课 课程计划
    Android:Unable to find explicit activity class报错
    不用写findViewById的偷懒方法
    人月神话阅读笔记03
    人月神话阅读笔记02
    javaweb验证码
    疫情地图展示
  • 原文地址:https://www.cnblogs.com/jishu/p/1940119.html
Copyright © 2011-2022 走看看