| 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 | |
| 5 | if string.find(_VERSION, " 5.0") then |
| 6 | lxp = assert(loadlib("./lxp.so", "luaopen_lxp"))() |
| 7 | else |
| 8 | lxp = require"lxp" |
| 9 | gcinfo = function () return collectgarbage"count" end |
| 10 | end |
| 11 | print (lxp._VERSION) |
| 12 | assert(lxp.new, "Cannot find function lxp.new ("..tostring(lxp.new)..")") |
| 13 | |
| 14 | -- basic test with no preamble |
| 15 | local p = lxp.new{} |
| 16 | p:setencoding("ISO-8859-1") |
| 17 | assert(p:parse[[<tag cap="5">hi</tag>]]) |
| 18 | p:close() |
| 19 | |
| 20 | |
| 21 | preamble = [[ |
| 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 | |
| 44 | X = {} |
| 45 | if 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 |
| 53 | else |
| 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]]() |
| 80 | end |
| 81 | |
| 82 | |
| 83 | |
| 84 | ------------------------------- |
| 85 | print("testing start/end tags") |
| 86 | callbacks = { |
| 87 | StartElement = getargs, |
| 88 | EndElement = getargs, |
| 89 | } |
| 90 | p = lxp.new(callbacks) |
| 91 | assert(p:getcallbacks() == callbacks) |
| 92 | assert(p:parse(preamble)) |
| 93 | assert(p:parse([[ |
| 94 | <to priority="10" xu = "hi"> |
| 95 | ]])) |
| 96 | assert(X.n == 3 and X[1] == p and X[2] == "to") |
| 97 | x = X[3] |
| 98 | assert(x.priority=="10" and x.xu=="hi" and x.method=="POST") |
| 99 | assert(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))) |
| 100 | assert(p:parse("</to>")) |
| 101 | assert(p:parse()) |
| 102 | p:close() |
| 103 | |
| 104 | |
| 105 | ------------------------------- |
| 106 | print("testing CharacterData/Cdata") |
| 107 | callbacks = { |
| 108 | CharacterData = getargs, |
| 109 | } |
| 110 | p = lxp.new(callbacks) |
| 111 | assert(p:parse(preamble)) |
| 112 | assert(p:parse"<to>a basic text<<![CDATA[<<ha>>]]></to>") |
| 113 | assert(X[1] == p and X[2] == "a basic text<<<ha>>") |
| 114 | callbacks.chardata = error -- no more calls to `chardata' |
| 115 | assert(p:parse("")) |
| 116 | assert(p:parse()) |
| 117 | -- assert(p:parse()) -- no problem to finish twice. alas, it has problems |
| 118 | assert(p:getcallbacks() == callbacks) |
| 119 | p:close() |
| 120 | |
| 121 | ------------------------------- |
| 122 | callbacks = { |
| 123 | CharacterData = xgetargs"c", |
| 124 | StartCdataSection = xgetargs"s", |
| 125 | EndCdataSection = xgetargs"e", |
| 126 | } |
| 127 | X = {} |
| 128 | p = lxp.new(callbacks) |
| 129 | assert(p:parse(preamble)) |
| 130 | assert(p:parse"<to>") |
| 131 | assert(p:parse"<![CDATA[hi]]>") |
| 132 | assert(table.getn(X) == 3) |
| 133 | assert(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)) |
| 134 | assert(X[2][1] == "c" and X[2][2] == p and X[2][3] == "hi") |
| 135 | assert(X[3][1] == "e" and X[3][2] == p) |
| 136 | assert(p:parse"</to>") |
| 137 | p:close() |
| 138 | |
| 139 | |
| 140 | ------------------------------- |
| 141 | print("testing ProcessingInstruction") |
| 142 | callbacks = {ProcessingInstruction = getargs} |
| 143 | p = lxp.new(callbacks) |
| 144 | assert(p:parse[[ |
| 145 | <to> |
| 146 | <?lua how is this passed to <here>? ?> |
| 147 | </to> |
| 148 | ]]) |
| 149 | assert(X[1] == p and X[2] == "lua" and |
| 150 | X[3] == "how is this passed to <here>? ") |
| 151 | p:close() |
| 152 | |
| 153 | |
| 154 | ------------------------------ |
| 155 | print("testing Comment") |
| 156 | callbacks = {Comment = xgetargs"c"; CharacterData = xgetargs"t"} |
| 157 | X = {} |
| 158 | p = lxp.new(callbacks) |
| 159 | assert(p:parse[[ |
| 160 | <to>some text |
| 161 | <!-- <a comment> with some & symbols --> |
| 162 | some more text</to> |
| 163 | |
| 164 | ]]) |
| 165 | p:close() |
| 166 | |
| 167 | assert(X[1][1] == "t" and X[2][1] == "c" and X[3][1] == "t") |
| 168 | assert(X[1][2] == X[2][2] and X[2][2] == X[3][2] and X[3][2] == p) |
| 169 | assert(X[1][3] == "some text\n") |
| 170 | assert(X[2][3] == " <a comment> with some & symbols ") |
| 171 | assert(X[3][3] == "\nsome more text") |
| 172 | |
| 173 | |
| 174 | ---------------------------- |
| 175 | print("testing ExternalEntity") |
| 176 | entities = { |
| 177 | ["entity1.xml"] = "<hi/>" |
| 178 | } |
| 179 | |
| 180 | callbacks = {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 | |
| 186 | X = {} |
| 187 | p = lxp.new(callbacks) |
| 188 | p:setbase("/base") |
| 189 | assert(p:parse(preamble)) |
| 190 | assert(p:parse[[ |
| 191 | <to> &test-entity; |
| 192 | </to> |
| 193 | ]]) |
| 194 | assert(p:getbase() == "/base") |
| 195 | p:close() |
| 196 | assert(X[1][1] == "s" and X[1][3] == "to") |
| 197 | assert(X[2][1] == "s" and X[2][3] == "hi") |
| 198 | assert(X[3][1] == "e" and X[3][3] == "hi") |
| 199 | assert(X[4][1] == "e" and X[4][3] == "to") |
| 200 | |
| 201 | |
| 202 | ---------------------------- |
| 203 | print("testing default handles") |
| 204 | text = [[<to> hi &xuxu; </to>]] |
| 205 | local t = "" |
| 206 | |
| 207 | callbacks = { Default = function (p, s) t = t .. s end } |
| 208 | p = lxp.new(callbacks) |
| 209 | assert(p:parse(preamble)) |
| 210 | assert(p:parse(text)) |
| 211 | p:close() |
| 212 | assert(t == preamble..text) |
| 213 | |
| 214 | t = "" |
| 215 | callbacks = { DefaultExpand = function (p, s) t = t .. s end } |
| 216 | p = lxp.new(callbacks) |
| 217 | assert(p:parse(preamble)) |
| 218 | assert(p:parse(text)) |
| 219 | p:close() |
| 220 | assert(t == preamble..string.gsub(text, "&xuxu;", "is this a xuxu?")) |
| 221 | |
| 222 | |
| 223 | ---------------------------- |
| 224 | print("testing notation declarations and unparsed entities") |
| 225 | |
| 226 | callbacks = { |
| 227 | UnparsedEntityDecl = getargs, |
| 228 | NotationDecl = function (p, name, base, systemId, publicId) |
| 229 | assert(name == "TXT" and systemId == "txt" and base == "/base") |
| 230 | end, |
| 231 | } |
| 232 | p = lxp.new(callbacks) |
| 233 | p:setbase("/base") |
| 234 | assert(p:parse(preamble)) |
| 235 | assert(p:parse[[<hihi explanation="test-unparsed"/>]]) |
| 236 | p:close() |
| 237 | assert(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 | ---------------------------- |
| 243 | print("testing namespace declarations") |
| 244 | callbacks = { StartNamespaceDecl = xgetargs"sn", |
| 245 | EndNamespaceDecl = xgetargs"en", |
| 246 | StartElement = xgetargs"s", |
| 247 | EndElement = xgetargs"e", |
| 248 | } |
| 249 | X = {} |
| 250 | p = lxp.new(callbacks, "?") |
| 251 | assert(p:parse[[ |
| 252 | <x xmlns:space='a/namespace'> |
| 253 | <space:a/> |
| 254 | </x> |
| 255 | ]]) |
| 256 | p:close() |
| 257 | x = X[1] |
| 258 | assert(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))) |
| 259 | x = X[3] |
| 260 | assert(x[1] == "s" and x[3] == "a/namespace?a") |
| 261 | x = X[4] |
| 262 | assert(x[1] == "e" and x[3] == "a/namespace?a") |
| 263 | x = X[6] |
| 264 | assert(x[1] == "en" and x[3] == "space" and table.getn(x) == 3) |
| 265 | |
| 266 | ---------------------------- |
| 267 | print("testing doctype declarations") |
| 268 | |
| 269 | callbacks = { |
| 270 | StartDoctypeDecl = getargs |
| 271 | } |
| 272 | p = lxp.new(callbacks) |
| 273 | assert(p:parse([[<!DOCTYPE root PUBLIC "foo" "hello-world">]])) |
| 274 | assert(p:parse[[<root/>]]) |
| 275 | p:close() |
| 276 | assert(X[2] == "root" and X[3] == "hello-world" and X[4] == "foo" and |
| 277 | X[5] == false) |
| 278 | |
| 279 | -- Error reporting |
| 280 | p = lxp.new{} |
| 281 | data = [[ |
| 282 | <tag> |
| 283 | <other< </other> |
| 284 | </tag> |
| 285 | ]] |
| 286 | local status, msg, line, col, byte = p:parse(data) |
| 287 | assert(status == nil and type(msg) == "string" and line == 2 and col == 9) |
| 288 | assert(string.sub(data, byte, byte) == "<") |
| 289 | |
| 290 | p = lxp.new{} |
| 291 | p:parse("<to>") |
| 292 | local status, msg, line, col, byte = p:parse() |
| 293 | assert(status == nil and line == 1 and col == 5 and byte == 5) |
| 294 | |
| 295 | |
| 296 | -- position reporting |
| 297 | callbacks = { ProcessingInstruction = function (p) |
| 298 | X = {p:pos()} |
| 299 | end |
| 300 | } |
| 301 | |
| 302 | p = lxp.new(callbacks) |
| 303 | assert(p:parse[[ |
| 304 | <to> <?test where is `pos'? ?> |
| 305 | </to> |
| 306 | ]]) |
| 307 | p:close() |
| 308 | assert(X[1] == 1 and X[2] == 6 and X[3] == 6) -- line, column, abs. position |
| 309 | |
| 310 | |
| 311 | |
| 312 | print("testing errors") |
| 313 | -- invalid keys |
| 314 | assert(not pcall(lxp.new, {StatCdata=print})) |
| 315 | assert(pcall(lxp.new, {StatCdata=print, _nonstrict = true})) |
| 316 | |
| 317 | -- invalid sequences |
| 318 | p = lxp.new{} |
| 319 | assert(p:parse[[<to></to>]]) |
| 320 | assert(p:parse()) |
| 321 | assert(p:parse(" ") == nil) |
| 322 | |
| 323 | -- closing unfinished document |
| 324 | p = lxp.new{} |
| 325 | assert(p:parse[[<to>]]) |
| 326 | local status, err = pcall(p.close, p) |
| 327 | assert(not status and string.find(err, "error closing parser")) |
| 328 | |
| 329 | -- closing unfinished document |
| 330 | print("testing parser:stop()"); |
| 331 | local stopped; |
| 332 | p = 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 | } |
| 342 | local ok, err = p:parse[[<root><parseme>Hello</parseme><stop>here</stop><notparsed/></root>]]; |
| 343 | assert(not ok) |
| 344 | assert(err == "parsing aborted") |
| 345 | assert(stopped == true, "parser not stopped") |
| 346 | |
| 347 | |
| 348 | -- test for GC |
| 349 | print("\ntesting garbage collection") |
| 350 | collectgarbage(); collectgarbage() |
| 351 | local x = gcinfo() |
| 352 | for 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({}) |
| 356 | end |
| 357 | collectgarbage(); collectgarbage() |
| 358 | assert(math.abs(gcinfo() - x) <= 2) |
| 359 | |
| 360 | -- test for GC (circular references) |
| 361 | print("testing garbage collection (circular reference)") |
| 362 | collectgarbage(); collectgarbage() |
| 363 | local x = gcinfo() |
| 364 | for i=1,100000 do |
| 365 | -- due to a small bug in Lua... |
| 366 | if (math.mod or math.fmod)(i, 100) == 0 then collectgarbage() end |
| 367 | local p; |
| 368 | p = lxp.new({ StartElement = function () print(p) end }); |
| 369 | end |
| 370 | collectgarbage(); collectgarbage() |
| 371 | assert(math.abs(gcinfo() - x) <= 2) |
| 372 | |
| 373 | print("OK", _VERSION) |