html回退一个字符,html解析器工作原理

html解析器工作原理

先看一个简单的html文档

[html]

test
Hello World!

1. 首先用一个类来描述一个节点

[java]

public class Node{

private String nodeName;

private int nodeType;

private Map attributes;

private List childNodes;

private Node parent;

// getter & setter

...

}

然后我们开始对输入内容进行解析,解析的过程其实就是解析字符串的过程,为了便于解析先把源字符串封装成一个HtmlStream对象.

[java]

String source = IO.read(new File("test.html"), "UTF-8");

HtmlStream stream = new HtmlStream(source);

char c;

int i = 0;

// 忽略掉文档开头的空格

while((i == stream.read()) != -1)

{

if(i != ' ')

{

// 回退一个字符

stream.back();

break;

}

}

Stack stack = new Stack();

StringBuilder buffer = new StringBuilder();

// 为了便于程序阅读,先分成两部分

// 第一部分解析节点,通过startTag来完成

// 第二部分读取文本内容,遇到

while((i == stream.read()) != -1)

{

if(i == '

this.startTag();

}

else if{

buffer.append((char)i);

while((i == stream.read()) != -1)

{

if(i == '

{

stream.back();

break;

}

buffer.append((char)i);

}

this.pushTextNode(stack, buffer.toString());

buffer.setLength(0);

}

}

再来看startTag

[java]

public void startTag(Stack stack)

{

int i = this.stream.peek();

if(i == '/')

{

String nodeName = this.readNodeName();

this.endTag(stack, nodeName);

}

else if(i == '!')

{

// 注释...

}

else

{

String nodeName = this.readNodeName();

if(nodeName.length > 0)

{

Node node = new Node(nodeName);

this.readAttributes(node.getAttributes());

this.pushNode(stack, node);

}

else

{

this.pushTextNode(stack, "

}

}

}

// 当标签结束时

public void endTag(Stack stack, String nodeName){

Node node = stack.peek();

if(node == null)

{

// 读取到>, 并写入文本节点, 略去

this.pushTextNode(stack, "

return;

}

if(node.getNodeName().equalsIgnoreCase(nodeName))

{

stack.pop();

// 其他处理...

}

}

先说一下栈的结构, 这个是html解析中一个很重要的东西.

当我们用Node这个类来描述一个节点的时候,很容易把一个树形结构的数据串起来, 只需要建立父子关系即可。

但是当解析一个html文件的时候,怎么把读取到的一个结束节点跟之前读取的n个开始节点中的某一对应呢?

为了简单的说明这个问题,可以用类json格式的数据来表示一下:

var array = []; // 定义一个数组

现在假设这个数组里面有4个节点,它描述了下面的一个html片段

test

现在整个数组是这样:

[{node: html}, {node: head}, {node: title}, {text: test}, {node: /title}, {node: /head}, {node: body}, {node: /body}, {node: /html}]

这样看起来它其实跟原始的数据没什么区别,只不过变了中描述方式。

现在我们用一个指针指向这个数组的末端, 并且始终指向末端, 就变成了一种栈结构. 当某一个节点结束时,取指针位置的元素,正常情况下,这个元素一定是这个结束节点对应的开始节点.

如果结束节点的节点名跟指针位置对应的节点的节点名不一致,那就说明某一个节点没有正确闭合, 这个时候需要一些容错处理, 如果是xml解析直接抛异常即可.

现在详细的描述一下这个步骤:

先解析第一个节点, 这个节点是, 并且是开始节点, 把它压入栈, 现在的栈中的数组元素应该是这样的:

[{node: html}]

只有一个节点,指针指向0

然后是第二个节点, 它也是一个开始节点,因此也压入栈, 现在的栈中的数组元素应该是这样的:

[{node: html}, {node: head}]

依次类推, 直到遇到文本节点和结束节点, 当遇到文本节点的时候, 栈中的元素如下:

[{node: html}, {node: head}, {node: title}]

现在处理下一个,发现是个文本节点, 节点内容是: test, 先从栈顶弹出一个节点,如果是文本节点,直接把该文本追加,如果是元素节点

[java]  补充:web前端 , HTML/CSS ,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值