// ==UserScript==
// @name 组卷网 (学科网) 试卷处理
// @version 1.0.1
// @description 排版组卷网试卷,使其处于可以打印状态
// @author HiRoger0001
// @match https://zujuan.xkw.com/*
// @icon https://zujuan.xkw.com/favicon.ico
// @grant GM_notification
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11
// @namespace https://zujuan.xkw.com/
// ==/UserScript==
const imgMaps = {
"3845f266f2826ada0b825caba49c64b5": `a`,
"0a6936d370d6a238a608ca56f87198de": `a`,
// "99a4658ab94306d39aedda17deed7216": "a = ",
"2c94bb12cee76221e13f9ef955b0aab1": "b",
"14a9ff0a382e947c7b5473c3d8154dd7": "b",
"071a7e733d466949ac935b4b8ee8d183": "c",
"5c02bc0c74292b1e8f395f90935d3174": "d",
"99a4658ab94306d39aedda17deed7216": "d = ",
"7c98c59cd4749afdd21e73529fc84323": "d = ",
"168b3e4b1d6f04226fa2687a72a268b4": "e",
"041a7c8fc017f596542c5e6ec7d1c40b": "e",
// "14a9ff0a382e947c7b5473c3d8154dd7": "f",
"276509f01529d982ab21e479a4619268": "g",
"3eabd5f3a86afe49dcd70571e2b96cfd": "h",
// "071a7e733d466949ac935b4b8ee8d183": "i",
// "071a7e733d466949ac935b4b8ee8d183": "j",
"f0a532e15e232cb4b99a8d4d07c89575": "k",
"0f85fca60a11e1af2bf50138d0e3fe62": "l",
"294f5ba74cdf695fc9a8a8e52f421328": "m",
"b6a24198bd04c29321ae5dc5a28fe421": "n",
"0f3cb8d72bb2e281b943b3b430138ef7": "n = ",
// "071a7e733d466949ac935b4b8ee8d183": "o",
"b1010846eeec6c9da29640f5aa3f8738": "p",
"df5be1440d099f464ef46dee39de6010": "p = ",
"9aa8a716a31b0f51b70fdf9bdb257909": "q",
"11bc05f41215f9894e11d1df0465751a": "r",
"c5873c01192b7d33b7483f444f90b5b0": "s",
"36a1b09c653185842513e24ebba60bb3": "t",
// "071a7e733d466949ac935b4b8ee8d183": "u",
"bc13a607ac0c7f76d252d7cb1bb040fd": "v",
// "071a7e733d466949ac935b4b8ee8d183": "w",
"81dea63b8ce3e51adf66cf7b9982a248": "x",
"d053b14c8588eee2acbbe44fc37a6886": "y",
"e81e59019989b7dc2fb59b037ef6e010": "z",
"5963abe8f421bd99a2aaa94831a951e9": "A",
"7f9e8449aad35c5d840a3395ea86df6d": "B",
"c5db41a1f31d6baee7c69990811edb9f": "C",
"8455657dde27aabe6adb7b188e031c11": "D",
"2a30f3a8b673cc28bd90c50cf1a35281": "E",
"a0ed1ec316bc54c37c4286c208f55667": "F",
"895dc3dc3a6606ff487a4c4863e18509": "G",
"73465a1f9aa03481295bf6bd3c6903ac": "H",
"e105760638b22b26ff8bec4354255e4c": "I",
// "e105760638b22b26ff8bec4354255e4c": "J",
"cf3834d7ec7531f3c3c0ce9b286f7a49": "K",
"0c88d9142df6ba8e43c1a93bd04a1362": "L",
"ac047e91852b91af639feec23a9598b2": "M",
"54a5d7d3b6b63fe5c24c3907b7a8eaa3": "N",
"1dde8112e8eb968fd042418dd632759e": "O",
"dad2a36927223bd70f426ba06aea4b45": "P",
"acc290b44635265137fdf13146b6a6d9": "Q",
"4aa0df7f1e45f9de29e802c7f19a4f64": "R",
"cf231f8f86fb922df4ca0c87f044cec3": "S",
"a454a2c91ace8ef50412a6adf91f8086": "S",
"0b68df477b3ee45ac0f725db00d465a1": "T",
"b52b4f24969673c863b5aff4fb6751ce": "U",
"be54e84508decfcce6d2fcbe6c8c1a92": "V",
"478abdd84506a8ef759e353a238db6c9": `V`,
"91edc7e2d4811f5ea6c01284cf00393a": "W",
"f022950e0faa45b617d497b01b5292b9": "X",
"54a829fdd8ec0f3b7ede883cf2c3e53b": "Y",
// "e105760638b22b26ff8bec4354255e4c": "S",
"bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
"e170f206fdbbd834aad7580c727e2cc6": "α",
"5b5858ee1ce52b251816757257a11c29": "β",
"074c228ffc7b1e306f8410afe7bc4b5c": "ω",
"c24095e409b025db711f14be783a406c": "θ",
"1c0ad7e7853a069537387b5192f73844": "σ",
"70f5389990c3a0c5373f3bd9fb2454c9": "π",
"ed2d1ecae9c649cc3c89f9ce0c063208": "π",
"35e2d7c958e99bcd9d7f251c19ee3544": "2π",
"6581916f5a65edfea257c804efee007e": "φ",
"171102a883b22fe6ca578efc8926f5b8": "ρ",
"1100379a4385b9ce064847bc21760adc": "μ",
"06b09bec6122ff51834335344ae13f29": "ν",
"df64046e91b047037f19e4032e3b6de3": "λ",
// "06b09bec6122ff51834335344ae13f29": "α",
// "06b09bec6122ff51834335344ae13f29": "α",
// "06b09bec6122ff51834335344ae13f29": "α",
// "06b09bec6122ff51834335344ae13f29": "α",
// "06b09bec6122ff51834335344ae13f29": "α",
// "1c0ad7e7853a069537387b5192f73844": "α",
"f52a58fbaf4fea03567e88a9f0f6e37e": "AB",
"01c74a907dda6bb7d9d56d009d9df253": "A, B",
"b79dd200766db27fb90d6bd1992cf658": "AB",
"9d78abbad68bbbf12af10cd40ef4c353": "CD",
"0dc5c9827dfd0be5a9c85962d6ccbfb1": "BC",
"60ef95894ceebaf236170e8832dcf7e3": "AC",
"20a541b81584a032f571159ea152c85a": "AP",
"7bef5239ddbb0972700ce01daf9ee7cf": "ABC",
"15c0dbe3c080c4c4636c64803e5c1f76": "△ABC",
"411b38a18046fea8e9fab1f9f9b80a5f": "ABCD",
"01e04a5b59cced25ab7057d03fe3c0ad": "BCD",
"2777840758e70e7dbbc18cef8f3d6d2b": "AA1",
// "2777840758e70e7dbbc18cef8f3d6d2b": "BB1",
"9d88bf46ad08f9677c37eed1d0369329": "CC1",
"22adbc0da438220f9cace11b629d799b": "DD1",
"11ddc92d84d188c66b435664a7e7b5a4": "A1B1",
"394c5d2f55221975503be8aa18022480": "A1D1",
"e168672b47d7e64dc1b404f8882c7dcf": "BCC1B1",
"2d9a8181f7a7fe7f3fac872ce9534f15": "ACC1A1",
"5ebb05874eb3353d754af24c9974273e": "ADD1A1",
"6e09725691ee7851f54c0dee86b2bf55": "ABCD – A1B1C1D1",
"42d3a82b8e587ee890467835bc4e854c": "ABC – A1B1C1",
"411461db15ee8086332c531e086c40c7": "MN",
"7a5f1641947153c80b987320885a2b57": "PQ",
"a43b2faa4f81f32d94612dce724e772b": "R", // 粗体!
"7ac87434324956e4145e38ad92a1aa95": "R", // 粗体!
"d41042207515dd2e8349c805e6aee400": `z`, // 共轭复数
// "09f86f37ec8e15846bd731ab4fcdbacd": `f ( x )`,
// "942c2141d01bde6b48210c56a17fc75e": `y = f ( x )`,
// "4fe7d5809da02c15a43a0e9a898b9086": `f ( x )`,
// "51c530f4b7491b95acb8ce3eef9aa09d": `y = f ( x )`,
"7ee31829d0d4d5f779a957d7df8058ab": "xOy",
"c3e5af20b2f8c1fba4470f9650989e51": "Ox",
"acbc6a613224461ade69362d46550474": "–1",
// "0585b6c0f156eecf9662b9846d4eb693": "P – ABCD",
// "20a541b81584a032f571159ea152c85a": "AP",
// "20a541b81584a032f571159ea152c85a": "AP",
// "20a541b81584a032f571159ea152c85a": "AP",
// "20a541b81584a032f571159ea152c85a": "AP",
// "20a541b81584a032f571159ea152c85a": "AP",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
// "bdaa19de263700a15fcf213d64a8cd57": "1",
"e72adb45c60c2f63b46e65ff787302bf": "a1",
"3e88093a749c0d46e0ee931ecfaff925": "a2",
"6c1ccc6c74b8754e9bcbb3f39a11b6f1": "a3",
// "e72adb45c60c2f63b46e65ff787302bf": "a1",
"80b53eab97158937f92039c1e133b0f1": "h1",
"f285174fbf90a9742de57c1e53224cff": "h2",
"e8e8854159e7443b5fe53436bf9e367f": "h3",
"2e9b0f5f44abbc6544a2f672b025b013": "l1",
"3f6f17bc385bafb37e8f964e5eb99cd0": "l2",
"77ab1256702aef4e9f1a5eb6c12ecc96": `m1`,
"1fbd67f60f04c278bdd867fdb3979dfb": `m2`,
// "1fbd67f60f04c278bdd867fdb3979dfb": `m2`,
// "27c44826e58f11a58d3a6c233fc5df2d": `n1`,
// "27c44826e58f11a58d3a6c233fc5df2d": `n1`,
// "27c44826e58f11a58d3a6c233fc5df2d": `n1`,
"27c44826e58f11a58d3a6c233fc5df2d": `n1`,
"215b1424b299b737554386b090af8316": `n2`,
"d70f2244d643d2393618288de6c13e6e": `n3`,
// "0b428e5d2e2f7ecbe7e83b1eee336278": `q0`,
"56b2087eadbbd0dab72b7483b9181585": "+q",
"0b428e5d2e2f7ecbe7e83b1eee336278": `q0`,
// "0b428e5d2e2f7ecbe7e83b1eee336278": `q1`,
// "0b428e5d2e2f7ecbe7e83b1eee336278": `q2`,
"a2833ddbe58a6f4e7585c69c698f0d2a": "r0",
"0d9fd58e71dcae6cafaf9037d20ebd76": `t0`,
"87c7eb49a823f757461cd5260757b088": `t1`,
// "0d9fd58e71dcae6cafaf9037d20ebd76": `t0`,
"6f58888df91890a19a1aa7511d19703f": `v0`,
"f44c235d8b49207ad3f2d77dc5d6cf20": `v1`,
"814f55724f7ee57bd395eb3b95393c68": `v2`,
// "478abdd84506a8ef759e353a238db6c9": `t0`,
// "478abdd84506a8ef759e353a238db6c9": `t0`,
// "478abdd84506a8ef759e353a238db6c9": `t0`,
"d23d96b9e0d40658127563525193bde4": `vm`,
"fc4eeb9cd408e73eb33347083df8cf33": `vx`,
"e9d18c7a49171a8b0df7e0741ffe1cc0": `vy`,
"79b752f0f189e5d8666daea73e145dff": `x0`, // x_0
"c814128ea2139e33db94ea590e7c2223": `x1`,
"aec19b68e3add9d5bfcc6269a1855b87": `x2`,
"291c25fc6a69d6d0ccfb8d839b9b4462": `x3`,
"8ce7ae90d808f05e86ea063238e4b2f9": `x1, x2`,
"26d8dafc71b106f39f4e15442220897b": `x1 < x2`,
"2210f152080d9a68a97c805f5c1cde96": `x1 > x2`,
"93c54705d32dc6820f1a90eec2225dcf": `x2 – x1`,
// "2210f152080d9a68a97c805f5c1cde96": `x3`,
// "97c01fdc7bc471af0b264a04aef0823e": `B1`,
"024e2379c58191758f8bd7602a6bcb9f": `A1`,
"b34a75c2a392f235c5f07b91d9fb58d5": `A2`,
"76f1eb87ce12491e171c43b238a6ecf7": `A3`,
"c41d793c851a2f72f787913ba23e459c": `B0`,
"97c01fdc7bc471af0b264a04aef0823e": `B1`,
"43a71fc9c0068109dad1382354570665": `B2`,
"b1241216f3c1cb5e73043dd1037f556d": "C1",
"b628d87cb667a0a31766a88c6c426324": "E0",
"522230546d4b802094e86ceb48c2ba38": "E1",
"0b4f150ab98bde511e0f65d9bafab031": "E2",
"8b15fa17c704f1988d059ba69a7ce304": "Ek",
"ef1c7229e345be3a4857b91d27126c88": "Ek",
"c6c6eb14380e627b6d3194192d234d4c": "ΔE",
"31c972b26b45c4ba54c6f169557d4080": "ΔE",
"b519e5794ef9932b64715619adf860db": "F0",
"f5076289823db419f94e9c0c8f4aafd9": "F1",
"a3fb78c5f885034612c0e030b920143d": "F2",
"84abbfb85a00f48b5db11eeb76b8d134": "I0",
"2f1ac49b4139636fb1809fe970b23a87": "I1",
"2d1a0fd1ad044a9ecfcba672779bd678": "I2",
// "ef1c7229e345be3a4857b91d27126c88": "I0",
"172722d11ea7e01411fa06dbb82f46ee": "L1",
"9fbd49bf20f987c05b4d36e31549075c": "L2",
"87f93f4e10d9672fa6bd67243bc23d4a": "L3",
"23f919bd3dde10dbbc076f7ec5149699": "O1",
"3c4f6f74444b2b7947fc6e35c8d62322": "O2",
"ce2581ae160692cd7e2686226fe5e2c6": "Rx",
"9d784ac902fe86e7bdcdd3fa5cc3ba83": "Rg",
"be9b4a83b9aebebf29de0c4406ebf894": "R0",
"9efc18a5bb2e53586331b2a58538a48b": "R1",
"19f20f21a9d50b61dac519a3ddab539d": "R2",
"bb9a33203d5812270ca911b3502c923e": "R1",
"9c69fdea31c367d1f7e7bbd245d1ed56": "R2",
"5a2c4640b2bee411935f588fc5433c74": "R3",
// "172722d11ea7e01411fa06dbb82f46ee": "L1",
// "5a2c4640b2bee411935f588fc5433c74": "R3",
// "5a2c4640b2bee411935f588fc5433c74": "R3",
"54f562eb3c2a45d65cba066d712825a5": "S0",
"e097c8d4c948de063796bd19f85b3a9a": "S1",
"1e0bd63f55069a3bc870915010b39225": "S2",
"6899bf9cadae2ccdb14cbc87d4f280ee": "S3",
"0f30f56664446f32dbbc2c5f12a99374": "S4",
"08eb71ecf8d733b6932f4680874dbbf3": "Sn",
"635ccd929471d564cc9d2d96266b34d1": "T0",
// "dbe5cb81b72e94f92d2aaecd553daac2": "U0",
// "dbe5cb81b72e94f92d2aaecd553daac2": "U0",
"dbe5cb81b72e94f92d2aaecd553daac2": "U0",
// "dbe5cb81b72e94f92d2aaecd553daac2": "U0",
// "dbe5cb81b72e94f92d2aaecd553daac2": "U0",
"a2e01e75a7ca05585d6d8e6207004f7c": "V0",
"1c9e573ec93341e027774f3575846952": "V1",
"9d9c8a02f5ec8bbbc2f8cb0c4952a39c": "V2",
"00caa3b28e26231108f7c81bca82bc2b": "ω0",
};
const additionalStyles = /*css*/`
/* 页面显示部分 */
.ai-entry.fixed, .aside-pop.activity-btn {
display: none !important;
}
/* #zujuanjs-reformatted-content { background: #fff; } */
html > body {
overflow: auto !important;
}
.zujuanjs-print-btn {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 9999;
padding: 4px 6px;
border-radius: 3px;
background-color: white;
box-shadow: 3px 3px 10px rgba(0,0,0,0.2);
margin-left: 5px;
transition: .3s;
text-decoration: none;
color: #333;
font-size: 12px;
cursor: pointer;
}
.ques-src {
font-size: 0.8em;
color: #444444;
font-family: "Microsoft YaHei UI";
font-weight: lighter;
}
.ques-src:before {
content: "[";
}
.ques-src:after {
content: "]";
}
.replaced-formula {
font-family: 'Times New Roman', '方正书宋_GBK', serif;
white-space: nowrap;
}
.dot-emphasis {
position: relative;
}
.dot-emphasis:before {
content: "";
background: black;
position: absolute;
width: 2px;
height: 2px;
border-radius: 50%;
margin: 0 0.44em;
top: 1.1em;
left: 0;
text-align: center;
user-select: none;
z-index: -1;
print-color-adjust: exact;
}
wave {
text-decoration-style: wavy;
text-decoration-line: underline;
text-underline-position: auto;
text-decoration-thickness: from-font;
}
u {
text-decoration-thickness: from-font;
}
.nowrap-wrapper {
white-space: nowrap;
}
.flex-wrapper {
display: flex;
gap: 15px;
}
ol.question-ol {
counter-reset: question-ol-counter;
margin-left: 32px;
li {
counter-increment: question-ol-counter;
list-style: auto;
*[style*="text-indent:28px;"] {
text-indent: 0 !important;
}
*[style*="margin-left:28px;"] {
margin-left: 0 !important;
}
}
li::marker {
content: "(" counter(question-ol-counter) ") ";
width: 32px;
}
}
#zujuanjs-reformatted-content {
margin: 3px;
position: relative;
}
@media screen {
#zujuanjs-reformatted-content {
width: 19.5cm;
}
}
/* @font-face {
font-family: 'TNRBold';
src: local('Times New Roman');
font-weight: bold;
unicode-range: U+0000-007F;
} */
/*
@font-face {
font-family: "CondensedXDX";
src: local("方正细等线_GBK");
font-stretch: condensed;
} */
.zujuanjs-question {
padding: 0;
margin-bottom: 12px;
font-size: 16px;
font-family: "Times New Roman", "方正书宋_GBK", SimSun, SimSun-ExtB;
font-variant-ligatures: discretionary-ligatures;
line-height: 24px;
break-inside: avoid;
text-align: justify;
text-spacing-trim: trim-start;
.chn-punc {
font-family: "Noto Serif CJK SC", "Noto Serif SC", SimSun, SimSun-ExtB;
}
.added-title {
text-align: center;
text-indent: 0 !important;
[style*="text-indent:28px;"] {
text-indent: 0 !important;
}
}
img:not([src*="quesimg/Upload/formula"]) {
right: 10px;
}
img[src*="quesimg/Upload/formula"] {
margin-bottom: 2.2px;
/* margin-left: 0.2em;
margin-right: 0.2em; */
}
*[style*="font-size:14px"] {
font-size: inherit !important;
}
*[style*="font-family:楷体"], .kaiti {
font-family: "Times New Roman", "方正楷体_GBK", 楷体, SimSun-ExtB !important;
}
*[style*="text-indent:28px;"]:not(.added-title *, ol.question-ol *) {
text-indent: 2em !important;
}
b:not(.zujuanjs-english b, .replaced-formula b), b[style*="font-family:楷体"]:not(.zujuanjs-english b) {
font-weight: normal;
font-family: "Helvetica LT Pro", "方正黑体_GBK", "Microsoft YaHei UI" !important;
}
/*
i[style*="font-family: Times New Roman"]:not(.zujuanjs-english i):before,
i[style*="font-family: Times New Roman"]:not(.zujuanjs-english i):after {
content: " ";
}
*/
sup {
margin-top: -6px;
}
sub {
margin-bottom: -6px;
}
sup, sub {
display: inline-block;
text-indent: 0 !important;
font-size: 0.75em;
}
table[name="optionsTable"] {
margin-left: 1.5em;
width: calc(100% - 1.5em) !important;
border-spacing: 0;
td {
padding-left: 1.7em;
text-indent: -1.7em;
}
}
ol.question-ol table[name="optionsTable"] {
margin-left: 0;
width: 100%;
}
td[style*="border-width:1px 1px 1px 1px"] {
border-width: 0.5px !important;
}
span[name="subquesnub"] + table[name="optionsTable"] {
margin-left: 0.5em;
}
table:not(table[name="optionsTable"]) {
margin: auto;
width: auto !important;
text-align: center;
td {
padding-left: 2px;
padding-right: 2px;
}
}
}
.zujuanjs-section-title {
font-size: 16px;
font-family: "Helvetica LT Pro", "方正黑体_GBK", "Microsoft YaHei UI" !important;
margin: 20px 0 10px;
break-after: avoid;
}
.zujuanjs-english .zujuanjs-section-title {
display: none;
}
.zujuanjs-english .zujuanjs-question {
font-family: "Minion Pro", "方正细等线_GBK", SimSun, SimSun-ExtB;
font-variant-ligatures: normal;
[style*="font-family: Times New Roman;"] {
font-family: "Minion Pro", "方正细等线_GBK", SimSun, SimSun-ExtB !important;
}
.exam-item__cnt > :nth-last-child(1 of p) {
margin-bottom: 12px;
}
.exam-item__cnt > p:last-child {
margin-bottom: 0;
}
/* font-weight: 300;
b {
font-weight: 400;
} */
}
#page-title {
text-align: center;
font-size: 2em;
font-weight: bold;
margin: 20px 0;
}
.font-preview {
border: 1px solid #ddd;
padding: 10px;
margin-top: 10px;
}
@page {
font-size: 12px;
font-family: "Jetbrains Mono", "方正细黑一_GBK";
font-weight: 200;
margin: 2cm;
@top-left {
content: var(--current-datetime);
}
@top-right {
content: var(--page-title);
}
@bottom-right {
content: counter(page) " / " counter(pages);
}
}
`;
function loadExternalScript(src, callback) {
const script = document.createElement('script');
script.src = src;
// script.async = true;
// script.type = 'module'; // 如果是 ES6 模块
script.onload = () => {
console.log(`脚本 ${src} 加载成功`);
if (callback) callback(null, script);
};
script.onerror = () => {
console.error(`脚本 ${src} 加载失败`);
if (callback) callback(new Error(`Failed to load script: ${src}`), script);
};
document.head.appendChild(script);
}
function replaceDotEmphases(father) {
father.querySelectorAll('dot').forEach(element => {
// 获取原始文本内容
const text = element.textContent;
// 清空原元素内容
element.innerHTML = '';
// 遍历每个字符
for (const char of text) {
if (!(char.match(/\s/))) {
const span = document.createElement('span');
span.className = 'dot-emphasis';
span.textContent = char;
element.appendChild(span);
} else {
element.appendChild(document.createTextNode(char));
}
}
});
}
const isChineseChar = (char) => {
if (!char) return false;
return /[\u4e00-\u9fa5]/.test(char);
};
function addSpacesAroundElements(father, selector) {
// 2. 获取所有符合选择器的元素
// 注意:我们需要从后往前遍历,或者在插入节点时小心处理,
// 但因为我们是在元素“外部”插入兄弟节点,不会影响元素本身的索引位置,
// 所以直接从前往后遍历也是安全的。
const elements = father.querySelectorAll(selector);
elements.forEach(el => {
// --- 处理前面的情况 ---
let prevNode = el.previousSibling;
// 如果前一个节点是文本节点,且最后一个字符是汉字
if (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
const textContent = prevNode.textContent;
if (textContent.length > 0) {
const lastChar = textContent.slice(-1); // 取最后一个字符
if (isChineseChar(lastChar)) {
// 在元素前插入一个空格
el.parentNode.insertBefore(document.createTextNode(' '), el);
}
}
}
// --- 处理后面的情况 ---
let nextNode = el.nextSibling;
// 如果后一个节点是文本节点,且第一个字符是汉字
if (nextNode && nextNode.nodeType === Node.TEXT_NODE) {
const textContent = nextNode.textContent;
if (textContent.length > 0) {
const firstChar = textContent.charAt(0); // 取第一个字符
if (isChineseChar(firstChar)) {
// 在元素后插入一个空格
// insertBefore 的第二个参数如果是 null,则追加到末尾
el.parentNode.insertBefore(document.createTextNode(' '), nextNode);
}
}
}
});
}
function replaceImgWithFormula(root, imgMaps) {
if (!imgMaps || typeof imgMaps !== 'object') return;
// 获取页面上所有的 img 标签
const allImages = root.querySelectorAll('img');
allImages.forEach(img => {
const src = img.src;
if (!src) return;
// 遍历 imgMaps 寻找匹配项
// 注意:如果 imgMaps 很大,这里内部循环依然可能慢。
// 最佳策略是:如果 src 格式固定(例如末尾是 hash),可以直接提取 hash 去查表。
// 假设我们不知道 src 的具体格式,只能包含匹配,则需遍历 keys。
for (const [key, htmlContent] of Object.entries(imgMaps)) {
if (src.includes(key)) {
const span = document.createElement('span');
span.className = 'replaced-formula';
span.innerHTML = `${htmlContent}`;
if (img.parentNode) {
img.parentNode.replaceChild(span, img);
}
// 找到匹配后跳出内层循环,避免重复处理
break;
}
}
});
}
function optimizeConditionsChoice(father) {
const tables = father.querySelectorAll('table[name="optionsTable"]');
tables.forEach(table => {
// 获取表格的可见文本内容(去除多余空白)
const text = table.innerText || table.textContent;
// 判断是否同时匹配两个正则
if (!(text.includes("不充分") && text.includes("不必要") && (text.includes("充要") || text.includes("充分必要")))) {
return;
}
table.style.tableLayout = 'unset';
// 获取表格内所有的 td 元素 (转换为数组以便操作,避免实时集合带来的问题)
const allCells = Array.from(table.querySelectorAll('td'));
if (allCells.length === 0) return; // 如果没有单元格,跳过
// 策略:保留第一个现有的 tr 作为目标行,将其他所有 tr 中的 td 都移过来
// 或者:创建一个全新的 tr 放在 tbody 最前面。
// 这里采用:找到第一个 tr,把其他所有 td 都追加进去。
let targetRow = table.querySelector('tr');
// 如果表格里完全没有 tr (极少见,但为了健壮性),创建一个
if (!targetRow) {
targetRow = document.createElement('tr');
// 尝试放入 tbody,如果没有 tbody 则直接放入 table
const tbody = table.querySelector('tbody') || table;
tbody.appendChild(targetRow);
}
// 遍历所有单元格,将它们移动到 targetRow
allCells.forEach(cell => {
// appendChild 会将节点从原位置移动到新位置
targetRow.appendChild(cell);
});
// (可选) 清理空的 tr 行
// 移动完单元格后,原来的 tr 可能变空了。如果你想清理掉空的行,可以解开下面注释:
const allRows = Array.from(table.querySelectorAll('tr'));
allRows.forEach(row => {
if (row !== targetRow && row.cells.length === 0) {
row.remove();
}
});
});
}
function centerParaImages(father) {
const paragraphs = father.querySelectorAll('p');
paragraphs.forEach(p => {
// --- 第一步:清理只包含 的 span ---
// 获取 p 下所有的 span 标签
const spans = p.querySelectorAll('span');
spans.forEach(span => {
const htmlContent = span.innerHTML;
// 正则表达式:匹配由一个或多个 组成的字符串,允许前后有空白字符
const onlyNbspRegex = /^\s*( )+\s*$/i;
if (onlyNbspRegex.test(htmlContent)) { span.remove(); }
});
// 重新获取清理后的所有子节点
const childNodes = Array.from(p.childNodes);
// 过滤出元素节点
const elementNodes = childNodes.filter(node => node.nodeType === Node.ELEMENT_NODE);
// 过滤出非空白文本节点
// 注意:此时之前的 span 已被移除,这里主要检查是否残留了其他文字
const textNodes = childNodes.filter(node =>
node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== ''
);
if (
elementNodes.length === 1 &&
elementNodes[0].tagName === 'IMG' &&
textNodes.length === 0
) {
p.style.textAlign = 'center';
}
});
}
/**
* 重新排列表格中的 td 元素
* @param {HTMLTableElement} table - 目标 table 元素
* @param {number} mode - 排列模式:1, 2, 或 4
*/
function reorganizeTable(table, mode) {
// 1. 基础验证
if (!(table instanceof HTMLTableElement)) {
console.error("第一个参数必须是一个 table 元素");
return;
}
// 获取所有的 td 元素 (将类数组转换为数组以便操作)
const tds = Array.from(table.querySelectorAll('td'));
// 验证 td 数量是否恰好为 4
if (tds.length !== 4) {
console.error("表格中必须恰好包含 4 个 td 元素");
return;
}
// 2. 清空表格中现有的所有 tr (但保留 td 引用)
// 注意:直接清空 innerHTML 会导致 tds 变成 detached 节点,我们需要保留它们
table.innerHTML = '';
// 3. 根据 mode 进行重组
if (mode === 4) {
// --- 模式 4: 所有 td 放到同一个 tr 中 ---
const tr = document.createElement('tr');
tds.forEach(td => tr.appendChild(td));
table.appendChild(tr);
} else if (mode === 2) {
// --- 模式 2: 前两个一个 tr,后两个一个 tr ---
const tr1 = document.createElement('tr');
tr1.appendChild(tds[0]);
tr1.appendChild(tds[1]);
table.appendChild(tr1);
const tr2 = document.createElement('tr');
tr2.appendChild(tds[2]);
tr2.appendChild(tds[3]);
table.appendChild(tr2);
} else if (mode === 1) {
// --- 模式 1: 每个 td 放到单独的 tr 中 ---
tds.forEach(td => {
const tr = document.createElement('tr');
tr.appendChild(td);
table.appendChild(tr);
});
} else {
console.error("第二个参数必须是 1, 2 或 4");
}
}
function removeAbundantTables(root) {
const allowedTexts = ['A.A', 'B.B', 'C.C', 'D.D'];
// 获取 root 下所有的 table 元素
const tables = root.querySelectorAll('table');
tables.forEach(table => {
const tds = table.querySelectorAll('td');
// 如果没有 td,跳过(可选)
if (tds.length === 0) return;
// 检查所有 td 的内容是否都在允许列表中
const allMatch = Array.from(tds).every(td => {
const text = td.textContent.trim();
return allowedTexts.includes(text);
});
// 如果所有 td 都匹配,则移除该 table
if (allMatch) {
table.remove();
}
});
}
/**
*
* @param {string} selector
* @returns {Element | null}
*/
function getFocusedElement(selector) {
const selection = window.getSelection();
// 如果没有选区或选区范围为空,返回 null
if (!selection || selection.rangeCount === 0) {
return null;
}
// 获取光标所在的节点(通常是文本节点或元素节点)
const anchorNode = selection.anchorNode;
if (!anchorNode) return null;
// 如果光标在文本节点中,我们需要找它的父元素
// 如果光标直接在元素上,就用它自己
const targetElement = anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
const closestElement = targetElement.closest(selector);
return closestElement;
}
/**
* 检索 root 元素下所有的 "bk" 元素,如果其内容仅由下划线组成,则替换为特定格式。
* @param {Element} root - 要检索的根 DOM 元素
*/
function addIndexToBlanks(root) {
if (!root) { return; }
// 1. 选择所有带有 "index" 属性的 "bk" 元素
// 注意:这里假设 "bk" 是标签名。如果是类名,请改为 '.bk[index]'
const bkElements = root.querySelectorAll('bk[index]');
bkElements.forEach((element) => {
// 2. 获取元素的文本内容并去除首尾空格
const textContent = element.textContent.trim();
// 3. 使用正则表达式检查内容是否只包含一个或多个下划线
// ^ 表示开头, _+ 表示一个或多个下划线, $ 表示结尾
if (/^_{1,10}$/.test(textContent)) {
// 4. 获取 index 属性值
const idx = element.getAttribute('index');
// 5. 替换内容
if (idx !== null) {
element.innerHTML = ` ${idx} `;
}
}
});
}
/**
* @param {Element} elem - 元素
*/
const addTitleBeforeElement = (function () {
let currentIndex = 0; // 初始索引
// 核心功能函数
/**
* @param {Element} elem - 元素
*/
function addTitle(elem) {
const p = document.createElement('div');
p.className = 'added-title';
const b = document.createElement('b');
b.textContent = String.fromCharCode(65 + currentIndex);
p.appendChild(b);
if (elem && elem.parentNode) {
elem.parentNode.insertBefore(p, elem);
}
currentIndex++;
}
// 添加重置方法到函数对象上
addTitle.reset = function () {
currentIndex = 0;
console.log('索引已重置为 A');
};
return addTitle;
})();
function optimizeChinesePunc(root) {
// 定义替换规则
const rules = [
{ regex: /(? {
let text = textNode.nodeValue;
let modified = false;
rules.forEach(rule => {
if (rule.regex.test(text)) {
text = text.replace(rule.regex, rule.replacement);
modified = true;
}
});
// 针对中文标点包装的特殊处理
// 注意:如果在文本节点内插入 HTML 标签,需要将文本节点分裂
const chinesePunctuationRegex = /([“”‘’,。?!()[]《》【】:;、.]|……|——)/g;
if (chinesePunctuationRegex.test(text)) {
// 使用 split 和 join 或者 replace 来构建新的 HTML 结构
// 这里为了演示,我们直接替换整个 nodeValue 为带 span 的 HTML
// 但要注意,nodeValue 不能直接放 HTML,需要用 range 替换
const tempDiv = document.createElement('div');
// 先处理前面的英文标点转换
// text = text
// .replace(/(?!\d\.?\d)\./g, '.')
// .replace(/, ?/g, ',')
// .replace(/\? ?/g, '?')
// .replace(/!/g, '!')
// .replace(/: ?/g, ':')
// .replace(/; ?/g, ';');
// 包装中文标点
const html = text.replace(chinesePunctuationRegex, '$1');
tempDiv.innerHTML = html;
// 将生成的 DOM 节点替换原来的文本节点
const parent = textNode.parentNode;
while (tempDiv.firstChild) {
parent.insertBefore(tempDiv.firstChild, textNode);
}
parent.removeChild(textNode);
} else if (modified) {
textNode.nodeValue = text;
}
});
}
/**
* 为特定标题下的题目添加字母序号
* @param {HTMLElement} root - 父元素 DOM 节点
*/
function addEnglishSectionLetters(root) {
if (!root) return;
// 获取所有直接子元素 (转换为数组以便遍历)
const children = Array.from(root.children);
let isTargetSection = false; // 标记当前是否处于目标板块(语法填空/阅读理解)
let charCode = 65; // ASCII码,65代表'A'
children.forEach(child => {
// 检查是否为标题元素
if (child.classList.contains('zujuanjs-section-title')) {
const titleText = child.innerText || child.textContent;
// 判断标题内容是否包含关键词
if (titleText.includes('语法填空') || titleText.includes('阅读理解')) {
isTargetSection = true;
charCode = 65; // 遇到新标题,重置字母为 A
} else {
isTargetSection = false; // 其他标题则退出目标模式
}
return; // 标题处理完毕,继续下一次循环
}
// 检查是否为题目元素
if (child.classList.contains('zujuanjs-question')) {
if (isTargetSection) {
// 创建新的 p 元素
const addedTitle = document.createElement('div');
addedTitle.className = 'added-title';
// 设置内容 A, B ...
addedTitle.innerHTML = `${String.fromCharCode(charCode)}`;
// 插入到题目元素的最开始
child.prepend(addedTitle);
// 字母递增
charCode++;
}
}
});
}
/**
*
* @param {Element} root
*/
const removeWidthInImg = (root) => {
if (!root) return;
const images = root.querySelectorAll('img');
images.forEach((img) => { img.removeAttribute('width'); });
};
/**
* @param {Element} root - 搜索范围的根节点 (例如 document.body)
*/
function fixPunctuationWrapping(root) {
if (!root) return;
// 定义两类标点
const rightPuncRegex = /^[,。?!;、.”’]/; // 句首禁止出现的标点
const leftPuncRegex = /[(【《“‘]/; // 行尾禁止出现的标点
const targetSelector = 'img, i, .replaced-formula';
const elements = root.querySelectorAll('.chn-punc');
elements.forEach(el => {
const text = el.textContent.trim();
if (!text) return;
// 逻辑 A: 如果是以靠右标点开头 -> 包裹 [前一个] + [当前]
if (rightPuncRegex.test(text)) {
const prev = el.previousSibling;
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.matches(targetSelector)) {
const wrapper = document.createElement('span');
wrapper.className = 'nowrap-wrapper';
el.parentNode.insertBefore(wrapper, prev);
wrapper.appendChild(prev);
wrapper.appendChild(el);
}
}
// 逻辑 B: 如果是以靠左标点结尾 -> 包裹 [当前] + [后一个]
else if (leftPuncRegex.test(text.slice(-1))) {
const next = el.nextSibling;
if (next && next.nodeType === Node.ELEMENT_NODE && next.matches(targetSelector)) {
const wrapper = document.createElement('span');
wrapper.className = 'nowrap-wrapper';
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
wrapper.appendChild(next);
}
}
});
}
/**
* 将可编辑区域中选中的内容用指定标签包裹
* @param {string} tagName - HTML标签名 (例如: 'span', 'b', 'div')
* @param {object} attributes - (可选) 标签的属性对象 (例如: {class: 'highlight', style: 'color:red'})
*/
function wrapSelectedText(tagName, attributes = {}) {
const selection = window.getSelection();
if (!selection.rangeCount) {
console.warn("未检测到选中的内容");
return;
}
const range = selection.getRangeAt(0);
// 3. 提取选中的内容
// extractContents() 会将选区内容从DOM中移除,并返回一个 DocumentFragment
const selectedContent = range.extractContents();
const wrapper = document.createElement(tagName);
// 5. 设置元素的属性 (如 class, id, style 等)
for (const [key, value] of Object.entries(attributes)) {
wrapper.setAttribute(key, value);
}
wrapper.appendChild(selectedContent);
range.insertNode(wrapper);
// selection.removeAllRanges();
}
/**
* 检查并自动为题目内容添加题号
* @param {HTMLElement} root - 检索的根节点
*/
function addQuestionNumbers(root) {
// 1. 检索所有目标元素 .exam-item__cnt
// 使用 querySelectorAll 在指定 root 范围内查找
const questions = root.querySelectorAll('.zujuanjs-question .exam-item__cnt');
if (questions.length === 0) return; // 如果没有找到题目,直接结束
// 2. 检测是否存在已有的题号
// 正则解释:^\s*\d+\.
// ^ 表示字符串开头
// \s* 表示允许开头有任意空白符(空格、换行等)
// \d+ 表示一个或多个数字
// \. 表示实际的点号
// const hasNumberRegex = /^\s*\d+\./;
// // 检查数组中是否【至少有一个】元素匹配已有题号的格式
// const hasExistingNumber = Array.from(questions).some(q =>
// hasNumberRegex.test(q.innerText.trim())
// );
let count = 1;
questions.forEach(q => {
// 这里假设是顺序生成题号 1., 2., 3. ...
// 如果你需要提取特定逻辑,可以在这里修改 count 的计算方式
// q.innerHTML = `${count}. ${q.innerHTML}`;
q.insertBefore(document.createTextNode(`${count}. `), q.firstChild);
count++;
});
console.log(`✅ 已为 ${questions.length} 道题目添加题号`);
}
/**
* 将元素内的图片提取出来,与该元素并列,并共同包裹在一个新父元素中
* @param {HTMLElement} elem - 目标 DOM 元素
*/
function wrapQuestionWithFlex(elem) {
// 1. 获取 elem 内部所有的 img 元素 (转为数组以便操作)
const images = Array.from(elem.querySelectorAll('img:not([src*="quesimg/Upload/formula"])'));
// 如果没有图片,直接返回,不做任何操作
if (images.length === 0) return;
// 2. 获取 elem 的父节点,用于后续插入操作
const parent = elem.parentNode;
if (!parent) return; // 如果 elem 没有父节点(比如是body),则无法进行同级操作
// 3. 创建一个新的父容器 (用于包裹图片和 elem)
const newWrapper = document.createElement('div');
// 给个类名方便调试样式,比如设置 display: flex
newWrapper.className = 'flex-wrapper';
// 4. 将图片移动到 wrapper 中
// 注意:appendChild 移动现有元素时,会自动将其从原位置移除
newWrapper.appendChild(elem);
images.forEach(img => {
newWrapper.appendChild(img);
});
// 6. 将新的 wrapper 插入到 DOM 树中(替换原来 elem 的位置)
parent.appendChild(newWrapper);
}
/**
* 格式化 exam-item__cnt 下的内容
* 功能:
* 1. 按换行分段(支持
和 块级元素)
* 2. 识别 (1), (2) 并转换为 ol/li 结构
* 3. 中间的非编号段落会自动归属到上一个编号项下
* 4. 完整保留 img, b, span 等内部标签
* 5. 【新增】自动移除 li 开头原有的 (1) 等编号标记
*
* @param {HTMLElement} root - 根元素
*/
function formatExamContent(root) {
if (!root) return;
const items = Array.from(root.querySelectorAll('div.exam-item__cnt'));
// 定义哪些标签被视为“块级元素”
const blockTags = new Set(['DIV', 'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI', 'TABLE', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'FIGURE', 'BLOCKQUOTE']);
items.forEach(item => {
// 1. 【预处理】将 innerHTML 转为 DOM 结构
const tempDiv = document.createElement('div');
tempDiv.innerHTML = item.innerHTML;
let lines = [];
let currentLineNodes = [];
// 2. 遍历所有子节点,进行“物理分行”
const nodes = Array.from(tempDiv.childNodes);
nodes.forEach(node => {
if (node.nodeName === 'BR') {
lines.push([...currentLineNodes]);
currentLineNodes = [];
}
else if (blockTags.has(node.nodeName)) {
if (currentLineNodes.length > 0) {
lines.push([...currentLineNodes]);
currentLineNodes = [];
}
lines.push([node]);
}
else {
currentLineNodes.push(node);
}
});
// 3. 处理最后一行
if (currentLineNodes.length > 0) {
lines.push(currentLineNodes);
}
// 4. 清空原内容,开始重组
item.innerHTML = '';
let currentOl = null;
lines.forEach(lineNodes => {
// 获取纯文本用于判断
const lineText = lineNodes.map(n => n.textContent).join('').trim();
// 正则匹配:以 (数字) 或 (数字) 开头
const isNumbered = /^\(\d+\)|^(\d+)/.test(lineText);
const rowWrapper = document.createElement('div');
lineNodes.forEach(node => rowWrapper.appendChild(node));
if (isNumbered) {
// --- 是编号段落 ---
if (!currentOl) {
currentOl = document.createElement('ol');
currentOl.className = "question-ol";
item.appendChild(currentOl);
}
const li = document.createElement('li');
// 我们遍历 wrapper 里的子节点,如果是文本节点,就进行替换
// 这样可以精准删除 "(1)" 而不影响后面的 或
Array.from(rowWrapper.childNodes).forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
// 使用正则替换掉开头的 (数字) 或 (数字)以及紧随其后的空格
node.textContent = node.textContent.replace(/^\(\d+\)|^(\d+)/, '');
}
});
// 将清洗后的内容移入 li
while (rowWrapper.firstChild) {
li.appendChild(rowWrapper.firstChild);
}
currentOl.appendChild(li);
} else {
// --- 是普通段落 ---
if (currentOl) {
currentOl.appendChild(rowWrapper);
} else {
item.appendChild(rowWrapper);
}
}
});
});
}
GM_addStyle(additionalStyles);
loadExternalScript('https://cdn.jsdelivr.net/npm/pangu@7/dist/browser/pangu.umd.js');
const usernameElement = document.querySelector('.user-nickname');
const username = usernameElement ? usernameElement.innerText : '未知用户';
function checkIn() {
const signInBtn = document.querySelector('a.sign-in-btn');
const daySignInBtn = document.querySelector('a.day-sign-in');
if (signInBtn) signInBtn.click();
if (daySignInBtn) daySignInBtn.click();
console.log("已签到!");
}
function canCheckIn() {
const signedInLink = document.querySelector(
'.user-assets-box a.assets-method[href="/score_task/"]'
);
return !signedInLink || signedInLink.textContent.trim() !== '已签到';
}
function signInLogic() {
if (canCheckIn()) { checkIn(); }
}
function debug() {
console.log("是否可签到:", canCheckIn());
signInLogic();
}
window.addEventListener('load', debug, false);
const buildBtn = document.createElement('a');
buildBtn.className = 'zujuanjs-print-btn';
buildBtn.innerHTML = `排版试卷`;
document.body.appendChild(buildBtn);
buildBtn.onclick = showPreDialog;
function showPreDialog() {
const savedSepPage = GM_getValue('sepPage', false);
const savedShowSource = GM_getValue('showSource', false);
Swal.fire({
title: '排版设置',
width: 600,
html: /*html*/`
${char}
`; // }); root.innerHTML = root.innerHTML.replace(grammarBlanksPrompt, ""); addEnglishSectionLetters(root); } replaceImgWithFormula(root, imgMaps); // root.innerHTML = root.innerHTML.replace(/阅读下列短文,根据短文内容填空。在未给提示词的空白处仅填写.*个恰当的单词,在给出提示词的空白处用括号内所给词的正确形式填空。请在答题卡指定区域作答。/g, ``); // root.innerHTML = root.innerHTML.replace(/([\u4e00-\u9fff])”/g, `$1”`); // root.innerHTML = root.innerHTML.replace(/”([\u4e00-\u9fff])/g, `”$1`); // root.innerHTML = root.innerHTML.replace(/([\u4e00-\u9fff])“/g, `$1“`); pangu.spacingNode(root); // root.contentEditable = true; setTimeout(() => { root.contentEditable = true; // root.addEventListener("keydown") }, 2000); return root; } document.addEventListener('keydown', (e) => { // console.log(e); if (e.ctrlKey && e.shiftKey) { const key = e.key; if (key === "_") { wrapSelectedText("sub"); } else if (key === "+") { wrapSelectedText("sup"); } } if (e.ctrlKey && e.shiftKey && e.altKey) { // 获取按下的数字键 (兼容主键盘和数字小键盘) const key = e.key; console.log(key); if (key === ")") { addQuestionNumbers(document.getElementById("zujuanjs-reformatted-content")); } if (key === "A") { const targetElement = getFocusedElement("p"); if (targetElement) { addTitleBeforeElement(targetElement); } } else if (key === "Q") { addTitleBeforeElement.reset(); } if (key === "ArrowRight") { const targetElement = getFocusedElement("p:has(img)"); if (targetElement) { targetElement.querySelector("img").style.position = "absolute"; } } else if (key === "ArrowUp") { const targetElement = getFocusedElement("p:has(img)"); if (targetElement) { targetElement.querySelector("img").style.marginTop = "-22px"; } } if (key === "X") { const targetElement = getFocusedElement("div, p, table"); if (targetElement) { targetElement.remove(); } } if (key === "E") { wrapSelectedText("p", { class: "added-title" }); } if (key === "P") { const targetElement = getFocusedElement("div.exam-item__cnt"); if (targetElement) { wrapQuestionWithFlex(targetElement); } } if (key === "Enter") { const targetElement = getFocusedElement("div.zujuanjs-section-title, div.zujuanjs-question"); if (targetElement) { targetElement.style.breakBefore = "page"; } } if (key === "K") { wrapSelectedText("span", { class: "kaiti" }); } let mode = null; if (key === '!') { mode = 1; } if (key === '@') { mode = 2; } if (key === '$') { mode = 4; } if (mode) { const targetTable = getFocusedElement("table"); if (targetTable) { reorganizeTable(targetTable, mode); } } // e.preventDefault(); } }, { capture: true });