lua 标签解析器


概述


一个类xml标签解析函数,将标签解析成Lua中的表结构
它可以用来解析简单xml结构,可以作为RichLabel控件的字符串解析组件(其实它现在就是这么用的;-))

原理


使用lua的模式匹配, 使用了模式串%b<>
%b用来匹配对称的字符。常写为 %bxy,x和y是任意两个不同的字符。 x作为匹配的开始,y作为匹配的结束。
比如,%b<> 匹配以 < 开始,以 > 结束的字符串

要解析的字符串

hello world
<div>hello world</div>
你好
<div fontName='nihao' fontColor=#ffccdd>hello,world</div>
<div></div>

代码基本结构

-- 迭代所有格式为<xxx>的标签(包含了标签头和标签尾巴)
local index =
for beginindex, endindex in function() return string.find(text, "%b<>", index) end do
local label = string.sub(text, beginindex, endindex)
index = endindex +
end

上面获得的beginindex和endindex分别是标签的在字符串中开始和结束的index值 但并不区分标签头和标签尾,
还得判断标签到底是标签头还是标签尾。

-- 获得当前标签,字符串截取出来
local label = string.sub(text, beginindex, endindex)
-- 判断当前标签是不是以'</'开头,若是以'</'开头则代表为标签尾巴,否则为标签头
if string.find(label, "^</") then
else
end

至此已经可以在正确的从字符串中提取所有的标签,并且区分出标签头和标签尾了

处理标签匹配问题
为了支持标签嵌套包含,我们得用一个栈记录标签头,要不然嵌套好几层标签不好判断内容被哪个标签修饰。
具体做法:
+ 若当前标签是标签头则将栈顶标签,到新标签头之间内容用栈顶标签修饰,并且将当前标签入栈
+ 若当前标签是标签尾则将栈顶标签,到新标签尾之间内容用栈顶标签修饰,并将栈顶标签出栈
简单来说:
栈顶标签修饰当前的内容(新标签和栈顶标签之间的内容) ,然后根据新标签是头还是尾决定入栈或出栈

我们没有考虑特殊情况,若字符串最完成不是标签那最外层处理会存在点问题。
好吧,那我们可以直接在字符串外层加上一组标签

-- 检测开头和结尾是否为标签 <xxx>即为标签
if not string.find(text, "^%b<>.+%b<>$") then
-- 在最外层包装一个标签,便于解析时的统一处理,不用处理没有包装标签的情况
text = table.concat({LABEL_DIV_BEGIN, text, LABEL_DIV_END})
end

现在我们解析出了内容和修饰内容的标签,离成功不远了!!

解析标签头
这应该是最简单的一部,但又是最繁琐的一步。
因为标签头内容很少了,格式也确定了,只要提取出标签名和属性的key-value对就可以了
不过提取属性key-value对比较繁琐,要考虑value的种种可能,当然我考虑的并不全面也没打算那么全面,只要功能够用就可以了

第一步
解析出标签名,简单的模式匹配

local labelnameindex1, labelnameindex2 = string.find(label, "%w+")

因为我们要解析的串大多是手写,为了减小书写难度,标签名属性名最好不区分大小写

-- 获得标签名称
local labelname = string.sub(label, labelnameindex1, labelnameindex2)
labelname = string.lower(labelname)

第二步
获取属性,还是模式匹配,匹配形式为 propertyname=propertyvalue(等号两边不能存在空格)
propertyname 要求就是字母或者数字的组合
propertyvalue 要求就比较多, 因为颜色使用的是web的标记形式#FF33AA, 而且字符串也可能是'括起来表示。
%w: 与任何字母/数字配对
%s: 与空白字符配对

-- value要求非空白字符并且不含有'>'字符的一个单词
string.gmatch(labelhead, "%w+%=[^%s%>]+")

gmatch会返回一个迭代器,每次运行都返回一个匹配串,所以我们这么写

for property in string.gmatch(labelhead, "%w+%=[^%s%>]+") do
end

在循环中我们可以处理每个属性对字符串无非就是根据=位置分离出属性名和属性值,属性名不需要处理,
把属性值做一下处理然后放到一个table中就好了,处理如下:

local equalmarkpos = string.find(property, "=")
-- 分离属性名和属性值
local propertyname = string.sub(property, , equalmarkpos-)
local propertyvalue = string.sub(property, equalmarkpos+, string.len(property))
-- 属性名转为小写
propertyname = string.lower(propertyname)
-- 属性值处理
local continue = false
-- 1.检测是否为字符串(单引号或者双引号括起来)
local beginindex, endindex = string.find(propertyvalue, "['\"].+['\"]")
if beginindex then
propertyvalue = string.sub(propertyvalue, beginindex+, endindex-)
continue = true
end
-- 2.检测是否为布尔值
if not continue then
local propertyvalue_lower = string.lower(propertyvalue)
if propertyvalue_lower == BOOLEAN_TRUE then
propertyvalue = true
continue = true
elseif propertyvalue_lower == BOOLEAN_FALSE then
propertyvalue = false
continue = true
end
end
-- 3.检测是否为数字
if not continue then
local propertyvalue_number = tonumber(propertyvalue)
if propertyvalue_number then
propertyvalue = propertyvalue_number
continue = true
end
end
-- 若以上都不是,则默认直接为字符串
labelparams[propertyname] = propertyvalue

顺便吐槽一下lua没有关键字continue,造成程序嵌套层次变深

最后完整代码在我的github上面叫labelparser,lua5.1解析器可以直接运行,无需任何依赖

------------------------------------------------------------------------------------------------

14-11-20:更新v1.0.1

  1. 标签解析器原来没有支持自闭合标签,例如:<img src='path/img.png' />
  2. 去除setfenv函数的使用,这样lua5.2也可以直接使用了

今天晚上突然兴起决定添加上自闭合的支持,其实也很简单,首先主循环发生了改变:

local beginindex, endindex = string.find(text, "%b<>", )
while beginindex do
-- 获得当前标签
local label = string.sub(text, beginindex, endindex) -- 检测字符串是否以"</"开头
if string.find(label, "^</") then
-- 标签尾
_M.disposeLabelTail(labelheadstack, parsedtable, text, label, beginindex, endindex)
elseif string.find(label, "/>$") then -- 检测以'/>'结尾
-- 自闭合标签
_M.disposeLabelSelfClosing(labelheadstack, parsedtable, text, label, beginindex, endindex)
else-- 检测到标签头
_M.disposeLabelHead(labelheadstack, parsedtable, text, label, beginindex, endindex)
end
-- 获得下一个标签的位置
beginindex, endindex = string.find(text, "%b<>", endindex)
end

主循环这里添加了一个自闭合标签的判断,然后相关的标签处理都提取出去了,主要有两点原因:

  1. 自闭合标签的处理其实是使用了标签头和标签尾处理一样的代码,为了代码重用
  2. 三个分支再加上嵌套会使主循环嵌套过深,逻辑不清晰

参数着实不少,不过逻辑清晰了很多,主循环结构一目了然(本来也没多少行代码;-))

自闭合标签为什么加在中间呢?

自闭合标签的条件,不能以'</'开头,并且要以'/>'结尾,所以加在中间比较好判断这个条件

下面看看自闭合标签的处理,更加简单

-- 处理自闭合标签
function _M.disposeLabelSelfClosing(labelheadstack, parsedtable, text, label, beginindex, endindex)
_M.disposeLabelHead(labelheadstack, parsedtable, text, label, beginindex, endindex)
_M.disposeLabelTail(labelheadstack, parsedtable, text, label, beginindex, endindex, true)
end

直接调用处理标签头和标签尾的函数,就可以了

因为处理自闭合标签和处理一对儿有些相似之处,可以直接使用处理一对儿标签方法直接处理自闭合标签

(虽然这样处理自闭合标签过程有些冗余,但是无需专门的代码处理自闭合标签)

首先,碰到自闭合标签之后,要将自闭合标签之前内容使用栈顶标签头修饰

然后标签头可以入栈,然后又直接是标签头出栈,修饰栈顶标签到当前标签直接内容,由于两个标签是同一个标签,

它们之间内容为空,直接处理这个标签属性即可

这样其实就差不多了,具体代码还是参照github

lua标签解析器的更多相关文章

  1. spring源码深度解析— IOC 之 自定义标签解析

    概述 之前我们已经介绍了spring中默认标签的解析,解析来我们将分析自定义标签的解析,我们先回顾下自定义标签解析所使用的方法,如下图所示: 我们看到自定义标签的解析是通过BeanDefinition ...

  2. Spring源码阅读笔记05:自定义xml标签解析

    在上篇文章中,提到了在Spring中存在默认标签与自定义标签两种,并且详细分析了默认标签的解析,本文就来分析自定义标签的解析,像Spring中的AOP就是通过自定义标签来进行配置的,这里也是为后面学习 ...

  3. SpringMVC 视图和视图解析器&表单标签

    视图和视图解析器 请求处理方法执行完成后,最终返回一个 ModelAndView 对象.对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它 ...

  4. SpringMVC(三)-- 视图和视图解析器、数据格式化标签、数据类型转换、SpringMVC处理JSON数据、文件上传

    1.视图和视图解析器 请求处理方法执行完成后,最终返回一个 ModelAndView 对象 对于那些返回 String,View 或 ModeMap 等类型的处理方法,SpringMVC 也会在内部将 ...

  5. 【swupdate文档 四】SWUpdate:使用默认解析器的语法和标记

    SWUpdate:使用默认解析器的语法和标记 介绍 SWUpdate使用库"libconfig"作为镜像描述的默认解析器. 但是,可以扩展SWUpdate并添加一个自己的解析器, ...

  6. SpringMVC入门案例及请求流程图(关于处理器或视图解析器或处理器映射器等的初步配置)

    SpringMVC简介:SpringMVC也叫Spring Web mvc,属于表现层的框架.Spring MVC是Spring框架的一部分,是在Spring3.0后发布的 Spring结构图 Spr ...

  7. HTML解析器HtmlAgilityPack的一些使用总结(C#)

    哎~本来这些总结是作为使用时的快速备注,但是用不上了.实际应用当中HtmlAgilityPack的可靠性不太稳定,一主要问题是:-> 一些字符会出现乱码或者变成'?',如韩语字符.由于我是已经有 ...

  8. 爬虫笔记(四)------关于BeautifulSoup4解析器与编码

    前言:本机环境配置:ubuntu 14.10,python 2.7,BeautifulSoup4 一.解析器概述 如同前几章笔记,当我们输入: soup=BeautifulSoup(response. ...

  9. pull解析器: 反序列化与序列化

    pull解析器:反序列化 读取xml文件来获取一个对象的数据 import java.io.FileInputStream; import java.io.IOException; import ja ...

随机推荐

  1. iOS 自定义导航栏 和状态栏

    一.更改状态栏颜色 (StatusBar) 就是比如导航栏是红色的状态栏是绿色的. 要实现这样的效果其实很简单,就是添加一个背景view. 简单的实现过程如下: 1 // 设置导航颜色 可用 2 [s ...

  2. 关于django批量上传图片

    本来想一张一张上传的,但是明显会对客户造成不必要的麻烦,所以如果前台一次性上传五张十张的话,那就简单的多. 但是后台我数据库对于图片存储的字段只有一个,不可能有多少张照片就要多少个字段来存储.也就是说 ...

  3. 设计模式之中介者模式(Mediator)

    中间者模者模式原理:中介者维持所有要交互对象的指针或者对象,所有对象维持一个中介者的指针或者对象. #include <iostream> #include <string> ...

  4. Apache环境.htaccess伪静态301跳转(www与不带www)

    一般而言,我们使用的301跳转就是WWW与不带WWW域名之间的跳转,用行话说就是权重要归一.对于我们使用Apache环境的来说其实相对nginx比较简单,因为只需要我们在网站根目录有.htaccess ...

  5. ActionScript基本语法讲解

    var a:int = 3;var b:int = 4; b = 9; trace ("a的值为:"+a);trace ("b的值为:"+b); var x1: ...

  6. ios中webservice报文的拼接

    1.报文头需要密码验证的 - (void)sendAsynWebServiceRequest:(NSString *)nameSpace method:(NSString *)method reque ...

  7. 帝国cms栏目别名如何调用?

    我们在用帝国cms建站时经常会发现栏目的标题不好设置,栏目名称太长的话在后台那边看了眼花,太短又不好优化.能不能直接调用栏目别名呢?栏目别名不会什么影响.那么,帝国cms栏目别名怎么调用呢?和ytka ...

  8. rdtsc获取时间统计程序的运行效率

    __u64 rdtsc() {         __u32 lo,hi;           __asm__ __volatile__         (          "rdtsc&q ...

  9. Codeforces 414C Mashmokh and Reverse Operation

    题意:给你2^n个数,每次操作将其分成2^k份,对于每一份内部的数进行翻转,每次操作完后输出操作后的2^n个数的逆序数. 解法:2^n个数,可以联想到建立一棵二叉树的东西,比如  2,1,4,3就可以 ...

  10. [C++]类的继承与派生

    继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义.利用类的继承和派 ...