Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

    • 编程语言

      • CSS

      • HTML

        • LearningHTML
        • htmlparser实现
          • 正则 获取大括号外的内容
          • 正则 匹配<script>xxx</script>外的内容
          • 正则 匹配所有节点的src属性的值
            • 注意:上述匹配<script>xxx</script>外的内容 没有考虑到script的src内容,故规则做了调整
            • 注意:script 和 textarea 里面的内容要忽略
            • textarea 的特殊判断
          • 正则 匹配所有节点带链接的属性的值
        • inline-block 文本宽度溢出问题
        • svg笔记
        • css与js的阻塞关系
        • document-write重写
        • getElementsByClassName遍历时出现的问题
        • 前端小知识-格式化标签
        • 前端监听资源加载错误
        • 浅谈 View Transitions API
        • 通读 HTML Standard
      • JavaScript

      • Rust

      • TypeScript

      • WebAssembly

    • 开发工具

    • 前端调试

    • 浏览器原理

    • 浏览器生态

  • 应用框架

  • 工程能力

  • 应用基础

  • 专业领域

  • 业务场景

  • 大前端
  • 前端基础
  • 编程语言
  • HTML
gahing
2018-11-02
目录

htmlparser实现草稿

# 方法1 全局开闭标签栈

忽略 script 和 textarea 中的内容 不处理

忽略 <xxx/>

忽略格式化标签:a, b, big, code, em, font, i, nobr, s, small, strike, strong, tt, and u. 这些可单独开标签存在

检测到开标签,将其放入栈中

检测到闭标签,弹出开标签栈相应的元素

每次write结束,判断是否存在开标签。若存在,内容继续存入buffer,否则直接原生write输出buffer

存在问题:开闭标签是通过多次write达到的, 此时不易检测标签。

可以unicode转码,判断当前内容是否有< 有的话即使没有开标签也不会直接输出

# 方法2 上下文开闭标签栈

document.write 一段完整的html结构,必须是在一个script中进行的,否则缺失闭标签将导致后续html解析失败

故可以获取当前docwrite的上下文环境,当前script与之前不同,则写入之前的全部。最后一个在window.onload时写入

延后了docwrite的时机,看下有没有办法实时

利用 script onload?

问题:在当前脚本运行时无法注册onload

无法做的话 用原始document.write插入个自己的脚本实现,插入的内容在该<script>之前,最后把该scrite删除 无法实现,write时 会先进行解析而不是运行剩余脚本,采用appendChild试试?

对于firefox 可以采用 document.addEventListener("afterscriptexecute",()=>{})

采用settimeout 0 ? 无法实现,不是每段js运行后运行 而是所有js运行完(一个事件周期内)才运行的

解决方案1:后端修改html,将<script/>带上onload事件。

# script textarea内的内容不处理 其他的进行正则匹配

当当前script中document.write一个script tag 时 闭标签一定要用<\/script> 否则会导致解析器提前结束当前script。

而后 我们获取document.write中内容时,拿到的就是</script>

那么在该write的script中,又write了script时,闭标签要用<\\\/script>

即 第一层是document.write('<script>var a;document.write(\'<scrip src="//whois.pconline.com.cn/jsFunction.jsp?callback=jsShow_476102"><\\\/script>\')<\/script>');

第二层为 document.write('<scrip src="//whois.pconline.com.cn/jsFunction.jsp?callback=jsShow_476102"><\/script>')

故 对于嵌套的script 其实只需要匹配

var matchUrl = ()=>{}
var encodeUrl = ()=>{}
var string = `<script type="text/javascript"><!--
google_ad_client = "ca-pub-9717849951270719";
/* 网易07-300x250 */
google_ad_slot = "8115538508";
google_ad_width = 300;
google_ad_height = 250;
//-->
</script>
<script type="text/javascript"
src="//pagead2.googlesyndication.com/pagead/show_ads.js">
</script>

<script type="text/javascript" > document.write('<script>var a;document.write('<scrip src="//whois.pconline.com.cn/jsFunction.jsp?callback=jsShow_476102"><\\\\/script>')<\\\/script>');
</script><a href="xxx"></a><script type="text/javascript" src="http://cbjs.baidu.com/js/o.js"></script>
<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan style='display:none;' id='cnzz_stat_icon_1256734798'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/stat.php%3Fid%3D1256734798' type='text/javascript'%3E%3C/script%3E"));</script>
<img src='a.jpg'/>`
string = string.replace(/(?<=(<\/script>)|^)[\s|\S]*?((<script[\s\S]*?>)|$)/gi,function(outerScript){
            if(outerScript.length>10){
                var regExp = /(src|href|poster|data|value|url)\s*=\s*(['"]?)(.*?)(['" >])/gmi;
                return outerScript.replace(regExp, function (match, arrt, pre, src, suf) {
                    if (!src.startsWith("<%=") && matchUrl(src)) {
                        if (match.slice(-1) == ">") {
                            return arrt + '=' + pre + encodeUrl(src) + suf + '>';
                        } else {
                            return arrt + '=' + pre + encodeUrl(src) + suf;
                        }
                    } else {
                        return match;
                    }
                });
            }
            return outerScript
        })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

ff、ie不支持 ?<=,可以删去

# 正则 获取大括号外的内容

输入:{sd{999}ee}as{6}ss{9} 匹配: as,ss

思路:匹配}xxx{中的xxx

表达式:/(?<=}|^)[^{}]+(?={|$)/gmi输入:{sd{999}ee}as{6}ss{9} 匹配: as,ss

思路:匹配}xxx{中的xxx

表达式:/(?<=}|^)[^{}]+(?={|$)/gi

# 正则 匹配<script>xxx</script>外的内容

注意由于document.write的存在 会有嵌套

`<iframe src="666.html"/>
<div>9999</div>
<script>
document_write('<script>function jsShow_476102(area){var adList = [{key:'其他地区',value:'<script>document_write(unescape(\'%3C%73%63%72%69%70%74%3E%3C%2F%73%63%72%69%70%74%3E\'));}
<\/script>'}];adList = document_write(getContent(adList,keyList,areaKey,area));}</script>
<scrip src="//whois.pconline.com.cn/jsFunction.jsp?callback=jsShow_476102"></script>');
</script>
<img src="666.js"/><div>9999</div>`.match(/(?<=(<\\?\/script>)|^)((?!<script>)(?!<\\?\/script)[\s|\S])*(?=(<script)|$)/gi)
1
2
3
4
5
6
7
8

# 正则 匹配所有节点的src属性的值

# 注意:上述匹配<script>xxx</script>外的内容 没有考虑到script的src内容,故规则做了调整

/(?<=(<\\?\/script>)|^)((?!<script[\s\S]*>)(?!<\\?\/script)[\s|\S])*((<script[\s\S]*?>)|$)/gi

取消了零宽度正预测先行断言

# 注意:script 和 textarea 里面的内容要忽略

由于textarea和script可以互相嵌套,导致规则变得复杂。

本小结内容忽略,见下一小节

把script比作{},textarea 比作[] ,{} 中可以嵌套[], []中也可以嵌套{}

故可以写出正则表达式:/(?<=}|^|\])[^{}\[\]]+(?={|$|\[)/gi

举例: 111{2{12[444]12}22}333[444]555 可以匹配

但是对于[]和{}同级且还有再外面一层{}或[]就会导致匹配了[]和{}间的内容。

举例:111{2{12[444]1{}2}22}333[444]555 错误匹配

一个思路是,故先用每个规则将[.*]做一个替换 比如换成$$$等唯一字符串存入map,后面等{}处理完后再还原。

js 没有固化分组,如果层数过多,我们应该自己用js写逻辑判断,

 var sc = '(', ec = ')', count = 0, rst = [],c;
    var s = '今年的雨水比较多(除了夏季(夏季(天气)炎热)),降雨量是往年的130%(特别是在江南地区)'
    var l = s.length;
    for (var i = 0; i < l; i++) {
        c = s.charAt(i);
        if (c == sc && count == 0) rst[rst.length] = sc;
        if (count > 0) rst[rst.length - 1] += c;
        if (c == sc) count++;
        else if (c == ec) count--;
    }
1
2
3
4
5
6
7
8
9
10

或者正则循环匹配和替换,

或者采用拓展库(xregexp)

这里的场景 层数最多两层,故我们针对特定两层可以这么写:/\[([^\[\]]*\[[^\[\]]*\][^\[\]]*)*\]/g

# textarea 的特殊判断

document.write(`<textarea>
666<textarea>77</textarea><script></script><img/>
</textarea>`)
1
2
3

结果为:结果为:

<textarea>666&lt;textarea&gt;77</textarea><script></script><img>`
1

而不是

<textarea>666&lt;textarea&gt;77&lt;/textarea&gt;&lt;script&gt;&lt;/script&gt;&lt;img&gt;</textarea>
1

说明·textarea 是尽早的匹配闭标签,那只要 /<textarea>[\s\S]*?<\/textarea>/ig 即可

那么 textarea 的匹配替换就是:

var uuid = 'd3NfdGVzdA=='
var uuidReg = /d3NfdGVzdA==(\d+)\$/gi
var regexOuterScript = /(?<=(<\\?\/script>)|^)((?!<script[\s\S]*>)(?!<\\?\/script)[\s|\S])*((<script[\s\S]*?>)|$)/gi
var regexTA = /<textarea[\s\S]*?>[\s\S]*?<\/textarea>/ig
var source = `<iframe src="666.html"/>
<textarea width=60>
666<textarea><script>console.log(test)</script><img src="test.jpg">77</textarea>
99<script src="666.js"></script>
999
<script>
document_write('<script>function jsShow_476102(area){var adList = [{key:'其他地区',value:'<script>document_write(unescape(\'%3C%73%63%72%69%70%74%3E%3C%2F%73%63%72%69%70%74%3E\'));}
<\/script>'}];adList = document_write(getContent(adList,keyList,areaKey,area));}</script>
<scrip src="//whois.pconline.com.cn/jsFunction.jsp?callback=jsShow_476102"></script>');
</script>
<textarea width=70>
99977</textarea>
<img src="666.js"/><div>9999</div>`
var arr = []
source = source.replace(regexTA,function(match){
  var len = arr.length
  arr.push(match)
  return uuid+len+'$'
})
// 一顿操作后
source = source.replace(uuidReg,function(match,index){
	return arr[index]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

这一顿操作就是:

source = source.replace(regexOuterScript,function(match){

})
1
2
3

# 正则 匹配所有节点带链接的属性的值

/(src|href|poster|data|value|url)\s*=\s*(['"]?)(.*?)(['" >])/gmi

url 的匹配场景:<meta http-equiv="refresh" content="2;URL=http://www.xxx.com/"> 2s后跳转至www.xxx.com

字符串长度<10忽略

编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
LearningHTML
inline-block 文本宽度溢出问题

← LearningHTML inline-block 文本宽度溢出问题→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式