src/lxp/totable.lua

1
-- See Copyright Notice in license.html
2
-- Based on Luiz Henrique de Figueiredo's lxml:
3
-- http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lxml
4
 
5
local lxp = require "lxp"
6
 
7
local table = require"table"
8
local tinsert, tremove = table.insert, table.remove
9
local assert, pairs, tostring, type = assert, pairs, tostring, type
10
 
11
-- auxiliary functions -------------------------------------------------------
12
local function starttag (p, tag, attr)
13
	local stack = p:getcallbacks().stack
14
	local newelement = {[0] = tag}
15
	for i = 1, #attr do
16
		local attrname = attr[i]
17
		local attrvalue = attr[attrname]
18
		newelement[attrname] = attrvalue
19
	end
20
	tinsert(stack, newelement)
21
end
22
 
23
local function endtag (p, tag)
24
	local stack = p:getcallbacks().stack
25
	local element = tremove(stack)
26
	assert(element[0] == tag, "Error while closing element: table[0] should be `"..tostring(tag).."' but is `"..tostring(element[0]).."'")
27
	local level = #stack
28
	tinsert(stack[level], element)
29
end
30
 
31
local function text (p, txt)
32
	local stack = p:getcallbacks().stack
33
	local element = stack[#stack]
34
	local n = #element
35
	if type(element[n]) == "string" and n > 0 then
36
		element[n] = element[n] .. txt
37
	else
38
		tinsert(element, txt)
39
	end
40
end
41
 
42
-- main function -------------------------------------------------------------
43
local function parse (o)
44
	local c = {
45
		StartElement = starttag,
46
		EndElement = endtag,
47
		CharacterData = text,
48
		_nonstrict = true,
49
		stack = {{}},
50
	}
51
	local p = lxp.new(c)
52
	if type(o) == "string" then
53
		local status, err, line, col, pos = p:parse(o)
54
		if not status then return nil, err, line, col, pos end
55
	else
56
		for l in pairs(o) do
57
			local status, err, line, col, pos = p:parse(l)
58
			if not status then return nil, err, line, col, pos end
59
		end
60
	end
61
	local status, err, line, col, pos = p:parse() -- close document
62
	if not status then return nil, err, line, col, pos end
63
	p:close()
64
	return c.stack[1][1]
65
end
66
 
67
-- utility functions ---------------------------------------------------------
68
local function compact (t) -- remove empty entries
69
	local n = 0
70
	for i = 1, #t do
71
		local v = t[i]
72
		if v then
73
			n = n+1
74
			if n ~= i then
75
				t[n] = v
76
				t[i] = nil
77
			end
78
		else
79
			t[i] = nil
80
		end
81
	end
82
end
83
 
84
local function clean (t) -- remove empty strings
85
	for i = 1, #t do
86
		local v = t[i]
87
		local tv = type(v)
88
		if tv == "table" then
89
			clean (v)
90
		elseif tv == "string" and v:match"^%s*$" then
91
			t[i] = false
92
		end
93
	end
94
	compact (t)
95
end
96
 
97
local function torecord (t) -- move 1-value subtables to table entries
98
	for i = 1, #t do
99
		local v = t[i]
100
		if type(v) == "table" then
101
			if #v == 1 and type(v[1]) == "string" and t[v[0]] == nil then
102
				t[v[0]] = v[1]
103
				t[i] = false
104
			else
105
				torecord (v)
106
			end
107
		end
108
	end
109
	compact (t)
110
end
111
 
112
return {
113
	clean = clean,
114
	compact = compact,
115
	parse = parse,
116
	torecord = torecord,
117
}