txt reader

小说阅读助手,文本清理修复、排版、生成索引等功能,方便阅读,也可用于初步较对

  1. // ==UserScript==
  2. // @name txt reader
  3. // @namespace txt reader
  4. // @description 小说阅读助手,文本清理修复、排版、生成索引等功能,方便阅读,也可用于初步较对
  5. // @include *.txt
  6. // @include *.txt#*
  7. // @exclude http://www.rfc-editor.org/*
  8. // @exclude *gfwlist.txt*
  9. // @version 1.0.2
  10.  
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. /*
  15. * 只在firefox下测试过
  16. * 暂只支持中文
  17. *
  18. */
  19.  
  20. /*
  21. * ===敏感词匹配===
  22. * 最大正向匹配,不符则回溯到上一个匹配的最长的子串。
  23. * 分前中后缀,优先词根、然后前缀、然后后缀
  24. * 优先搜索前后缀,其次归纳
  25. *
  26. * 匹配算法:
  27. * 1. 循环搜索词根
  28. * 1. 成功则将输入字符串的词根部分替换为结果中的替换字符串
  29. * 2. 失败则将将起始位置后移一位重新开始搜索词根
  30. * 2. 直到起始位置超出字符串,停止
  31. *
  32. * 词根搜索:
  33. * 1. 循环匹配下一个字符
  34. * 1. 如果该节点有result、prefix、suffix子节点,将该节点及该字符所对应的位置压入栈中
  35. * 2. 直到匹配失败
  36. * 1. 循环对栈中节点进行操作
  37. * 1. 如果该节点有prefix子节点,则对其进行前缀搜索
  38. * 1. 搜索成功,则返回搜索到的替换字符串以及该节点(词根栈中的节点)对应字符的位置
  39. * 2. 如果该节点有suffix子节点,则对其进行后缀搜索
  40. * 1. 搜索成功,则返回搜索到的替换字符串以及该节点(词根栈中的节点)对应字符的位置
  41. * 3. 如果该节点有result子节点,则返回result节点中的内容作为替换字符串以及该节点(词根栈中的节点)对应字符的位置
  42. * 4. 如果以上都没成功,则弹出下一个节点继续匹配
  43. * 2. 直到栈为空,还没有搜索到替换字符串的话,返回匹配失败
  44. *
  45. * 前缀搜索:
  46. * 1. 循环匹配下一个字符
  47. * 1. 如果该节点有result、suffix子节点,将该节点及该字符所对应的位置压入栈中
  48. * 2. 直到匹配失败
  49. * 1. 循环对栈中节点进行操作
  50. * 1. 如果该节点有suffix子节点,则对其进行后缀搜索
  51. * 1. 搜索成功,则返回搜索到的替换字符串
  52. * 2. 如果该节点有result子节点,则返回result节点中的内容作为替换字符串
  53. * 3. 如果以上都没成功,则弹出下一个节点继续匹配
  54. * 2. 直到栈为空,还没有搜索到替换字符串的话,返回匹配失败
  55. *
  56. * 后缀搜索:
  57. * 1. 循环匹配下一个字符
  58. * 1. 如果该节点有result子节点,将该节点及该字符所对应的位置压入栈中
  59. * 2. 直到匹配失败
  60. * 1. 循环对栈中节点进行操作
  61. * 1. 如果该节点有result子节点,则返回result节点中的内容作为替换字符串
  62. * 2. 如果以上都没成功,则弹出下一个节点继续匹配
  63. * 2. 直到栈为空,还没有搜索到替换字符串的话,返回匹配失败
  64. */
  65.  
  66. /*
  67. * 段落解析
  68. *
  69. * line_break_char = "\n" | "\r"
  70. * space_char = <UNICODE coded character 9, 20, 3000 hexadecimal>
  71. * control_char = <UNICODE coded characters 0001-0008, 001B, 001C, 001E-001F and 007F hexadecimal>
  72. * comm_char = <UNICODE coded characters 0021-007E ,0080-2FFF and 3001-FFFF hexadecimal>
  73. *
  74. * paragraph_break = *( control_char | line_break_char | space_char ) line_break_char *( control_char | line_break_char | space_char )
  75. * paragraph = 1*comm_char *( comm_char | control_char | space_char ) 1*comm_char
  76. * novel = [paragraph_break paragraph] ( paragraph_break paragraph )* [paragraph_break]
  77. *
  78. * In the context of paragraph
  79. * paragraph = novel_title | preface_heading | postscript_heading | section_heading | comm_paragraph
  80. *
  81. * novel_title = "《" 1*( space_char | comm_char ) "》"
  82. *
  83. * preface_heading = "序" |
  84. * "序" *space_char "章" |
  85. * "序" *space_char "言" |
  86. * "前" *space_char "言" |
  87. * "引" *space_char "言" |
  88. * "引" *space_char "子" |
  89. * "摘" *space_char "要" |
  90. * "楔" *space_char "子" |
  91. * "背景简介";
  92. *
  93. * postscript_heading = "后" *space_char "记" |
  94. * "附" *space_char "言" |
  95. * "结" *space_char "语"
  96. *
  97. * section_kw = "章" | "节" | "回" | "卷" | "折" | "篇" | "幕" | "集"
  98. * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  99. * chinese_num_kw = "〇" | "零" | "一" | "二" | "三" | "四" | "五" | "六" | "七" | "八" | "九" |
  100. * "十" | "百" | "千" | "万" | "亿" | "萬" | "億" ??
  101. * section_digit_num = 1*digit [ "." 1*digit ]
  102. * section_chinese_num = 1*chinese_num_kw
  103. * section_heading = [*"第" *space_char ] ( section_digit_num | section_chinese_num ) *space_char section_kw *50( space_char | comm_char )
  104. *
  105. * Grammers of title. A novel text have only one title even if it contains more than one story
  106. * novel = [ no title ] *title [ no title ]
  107. *
  108. * Grammers of sections, for generating index. The earlier section has the higher level
  109. * novel = *( preface | top_level_section | postscript )
  110. * section = *( child_section)
  111. *
  112. * 实际情况控制字符已在文本清理时去除
  113. *
  114. */
  115.  
  116. //是否是开发模式
  117. var devmode = false;
  118. var timestamp;
  119.  
  120. //-------------------------------辅助方法----------------------------------
  121. String.prototype.trim = function () {
  122. return this.replace(/(^\s*)|(\s*$)/g, "");
  123. };
  124.  
  125. Array.prototype.contains = function (item) {
  126. for(var i = 0; i < this.length; i++) {
  127. if (this[i] == item) {
  128. return true;
  129. }
  130. }
  131. return false;
  132. };
  133. //--------------------------------标题-----------------------------------
  134. document.title = decodeURI(window.location.pathname).match(/[^\\\/]*(?=.txt$)/);
  135.  
  136. //--------------------------------整体布局--------------------------------
  137. document.body.style.margin = "0";
  138. document.body.style.padding = "0";
  139. var pageLayout = document.createElement("div");
  140. pageLayout.style.margin = "0 auto";
  141. pageLayout.style.padding = "0";
  142. pageLayout.style.height = "100%";
  143. pageLayout.style.width = "1224px";
  144. pageLayout.style.borderLeft = "1px solid #999";
  145. pageLayout.style.borderRight = "1px solid #999";
  146. document.body.appendChild(pageLayout);
  147.  
  148. //创建索引显示框,向左浮动
  149. var index_containter = document.createElement("div");
  150. var index_box = document.createElement("div");
  151. index_containter.id = "index_containter";
  152. index_containter.style.height = "100%";
  153. index_containter.style.cssFloat = "left";
  154. index_containter.style.overflow = "auto";
  155. index_containter.style.backgroundColor = "#F3F2EE";
  156. index_containter.style.width = "219px";
  157. index_containter.appendChild(index_box);
  158. index_box.id = "index_box";
  159. index_box.style.margin = "0 10px";
  160. index_box.style.paddingTop = "5px";
  161. pageLayout.appendChild(index_containter);
  162.  
  163. //分割线
  164. var split_line = document.createElement("div");
  165. split_line.style.width = "1px";
  166. split_line.style.height = "100%";
  167. split_line.style.cssFloat = "left";
  168. split_line.style.backgroundColor = "#999";
  169. pageLayout.appendChild(split_line);
  170.  
  171. //创建正文显示框,外边距浏览器默认
  172. var text_containter = document.createElement("div");
  173. var text_box = document.createElement("div");
  174. text_containter.id = "text_containter";
  175. text_box.id = "text_box";
  176. text_containter.style.cssFloat = "left";
  177. text_containter.style.width = "1002px";
  178. text_containter.style.height = "100%";
  179. text_containter.style.margin = "0 1px";
  180. text_containter.style.overflowY = "auto";
  181. text_containter.style.overflowX = "hidden";
  182. text_box.style.width = "904px";
  183. text_box.style.padding = "24px 50px 24px 48px";
  184. text_box.style.backgroundColor = "#F3F2EE";
  185. //text_box.style.backgroundImage = "url(file:///home/zbfs/Desktop/bg.png)";
  186. text_containter.appendChild(text_box);
  187. pageLayout.appendChild(text_containter);
  188.  
  189. //--------------------------------文本获取----------------------------
  190. //获取原始文本
  191. var originalText = document.getElementsByTagName("pre")[0].innerHTML;
  192. if (devmode) {
  193. alert("文本长度" + originalText.length);
  194. }
  195.  
  196. //清除页面文本
  197. document.body.removeChild(document.getElementsByTagName("pre")[0]);
  198.  
  199. //-------------------------------文本清理----------------------------
  200. if (devmode) {
  201. timestamp = new Date();
  202. }
  203.  
  204. cleanedText = originalText.replace(/[\u0001-\u0009\u001B\u001C\u001E\u001F\u007F]|((<|(&lt;))[a-z\/\u0021\u0025\u002D][\u0000-\u003D\u003F-\uFFFF]{0,200}(>|(&gt;)))/ig, "");
  205.  
  206. if (devmode) {
  207. alert("文本清理完成,耗时:" + (new Date().getTime() - timestamp.getTime()) + "毫秒");
  208. }
  209. //-------------------------------文本清理完成----------------------------
  210.  
  211. //-------------------------------敏感词修复----------------------------
  212. if (devmode) {
  213. timestamp = new Date();
  214. }
  215. //定义词法树原型
  216. function LexTree() {};
  217. LexTree.prototype.attributeWords = ["suffix", "prefix", "result"];
  218. LexTree.prototype.addRule = function (rule) {
  219.  
  220. if (rule == null) {
  221. return this;
  222. }
  223.  
  224. var substring;
  225. var prefix;
  226. var suffix;
  227.  
  228. var strings1 = rule.split("=>");
  229. var replaceText = strings1[1].trim();
  230. var lstr = strings1[0].trim();
  231. var fl = lstr.indexOf("(");
  232. var ll = lstr.lastIndexOf(")");
  233. if (fl >= 0 && ll >= 0) {
  234. prefix = lstr.substr(0, fl);
  235. suffix = lstr.substr(ll + 1);
  236. substring = lstr.slice(fl + 1, ll)
  237. } else {
  238. substring = lstr;
  239. }
  240. if (substring == "") {
  241. return this;
  242. }
  243. var node = this;
  244. function addSubstring(str) {
  245. if (str) {
  246. if (node[str.charAt(0)] == null) {
  247. node[str.charAt(0)] = {};
  248. }
  249. node = node[str.charAt(0)];
  250. addSubstring(str.substr(1));
  251. }
  252. }
  253. addSubstring(substring);
  254. function addPrefix(str) {
  255. if (str) {
  256. if (node[str.charAt(str.length - 1)] == null) {
  257. node[str.charAt(str.length - 1)] = {};
  258. }
  259. node = node[str.charAt(str.length - 1)];
  260. addPrefix(str.substr(0, str.length - 1));
  261. }
  262. }
  263. if (prefix) {
  264. if (node["prefix"] == null) {
  265. node["prefix"] = {};
  266. }
  267. node = node["prefix"];
  268. addPrefix(prefix);
  269. }
  270. function addSuffix(str) {
  271. if (str) {
  272. if (node[str.charAt(0)] == null) {
  273. node[str.charAt(0)] = {};
  274. }
  275. node = node[str.charAt(0)];
  276. addSuffix(str.substr(1));
  277. }
  278. }
  279. if (suffix) {
  280. if (node["suffix"] == null) {
  281. node["suffix"] = {};
  282. }
  283. node = node["suffix"];
  284. addSuffix(suffix);
  285. }
  286. node.result = replaceText;
  287. return this;
  288. }
  289. LexTree.prototype.toJsonString = function () {
  290. var lexTree = this;
  291. function isTreeAttribute(str) {
  292. if (str) {
  293. if (str.length == 1) {
  294. return true;
  295. } else if (lexTree.attributeWords.contains(str)) {
  296. return true;
  297. }
  298. }
  299. return false;
  300. }
  301. function getFieldStr(i) {
  302. var reg = /^[A-Za-z]+$/;
  303. if (reg.test(i)) {
  304. return i;
  305. } else if (lexTree.attributeWords.contains(i)) {
  306. return i;
  307. }
  308. return "\"" + i + "\"";
  309. }
  310. function _toJsonString(element) {
  311. if (element == null) {
  312. return "";
  313. }
  314. if (typeof element == "string") {
  315. return "\"" + element + "\""
  316. }
  317. var str = "{";
  318. var hasFlagStr = "";
  319. for (var i in element) {
  320. if (isTreeAttribute(i)) {
  321. str = str + hasFlagStr + getFieldStr(i) + ": " + _toJsonString(element[i]);
  322. hasFlagStr = ",";
  323. }
  324. }
  325. str = str + "}"
  326. return str;
  327. }
  328. return _toJsonString(this);
  329. }
  330. //定义替换器原型
  331. function TextReplacer() {};
  332. TextReplacer.prototype.addLexTree = function (lexTree) {
  333. this.lexTree = lexTree;
  334. return this;
  335. }
  336. TextReplacer.prototype.parse = function (str) {
  337. if (this.lexTree == null) {
  338. throw new Exception("LexTree Not Found");
  339. }
  340.  
  341. if (str == null) {
  342. return null;
  343. }
  344.  
  345. this.inStr = str; //输入字符串
  346. this.inputStrLength = str.length; //输入字符串长度
  347.  
  348. this.rootAnchor = 0; //匹配词根的起始位置
  349. this.rootFocus = 0; //匹配词根的当前位置
  350. var outStr = ""; //输出字符串
  351. var result = null; //暂存匹配词根的结果
  352.  
  353. while (this.rootAnchor < this.inputStrLength) {
  354. result = this.matchRoot(this.rootAnchor);
  355. if (result) {
  356. outStr = outStr + result;
  357. this.rootAnchor = this.rootFocus + 1;
  358. } else {
  359. outStr = outStr + this.inStr[this.rootAnchor];
  360. this.rootAnchor = this.rootAnchor + 1;
  361. }
  362. this.rootFocus = this.rootAnchor;
  363. }
  364. return outStr;
  365. }
  366. TextReplacer.prototype.matchRoot = function () {
  367. var stack = [];
  368. var current = this.lexTree;
  369. var focus = this.rootAnchor;
  370. var item = null;
  371. var node = null;
  372. var result = null;
  373. while (focus < this.inputStrLength) {
  374. current = current[this.inStr[focus]];
  375. if (current == null) {
  376. break;
  377. }
  378. if (current.result == null && current.prefix == null && current.suffix == null) {
  379. ;
  380. } else {
  381. stack.push({index: focus, node: current});
  382. }
  383. focus++;
  384. }
  385. while (stack.length > 0) {
  386. item = stack.pop();
  387. node = item.node;
  388. this.rootFocus = item.index;
  389. if (node.prefix != null) {
  390. result = this.matchPrefix(node.prefix);
  391. if (result) {
  392. return result;
  393. }
  394. }
  395. if (node.suffix != null) {
  396. result = this.matchSuffix(node.suffix);
  397. if (result) {
  398. return result;
  399. }
  400. }
  401. if (node.result != null) {
  402. return node.result;
  403. }
  404. }
  405. return null;
  406. }
  407. TextReplacer.prototype.matchPrefix = function (root) {
  408. var stack = [];
  409. var focus = this.rootAnchor - 1;
  410. var current = root;
  411. var node;
  412. while (focus >= 0) {
  413. current = current[this.inStr[focus]];
  414. if (current == null) {
  415. break;
  416. }
  417. if (current.result == null && current.suffix == null) {
  418. ;
  419. } else {
  420. stack.push(current);
  421. }
  422. focus--;
  423. }
  424. while (stack.length > 0) {
  425. node = stack.pop();
  426. if (node.suffix != null) {
  427. return this.matchSuffix(node.suffix);
  428. } else if (node.result != null) {
  429. return node.result;
  430. }
  431. }
  432. return null;
  433. }
  434. TextReplacer.prototype.matchSuffix = function (root) {
  435. var stack = [];
  436. var focus = this.rootFocus + 1;
  437. var current = root;
  438. var node;
  439. while (focus >= 0) {
  440. current = current[this.inStr[focus]];
  441. if (current == null) {
  442. break;
  443. }
  444. if (current.result == null && current.suffix == null) {
  445. ;
  446. } else {
  447. stack.push(current);
  448. }
  449. focus++;
  450. }
  451. while (stack.length > 0) {
  452. node = stack.pop();
  453. if (node.result != null) {
  454. return node.result;
  455. }
  456. }
  457. return null;
  458. }
  459.  
  460. if (devmode) {
  461. timestamp = new Date();
  462. }
  463. //根据替换规则构造词法树
  464. var lexTree = new LexTree()
  465. .addRule("&gt; => >").addRule("&lt; => <")
  466. .addRule("bàng => 棒").addRule("bào => 爆").addRule("bī => 逼").addRule("bō => 波")
  467. .addRule("cāo => 操").addRule("cǎo => 草").addRule("cào => 操").addRule("chā => 插").addRule("chāng => 娼").addRule("cháo => 潮").addRule("chōu => 抽").addRule("chuáng => 床").addRule("chūn => 春").addRule("cuō => 搓").addRule("cū => 粗")
  468. .addRule("dàn => 弹").addRule("dǎng => 党").addRule("dàng => 荡").addRule("diao => 屌").addRule("dòng => 洞")
  469. .addRule("fǎ => 法").addRule("fù => 妇")
  470. .addRule("guān => 官")
  471. .addRule("hán => 含")
  472. .addRule("jing => 精")
  473. .addRule("jī => 激").addRule("jiān => 奸").addRule("jiāng => 江").addRule("jiāo => 交").addRule("jìn => 禁").addRule("jīng => 精").addRule("jǐng => 警").addRule("jū => 拘")
  474. .addRule("kù => 裤")
  475. .addRule("làng => 浪").addRule("liáo => 撩").addRule("luàn => 乱").addRule("lún => 伦").addRule("luǒ => 裸").addRule("lù => 露")
  476. .addRule("máo => 毛").addRule("mí => 迷").addRule("mō => 摸")
  477. .addRule("pào => 炮").addRule("piàn => 片")
  478. .addRule("qiāng => 枪").addRule("qíng => 情")
  479. .addRule("ri => 日")
  480. .addRule("rì => 日").addRule("rǔ => 乳")
  481. .addRule("se => 色")
  482. .addRule("sāo => 骚").addRule("sè => 色").addRule("sè => 色").addRule("shā => 杀").addRule("shēn => 呻").addRule("shén => 神").addRule("shè => 射").addRule("shǐ => 屎").addRule("shǔn => 吮").addRule("sǐ => 死").addRule("sū => 酥")
  483. .addRule("ting => 挺")
  484. .addRule("tài => 态").addRule("tān => 贪").addRule("tǐ => 体").addRule("tiǎn => 舔").addRule("tiáo => 调").addRule("tǐng => 挺").addRule("tǒng => 捅").addRule("tōu => 偷").addRule("tuǐ => 腿").addRule("tūn => 吞").addRule("tún => 臀")
  485. .addRule("wēn => 温").addRule("wěn => 吻")
  486. .addRule("xing => 性")
  487. .addRule("xī => 吸").addRule("xí => 习").addRule("xìng => 性").addRule("xiōng => 胸").addRule("xué => 穴")
  488. .addRule("yu => 欲")
  489. .addRule("yàn => 艳").addRule("yāng => 央").addRule("yào => 药").addRule("yín => 淫").addRule("yòu => 诱").addRule("yù => 欲")
  490. .addRule("zàng => 藏").addRule("zhà => 炸").addRule("zhèng => 政").addRule("zhōng => 中").addRule("zuì => 罪").addRule("zuò => 做")
  491. .addRule("德(xing) => 行")
  492. .addRule("碧(yu) => 玉").addRule("美(yu) => 玉").addRule("(yu)石 => 玉")
  493. .addRule("十之(**) => 八九").addRule("十有(**) => 八九").addRule("(**)不离十 => 八九")
  494. .addRule("赤身(**) => 裸体")
  495. .addRule("感(**)彩 => 情色")
  496. .addRule("本(*)难移 => 性")
  497. .addRule("(dang)然无存 => 荡");
  498.  
  499. if (devmode) {
  500. alert("构造词法树完成,耗时:" + (new Date().getTime() - timestamp.getTime()) + "毫秒");
  501. }
  502.  
  503. if (devmode) {
  504. timestamp = new Date();
  505. }
  506.  
  507. var decensoredText = new TextReplacer().addLexTree(lexTree).parse(cleanedText);
  508.  
  509. if (devmode) {
  510. alert("敏感词修复完成,耗时:" + (new Date().getTime() - timestamp.getTime()) + "毫秒");
  511. }
  512. //-----------------------------------敏感词修复完成----------------------------
  513.  
  514. //-----------------------------------解析段落工作开始------------------------------
  515. if (devmode) {
  516. timestamp = new Date();
  517. }
  518.  
  519. function parseParagraph(source) {
  520. var tempStr;
  521. var sectionTagStack = []; //匹配到的章节关键字列表
  522. var postscript; //后记节点
  523. var allowTitle = true;
  524. var box = document.createElement("div");
  525. box.className = "para_box"
  526. var state = "go";
  527.  
  528. var indexRootNode = { //索引根节点
  529. name: "索引",
  530. level: -1,
  531. children: []
  532. };
  533. var cursorIndex = indexRootNode; //指向索引树的游标
  534.  
  535. var action = {};
  536. action["go"] = {}
  537. action["go"]["title"] = function (str) {
  538. var p = document.createElement("p");
  539. p.appendChild(document.createTextNode(str.replace(/[《》]/g, "")));
  540. p.className = "title"
  541. box.appendChild(p);
  542. var hr = document.createElement("hr");
  543. hr.className = "h-dec";
  544. hr.style.margin = "0px -20px";
  545. box.appendChild(hr);
  546. allowTitle = false;
  547. }
  548. action["go"]["prefaceHead"] = function (str) {
  549. var p = document.createElement("p");
  550. p.appendChild(document.createTextNode(str));
  551. p.className = "sectionHead prefaceHead";
  552. box.appendChild(p);
  553. var hr = document.createElement("hr");
  554. hr.className = "h-dec";
  555. box.appendChild(hr);
  556.  
  557. indexRootNode.children.push(cursorIndex = {
  558. name: str,
  559. level: 0,
  560. children: [],
  561. parent: indexRootNode,
  562. dom: p,
  563. aname: str
  564. });
  565.  
  566. allowTitle = false;
  567. }
  568. action["go"]["sectionHead"] = function (str, tag) {
  569. var p = document.createElement("p");
  570. p.appendChild(document.createTextNode(str));
  571. var level = sectionTagStack.indexOf(tag);
  572. if (level == -1) {
  573. sectionTagStack.push(tag);
  574. level = sectionTagStack.indexOf(tag);
  575. }
  576. p.className = "sectionHead sectionHead" + level;
  577. box.appendChild(p);
  578. var hr = document.createElement("hr");
  579. hr.className = "h-dec";
  580. box.appendChild(hr);
  581.  
  582. while (cursorIndex.level >= level) {
  583. cursorIndex = cursorIndex.parent;
  584. }
  585. var indexNode = {
  586. name: str,
  587. level: level,
  588. children: [],
  589. parent: cursorIndex,
  590. dom: p,
  591. aname: (cursorIndex.level == -1 ? str : cursorIndex.aname + " -> " + str)
  592. };
  593. cursorIndex.children.push(indexNode);
  594. cursorIndex = indexNode;
  595.  
  596. allowTitle = false;
  597. }
  598. action["go"]["postscriptHead"] = function (str) {
  599. var p = document.createElement("p");
  600. p.appendChild(document.createTextNode(str));
  601. p.className = "sectionHead postscriptHead";
  602. box.appendChild(p);
  603. var hr = document.createElement("hr");
  604. hr.className = "h-dec";
  605. box.appendChild(hr);
  606.  
  607. indexRootNode.children.push(cursorIndex = {
  608. name: str,
  609. level: 0,
  610. children: [],
  611. parent: indexRootNode,
  612. dom: p,
  613. aname: str
  614. });
  615.  
  616. allowTitle = false;
  617. }
  618. action["go"]["paragraph"] = function (str) {
  619. var p = document.createElement("p");
  620. p.appendChild(document.createTextNode(str));
  621. p.className = "paragraph"
  622. box.appendChild(p);
  623. }
  624.  
  625. var pr = /^[\u0020\u3000\t\n\r]*(?:(《[^\n\r]+》)|(序|序[\u0020\u3000\t]*章|序[\u0020\u3000\t]*言|前[\u0020\u3000\t]*言|引[\u0020\u3000\t]*言|引[\u0020\u3000\t]*子|摘[\u0020\u3000\t]*要|楔[\u0020\u3000\t]*子|背景简介|内容简介)|((?:第[\u0020\u3000\t]*)?(?:(?:[0-9]+(?:.[0-9]+)?)|(?:[〇零一壹二贰三叁四肆五伍六陆七柒八捌九玖十拾百佰千仟万亿萬億廿卅卌]+))[\u0020\u3000\t]*([章节回卷折篇幕集])(?:[\u0020\u3000\t][^\n\r]{1,20}[^\n\r\u0020\u3000\t])?)|(后[\u0020\u3000\t]*记|附[\u0020\u3000\t]*言|结[\u0020\t]*语)|([^\n\r]+))(?=[\u0020\u3000\t]*[\n\r]+)/;
  626. var r;
  627.  
  628. while (true) {
  629. r = pr.exec(source);
  630. if (r == null) {
  631. break;
  632. }
  633. // r[0]匹配到的包含分段符的字符串
  634. if (r[1]) { // r[1]匹配到标题,只匹配一次
  635. if (allowTitle) {
  636. action[state]["title"](r[1]);
  637. } else {
  638. action[state]["paragraph"](r[1]);
  639. }
  640. } else if (r[2]) { // r[2]匹配到的前言
  641. action[state]["prefaceHead"](r[2]);
  642. } else if (r[3]) { // r[3]匹配到的段落标题,r[4]匹配到段落关键字
  643. action[state]["sectionHead"](r[3], r[4]);
  644. } else if (r[5]) { // r[5]匹配到的后记
  645. action[state]["postscriptHead"](r[5]);
  646. } else { // r[6]匹配到的普通段落
  647. action[state]["paragraph"](r[6]);
  648. }
  649. source = source.slice(r[0].length);
  650. }
  651.  
  652. return {box: box, index: indexRootNode};
  653. }
  654.  
  655. var res = parseParagraph(decensoredText);
  656.  
  657. if (devmode) {
  658. alert("解析段落完成,耗时:" + (new Date().getTime() - timestamp.getTime()) + "毫秒");
  659. }
  660. //------------------------解析段落工作完成------------------------------
  661.  
  662. //------------------------绘制文本工作开始-------------------------------
  663. if (devmode) {
  664. timestamp = new Date();
  665. }
  666. text_box.appendChild(res.box);
  667.  
  668. //可自定义样式
  669. var style = document.createElement('style');
  670. style.type = "text/css";
  671. var head = document.head || document.getElementsByTagName('head')[0];
  672. head.appendChild(style);
  673. function setCSSText(str) {
  674. if (style.styleSheet) {
  675. style.styleSheet.cssText = str;
  676. } else {
  677. style.appendChild(document.createTextNode(str));
  678. }
  679. }
  680. setCSSText(".paragraph {\n" +
  681. "\tfont-family: 微软雅黑,文泉驿正黑,苹果丽黑,黑体;\n" +
  682. "\tfont-size: 18px;\n" +
  683. "\tline-height: 2em;\n" +
  684. "}\n" +
  685. ".title {\n" +
  686. "\tfont-family: 华文新魏,华文魏碑,微软雅黑,文泉驿正黑,苹果丽黑,黑体;\n" +
  687. "\tfont-size: 44px;\n" +
  688. "\tfont-weight: bold;\n" +
  689. "\tmargin: 6 -20px;\n" +
  690. "}\n" +
  691. ".sectionHead {\n" +
  692. "\tfont-family: 微软雅黑,文泉驿正黑,苹果丽黑,黑体;\n" +
  693. "\tfont-size: 19px;\n" +
  694. "\tmargin: 2em 0 0 0;\n" +
  695. "}\n" +
  696. ".sectionHead0, .prefaceHead, .postscriptHead {\n" +
  697. "\tfont-size: 24px;\n" +
  698. "}\n" +
  699. ".sectionHead1 {\n" +
  700. "\tfont-size: 22px;\n" +
  701. "}\n" +
  702. ".sectionHead2 {\n" +
  703. "\tfont-size: 21px;\n" +
  704. "}\n" +
  705. ".sectionHead3 {\n" +
  706. "\tfont-size: 20px;\n" +
  707. "}\n" +
  708. ".h-dec {\n" +
  709. "\tcolor: rgba(0, 0, 0, 0.35);\n" +
  710. "\tmargin: 4px -4px;\n" +
  711. "}\n"
  712. );
  713. if (devmode) {
  714. alert("绘制文本工作完成,耗时:" + (new Date().getTime() - timestamp.getTime()) + "毫秒");
  715. }
  716. //------------------------绘制文本工作完成------------------------------
  717.  
  718. //------------------------绘制索引工作开始-------------------------------
  719. if (devmode) {
  720. timestamp = new Date();
  721. }
  722.  
  723. //绘制索引节点函数
  724. function drawIndex(indexNode, container) {
  725. var indexDiv = document.createElement("div");
  726. var headDiv = document.createElement("div");
  727. var iconSpan = document.createElement("span");
  728. var nameSpan = document.createElement("a");
  729. indexDiv.style.whiteSpace = "nowrap";
  730. if (indexNode.children.length > 0) {
  731. iconSpan.appendChild(document.createTextNode("▶ "));
  732. iconSpan.style.verticalAlign = ".05em";
  733. iconSpan.style.cursor = "pointer";
  734. iconSpan.onclick = function () {
  735. if (childrenDiv.style.display == "none") {
  736. childrenDiv.style.display = "block";
  737. iconSpan.childNodes[0].data = "▼ ";
  738. } else {
  739. childrenDiv.style.display = "none";
  740. iconSpan.childNodes[0].data = "▶ ";
  741. }
  742. }
  743. } else {
  744. iconSpan.appendChild(document.createTextNode("▶ "));
  745. iconSpan.style.verticalAlign = ".05em";
  746. iconSpan.style.visibility = "hidden";
  747. }
  748. nameSpan.appendChild(document.createTextNode(indexNode.name));
  749. nameSpan.style.color = "black";
  750. nameSpan.style.textDecoration = "none";
  751. if (indexNode.dom != null) {
  752. var a = document.createElement("a");
  753. a.name = indexNode.aname;
  754. indexNode.dom.insertBefore(a, indexNode.dom.childNodes[0]);
  755. nameSpan.href = "#" + a.name;
  756. }
  757. nameSpan.style.whiteSpace = "nowrap";
  758. nameSpan.style.width = index_box.style.width.slice(0, -2) + "px";
  759. headDiv.appendChild(iconSpan);
  760. headDiv.appendChild(nameSpan);
  761. headDiv.style.textIndent = new Number(indexNode.level) + "em";
  762. headDiv.style.verticalAlign = "middle";
  763. indexDiv.appendChild(headDiv);
  764. var childrenDiv = document.createElement("div");
  765. childrenDiv.style.display = "none"
  766. indexDiv.appendChild(childrenDiv);
  767. container.appendChild(indexDiv);
  768. for (var i = 0; i < indexNode.children.length; i++) {
  769. drawIndex(indexNode.children[i], childrenDiv);
  770. }
  771. }
  772.  
  773. //绘制索引
  774. for (var i = 0; i < res.index.children.length; i++) {
  775. drawIndex(res.index.children[i], index_box);
  776. }
  777.  
  778. //如果没有索引,则隐藏索引框
  779. if (res.index.children.length == 0) {
  780. index_containter.style.width = "0px";
  781. split_line.style.width = "0px";
  782. pageLayout.style.width = "1004px";
  783. }
  784.  
  785. if (devmode) {
  786. alert("索引生成工作完成,耗时:" + (new Date().getTime() - timestamp.getTime()));
  787. }
  788. //--------------------------------索引生成工作完成--------------------------
  789.  
  790. //--------------------------------关闭后自动恢复功能(HTML5)------------------
  791. var filename = window.location.pathname.split('/').pop();
  792. //保存当前位置
  793. text_containter.onscroll = function () {
  794. localStorage[filename + "-lastTextPosition"] = this.scrollTop;
  795. }
  796. //滚动到上次位置
  797. if (localStorage[filename + "-lastTextPosition"] != null) {
  798. text_containter.scrollTop = localStorage[filename + "-lastTextPosition"];
  799. }
  800. //--------------------------------自动恢复功能完成(HTML5)--------------------