using System.Text;
using System.Xml;
using NewLife.Reflection;
namespace NewLife.Xml;
/// <summary>Xml辅助类</summary>
public static class XmlHelper
{
#region 实体转Xml
/// <summary>序列化为Xml字符串</summary>
/// <param name="obj">要序列化为Xml的对象</param>
/// <param name="encoding">编码</param>
/// <param name="attachComment">是否附加注释,附加成员的Description和DisplayName注释</param>
/// <param name="useAttribute">是否使用特性输出</param>
/// <returns>Xml字符串</returns>
public static String ToXml(this Object obj, Encoding? encoding = null, Boolean attachComment = false, Boolean useAttribute = false)
{
if (obj == null) return String.Empty;
encoding ??= Encoding.UTF8;
using var stream = new MemoryStream();
ToXml(obj, stream, encoding, attachComment, useAttribute);
return stream.ToArray().ToStr();
}
/// <summary>序列化为Xml字符串</summary>
/// <param name="obj">要序列化为Xml的对象</param>
/// <param name="encoding">编码</param>
/// <param name="attachComment">是否附加注释,附加成员的Description和DisplayName注释</param>
/// <param name="useAttribute">是否使用特性输出</param>
/// <param name="omitXmlDeclaration">忽略XML声明</param>
/// <returns>Xml字符串</returns>
public static String ToXml(this Object obj, Encoding encoding, Boolean attachComment, Boolean useAttribute, Boolean omitXmlDeclaration)
{
if (obj == null) return String.Empty;
using var stream = new MemoryStream();
var xml = new Serialization.Xml
{
Stream = stream,
Encoding = encoding ?? Encoding.UTF8,
UseAttribute = useAttribute,
UseComment = attachComment,
Setting = new XmlWriterSettings
{
OmitXmlDeclaration = omitXmlDeclaration,
Indent = true
}
};
xml.Write(obj);
return stream.ToArray().ToStr();
}
/// <summary>序列化为Xml数据流</summary>
/// <param name="obj">要序列化为Xml的对象</param>
/// <param name="stream">目标数据流</param>
/// <param name="encoding">编码</param>
/// <param name="attachComment">是否附加注释,附加成员的Description和DisplayName注释</param>
/// <param name="useAttribute">是否使用特性输出</param>
public static void ToXml(this Object obj, Stream stream, Encoding? encoding = null, Boolean attachComment = false, Boolean useAttribute = false)
{
if (obj == null) return;
var xml = new Serialization.Xml
{
Stream = stream,
Encoding = encoding ?? Encoding.UTF8,
UseAttribute = useAttribute,
UseComment = attachComment
};
xml.Write(obj);
}
/// <summary>序列化为Xml文件</summary>
/// <param name="obj">要序列化为Xml的对象</param>
/// <param name="file">目标Xml文件</param>
/// <param name="encoding">编码</param>
/// <param name="attachComment">是否附加注释,附加成员的Description和DisplayName注释</param>
/// <returns>Xml字符串</returns>
public static void ToXmlFile(this Object obj, String file, Encoding? encoding = null, Boolean attachComment = true)
{
if (File.Exists(file)) File.Delete(file);
file.EnsureDirectory(true);
// 如果是字符串字典,直接写入文件,其它设置无效
if (obj is IDictionary<String, String> dic)
{
var xml = dic.ToXml();
File.WriteAllText(file, xml, encoding ?? Encoding.UTF8);
return;
}
using var stream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
obj.ToXml(stream, encoding, attachComment);
// 必须通过设置文件流长度来实现截断,否则后面可能会多一截旧数据
stream.SetLength(stream.Position);
stream.Flush();
}
#endregion
#region Xml转实体
/// <summary>字符串转为Xml实体对象</summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="xml">Xml字符串</param>
/// <returns>Xml实体对象</returns>
public static TEntity? ToXmlEntity<TEntity>(this String xml) where TEntity : class
{
return xml.ToXmlEntity(typeof(TEntity)) as TEntity;
}
/// <summary>字符串转为Xml实体对象</summary>
/// <param name="xml">Xml字符串</param>
/// <param name="type">实体类型</param>
/// <returns>Xml实体对象</returns>
public static Object? ToXmlEntity(this String xml, Type type)
{
if (xml.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(xml));
if (type == null) throw new ArgumentNullException(nameof(type));
var x = new Serialization.Xml
{
Stream = new MemoryStream(xml.GetBytes())
};
return x.Read(type);
//if (!type.IsPublic) throw new XException("类型{0}不是public,不能进行Xml序列化!", type.FullName);
//var serial = new XmlSerializer(type);
//using (var reader = new StringReader(xml))
//using (var xr = new XmlTextReader(reader))
//{
// // 必须关闭Normalization,否则字符串的\r\n会变为\n
// //xr.Normalization = true;
// return serial.Deserialize(xr);
//}
}
/// <summary>数据流转为Xml实体对象</summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="stream">数据流</param>
/// <param name="encoding">编码</param>
/// <returns>Xml实体对象</returns>
public static TEntity? ToXmlEntity<TEntity>(this Stream stream, Encoding? encoding = null) where TEntity : class
{
return stream.ToXmlEntity(typeof(TEntity), encoding) as TEntity;
}
/// <summary>数据流转为Xml实体对象</summary>
/// <param name="stream">数据流</param>
/// <param name="type">实体类型</param>
/// <param name="encoding">编码</param>
/// <returns>Xml实体对象</returns>
public static Object? ToXmlEntity(this Stream stream, Type type, Encoding? encoding = null)
{
if (stream == null) throw new ArgumentNullException(nameof(stream));
if (type == null) throw new ArgumentNullException(nameof(type));
encoding ??= Encoding.UTF8;
var x = new Serialization.Xml
{
Stream = stream,
Encoding = encoding
};
return x.Read(type);
//if (!type.IsPublic) throw new XException("类型{0}不是public,不能进行Xml序列化!", type.FullName);
//var serial = new XmlSerializer(type);
//using (var reader = new StreamReader(stream, encoding))
//using (var xr = new XmlTextReader(reader))
//{
// // 必须关闭Normalization,否则字符串的\r\n会变为\n
// //xr.Normalization = true;
// return serial.Deserialize(xr);
//}
}
/// <summary>Xml文件转为Xml实体对象</summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="file">Xml文件</param>
/// <param name="encoding">编码</param>
/// <returns>Xml实体对象</returns>
public static TEntity? ToXmlFileEntity<TEntity>(this String file, Encoding? encoding = null) where TEntity : class
{
if (file.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(file));
if (!File.Exists(file)) return null;
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
return stream.ToXmlEntity<TEntity>(encoding);
}
#endregion
#region Xml类型转换
///// <summary>删除字节序,硬编码支持utf-8、utf-32、Unicode三种</summary>
///// <param name="encoding">原始编码</param>
///// <returns>删除字节序后的编码</returns>
//internal static Encoding TrimPreamble(this Encoding encoding)
//{
// if (encoding == null) return encoding;
// var bts = encoding.GetPreamble();
// if (bts == null || bts.Length <= 0) return encoding;
// if (encoding is UTF8Encoding) return _utf8Encoding ?? (_utf8Encoding = new UTF8Encoding(false));
// if (encoding is UTF32Encoding) return _utf32Encoding ?? (_utf32Encoding = new UTF32Encoding(false, false));
// if (encoding is UnicodeEncoding) return _unicodeEncoding ?? (_unicodeEncoding = new UnicodeEncoding(false, false));
// return encoding;
//}
//private static Encoding _utf8Encoding;
//private static Encoding _utf32Encoding;
//private static Encoding _unicodeEncoding;
internal static Boolean CanXmlConvert(this Type type)
{
if (type.IsBaseType()) return true;
if (!type.IsValueType) return false;
if (type == typeof(Guid) || type == typeof(DateTimeOffset) || type == typeof(TimeSpan)) return true;
return false;
}
internal static String? XmlConvertToString(Object value)
{
if (value == null) return null;
var type = value.GetType();
var code = type.GetTypeCode();
if (code == TypeCode.String) return value.ToString();
if (code == TypeCode.DateTime) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.RoundtripKind);
// 枚举的 TypeCode 为其基元类型,但 XmlConvert 不提供枚举重载,与原反射查找语义一致地拒绝
if (type.IsEnum) throw new XException("Type {0} does not support converting to Xml strings. Please use the CanXmlConvert method first to determine!", type);
// 曾经使用方法名反射查找,AOT 下方法元数据可能被裁剪导致失效,改为显式分发
//var method = Reflect.GetMethodEx(typeof(XmlConvert), "ToString", type);
//var method = typeof(XmlConvert).GetMethodEx("ToString", type);
if (value is Guid guid) return XmlConvert.ToString(guid);
if (value is TimeSpan span) return XmlConvert.ToString(span);
return code switch
{
TypeCode.Boolean => XmlConvert.ToString((Boolean)value),
TypeCode.Char => XmlConvert.ToString((Char)value),
TypeCode.SByte => XmlConvert.ToString((SByte)value),
TypeCode.Byte => XmlConvert.ToString((Byte)value),
TypeCode.Int16 => XmlConvert.ToString((Int16)value),
TypeCode.UInt16 => XmlConvert.ToString((UInt16)value),
TypeCode.Int32 => XmlConvert.ToString((Int32)value),
TypeCode.UInt32 => XmlConvert.ToString((UInt32)value),
TypeCode.Int64 => XmlConvert.ToString((Int64)value),
TypeCode.UInt64 => XmlConvert.ToString((UInt64)value),
TypeCode.Single => XmlConvert.ToString((Single)value),
TypeCode.Double => XmlConvert.ToString((Double)value),
TypeCode.Decimal => XmlConvert.ToString((Decimal)value),
_ => throw new XException("Type {0} does not support converting to Xml strings. Please use the CanXmlConvert method first to determine!", type),
};
}
internal static T? XmlConvertFromString<T>(String xml) => (T?)XmlConvertFromString(typeof(T), xml);
internal static Object? XmlConvertFromString(Type type, String xml)
{
if (xml == null) return null;
var code = type.GetTypeCode();
if (code == TypeCode.String) return xml;
if (code == TypeCode.DateTime) return XmlConvert.ToDateTime(xml, XmlDateTimeSerializationMode.RoundtripKind);
// 枚举的 TypeCode 为其基元类型,但 XmlConvert 不提供枚举重载,与原反射查找语义一致地拒绝
if (type.IsEnum) throw new XException("Type {0} does not support converting from Xml strings. Please use the CanXmlConvert method first!", type);
// 曾经使用方法名拼接反射查找,AOT 下方法元数据可能被裁剪导致失效,改为显式分发
//var method = Reflect.GetMethodEx(typeof(XmlConvert), "To" + type.Name, typeof(String));
//var method = typeof(XmlConvert).GetMethodEx("To" + type.Name, typeof(String));
if (type == typeof(Guid)) return XmlConvert.ToGuid(xml);
if (type == typeof(TimeSpan)) return XmlConvert.ToTimeSpan(xml);
return code switch
{
TypeCode.Boolean => XmlConvert.ToBoolean(xml),
TypeCode.Char => XmlConvert.ToChar(xml),
TypeCode.SByte => XmlConvert.ToSByte(xml),
TypeCode.Byte => XmlConvert.ToByte(xml),
TypeCode.Int16 => XmlConvert.ToInt16(xml),
TypeCode.UInt16 => XmlConvert.ToUInt16(xml),
TypeCode.Int32 => XmlConvert.ToInt32(xml),
TypeCode.UInt32 => XmlConvert.ToUInt32(xml),
TypeCode.Int64 => XmlConvert.ToInt64(xml),
TypeCode.UInt64 => XmlConvert.ToUInt64(xml),
TypeCode.Single => XmlConvert.ToSingle(xml),
TypeCode.Double => XmlConvert.ToDouble(xml),
TypeCode.Decimal => XmlConvert.ToDecimal(xml),
_ => throw new XException("Type {0} does not support converting from Xml strings. Please use the CanXmlConvert method first!", type),
};
}
#endregion
#region Xml转字典
/// <summary>简单Xml转为字符串字典</summary>
/// <param name="xml"></param>
/// <returns></returns>
public static Dictionary<String, String>? ToXmlDictionary(this String xml)
{
if (String.IsNullOrEmpty(xml)) return null;
var doc = new XmlDocument();
doc.LoadXml(xml);
var root = doc.DocumentElement;
if (root == null) return null;
var dic = new Dictionary<String, String>();
if (root.ChildNodes != null && root.ChildNodes.Count > 0)
{
foreach (var item in root.ChildNodes)
{
if (item is not XmlNode node) continue;
if (node.ChildNodes != null && (node.ChildNodes.Count > 1 ||
node.ChildNodes.Count == 1 && !(node.FirstChild is XmlText) && !(node.FirstChild is XmlCDataSection)))
{
dic[node.Name] = node.InnerXml;
}
else
{
dic[node.Name] = node.InnerText;
}
}
}
return dic;
}
/// <summary>字符串字典转为Xml</summary>
/// <param name="dic"></param>
/// <param name="rootName"></param>
/// <returns></returns>
public static String ToXml(this IDictionary<String, String> dic, String? rootName = null)
{
if (String.IsNullOrEmpty(rootName)) rootName = "xml";
var doc = new XmlDocument();
var root = doc.CreateElement(rootName);
doc.AppendChild(root);
if (dic != null && dic.Count > 0)
{
foreach (var item in dic)
{
var elm = doc.CreateElement(item.Key);
elm.InnerText = item.Value;
root.AppendChild(elm);
}
}
return doc.OuterXml;
}
#endregion
}
|