增加同步指定博主的作品
This commit is contained in:
+324
@@ -0,0 +1,324 @@
|
||||
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class ABogus
|
||||
{
|
||||
private static readonly int[] _arguments = { 0, 1, 14 };
|
||||
private static readonly string _uaKey = "\u0000\u0001\u000e";
|
||||
private static readonly string _endString = "cus";
|
||||
private static readonly int[] _version = { 1, 0, 1, 5 };
|
||||
private static readonly string _browser = "1536|742|1536|864|0|0|0|0|1536|864|1536|864|1536|742|24|24|MacIntel";
|
||||
private static readonly uint[] _reg = {
|
||||
1937774191,
|
||||
1226093241,
|
||||
388252375,
|
||||
3666478592,
|
||||
2842636476,
|
||||
372324522,
|
||||
3817729613,
|
||||
2969243214
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> _str = new Dictionary<string, string>
|
||||
{
|
||||
{ "s0", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" },
|
||||
{ "s1", "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=" },
|
||||
{ "s2", "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=" },
|
||||
{ "s3", "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe" },
|
||||
{ "s4", "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe" }
|
||||
};
|
||||
|
||||
private List<byte> _chunk = new List<byte>();
|
||||
private int _size = 0;
|
||||
private uint[] _registers;
|
||||
private int[] _uaCode;
|
||||
private string _browserInfo;
|
||||
private int _browserLength;
|
||||
private int[] _browserCode;
|
||||
|
||||
public ABogus(string platform = null)
|
||||
{
|
||||
_registers = (uint[])_reg.Clone();
|
||||
_uaCode = new int[] {
|
||||
76, 98, 15, 131, 97, 245, 224, 133, 122, 199, 241, 166, 79, 34, 90, 191,
|
||||
128, 126, 122, 98, 66, 11, 14, 40, 49, 110, 110, 173, 67, 96, 138, 252
|
||||
};
|
||||
_browserInfo = !string.IsNullOrEmpty(platform) ? GenerateBrowserInfo(platform) : _browser;
|
||||
_browserLength = _browserInfo.Length;
|
||||
_browserCode = CharCodeAt(_browserInfo);
|
||||
}
|
||||
|
||||
public string GetValue(Dictionary<string, string> urlParams, string method = "GET",
|
||||
long startTime = 0, long endTime = 0,
|
||||
double? randomNum1 = null, double? randomNum2 = null, double? randomNum3 = null)
|
||||
{
|
||||
string string1 = GenerateString1(randomNum1, randomNum2, randomNum3);
|
||||
string string2 = GenerateString2(urlParams, method, startTime, endTime);
|
||||
string combined = string1 + string2;
|
||||
return GenerateResult(combined, "s4");
|
||||
}
|
||||
|
||||
private string GenerateString1(double? randomNum1 = null, double? randomNum2 = null, double? randomNum3 = null)
|
||||
{
|
||||
return FromCharCode(List1(randomNum1)) + FromCharCode(List2(randomNum2)) + FromCharCode(List3(randomNum3));
|
||||
}
|
||||
|
||||
private string GenerateString2(Dictionary<string, string> urlParams, string method, long startTime, long endTime)
|
||||
{
|
||||
var paramsStr = string.Join("&", urlParams.Select(kv => $"{kv.Key}={kv.Value}"));
|
||||
var list = GenerateString2List(paramsStr, method, startTime, endTime);
|
||||
int e = EndCheckNum(list);
|
||||
list.AddRange(_browserCode);
|
||||
list.Add(e);
|
||||
return RC4Encrypt(FromCharCode(list.ToArray()), "y");
|
||||
}
|
||||
|
||||
private int[] List1(double? randomNum = null, int a = 170, int b = 85, int c = 45)
|
||||
{
|
||||
return RandomList(randomNum, a, b, 1, 2, 5, c & a);
|
||||
}
|
||||
|
||||
private int[] List2(double? randomNum = null, int a = 170, int b = 85)
|
||||
{
|
||||
return RandomList(randomNum, a, b, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
private int[] List3(double? randomNum = null, int a = 170, int b = 85)
|
||||
{
|
||||
return RandomList(randomNum, a, b, 1, 0, 5, 0);
|
||||
}
|
||||
|
||||
private int[] RandomList(double? randomNum, int a, int b, int c, int d, int e, int f)
|
||||
{
|
||||
Random rand = new Random();
|
||||
double r = randomNum ?? rand.NextDouble() * 10000;
|
||||
int[] v = {
|
||||
(int)r,
|
||||
(int)r & 255,
|
||||
(int)r >> 8
|
||||
};
|
||||
int[] result = {
|
||||
v[1] & a | c,
|
||||
v[1] & b | d,
|
||||
v[2] & a | e,
|
||||
v[2] & b | f
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string FromCharCode(params int[] codes)
|
||||
{
|
||||
return string.Join("", codes.Select(c => (char)c));
|
||||
}
|
||||
|
||||
private static int[] CharCodeAt(string s)
|
||||
{
|
||||
return s.Select(c => (int)c).ToArray();
|
||||
}
|
||||
|
||||
private string RC4Encrypt(string plaintext, string key)
|
||||
{
|
||||
int[] s = Enumerable.Range(0, 256).ToArray();
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
j = (j + s[i] + key[i % key.Length]) % 256;
|
||||
(s[i], s[j]) = (s[j], s[i]);
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
j = 0;
|
||||
var cipher = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < plaintext.Length; i++)
|
||||
{
|
||||
x = (x + 1) % 256;
|
||||
j = (j + s[x]) % 256;
|
||||
(s[x], s[j]) = (s[j], s[x]);
|
||||
int t = (s[x] + s[j]) % 256;
|
||||
cipher.Append((char)(s[t] ^ plaintext[i]));
|
||||
}
|
||||
|
||||
return cipher.ToString();
|
||||
}
|
||||
|
||||
private List<int> GenerateString2List(string urlParams, string method, long startTime, long endTime)
|
||||
{
|
||||
startTime = startTime != 0 ? startTime : DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
endTime = endTime != 0 ? endTime : startTime + new Random().Next(4, 8);
|
||||
|
||||
var paramsArray = GenerateParamsCode(urlParams);
|
||||
var methodArray = GenerateMethodCode(method);
|
||||
|
||||
return new List<int> {
|
||||
44,
|
||||
(int)(endTime >> 24) & 255,
|
||||
0, 0, 0, 0,
|
||||
24,
|
||||
paramsArray[21],
|
||||
methodArray[21],
|
||||
0,
|
||||
_uaCode[23],
|
||||
(int)(endTime >> 16) & 255,
|
||||
0, 0, 0, 1,
|
||||
0,
|
||||
239,
|
||||
paramsArray[22],
|
||||
methodArray[22],
|
||||
_uaCode[24],
|
||||
(int)(endTime >> 8) & 255,
|
||||
(int)(endTime >> 0) & 255,
|
||||
0, 0, 0, 0,
|
||||
(int)(startTime >> 24) & 255,
|
||||
0, 0, 14,
|
||||
(int)(startTime >> 16) & 255,
|
||||
(int)(startTime >> 8) & 255,
|
||||
0,
|
||||
(int)(startTime >> 0) & 255,
|
||||
3,
|
||||
(int)(endTime / 256 / 256 / 256 / 256) >> 0,
|
||||
1,
|
||||
(int)(startTime / 256 / 256 / 256 / 256) >> 0,
|
||||
1,
|
||||
_browserLength,
|
||||
0, 0, 0
|
||||
};
|
||||
}
|
||||
|
||||
private int[] GenerateParamsCode(string paramsStr)
|
||||
{
|
||||
return Sm3ToArray(Sm3ToArray(paramsStr + _endString));
|
||||
}
|
||||
|
||||
private int[] GenerateMethodCode(string method)
|
||||
{
|
||||
return Sm3ToArray(Sm3ToArray(method + _endString));
|
||||
}
|
||||
|
||||
// ... 其他成员和初始化代码 ...
|
||||
|
||||
public int[] Sm3ToArray(string data)
|
||||
{
|
||||
byte[] input = Encoding.UTF8.GetBytes(data);
|
||||
byte[] hash = SM3Hash(input);
|
||||
return hash.Select(b => (int)b).ToArray();
|
||||
}
|
||||
|
||||
public int[] Sm3ToArray(int[] data)
|
||||
{
|
||||
byte[] input = data.SelectMany(BitConverter.GetBytes).ToArray();
|
||||
//byte[] input = Encoding.UTF8.GetBytes(data);
|
||||
byte[] hash = SM3Hash(input);
|
||||
return hash.Select(b => (int)b).ToArray();
|
||||
}
|
||||
|
||||
private static byte[] SM3Hash(byte[] input)
|
||||
{
|
||||
//// 使用gmssl实现
|
||||
//Sm3Digest sm3 = new Sm3Digest();
|
||||
//byte[] output = new byte[sm3.GetDigestSize()];
|
||||
//sm3.BlockUpdate(input, 0, input.Length);
|
||||
//sm3.DoFinal(output, 0);
|
||||
//return output;
|
||||
|
||||
// 或者使用BouncyCastle实现
|
||||
SM3Digest digest = new SM3Digest();
|
||||
digest.BlockUpdate(input, 0, input.Length);
|
||||
byte[] output = new byte[digest.GetDigestSize()];
|
||||
digest.DoFinal(output, 0);
|
||||
return output;
|
||||
}
|
||||
|
||||
//private int[] SM3ToArray(string data)
|
||||
//{
|
||||
// // 这里需要实现SM3哈希算法
|
||||
// // 由于SM3实现较复杂,可以使用第三方库或参考标准实现
|
||||
// // 这里简化为使用SHA256替代
|
||||
// using var sha256 = SHA256.Create();
|
||||
// byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
// return hash.Select(b => (int)b).ToArray();
|
||||
//}
|
||||
|
||||
private int EndCheckNum(List<int> list)
|
||||
{
|
||||
int r = 0;
|
||||
foreach (var i in list)
|
||||
{
|
||||
r ^= i;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private string GenerateBrowserInfo(string platform)
|
||||
{
|
||||
Random rand = new Random();
|
||||
int innerWidth = rand.Next(1280, 1920);
|
||||
int innerHeight = rand.Next(720, 1080);
|
||||
int outerWidth = rand.Next(innerWidth, 1920);
|
||||
int outerHeight = rand.Next(innerHeight, 1080);
|
||||
int screenX = 0;
|
||||
int screenY = rand.Next(0, 1) == 0 ? 0 : 30;
|
||||
|
||||
return string.Join("|", new object[] {
|
||||
innerWidth, innerHeight,
|
||||
outerWidth, outerHeight,
|
||||
screenX, screenY,
|
||||
0, 0,
|
||||
outerWidth, outerHeight,
|
||||
outerWidth, outerHeight,
|
||||
innerWidth, innerHeight,
|
||||
24, 24,
|
||||
platform
|
||||
});
|
||||
}
|
||||
|
||||
private string GenerateResult(string s, string e)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < s.Length; i += 3)
|
||||
{
|
||||
int n;
|
||||
if (i + 2 < s.Length)
|
||||
{
|
||||
n = (s[i] << 16) | (s[i + 1] << 8) | s[i + 2];
|
||||
}
|
||||
else if (i + 1 < s.Length)
|
||||
{
|
||||
n = (s[i] << 16) | (s[i + 1] << 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = s[i] << 16;
|
||||
}
|
||||
|
||||
for (int j = 18; j >= 0; j -= 6)
|
||||
{
|
||||
int mask = j == 18 ? 0xFC0000 :
|
||||
j == 12 ? 0x03F000 :
|
||||
j == 6 ? 0x0FC0 : 0x3F;
|
||||
|
||||
if (j == 6 && i + 1 >= s.Length) break;
|
||||
if (j == 0 && i + 2 >= s.Length) break;
|
||||
|
||||
result.Append(_str[e][(n & mask) >> j]);
|
||||
}
|
||||
}
|
||||
|
||||
int padding = (4 - result.Length % 4) % 4;
|
||||
result.Append('=', padding);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class DySyncBaseParamDics
|
||||
{
|
||||
public static Dictionary<string, string> CollectParams { get; } = InitializeUserCollecParams();
|
||||
public static Dictionary<string, string> FavoriteParams { get; } = InitializeUserFavoriteParams();
|
||||
public static Dictionary<string, string> UpderPostParams { get; } = InitializeUpderPostParams();
|
||||
|
||||
private static Dictionary<string, string> InitializeUserCollecParams()
|
||||
{
|
||||
var parameters = GetDyBaseParameters();
|
||||
|
||||
// 覆盖与基础字段不同的值
|
||||
parameters["version_code"] = "290100";
|
||||
parameters["version_name"] = "29.1.0";
|
||||
parameters["screen_width"] = "1920";
|
||||
parameters["screen_height"] = "1080";
|
||||
parameters["browser_version"] = "130.0.0.0";
|
||||
parameters["engine_version"] = "130.0.0.0";
|
||||
parameters["cpu_core_num"] = "12";
|
||||
|
||||
// 添加当前字段特有的键值对
|
||||
parameters["from_user_page"] = "1";
|
||||
parameters["locate_query"] = "false";
|
||||
parameters["need_time_list"] = "1";
|
||||
parameters["show_live_replay_strategy"] = "1";
|
||||
parameters["time_list_query"] = "0";
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// 初始化用户收藏参数
|
||||
private static Dictionary<string, string> InitializeUserFavoriteParams()
|
||||
{
|
||||
var parameters = GetDyBaseParameters();
|
||||
|
||||
// 覆盖与基础字段不同的值
|
||||
parameters["version_code"] = "170400";
|
||||
parameters["version_name"] = "17.4.0";
|
||||
parameters["screen_width"] = "1536";
|
||||
parameters["screen_height"] = "960";
|
||||
parameters["browser_version"] = "140.0.0.0";
|
||||
parameters["engine_version"] = "140.0.0.0";
|
||||
parameters["cpu_core_num"] = "20";
|
||||
|
||||
// 添加当前字段特有的键值对
|
||||
parameters["min_cursor"] = "0";
|
||||
parameters["cut_version"] = "1";
|
||||
parameters["count"] = "18";
|
||||
parameters["support_h265"] = "1";
|
||||
parameters["support_dash"] = "1";
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// 初始化抖音博主发布作品参数
|
||||
private static Dictionary<string,string> InitializeUpderPostParams()
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "WebIdLastTime", "1714385892" },
|
||||
{ "aid", "1988" },
|
||||
{ "app_language", "zh-Hans" },
|
||||
{ "app_name", "tiktok_web" },
|
||||
{ "browser_language", "zh-CN" },
|
||||
{ "browser_name", "Mozilla" },
|
||||
{ "browser_online", "true" },
|
||||
{ "browser_platform", "Win32" },
|
||||
{ "browser_version", "5.0%20%28Windows%29" },
|
||||
{ "channel", "tiktok_web" },
|
||||
{ "cookie_enabled", "true" },
|
||||
{ "count", "18" },
|
||||
{ "coverFormat", "2" },
|
||||
{ "cursor", "0" },
|
||||
{ "data_collection_enabled", "true" },
|
||||
{ "device_id", "7380187414842836523" },
|
||||
{ "device_platform", "web_pc" },
|
||||
{ "focus_state", "true" },
|
||||
{ "from_page", "user" },
|
||||
{ "history_len", "3" },
|
||||
{ "is_fullscreen", "false" },
|
||||
{ "is_page_visible", "true" },
|
||||
{ "language", "zh-Hans" },
|
||||
{ "locate_item_id", "" },
|
||||
{ "needPinnedItemIds", "true" },
|
||||
{ "odinId", "7404669909585003563" },
|
||||
{ "os", "windows" },
|
||||
{ "post_item_list_request_type", "0" },
|
||||
{ "priority_region", "US" },
|
||||
{ "referer", "" },
|
||||
{ "region", "US" },
|
||||
{ "screen_height", "827" },
|
||||
{ "screen_width", "1323" },
|
||||
{ "secUid", "" },
|
||||
{ "tz_name", "America%2FLos_Angeles" },
|
||||
{ "user_is_login", "true" },
|
||||
{ "webcast_language", "zh-Hans" },
|
||||
{ "msToken", "" },
|
||||
{ "_signature", "_02B4Z6wo000017oyWOQAAIDD9xNhTSnfaDu6MFxAAIlj23" },
|
||||
{"sec_user_id",""}
|
||||
};
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// 静态基础参数(供内部初始化使用)
|
||||
private static Dictionary<string, string> GetDyBaseParameters()
|
||||
{
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
{"device_platform", "webapp"},
|
||||
{"aid", "6383"},
|
||||
{"channel", "channel_pc_web"},
|
||||
{"pc_client_type", "1"},
|
||||
{"cookie_enabled", "true"},
|
||||
{"browser_language", "zh-CN"},
|
||||
{"browser_platform", "Win32"},
|
||||
{"browser_name", "Chrome"},
|
||||
{"browser_online", "true"},
|
||||
{"engine_name", "Blink"},
|
||||
{"os_name", "Windows"},
|
||||
{"os_version", "10"},
|
||||
{"device_memory", "8"},
|
||||
{"platform", "PC"},
|
||||
{"downlink", "10"},
|
||||
{"effective_type", "4g"},
|
||||
{"pc_libra_divert", "Windows"},
|
||||
{"publish_video_strategy_type", "2"},
|
||||
{"round_trip_time", "0"},
|
||||
{"whale_cut_token", ""},
|
||||
{"update_version_code", "170400"}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+94
-28
@@ -1,4 +1,5 @@
|
||||
using dy.net.dto;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace dy.net.utils
|
||||
@@ -8,44 +9,109 @@ namespace dy.net.utils
|
||||
/// </summary>
|
||||
public class NfoFileGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据视频信息生成NFO文件
|
||||
/// </summary>
|
||||
/// <param name="videoInfo">视频信息对象</param>
|
||||
/// <param name="filePath">保存NFO文件的路径</param>
|
||||
|
||||
public static void GenerateNfoFile(VideoNfo videoInfo, string filePath)
|
||||
{
|
||||
if (videoInfo == null)
|
||||
throw new ArgumentNullException(nameof(videoInfo));
|
||||
try
|
||||
{
|
||||
if (videoInfo == null)
|
||||
throw new ArgumentNullException(nameof(videoInfo));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentException("文件路径不能为空", nameof(filePath));
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentException("文件路径不能为空", nameof(filePath));
|
||||
|
||||
// 创建根元素
|
||||
XElement root = new XElement("movie"); // 对于电影使用"movie",电视剧可以使用"tvshow"
|
||||
// 创建根元素
|
||||
XElement root = new XElement("movie");
|
||||
|
||||
// 添加视频信息
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Title))
|
||||
root.Add(new XElement("title", videoInfo.Title));
|
||||
// 添加视频信息(先清理无效字符)
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Title))
|
||||
root.Add(new XElement("title", CleanInvalidXmlChars(videoInfo.Title)));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Author))
|
||||
root.Add(new XElement("author", videoInfo.Author));
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Author))
|
||||
root.Add(new XElement("author", CleanInvalidXmlChars(videoInfo.Author)));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Thumbnail))
|
||||
root.Add(new XElement("thumb", new XAttribute("aspect", "poster"), videoInfo.Thumbnail));
|
||||
// 发布时间(无需清理,因为是格式化的日期字符串)
|
||||
if (videoInfo.ReleaseDate.HasValue)
|
||||
root.Add(new XElement("releasedate", videoInfo.ReleaseDate.Value.ToString("yyyy-MM-dd")));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Poster))
|
||||
root.Add(new XElement("fanart",
|
||||
new XElement("thumb", videoInfo.Poster)
|
||||
));
|
||||
// 分类标签(清理每个标签)
|
||||
if (videoInfo.Genres != null && videoInfo.Genres.Any())
|
||||
{
|
||||
foreach (var genre in videoInfo.Genres)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(genre))
|
||||
root.Add(new XElement("genre", CleanInvalidXmlChars(genre)));
|
||||
}
|
||||
}
|
||||
|
||||
// 创建XDocument并保存
|
||||
XDocument doc = new XDocument(
|
||||
new XDeclaration("1.0", "utf-8", "yes"),
|
||||
root
|
||||
);
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Thumbnail))
|
||||
root.Add(new XElement("thumb", new XAttribute("aspect", "poster"), CleanInvalidXmlChars(videoInfo.Thumbnail)));
|
||||
|
||||
doc.Save(filePath);
|
||||
if (!string.IsNullOrWhiteSpace(videoInfo.Poster))
|
||||
root.Add(new XElement("fanart",
|
||||
new XElement("thumb", CleanInvalidXmlChars(videoInfo.Poster))
|
||||
));
|
||||
|
||||
// 创建XDocument并保存
|
||||
XDocument doc = new XDocument(
|
||||
new XDeclaration("1.0", "utf-8", "yes"),
|
||||
root
|
||||
);
|
||||
|
||||
doc.Save(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error($"生成 {videoInfo?.Title ?? "未知视频"}, NFO文件时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理字符串中无效的XML字符(包括无效的UTF-16代理对)
|
||||
/// </summary>
|
||||
private static string CleanInvalidXmlChars(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
StringBuilder cleaned = new StringBuilder();
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
char c = input[i];
|
||||
// XML 1.0 允许的字符范围:#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
||||
if (IsValidXmlChar(c))
|
||||
{
|
||||
// 检查是否是高代理项,如果是则需要配对低代理项
|
||||
if (char.IsHighSurrogate(c))
|
||||
{
|
||||
// 确保不越界,且下一个字符是低代理项
|
||||
if (i + 1 < input.Length && char.IsLowSurrogate(input[i + 1]))
|
||||
{
|
||||
cleaned.Append(c); // 高代理项
|
||||
cleaned.Append(input[i + 1]); // 低代理项
|
||||
i++; // 跳过已处理的低代理项
|
||||
}
|
||||
// 否则,高代理项无效,跳过
|
||||
}
|
||||
else
|
||||
{
|
||||
cleaned.Append(c);
|
||||
}
|
||||
}
|
||||
// 无效字符直接跳过
|
||||
}
|
||||
return cleaned.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断字符是否为XML允许的有效字符
|
||||
/// </summary>
|
||||
private static bool IsValidXmlChar(char c)
|
||||
{
|
||||
return (c == 0x9) || (c == 0xA) || (c == 0xD)
|
||||
|| (c >= 0x20 && c <= 0xD7FF)
|
||||
|| (c >= 0xE000 && c <= 0xFFFD)
|
||||
|| (c >= 0x10000 && c <= 0x10FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user