(function (window, document) { 'use strict'; /* ======================================================================== * ★ 自定义区 * 【顺序无需担心】引擎初始化时按原词长度降序排序, 长词永远先匹配: * 如 "二十余" 必先于 "二十" 替换, 替换后残文不会再被短词误切 * ("二十余年"→"多年", 不会出现 "多余年")。 * ====================================================================== */ var CUSTOM_RULES = [ { w: '无可比拟', r: '', note: '自定义-删除' }, { w: '二十余', r: '多', note: '自定义-模糊化, 如 二十余年→多年' }, { w: '三十余', r: '多', note: '自定义-模糊化, 如 三十余年→多年' }, { w: '二十', r: '多', note: '自定义-模糊化, 如 二十个→多个' }, { w: '三十', r: '多', note: '自定义-模糊化, 如 三十个→多个' } ]; /* ======================================================================== * 一、内置规则表 (依据 违禁词2021.docx + 用户自定义词表, 集中管理) * ====================================================================== */ var DEFAULT_RULES = [ /* ---------- 与"最"有关 ---------- */ { w: '最先进加工工艺', r: '先进加工工艺', note: '极限用语' }, { w: '最先进科学', r: '先进科学', note: '极限用语' }, { w: '最大程度', r: '很大程度', note: '极限用语' }, { w: '最佳', r: '优良', note: '极限用语(链式→很好)' }, { w: '最具', r: '颇具', note: '极限用语' }, { w: '最爱', r: '喜爱', note: '极限用语' }, { w: '最赚', r: '', note: '营销词, 删除' }, { w: '最优', r: '优异', note: '极限用语' }, { w: '最优秀', r: '优良', note: '极限用语(链式→很好)' }, { w: '最好', r: '优良', note: '极限用语(链式→很好)' }, { w: '最大', r: '较大', note: '客观技术参数不替换', ka: ['压力', '温度', '位移', '补偿量', '伸缩量', '口径', '通径', '直径', '流速', '流量', '工作', '试验', '爆破', '负压', '正压', '值', '限度', '范围', '载荷', '推力', '扭矩', '长度', '宽度', '高度', '角度', '电压', '电流', '功率', '浓度', '湿度', '密度', '强度', '刚度', '变形', '间隙', '偏差', '精度', '转速', '风量', '扬程', '量程', '使用', '允许', '承受', '耐'] }, { w: '最高级', r: '高级', note: '极限用语' }, { w: '最高档', r: '高端', note: '极限用语' }, { w: '最高', r: '较高', note: '客观技术参数不替换', ka: ['温度', '压力', '工作', '试验', '耐', '值', '限度', '频率', '电压', '转速', '浓度', '湿度', '强度', '精度', '量程', '海拔', '使用', '允许', '承受'] }, { w: '最奢侈', r: '高端', note: '极限用语' }, { w: '最低级', r: '', note: '删除' }, { w: '最低价格', r: '优惠价格', note: '极限用语' }, { w: '最低价', r: '优惠价', note: '极限用语' }, { w: '最低', r: '较低', note: '客观技术参数不替换', ka: ['温度', '压力', '值', '限度', '工作', '使用', '启动', '转速', '浓度', '湿度', '要求', '允许', '承受', '安装'] }, { w: '最底', r: '较低', note: '"最低"别字', ka: ['温度', '压力', '值', '限度', '工作', '使用'] }, { w: '最便宜', r: '实惠', note: '极限用语' }, { w: '时尚最低价', r: '时尚优惠价', note: '极限用语' }, { w: '最流行', r: '流行', note: '极限用语' }, { w: '最受欢迎', r: '广受欢迎', note: '极限用语' }, { w: '最时尚', r: '时尚', note: '极限用语' }, { w: '最聚拢', r: '', note: '化妆品类, 删除' }, { w: '最符合', r: '符合', note: '极限用语' }, { w: '最舒适', r: '舒适', note: '极限用语' }, { w: '最先享受', r: '率先享受', note: '极限用语' }, { w: '最先进', r: '先进', note: '极限用语(链式→好)' }, { w: '最先', r: '率先', note: '极限用语' }, { w: '最后一波', r: '本批', note: '促销时限词' }, { w: '最新科技', r: '新科技', note: '极限用语' }, { w: '最新科学', r: '新科学', note: '极限用语' }, { w: '最新技术', r: '新技术', note: '极限用语' }, { w: '最新工艺', r: '新工艺', note: '极限用语' }, { w: '最新', r: '新型', note: '页面元信息不替换', ka: ['更新', '日期', '时间', '版本'] }, { w: '最', r: '很', note: '用户词表; 常用固定搭配不替换', ka: ['后', '近', '终', '为', '初', '多', '少', '早', '先', '晚', '快', '慢', '短', '长', '远', '高', '低', '重', '轻', '深', '广', '全', '新'] }, /* ---------- 与"一"有关 ---------- */ { w: '中国第一', r: '国内领先', note: '排名类极限用语' }, { w: '全网第一', r: '行业领先', note: '排名类(链式→盛名)' }, { w: '销量第一', r: '销量领先', note: '排名类极限用语' }, { w: '排名第一', r: '排名靠前', note: '排名类极限用语' }, { w: '全国第一', r: '国内领先', note: '排名类极限用语' }, { w: '第一品牌', r: '知名品牌', note: '排名类极限用语' }, { w: '独一无二', r: '特别', note: '用户词表' }, { w: '第一', r: '优选', note: '用户词表; 固定标语不替换', kb: ['安全', '质量', '客户', '服务', '用户', '信誉'] }, { w: '唯一', r: '盛名', note: '用户词表; 技术术语不替换', ka: ['标识', '标识符', '键', '编码', '索引'] }, { w: '一流', r: '颇负盛名', note: '用户词表' }, { w: '仅此一次', r: '本次', note: '促销时限词' }, { w: '仅此一款', r: '本款', note: '促销时限词' }, { w: 'no.1', r: '盛名', note: '用户词表no1; 紧邻型号字符时保留' }, { w: 'no1', r: '盛名', note: '用户词表; 紧邻型号字符时保留' }, { w: 'top.1', r: '盛名', note: '用户词表top1; 紧邻型号字符时保留' }, { w: 'top1', r: '盛名', note: '用户词表; 紧邻型号字符时保留' }, /* ---------- 与"级/极"有关 ---------- */ { w: '国家级产品', r: '盛名产品', note: '极限用语' }, { w: '国家级', r: '盛名', note: '用户词表; 所颁资质不替换且锁定防穿透', ka: ['高新技术', '资质', '认证', '证书', '奖项', '荣誉', '项目', '实验室', '技术中心', '检测中心', '标准', '工程', '平台', '基地', '人才'] }, { w: '全球级', r: '盛名', note: '用户词表(世界级同类)' }, { w: '宇宙级', r: '', note: '删除' }, { w: '世界级', r: '盛名', note: '用户词表' }, { w: '顶级工艺', r: '精湛工艺', note: '极限用语' }, { w: '顶级享受', r: '高端享受', note: '极限用语' }, { w: '顶级', r: '优异', note: '用户词表' }, { w: '顶尖', r: '卓越', note: '用户词表' }, { w: '尖端', r: '卓越', note: '用户词表' }, { w: '极品', r: '卓越', note: '用户词表' }, { w: '极佳', r: '很好', note: '用户词表' }, { w: '绝佳', r: '优良', note: '极限用语(链式→很好)' }, { w: '绝对', r: '真的', note: '用户词表; 物理概念不替换', ka: ['压力', '温度', '值', '湿度', '精度', '误差', '坐标', '零'] }, { w: '终极', r: '好', note: '用户词表' }, { w: '极致', r: '很好', note: '用户词表' }, /* ---------- 与"首/家/国"及品牌有关 ---------- */ { w: '全国销量冠军', r: '销量领先', note: '排名类极限用语' }, { w: '全网销量第一', r: '销量领先', note: '排名类极限用语' }, { w: '销量冠军', r: '销量领先', note: '排名类极限用语' }, { w: '全国首发', r: '新品发布', note: '首发类极限用语' }, { w: '全球首发', r: '新品发布', note: '首发类极限用语' }, { w: '全网首发', r: '新品发布', note: '首发类极限用语' }, { w: '填补国内空白', r: '颇具特色', note: '极限用语' }, { w: '中国驰名', r: '盛名', note: '用户词表' }, { w: '中国驰名商标', r: '知名品牌', note: '明确禁用' }, { w: '驰名商标', r: '知名品牌', note: '明确禁用' }, { w: '行业领先', r: '盛名', note: '用户词表' }, { w: '遥遥领先', r: '盛名', note: '用户词表' }, { w: '世界领先', r: '盛名', note: '避免链式产生"行业好"' }, { w: '国家免检', r: '质量可靠', note: '违禁权威词' }, { w: '国家领导人', r: '', note: '违禁权威词, 删除' }, { w: '独家配方', r: '专有配方', note: '独家类' }, { w: '独家', r: '全力', note: '用户词表; 商业事实表述不替换', ka: ['代理', '经销商', '授权', '经营', '合作'] }, { w: '首个', r: '早期', note: '用户词表' }, { w: '首家', r: '优选', note: '用户词表' }, { w: '首创', r: '开创', note: '用户词表' }, { w: '首选', r: '优选', note: '首类' }, { w: '首款', r: '新款', note: '用户词表' }, { w: '首席', r: '负责人', note: '用户词表' }, { w: '领袖品牌', r: '知名品牌', note: '品牌类极限用语' }, { w: '领导品牌', r: '知名品牌', note: '品牌类极限用语' }, { w: '创领品牌', r: '知名品牌', note: '品牌类极限用语' }, { w: '领先上市', r: '成功上市', note: '品牌类极限用语' }, { w: '领导者', r: '优异者', note: '用户词表' }, { w: '缔造者', r: '专业制造商', note: '品牌类极限用语' }, { w: '掌门人', r: '优异者', note: '用户词表' }, { w: '王牌', r: '执掌人', note: '用户词表' }, { w: '至尊', r: '优异', note: '用户词表' }, { w: '巅峰', r: '峰顶', note: '用户词表' }, { w: '领袖', r: '专业', note: '用户词表' }, { w: '领衔', r: '盛名', note: '用户词表' }, { w: '问鼎', r: '盛名', note: '用户词表' }, { w: '之王', r: '盛名', note: '用户词表' }, { w: '王者', r: '盛名', note: '用户词表' }, { w: '冠军', r: '盛名', note: '用户词表' }, { w: '金牌', r: '好', note: '用户词表' }, { w: '名牌', r: '', note: '用户词表-删除' }, { w: '领导', r: '优异', note: '用户词表' }, { w: '优秀', r: '优良', note: '所获荣誉/人物表述不替换(链式→很好)', ka: ['奖', '奖项', '称号', '证书', '员工', '个人', '事迹', '团队', '企业', '供应商', '合作伙伴', '作品', '论文', '代表'] }, { w: '权威', r: '资深', note: '用户词表' }, /* ---------- 与虚假有关 ---------- */ { w: '史无前例', r: '少见', note: '用户词表' }, { w: '前无古人', r: '少见', note: '用户词表' }, { w: '绝无仅有', r: '少见', note: '虚假类' }, { w: '永久', r: '长久', note: '用户词表; 客观技术术语不替换', ka: ['变形', '位移', '标记', '记录', '保存', '存储'] }, { w: '万能', r: '全能', note: '用户词表; 产品通用名称不替换', ka: ['接头', '法兰', '联轴器', '卡箍', '接口', '适配器'] }, { w: '祖传', r: '古方', note: '用户词表' }, { w: '特效', r: '显著', note: '虚假类' }, { w: '无敌', r: '必胜', note: '用户词表' }, { w: '纯天然', r: '天然', note: '虚假类' }, { w: '百分百', r: '百分之九十九', note: '用户词表' }, { w: '100%', r: '99.9%', note: '用户词表' }, { w: '完美', r: '好', note: '用户词表' }, /* ---------- 与欺诈/诱导消费有关 ---------- */ { w: '点击领奖', r: '领奖', note: '欺诈类, 去"点击"' }, { w: '恭喜获奖', r: '获奖', note: '欺诈类' }, { w: '全民免单', r: '免单', note: '欺诈类' }, { w: '点击有惊喜', r: '有惊喜', note: '欺诈类, 去"点击"' }, { w: '点击获取', r: '获取', note: '欺诈类, 去"点击"' }, { w: '点击转身', r: '', note: '欺诈类, 删除' }, { w: '点击试穿', r: '试穿', note: '欺诈类, 去"点击"' }, { w: '点击翻转', r: '', note: '欺诈类, 删除' }, { w: '领取奖品', r: '领取', note: '欺诈类' }, { w: '再不抢就没了', r: '', note: '诱导类, 删除' }, { w: '不会更便宜了', r: '', note: '诱导类, 删除' }, { w: '错过就没机会了', r: '', note: '诱导类, 删除' }, { w: '万人疯抢', r: '争先抢', note: '用户词表' }, { w: '全民疯抢', r: '争先抢', note: '用户词表' }, { w: '秒杀', r: '瞬秒', note: '用户词表' }, { w: '抢爆', r: '争先抢', note: '用户词表' }, { w: '抢购', r: '选购', note: '诱导类' }, { w: '抢疯了', r: '热销', note: '诱导类' }, { w: '卖疯了', r: '热销', note: '诱导类' }, { w: '供不应求', r: '', note: '用户词表-删除' }, /* ---------- 与时间有关 ---------- */ { w: '随时结束', r: '', note: '时限类, 删除' }, { w: '随时涨价', r: '', note: '时限类, 删除' }, { w: '马上降价', r: '降价', note: '时限类, 去"马上"' }, /* ---------- 违禁权威性词语 ---------- */ { w: '质量免检', r: '质量可靠', note: '明确禁用' }, { w: '无需国家质量检测', r: '质量可靠', note: '明确禁用' }, { w: '免抽检', r: '', note: '明确禁用, 删除' }, { w: '免检', r: '', note: '用户词表-删除' }, { w: '老字号', r: '老牌', note: '需认证类' }, { w: '特供', r: '供应', note: '用户词表' }, { w: '专供', r: '专用', note: '明确禁用' }, /* ---------- 认证/体系/标准号类 (用户词表-删除) ---------- */ { w: '职业健康安全管理', r: '', note: '用户词表-删除' }, { w: 'ISO14001', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: 'ISO45001', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '9001-2008', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '9001-2000', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '9001:2000', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '2008标准', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: 'T9001', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '9001-', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '-2008', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: ':2018', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: ':2015', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: '9001', r: '', nb: true, note: '配套删除, 覆盖 ISO9001 等残号' }, { w: 'GB/T', r: '', nb: true, note: '用户词表-删除(连号)' }, { w: 'ISO', r: '', nb: true, note: '用户词表-删除(含iso大小写)' }, { w: '质量体系', r: '', note: '用户词表-删除' }, { w: '环境体系', r: '', note: '用户词表-删除' }, { w: '质量认证', r: '', note: '用户词表-删除' }, { w: '环境认证', r: '', note: '用户词表-删除' }, { w: '质量管理', r: '', note: '用户词表-删除' }, { w: '环境管理', r: '', note: '用户词表-删除' }, { w: '认证证书', r: '', note: '用户词表-删除' }, { w: '专精特新', r: '', note: '用户词表-删除' }, { w: '高新企业', r: '', note: '用户词表-删除' }, { w: '高科技', r: '', note: '用户词表-删除' }, { w: '严格执行', r: '', note: '用户词表-删除' }, { w: '松江牌', r: '', note: '用户词表-删除' }, { w: '松江区', r: '', note: '用户词表-删除' }, { w: '淞江牌', r: '', note: '用户词表-删除' }, { w: '淞江区', r: '', note: '用户词表-删除' }, { w: '高新', r: '', note: '用户词表-删除' }, { w: '松江', r: '', note: '用户词表-删除' }, { w: '淞江', r: '', note: '用户词表-删除' }, { w: '专利', r: '', note: '用户词表-删除' }, { w: '体系', r: '', note: '用户词表-删除' }, { w: '认证', r: '', note: '用户词表-删除' }, { w: '国际', r: '', note: '用户词表-删除' }, { w: '国家', r: '', note: '用户词表-删除; 受保护间隔锁定不穿透(如国家级高新技术企业)' }, { w: '彻底', r: '', note: '用户词表-删除' }, { w: '一切', r: '', note: '用户词表-删除' }, { w: '任何', r: '', note: '用户词表-删除' }, /* ---------- 附录补充 ---------- */ { w: '国际品质', r: '优良品质', note: '无法考证类(链式→很好品质)' }, { w: '领导品牌', r: '知名品牌', note: '无法考证类' }, { w: '大牌', r: '好', note: '用户词表' }, { w: '著名', r: '知名', note: '无法考证类' }, { w: '奢侈', r: '高端', note: '无法考证类' }, { w: '高档', r: '优质', note: '夸张表述(链式→很好)' }, { w: '正品', r: '品质', note: '无法判断真伪' }, { w: '精确', r: '精准', note: '客观技术表述不替换', ka: ['度', '到', '测量', '控制', '定位', '数据', '数值', '计算', '加工', '匹配'] }, { w: '超赚', r: '', note: '删除' }, { w: '巨星', r: '明星', note: '无法考证类' }, { w: '优良', r: '很好', note: '用户词表' }, { w: '优质', r: '很好', note: '用户词表' }, { w: '先进', r: '好', note: '用户词表' }, { w: '全网', r: '各平台', note: '用户词表' }, /* ---------- 疑似医疗/化妆品/迷信用语 (与管道行业无关, 命中即删除) ---------- */ { w: '延年益寿', r: '', note: '医疗/化妆品类, 删除' }, { w: '提高免疫力', r: '', note: '医疗类, 删除' }, { w: '增强免疫力', r: '', note: '医疗类, 删除' }, { w: '免疫力', r: '', note: '医疗类, 删除' }, { w: '消炎', r: '', note: '医疗类, 删除' }, { w: '抗炎', r: '', note: '医疗类, 删除' }, { w: '抗菌', r: '', note: '医疗类, 删除' }, { w: '杀菌', r: '', note: '医疗类, 删除' }, { w: '灭菌', r: '', note: '医疗类, 删除' }, { w: '消毒', r: '', note: '医疗类, 删除' }, { w: '排毒', r: '', note: '医疗类, 删除' }, { w: '解毒', r: '', note: '医疗类, 删除' }, { w: '活血', r: '', note: '医疗类, 删除' }, { w: '抗癌', r: '', note: '医疗类, 删除' }, { w: '防癌', r: '', note: '医疗类, 删除' }, { w: '治疗', r: '', note: '医疗类, 删除' }, { w: '治愈', r: '', note: '医疗类, 删除' }, { w: '减肥', r: '', note: '医疗/化妆品类, 删除' }, { w: '瘦身', r: '', note: '化妆品类, 删除' }, { w: '助眠', r: '', note: '医疗类, 删除' }, { w: '安神', r: '', note: '医疗类, 删除' }, { w: '生发', r: '', note: '医疗类, 删除' }, { w: '止脱', r: '', note: '医疗类, 删除' }, { w: '祛疤', r: '', note: '医疗类, 删除' }, { w: '降血压', r: '', note: '医疗类, 删除' }, { w: '调节内分泌', r: '', note: '医疗类, 删除' }, { w: '无毒', r: '', note: '删除(如确为材质客观指标, 请用 data-rtg-keep 豁免该段落)' }, { w: '招财进宝', r: '', note: '迷信用语, 删除' }, { w: '逢凶化吉', r: '', note: '迷信用语, 删除' }, { w: '时来运转', r: '', note: '迷信用语, 删除' }, { w: '万事亨通', r: '', note: '迷信用语, 删除' }, { w: '助吉避凶', r: '', note: '迷信用语, 删除' }, { w: '转富招福', r: '', note: '迷信用语, 删除' } /* 说明: 单字"一"及"今天/今日/周末/仅限/倒计时/周年庆/高效/安全"等日常无害词 不纳入替换表, 避免破坏正常语句。 */ ]; /* ======================================================================== * 二、引擎配置 * ====================================================================== */ var config = { debug: false }; if (window.RISK_TEXT_CONFIG && window.RISK_TEXT_CONFIG.debug) { config.debug = true; } if (window.location && window.location.search && window.location.search.indexOf('rtgDebug') > -1) { config.debug = true; } /* 受保护的标签: 其中的文本节点一律不处理 (脚本/样式/代码/表单值/媒体等) */ var PROTECT_TAGS = { SCRIPT: 1, STYLE: 1, NOSCRIPT: 1, TEMPLATE: 1, TEXTAREA: 1, INPUT: 1, SELECT: 1, OPTION: 1, DATALIST: 1, CODE: 1, PRE: 1, KBD: 1, SAMP: 1, VAR: 1, MATH: 1, SVG: 1, CANVAS: 1, IFRAME: 1, VIDEO: 1, AUDIO: 1, SOURCE: 1, TRACK: 1, EMBED: 1, OBJECT: 1, PARAM: 1, MAP: 1, AREA: 1, PROGRESS: 1, METER: 1, OUTPUT: 1 }; var URL_RE = /https?:\/\//i; /* 文本中含 URL 的节点不处理 */ var LATIN_RE = /[a-z0-9%.\-]/i; /* 原词含字母/数字/符号时启用词边界保护 */ var BOUNDARY_RE = /[a-z0-9%.\-]/i; /* 型号/参数边界字符 */ var RULES = []; var observer = null; var pendingTimer = null; /* ======================================================================== * 三、规则初始化 (长词优先 + 外部扩展合并) * ====================================================================== */ function normalizeRule(rule) { return { w: rule.w, wl: String(rule.w).toLowerCase(), r: (typeof rule.r === 'string') ? rule.r : '', /* r 缺省/null 均视为删除 */ ka: rule.ka || null, kb: rule.kb || null, nb: !!rule.nb, /* true=关闭边界保护 */ note: rule.note || '', latin: LATIN_RE.test(rule.w) }; } function mergeRules(list) { var i; for (i = 0; i < list.length; i++) { if (list[i] && list[i].w) { RULES.push(normalizeRule(list[i])); } } RULES.sort(function (a, b) { return b.w.length - a.w.length; }); } mergeRules(CUSTOM_RULES); /* 自定义区优先合并 */ mergeRules(DEFAULT_RULES); if (window.RISK_TEXT_RULES_EXTRA && window.RISK_TEXT_RULES_EXTRA.length) { mergeRules(window.RISK_TEXT_RULES_EXTRA); } /* ======================================================================== * 四、核心替换逻辑 (v1.2: 硬替换 + 链式替换 + 保护间隔防穿透) * ====================================================================== */ /* 匹配区间 [s,e) 是否与任一保护间隔相交 */ function intersects(prot, s, e) { for (var i = 0; i < prot.length; i++) { if (s < prot[i][1] && prot[i][0] < e) { return true; } } return false; } function replaceInText(text) { var lower = text.toLowerCase(); var prot = []; /* 保护间隔(当前文本坐标): 被 ka/kb 锁定的区间, 短词不得穿透 */ var i, k, idx, end, out, last, skip, guarded, pre, post; for (i = 0; i < RULES.length; i++) { var rule = RULES[i]; idx = lower.indexOf(rule.wl); if (idx === -1) { continue; } out = ''; last = 0; var segs = []; /* 未替换段映射 [旧起, 旧止, 新起], 用于保护间隔坐标重映射 */ var guardedNow = []; /* 本轮被 ka/kb 锁定的区间(旧坐标) */ while (idx !== -1) { end = idx + rule.w.length; skip = intersects(prot, idx, end); guarded = false; var lockStart = idx, lockEnd = end; /* 保护1: 含字母/数字的原词, 若紧邻型号/参数字符则跳过 (nb 可关闭) */ if (!skip && rule.latin && !rule.nb) { if (BOUNDARY_RE.test(text.charAt(idx - 1)) || BOUNDARY_RE.test(text.charAt(end))) { skip = true; } } /* 保护2: 前接固定语境 (kb) 锁定, 如 安全第一 v1.2.1: 锁定整个短语(前接词+匹配词), 防止短词穿透前接词 */ if (!skip && rule.kb) { pre = text.slice(idx - 6 < 0 ? 0 : idx - 6, idx); for (k = 0; k < rule.kb.length; k++) { if (pre.slice(pre.length - rule.kb[k].length) === rule.kb[k]) { skip = true; guarded = true; lockStart = idx - rule.kb[k].length; break; } } } /* 保护3: 后接技术/客观语境 (ka) 锁定, 如 最大工作压力 v1.2.1: 锁定整个短语(匹配词+后接词), 防止短词穿透后接词 */ if (!skip && rule.ka) { post = text.slice(end, end + 10); for (k = 0; k < rule.ka.length; k++) { if (post.indexOf(rule.ka[k]) === 0) { skip = true; guarded = true; lockEnd = end + rule.ka[k].length; break; } } } if (skip) { if (guarded) { guardedNow.push([lockStart, lockEnd]); } /* 锁定整个短语区间 */ } else { segs.push([last, idx, out.length]); out += text.slice(last, idx) + rule.r; /* rule.r 为 '' 时即删除原词 */ last = end; } idx = lower.indexOf(rule.wl, end); } if (last > 0) { segs.push([last, text.length, out.length]); /* 尾段映射 */ /* 将旧保护间隔 + 本轮新锁定区间 重映射到新文本坐标 */ var src = prot.concat(guardedNow); var newProt = []; for (k = 0; k < src.length; k++) { var ps = src[k][0], pe = src[k][1]; for (var s2i = 0; s2i < segs.length; s2i++) { var sg = segs[s2i]; if (ps >= sg[0] && pe <= sg[1]) { newProt.push([sg[2] + (ps - sg[0]), sg[2] + (pe - sg[0])]); break; } } } prot = newProt; text = out + text.slice(last); lower = text.toLowerCase(); } else if (guardedNow.length) { prot = prot.concat(guardedNow); } } return text; } /* 判断元素是否处于受保护区域 (脚本/样式/代码/表单 或 data-rtg-keep 标记块) */ function isProtectedEl(el) { while (el && el.nodeType === 1 && el !== document.body && el !== document.documentElement) { if (PROTECT_TAGS[el.tagName]) { return true; } if (el.hasAttribute && (el.hasAttribute('data-rtg-keep') || (' ' + (el.className || '') + ' ').indexOf(' rtg-keep ') > -1)) { return true; } el = el.parentNode; } return false; } /* 遍历子树, 替换所有可见文本节点 */ function processTree(root) { if (!root) { return; } var SHOW_TEXT = (window.NodeFilter && NodeFilter.SHOW_TEXT) || 4; var walker = document.createTreeWalker(root, SHOW_TEXT, null, false); var nodes = []; while (walker.nextNode()) { nodes.push(walker.currentNode); } var changes = []; var i, node, text, parent, newText; for (i = 0; i < nodes.length; i++) { node = nodes[i]; text = node.nodeValue; if (!text || text.length < 2) { continue; } if (!/[^\s]/.test(text)) { continue; } /* 纯空白跳过 */ if (URL_RE.test(text)) { continue; } /* 含URL文本跳过 */ parent = node.parentNode; if (!parent || parent.nodeType !== 1) { continue; } if (isProtectedEl(parent)) { continue; } newText = replaceInText(text); if (newText !== text) { changes.push([node, newText]); } } /* 先断开监听再写回, 避免自身修改触发死循环 */ if (changes.length) { if (observer) { observer.disconnect(); } for (i = 0; i < changes.length; i++) { changes[i][0].nodeValue = changes[i][1]; } if (observer) { startObserve(); } if (config.debug && window.console && console.log) { console.log('[risk-text-guard] 本次替换文本节点 ' + changes.length + ' 个'); } } } /* ======================================================================== * 五、动态内容监听 (AJAX / 懒加载 / SPA 局部刷新) * ====================================================================== */ function startObserve() { if (!window.MutationObserver) { return; } if (!observer) { observer = new MutationObserver(function (muts) { if (pendingTimer) { clearTimeout(pendingTimer); } pendingTimer = setTimeout(function () { pendingTimer = null; var i, j, m, added; for (i = 0; i < muts.length; i++) { m = muts[i]; if (!m.addedNodes) { continue; } for (j = 0; j < m.addedNodes.length; j++) { added = m.addedNodes[j]; if (added.nodeType === 1) { processTree(added); } else if (added.nodeType === 3 && added.parentNode && added.parentNode.nodeType === 1 && !isProtectedEl(added.parentNode)) { var t = replaceInText(added.nodeValue); if (t !== added.nodeValue) { observer.disconnect(); added.nodeValue = t; startObserve(); } } } } }, 200); }); } observer.observe(document.documentElement, { childList: true, subtree: true }); } /* ======================================================================== * 六、启动与对外 API * ====================================================================== */ function boot() { processTree(document.body); startObserve(); } if (document.readyState === 'loading') { if (document.addEventListener) { document.addEventListener('DOMContentLoaded', boot); } else { document.attachEvent('onreadystatechange', function () { if (document.readyState !== 'loading') { boot(); } }); } } else { boot(); } if (window.addEventListener) { window.addEventListener('load', function () { processTree(document.body); }); } window.RiskTextGuard = { version: '1.2.0', /* 手动对指定容器执行一次替换, 如 RiskTextGuard.run(document.getElementById('news')) */ run: function (root) { processTree(root || document.body); }, /* 运行时追加规则, 如 RiskTextGuard.addRules([{w:'xx', r:'yy'}]) , r:'' 为删除 */ addRules: function (list) { if (list && list.length) { mergeRules(list); } }, setDebug: function (v) { config.debug = !!v; }, ruleCount: function () { return RULES.length; } }; }(window, document));