cgi-bin/tek/class/markup.lua
author Timm S. Mueller <tmueller@neoscientists.org>
Sat, 15 Dec 2007 17:32:40 +0100
changeset 223 b2151b233fa7
parent 220 b2e0af2b5a8f
child 228 4386f52e29fb
permissions -rw-r--r--
Incomplete Markup class instantiation; indentation now using <div> again
tmueller@219
     1
-------------------------------------------------------------------------------
tmueller@0
     2
--
tmueller@219
     3
--	tek.class.markup
tmueller@219
     4
--	Written by Timm S. Mueller <tmueller@schulze-mueller.de>
tmueller@0
     5
--	See copyright notice in COPYRIGHT
tmueller@0
     6
--
tmueller@219
     7
--	OVERVIEW::
tmueller@219
     8
--	Markup parser - produces fancy HTML from plain text with special
tmueller@219
     9
--	formattings.
tmueller@219
    10
--
tmueller@219
    11
-------------------------------------------------------------------------------
tmueller@0
    12
tmueller@219
    13
local Class = require "tek.class"
tmueller@0
    14
tmueller@219
    15
local type = type
tmueller@219
    16
local insert = table.insert
tmueller@219
    17
local remove = table.remove
tmueller@219
    18
local concat = table.concat
tmueller@219
    19
local min = math.min
tmueller@219
    20
local max = math.max
tmueller@219
    21
local char = string.char
tmueller@219
    22
local stdin = io.stdin
tmueller@219
    23
local stdout = io.stdout
tmueller@219
    24
local ipairs = ipairs
tmueller@99
    25
tmueller@219
    26
module("tek.class.markup", tek.class)
tmueller@99
    27
tmueller@219
    28
_VERSION = "Markup 1.0"
tmueller@219
    29
local Markup = _M
tmueller@99
    30
tmueller@219
    31
-------------------------------------------------------------------------------
tmueller@219
    32
--	iterate over lines in a string
tmueller@219
    33
-------------------------------------------------------------------------------
tmueller@99
    34
tmueller@219
    35
local function rd_string(s)
tmueller@0
    36
	local pos = 1
tmueller@0
    37
	return function()
tmueller@0
    38
		if pos then
tmueller@0
    39
			local a, e = s:find("[\r]?\n", pos)
tmueller@0
    40
			local o = pos
tmueller@0
    41
			if not a then
tmueller@0
    42
				pos = nil
tmueller@0
    43
				return s:sub(o)
tmueller@0
    44
			else
tmueller@0
    45
				pos = e + 1
tmueller@0
    46
				return s:sub(o, a - 1)
tmueller@0
    47
			end
tmueller@0
    48
		end
tmueller@0
    49
	end
tmueller@0
    50
end
tmueller@0
    51
tmueller@219
    52
-------------------------------------------------------------------------------
tmueller@219
    53
--	utf8values: iterator over UTF-8 encoded Unicode chracter codes
tmueller@219
    54
-------------------------------------------------------------------------------
tmueller@58
    55
tmueller@219
    56
local function utf8values(s)
tmueller@219
    57
tmueller@219
    58
	local readc
tmueller@219
    59
	local i = 0
tmueller@219
    60
	if type(s) == "string" then
tmueller@219
    61
		readc = function()
tmueller@219
    62
			i = i + 1
tmueller@219
    63
			return s:byte(i)
tmueller@219
    64
		end
tmueller@219
    65
	else
tmueller@219
    66
		readc = function()
tmueller@219
    67
			local c = s:read(1)
tmueller@219
    68
			return c and c:byte(1)
tmueller@219
    69
		end
tmueller@219
    70
	end
tmueller@219
    71
tmueller@219
    72
	local accu = 0
tmueller@219
    73
	local numa = 0
tmueller@219
    74
	local min, buf
tmueller@219
    75
tmueller@219
    76
	return function()
tmueller@219
    77
		local c
tmueller@219
    78
		while true do
tmueller@219
    79
			if buf then
tmueller@219
    80
				c = buf
tmueller@219
    81
				buf = nil
tmueller@219
    82
			else
tmueller@219
    83
				c = readc()
tmueller@219
    84
			end
tmueller@219
    85
			if not c then
tmueller@219
    86
				return
tmueller@219
    87
			end
tmueller@219
    88
			if c == 254 or c == 255 then
tmueller@219
    89
				break
tmueller@219
    90
			end
tmueller@219
    91
			if c < 128 then
tmueller@219
    92
				if numa > 0 then
tmueller@219
    93
					buf = c
tmueller@219
    94
					break
tmueller@219
    95
				end
tmueller@219
    96
				return c
tmueller@219
    97
			elseif c < 192 then
tmueller@219
    98
				if numa == 0 then break end
tmueller@219
    99
				accu = accu * 64 + c - 128
tmueller@219
   100
				numa = numa - 1
tmueller@219
   101
				if numa == 0 then
tmueller@219
   102
					if accu == 0 or accu < min or
tmueller@219
   103
						(accu >= 55296 and accu <= 57343) then
tmueller@219
   104
						break
tmueller@219
   105
					end
tmueller@219
   106
					c = accu
tmueller@219
   107
					accu = 0
tmueller@219
   108
					return c
tmueller@219
   109
				end
tmueller@219
   110
			else
tmueller@219
   111
				if numa > 0 then
tmueller@219
   112
					buf = c
tmueller@219
   113
					break
tmueller@219
   114
				end
tmueller@219
   115
				if c < 224 then
tmueller@219
   116
					min = 128
tmueller@219
   117
					accu = c - 192
tmueller@219
   118
					numa = 1
tmueller@219
   119
				elseif c < 240 then
tmueller@219
   120
					min = 2048
tmueller@219
   121
					accu = c - 224
tmueller@219
   122
					numa = 2
tmueller@219
   123
				elseif c < 248 then
tmueller@219
   124
					min = 65536
tmueller@219
   125
					accu = c - 240
tmueller@219
   126
					numa = 3
tmueller@219
   127
				elseif c < 252 then
tmueller@219
   128
					min = 2097152
tmueller@219
   129
					accu = c - 248
tmueller@219
   130
					numa = 4
tmueller@219
   131
				else
tmueller@219
   132
					min = 67108864
tmueller@219
   133
					accu = c - 252
tmueller@219
   134
					numa = 5
tmueller@219
   135
				end
tmueller@219
   136
			end
tmueller@219
   137
		end
tmueller@219
   138
		accu = 0
tmueller@219
   139
		numa = 0
tmueller@219
   140
		return 65533 -- bad character
tmueller@219
   141
	end
tmueller@219
   142
end
tmueller@219
   143
tmueller@219
   144
-------------------------------------------------------------------------------
tmueller@219
   145
--	encodeform: encode for forms (display '<', '>', '&', '"' literally)
tmueller@219
   146
-------------------------------------------------------------------------------
tmueller@219
   147
tmueller@220
   148
function Markup:encodeform(s)
tmueller@219
   149
	local tab = { }
tmueller@219
   150
	if s then
tmueller@219
   151
		for c in utf8values(s) do
tmueller@219
   152
			if c == 34 then
tmueller@219
   153
				insert(tab, "&quot;")
tmueller@219
   154
			elseif c == 38 then
tmueller@219
   155
				insert(tab, "&amp;")
tmueller@219
   156
			elseif c == 60 then
tmueller@219
   157
				insert(tab, "&lt;")
tmueller@219
   158
			elseif c == 62 then
tmueller@219
   159
				insert(tab, "&gt;")
tmueller@219
   160
			elseif c == 91 or c == 93 or c > 126 then
tmueller@219
   161
				insert(tab, ("&#%03d;"):format(c))
tmueller@219
   162
			else
tmueller@219
   163
				insert(tab, char(c))
tmueller@219
   164
			end
tmueller@219
   165
		end
tmueller@219
   166
	end
tmueller@219
   167
	return concat(tab)
tmueller@219
   168
end
tmueller@219
   169
tmueller@219
   170
-------------------------------------------------------------------------------
tmueller@219
   171
--	encodeurl: encode string to url; optionally specify a string with a
tmueller@219
   172
--	set of characters that should be left unmodified, from: $&+,/:;=?@
tmueller@219
   173
-------------------------------------------------------------------------------
tmueller@219
   174
tmueller@219
   175
local function encodefunc(c)
tmueller@219
   176
	return ("%%%02x"):format(c:byte())
tmueller@219
   177
end
tmueller@219
   178
tmueller@220
   179
function Markup:encodeurl(s, excl)
tmueller@219
   180
	-- reserved chars with special meaning:
tmueller@219
   181
	local matchset = "$&+,/:;=?@"
tmueller@219
   182
	if excl == true then
tmueller@219
   183
		matchset = ""
tmueller@219
   184
	elseif excl and type(excl) == "string" then
tmueller@219
   185
		matchset = matchset:gsub("[" .. excl:gsub(".", "%%%1") .. "]", "")
tmueller@219
   186
	end
tmueller@219
   187
	-- unsafe chars are always substituted:
tmueller@219
   188
	matchset = matchset .. '"<>#%{}|\\^~[]`]'
tmueller@219
   189
	matchset = "[%z\001-\032\127-\255" .. matchset:gsub(".", "%%%1") .. "]"
tmueller@219
   190
	return s:gsub(matchset, encodefunc)
tmueller@219
   191
end
tmueller@219
   192
tmueller@219
   193
-------------------------------------------------------------------------------
tmueller@0
   194
--	get a SGML/XML tag
tmueller@219
   195
-------------------------------------------------------------------------------
tmueller@58
   196
tmueller@219
   197
function Markup:getTagML(id, tag, open)
tmueller@0
   198
	if tag then
tmueller@0
   199
		local tab = { tag }
tmueller@0
   200
		if id == "link" or id == "emphasis" or id == "code" then
tmueller@219
   201
			if not self.brpend and not self.inline then
tmueller@219
   202
				insert(tab, 1, ("\t"):rep(self.depth))
tmueller@0
   203
			end
tmueller@219
   204
			self.inline = true
tmueller@219
   205
			self.brpend = nil
tmueller@0
   206
		else
tmueller@160
   207
			if id == "image" then
tmueller@219
   208
				if not self.inline then
tmueller@219
   209
					insert(tab, 1, ("\t"):rep(self.depth))
tmueller@160
   210
				end
tmueller@219
   211
				self.brpend = true
tmueller@160
   212
			else
tmueller@160
   213
				if id == "pre" or id == "preline" then
tmueller@219
   214
					self.brpend = nil
tmueller@160
   215
				elseif open or id ~= "preline" then
tmueller@219
   216
					insert(tab, 1, ("\t"):rep(self.depth))
tmueller@219
   217
					if not open and self.inline then
tmueller@219
   218
						self.brpend = true
tmueller@160
   219
					end
tmueller@160
   220
				end
tmueller@219
   221
				if self.brpend then
tmueller@160
   222
					insert(tab, 1, "\n")
tmueller@160
   223
				end
tmueller@160
   224
				if id ~= "preline" or not open then
tmueller@160
   225
					insert(tab, "\n")
tmueller@160
   226
				end
tmueller@219
   227
				self.inline = nil
tmueller@219
   228
				self.brpend = nil
tmueller@0
   229
			end
tmueller@0
   230
		end
tmueller@99
   231
		return concat(tab)
tmueller@0
   232
	end
tmueller@0
   233
end
tmueller@0
   234
tmueller@219
   235
-------------------------------------------------------------------------------
tmueller@219
   236
--	get a SGML/XML line of text
tmueller@219
   237
-------------------------------------------------------------------------------
tmueller@99
   238
tmueller@219
   239
function Markup:getTextML(line, id)
tmueller@0
   240
	local tab = { }
tmueller@219
   241
	if self.brpend then
tmueller@99
   242
		insert(tab, "\n")
tmueller@219
   243
		insert(tab, ("\t"):rep(self.depth))
tmueller@0
   244
	else
tmueller@0
   245
		if id ~= "link" and id ~= "emphasis" and id ~= "code" then
tmueller@219
   246
			if not self.inline then
tmueller@219
   247
				insert(tab, ("\t"):rep(self.depth))
tmueller@0
   248
			end
tmueller@0
   249
		end
tmueller@0
   250
	end
tmueller@99
   251
	line:gsub("^(%s*)(.-)(%s*)$", function(a, b, c)
tmueller@0
   252
		if a ~= "" then
tmueller@99
   253
			insert(tab, " ")
tmueller@0
   254
		end
tmueller@99
   255
		insert(tab, b)
tmueller@0
   256
		if c ~= "" then
tmueller@99
   257
			insert(tab, " ")
tmueller@0
   258
		end
tmueller@0
   259
	end)
tmueller@219
   260
	self.brpend = true
tmueller@99
   261
	return concat(tab)
tmueller@0
   262
end
tmueller@0
   263
tmueller@219
   264
-------------------------------------------------------------------------------
tmueller@219
   265
--	definitions for output as XHTML 1.0 strict
tmueller@219
   266
-------------------------------------------------------------------------------
tmueller@99
   267
tmueller@219
   268
function Markup:out(...)
tmueller@219
   269
	self.wrfunc(concat { ... })
tmueller@219
   270
end
tmueller@58
   271
tmueller@219
   272
function Markup:init(docname)
tmueller@219
   273
	self.depth = 1
tmueller@219
   274
	return [[
tmueller@219
   275
<?xml version="1.0" encoding="utf-8" ?>
tmueller@219
   276
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
tmueller@219
   277
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
tmueller@219
   278
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
tmueller@219
   279
	<head>
tmueller@219
   280
		<title>]] .. docname .. [[</title>
tmueller@219
   281
		<link rel="stylesheet" href="manual.css" />
tmueller@219
   282
	</head>
tmueller@219
   283
	<body>
tmueller@219
   284
]]
tmueller@219
   285
end
tmueller@0
   286
tmueller@219
   287
function Markup:exit()
tmueller@219
   288
	return [[
tmueller@219
   289
	</body>
tmueller@219
   290
</html>
tmueller@219
   291
]]
tmueller@219
   292
end
tmueller@0
   293
tmueller@219
   294
function Markup:gettag(id, tag, open)
tmueller@219
   295
	return self:getTagML(id, tag, open)
tmueller@219
   296
end
tmueller@0
   297
tmueller@219
   298
function Markup:gettext(line, id)
tmueller@220
   299
	return self:encodeform(self:getTextML(line, id))
tmueller@219
   300
end
tmueller@0
   301
tmueller@219
   302
function Markup:getpre(line)
tmueller@220
   303
	return self:encodeform(line), '\n'
tmueller@219
   304
end
tmueller@0
   305
tmueller@219
   306
function Markup:getcode(line)
tmueller@220
   307
	return self:encodeform(line)
tmueller@219
   308
end
tmueller@0
   309
tmueller@219
   310
function Markup:head(level, text)
tmueller@219
   311
	return '<h' .. level .. '>', '</h' .. level .. '>'
tmueller@219
   312
end
tmueller@0
   313
tmueller@219
   314
function Markup:node(id, text)
tmueller@219
   315
	return ('<div class="node"><h3><a name="%s" id="%s"><code>%s</code></a></h3>'):format(
tmueller@219
   316
		id, id, text), '</div>'
tmueller@219
   317
end
tmueller@0
   318
tmueller@219
   319
function Markup:headnode(id, text, len, is_code)
tmueller@219
   320
	if is_code then
tmueller@219
   321
		return ('<hr /><div class="node"><h%d><a name="%s" id="%s"><code>%s</code></a></h%d>'):format(
tmueller@219
   322
			len, id, id, text, len), '</div>'
tmueller@219
   323
	else
tmueller@219
   324
		return ('<hr /><div class="node"><h%d><a name="%s" id="%s">%s</a></h%d>'):format(
tmueller@219
   325
			len, id, id, text, len), '</div>'
tmueller@219
   326
	end
tmueller@219
   327
end
tmueller@217
   328
tmueller@219
   329
function Markup:def(name)
tmueller@219
   330
	self.prebr = nil
tmueller@219
   331
	return '<div class="definition"><dfn>' .. name .. '</dfn>', '</div>'
tmueller@219
   332
end
tmueller@217
   333
tmueller@219
   334
function Markup:indent()
tmueller@219
   335
	return '<blockquote>', '</blockquote>'
tmueller@219
   336
end
tmueller@0
   337
tmueller@219
   338
function Markup:list()
tmueller@219
   339
	return '<ul>', '</ul>'
tmueller@219
   340
end
tmueller@0
   341
tmueller@219
   342
function Markup:item(bullet)
tmueller@219
   343
	if bullet then
tmueller@219
   344
		return '<li>', '</li>'
tmueller@219
   345
	end
tmueller@219
   346
	return '<li style="list-style-type: none">', '</li>'
tmueller@219
   347
end
tmueller@0
   348
tmueller@219
   349
function Markup:block()
tmueller@219
   350
	return '<p>', '</p>'
tmueller@219
   351
end
tmueller@0
   352
tmueller@219
   353
function Markup:rule()
tmueller@219
   354
	return '<hr />'
tmueller@219
   355
end
tmueller@0
   356
tmueller@219
   357
function Markup:pre()
tmueller@219
   358
	return '<pre>', '</pre>'
tmueller@219
   359
end
tmueller@0
   360
tmueller@219
   361
function Markup:code()
tmueller@219
   362
	return '<code>', '</code>'
tmueller@219
   363
end
tmueller@0
   364
tmueller@219
   365
function Markup:emphasis(len, text)
tmueller@219
   366
	if len == 1 then
tmueller@219
   367
		return '<em>', '</em>'
tmueller@219
   368
	elseif len == 2 then
tmueller@219
   369
		return '<strong>', '</strong>'
tmueller@219
   370
	else
tmueller@219
   371
		return '<em><strong>', '</strong></em>'
tmueller@219
   372
	end
tmueller@219
   373
end
tmueller@0
   374
tmueller@219
   375
function Markup:link(link)
tmueller@219
   376
	--local isurl = link:match("^%a*://.*$")
tmueller@219
   377
	local func = link:match("^(.*)%(%)$")
tmueller@219
   378
	if func then
tmueller@219
   379
		return '<a href="#' .. func .. '"><code>', '</code></a>'
tmueller@219
   380
	else
tmueller@219
   381
		return '<a href="' .. link .. '">', '</a>'
tmueller@219
   382
	end
tmueller@219
   383
end
tmueller@0
   384
tmueller@219
   385
function Markup:table(border)
tmueller@219
   386
	return '<table>', '</table>'
tmueller@219
   387
end
tmueller@0
   388
tmueller@219
   389
function Markup:row()
tmueller@219
   390
	self.column = 0
tmueller@219
   391
	return '<tr>', '</tr>'
tmueller@219
   392
end
tmueller@0
   393
tmueller@219
   394
function Markup:cell(border)
tmueller@219
   395
	self.column = self.column + 1
tmueller@219
   396
	return '<td class="column' .. self.column .. '">', '</td>'
tmueller@219
   397
end
tmueller@0
   398
tmueller@219
   399
function Markup:image(link)
tmueller@220
   400
	return '<img src="' .. self:encodeurl(link, true) .. '" />'
tmueller@219
   401
end
tmueller@0
   402
tmueller@219
   403
-------------------------------------------------------------------------------
tmueller@219
   404
--	run: Runs the parser
tmueller@219
   405
-------------------------------------------------------------------------------
tmueller@0
   406
tmueller@219
   407
function Markup:run()
tmueller@198
   408
tmueller@99
   409
	local function doid(id, ...)
tmueller@219
   410
		if self[id] then
tmueller@219
   411
			return self[id](self, ...)
tmueller@99
   412
		end
tmueller@99
   413
	end
tmueller@198
   414
tmueller@0
   415
	local function doout(id, ...)
tmueller@219
   416
		doid("out", doid(id, ...))
tmueller@0
   417
	end
tmueller@0
   418
tmueller@0
   419
	local function push(id, ...)
tmueller@219
   420
		local opentag, closetag = doid(id, ...)
tmueller@0
   421
		doout("gettag", id, opentag, true)
tmueller@219
   422
		insert(self.stack, { id = id, closetag = closetag })
tmueller@219
   423
		self.depth = self.depth + 1
tmueller@0
   424
	end
tmueller@0
   425
tmueller@0
   426
	local function pop()
tmueller@219
   427
		local e = remove(self.stack)
tmueller@0
   428
		if e then
tmueller@219
   429
			self.depth = self.depth - 1
tmueller@0
   430
			doout("gettag", e.id, e.closetag)
tmueller@0
   431
			return e.id
tmueller@0
   432
		end
tmueller@0
   433
	end
tmueller@0
   434
tmueller@0
   435
	local function top()
tmueller@219
   436
		local e = self.stack[#self.stack]
tmueller@0
   437
		if e then
tmueller@0
   438
			return e.id
tmueller@0
   439
		end
tmueller@0
   440
	end
tmueller@0
   441
tmueller@0
   442
	local function popuntil(...)
tmueller@0
   443
		local i
tmueller@0
   444
		repeat
tmueller@0
   445
			i = pop()
tmueller@219
   446
			for _, v in ipairs { ... } do
tmueller@219
   447
				if v == i then
tmueller@0
   448
					return
tmueller@0
   449
				end
tmueller@0
   450
			end
tmueller@0
   451
		until not i
tmueller@0
   452
	end
tmueller@0
   453
tmueller@0
   454
	local function popwhilenot(...)
tmueller@0
   455
		local i
tmueller@0
   456
		repeat
tmueller@0
   457
			i = top()
tmueller@219
   458
			for _, v in ipairs { ... } do
tmueller@219
   459
				if v == i then
tmueller@0
   460
					return
tmueller@0
   461
				end
tmueller@0
   462
			end
tmueller@0
   463
			pop()
tmueller@0
   464
		until not i
tmueller@0
   465
	end
tmueller@0
   466
tmueller@0
   467
	local function popwhile(...)
tmueller@0
   468
		local cont
tmueller@0
   469
		repeat
tmueller@0
   470
			local id = top()
tmueller@0
   471
			cont = false
tmueller@219
   472
			for _, v in ipairs { ... } do
tmueller@219
   473
				if v == id then
tmueller@0
   474
					local i = pop()
tmueller@0
   475
					cont = true
tmueller@0
   476
					break
tmueller@0
   477
				end
tmueller@0
   478
			end
tmueller@0
   479
		until not cont
tmueller@0
   480
	end
tmueller@0
   481
tmueller@0
   482
	local function popif(...)
tmueller@0
   483
		local i = top()
tmueller@219
   484
		for _, v in ipairs { ... } do
tmueller@219
   485
			if v == i then
tmueller@0
   486
				pop()
tmueller@0
   487
				return
tmueller@0
   488
			end
tmueller@0
   489
		end
tmueller@0
   490
	end
tmueller@0
   491
tmueller@0
   492
	local function checktop(...)
tmueller@0
   493
		local i = top()
tmueller@219
   494
		for _, v in ipairs { ... } do
tmueller@219
   495
			if v == i then
tmueller@0
   496
				return true
tmueller@0
   497
			end
tmueller@219
   498
		end
tmueller@0
   499
	end
tmueller@0
   500
tmueller@0
   501
	local function pushfragment(...)
tmueller@219
   502
		local line = concat { ... }
tmueller@219
   503
		if not self.context.fragments then
tmueller@219
   504
			self.context.fragments = { }
tmueller@0
   505
		end
tmueller@219
   506
		self.id = (self.id or 0) + 1
tmueller@219
   507
		insert(self.context.fragments, 1, { line = line, id = self.id })
tmueller@219
   508
		self.context.topid = self.id
tmueller@0
   509
	end
tmueller@0
   510
tmueller@0
   511
	local function popfragment()
tmueller@219
   512
		self.context.firstfragment = nil
tmueller@219
   513
		local frag = remove(self.context.fragments, 1)
tmueller@0
   514
		if frag then
tmueller@219
   515
			self.line = frag.line
tmueller@219
   516
			if frag.id == self.context.topid then
tmueller@219
   517
				self.context.firstfragment = self.context.parentfirstfragment
tmueller@0
   518
			end
tmueller@0
   519
		else
tmueller@219
   520
			self.line = nil
tmueller@0
   521
		end
tmueller@219
   522
		return self.line
tmueller@0
   523
	end
tmueller@0
   524
tmueller@0
   525
	local function pushcontext(id, line)
tmueller@219
   526
		insert(self.cstack, self.context)
tmueller@0
   527
		if id then
tmueller@219
   528
			self.context = { id = id, fragments = { } }
tmueller@0
   529
			pushfragment(line)
tmueller@219
   530
			insert(self.cstack, self.context)
tmueller@0
   531
		end
tmueller@0
   532
	end
tmueller@0
   533
tmueller@0
   534
	local function popcontext()
tmueller@219
   535
		self.context = remove(self.cstack)
tmueller@219
   536
		return self.context
tmueller@0
   537
	end
tmueller@0
   538
tmueller@0
   539
	-- parse
tmueller@0
   540
tmueller@219
   541
	self.lnr = 1
tmueller@219
   542
	self.previndent = 0
tmueller@219
   543
	self.stack = { }
tmueller@219
   544
	self.depth = 0
tmueller@219
   545
	self.in_table = nil
tmueller@219
   546
	self.prebr = nil
tmueller@221
   547
	self.is_dynamic_content = nil
tmueller@0
   548
tmueller@219
   549
	doout("init", self.docname)
tmueller@0
   550
	push("document")
tmueller@0
   551
tmueller@219
   552
	local linematch = ("^(%s*)(.-)%%s*$"):format(self.indentchar)
tmueller@0
   553
tmueller@219
   554
	local feature = { }
tmueller@219
   555
	self.features:gsub("%a", function(f)
tmueller@219
   556
		feature[f] = true
tmueller@219
   557
	end)
tmueller@219
   558
tmueller@219
   559
	for line in self.rdfunc(self.input) do
tmueller@219
   560
tmueller@219
   561
		self.indentlevel = 0
tmueller@219
   562
		line = line:gsub(linematch, function(s, t)
tmueller@0
   563
			if t ~= "" then
tmueller@219
   564
				self.indentlevel = s:len()
tmueller@0
   565
			else
tmueller@219
   566
				self.indentlevel = self.previndent
tmueller@0
   567
			end
tmueller@0
   568
			return t
tmueller@0
   569
		end)
tmueller@0
   570
tmueller@219
   571
		if feature["t"] then
tmueller@219
   572
			self.istabline = line:find("||", 1, 1)
tmueller@219
   573
			if self.istabline then
tmueller@219
   574
				self.indentlevel = self.previndent
tmueller@219
   575
			end
tmueller@219
   576
			if self.in_table then
tmueller@219
   577
				popuntil("row")
tmueller@219
   578
				if not self.istabline then
tmueller@219
   579
					popuntil("table")
tmueller@219
   580
					self.in_table = nil
tmueller@219
   581
				end
tmueller@0
   582
			end
tmueller@0
   583
		end
tmueller@198
   584
tmueller@219
   585
		if self.indentlevel < self.previndent then
tmueller@219
   586
			if not self.preindent or self.indentlevel < self.preindent then
tmueller@219
   587
				local i = self.indentlevel
tmueller@219
   588
				while i < self.previndent do
tmueller@99
   589
					popuntil("indent", "pre")
tmueller@99
   590
					i = i + 1
tmueller@99
   591
				end
tmueller@219
   592
				self.preindent = nil
tmueller@0
   593
			end
tmueller@219
   594
		elseif self.indentlevel == self.previndent + 1 then
tmueller@0
   595
			popif("block")
tmueller@0
   596
			push("indent")
tmueller@219
   597
		elseif feature["p"] and self.indentlevel >= self.previndent + 2 then
tmueller@219
   598
			if not self.preindent then
tmueller@219
   599
				self.preindent = self.previndent + 2
tmueller@217
   600
				popif("block")
tmueller@99
   601
				push("pre")
tmueller@99
   602
			end
tmueller@0
   603
		end
tmueller@0
   604
tmueller@219
   605
		if not self.preindent then
tmueller@0
   606
tmueller@219
   607
			if feature["d"] then
tmueller@219
   608
				-- def ( SYNOPSIS )
tmueller@219
   609
				line = line:gsub("^(%u[%u%d%s_]+)::$", function(name)
tmueller@219
   610
					popwhile("def", "item", "list", "block", "pre")
tmueller@219
   611
					push("def", name)
tmueller@219
   612
					return ""
tmueller@219
   613
				end)
tmueller@219
   614
			end
tmueller@0
   615
tmueller@219
   616
			if feature["f"] then
tmueller@219
   617
				-- function ( INCLUDE(name) )
tmueller@219
   618
				line = line:gsub("^%s*(INCLUDE_?%a*)%(%s*(.*)%s*%)%s*$",
tmueller@219
   619
					function(key, line)
tmueller@219
   620
					if key == "INCLUDE" or key == "INCLUDE_STATIC" then
tmueller@219
   621
						local args = { }
tmueller@219
   622
						line:gsub(",?([^,]*)", function(a)
tmueller@219
   623
							a = a:match("^%s*(%S.-)%s*$")
tmueller@219
   624
							if a then
tmueller@219
   625
								insert(args, a)
tmueller@219
   626
							end
tmueller@219
   627
						end)
tmueller@219
   628
						popwhilenot("document")
tmueller@219
   629
						push("func", key == "INCLUDE", args)
tmueller@219
   630
						return ""
tmueller@219
   631
					end
tmueller@219
   632
				end)
tmueller@219
   633
				-- argument ( ======== )
tmueller@219
   634
				line = line:gsub("^%s*========+%s*$", function()
tmueller@219
   635
					popwhilenot("func")
tmueller@219
   636
					push("argument")
tmueller@219
   637
					return ""
tmueller@219
   638
				end)
tmueller@219
   639
			end
tmueller@217
   640
tmueller@219
   641
-- 			-- node ( @@ ... : ... @@ )
tmueller@219
   642
--
tmueller@219
   643
-- 			line = line:gsub(
tmueller@219
   644
-- 				"^%s*@@%s+(.+)%s+:%s+(.-)%s+@@%s*$", function(text, id)
tmueller@219
   645
-- 				popwhilenot("document")
tmueller@219
   646
-- 				id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
tmueller@219
   647
-- 				push("node", id, text)
tmueller@219
   648
-- 				return ""
tmueller@219
   649
-- 			end)
tmueller@219
   650
--
tmueller@219
   651
-- 			-- node ( @@ ... @@ )
tmueller@219
   652
--
tmueller@219
   653
-- 			line = line:gsub("^%s*@@%s+(.+)%s+@@%s*$", function(text)
tmueller@219
   654
-- 				popwhilenot("document")
tmueller@219
   655
-- 				local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
tmueller@219
   656
-- 				push("node", id, text)
tmueller@219
   657
-- 				return ""
tmueller@219
   658
-- 			end)
tmueller@0
   659
tmueller@219
   660
			if feature["n"] then
tmueller@219
   661
				-- headnode ( ==( id : text )== )
tmueller@219
   662
				line = line:gsub("^%s*(=+[%(%[])%s+(.+)%s+:%s+(.+)%s+([%)%]]=+)%s*$",
tmueller@219
   663
					function(s1, id, text, s2)
tmueller@219
   664
					local l = min(s1:len() - 1, s2:len() - 1, 5)
tmueller@171
   665
					popwhilenot("document")
tmueller@219
   666
					local id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
tmueller@219
   667
					push("headnode", id, text, l, s2:sub(1, 1) == "]")
tmueller@171
   668
					return ""
tmueller@219
   669
				end)
tmueller@219
   670
				-- headnode ( ==( text )== )
tmueller@219
   671
				line = line:gsub("^%s*(=+[%(%[])%s+(.*)%s+([%)%]]=+)%s*$",
tmueller@219
   672
					function(s1, text, s2)
tmueller@219
   673
					local l = min(s1:len(), s2:len(), 5)
tmueller@219
   674
					popwhilenot("document")
tmueller@219
   675
					local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
tmueller@219
   676
					push("headnode", id, text, l, s2:sub(1, 1) == "]")
tmueller@219
   677
					return ""
tmueller@219
   678
				end)
tmueller@219
   679
			end
tmueller@198
   680
tmueller@219
   681
			if feature["s"] then
tmueller@219
   682
				-- rule ( ----.. )
tmueller@219
   683
				line = line:gsub("^%s*(%-%-%-%-+)%s*$", function(s)
tmueller@219
   684
					popwhile("block", "list", "item")
tmueller@219
   685
					push("rule")
tmueller@219
   686
					pop()
tmueller@219
   687
					return ""
tmueller@219
   688
				end)
tmueller@219
   689
			end
tmueller@0
   690
tmueller@0
   691
		end
tmueller@0
   692
tmueller@0
   693
		--
tmueller@0
   694
tmueller@219
   695
		self.cstack = { }
tmueller@219
   696
		self.context = { parentfirstfragment = true }
tmueller@0
   697
		pushfragment(line)
tmueller@0
   698
		pushcontext()
tmueller@0
   699
tmueller@0
   700
		if line == "" then
tmueller@0
   701
			popwhile("block")
tmueller@0
   702
		end
tmueller@0
   703
tmueller@0
   704
		while popcontext() do
tmueller@0
   705
			while popfragment() do
tmueller@219
   706
				line = self.line
tmueller@0
   707
tmueller@219
   708
				if self.preindent then
tmueller@0
   709
tmueller@219
   710
					if self.prepend then
tmueller@0
   711
						push("preline")
tmueller@0
   712
						doout("getpre", "")
tmueller@0
   713
						pop()
tmueller@219
   714
						self.prepend = nil
tmueller@0
   715
					end
tmueller@0
   716
tmueller@0
   717
					if line == "" then
tmueller@219
   718
						self.prepend = true
tmueller@0
   719
					else
tmueller@0
   720
						push("preline")
tmueller@219
   721
						doout("getpre", (" "):rep(self.indentlevel -
tmueller@219
   722
							self.preindent) .. line)
tmueller@0
   723
						pop()
tmueller@0
   724
					end
tmueller@0
   725
tmueller@0
   726
				else
tmueller@219
   727
					self.prepend = nil
tmueller@0
   728
tmueller@0
   729
					if line ~= "" then
tmueller@0
   730
tmueller@219
   731
						if feature["l"] then
tmueller@219
   732
							-- list/item ( * ... )
tmueller@219
   733
							local _, _, b, a = line:find("^([%*%-])%s+(.*)$")
tmueller@219
   734
							if b and a then
tmueller@219
   735
								local inlist = checktop("item", "list")
tmueller@219
   736
								popwhile("item", "block")
tmueller@219
   737
								if not inlist then
tmueller@219
   738
									push("list")
tmueller@219
   739
								end
tmueller@219
   740
								if b == "*" then
tmueller@219
   741
									push("item", true)
tmueller@219
   742
								else
tmueller@219
   743
									push("item")
tmueller@219
   744
								end
tmueller@219
   745
								pushcontext("item", a)
tmueller@219
   746
								break
tmueller@0
   747
							end
tmueller@0
   748
						end
tmueller@0
   749
tmueller@219
   750
						if feature["t"] then
tmueller@219
   751
							-- table cells
tmueller@219
   752
							local _, pos, a, b =
tmueller@219
   753
								line:find("^%s*(.-)%s*||%s*(.*)%s*$")
tmueller@219
   754
							if pos then
tmueller@219
   755
								if self.context.id == "table" then
tmueller@219
   756
									popuntil("cell")
tmueller@219
   757
								else
tmueller@219
   758
									self.context.id = "table"
tmueller@219
   759
									if not self.in_table then
tmueller@219
   760
										self.in_table = true
tmueller@219
   761
										push("table")
tmueller@219
   762
									end
tmueller@219
   763
									push("row")
tmueller@219
   764
								end
tmueller@219
   765
								pushfragment(b)
tmueller@219
   766
								push("cell")
tmueller@219
   767
								pushcontext("cell", a)
tmueller@219
   768
								break
tmueller@219
   769
							elseif self.context.id == "table" then
tmueller@0
   770
								popuntil("cell")
tmueller@219
   771
								push("cell")
tmueller@219
   772
								pushcontext("cell", line)
tmueller@219
   773
								break
tmueller@0
   774
							end
tmueller@0
   775
						end
tmueller@0
   776
tmueller@219
   777
						if feature["c"] then
tmueller@219
   778
							-- code
tmueller@219
   779
							local _, _, a, text, b =
tmueller@219
   780
								line:find("^(.-){{(.-)}}(.*)%s*$")
tmueller@219
   781
							if text then
tmueller@0
   782
								if a == "" then
tmueller@0
   783
									if not checktop("block", "item", "cell") then
tmueller@0
   784
										push("block")
tmueller@0
   785
									end
tmueller@219
   786
									if self.context.firstfragment then
tmueller@0
   787
										doout("gettext", "")
tmueller@0
   788
									end
tmueller@219
   789
									push("code")
tmueller@0
   790
									if b == "" then b = " " end
tmueller@0
   791
									pushfragment(b)
tmueller@219
   792
									pushcontext("code", text)
tmueller@0
   793
								else
tmueller@219
   794
									pushfragment("{{", text, "}}", b)
tmueller@0
   795
									pushfragment(a)
tmueller@0
   796
									pushcontext()
tmueller@0
   797
								end
tmueller@0
   798
								break
tmueller@0
   799
							end
tmueller@0
   800
						end
tmueller@0
   801
tmueller@219
   802
						if feature["e"] then
tmueller@219
   803
							-- emphasis
tmueller@219
   804
							local _, _, a, x, text, y, b =
tmueller@219
   805
								line:find("^(.-)(''+)(.-)(''+)(.*)$")
tmueller@219
   806
							if text then
tmueller@219
   807
								if a == "" then
tmueller@219
   808
									x, y = x:len(), y:len()
tmueller@219
   809
									local len = min(x, y, 4)
tmueller@219
   810
									if not checktop("block", "item", "cell") then
tmueller@219
   811
										push("block")
tmueller@219
   812
									end
tmueller@219
   813
									if self.context.firstfragment then
tmueller@219
   814
										doout("gettext", "")
tmueller@219
   815
									end
tmueller@219
   816
									push("emphasis", len - 1)
tmueller@219
   817
									if b == "" then b = " " end
tmueller@219
   818
									pushfragment(b)
tmueller@219
   819
									pushcontext("emphasis", text)
tmueller@219
   820
								else
tmueller@219
   821
									pushfragment(x, text, y, b)
tmueller@219
   822
									pushfragment(a)
tmueller@219
   823
									pushcontext()
tmueller@219
   824
								end
tmueller@219
   825
								break
tmueller@219
   826
							end
tmueller@219
   827
						end
tmueller@0
   828
tmueller@219
   829
						if feature["l"] then
tmueller@219
   830
							-- [[link]], [[title][link]], function(), [[link : title]]
tmueller@219
   831
							if self.context.id ~= "link"
tmueller@219
   832
								and self.context.id ~= "code" then
tmueller@219
   833
								local a, title, link, b
tmueller@219
   834
								a, link, title, b = -- [[link : title]]
tmueller@219
   835
									line:match("^(.-)%[%[(.-)%s+:%s+(.-)%]%](.*)%s*$")
tmueller@219
   836
								if not link then
tmueller@219
   837
									a, title, link, b = -- [[text][...]]
tmueller@219
   838
										line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
tmueller@219
   839
								end
tmueller@219
   840
								if not link then -- [[....]]
tmueller@219
   841
									a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
tmueller@219
   842
								end
tmueller@219
   843
								if not link then -- class:function()
tmueller@219
   844
									a, link, b = line:match("^(.-)(%a[%w_:]-%(%))(.*)%s*$")
tmueller@219
   845
								end
tmueller@219
   846
								if not link then -- prot://foo/bar
tmueller@219
   847
									a, link, b = line:match(
tmueller@219
   848
										"^(.-)(%a*://[%w_%-%.,:;/%?=~]*)(.*)%s*$")
tmueller@219
   849
								end
tmueller@219
   850
								if link then
tmueller@219
   851
									if a == "" then
tmueller@219
   852
										if not checktop("block", "item", "cell") then
tmueller@219
   853
											push("block")
tmueller@219
   854
										end
tmueller@219
   855
										if self.context.firstfragment then
tmueller@219
   856
											doout("gettext", "")
tmueller@219
   857
										end
tmueller@219
   858
										push("link", link)
tmueller@219
   859
										if b == "" then b = " " end
tmueller@219
   860
										pushfragment(b)
tmueller@219
   861
										pushcontext("link", title or link)
tmueller@219
   862
									else
tmueller@219
   863
										pushfragment("[[", title or link, "][", link, "]]", b)
tmueller@219
   864
										pushfragment(a)
tmueller@219
   865
										pushcontext()
tmueller@219
   866
									end
tmueller@219
   867
									break
tmueller@219
   868
								end
tmueller@219
   869
							end
tmueller@219
   870
						end
tmueller@0
   871
tmueller@219
   872
						if feature["i"] then
tmueller@219
   873
							-- imglink (@@...@@)
tmueller@219
   874
							line = line:gsub("@@(.-)@@", function(link)
tmueller@219
   875
								push("image", link)
tmueller@219
   876
								pop()
tmueller@219
   877
								return ""
tmueller@219
   878
							end)
tmueller@219
   879
						end
tmueller@0
   880
tmueller@219
   881
						if feature["h"] then
tmueller@219
   882
							-- head ( = ... = )
tmueller@219
   883
							line = line:gsub("(=+)%s+(.*)%s+(=+)",
tmueller@219
   884
								function(s1, text, s2)
tmueller@219
   885
								local l = min(s1:len(), s2:len(), 5)
tmueller@219
   886
								popwhile("block", "item", "list")
tmueller@219
   887
								push("head", l)
tmueller@219
   888
								return text
tmueller@219
   889
							end)
tmueller@219
   890
						end
tmueller@0
   891
tmueller@0
   892
						-- output
tmueller@0
   893
						if line ~= "" then
tmueller@0
   894
							if not checktop("item", "block", "cell", "pre",
tmueller@0
   895
								"head", "emphasis", "link", "code") then
tmueller@0
   896
								popwhile("item", "list", "pre", "code")
tmueller@0
   897
								push("block")
tmueller@0
   898
							end
tmueller@0
   899
							if top() == "code" then
tmueller@0
   900
								doout("getcode", line, top())
tmueller@0
   901
							else
tmueller@0
   902
								doout("gettext", line, top())
tmueller@0
   903
							end
tmueller@0
   904
						end
tmueller@0
   905
						popif("emphasis", "head", "link", "code")
tmueller@0
   906
					end
tmueller@0
   907
				end
tmueller@0
   908
			end
tmueller@0
   909
		end
tmueller@0
   910
tmueller@219
   911
		self.previndent = self.indentlevel
tmueller@219
   912
		self.lnr = self.lnr + 1
tmueller@0
   913
	end
tmueller@0
   914
tmueller@0
   915
	popuntil()
tmueller@0
   916
	doout("exit")
tmueller@219
   917
tmueller@219
   918
	return self.is_dynamic_content
tmueller@0
   919
end
tmueller@0
   920
tmueller@219
   921
-------------------------------------------------------------------------------
tmueller@219
   922
--
tmueller@219
   923
--	parser = Markup:new(args): Creates a new markup parser. {{args}} can be a
tmueller@219
   924
--	table of initial attributes that constitute its behavior. Fields supported
tmueller@219
   925
--	are:
tmueller@219
   926
--
tmueller@219
   927
--	args.indentchar || character recognized for indentation, default {{"\t"}}
tmueller@219
   928
--	args.input      || filehandle or string, default {{io.stdin}}
tmueller@219
   929
--	args.rdfunc     || line reader func, by default reads from {{args.input}}
tmueller@219
   930
--	args.wrfunc     || writer func, by default {{io.stdout.write}}
tmueller@219
   931
--	args.docname    || name of document, default {{"Manual"}}
tmueller@219
   932
--	args.features   || string of feature codes, default {{"hespcadlint"}}
tmueller@219
   933
--
tmueller@219
   934
--	The parser supports the following features, which can be combined into
tmueller@219
   935
--	a string and passed to the constructor in {{args.features}}:
tmueller@219
   936
--
tmueller@219
   937
--	Code || Description        || rough HTML equivalent
tmueller@219
   938
--	h	   || Heading            || <h1>, <h2>, ...
tmueller@219
   939
--	e    || Emphasis           || <strong>
tmueller@219
   940
--	s    || Separator          || <hr>
tmueller@219
   941
--	p    || Preformatted block || <pre>
tmueller@219
   942
--	c    || Code               || <code>
tmueller@219
   943
--	a    || Anchor, link       || <a href="...">
tmueller@219
   944
--	d    || Definition         || <dfn>
tmueller@219
   945
--	l    || List               || <ul>, <li>
tmueller@219
   946
--	i    || Image              || <img>
tmueller@219
   947
--	n    || Node header        || <a name="...">
tmueller@219
   948
--	t    || Table              || <table>
tmueller@219
   949
--	f    || Function           || (undocumented, internal use only)
tmueller@219
   950
--
tmueller@219
   951
--	After creation of the parser object, conversion starts by calling the
tmueller@219
   952
--	method Markup:run().
tmueller@219
   953
--
tmueller@219
   954
-------------------------------------------------------------------------------
tmueller@58
   955
tmueller@219
   956
function Markup.new(class, self)
tmueller@219
   957
	self = self or { }
tmueller@219
   958
	self.indentchar = self.indentchar or "\t"
tmueller@219
   959
	self.input = self.input or stdin
tmueller@219
   960
	self.rdfunc = self.rdfunc or
tmueller@219
   961
		type(self.input) == "string" and rd_string or self.input.lines
tmueller@219
   962
	self.wrfunc = self.wrfunc or function(s) stdout:write(s) end
tmueller@219
   963
	self.features = self.features or "hespcadlint"
tmueller@219
   964
	self.docname = self.docname or "Manual"
tmueller@219
   965
	return Class.new(class, self)
tmueller@0
   966
end