Mojo::DOM

Mojo::DOM 是一个有CSS选择器支持的,简约的HTML/XML DOM解析器。它会尝试解释不正常的HTML和XML,所以你不应该使用它进行HTML和XML语法正确性的验证。

简介

当我们解析HTML/XML片段时,它会变成一个节点树。目前有八种不同类型的节点,它们分别是:cdata、comment、doctype、pi、raw、root、tag、text。元素是类型为tag的节点。

虽然所有节点类型都表示为Mojo::DOM对象,但像"attr"和"namespace"这样的方法只适用于元素操作。

<!DOCTYPE html>
<html>
  <head><title>Hello</title></head>
  <body>World!</body>
</html>         

## 以上html代码会被解析成如下节点树

root
|- doctype (html)
+- tag (html)
   |- tag (head)
   |  +- tag (title)
   |     +- raw (Hello)
   +- tag (body)
      +- text (World!)

大小写敏感

Mojo::DOM 默认为HTML语义,也就是说所有标签和属性名都是小写的,并且选择器也是小写的。

# HTML semantics
my $dom = Mojo::DOM->new('<P ID="greeting">Hi!</P>');
say $dom->at('p[id]')->text;

如果发现代码中有XML声明,解析器将自动切换到XML模式,并且所有内容都会区分大小写。

# XML semantics
my $dom = Mojo::DOM->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
say $dom->at('P[ID]')->text;

使用xml方法可以强制使用HTML或XML模式。

# Force HTML semantics
my $dom = Mojo::DOM->new->xml(0)->parse('<P ID="greeting">Hi!</P>');
say $dom->at('p[id]')->text;

# Force XML semantics
my $dom = Mojo::DOM->new->xml(1)->parse('<P ID="greeting">Hi!</P>');
say $dom->at('P[ID]')->text;

方法

Mojo::DOM 模块中实现了以下方法。

all_text

my $text = $dom->all_text;

从此元素的所有后代节点提取文本内容。

# "foo\nbarbaz\n"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;

ancestors

my $collection = $dom->ancestors;
my $collection = $dom->ancestors('div ~ p');

查找与CSS选择器匹配的此节点的所有祖先元素,并将包含这些元素的Mojo::Collection对象返回为Mojo::DOM对象。支持Mojo::DOM::CSS中的SELECTORS介绍的所有选择器。

# List tag names of ancestor elements
say $dom->ancestors->map('tag')->join("\n");

append

$dom = $dom->append('<p>I ♥ Mojolicious!</p>');

此方法可以在除root节点之外的其他所以类型的节点后面添加HTML/XML代码片段。

# "<div><h1>Test</h1><h2>123</h2></div>"
$dom->parse('<div><h1>Test</h1></div>') ->at('h1')->append('<h2>123</h2>')->root;

# "<p>Test 123</p>"
$dom->parse('<p>Test</p>')->at('p') ->child_nodes->first->append(' 123')->root;

append_content

$dom = $dom->append_content('<p>I ♥ Mojolicious!</p>');

在root或tag类型节点的内容后面添加HTML/XML代码片段或“原始内容”。

# "<div><h1>Test123</h1></div>"
$dom->parse('<div><h1>Test</h1></div>') ->at('h1')->append_content('123')->root;

# "<!-- Test 123 --><br>"
$dom->parse('<!-- Test --><br>')->child_nodes->first->append_content('123 ')->root;

# "<p>Test<i>123</i></p>"
$dom->parse('<p>Test</p>')->at('p')->append_content('<i>123</i>')->root;

at

my $result = $dom->at('div ~ p');

查找$dom后代元素中与CSS选择器匹配的元素,并把查找到的第一个符合要求的元素作为Mojo::DOM对象返回。当找不到符合要求的元素时返回undef。支持Mojo::DOM::CSS中所有的选择器(SELECTORS)。

# Find first element with "svg" namespace definition
my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};

attr

my $hash = $dom->attr;
my $foo  = $dom->attr('foo');
$dom     = $dom->attr({foo => 'bar'});
$dom     = $dom->attr(foo => 'bar');

对元素属性的操作,可以获取属性值,设置属性值,删除属性值。

# Remove an attribute
delete $dom->attr->{id};

# Attribute without value
$dom->attr(selected => undef);

# List id attributes
say $dom->find('*')->map(attr => 'id')->compact->join("\n");

child_nodes

my $collection = $dom->child_nodes;

返回 Mojo::Collection对象,该对象包含的是以Mojo::DOM对象存在的$dom元素的所有子节点。

# "<p><b>123</b></p>"
$dom->parse('<p>Test<b>123</b></p>')->at('p')->child_nodes->first->remove;

# "<!DOCTYPE html>"
$dom->parse('<!DOCTYPE html><b>123</b>')->child_nodes->first;

# " Test "
$dom->parse('<b>123</b><!-- Test -->')->child_nodes->last->content;

children

my $collection = $dom->children;
my $collection = $dom->children('div ~ p');

查找与CSS选择器匹配的$dom元素的所有子元素。返回一个Mojo::Collection对象,其中包含的是以Mojo::DOM对象表示的符合要求的元素。支持Mojo::DOM::CSS的SELECTORS中介绍的所有选择器。

# Show tag name of random child element
say $dom->children->shuffle->first->tag;

content

my $str = $dom->content;
$dom    = $dom->content('<p>I ♥ Mojolicious!</p>');

返回$dom元素的内容,或将$dom中的内容替换为指定的HTML/XML代码片段或原始内容。仅针对root和tag类型的元素有效。

# "<b>Test</b>"
$dom->parse('<div><b>Test</b></div>')->at('div')->content;

# "<div><h1>123</h1></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('123')->root;

# "<p><i>123</i></p>"
$dom->parse('<p>Test</p>')->at('p')->content('<i>123</i>')->root;

# "<div><h1></h1></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('')->root;

# " Test "
$dom->parse('<!-- Test --><br>')->child_nodes->first->content;

# "<div><!-- 123 -->456</div>"
$dom->parse('<div><!-- Test -->456</div>')->at('div')->child_nodes->first->content(' 123 ')->root;

descendant_nodes

my $collection = $dom->descendant_nodes;

返回一个Mojo::Collection对象,该对象中包含的是以Mojo::DOM对象表示的$dom元素的所有后代节点。

# "<p><b>123</b></p>"
$dom->parse('<p><!-- Test --><b>123<!-- 456 --></b></p>')
->descendant_nodes->grep(sub { $_->type eq 'comment' })
->map('remove')->first;

# "<p><b>test</b>test</p>"
$dom->parse('<p><b>123</b>456</p>')
->at('p')->descendant_nodes->grep(sub { $_->type eq 'text' })
->map(content => 'test')->first->root;

find

my $collection = $dom->find('div ~ p');

查找与CSS选择器匹配的$dom元素的所有后代元素。返回一个Mojo::Collection对象,其中包含的是以Mojo::DOM对象表示的符合要求的元素。支持Mojo::DOM::CSS的SELECTORS中介绍的所有选择器。

# Find a specific element and extract information
my $id = $dom->find('div')->[23]{id};

# Extract information from multiple elements
my @headers = $dom->find('h1, h2, h3')->map('text')->each;

# Count all the different tags
my $hash = $dom->find('*')->reduce(sub { $a->{$b->tag}++; $a }, {});

# Find elements with a class that contains dots
my @divs = $dom->find('div.foo\.bar')->each;

following

my $collection = $dom->following;
my $collection = $dom->following('div ~ p');

查找与CSS选择器匹配的$dom元素之后的所有同级元素。返回一个Mojo::Collection对象,其中包含的是以Mojo::DOM对象表示的符合要求的元素。支持Mojo::DOM::CSS的SELECTORS中介绍的所有选择器。

# List tags of sibling elements after this node
say $dom->following->map('tag')->join("\n");

following_nodes

my $collection = $dom->following_nodes;

返回一个Mojo::Collection对象,其中包含以Mojo::DOM对象表示的$dom节点之后的所有兄弟节点。

# "C"
$dom->parse('<p>A</p><!-- B -->C')->at('p')->following_nodes->last->content;

matches

my $bool = $dom->matches('div ~ p');

查询当前元素是否与CSS选择器匹配。支持Mojo::DOM::CSS的SELECTORS中介绍的所有选择器。

# True
$dom->parse('<p class="a">A</p>')->at('p')->matches('.a');
$dom->parse('<p class="a">A</p>')->at('p')->matches('p[class]');

# False
$dom->parse('<p class="a">A</p>')->at('p')->matches('.b');
$dom->parse('<p class="a">A</p>')->at('p')->matches('p[id]');

namespace

my $namespace = $dom->namespace;

返回当前元素的命名空间,如果找不到则会把undef。

# Find namespace for an element with namespace prefix
my $namespace = $dom->at('svg > svg\:circle')->namespace;

# Find namespace for an element that may or may not have a namespace prefix
my $namespace = $dom->at('svg > circle')->namespace;

new

my $dom = Mojo::DOM->new;
my $dom = Mojo::DOM->new('<foo bar="baz">I ♥ Mojolicious!</foo>');

构建一个新的基于标题的Mojo::DOM对象,并在必要时解析HTML/XML代码片段。

next

my $sibling = $dom->next;

以Mojo::DOM对象的形式返回下一个同级元素,如果没有则返回undef。

# "<h2>123</h2>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;

next_node

my $sibling = $dom->next_node;

为下一个兄弟节点返回Mojo::DOM对象,如果找不到则返回undef。

# "456"
$dom->parse('<p><b>123</b><!-- Test -->456</p>')->at('b')->next_node->next_node;

# " Test "
$dom->parse('<p><b>123</b><!-- Test -->456</p>')->at('b')->next_node->content;

parent

my $parent = $dom->parent;

找到当前节点的父节点,并以Mojo::DOM对象返回,如果没有父节点则返回undef。

# "<b><i>Test</i></b>"
$dom->parse('<p><b><i>Test</i></b></p>')->at('i')->parent;

parse

$dom = $dom->parse('<foo bar="baz">I ♥ Mojolicious!</foo>');

使用Mojo::DOM::HTML解析HTML/XML片段。

# Parse XML
my $dom = Mojo::DOM->new->xml(1)->parse('<foo>I ♥ Mojolicious!</foo>');

prepend

$dom = $dom->prepend('<p>I ♥ Mojolicious!</p>');

在此节点之前添加HTML/XML代码片段(除root节点外,其他所有节点都可以使用些方法)。

# "<div><h1>Test</h1><h2>123</h2></div>"
$dom->parse('<div><h2>123</h2></div>')->at('h2')->prepend('<h1>Test</h1>')->root;

# "<p>Test 123</p>"
$dom->parse('<p>123</p>')->at('p')->child_nodes->first->prepend('Test ')->root;

prepend_content

$dom = $dom->prepend_content('<p>I ♥ Mojolicious!</p>');

将HTML/XML代码片段或原始内容添加到root或tag类型节点内容的前面。

# "<div><h2>Test123</h2></div>"
$dom->parse('<div><h2>123</h2></div>')->at('h2')->prepend_content('Test')->root;

# "<!-- Test 123 --><br>"
$dom->parse('<!-- 123 --><br>')->child_nodes->first->prepend_content(' Test')->root;

# "<p><i>123</i>Test</p>"
$dom->parse('<p>Test</p>')->at('p')->prepend_content('<i>123</i>')->root;

previous

my $sibling = $dom->previous;

以Mojo::DOM形式返回$dom元素前一个同级元素,如果没有则返回undef。

# "<h1>Test</h1>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;

previous_node

my $sibling = $dom->previous_node;

以Mojo::DOM对象的形式返回$dom节点的前一个同级节点,如果没有则返回undef。

# "123"
$dom->parse('<p>123<!-- Test --><b>456</b></p>')->at('b')->previous_node->previous_node;

# " Test "
$dom->parse('<p>123<!-- Test --><b>456</b></p>')->at('b')->previous_node->content;

remove

my $parent = $dom->remove;

删除当前节点,返回父节点或root节点(如果调用此方法的是root)。

# "<div></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->remove;

# "<p><b>456</b></p>"
$dom->parse('<p>123<b>456</b></p>')->at('p')->child_nodes->first->remove->root;

replace

my $parent = $dom->replace('<div>I ♥ Mojolicious!</div>');

使用HTML/XML代码片段替换当前节点,以Mojo::DOM的形式返回父结点或返回root结点(如果调用此方法的是root)。

# "<div><h2>123</h2></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->replace('<h2>123</h2>');

# "<p><b>123</b></p>"
$dom->parse('<p>Test</p>')->at('p')->child_nodes->[0]->replace('<b>123</b>')->root;

root

my $root = $dom->root;

以Mojo::DOM对象的形式返回当前结点的root结节。

strip

my $parent = $dom->strip;

在保留当前元素内容的情况下删除当前元素。就像是给当前元素脱去了衣服一样。以Mojo::DOM对象的形式返回当前元素的父元素。

# "<div>Test</div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;

tag

my $tag = $dom->tag;
$dom    = $dom->tag('div');

设置或返回些元素的标签名。

# List tag names of child elements
say $dom->children->map('tag')->join("\n");

tap

$dom = $dom->tap(sub {...});

Mojo::Base 中 trap 的别名。

text

my $text = $dom->text;

仅从当前元素提取文件,不包含子元素。

# "bar"
$dom->parse("<div>foo<p>bar</p>baz</div>")->at('p')->text;

# "foo\nbaz\n"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;

to_string

my $str = $dom->to_string;

将此节点及其内容以HTML/XML字符串的形式返回。

# "<b>Test</b>"
$dom->parse('<div><b>Test</b></div>')->at('div b')->to_string;

tree

my $tree = $dom->tree;
$dom     = $dom->tree(['root']);

文档对象模型的内部数据结构,因为这种数据结构非常灵活,所以返回的结果是动态的。

type

my $type = $dom->type;

返回当前节点的类型,可能的类型如下:cdata, comment,doctype,pi, raw, root, tag,text。

# "cdata"
$dom->parse('<![CDATA[Test]]>')->child_nodes->first->type;

# "comment"
$dom->parse('<!-- Test -->')->child_nodes->first->type;

# "doctype"
$dom->parse('<!DOCTYPE html>')->child_nodes->first->type;

# "pi"
$dom->parse('<?xml version="1.0"?>')->child_nodes->first->type;

# "raw"
$dom->parse('<title>Test</title>')->at('title')->child_nodes->first->type;

# "root"
$dom->parse('<p>Test</p>')->type;

# "tag"
$dom->parse('<p>Test</p>')->at('p')->type;

# "text"
$dom->parse('<p>Test</p>')->at('p')->child_nodes->first->type;

val

my $value = $dom->val;

从表单元素(button、input、option、select、textarea)中提取表单的值。如果元素没有值则返回undef。在select中使用multiple属性的情况下,会查找所有具有selected属性的option元素,返回的是一个包含所有找到option元素中值的数组的引用,如果找不到任何selected属性的option元素,则返回undef。

# "a"
$dom->parse('<input name=test value=a>')->at('input')->val;

# "b"
$dom->parse('<textarea>b</textarea>')->at('textarea')->val;

# "c"
$dom->parse('<option value="c">Test</option>')->at('option')->val;

# "d"
$dom->parse('<select><option selected>d</option></select>')->at('select')->val;

# "e"
$dom->parse('<select multiple><option selected>e</option></select>')->at('select')->val->[0];

# "on"
$dom->parse('<input name=test type=checkbox>')->at('input')->val;

with_roles

my $new_class = Mojo::DOM->with_roles('Mojo::DOM::Role::One');
my $new_class = Mojo::DOM->with_roles('+One', '+Two');
$dom          = $dom->with_roles('+One', '+Two');

Mojo::Base中with_roles方法的别名。

wrap

$dom = $dom->wrap('<div></div>');

在当前节点外包装HTML/XML代码片段(除root节点以外的所有节点都可以调用此方法),将当前节点作为“包装器”第一个最内层元素的最后一个子节点。

# "<p>123<b>Test</b></p>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<p>123</p>')->root;

# "<div><p><b>Test</b></p>123</div>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<div><p></p>123</div>')->root;

# "<p><b>Test</b></p><p>123</p>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<p></p><p>123</p>')->root;

# "<p><b>Test</b></p>"
$dom->parse('<p>Test</p>')->at('p')->child_nodes->first->wrap('<b>')->root;

wrap_content

$dom = $dom->wrap_content('<div></div>');

在当前节点的“内容”外包装HTML/XML代码片段(只有root和tag节点可以调用此方法),将当前节点作为“包装器”第一个最内层元素的最后一个子节点。

# "<p><b>123Test</b></p>"
$dom->parse('<p>Test<p>')->at('p')->wrap_content('<b>123</b>')->root;

# "<p><b>Test</b></p><p>123</p>"
$dom->parse('<b>Test</b>')->wrap_content('<p></p><p>123</p>');

xml

my $bool = $dom->xml;
$dom     = $dom->xml($bool);

获取或设置解析器中禁用HTML语义的开关。默认为关闭状态。

重载的操作符

array

my @nodes = @$dom;

相当于调用了 child_nodes,可以理解为child_nodes的别名函数。

bool

my $bool = !!$dom;

总是返回true。

hash

my %attrs = %$dom;

相当于调用了 attr ,可以理解为 attr 的别名函数。

stringify

my $str = "$dom";

相当于调用了 to_string,可以理解为 to_string的别名函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 之前通过深入学习DOM的相关知识,看了慕课网DOM探索之基础详解篇这个视频(在最近看第三遍的时候,准备记录一点东西...
    微醺岁月阅读 4,459评论 2 62
  • 一、样式篇 第1章 初识jQuery (1)环境搭建 进入官方网站获取最新的版本 http://jquery.co...
    凛0_0阅读 3,358评论 0 44
  • 一、JS前言 (1)认识JS 也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HT...
    凛0_0阅读 2,755评论 0 8
  • 正值二十岁的我们是许多人眼中最美的年纪,至少我们还可以肆意挥洒我们的青春,因为我们还年轻。 也许过了很多很多年,我...
    八块腹肌的麻辣烫阅读 381评论 1 2
  • 自己自学的,没有人指导,虽说画的不好,但也要练练,求指导 只是起了个稿,以后再上色。 如果你也喜欢画画一起吧
    寻一个爱哭的男孩子阅读 332评论 5 1