zoukankan      html  css  js  c++  java
  • lua工具库penlight--06数据(二)

    词法扫描

    虽然 Lua 的字符串模式匹配是非常强大,但需要更强大的东西。pl.lexer.scan可以提供标记字符串,按标记机分类数字、字符串等。

     > lua -lpl

     Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio

     > tok = lexer.scan 'alpha = sin(1.5)'

     > = tok()

     iden    alpha

     > = tok()

     =       =

     > = tok()

     iden    sin

     > = tok()

     (       (

     > = tok()

     number  1.5

     > = tok()

     )       )

     > = tok()

     (nil)

     

    scanner是一个函数,它会反复被调用并返回标记的类型和值。基本类型有'iden'、 'string'、 'number' 和 'space' ,一切都由基本类型识别。请注意默认情况下扫描程序将跳过任何’space’(空白)标记。

    'comment'(注释) 和 'keyword' (关键字)并不是适用于简单的scanner,对所有语言一样,但了解 Lua scanner可以。它承认了 Lua 的关键字,并了解短和长注释和字符串。

     > for t,v in lexer.lua 'for i=1,n do' do print(t,v) end

     keyword for

     iden    i

     =       =

     number  1

     ,       ,

     iden    n

     keyword do

     

    词法扫描器会很有用,当没有高度结构化的数据。例如,这里是我维护一个内部文件格式的片段:

     points

     

    (818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1)

     ,(818327.4,–20388,–0.1),(818322,–20387.7,–0.1),(818316.3,–20388.6,–0.1)

     ,(818309.7,–20389.4,–0.1),(818303.5,–20390.6,–0.1),(818295.8,–20388.3,–0.1)

     ,(818290.5,–20386.9,–0.1),(818285.2,–20386.1,–0.1),(818279.3,–20383.6,–0.1)

     ,(818274,–20381.2,–0.1),(818274,–20380.7,–0.1);

     

    这是用pl.lexer提取点 :

     -- assume 's' contains the text above...

     local lexer = require 'pl.lexer'

     local expecting = lexer.expecting

     local append = table.insert

     

     local tok = lexer.scan(s)

     

     local points = {}

     local t,v = tok() -- should be 'iden','points'

     

     while t ~= ';' do

         c = {}

         expecting(tok,'(')

         c.x = expecting(tok,'number')

         expecting(tok,',')

         c.y = expecting(tok,'number')

         expecting(tok,',')

         c.z = expecting(tok,'number')

         expecting(tok,')')

         t,v = tok()  -- either ',' or ';'

         append(points,c)

     end

     

    expecting函数抓取下一个标记,如果类型不匹配,则会引发一个错误。(pl.lexer ,不同于其他 PL库,如果出了什么差错将引发错误,所以你应该代码中用pcall优雅地捕获错误。)

    所有scanner都有第二个可选参数,这是一个表,控制是否您想要排除空白和评论。Lexer.lua的默认值是{space=true,comments=true}。有第三个可选的参数如何处理字符串和数字标记。

    高度结构化的数据当然是程序源代码。 text-lexer.lua'的一点片段:

     require 'pl'

     

     lines = [[

     for k,v in pairs(t) do

         if type(k) == 'number' then

             print(v) -- array-like case

         else

             print(k,v)

         end

     end

     ]]

     

     ls = List()

     for tp,val in lexer.lua(lines,{space=true,comments=true}) do

         assert(tp ~= 'space' and tp ~= 'comment')

         if tp == 'keyword' then ls:append(val) end

     end

     test.asserteq(ls,List{'for','in','do','if','then','else','end','end'})

     

    这里是一个有用的小工具,用于标识在 lua 模块 (暂时忽略那些局部声明) 中找到的所有常见全局变量:

     -- testglobal.lua

     require 'pl'

     

     local txt,err = utils.readfile(arg[1])

     if not txt then return print(err) end

     

     local globals = List()

     for t,v in lexer.lua(txt) do

         if t == 'iden' and _G[v] then

             globals:append(v)

         end

     end

     pretty.dump(seq.count_map(globals))

     

    与其dump整个列表,我们把它传入seq.count_map转换为键值对,并且关联的值的表示这些值会出现在序列中的次数。典型输出如下所示:

     {

       type = 2,

       pairs = 2,

       table = 2,

       print = 3,

       tostring = 2,

       require = 1,

       ipairs = 4

     }

     

    您可以进一步通过这tablex.keys以获取唯一的符号列表。当编写 '严格'的 Lua 模块时,所有全局符号都必须在文件的顶部定义为local。

    lexer.scan的更多详细使用,请看示例目录中testxml.lua

     

    XML

    新的 0.9.7 版本是 支持一些 XML的。这是一个大的话题,和Penlight不提供一个完整的 XML 堆栈,这是更专门库的任务。

    解析和好的打印

    Lua 中的半标准 XML 分析器是lua-expat。尤其是,它有lxp.lom.parse函数可以解析 XML成 Lua 对象模型 (LOM)。然而,它不提供将此数据转换回为 XML 文本的方法。如果 lua-expat可用xml.parse将使用这个函数,否则为切换回原来 Roberto Ierusalimschy 写的纯 Lua 分析器。

    生成的文档对象知道如何以字符串呈现,这对于调试非常有用:

     > d = xml.parse "<nodes><node id='1'>alice</node></nodes>"

     > = d

     <nodes><node id='1'>alice</node></nodes>

     > pretty.dump (d)

     {

       {

         "alice",

         attr = {

           "id",

           id = "1"

         },

         tag = "node"

       },

       attr = {

       },

       tag = "nodes"

     }

     

    数据的实际形状揭示了 LOM 的结构:

    每个元素都具有其名称的tag字段

    attr字段包含属性字段,并是数组。

    元素的子级在元素数组里,所以d[1]d的第一个孩子。

    可以认为,具有属性作为数组的attr部分不是必不可少的 (你不能依赖 XML 中的属性顺序),但这与标准一致。

    lua-expatPenlight 的另一个软依赖项;一般来说,回退分析器对于简单 XML配置文件是是好足够的。doc.basic_parse不打算成为强大的符合分析器 (它只有六十行),但它可以处理简单没有的注释或 DTD 的指令的文档。它有足够的智力,忽略<?xml指令,仅此而已。

    你可以得到好的打印效果,通过显式调用xml.tostring并传递给它的初始缩进和每个元素缩进:

     > = xml.tostring(d,'','  ')

     

     <nodes>

       <node id='1'>alice</node>

     </nodes>

     

    第四个参数是属性缩进:

     > a = xml.parse "<frodo name='baggins' age='50' type='hobbit'/>"

     > = xml.tostring(a,'','  ','  ')

     

     <frodo

       type='hobbit'

       name='baggins'

       age='50'

     />

    分析和使用配置文件

    现在用XML配置很常见。处理LOM数据和按你的想要的格式提取数据,非常简单 :

     require 'pl'

     

     local config = [[

     <config>

         <alpha>1.3</alpha>

         <beta>10</beta>

         <name>bozo</name>

     </config>

     ]]

     local d,err = xml.parse(config)

     

     local t = {}

     for item in d:childtags() do

         t[item.tag] = item[1]

     end

     

     pretty.dump(t)

     --->

     {

       beta = "10",

       alpha = "1.3",

       name = "bozo"

     }

     

    唯一的地方是,在这里我们必须使用的Doc:childtags方法,它可以跳过任何文本元素。

    本文摘录自serviceproviders.xml它通常被发现位于Debian/Ubuntu Linux 系统/usr/share/mobile-broadband-provider-info/serviceproviders.xml

     d = xml.parse [[

     <serviceproviders format="2.0">

     ...

     <country code="za">

         <provider>

             <name>Cell-c</name>

             <gsm>

                 <network-id mcc="655" mnc="07"/>

                 <apn value="internet">

                     <username>Cellcis</username>

                     <dns>196.7.0.138</dns>

                     <dns>196.7.142.132</dns>

                 </apn>

             </gsm>

         </provider>

         <provider>

             <name>MTN</name>

             <gsm>

                 <network-id mcc="655" mnc="10"/>

                 <apn value="internet">

                     <dns>196.11.240.241</dns>

                     <dns>209.212.97.1</dns>

                 </apn>

             </gsm>

         </provider>

         <provider>

             <name>Vodacom</name>

             <gsm>

                 <network-id mcc="655" mnc="01"/>

                 <apn value="internet">

                     <dns>196.207.40.165</dns>

                     <dns>196.43.46.190</dns>

                 </apn>

                 <apn value="unrestricted">

                     <name>Unrestricted</name>

                     <dns>196.207.32.69</dns>

                     <dns>196.43.45.190</dns>

                 </apn>

             </gsm>

         </provider>

         <provider>

             <name>Virgin Mobile</name>

             <gsm>

                 <apn value="vdata">

                     <dns>196.7.0.138</dns>

                     <dns>196.7.142.132</dns>

                 </apn>

             </gsm>

         </provider>

     </country>

     ....

     </serviceproviders>

     ]]

     

    得到每个国家的供应商的名称非常简单:

     local t = {}

     for country in d:childtags() do

         local providers = {}

         t[country.attr.code] = providers

         for provider in country:childtags() do

             table.insert(providers,provider:child_with_name('name'):get_text())

         end

     end

     

     pretty.dump(t)

     -->

     {

       za = {

         "Cell-c",

         "MTN",

         "Vodacom",

         "Virgin Mobile"

       }

       ....

     }

     

    使用'Xmlification' 生成 XML

    此功能的灵感来自Oribthtmlify简化 HTML 生成,只是没有环境函数 ;tags函数返回一组给定标记名称的元素构造器。

     > nodes, node = xml.tags 'nodes, node'

     > = node 'alice'

     <node>alice</node>

     > = nodes { node {id='1','alice'}}

     <nodes><node id='1'>alice</node></nodes>

     

    Lua 表的灵活性会非常有用,因此可以自然地编码的属性和元素的子级。这些标记的构造函数的参数是单个值 (如字符串) 或表,这个表的属性是命名的键和孩子们的数组值。

    使用模板生成 XML 

    模板是一个小的XML 文档,其中包含”$”变量。subst方法可以产生包含这些变量值的数组。请。注意指定父标记名称的方式:

     > templ = xml.parse "<node id='$id'>$name</node>"

     > = templ:subst {tag='nodes', {id=1,name='alice'},{id=2,name='john'}}

     <nodes><node id='1'>alice</node><node id='2'>john</node></nodes>

     

    替代是和过滤文件相关。有关 XML 的令人讨厌的事情有,一是它是一种文档标记语言,二是它是数据语言。标准的解析器将假定你真的关心所有这些额外的文本元素。请考虑此片段,已经一个五岁孩子改变了:

     T = [[

       <weather>

         boops!

         <current_conditions>

           <condition data='$condition'/>

           <temp_c data='$temp'/>

           <bo>whoops!</bo>

         </current_conditions>

       </weather>

     ]]

     

    标准解析器会在<current_conditions>文本元素,虽然它使处理数据更令人恼火。

     local function parse (str)

         return xml.parse(str,false,true)

     end

     

    第二个参数是指 字符串,而非文件,第三个参数表示使用 Lua 内置解析器 (而不是 LuaExpat ,如果可用),默认情况下保持字符串不感兴趣。

    如何删除字符串boops!?  clone(作为一种方法调用时也称为filter) 可以复制LOM 的文档。它可以传入一个filter函数,作用到每个找到的字符串。这个函数的强大之处在于接收结构信息 — 父节点,无论是否是一个标记名称,一个文本元素或属性的名称:

     d = parse (T)

     c = d:filter(function(s,kind,parent)

         print(stringx.strip(s),kind,parent and parent.tag or '?')

         if kind == '*TEXT' and #parent > 1 then return nil end

         return s

     end)

     --->

     weather    *TAG    ?

     boops!    *TEXT    weather

     current_conditions    *TAG    weather

     condition    *TAG    current_conditions

     $condition    data    condition

     temp_c    *TAG    current_conditions

     $temp    data    temp_c

     bo    *TAG    current_conditions

     whoops!    *TEXT    bo

     

    通过丢弃不是单一元素子元素的文本元素,我们可以把拉出来 'boops' 而不是 'whoops' 

    使用模板提取数据

    匹配在相反的方向。我们有一份文件,并想使用模式从中提取值。

    这样的一个常见用途是分析 API 查询的 XML 结果。谷歌天气 API是一个很好的例子。使用pretty-print打印抓取的结果http://www.google.com/ig/api?weather=Johannesburg,ZA"如下:

     <xml_api_reply version='1'>

     

    mobile_row = '0' >

     <forecast_information>

       <city data=‘Johannesburg, Gauteng’/>

       <postal_code data=‘Johannesburg,ZA’/>

       <latitude_e6 data=‘’/>

       <longitude_e6 data=‘’/>

       <forecast_date data=‘2010-10-02’/>

       <current_date_time data=‘2010-10-02 18:30:00 +0000’/>

       <unit_system data=‘US’/>

     </forecast_information>

     <current_conditions>

       <condition data=‘Clear’/>

       <temp_f data=‘75’/>

       <temp_c data=‘24’/>

       <humidity data=‘Humidity: 19%’/>

       <icon data=‘/ig/images/weather/sunny.gif’/>

       <wind_condition data=‘Wind: NW at 7 mph’/>

     </current_conditions>

     <forecast_conditions>

       <day_of_week data=‘Sat’/>

       <low data=‘60’/>

       <high data=‘89’/>

       <icon data=‘/ig/images/weather/sunny.gif’/>

       <condition data=‘Clear’/>

     </forecast_conditions>

     ….

    </weather>

    ml_api_reply>

     

    假设上述 XML 从google被读取。这个想法写一个模板,并使用它来提取一些感兴趣的值:

     t = [[

       <weather>

         <current_conditions>

           <condition data='$condition'/>

           <temp_c data='$temp'/>

         </current_conditions>

       </weather>

     ]]

     

     local res, ret = google:match(t)

     pretty.dump(res)

     

    的输出是:

     {

       condition = "Clear",

       temp = "24"

     }

     

    match方法可以传入一个LOM文档或一些文本,可以进行文本分析。

    但是如果我们需要从重复元素中提取值呢?匹配模板可能包含 '数组匹配,用'{{}.}' 住:

     <weather>

       {{<forecast_conditions>

         <day_of_week data='$day'/>

         <low data='$low'/>

         <high data='$high'/>

         <condition data='$condition'/>

       </forecast_conditions>}}

     </weather>

     

    匹配结果是:

     {

       {

         low = "60",

         high = "89",

         day = "Sat",

         condition = "Clear",

       },

       {

         low = "53",

         high = "86",

         day = "Sun",

         condition = "Clear",

       },

       {

         low = "57",

         high = "87",

         day = "Mon",

         condition = "Clear",

       },

       {

         low = "60",

         high = "84",

         day = "Tue",

         condition = "Clear",

       }

     }

     

    这一系列的表,您可以使用tablex或List重新塑造成所需的格式。和读取 Unix 密码文件与配置类似,您可以进行数组到的天气图到使用条件:

     tablex.pairmap ('|k,v| v,v.day',conditions)

     

    (在这里使用替代字符串 lambda

    但是,xml 匹配可以塑造输出结构。通过替换模板的day_of_week<day_of_week data=‘$’/> 我们得到同样的效果 ;$是一个特殊的符号,意味着它捕获的值 (或只是捕获) 成为key。

     

    请注意$NUMBER是指一个数值索引,这样, $1是生成的数组,等等的第一个元素。您可以混合使用编号和命名捕获,但它已强烈建议使编号的捕获形成适当的数组序列 (从1n包容一切)$0有特殊的含义 ;如果它是唯一的捕获 ({[0]=‘foo’}) 表可以折叠为 foo

     <weather>

       {{<forecast_conditions>

         <day_of_week data='$_'/>

         <low data='$1'/>

         <high data='$2'/>

         <condition data='$3'/>

       </forecast_conditions>}}

     </weather>

     

    现在的结果是:

     {

       Tue = {

         "60",

         "84",

         "Clear"

       },

       Sun = {

         "53",

         "86",

         "Clear"

       },

       Sat = {

         "60",

         "89",

         "Clear"

       },

       Mon = {

         "57",

         "87",

         "Clear"

       }

     }

     

    将匹配应用到此配置文件会带来另一个问题,因为实际标记匹配本身有意义。

     <config>

         <alpha>1.3</alpha>

         <beta>10</beta>

         <name>bozo</name>

     </config>

     

    标记 '通配符的元素名称用连字符结束。

     <config>

         {{<key->$value</key->}}

     </config>

     

    你就会找到{{alpha=‘1.3’},…}。将由这返回的最方便的格式 (请注意, 的行为就像$):

     <config>

         {{<_->$0</_->}}

     </config>

     

    这会返回{alpha=‘1.3’,beta=‘10’,name=‘bozo’}.

    我们可以无止境地,玩这种游戏和编码方式的转换的捕获。但该模式足够复杂,不过很容易地进行转换。

     local numbers = {alpha=true,beta=true}

     for k,v in pairs(res) do

         if numbers[v] then res[k] = tonumber(v) end

     end

     

    HTML 分析

    HTML 是一种异常地退化形式的 XMLDennis Schridde 贡献的一个功能,可以更轻松解析它。例如:

     doc = xml.parsehtml [[

     <BODY>

     Hello dolly<br>

     HTML is <b>slack</b><br>

     </BODY>

     ]]

     

     asserteq(xml.tostring(doc),[[

     <body>

     Hello dolly<br/>

     HTML is <b>slack</b><br/></body>]])

     

    也就是说,所有的标记都转换为小写字母,空的 HTML 元素如br已正确关闭 ;不需要被引用属性。

    此外,DOCTYPE 指令和注释被跳过。对于真正格式不好的 HTML,这不是你的工具 !

     

  • 相关阅读:
    Oracle查询一个用户的所有表的结构信息的SQL语句
    Unable to locate provider for protocol: smtp
    IE6 IE7 IE8(Q) 不支持 JSON 对象
    如何获取tinyeditor编辑器里面的值
    POJ 2513 Colored Sticks 字典树 + 并查集 + 欧拉路
    HDOJ 2574 Hdu Girls' Day
    HDOJ 2087 剪花布条 KMP算法
    HDOJ 2094 产生冠军
    NYOJ525 一道水题
    POJ 2406 power strings KMP中next函数的应用
  • 原文地址:https://www.cnblogs.com/xdao/p/lua_penlight06_2.html
Copyright © 2011-2022 走看看