zoukankan      html  css  js  c++  java
  • lua标签解析器

    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 = 0
    for beginindex, endindex in function() return string.find(text, "%b<>", index) end do
        local label = string.sub(text, beginindex, endindex)
        index = endindex + 1
    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, 1, equalmarkpos-1)
    local propertyvalue = string.sub(property, equalmarkpos+1, 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+1, endindex-1)
        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<>", 1)
        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

  • 相关阅读:
    区块链技术的自我见解
    Nengo 神经网络
    elasticsearch更新操作问题
    elasticsearch httpclient认证机制
    spring 常见的注解
    elasticsearch插入索引文档 对数字字符串的处理
    Elasticsearch搜索含有数字标签的处理
    Elasticsearch 插入地理索引文档一直为空
    UVA-10163 Storage Keepers (0-1背包)
    UVA-1632 Alibaba (区间DP+滚动数组)
  • 原文地址:https://www.cnblogs.com/luweimy/p/4098380.html
Copyright © 2011-2022 走看看