Lua_第28章 资源管理(下)

29.2 XML 解析

现在,我们将要看到一个xml解析器的简单实现,称为lxp(估计是lua xml parser的简写) ,它包括了Lua和Expat。Expat是一个开源的C语言写成的XML  1.0 的解析器。它实现 了SAXC,SAX是XML简单的API,是基于事件的API,这意 味着一个SAX解析器读取有一个XML文档,然后反馈给应用程序他所发现的。举个例子,
我们要通知Expat解析这样一个字符串:

<tag cap="5">hi</tag>

它将会产生三个事件:当它读取子字符串 "<tag cap="5">hi</tag>",产生一个读取到开始元素的事件;当它解析 "hi" 时,产生一个读取文本事件(有时也称为字符数据事件);当解析 "end" 时,产生一个读取结束元素的事件。而每个事件,都会调用应用
程序适当的句柄。

这里我们不会涉及到整个 Expat 库,我们只会集中精力关注那些能够阐明和 Lua 相 互作用的新技术的部分。当我们实现了核心功能后,在上面进行扩展将会变得很容易。虽然 Expat 解析 XML 文档时会有很多事件,我们将会关心的仅仅是上面例子提到的三个事件(开始元素,结束元素,文本数据),我们需要调用的 API 是 Expat 众多 API 中 很少的几个。首先,我们需要创建和析构 Expat 解析器的函数:

#include <xmlparse.h>
XML_Parser XML_ParserCreate (const char *encoding);
void XML_ParserFree (XML_Parser p);

这里函数参数是可选的;在我们的使用中,我们直接选用NULL 作为参数。当我们 有了一个解析器的时候,我们必须注册回调的句柄:

XML_SetElementHandler(XML_Parser p,XML_StartElementHandlerstart, XML_EndElementHandler end);
XML_SetCharacterDataHandler(XML_Parser p,XML_CharacterDataHandler hndl);

第一个函数登记了开始元素和结束元素的句柄。第二个函数登记了文本数据(在 XML 语法中的字符数据)的句柄。所有回掉的句柄通过第一个参数接收用户数据。开始 元素的句柄同样接收到标签的名称和它的属性作为参数:

typedef void (*XML_StartElementHandler)(void *uData,const char *name,const char **atts);

这些属性来自于以 ‘\0‘ 结束的字符串组成的数组,这些字符串分别对应了一对以属 性名和属性值组成的属性。结束元素的句柄只有一个参数,就是标签名。

typedef void (*XML_EndElementHandler)(void *uData,const char *name)

最终,一个文本句柄仅仅以字符串作为额外的参数。该文本字符串不能是以‘\0‘结束 的字符串,而是显式指明长度的字符串:

typedef void
(*XML_CharacterDataHandler)(void *uData,const char *s,int len);

我们用下面的函数将这些文本传给 Expat:

int XML_Parse (XML_Parser p,const char *s, int len, int isFinal);

Expat 通过成功调用 XML_Parse一段一段的解析它接收到的文本。XML_Parse 最后 一个参数为 isFinal,他表示这部分是不是XML文档的最后一个部分了。需要注意的是,不是每段文本都需要通过 0来表示结束,我们也可以通过显实的长度来判定。XML_Parse 函数如果发现解析错误就会返回一个 0(expat 也提供了辅助的函数来显示错误信息,但 是因为简单的缘故,我们这里都将之忽略掉)。我们需要Expat 的最后一个函数是允许我
们设置将要传给句柄的用户数据的函数:

void XML_SetUserData (XML_Parser p, void *uData);

好了,现在我们来看一下如何在 Lua 中使用 Expat 库。第一种方法也是最直接的一 种方法:简单的在 Lua 中导入这些函数。比较好的方法是对Lua 调整这些函数。比如 Lua 是没有类型的,我们不需要用不同的函数来设置不同的调用。但是我们怎么样避免一起调用那些注册了的函数呢。替代的是,当我们创建了一个解析器,我们同时给出了一个 包含所有回调句柄以及相应的键值的回调表。举个例子来说,如果我们要打印一个文档
的布局,我们可以用下面的回调表:

local count= 0
callbacks = {
StartElement =function (parser, tagname)
      io.write("+ ", string.rep(" ", count),tagname, "\n")
      count =count + 1
end,

EndElement = function(parser, tagname)
     count =count - 1
     io.write("- ", string.rep(" ", count),tagname, "\n")
end,
}

输入"<to> <yes/> </to>",这些句柄将会打印出:

+ to
+   yes
-   yes
-  to

通过这个 API,我们不需要维护这些函数的调用。我们直接在回调表中维回他们。因此,整个 API 需要三个函数:一个创建解析器,一个解析一段段文本,最后一个关闭 解析器。(实际上,我们用解析器对象的方法,实现了最后两个功能)。对这些 API函数 的典型使用如下:

p = lxp.new(callbacks)      --create new parser
for l in io.lines() do      -- iterate over inputlines
     assert(p:parse(l))  -- parse the line
     assert(p:parse("\n")) -- add anewline
end
assert(p:parse())           --finish document
p:close()

现在,让我们把注意力集中到实现中来。首先,考虑如何在 Lua中实现解析器。很自然的会想到使用 userdatum,但是我们将什么内容放在userdata 里呢?至少,我们必须保留实际的 Expat 解析器和一个回调表。我们不能将一个 Lua 表保存在一个 userdatum(或 者在任何的 C  结构中),然而,我们可以创建一个指向表的引用,并将这个引用保存在 userdatum 中。(我们在
26.3.2 节己经说过,一个引用就是 Lua 自动产生的在 registry 中 的一个整数)最后,我们还必须能够将 Lua  的状态保存到一个解析器对象中,因为这些解析器对象就是 Expat回调从我们程序中接受的所有内容,并且这些回调需要调用 Lua。 一个解析器的对象的定义如下:

#include <xmlparse.h>
typedef struct lxp_userdata {
   lua_State *L;
   XML_Parser *parser;  /* associated expat parser */
   int tableref; /* tablewith callbacks forthis parser */
} lxp_userdata;

下面是创建解析器对象的函数:

static int lxp_make_parser (lua_State *L) {
   XML_Parser p;
  lxp_userdata *xpu;

  /* (1) createa parser object*/
  xpu =(lxp_userdata *)lua_newuserdata(L,sizeof(lxp_userdata));

  /* pre-initialize it, in caseof errors */
  xpu->tableref = LUA_REFNIL;
  xpu->parser = NULL;

  /* set its metatable */
   luaL_getmetatable(L, "Expat");
   lua_setmetatable(L, -2);

  /* (2) createthe Expat parser*/
  p =xpu->parser = XML_ParserCreate(NULL);
  if (!p)
  luaL_error(L, "XML_ParserCreate failed");

  /* (3) createand store reference to callback table*/
  luaL_checktype(L, 1,LUA_TTABLE);
  lua_pushvalue(L, 1); /* put table on thestack top */
  xpu->tableref =luaL_ref(L, LUA_REGISTRYINDEX);

  /* (4) configure Expat parser */
  XML_SetUserData(p, xpu);
  XML_SetElementHandler(p,f_StartElement, f_EndElement);
  XML_SetCharacterDataHandler(p,f_CharData);

  return 1;
}

函数 lxp_make_parser 有四个主要步骤:

        第一步遵循共同的模式:首先创建一个userdatum,然后使用 consistent 的值预初始 化 userdatum,最后设置 userdatum 的 metatable。预初始化的原因在于:如果在初始化的时候有任何错误的话,我们必须保证析构器(_gc 元方法)能够发现在可靠状态下发现 userdata 并释放资源。

      第二步,函数创建一个 Expat 解析器,将它保存在 userdatum 中,并检测错误。

第三步,保证函数的第一个参数是一个表(回调表),创建一个指向表的引用,并将这个引用保存到新的 userdatum 中。

第四步,初始化 Expat解析器,将 userdatum 设置为将要传递给回调函数的对象,并 设置这些回调函数。注意,对于所有的解析器来说这些回调函数都一样。毕竟,在 C中不可能动态的创建新的函数,取代的方法是,这些固定的C 函数使用回调表来决定每次应该调用哪个 Lua 函数。

下一步是解析方法,负责解析一段 XML 数据。他有两个参数:解析器对象(方法自己)和一个可选的一段 XML 数据。当没有数据调用这个方法时,他通知 Expat 文档己经 解析结束:

static int lxp_parse (lua_State *L) {
        int status;
       size_t len;
       const char *s;
       lxp_userdata *xpu;

    /* get and checkfirst argument (shouldbe a parser) */
    xpu = (lxp_userdata *)luaL_checkudata(L, 1, "Expat");
    luaL_argcheck(L, xpu, 1, "expat parser expected");

    /* get secondargument (a string)*/
   s =luaL_optlstring(L, 2, NULL,&len);

     /* prepare environment for handlers: */
    /* put callbacktable at stackindex 3 */
     lua_settop(L,2);
     lua_getref(L, xpu->tableref);
    xpu->L = L; /*set Lua state*/

    /* call Expatto parse string*/
      status =XML_Parse(xpu->parser, s, (int)len, s == NULL);

     /* return errorcode */
    lua_pushboolean(L, status);

   return 1;
}

当 lxp_parse调用 XML_Parse的时候,后一个函数将会对在给定的一段 XML数据中找到的所有元素,分别调用这些元素对应的句柄。所以,lxp_parse 会首先为这些句柄准备环境,在调用XML_Parse 的时候有一些细节:记住这个函数的最后一个参数告诉 Expat 给定的文本段是否是最后一段。当我们不带参数调用他时,s 将使用缺省的 NULL,因此这时候最后一个参数将为 true。现在让我们注意力集中到回调函数 f_StartElement、
f_EndElement 和 f_CharData 上,这三个函数有相似的结构:每一个都会针对他的指定事件检查 callback 表是否定义了 Lua 句柄,如果有,预处理参数然后调用这个 Lua 句柄。

我们首先来看 f_CharData句柄,他的代码非常简单。她调用他对应的 Lua 中的句柄 (当存在的时候),带有两个参数:解析器 parser 和字符数据(一个字符串)

static void f_CharData (void *ud, const char *s, int len) {
   lxp_userdata *xpu = (lxp_userdata *)ud;
   lua_State *L = xpu->L;

  /* get handler*/
  lua_pushstring(L, "CharacterData");
  lua_gettable(L, 3);
  if (lua_isnil(L, -1)) { /* no handler?*/
  lua_pop(L, 1);
  return;
}

   lua_pushvalue(L, 1);/* push the parser (`self')*/
   lua_pushlstring(L, s, len);/*push Char data*/
   lua_call(L, 2, 0);  /* callthe handler */
}

注意,由于当我们创建解析器的时候调用了 XML_SetUserData,所以,所有的 C 句柄都接受 lxp_userdata 数据结构作为第一个参数。还要注意程序是如何使用由 lxp_parse设置的环境的。首先,他假定callback 表在栈中的索引为 3;第二,假定解析器 parser 在栈中索引为 1(parser 的位置肯定是这样的,因为她应该是 lxp_parse的第一个参数)。

f_EndElement  句柄和 f_CharData  类似,也很简单。他也是用两个参数调用相应的Lua 句柄:一个解析器 parser 和一个标签名(也是一个字符串,但现在是以 ‘\0‘ 结尾):

static void f_EndElement (void *ud, const char *name) {
   lxp_userdata *xpu = (lxp_userdata *)ud;
   lua_State *L = xpu->L;

   lua_pushstring(L, "EndElement"); lua_gettable(L, 3);
  if (lua_isnil(L, -1)) { /* no handler?*/
  lua_pop(L, 1);
  return;
}
   lua_pushvalue(L, 1);/* push the parser (`self')*/
   lua_pushstring(L, name); /* push tagname */
   lua_call(L, 2, 0);   /* callthe handler */
}

最后一个句柄 f_StartElement 带有三个参数:解析器 parser,标签名,和一个属性列表。这个句柄比上面两个稍微复杂点,因为它需要将属性的标签列表翻译成 Lua识别的内容。我们是用自然的翻译方式,比如,类似下面的开始标签:

<to method="post" priority="high">

产生下面的属性表:

{ method= "post", priority= "high" }

f_StartElement 的实现如下:

static void f_StartElement (void *ud,const char *name,const char **atts) {
  lxp_userdata *xpu = (lxp_userdata *)ud;
  lua_State *L =xpu->L;

  lua_pushstring(L, "StartElement");
  lua_gettable(L, 3);
  if (lua_isnil(L, -1)) { /* no handler?*/
       lua_pop(L, 1);
       return;
}
   lua_pushvalue(L, 1); /* push the parser(`self') */
   lua_pushstring(L, name);/* push tag name*/

    /* create andfill the attribute table */
   lua_newtable(L);
   while (*atts) {
       lua_pushstring(L, *atts++);
       lua_pushstring(L, *atts++);
       lua_settable(L, -3);
}
    lua_call(L, 3, 0);  /* call thehandler */
}

解析器的最后一个方法是 close。当我们关闭一个解析器的时候,我们必须释放解析器对应的所有资源,即 Expat 结构和 callback 表。记住,在解析器创建的过程中如果发生错误,解析器并不拥有这些资源:

static int lxp_close (lua_State *L) {
  lxp_userdata *xpu;

  xpu = (lxp_userdata *)luaL_checkudata(L, 1,"Expat");
  luaL_argcheck(L, xpu, 1, "expat parserexpected");

  /* free (unref)callback table */
  luaL_unref(L,LUA_REGISTRYINDEX,xpu->tableref);
  xpu->tableref = LUA_REFNIL;

  /* free Expatparser (if thereis one) */
  if (xpu->parser)XML_ParserFree(xpu->parser);
  xpu->parser =NULL;
  return 0;
}

注意我们在关闭解析器的时候,是如何保证它处于一致的(consistent)状态的,当我们对一个己经关闭的解析器或者垃圾收集器己经收集这个解析器之后,再次关闭这个解 析器是没有问题的。实际上,我们使用这个函数作为我们的析构函数。他负责保证每一个解析器自动得释放他所有的资源,即使程序员没有关闭解析器。

最后一步是打开库,将上面各个部分放在一起。这儿我们使用和面向对象的数组例 子(27.3 节)一样的方案:创建一个 metatable,将所有的方法放在这个表内,表的_index 域指向自己。这样,我们还需要一个解析器方法的列表:

static const structluaL_reg lxp_meths[] = {
  {"parse", lxp_parse},
  {"close", lxp_close},
  {"_gc", lxp_close},
  {NULL, NULL}
};

我们也需要一个关于这个库中所有函数的列表。和 OO 库相同的是,这个库只有一 个函数,这个函数负责创建一个新的解析器:

static const structluaL_reg lxp_funcs[ ] = {
  {"new",lxp_make_parser},
  {NULL, NULL}
};

最终,open 函数必须要创建 metatable,并通过 _index 指向表本身,并且注册方法和函数:

int luaopen_lxp (lua_State *L) {
  /* create metatable */
  luaL_newmetatable(L,"Expat");

  /* metatable. index = metatable */
  lua_pushliteral(L, "index");
  lua_pushvalue(L, -2);
  lua_rawset(L, -3);

  /* register methods*/
  luaL_openlib (L,NULL, lxp_meths, 0);

  /* register functions (onlylxp.new) */
 luaL_openlib (L, "lxp", lxp_funcs, 0);
 return 1;
}

时间: 2024-11-11 23:05:52

Lua_第28章 资源管理(下)的相关文章

Lua_第28章 资源管理(上)

Lua_第28章  资源管理 (上)  在前面一章介绍的数组实现方法,我们不必担心如何管理资源,只需要分配内存. 每一个表示数组的 userdatum 都有自己的内存,这个内存由 Lua 管理.当数组变为垃圾 (也就是说,当程序不需要)的时候,Lua 会自动收集并释放内存. 生活总是不那么如意.有时候,一个对象除了需要物理内存以外,还需要文件描述符.窗口句柄等类似的资源.(通常这些资源也是内存,但由系统的其他部分来管理). 在这种情况下,当一个对象成为垃圾并被收集的时候,这些相关的资源也应该被释

Lua_第19章 String 库(下)

Lua_第19章 String 库(下) 19.3捕获(Captures) Capture(下面译为捕获或者capture,模式中捕获的概念指,使用临时变量来保存匹配的子模式,常用于 向前引用.)是这样一种机制:可以使用模式串的一部分匹配目标串的一部分.将你想捕 获的模式用圆括号括起来,就指定了一个capture.在 string.find 使用captures 的时候,函数会返回捕获的值作为额外的结果.这常被用 来将一个目标串拆分成多个: pair = "name =Anna" _,

Lua_第27章 User-Defined Types in C

Lua_第27章  User-Defined Types in C 在上一章,我们讨论了如何使用 C 函数扩展 Lua 的功能,现在我们讨论如何使用 C 中新创建的类型来扩展 Lua.我们从一个小例子开始,本章后续部分将以这个小例子 为基础逐步加入 metamethods 等其他内容来介绍如何使用 C 中新类型扩展 Lua. 我们的例子涉及的类型非常简单,数字数组.这个例子的目的在于将目光集中到 API 问题上,所以不涉及复杂的算法.尽管例子中的类型很简单,但很多应用中都会用到这 种类型.一般情

Lua_第19章 String 库(上)

Lua_第19章String 库 Lua解释器对字符串的支持很有限.一个程序可以创建字符串并连接字符串,但不能截取子串,检查字符串的大小,检测字符串的内容.在 Lua中操纵字符串的功能基本来自于 string 库. String 库中的一些函数是非常简单的:string.len(s)返回字符串 s 的长度;string.rep(s, n)返回重复 n 次字符串 s 的串;你使用 string.rep("a", 2^20)可以创建一个 1M bytes 的字符 串(比如,为了测试需要);

Lua_第16 章 Weak 表

Lua_第16 章 Weak 表 Lua自动进行内存的管理.程序只能创建对象(表,函数等),而没有执行删除对象 的函数.通过使用垃圾收集技术,Lua 会自动删除那些失效的对象.这可以使你从内存 管理的负担中解脱出来.更重要的,可以让你从那些由此引发的大部分 BUG中解脱出 来,比如指针挂起(dangling   pointers)和内存溢出. 和其他的不同,Lua 的垃圾收集器不存在循环的问题.在使用循环性的数据结构的 时候,你无须加入特殊的操作;他们会像其他数据一样被收集.当然,有些时候即使更

Lua_第25章 调用 C 函数

第25章 调用 C 函数  扩展 Lua 的基本方法之一就是为应用程序注册新的 C 函数到 Lua中去. 当我们提到 Lua 可以调用 C 函数,不是指 Lua 可以调用任何类型的 C 函数(有一些包可以让 Lua 调用任意的 C 函数,但缺乏便捷和健壮性).正如我们前面所看到的,当C 调用 Lua函数的时候,必须遵循一些简单的协议来传递参数和获取返回结果.相似的, 从Lua 中调用 C 函数,也必须遵循一些协议来传递参数和获得返回结果.另外,从 Lua 调用 C 函数我们必须注册函数,也就是说

(linux shell)第一章--小试牛刀(下)

文章来源: (linux shell)第一章--小试牛刀(下) 1.6 数组和关联数组 1.6.1 预备知识 Bash同一时候支持普通数组和关联数组.普通数组仅仅能使用整数作为数组索引,而关联数组能够使用字符串作为数组索引.关联数组在非常多操作中相当实用. 1.6.2 实战演练 定义数组的方法有非常多,能够在单行中使用一列值来定义一个数组: array_var=(1,2,3,4,5,6)   #这些值将会存储在以0为起始索引的连续位置上 另外.还能够将数组定义成一组索引-值: array_var

【数据结构】第6章 树(下)

数据结构第6章 树(下) §6.4 树和森林 6.4.1 树的储存结构 ①父亲表示法(利用每个(除根)结点只有唯一的父亲的性质) ②孩子表示法(用广义表实现) ③孩子兄弟表示法(二叉链表指向第一个孩子结点和下一个兄弟结点) 6.4.2森林与二叉树的转换 二叉树和树都可以用二叉链作为储存结构(分别是孩子表示法和孩子兄弟表示法),给定一棵树,可以找到唯一的一棵二叉树与之对应.两者的物理结构是相同的,只是解释不同而已(旋转). 任何一棵和树对应的二叉树,其右子树必空(因为根是没有兄弟的),在森林中可以

第28章 CSS3多列布局

第 28章 CSS3多列布局学习要点:1.早期多列问题2.属性及版本3.属性解释 本章主要探讨 HTML5中 CSS3提供的多列布局,通过多列布局我们方便的创建流体的多列布局.一.早期多列问题我们有时想布局成报纸.杂志那样的多列方式(至少两列,一般三列以上),但早期CSS提供的布局方式都有着极大的限制.如果是固体布局,那么使用浮动或定位布局都可以完成.但对于流体的多列,比如三列以上,那只能使用浮动布局进行,而它又有极大的限制.因为它是区块性的,对于动态的内容无法伸缩自适应,基本无能力.//五段内