diff --git a/.github/workflows/json-validator.yml b/.github/workflows/json-validator.yml index a726236d040..01911cc1730 100644 --- a/.github/workflows/json-validator.yml +++ b/.github/workflows/json-validator.yml @@ -13,9 +13,9 @@ jobs: - uses: actions/checkout@v4 - name: json-yaml-validate - uses: GrantBirki/json-yaml-validate@v3.0.0 + uses: GrantBirki/json-yaml-validate@v5 id: json-yaml-validate with: json_schema: Pagetual/pagetual.schema.json files: | - Pagetual/pagetualRules.json \ No newline at end of file + Pagetual/pagetualRules.json diff --git a/BingBgForBaidu/BingBgForBaidu.user.js b/BingBgForBaidu/BingBgForBaidu.user.js index 6c86b1050dd..ba0fa832396 100644 --- a/BingBgForBaidu/BingBgForBaidu.user.js +++ b/BingBgForBaidu/BingBgForBaidu.user.js @@ -2,7 +2,7 @@ // @name 百Bing图 // @name:en BingBgForBaidu // @namespace hoothin -// @version 2.3.44 +// @version 2.3.45 // @description 给百度首页换上Bing的背景图,并添加背景图链接与日历组件 // @description:en Just change the background image of baidu.com to bing.com // @author hoothin @@ -124,7 +124,10 @@ } }; var skinContainer=document.getElementsByTagName("body")[0]; - GM_addStyle(".s-news-rank-content{max-height: 180px; width: 99%; overflow-y: auto; overflow-x: hidden;}.s-top-right .ai-entry-right-nologin,.s-top-right .operate-wrapper-nologin{right:362px;}.hot-refresh{padding-bottom:7px;}.hot-title>div,.hot-refresh{border-radius: 3px 3px 0 0}.s-hotsearch-title>a,.s-hotsearch-title>a>div{padding: 5px;background-color: #f0f8ff95;border-radius: 5px;}.s-hotsearch-content{position: absolute; background-color: #f0f8ff95; border-radius: 5px;padding: 5px;}.s_ipt{margin:0!important;}.s_ipt_wr{border-radius: 10px 4px 4px 10px;border-radius: 10px 0 0 10px;background: #fff!important;}#qrcodeCon{display:none}body{position:fixed;_position:absolute;top:0;left:0;height:100%;width:100%;min-width:1000px;z-index:-10;background-position:center 0;background-repeat:no-repeat;background-size:cover;-webkit-background-size:cover;-o-background-size:cover;zoom:1;}"); + if (!document.querySelector(".aigc-skin-bg,#s_mancard_main")) { + GM_addStyle(".s-news-rank-content{max-height: 180px; width: 99%; overflow-y: auto; overflow-x: hidden;}.s-top-right .ai-entry-right-nologin,.s-top-right .operate-wrapper-nologin{right:362px;}.hot-refresh{padding-bottom:7px;}.hot-title>div,.hot-refresh{border-radius: 3px 3px 0 0}.s-hotsearch-title>a,.s-hotsearch-title>a>div{padding: 5px;background-color: #f0f8ff95;border-radius: 5px;}.s-hotsearch-content{position: absolute; background-color: #f0f8ff95; border-radius: 5px;padding: 5px;}"); + } + GM_addStyle(".s_ipt{margin:0!important;}.s_ipt_wr{border-radius: 10px 4px 4px 10px;border-radius: 10px 0 0 10px;background: #fff!important;}#qrcodeCon{display:none}body{position:fixed;_position:absolute;top:0;left:0;height:100%;width:100%;min-width:1000px;z-index:-10;background-position:center 0;background-repeat:no-repeat;background-size:cover;-webkit-background-size:cover;-o-background-size:cover;zoom:1;}"); var inputsu=document.querySelector("input#su"); var clickHandler=e=>{ if(skinContainer)skinContainer.style.backgroundImage=""; diff --git a/DownloadAllContent/DownloadAllContent.user.js b/DownloadAllContent/DownloadAllContent.user.js index 4df64b84efc..de600e6dc11 100644 --- a/DownloadAllContent/DownloadAllContent.user.js +++ b/DownloadAllContent/DownloadAllContent.user.js @@ -4,7 +4,7 @@ // @name:zh-TW 怠惰小説下載器 // @name:ja 怠惰小説ダウンローダー // @namespace hoothin -// @version 2.8.3.18 +// @version 2.8.3.19 // @description Lightweight web scraping script. Fetch and download main textual content from the current page, provide special support for novels // @description:zh-CN 通用网站内容爬虫抓取工具,可批量抓取任意站点的小说、论坛内容等并保存为TXT文档 // @description:zh-TW 通用網站內容爬蟲抓取工具,可批量抓取任意站點的小說、論壇內容等並保存為TXT文檔 @@ -1536,10 +1536,10 @@ if (window.top != window.self) { function getPageContent(doc, cb, url){ if(!doc)return i18n.error; - if(doc.body && !doc.body.children.length)return doc.body.innerText; if(processFunc){ return processFunc(doc, cb, url); } + if(doc.body && !doc.body.children.length)return doc.body.innerText; [].forEach.call(doc.querySelectorAll("span,div,ul"),function(item){ var thisStyle=doc.defaultView?doc.defaultView.getComputedStyle(item):item.style; if(thisStyle && (thisStyle.display=="none" || (item.nodeName=="SPAN" && thisStyle.fontSize=="0px"))){ diff --git a/DownloadAllContent/README.md b/DownloadAllContent/README.md index 46df4e75dbd..f5de4823ebd 100644 --- a/DownloadAllContent/README.md +++ b/DownloadAllContent/README.md @@ -104,6 +104,10 @@ ``` javascript main>section ul>li div>a@@@@@@var noval=JSON.parse(doc.querySelector("#meta-preload-data").content).novel;noval[Object.keys(noval)[0]].content; ``` + 新規則 + ``` javascript +main>section ul>li div>a@@novel/show\.php\?id=@@ajax/novel/@@data.json().body.content; + ``` + [📕紅薯中文網](https://g.hongshu.com/chapterlist/91735.do) > 這個站沒有目錄連結,此時可以遍歷標籤自己創建目錄連結下載 ``` javascript diff --git a/Pagetual/README.md b/Pagetual/README.md index b41b3d04643..40854710e73 100644 --- a/Pagetual/README.md +++ b/Pagetual/README.md @@ -1,8 +1,8 @@ -[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.125](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") +[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.131](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version") == *Pagetual - Perpetual pages. Auto loading paginated web pages for 90% of all web sites !* -🔧CONFIGURATION PAGE +🔧CONFIGURATION PAGE
@@ -30,10 +30,7 @@ https://raw.githubusercontent.com/hoothin/UserScripts/master/Pagetual/pagetualRu
Send 📧email
- Made with ❤️ by Hoothin
-
-
- 
+ ![]()
Made with ❤️ by Hoothin
diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js
index 32c0e0c844c..ae4b3ad94f3 100644
--- a/Pagetual/pagetual.user.js
+++ b/Pagetual/pagetual.user.js
@@ -31,7 +31,7 @@
// @name:da Pagetual
// @name:fr-CA Pagetual
// @namespace hoothin
-// @version 1.9.37.125
+// @version 1.9.37.132
// @description Perpetual pages - powerful auto-pager script. Auto fetching next paginated web pages and inserting into current page for infinite scroll. Support thousands of web sites without any rule.
// @description:zh-CN 终极自动翻页 - 加载并拼接下一分页内容至当前页尾,智能适配任意网页
// @description:zh-TW 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁
@@ -137,6 +137,9 @@
if (document.body && getComputedStyle(document.body).display === 'none') {
document.body.style.display = 'block';
}
+ Element.prototype.scrollIntoView = function() {
+ console.log('ScrollIntoView blocked.');
+ };
return;
}
@@ -153,7 +156,7 @@
}
const noRuleTest = false;
- const lang = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage;
+ var langName = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage;
const langData = [
{
// English translation update by github.com/https433, admin@abby0666.xyz.
@@ -4135,6 +4138,7 @@
});
var i18nData = langData[0].lang;
function setLang(la) {
+ langName = la;
for (let i = 0; i < langData.length; i++) {
let lang = langData[i];
if (lang && lang.match.indexOf(la) !== -1) {
@@ -4148,7 +4152,7 @@
}
}
}
- setLang(lang);
+ setLang(langName);
var enableDebug = true;
var _GM_xmlhttpRequest, _GM_registerMenuCommand, _GM_notification, _GM_addStyle, _GM_openInTab, _GM_info, _GM_setClipboard;
function i18n(name, param) {
@@ -4165,12 +4169,199 @@
}
}
+ function requestWithFetch(f, onFetchError) {
+ function getHeaderValue(headers, name) {
+ if (!headers || !name) return "";
+ let lowerName = String(name).toLowerCase();
+ if (typeof Headers !== "undefined" && headers instanceof Headers) {
+ return headers.get(lowerName) || "";
+ }
+ if (typeof headers === "object") {
+ for (let key in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, key) && String(key).toLowerCase() === lowerName) {
+ return headers[key] || "";
+ }
+ }
+ }
+ return "";
+ }
+ function extractCharsetFromContentType(contentType) {
+ if (!contentType || typeof contentType !== "string") return "";
+ let match = contentType.match(/charset\s*=\s*["']?([^;"'\s]+)/i);
+ return match && match[1] ? match[1].trim() : "";
+ }
+ function decodeArrayBufferByCharset(buffer, preferredCharset) {
+ let bytes = new Uint8Array(buffer);
+ let decoderList = [];
+ let normalize = label => String(label || "").trim().toLowerCase();
+ let addAlias = (name, aliases) => {
+ if (aliases.indexOf(normalizedPreferred) !== -1) {
+ pushDecoder(name);
+ for (let i = 0; i < aliases.length; i++) {
+ pushDecoder(aliases[i]);
+ }
+ }
+ };
+ let pushDecoder = label => {
+ let raw = String(label || "").trim();
+ if (raw && decoderList.indexOf(raw) === -1) {
+ decoderList.push(raw);
+ }
+ };
+ let normalizedPreferred = normalize(preferredCharset).replace(/["']/g, "");
+ pushDecoder(preferredCharset);
+ pushDecoder(normalizedPreferred);
+ pushDecoder(normalizedPreferred.replace(/_/g, "-"));
+ pushDecoder(normalizedPreferred.replace(/-/g, "_"));
+ addAlias("shift_jis", ["shiftjis", "shift-jis", "sjis", "ms_kanji", "windows-31j", "cp932", "ms932"]);
+ addAlias("euc-jp", ["eucjp"]);
+ addAlias("iso-2022-jp", ["iso2022jp", "jis"]);
+ addAlias("gb18030", ["gbk", "gb2312", "x-gbk", "cp936", "ms936", "windows-936"]);
+ addAlias("big5", ["big-5", "cn-big5", "x-x-big5"]);
+ addAlias("euc-kr", ["euckr", "ks_c_5601-1987", "ksc5601", "windows-949", "cp949"]);
+ addAlias("windows-1251", ["cp1251"]);
+ addAlias("windows-1252", ["cp1252", "iso-8859-1", "latin1", "latin-1"]);
+ pushDecoder("utf-8");
+ for (let i = 0; i < decoderList.length; i++) {
+ try {
+ return new TextDecoder(decoderList[i]).decode(bytes);
+ } catch (e) {}
+ }
+ try {
+ return new TextDecoder().decode(bytes);
+ } catch (e) {
+ return "";
+ }
+ }
+ function detectCharsetFromHtmlHead(buffer) {
+ if (!buffer || !buffer.byteLength) return "";
+ let scanLen = Math.min(buffer.byteLength, 16384);
+ let bytes = new Uint8Array(buffer, 0, scanLen);
+ let ascii = "";
+ for (let i = 0; i < bytes.length; i++) {
+ let code = bytes[i];
+ ascii += code < 128 ? String.fromCharCode(code) : " ";
+ }
+ let charsetMatch = ascii.match(/]*charset\s*=\s*["']?\s*([a-zA-Z0-9._-]+)/i);
+ if (!charsetMatch) {
+ charsetMatch = ascii.match(/]*content\s*=\s*["'][^"']*charset\s*=\s*([a-zA-Z0-9._-]+)/i);
+ }
+ return charsetMatch && charsetMatch[1] ? charsetMatch[1].trim() : "";
+ }
+ function isUtf8Charset(label) {
+ let normalized = String(label || "").trim().toLowerCase().replace(/_/g, "-");
+ return !normalized || normalized === "utf-8" || normalized === "utf8";
+ }
+ if (!f || !f.url || typeof fetch === "undefined") {
+ if (onFetchError) {
+ onFetchError({error: "Fetch not available"});
+ } else if (f && f.onerror) {
+ f.onerror({error: "Fetch not available"});
+ }
+ return;
+ }
+ let method = (f.method || "GET").toUpperCase();
+ let timeoutId = null;
+ let controller = typeof AbortController !== "undefined" ? new AbortController() : null;
+ if (f.timeout > 0 && controller) {
+ timeoutId = setTimeout(() => {
+ controller.abort();
+ }, f.timeout);
+ }
+ let options = {
+ method: method,
+ headers: f.headers || {}
+ };
+ if (controller) options.signal = controller.signal;
+ if (method !== "GET" && method !== "HEAD" && typeof f.data !== "undefined") {
+ options.body = f.data;
+ }
+ fetch(f.url, options).then(async response => {
+ if (timeoutId) clearTimeout(timeoutId);
+ let responseCharset = extractCharsetFromContentType(response.headers.get("content-type") || "");
+ let overrideCharset = extractCharsetFromContentType(f.overrideMimeType || "");
+ let requestCharset = extractCharsetFromContentType(getHeaderValue(options.headers, "content-type"));
+ let text;
+ let charsetFromHeaders = responseCharset || overrideCharset || requestCharset;
+ if (charsetFromHeaders) {
+ if (isUtf8Charset(charsetFromHeaders)) {
+ text = await response.text();
+ } else {
+ text = decodeArrayBufferByCharset(await response.arrayBuffer(), charsetFromHeaders);
+ }
+ } else {
+ let rawBuffer = await response.arrayBuffer();
+ let metaCharset = detectCharsetFromHtmlHead(rawBuffer);
+ let targetCharset = metaCharset || charset || "utf-8";
+ if (isUtf8Charset(targetCharset)) {
+ text = new TextDecoder("utf-8").decode(new Uint8Array(rawBuffer));
+ } else {
+ text = decodeArrayBufferByCharset(rawBuffer, targetCharset);
+ }
+ }
+ let headers = "";
+ response.headers.forEach((value, key) => {
+ headers += key + ": " + value + "\r\n";
+ });
+ if (f.onload) {
+ f.onload({
+ response: text,
+ responseText: text,
+ status: response.status,
+ statusText: response.statusText,
+ finalUrl: response.url,
+ responseHeaders: headers
+ });
+ }
+ }).catch(error => {
+ if (timeoutId) clearTimeout(timeoutId);
+ if (error && error.name === "AbortError" && f.ontimeout) {
+ f.ontimeout(error);
+ return;
+ }
+ if (onFetchError) {
+ onFetchError(error);
+ } else if (f.onerror) {
+ f.onerror(error);
+ }
+ });
+ }
+ function isSameOriginRequest(f) {
+ if (!f || !f.url || typeof location === "undefined") return false;
+ try {
+ return new URL(f.url, location.href).origin === location.origin;
+ } catch (e) {
+ return false;
+ }
+ }
+ let nativeGMRequest = null;
if (typeof GM_xmlhttpRequest !== 'undefined') {
- _GM_xmlhttpRequest = GM_xmlhttpRequest;
+ nativeGMRequest = GM_xmlhttpRequest;
} else if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
- _GM_xmlhttpRequest = GM.xmlHttpRequest;
+ nativeGMRequest = GM.xmlHttpRequest;
+ }
+ if (nativeGMRequest) {
+ _GM_xmlhttpRequest = function(f) {
+ if (!f) return nativeGMRequest(f);
+ if (isSameOriginRequest(f)) {
+ requestWithFetch(f);
+ return;
+ }
+ let originalOnerror = f.onerror;
+ let request = Object.assign({}, f);
+ request.onerror = function(gmError) {
+ requestWithFetch(f, function(fetchError) {
+ if (originalOnerror) {
+ originalOnerror(fetchError || gmError || {error: "Request failed"});
+ }
+ });
+ };
+ return nativeGMRequest(request);
+ };
} else {
- _GM_xmlhttpRequest = (f) => {fetch(f.url, {method: f.method || 'GET', body: f.data, headers: f.headers}).then(response => response.text()).then(data => {f.onload && f.onload({response: data})}).catch(f.onerror && f.onerror())};
+ _GM_xmlhttpRequest = function(f) {
+ requestWithFetch(f);
+ };
}
if (typeof GM_registerMenuCommand !== 'undefined') {
_GM_registerMenuCommand = GM_registerMenuCommand;
@@ -4207,7 +4398,7 @@
} else {
_GM_addStyle = cssStr => {
let styleEle = document.createElement("style");
- styleEle.innerHTML = cssStr;
+ setHTML(styleEle, cssStr);
document.head.appendChild(styleEle);
return styleEle;
};
@@ -4314,12 +4505,15 @@
});
}
const isMobile = ('ontouchstart' in document.documentElement && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
- const configPage = [`https://pagetual.hoothin.com/${lang === 'zh-CN' ? 'cn/' : ''}rule.html`,
+ const cnConfigPage = "https://pagetual.hoothin.com/cn/rule.html";
+ const configPage = ["https://pagetual.hoothin.com/rule.html",
"https://github.com/hoothin/UserScripts/tree/master/Pagetual",
"https://hoothin.github.io/UserScripts/Pagetual/"];
const firstRunPage = "https://pagetual.hoothin.com/firstRun";
+ const wedataRulesUrl = "http://wedata.net/databases/AutoPagerize/items_all.json";
+ const wedataMirrorRulesUrl = "https://hoothin.github.io/UserScripts/Pagetual/items_all.json";
const guidePage = /^https?:\/\/.*pagetual.*rule\.html/i;
- const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/438684(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Pagetual|issues)|^https:\/\/pagetual\.hoothin\.com\/.*firstRun\.html/i;
+ const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/438684(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Pagetual|issues)|^https:\/\/pagetual\.hoothin\.com\/.*first(Run|-run)\.html/i;
const allOfBody = "body>*";
const mainSel = ["article,.article","[role=main],main,.main,#main","#results"];
const nextTextReg1 = new RegExp("\u005e\u7ffb\u003f\u005b\u4e0b\u540e\u5f8c\u6b21\u005d\u005b\u4e00\u30fc\u2500\u0031\u005d\u003f\u005b\u9875\u9801\u5f20\u5f35\u005d\u007c\u005e\u006e\u0065\u0078\u0074\u005b\u005c\u0073\u005f\u002d\u005d\u003f\u0070\u0061\u0067\u0065\u005c\u0073\u002a\u005b\u203a\u003e\u2192\u00bb\u005d\u003f\u0024\u007c\u6b21\u306e\u30da\u30fc\u30b8\u007c\u005e\u6b21\u3078\u0024\u007c\u0412\u043f\u0435\u0440\u0435\u0434\u007c\u005e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435", "i");
@@ -4560,139 +4754,211 @@
return segs.length ? '/' + segs.join('/') : null;
}
- function parseTrustedTypes(cspString) {
- const policies = new Set();
- let allowDuplicates = false;
- let ttDirectiveFound = false;
- const ttRegex = /trusted-types\s+([^;]+)/gi;
+ function createHTML(html, doc) {
+ const targetDoc = doc || document;
+ const fragment = targetDoc.createDocumentFragment();
+ if (html === null || html === undefined || html === '') return fragment;
+ parseHTMLToFragment(String(html), fragment, targetDoc);
+ return fragment;
+ }
+ let canDirectSetHTML = true;
+ let canPolicySetHTML = true;
+ let escapeHTMLPolicy;
+ let escapeHTMLCreator;
+ const MY_POLICY_NAME = 'pagetual_default';
+ const SVG_NS = 'http://www.w3.org/2000/svg';
+ const VOID_TAGS = {
+ area: true,
+ base: true,
+ br: true,
+ col: true,
+ embed: true,
+ hr: true,
+ img: true,
+ input: true,
+ link: true,
+ meta: true,
+ param: true,
+ source: true,
+ track: true,
+ wbr: true
+ };
+ const RAW_TEXT_TAGS = {
+ script: true,
+ style: true,
+ textarea: true,
+ title: true,
+ xmp: true,
+ plaintext: true,
+ noscript: true
+ };
+ const HTML_ENTITIES = {
+ amp: '&',
+ lt: '<',
+ gt: '>',
+ quot: '"',
+ apos: "'",
+ nbsp: '\u00A0'
+ };
+ function decodeEntities(text) {
+ return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, function(_, code) {
+ if (code[0] === '#') {
+ const isHex = code[1] === 'x' || code[1] === 'X';
+ const num = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
+ if (!isNaN(num)) {
+ try { return String.fromCodePoint(num); } catch(e) {}
+ }
+ return '&' + code + ';';
+ }
+ const key = code.toLowerCase();
+ return (key in HTML_ENTITIES) ? HTML_ENTITIES[key] : '&' + code + ';';
+ });
+ }
+ function parseHTMLToFragment(html, fragment, doc) {
+ const stack = [fragment];
+ const tokenRe = /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
let match;
-
- while ((match = ttRegex.exec(cspString)) !== null) {
- ttDirectiveFound = true;
-
- const policyNames = match[1].trim().split(/\s+/);
- for (const name of policyNames) {
- if (name === "'allow-duplicates'") {
- allowDuplicates = true;
- } else if (name !== "'none'") {
- policies.add(name.replace(/'/g, ''));
- }
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
}
- }
- return { names: policies, allowDuplicates: allowDuplicates, ttDirectiveFound: ttDirectiveFound };
- }
-
- async function getCspTrustedTypesInfo() {
- const combinedPolicies = new Set();
- let combinedAllowDuplicates = false;
- let combinedTtDirectiveFound = false;
-
- const meta = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
- if (meta) {
- const metaResult = parseTrustedTypes(meta.content);
- metaResult.names.forEach(name => combinedPolicies.add(name));
- if (metaResult.allowDuplicates) {
- combinedAllowDuplicates = true;
+ if (token.indexOf('|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
+ let match;
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
+ }
+ if (token.indexOf('|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
+ let match;
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
+ }
+ if (token.indexOf('0){
- if(char_f[x])processChar=char_f[x]+processChar;
+ if (!char_f[x]) break;
+ processChar=char_f[x]+processChar;
+ x++;
}
x=0;
while(badd-->0){
- if(char_b[x])processChar+=char_b[x];
+ if (!char_b[x]) break;
+ processChar+=char_b[x];
+ x++;
}
if(processChar.indexOf(curOther) != -1){
newChar=otherChar;
@@ -944,11 +944,15 @@
var curOther=others[k],fadd=curOther.indexOf(char),badd=curOther.length-1-fadd,x=0;
var processChar=char;
while(fadd-->0){
- if(char_f[x])processChar=char_f[x]+processChar;
+ if (!char_f[x]) break;
+ processChar=char_f[x]+processChar;
+ x++;
}
x=0;
while(badd-->0){
- if(char_b[x])processChar+=char_b[x];
+ if (!char_b[x]) break;
+ processChar+=char_b[x];
+ x++;
}
if(processChar.indexOf(curOther) != -1){
newChar=otherChar;
@@ -1122,6 +1126,17 @@
_unsafeWindow.tc2sc = simplized;
_unsafeWindow.sc2tc = traditionalized;
+ window.addEventListener("message", function(event) {
+ if (event.data && event.data.type === "switchChineseRequest") {
+ const receivedData = event.data.payload;
+ const result = receivedData.target === 'sc' ? simplized(receivedData.str) : traditionalized(receivedData.str);
+ window.postMessage({
+ type: "switchChineseResult",
+ payload: result
+ }, "*");
+ }
+ });
+
var storage = {
supportGM: typeof GM_getValue == 'function' && typeof GM_getValue('a', 'b') != 'undefined',
supportGMPromise: typeof GM != 'undefined' && typeof GM.getValue == 'function' && typeof GM.getValue('a','b') != 'undefined',
@@ -1217,14 +1232,14 @@
curLang = isSimple;
}
curLang=!curLang;
- activeEle.innerHTML=curLang?traditionalized(activeEle.innerHTML):simplized(activeEle.innerHTML);
- activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value);
+ activeEle.innerHTML=curLang?simplized(activeEle.innerHTML):traditionalized(activeEle.innerHTML);
+ activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value);
}else if("INPUT"==activeEle.nodeName.toUpperCase()){
if (curInput != activeEle) {
curLang = isSimple;
}
curLang=!curLang;
- activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value);
+ activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value);
}else{
var selecter;
if(window.getSelection()){
@@ -1916,7 +1931,7 @@
if(i==value.length-1){
newTree={"end":key};
if(branch){
- branch.end=value;
+ branch.end=key;
}
}
if(branch){
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/package.json b/Switch Traditional Chinese and Simplified Chinese/lib/package.json
index 623ba02340f..489310114ed 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/package.json
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/package.json
@@ -1,7 +1,7 @@
{
"name": "switch-chinese",
- "version": "1.0.10",
- "description": "簡繁轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。",
+ "version": "1.0.16",
+ "description": "繁簡轉換,支援簡繁雙向轉換、智慧分詞、自訂詞庫、文字偵測及多種輸出格式,零依賴。 Lightweight Chinese converter library for conversion between Simplified and Traditional Chinese. 轻量级简繁体中文智能转换库,支持简繁双向转换、智能分词、自定义词库、文本检测及多种输出格式,零依赖。",
"main": "stcasc.lib.js",
"types": "stcasc.d.ts",
"type": "module",
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
index 5e61732887f..670521a96db 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
@@ -1,5 +1,7 @@
# switch-chinese
+[Online Demo](https://tool.hoothin.com/chinese-converter)
+
[简体中文](#简体中文) | [繁體中文](#繁體中文) | [English](#english)
---
@@ -14,6 +16,7 @@
- **轻量级**:零依赖,体积小,性能优异
- **智能转换**:支持基于词组的智能分词和「一简多繁」精准转换
+- **多种数据类型**:支持字符串、数组、对象的转换,自动递归处理嵌套结构
- **自定义词库**:允许用户自定义简繁转换词汇
- **缓存机制**:支持字典缓存,避免重复初始化
- **简繁检测**:自动检测文本是简体中文、繁体中文还是未知类型
@@ -82,6 +85,54 @@ ChineseType 枚举值:
- `ChineseType.TRADITIONAL` (1): 繁体中文
- `ChineseType.UNKNOWN` (2): 未知类型
+#### 转换数组和对象
+
+库支持转换数组和对象中的所有字符串,非字符串值保持原样:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// 转换数组
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// 输出: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// 转换对象
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// 输出: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// 转换嵌套结构
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// 所有字符串属性值都会被转换,数字等其他类型保持不变
+```
+
### 高级用法
#### 使用缓存优化性能
@@ -186,8 +237,14 @@ OutputFormat 枚举值:
返回包含以下方法的对象:
-- `traditionalized(text, options?)`: 将简体中文转换为繁体中文
-- `simplized(text, options?)`: 将繁体中文转换为简体中文
+- `traditionalized(input, options?)`: 将简体中文转换为繁体中文
+ - `input`: 可以是字符串、数组或对象
+ - 字符串:直接转换返回新字符串
+ - 数组:转换所有字符串元素,其他类型保持不变
+ - 对象:递归转换所有字符串属性值,其他类型保持不变
+- `simplized(input, options?)`: 将繁体中文转换为简体中文
+ - `input`: 可以是字符串、数组或对象
+ - 支持的数据类型同 `traditionalized`
- `detect(text)`: 检测文本的中文类型,返回 ChineseType 枚举值
- `cache`: 字典缓存对象
@@ -318,6 +375,7 @@ Lightweight Chinese converter library for bidirectional conversion between Simpl
- **Lightweight**: Zero dependencies, small footprint, excellent performance
- **Intelligent Conversion**: Context-aware word segmentation and accurate one-to-many character mapping
+- **Multiple Data Types**: Supports string, array, and object conversion with automatic recursive processing
- **Custom Dictionary**: User-defined conversion rules support
- **Caching Mechanism**: Dictionary caching to avoid repeated initialization
- **Text Detection**: Automatic detection of Simplified Chinese, Traditional Chinese, or unknown text
@@ -386,6 +444,54 @@ ChineseType enumeration values:
- `ChineseType.TRADITIONAL` (1): Traditional Chinese
- `ChineseType.UNKNOWN` (2): Unknown type
+#### Converting Arrays and Objects
+
+The library supports converting all strings in arrays and objects, while preserving non-string values:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// Convert array
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// Output: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// Convert object
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// Output: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// Convert nested structures
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// All string property values will be converted, other types remain unchanged
+```
+
### Advanced Usage
#### Performance Optimization with Caching
@@ -490,8 +596,14 @@ Main function to create a converter instance.
An object containing the following methods:
-- `traditionalized(text, options?)`: Convert Simplified Chinese to Traditional Chinese
-- `simplized(text, options?)`: Convert Traditional Chinese to Simplified Chinese
+- `traditionalized(input, options?)`: Convert Simplified Chinese to Traditional Chinese
+ - `input`: Can be a string, array, or object
+ - String: Directly converts and returns a new string
+ - Array: Converts all string elements, other types remain unchanged
+ - Object: Recursively converts all string property values, other types remain unchanged
+- `simplized(input, options?)`: Convert Traditional Chinese to Simplified Chinese
+ - `input`: Can be a string, array, or object
+ - Supports the same data types as `traditionalized`
- `detect(text)`: Detect Chinese text type, returns ChineseType enumeration value
- `cache`: Dictionary cache object
@@ -622,6 +734,7 @@ Chinese Converter, Simplified Chinese, Traditional Chinese, Chinese Translation,
- **輕量級**:零依賴,體積小,效能優異
- **智能轉換**:支援基於詞組的智能分詞和「一簡多繁」精準轉換
+- **多種資料類型**:支援字串、陣列、物件的轉換,自動遞迴處理巢狀結構
- **自訂詞庫**:允許使用者自訂簡繁轉換詞彙
- **快取機制**:支援字典快取,避免重複初始化
- **簡繁檢測**:自動檢測文字是簡體中文、繁體中文還是未知類型
@@ -690,6 +803,54 @@ ChineseType 列舉值:
- `ChineseType.TRADITIONAL` (1): 繁體中文
- `ChineseType.UNKNOWN` (2): 未知類型
+#### 轉換陣列和物件
+
+函式庫支援轉換陣列和物件中的所有字串,非字串值保持原樣:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// 轉換陣列
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// 輸出: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// 轉換物件
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// 輸出: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// 轉換巢狀結構
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// 所有字串屬性值都會被轉換,數字等其他類型保持不變
+```
+
### 進階用法
#### 使用快取最佳化效能
@@ -794,8 +955,14 @@ OutputFormat 列舉值:
回傳包含以下方法的物件:
-- `traditionalized(text, options?)`: 將簡體中文轉換為繁體中文
-- `simplized(text, options?)`: 將繁體中文轉換為簡體中文
+- `traditionalized(input, options?)`: 將簡體中文轉換為繁體中文
+ - `input`: 可以是字串、陣列或物件
+ - 字串:直接轉換並回傳新字串
+ - 陣列:轉換所有字串元素,其他類型保持不變
+ - 物件:遞迴轉換所有字串屬性值,其他類型保持不變
+- `simplized(input, options?)`: 將繁體中文轉換為簡體中文
+ - `input`: 可以是字串、陣列或物件
+ - 支援的資料類型同 `traditionalized`
- `detect(text)`: 檢測文字的中文類型,回傳 ChineseType 列舉值
- `cache`: 字典快取物件
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts
index a1826ffbc2b..350324519b8 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts
@@ -33,12 +33,20 @@ export interface ConversionOptions {
format?: 0 | 1 | 2;
}
+/**
+ * Tree node for combination dictionary
+ */
+interface CombTreeNode {
+ end?: string;
+ [key: string]: CombTreeNode | string | undefined;
+}
+
/**
* Cache object for storing conversion dictionaries
*/
export interface ConversionCache {
- sc2tcCombTree?: Record;
- tc2scCombTree?: Record;
+ sc2tcCombTree?: Record;
+ tc2scCombTree?: Record;
stDict?: Record;
tsDict?: Record;
}
@@ -56,20 +64,52 @@ export type CustomDictionary = Record;
export interface StcascConverter {
/**
* Convert traditional Chinese to simplified Chinese
- * @param text - Text to convert
+ * @param text - String to convert
* @param options - Conversion options
- * @returns Converted simplified Chinese text
+ * @returns Converted simplified Chinese string
*/
simplized(text: string, options?: ConversionOptions): string;
+ /**
+ * Convert traditional Chinese to simplified Chinese (array version)
+ * @param data - Array to convert (converts all strings in the array)
+ * @param options - Conversion options
+ * @returns Array with converted strings
+ */
+ simplized(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Convert traditional Chinese to simplified Chinese (object version)
+ * @param data - Object to convert (converts all string property values)
+ * @param options - Conversion options
+ * @returns Object with converted string values
+ */
+ simplized>(data: T, options?: ConversionOptions): T;
+
/**
* Convert simplified Chinese to traditional Chinese
- * @param text - Text to convert
+ * @param text - String to convert
* @param options - Conversion options
- * @returns Converted traditional Chinese text
+ * @returns Converted traditional Chinese string
*/
traditionalized(text: string, options?: ConversionOptions): string;
+ /**
+ * Convert simplified Chinese to traditional Chinese (array version)
+ * @param data - Array to convert (converts all strings in the array)
+ * @param options - Conversion options
+ * @returns Array with converted strings
+ */
+ traditionalized(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Convert simplified Chinese to traditional Chinese (object version)
+ * @param data - Object to convert (converts all string property values)
+ * @param options - Conversion options
+ * @returns Object with converted string values
+ */
+ traditionalized>(data: T, options?: ConversionOptions): T;
+
/**
* Detect Chinese text type
* @param text - Text to detect
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
index f45a1c0867e..2256dc7222a 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
@@ -96,10 +96,6 @@ const sc2tc = {
'栗',
['慄','战栗','颤栗','不寒而栗']
],
- '凄':[
- '淒',
- ['悽','凄厉','凄惨','悲凄','凄苦']
- ],
'沈':[
'沈',
['瀋','沈阳']
@@ -333,7 +329,7 @@ const sc2tc = {
'干':[
'幹',
['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'],
- ['干','干系','天干','干涉','干扰','干戈','相干']
+ ['干','干系','天干','干涉','干扰','干戈','相干','干我什','干我事','干我的事','干你什','干你事','干你的事','干他什','干他事','干他的事','干她什','干她事','干她的事']
],
'了':[
'了',
@@ -622,10 +618,7 @@ let sc2tcComb = {
var stDict = {}, tsDict = {};
var sc2tcCombTree = {}, tc2scCombTree = {};
-function traditionalized(orgStr, options) {
- options = options || {};
- const format = options.format !== undefined ? options.format : OutputFormat.NORMAL;
-
+function traditionalizedString(orgStr, format) {
if (!orgStr) return "";
var str = '', char;
for (var i = 0; i < orgStr.length; i++) {
@@ -684,11 +677,15 @@ function traditionalized(orgStr, options) {
var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0;
var processChar = char;
while (fadd-- > 0) {
- if (char_f[x]) processChar = char_f[x] + processChar;
+ if (!char_f[x]) break;
+ processChar = char_f[x] + processChar;
+ x++;
}
x = 0;
while (badd-- > 0) {
- if (char_b[x]) processChar += char_b[x];
+ if (!char_b[x]) break;
+ processChar += char_b[x];
+ x++;
}
if (processChar.indexOf(curOther) != -1) {
newChar = otherChar;
@@ -716,10 +713,39 @@ function traditionalized(orgStr, options) {
return str;
}
-function simplized(orgStr, options) {
+function traditionalized(input, options) {
options = options || {};
const format = options.format !== undefined ? options.format : OutputFormat.NORMAL;
+ // Handle null/undefined
+ if (input == null) return input;
+
+ // Handle string
+ if (typeof input === 'string') {
+ return traditionalizedString(input, format);
+ }
+
+ // Handle array
+ if (Array.isArray(input)) {
+ return input.map(item => traditionalized(item, options));
+ }
+
+ // Handle object
+ if (typeof input === 'object') {
+ const result = {};
+ for (const key in input) {
+ if (input.hasOwnProperty(key)) {
+ result[key] = traditionalized(input[key], options);
+ }
+ }
+ return result;
+ }
+
+ // Return other types as-is (number, boolean, etc.)
+ return input;
+}
+
+function simplizedString(orgStr, format) {
if (!orgStr) return "";
var str = '', char;
for (var i = 0; i < orgStr.length; i++) {
@@ -778,11 +804,15 @@ function simplized(orgStr, options) {
var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0;
var processChar = char;
while (fadd-- > 0) {
- if (char_f[x]) processChar = char_f[x] + processChar;
+ if (!char_f[x]) break;
+ processChar = char_f[x] + processChar;
+ x++;
}
x = 0;
while (badd-- > 0) {
- if (char_b[x]) processChar += char_b[x];
+ if (!char_b[x]) break;
+ processChar += char_b[x];
+ x++;
}
if (processChar.indexOf(curOther) != -1) {
newChar = otherChar;
@@ -810,6 +840,38 @@ function simplized(orgStr, options) {
return str;
}
+function simplized(input, options) {
+ options = options || {};
+ const format = options.format !== undefined ? options.format : OutputFormat.NORMAL;
+
+ // Handle null/undefined
+ if (input == null) return input;
+
+ // Handle string
+ if (typeof input === 'string') {
+ return simplizedString(input, format);
+ }
+
+ // Handle array
+ if (Array.isArray(input)) {
+ return input.map(item => simplized(item, options));
+ }
+
+ // Handle object
+ if (typeof input === 'object') {
+ const result = {};
+ for (const key in input) {
+ if (input.hasOwnProperty(key)) {
+ result[key] = simplized(input[key], options);
+ }
+ }
+ return result;
+ }
+
+ // Return other types as-is (number, boolean, etc.)
+ return input;
+}
+
function detect(text) {
if (!text) return ChineseType.UNKNOWN;
@@ -875,7 +937,7 @@ function stcasc(cache, custom, disableTerms) {
if (i == value.length - 1) {
newTree = {"end": key};
if (branch) {
- branch.end = value;
+ branch.end = key;
}
}
if (branch) {
@@ -923,4 +985,4 @@ function stcasc(cache, custom, disableTerms) {
return {simplized, traditionalized, detect, cache};
}
-export default stcasc;
\ No newline at end of file
+export default stcasc;
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/test.js b/Switch Traditional Chinese and Simplified Chinese/lib/test.js
index e62bdfd428d..55337889052 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/test.js
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/test.js
@@ -111,4 +111,105 @@ console.log('输入:', mixed);
console.log('输出:', mixedTc);
console.log('');
+// ========== 新增测试:Array/Object 支持 ==========
+console.log('========== Array/Object 测试 ==========\n');
+
+// 测试 14: 转换数组(简体->繁体)
+console.log('测试 14: 转换数组(简体->繁体)');
+const arr1 = ['简体中文', '软件', '硬盘', 123, true, null];
+const arr1Tc = traditionalized(arr1);
+console.log('输入:', arr1);
+console.log('输出:', arr1Tc);
+console.log('');
+
+// 测试 15: 转换数组(繁体->简体)
+console.log('测试 15: 转换数组(繁体->简体)');
+const arr2 = ['繁體中文', '軟體', '硬碟', 456, false];
+const arr2Sc = simplized(arr2);
+console.log('输入:', arr2);
+console.log('输出:', arr2Sc);
+console.log('');
+
+// 测试 16: 转换对象(简体->繁体)
+console.log('测试 16: 转换对象(简体->繁体)');
+const obj1 = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const obj1Tc = traditionalized(obj1);
+console.log('输入:', JSON.stringify(obj1, null, 2));
+console.log('输出:', JSON.stringify(obj1Tc, null, 2));
+console.log('');
+
+// 测试 17: 转换对象(繁体->简体)
+console.log('测试 17: 转换对象(繁体->简体)');
+const obj2 = {
+ title: '繁體中文標題',
+ description: '這是一個繁體中文描述',
+ price: 99.99,
+ items: ['軟體', '硬碟']
+};
+const obj2Sc = simplized(obj2);
+console.log('输入:', JSON.stringify(obj2, null, 2));
+console.log('输出:', JSON.stringify(obj2Sc, null, 2));
+console.log('');
+
+// 测试 18: 转换嵌套数组
+console.log('测试 18: 转换嵌套数组');
+const nestedArr = [
+ '简体中文',
+ ['软件', '硬盘'],
+ [['网络', '服务器']]
+];
+const nestedArrTc = traditionalized(nestedArr);
+console.log('输入:', JSON.stringify(nestedArr));
+console.log('输出:', JSON.stringify(nestedArrTc));
+console.log('');
+
+// 测试 19: 转换嵌套对象
+console.log('测试 19: 转换嵌套对象');
+const nestedObj = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedObjTc = traditionalized(nestedObj);
+console.log('输入:', JSON.stringify(nestedObj, null, 2));
+console.log('输出:', JSON.stringify(nestedObjTc, null, 2));
+console.log('');
+
+// 测试 20: 数组转换 - BRACKET 格式
+console.log('测试 20: 数组转换 - BRACKET 格式');
+const arr3 = ['简体', '中文'];
+const arr3Bracket = traditionalized(arr3, { format: OutputFormat.BRACKET });
+console.log('输入:', arr3);
+console.log('输出:', arr3Bracket);
+console.log('');
+
+// 测试 21: 对象转换 - RUBY 格式
+console.log('测试 21: 对象转换 - RUBY 格式');
+const obj3 = {
+ text1: '简体',
+ text2: '中文'
+};
+const obj3Ruby = traditionalized(obj3, { format: OutputFormat.RUBY });
+console.log('输入:', JSON.stringify(obj3, null, 2));
+console.log('输出:', JSON.stringify(obj3Ruby, null, 2));
+console.log('');
+
+// 测试 22: 处理 null 和 undefined
+console.log('测试 22: 处理 null 和 undefined');
+console.log('null 输入:', traditionalized(null));
+console.log('undefined 输入:', traditionalized(undefined));
+console.log('包含 null 的数组:', traditionalized(['简体', null, '中文']));
+console.log('');
+
console.log('========== 测试完成 ==========');