1#!/usr/local/bin/lua5.1
2-- See Copyright Notice in license.html
3-- $Id: test.lua,v 1.6 2006/06/08 20:34:52 tomas Exp $
4
5if string.find(_VERSION, " 5.0") then
6	lxp = assert(loadlib("./lxp.so", "luaopen_lxp"))()
7else
8	lxp = require"lxp"
9	gcinfo = function () return collectgarbage"count" end
10end
11print (lxp._VERSION)
12assert(lxp.new, "Cannot find function lxp.new ("..tostring(lxp.new)..")")
13
14-- basic test with no preamble
15local p = lxp.new{}
16p:setencoding("ISO-8859-1")
17assert(p:parse[[<tag cap="5">hi</tag>]])
18p:close()
19
20
21preamble = [[
22<?xml version="1.0" encoding="ISO-8859-1"?>
23
24<!DOCTYPE greeting [
25  <!ENTITY xuxu "is this a xuxu?">
26
27  <!ATTLIST to
28     method  CDATA   #FIXED "POST"
29  >
30
31  <!ENTITY test-entity
32           SYSTEM "entity1.xml">
33
34  <!NOTATION TXT SYSTEM "txt">
35
36  <!ENTITY test-unparsed SYSTEM "unparsed.txt" NDATA txt>
37
38  <!ATTLIST hihi
39      explanation ENTITY #REQUIRED>
40
41]>
42]]
43
44X = {}
45if string.find(_VERSION, " 5.0") then
46	function getargs (...) X = arg end
47	function xgetargs (c)
48	  return function (...)
49	    table.insert(arg, 1, c)
50	    table.insert(X, arg)
51	  end
52	end
53else
54	(loadstring or load)[[
55	function getargs (...)
56		X = { ... }
57		X.n = select('#', ...)
58	end
59	function xgetargs (c)
60	  return function (...)
61	    local arg = { ... }
62	    arg.n = select('#', ...) + 1
63	    table.insert(arg, 1, c)
64	    table.insert(X, arg)
65	  end
66	end
67	table.getn = function (t)
68		if t.n then
69			return t.n
70		else
71			local n = 0
72			for i in pairs(t) do
73				if type(i) == "number" then
74					n = math.max(n, i)
75				end
76			end
77			return n
78		end
79	end]]()
80end
81
82
83
84-------------------------------
85print("testing start/end tags")
86callbacks = {
87  StartElement = getargs,
88  EndElement = getargs,
89}
90p = lxp.new(callbacks)
91assert(p:getcallbacks() == callbacks)
92assert(p:parse(preamble))
93assert(p:parse([[
94<to priority="10" xu = "hi">
95]]))
96assert(X.n == 3 and X[1] == p and X[2] == "to")
97x = X[3]
98assert(x.priority=="10" and x.xu=="hi" and x.method=="POST")
99assert(x[1] == "priority" and x[2] == "xu" and table.getn(x) == 2, "x[1] == "..tostring(x[1])..", x[2] == "..tostring(x[2])..", #x == "..tostring(table.getn(x)))
100assert(p:parse("</to>"))
101assert(p:parse())
102p:close()
103
104
105-------------------------------
106print("testing CharacterData/Cdata")
107callbacks = {
108  CharacterData = getargs,
109}
110p = lxp.new(callbacks)
111assert(p:parse(preamble))
112assert(p:parse"<to>a basic text&lt;<![CDATA[<<ha>>]]></to>")
113assert(X[1] == p and X[2] == "a basic text<<<ha>>")
114callbacks.chardata = error   -- no more calls to `chardata'
115assert(p:parse(""))
116assert(p:parse())
117-- assert(p:parse())   -- no problem to finish twice. alas, it has problems
118assert(p:getcallbacks() == callbacks)
119p:close()
120
121-------------------------------
122callbacks = {
123  CharacterData = xgetargs"c",
124  StartCdataSection = xgetargs"s",
125  EndCdataSection = xgetargs"e",
126}
127X = {}
128p = lxp.new(callbacks)
129assert(p:parse(preamble))
130assert(p:parse"<to>")
131assert(p:parse"<![CDATA[hi]]>")
132assert(table.getn(X) == 3)
133assert(X[1][1] == "s" and X[1][2] == p, "X[1][1] == "..tostring(X[1][1])..", X[1][2] == "..tostring(X[1][2])..", p == "..tostring(p))
134assert(X[2][1] == "c" and X[2][2] == p and X[2][3] == "hi")
135assert(X[3][1] == "e" and X[3][2] == p)
136assert(p:parse"</to>")
137p:close()
138
139
140-------------------------------
141print("testing ProcessingInstruction")
142callbacks = {ProcessingInstruction = getargs}
143p = lxp.new(callbacks)
144assert(p:parse[[
145<to>
146  <?lua how is this passed to <here>? ?>
147</to>
148]])
149assert(X[1] == p and X[2] == "lua" and
150       X[3] == "how is this passed to <here>? ")
151p:close()
152
153
154------------------------------
155print("testing Comment")
156callbacks = {Comment = xgetargs"c"; CharacterData = xgetargs"t"}
157X = {}
158p = lxp.new(callbacks)
159assert(p:parse[[
160<to>some text
161<!-- <a comment> with some & symbols -->
162some more text</to>
163
164]])
165p:close()
166
167assert(X[1][1] == "t" and X[2][1] == "c" and X[3][1] == "t")
168assert(X[1][2] == X[2][2] and X[2][2] == X[3][2] and X[3][2] == p)
169assert(X[1][3] == "some text\n")
170assert(X[2][3] == " <a comment> with some & symbols ")
171assert(X[3][3] == "\nsome more text")
172
173
174----------------------------
175print("testing ExternalEntity")
176entities = {
177["entity1.xml"] = "<hi/>"
178}
179
180callbacks = {StartElement = xgetargs"s", EndElement = xgetargs"e",
181  ExternalEntityRef = function (p, context, base, systemID, publicId)
182    assert(base == "/base")
183    return context:parse(entities[systemID])
184  end}
185
186X = {}
187p = lxp.new(callbacks)
188p:setbase("/base")
189assert(p:parse(preamble))
190assert(p:parse[[
191<to> &test-entity;
192</to>
193]])
194assert(p:getbase() == "/base")
195p:close()
196assert(X[1][1] == "s" and X[1][3] == "to")
197assert(X[2][1] == "s" and X[2][3] == "hi")
198assert(X[3][1] == "e" and X[3][3] == "hi")
199assert(X[4][1] == "e" and X[4][3] == "to")
200
201
202----------------------------
203print("testing default handles")
204text = [[<to> hi &xuxu; </to>]]
205local t = ""
206
207callbacks = { Default = function (p, s) t = t .. s end }
208p = lxp.new(callbacks)
209assert(p:parse(preamble))
210assert(p:parse(text))
211p:close()
212assert(t == preamble..text)
213
214t = ""
215callbacks = { DefaultExpand = function (p, s) t = t .. s end }
216p = lxp.new(callbacks)
217assert(p:parse(preamble))
218assert(p:parse(text))
219p:close()
220assert(t == preamble..string.gsub(text, "&xuxu;", "is this a xuxu?"))
221
222
223----------------------------
224print("testing notation declarations and unparsed entities")
225
226callbacks = {
227  UnparsedEntityDecl = getargs,
228  NotationDecl = function (p, name, base, systemId, publicId)
229    assert(name == "TXT" and systemId == "txt" and base == "/base")
230  end,
231 }
232p = lxp.new(callbacks)
233p:setbase("/base")
234assert(p:parse(preamble))
235assert(p:parse[[<hihi explanation="test-unparsed"/>]])
236p:close()
237assert(X[2] == "test-unparsed" and X[3] == "/base" and
238       X[4] == "unparsed.txt" and X[6] == "txt" and X.n == 6)
239
240
241
242----------------------------
243print("testing namespace declarations")
244callbacks = { StartNamespaceDecl = xgetargs"sn",
245              EndNamespaceDecl = xgetargs"en",
246              StartElement = xgetargs"s",
247              EndElement = xgetargs"e",
248}
249X = {}
250p = lxp.new(callbacks, "?")
251assert(p:parse[[
252<x xmlns:space='a/namespace'>
253  <space:a/>
254</x>
255]])
256p:close()
257x = X[1]
258assert(x[1] == "sn" and x[3] == "space" and x[4] == "a/namespace" and table.getn(x) == 4, "x[1] == "..tostring(x[1])..", x[3] == "..tostring(x[3])..", x[4] == "..tostring(x[4])..", #x == "..tostring(table.getn(x)))
259x = X[3]
260assert(x[1] == "s" and x[3] == "a/namespace?a")
261x = X[4]
262assert(x[1] == "e" and x[3] == "a/namespace?a")
263x = X[6]
264assert(x[1] == "en" and x[3] == "space" and table.getn(x) == 3)
265
266----------------------------
267print("testing doctype declarations")
268
269callbacks = {
270  StartDoctypeDecl = getargs
271 }
272p = lxp.new(callbacks)
273assert(p:parse([[<!DOCTYPE root PUBLIC "foo" "hello-world">]]))
274assert(p:parse[[<root/>]])
275p:close()
276assert(X[2] == "root" and X[3] == "hello-world" and X[4] == "foo" and
277       X[5] == false)
278
279-- Error reporting
280p = lxp.new{}
281data = [[
282<tag>
283  <other< </other>
284</tag>
285]]
286local status, msg, line, col, byte = p:parse(data)
287assert(status == nil and type(msg) == "string" and line == 2 and col == 9)
288assert(string.sub(data, byte, byte) == "<")
289
290p = lxp.new{}
291p:parse("<to>")
292local status, msg, line, col, byte = p:parse()
293assert(status == nil and line == 1 and col == 5 and byte == 5)
294
295
296-- position reporting
297callbacks = { ProcessingInstruction = function (p)
298  X = {p:pos()}
299end
300}
301
302p = lxp.new(callbacks)
303assert(p:parse[[
304<to> <?test where is `pos'? ?>
305</to>
306]])
307p:close()
308assert(X[1] == 1  and X[2] == 6 and X[3] == 6)  -- line, column, abs. position
309
310
311
312print("testing errors")
313-- invalid keys
314assert(not pcall(lxp.new, {StatCdata=print}))
315assert(pcall(lxp.new, {StatCdata=print, _nonstrict = true}))
316
317-- invalid sequences
318p = lxp.new{}
319assert(p:parse[[<to></to>]])
320assert(p:parse())
321assert(p:parse(" ") == nil)
322
323-- closing unfinished document
324p = lxp.new{}
325assert(p:parse[[<to>]])
326local status, err = pcall(p.close, p)
327assert(not status and string.find(err, "error closing parser"))
328
329-- closing unfinished document
330print("testing parser:stop()");
331local stopped;
332p = lxp.new{
333	StartElement = function (parser, name, attr)
334		if name == "stop" then
335			parser:stop()
336			stopped = true
337		else
338			stopped = false
339		end
340	end
341}
342local ok, err = p:parse[[<root><parseme>Hello</parseme><stop>here</stop><notparsed/></root>]];
343assert(not ok)
344assert(err == "parsing aborted")
345assert(stopped == true, "parser not stopped")
346
347
348-- test for GC
349print("\ntesting garbage collection")
350collectgarbage(); collectgarbage()
351local x = gcinfo()
352for i=1,100000 do
353  -- due to a small bug in Lua...
354  if (math.mod or math.fmod)(i, 100) == 0 then collectgarbage() end
355  lxp.new({})
356end
357collectgarbage(); collectgarbage()
358assert(math.abs(gcinfo() - x) <= 2)
359
360
361print"OK"
362
363