软件包Mojo::DOM::HTML;使用Mojo::Base-Base;使用Exporter qw(导入);使用Mojo::Util qw(html_attr_unescape html_unescape-xml_escape);使用标量::Util qw(弱化);我们的@EXPORT_OK=('tag_to_html');具有树=>sub{['root']};具有“xml”;我的$ATTR_RE=qr/([^<>=\s\/0-9.\-][^<>=\s\/]*|\/)#键(?:\s*=\s*(?s:([“'])(.*?)\g{-2}|([^>\s]*))#值)?\秒*/x;我的$TOKEN_RE=qr/([^<]+)? # 文本(?:<(?:(?:DOCTYPE(文件类型)(\s+\w+#文档类型(?:(?:\s+\w+)?(?:\s+(?:“[^”]*“|'[^']*'))+)?#外部ID(?:\s+\[.+?\])?#内部子集\s*)|--(.*?)--\s*#注释|\[CDATA\[(.*?)\]\]#CDATA)|\?(.*?)\? # 处理说明|\s*((?:\/\s*)?[^<>\s\/0-9.\-][^<>\s\/]*\s*(?:(?:$ATTR_RE){032766})**)#标记)>|(<)#逃跑“<”)??/xis;#仅包含原始文本的HTML元素my%RAW=映射{$_=>1}qw(脚本样式);#仅包含原始文本和实体的HTML元素my%RCDATA=映射{$_=>1}qw(标题文本区域);#带有可选结束标记的HTML元素my%END=(body=>'head',optgroup=>'optgroup',option=>'Options');#打断段落的HTML元素映射{$END{$_}='p'}(qw(地址文章旁块引用详细信息对话框div dl字段集图形标题数字页脚格式h1 h2 h3 h4 h5 h6),qw(标题hgroup hr主菜单nav ol p pre-section table ul));#创建自己范围的容器HTML元素my%SCOPE=映射{$_=>1}qw(数学svg);#带有可选结束标记的HTML表元素my%TABLE=map{$_=>1}qw(列组tbody td tfoot thead tr);#带有可选结束标记和范围规则的HTML元素我的%CLOSE=(li=>[{li=>1},{ul=>1,ol=>1}],tr=>[[{tr=>1{,{table=>1{]);qw(colgroup tbody tfoot thead)的$CLOSE{$_}=[\%TABLE,{TABLE=>1}];qw(dd-dt)的$CLOSE{$_}=[{dd=>1,dt=>1},{dl=>1{];qw(rp-rt)的$CLOSE{$_}=[{rp=>1,rt=>1},{ruby=>1{];qw(td-th)的$CLOSE{$_}=[{th=>1,td=>1},{table=>1{];#HTML父元素在关闭时表示没有更多内容,但也是语法内容my%NO_MORE_CONTENT=(ruby=>[qw(rt rp)],select=>[qw(option optgroup)]);#没有结束标记的HTML元素my%EMPTY=map{$_=>1}qw(区域基础br列嵌入hr输入keygen链接菜单ema元参数源轨迹wbr);#归类为短语内容的HTML元素(和过时的内联元素)my@短语=(qw(缩写区域音频b bdi bdo br按钮画布引用代码数据数据列表del dfn em嵌入i iframe img输入in kbd),qw(keygen标签链接映射标记数学meta meter noscript对象输出图片进度qruby的samp脚本选择),qw(时隙小跨度强sub-sup svg模板文本区域时间u-var视频wbr));my@OBSOLETE=qw(缩写applet basefont-bigh-font-strike tt);我的%PHRASING=map{$_=>1}@OBSOLETE,@PHRASING;#未确认其自动关闭标志的HTML元素我的%BLOCK=地图{$_=>1}(qw(a address applet article aside b big blockquote body按钮标题中心代码col colgroup dd details对话框),qw(dir div dl dt em fieldset图形标题图形字体页脚形式框架集h1 h2 h3 h4 h5 h6头页眉hgroup html),qw(i iframe li列出主选取框菜单nav nobr noembed noframes noscript object ol optgroup option p明文),qw(pre-rp-rts脚本部分选择small strike strong样式摘要表tbody td模板文本区域tfoot-th),qw(标题tr tt u ul xmp));子解析{my($self,$html)=(移位,“$_[0]”);my$xml=$self->xml;my$current=my$tree=['root'];while($html=~/\G$TOKEN_RE/gcso){my($text、$doctype、$comment、$cdata、$pi、$tag、$失控)=($1、$2、$3、$4、$5、$6、$11);#文本(和失控的“<”)$text.=“<”如果定义为$失控;_节点($current,'text',html_unescape$text)(如果定义了$text;#标签if(定义的$tag){#结束if($tag=~/^\/\s*(\s+)/){my$end=$xml$1:lc$1;#没有更多内容if(!$xml&&(my$tags=$NO_MORE_CONTENT{$end}){_end($_,$xml,\$current)for@$tags}_end($end、$xml、\$current);}#开始elsif($标记=~m!^([^\s/]+)([\s\s]*)!){my($start,$attr)=($xml?$1:lc$1,$2);#属性my(%attrs,$closing);while($attr=~/$attr_RE/go){my($key,$value)=($xml?$1:lc$1,$3//$4);#空标签++$closing和next if$key eq'/';$attrs{$key}=定义的$value?html_attr_unescape$值:$value;}#“image”是“img”的别名$start='img'如果$xml&&$start eq“图像”;_开始($start,\%attrs,$xml,\$current);#没有结束标记的元素(自动关闭)_end($start,$xml,\$current)if$xml&&$EMPTY{$start}||($xml||!$BLOCK{$start_})&&$closing;#原始文本元素next if$xml |$原始{$start}&&$RCDATA{$start};下一个,除非$html=~m!\G(.*?))!gcsi;_节点($current,'raw',$RCDATA{$start}?html_unescape$1:$1);_结束($start,0,\$current);}}#DOCTYPE(文件类型)elsif(定义的$doctype){_node($current,'doctype',$doctype')}#注释elsif(定义的$comment){_node($current,'comment',$comment')}#CDATA(CDATA)elsif(定义的$cdata){节点($current,'cdata',$cdata')}#处理指令(尝试检测XML)elsif(定义$pi){$self->xml($xml=1)if!存在$self->{xml}&&$pi=~/xml/i;_节点($current,'pi',$pi);}}返回$self->tree($tree);}子呈现{_render($[0]->树,$[0]->xml)}子标记{shift->tree(['root',_tag(@_)])}子标记to_html{_render(标记(@_),undef)}子结束(_E){my($end,$xml,$current)=@_;#搜索堆栈中的开始标记my$next=当前$$;做{#忽略无用的结束标记如果$next->[0]eq'root',则返回;#不要遍历容器标签如果$SCOPE{$next->[1]}&&$next->[1]ne$end;,则返回;#右标签return$$current=$next->[3]if$next->[1]eq$end;#短语内容只能跨短语内容如果返回$xml&&$PHRASING{$end}&&$语法{$next->[1]};}而$next=$next->[3];}子节点(_N){my($current,$type,$content)=@_;push@$current,my$new=[$type,$content,$current];削弱$new->[2];}子阅读器(_R){my($tree,$xml)=@_;#标签我的$type=$tree->[0];if($type eq“标签”){#开始标记我的$tag=$tree->[1];my$result=“<$tag”;#属性对于我的$键(排序键%{$tree->[2]){我的$value=$tree->[2]{$key};$结果=$xml?qq{$key=“$key”}:除非定义了$value,否则为“$key”和next;$结果=qq{$key=“}.xml_escape($value).'”;}#没有孩子返回$xml?“$result/>”:$EMPTY{$tag}?“$result>”:“$resort>“除非$tree->[4];#儿童没有警告“递归”;$result.=“>”。连接“”,映射{_render($_,$xml)}@$tree[4..$#$tree];#结束标记return“$result";}#文本(转义)如果$type eq'text',则返回xml_escape$tree->[1];#原始文本如果$type eq为“raw”,则返回$tree->[1];#根如果$type eq为“root”,则返回连接“”,映射{_render($_,$xml)}@$tree[1..$#$tree];#DOCTYPE(文件类型)返回'[1] . '>' 如果$type等于'doctype';#注释返回''if$type eq'注释';#CDATA(CDATA)返回'[1] . ']]>' 如果$type eq“cdata”;#加工说明返回'[1] . '?>' 如果$type eq‘pi’;#其他一切返回“”;}子启动(_S){my($start,$attrs,$xml,$current)=@_;#自动关闭可选HTML元素if(!$xml&&$current->[0]ne“根”){if(my$end=$end{$start}){_end($end,0,$current)}elsif(我的$close=$close{$start}){my($allowed,$scope)=@$close;#关闭范围中允许的父元素我的$parent=$$current;while($parent->[0]ne“根”&&!$scope->{$parent->[1]}){_如果$allowed->{$parent->[1]},则结束($parent->[1],0,$current);$parent=$parent->[3];}}}#新标签推送@$$current,我的$new=['tag',$start,$attrs,$$current];削弱$new->[3];$$当前=$新;}子标记(_T){my$tree=['tag',shift,undef,undef];#内容push@$tree,ref$_[-1]eq“CODE”?['raw',pop->()]:['text',pop]如果@_%2;#属性我的$attrs=$tree->[2]={@_};返回$tree除非存在$attrs->{data}&&ref$attrs->{data}eq'HASH';my$data=delete$attrs->{data};@$attrs{map{y/_/-/;lc“data-$_”}键%$data}=值%$data;return$tree;}1;=编码utf8=头1名称Mojo::DOM::HTML-HTML/XML引擎=头1概述使用Mojo::DOM::HTML;#将HTML转换为DOM树my$html=Mojo::DOM::html->new;$html->parse('

测试

123

');我的$tree=$html->tree;=头1描述L(左)是L使用的HTML/XML引擎,基于L和L.=头1功能L(左)实现以下功能,这些功能可以单独导入。=头2标记_to_htmlmy$str=tag_to_html“div”,id=>“foo”,“安全内容”;生成HTML/XML标记并立即呈现。这是L的一个明显更快的替代方案用于模板必须生成大量标记的系统。=头1属性L(左)实现以下属性。=头2树我的$tree=$html->tree;$html=$html->tree(['root']);文档对象模型。请注意,由于此结构非常动态,因此只能非常小心地使用。=头2 xml我的$bool=$html->xml;$html=$html->xml($bool);禁用解析器中的HTML语义并激活区分大小写,默认为基于XML声明的自动检测。=头1方法L(左)从L继承所有方法并实现了以下新功能。=头2解析$html=$html->parse('莫名其妙!');解析HTML/XML片段。=头部2渲染我的$str=$html->render;将DOM渲染为HTML/XML。=head2标签$html=$html->标记('div',id=>'foo','安全内容');生成HTML/XML标记。=头部1另见L(左),L,L.=切割