summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/back-exp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/back-exp.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/back-exp.lua2707
1 files changed, 1873 insertions, 834 deletions
diff --git a/Master/texmf-dist/tex/context/base/back-exp.lua b/Master/texmf-dist/tex/context/base/back-exp.lua
index 7f52223a17b..3ce40a6c58b 100644
--- a/Master/texmf-dist/tex/context/base/back-exp.lua
+++ b/Master/texmf-dist/tex/context/base/back-exp.lua
@@ -8,52 +8,37 @@ if not modules then modules = { } end modules ['back-exp'] = {
-- language -> only mainlanguage, local languages should happen through start/stoplanguage
-- tocs/registers -> maybe add a stripper (i.e. just don't flush entries in final tree)
+-- footnotes -> css 3
+-- bodyfont -> in styles.css
+-- delimited -> left/right string (needs marking)
-- Because we need to look ahead we now always build a tree (this was optional in
-- the beginning). The extra overhead in the frontend is neglectable.
-
--- We can consider replacing attributes by the hash entry ... slower in resolving but it's still
--- quite okay.
-
--- todo: less attributes e.g. internal only first node
--- todo: build xml tree in mem (handy for cleaning)
-
--- delimited: left/right string (needs marking)
-
-local nodecodes = nodes.nodecodes
-local traverse_nodes = node.traverse
-local hlist_code = nodecodes.hlist
-local vlist_code = nodecodes.vlist
-
-local function locate(start,wantedid,wantedsubtype)
- for n in traverse_nodes(start) do
- local id = n.id
- if id == wantedid then
- if not wantedsubtype or n.subtype == wantedsubtype then
- return n
- end
- elseif id == hlist_code or id == vlist_code then
- local found = locate(n.list,wantedid,wantedsubtype)
- if found then
- return found
- end
- end
- end
-end
-
-nodes.locate = locate
+--
+-- We can optimize the code ... currently the overhead is some 10% for xml + html so
+-- there is no hurry.
local next, type = next, type
-local format, match, concat, rep, sub, gsub, gmatch = string.format, string.match, table.concat, string.rep, string.sub, string.gsub, string.gmatch
+local format, match, concat, rep, sub, gsub, gmatch, find = string.format, string.match, table.concat, string.rep, string.sub, string.gsub, string.gmatch, string.find
local lpegmatch = lpeg.match
-local utfchar, utfsub = utf.char, utf.sub
+local utfchar, utfbyte, utfsub, utfgsub = utf.char, utf.byte, utf.sub, utf.gsub
local insert, remove = table.insert, table.remove
+local topoints = number.topoints
+local utfvalues = string.utfvalues
+local fromunicode16 = fonts.mappings.fromunicode16
+local sortedhash = table.sortedhash
+
+local trace_export = false trackers.register ("export.trace", function(v) trace_export = v end)
+local trace_spacing = false trackers.register ("export.trace.spacing", function(v) trace_spacing = v end)
+local less_state = false directives.register("export.lessstate", function(v) less_state = v end)
+local show_comment = true directives.register("export.comment", function(v) show_comment = v end)
-local trace_export = false trackers.register ("structures.export", function(v) trace_export = v end)
-local trace_spaces = false trackers.register ("structures.export.spaces", function(v) trace_spaces = v end)
-local trace_tree = false trackers.register ("structures.export.showtree", function(v) trace_tree = v end)
-local less_state = false directives.register("structures.export.lessstate", function(v) less_state = v end)
-local page_breaks = false directives.register("structures.export.pagebreaks", function(v) page_breaks = v end)
+-- maybe we will also support these:
+--
+-- local css_hyphens = false directives.register("export.css.hyphens", function(v) css_hyphens = v end)
+-- local css_textalign = false directives.register("export.css.textalign", function(v) css_textalign = v end)
+-- local css_bodyfontsize = false directives.register("export.css.bodyfontsize", function(v) css_bodyfontsize = v end)
+-- local css_textwidth = false directives.register("export.css.textwidth", function(v) css_textwidth = v end)
local report_export = logs.reporter("backend","export")
@@ -66,6 +51,7 @@ local settings_to_array = utilities.parsers.settings_to_array
local setmetatableindex = table.setmetatableindex
local tasks = nodes.tasks
local fontchar = fonts.hashes.characters
+local fontquads = fonts.hashes.quads
local languagenames = languages.numbers
local nodecodes = nodes.nodecodes
@@ -82,31 +68,34 @@ local disc_code = nodecodes.disc
local insert_code = nodecodes.insert
local whatsit_code = nodecodes.whatsit
local refximage_code = whatsitcodes.pdfrefximage
+local localpar_code = whatsitcodes.localpar
local userskip_code = skipcodes.userskip
local rightskip_code = skipcodes.rightskip
local parfillskip_code = skipcodes.parfillskip
+local spaceskip_code = skipcodes.spaceskip
+local xspaceskip_code = skipcodes.xspaceskip
local line_code = listcodes.line
-local a_tagged = attributes.private('tagged')
-local a_image = attributes.private('image')
+local a_characters = attributes.private('characters')
+local a_exportstatus = attributes.private('exportstatus')
-local a_taggedalign = attributes.private("taggedalign")
-local a_taggedcolumns = attributes.private("taggedcolumns")
-local a_taggedrows = attributes.private("taggedrows")
+local a_tagged = attributes.private('tagged')
local a_taggedpar = attributes.private("taggedpar")
-local a_taggedpacked = attributes.private("taggedpacked")
-local a_taggedsymbol = attributes.private("taggedsymbol")
-local a_taggedinsert = attributes.private("taggedinsert")
-local a_taggedtag = attributes.private("taggedtag")
-
+local a_image = attributes.private('image')
local a_reference = attributes.private('reference')
+local a_textblock = attributes.private("textblock")
+
local has_attribute = node.has_attribute
+local set_attribute = node.set_attribute
+local traverse_id = node.traverse_id
local traverse_nodes = node.traverse
local slide_nodelist = node.slide
local texattribute = tex.attribute
+local texdimen = tex.dimen
+local texcount = tex.count
local unsetvalue = attributes.unsetvalue
local locate_node = nodes.locate
@@ -116,32 +105,69 @@ local taglist = structurestags.taglist
local properties = structurestags.properties
local userdata = structurestags.userdata -- might be combines with taglist
local tagdata = structurestags.data
+local tagmetadata = structurestags.metadata
+local detailedtag = structurestags.detailedtag
local starttiming = statistics.starttiming
local stoptiming = statistics.stoptiming
-- todo: more locals (and optimize)
-local version = "0.20"
-local result = nil -- todo: nofresult
-local entry = nil
-local attributehash = { }
-local hyphen = utfchar(0xAD) -- todo: also emdash etc
-local colonsplitter = lpeg.splitat(":")
-local dashsplitter = lpeg.splitat("-")
-local threshold = 65536
-local indexing = false
-local linedone = false
-local inlinedepth = 0
-local tree = { data = { }, depth = 0 } -- root
-local treestack = { }
-local treehash = { }
-local extras = { }
-local nofbreaks = 0
-local used = { }
-local exporting = false
-local last = nil
-local lastpar = nil
+local exportversion = "0.30"
+
+local nofcurrentcontent = 0 -- so we don't free (less garbage collection)
+local currentcontent = { }
+local currentnesting = nil
+local currentattribute = nil
+local last = nil
+local currentparagraph = nil
+
+local noftextblocks = 0
+
+local attributehash = { } -- to be considered: set the values at the tex end
+local hyphencode = 0xAD
+local hyphen = utfchar(0xAD) -- todo: also emdash etc
+local colonsplitter = lpeg.splitat(":")
+local dashsplitter = lpeg.splitat("-")
+local threshold = 65536
+local indexing = false
+local keephyphens = false
+
+local finetuning = { }
+
+local treestack = { }
+local nesting = { }
+local currentdepth = 0
+
+local tree = { data = { }, fulltag == "root" } -- root
+local treeroot = tree
+local treehash = { }
+local extras = { }
+local checks = { }
+local finalizers = { }
+local nofbreaks = 0
+local used = { }
+local exporting = false
+local restart = false
+local specialspaces = { [0x20] = " " } -- for conversion
+local somespace = { [0x20] = true, [" "] = true } -- for testing
+local entities = { ["&"] = "&amp;", [">"] = "&gt;", ["<"] = "&lt;" }
+local attribentities = { ["&"] = "&amp;", [">"] = "&gt;", ["<"] = "&lt;", ['"'] = "quot;" }
+
+local alignmapping = {
+ flushright = "right",
+ middle = "center",
+ flushleft = "left",
+}
+
+local numbertoallign = {
+ [0] = "justify", ["0"] = "justify", [variables.normal ] = "justify",
+ [1] = "right", ["1"] = "right", [variables.flushright] = "right",
+ [2] = "center", ["2"] = "center", [variables.middle ] = "center",
+ [3] = "left", ["3"] = "left", [variables.flushleft ] = "left",
+}
+
+local defaultnature = "mixed" -- "inline"
setmetatableindex(used, function(t,k)
if k then
@@ -151,16 +177,15 @@ setmetatableindex(used, function(t,k)
end
end)
-local joiner_1 = " "
-local joiner_2 = " " -- todo: test if this one can always be ""
-local joiner_3 = " "
-local joiner_4 = " "
-local joiner_5 = " "
-local joiner_6 = " "
-local joiner_7 = "\n"
-local joiner_8 = " "
-local joiner_9 = " "
-local joiner_0 = " "
+setmetatableindex(specialspaces, function(t,k)
+ local v = utfchar(k)
+ t[k] = v
+ entities[v] = format("&#x%X;",k)
+ somespace[k] = true
+ somespace[v] = true
+ return v
+end)
+
local namespaced = {
-- filled on
@@ -185,15 +210,31 @@ local namespaces = {
merror = "m",
math = "m",
mrow = "m",
+ mtable = "m",
+ mtr = "m",
+ mtd = "m",
+ mfenced = "m",
+ maction = "m",
+ mspace = "m",
}
setmetatableindex(namespaced, function(t,k)
- local namespace = namespaces[k]
- local v = namespace and namespace .. ":" .. k or k
- t[k] = v
- return v
+ if k then
+ local namespace = namespaces[k]
+ local v = namespace and namespace .. ":" .. k or k
+ t[k] = v
+ return v
+ end
end)
+local function attribute(key,value)
+ if value and value ~= "" then
+ return format(' %s="%s"',key,gsub(value,".",attribentities))
+ else
+ return ""
+ end
+end
+
-- local P, C, Cc = lpeg.P, lpeg.C, lpeg.Cc
--
-- local dash, colon = P("-"), P(":")
@@ -211,180 +252,375 @@ local function hashlistdata()
local ci = c[i]
local tag = ci.references.tag
if tag then
- listdata[ci.metadata.kind .. ":" .. ci.metadata.name .. "-" .. tag] = ci
+ local m = ci.metadata
+ listdata[m.kind .. ":" .. m.name .. "-" .. tag] = ci
+ end
+ end
+end
+
+--~ local spaces = { } -- watch how we also moved the -1 in depth-1 to the creator
+
+--~ setmetatableindex(spaces, function(t,k)
+--~ if not k then
+--~ return ""
+--~ end
+--~ local s = rep(" ",k-1)
+--~ t[k] = s
+--~ return s
+--~ end)
+
+local spaces = utilities.strings.newrepeater(" ",-1)
+
+function structurestags.setattributehash(fulltag,key,value) -- public hash
+ if type(fulltag) == "number" then
+ fulltag = taglist[fulltag]
+ if fulltag then
+ fulltag = fulltag[#fulltag]
end
end
+ if fulltag then
+ local ah = attributehash[fulltag] -- could be metatable magic
+ if not ah then
+ ah = { }
+ attributehash[fulltag] = ah
+ end
+ ah[key] = value
+ end
+end
+
+
+-- experiment: styles and images
+--
+-- officially we should convert to bp but we round anyway
+
+local usedstyles = { }
+
+-- /* padding : ; */
+-- /* text-justify : inter-word ; */
+
+local documenttemplate = [[
+document {
+ font-size : %s !important ;
+ max-width : %s !important ;
+ text-align : %s !important ;
+ hyphens : %s !important ;
+}
+]]
+
+local styletemplate = [[
+%s[detail='%s'] {
+ font-style : %s ;
+ font-variant : %s ;
+ font-weight : %s ;
+ font-family : %s ;
+ color : %s ;
+}]]
+
+local function allusedstyles(xmlfile)
+ local result = { format("/* styles for file %s */",xmlfile) }
+ --
+ local bodyfont = finetuning.bodyfont
+ local width = finetuning.width
+ local hyphen = finetuning.hyphen
+ local align = finetuning.align
+ --
+ if not bodyfont or bodyfont == "" then
+ bodyfont = "12pt"
+ elseif type(bodyfont) == "number" then
+ bodyfont = number.todimen(bodyfont,"pt","%ipt") or "12pt"
+ end
+ if not width or width == "" then
+ width = "50em"
+ elseif type(width) == "number" then
+ width = number.todimen(width,"pt","%ipt") or "50em"
+ end
+ if hyphen == variables.yes then
+ hyphen = "manual"
+ else
+ hyphen = "inherited"
+ end
+ if align then
+ align = numbertoallign[align]
+ end
+ if not align then
+ align = hyphens and "justify" or "inherited"
+ end
+ --
+ result[#result+1] = format(documenttemplate,bodyfont,width,align,hyphen)
+ --
+ local colorspecification = xml.css.colorspecification
+ local fontspecification = xml.css.fontspecification
+ for element, details in sortedhash(usedstyles) do
+ for detail, data in sortedhash(details) do
+ local s = fontspecification(data.style)
+ local c = colorspecification(data.color)
+ result[#result+1] = format(styletemplate,element,detail,
+ s.style or "inherit",
+ s.variant or "inherit",
+ s.weight or "inherit",
+ s.family or "inherit",
+ c or "inherit")
+ end
+ end
+ return concat(result,"\n\n")
+end
+
+local usedimages = { }
+
+local imagetemplate = [[
+%s[id="%s"] {
+ display : block ;
+ background-image : url(%s) ;
+ background-size : 100%% auto ;
+ background-repeat : no-repeat ;
+ width : %s ;
+ height : %s ;
+}]]
+
+local function allusedimages(xmlfile)
+ local result = { format("/* images for file %s */",xmlfile) }
+ for element, details in sortedhash(usedimages) do
+ for detail, data in sortedhash(details) do
+ local name = data.name
+ if file.extname(name) == "pdf" then
+ -- temp hack .. we will have a remapper
+ name = file.replacesuffix(name,"svg")
+ end
+ result[#result+1] = format(imagetemplate,element,detail,name,data.width,data.height)
+ end
+ end
+ return concat(result,"\n\n")
end
-local spaces = { } -- watch how we also moved the -1 in depth-1 to the creator
+local function uniqueusedimages()
+ local unique = { }
+ for element, details in next, usedimages do
+ for detail, data in next, details do
+ local name = data.name
+ if file.extname(name) == "pdf" then
+ unique[file.replacesuffix(name,"svg")] = name
+ else
+ unique[name] = name
+ end
+ end
+ end
+ return unique
+end
-setmetatableindex(spaces, function(t,k) t[k] = rep(" ",k-1) return t[k] end)
+--
properties.vspace = { export = "break", nature = "display" }
-properties.pbreak = { export = "pagebreak", nature = "display" }
+----------------- = { export = "pagebreak", nature = "display" }
-local function makebreak(entry)
+local function makebreaklist(list)
nofbreaks = nofbreaks + 1
- local t, tl = { }, entry[1]
- if tl then
- for i=1,#tl do
- t[i] = tl[i]
+ local t = { }
+ if list then
+ for i=1,#list do
+ t[i] = list[i]
end
end
- t[#t+1] = "break-" .. nofbreaks
- return { t, { "" }, 0, 0 }
+ t[#t+1] = "break-" .. nofbreaks -- maybe no number
+ return t
end
-local function makebreaknode(node)
+local breakattributes = {
+ type = "collapse"
+}
+
+local function makebreaknode(attributes) -- maybe no fulltag
nofbreaks = nofbreaks + 1
return {
tg = "break",
fulltag = "break-" .. nofbreaks,
n = nofbreaks,
- depth = node.depth,
element = "break",
nature = "display",
- data = { },
- attribute = { } ,
- parnumber = 0,
+ attributes = attributes or nil,
+ -- data = { }, -- not needed
+ -- attribute = 0, -- not needed
+ -- parnumber = 0,
}
end
local fields = { "title", "subtitle", "author", "keywords" }
-function extras.document(handle,element,detail,n,fulltag,hash)
- handle:write(format(" language=%q",languagenames[tex.count.mainlanguagenumber]))
+local function checkdocument(root)
+ local data = root.data
+ if data then
+ for i=1,#data do
+ local di = data[i]
+ if di.content then
+ -- ok
+ elseif di.tg == "ignore" then
+ di.element = ""
+ checkdocument(di)
+ else
+ -- can't happen
+ end
+ end
+ end
+end
+
+function extras.document(result,element,detail,n,fulltag,di)
+ result[#result+1] = format(" language=%q",languagenames[tex.count.mainlanguagenumber])
if not less_state then
- handle:write(format(" file=%q",tex.jobname))
- handle:write(format(" date=%q",os.date()))
- handle:write(format(" context=%q",environment.version))
- handle:write(format(" version=%q",version))
- handle:write(format(" xmlns:m=%q","http://www.w3.org/1998/Math/MathML"))
+ result[#result+1] = format(" file=%q",tex.jobname)
+ result[#result+1] = format(" date=%q",os.date())
+ result[#result+1] = format(" context=%q",environment.version)
+ result[#result+1] = format(" version=%q",exportversion)
+ result[#result+1] = format(" xmlns:m=%q","http://www.w3.org/1998/Math/MathML")
local identity = interactions.general.getidentity()
for i=1,#fields do
local key = fields[i]
local value = identity[key]
if value and value ~= "" then
- handle:write(format(" %s=%q",key,value))
+ result[#result+1] = format(" %s=%q",key,value)
end
end
end
+ checkdocument(di)
end
-local snames, snumbers = { }, { }
+local itemgroups = { }
-function structurestags.setitemgroup(packed,symbol,di)
- local s = snumbers[symbol]
- if not s then
- s = #snames + 1
- snames[s], snumbers[symbol] = symbol, s
+function structurestags.setitemgroup(current,packed,symbol)
+ itemgroups[detailedtag("itemgroup",current)] = {
+ packed = packed,
+ symbol = symbol,
+ }
+end
+
+function extras.itemgroup(result,element,detail,n,fulltag,di)
+ local hash = itemgroups[fulltag]
+ if hash then
+ local v = hash.packed
+ if v then
+ result[#result+1] = " packed='yes'"
+ end
+ local v = hash.symbol
+ if v then
+ result[#result+1] = attribute("symbol",v)
+ end
end
- texattribute[a_taggedpacked] = packed and 1 or unsetvalue
- texattribute[a_taggedsymbol] = s
end
--- todo: per class
+local synonyms = { }
-local synonymnames, synonymnumbers = { }, { } -- can be one hash
+function structurestags.setsynonym(current,tag)
+ synonyms[detailedtag("synonym",current)] = tag
+end
-function structurestags.setsynonym(class,tag)
- local s = synonymnumbers[tag]
- if not s then
- s = #synonymnames + 1
- synonymnames[s], synonymnumbers[tag] = tag, s
+function extras.synonym(result,element,detail,n,fulltag,di)
+ local tag = synonyms[fulltag]
+ if tag then
+ result[#result+1] = format(" tag='%s'",tag)
end
- texattribute[a_taggedtag] = s
end
-local sortingnames, sortingnumbers = { }, { } -- can be one hash
+local sortings = { }
-function structurestags.setsorting(class,tag)
- local s = sortingnumbers[tag]
- if not s then
- s = #sortingnames + 1
- sortingnames[s], sortingnumbers[tag] = tag, s
+function structurestags.setsorting(current,tag)
+ sortings[detailedtag("sorting",current)] = tag
+end
+
+function extras.sorting(result,element,detail,n,fulltag,di)
+ local tag = sortings[fulltag]
+ if tag then
+ result[#result+1] = format(" tag='%s'",tag)
end
- texattribute[a_taggedtag] = s
end
-local insertids = { }
+usedstyles.highlight = { }
-function structurestags.setdescriptionid(tag,n)
+function structurestags.sethighlight(current,style,color) -- we assume global styles
+ usedstyles.highlight[current] = {
+ style = style, -- xml.css.fontspecification(style),
+ color = color, -- xml.css.colorspec(color),
+ }
+end
+
+local descriptions = { }
+local symbols = { }
+local linked = { }
+
+function structurestags.setdescription(tag,n)
local nd = structures.notes.get(tag,n) -- todo: use listdata instead
if nd then
- local r = nd.references
- texattribute[a_taggedinsert] = r.internal or unsetvalue
- else
- texattribute[a_taggedinsert] = unsetvalue
+ local references = nd.references
+ descriptions[references and references.internal] = detailedtag("description",tag)
end
end
-function extras.descriptiontag(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
- if hash then
- local v = hash.insert
- v = v and insertids[v]
- if v then
- handle:write(" insert='",v,"'")
- end
+function structurestags.setdescriptionsymbol(tag,n)
+ local nd = structures.notes.get(tag,n) -- todo: use listdata instead
+ if nd then
+ local references = nd.references
+ symbols[references and references.internal] = detailedtag("descriptionsymbol",tag)
end
end
-function extras.descriptionsymbol(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
- if hash then
- local v = hash.insert
- v = v and insertids[v]
- if v then
- handle:write(" insert='",v,"'")
+function finalizers.descriptions(tree)
+ local n = 0
+ for id, tag in next, descriptions do
+ local sym = symbols[id]
+ if sym then
+ n = n + 1
+ linked[tag] = n
+ linked[sym] = n
end
end
end
-function extras.synonym(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
- if hash then
- local v = hash.tag
- v = v and synonymnames[v]
- if v then
- handle:write(" tag='",v,"'")
- end
+function extras.description(result,element,detail,n,fulltag,di)
+ local id = linked[fulltag]
+ if id then
+ result[#result+1] = format(" insert='%s'",id) -- maybe just fulltag
end
end
-function extras.sorting(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
- if hash then
- local v = hash.tag
- v = v and sortingnames[v]
- if v then
- handle:write(" tag='",v,"'")
- end
+function extras.descriptionsymbol(result,element,detail,n,fulltag,di)
+ local id = linked[fulltag]
+ if id then
+ result[#result+1] = format(" insert='%s'",id)
end
end
-function extras.image(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
- if hash then
- local v = hash.imageindex
- if v then
- local figure = img.ofindex(v)
- if figure then
- local fullname = figure.filepath
- local name = file.basename(fullname)
- local path = file.dirname(fullname)
- local page = figure.page or 1
- if name ~= "" then
- handle:write(" name='",name,"'")
- end
- if path ~= "" then
- handle:write(" path='",path,"'")
- end
- if page > 1 then
- handle:write(" page='",page,"'")
- end
- end
+usedimages.image = { }
+
+function structurestags.setfigure(name,page,width,height)
+ usedimages.image[detailedtag("image")] = {
+ name = name,
+ page = page,
+ width = number.todimen(width,"cm","%0.3fcm"),
+ height = number.todimen(height,"cm","%0.3fcm"),
+ }
+end
+
+function extras.image(result,element,detail,n,fulltag,di)
+ local data = usedimages.image[fulltag]
+ if data then
+ result[#result+1] = attribute("name",data.name)
+ if tonumber(data.page) > 1 then
+ result[#result+1] = format(" page='%s'",data.page)
end
+ result[#result+1] = format(" id='%s' width='%s' height='%s'",fulltag,data.width,data.height)
+ end
+end
+
+local combinations = { }
+
+function structurestags.setcombination(nx,ny)
+ combinations[detailedtag("combination")] = {
+ nx = nx,
+ ny = ny,
+ }
+end
+
+function extras.combination(result,element,detail,n,fulltag,di)
+ local data = combinations[fulltag]
+ if data then
+ result[#result+1] = format(" nx='%s' ny='%s'",data.nx,data.ny)
end
end
@@ -393,37 +629,37 @@ end
local evaluators = { }
local specials = { }
-evaluators.inner = function(handle,var)
+evaluators.inner = function(result,var)
local inner = var.inner
- if var.inner then
- handle:write(" location='",inner,"'")
+ if inner then
+ result[#result+1] = attribute("location",inner)
end
end
-evaluators.outer = function(handle,var)
+evaluators.outer = function(result,var)
local file, url = references.checkedfileorurl(var.outer,var.outer)
if url then
- handle:write(" url='",url,"'")
+ result[#result+1] = attribute("url",url)
elseif file then
- handle:write(" file='",file,"'")
+ result[#result+1] = attribute("file",file)
end
end
-evaluators["outer with inner"] = function(handle,var)
+evaluators["outer with inner"] = function(result,var)
local file = references.checkedfile(var.f)
if file then
- handle:write(" file='",file,"'")
+ result[#result+1] = attribute("file",file)
end
local inner = var.inner
- if var.inner then
- handle:write(" location='",inner,"'")
+ if inner then
+ result[#result+1] = attribute("location",inner)
end
end
-evaluators.special = function(handle,var)
+evaluators.special = function(result,var)
local handler = specials[var.special]
if handler then
- handler(handle,var)
+ handler(result,var)
end
end
@@ -431,99 +667,449 @@ evaluators["special outer with operation"] = evaluators.special
evaluators["special operation"] = evaluators.special
evaluators["special operation with arguments"] = evaluators.special
-function specials.url(handle,var)
+function specials.url(result,var)
local url = references.checkedurl(var.operation)
if url then
- handle:write(" url='",url,"'")
+ result[#result+1] = attribute("url",url)
end
end
-function specials.file(handle,var)
+function specials.file(result,var)
local file = references.checkedfile(var.operation)
if file then
- handle:write(" file='",file,"'")
+ result[#result+1] = attribute("file",file)
end
end
-function specials.fileorurl(handle,var)
+function specials.fileorurl(result,var)
local file, url = references.checkedfileorurl(var.operation,var.operation)
if url then
- handle:write(" url='",url,"'")
+ result[#result+1] = attribute("url",url)
elseif file then
- handle:write(" file='",file,"'")
+ result[#result+1] = attribute("file",file)
end
end
-function specials.internal(handle,var)
+function specials.internal(result,var)
local internal = references.checkedurl(var.operation)
if internal then
- handle:write(" location='aut:",internal,"'")
+ result[#result+1] = format(" location='aut:%s'",internal)
end
end
-local function adddestination(handle,references) -- todo: specials -> exporters and then concat
+local referencehash = { }
+
+local function adddestination(result,references) -- todo: specials -> exporters and then concat
if references then
local reference = references.reference
if reference and reference ~= "" then
local prefix = references.prefix
if prefix and prefix ~= "" then
- handle:write(" prefix='",prefix,"'")
+ result[#result+1] = format(" prefix='%s'",prefix)
end
- handle:write(" destination='",reference,"'")
+ result[#result+1] = format(" destination='%s'",reference)
for i=1,#references do
local r = references[i]
local e = evaluators[r.kind]
if e then
- e(handle,r)
+ e(result,r)
end
end
end
end
end
-local function addreference(handle,references)
+local function addreference(result,references)
if references then
local reference = references.reference
if reference and reference ~= "" then
local prefix = references.prefix
if prefix and prefix ~= "" then
- handle:write(" prefix='",prefix,"'")
+ result[#result+1] = format(" prefix='%s'",prefix)
end
- handle:write(" reference='",reference,"'")
+ result[#result+1] = format(" reference='%s'",reference)
end
local internal = references.internal
if internal and internal ~= "" then
- handle:write(" location='aut:",internal,"'")
+ result[#result+1] = format(" location='aut:%s'",internal)
end
end
end
-function extras.link(handle,element,detail,n,fulltag,di)
+function extras.link(result,element,detail,n,fulltag,di)
-- for instance in lists a link has nested elements and no own text
- local hash = attributehash[fulltag]
- if hash then
- local references = hash.reference
- if references then
- adddestination(handle,structures.references.get(references))
- end
+ local reference = referencehash[fulltag]
+ if reference then
+ adddestination(result,structures.references.get(reference))
return true
else
local data = di.data
if data then
for i=1,#data do
local di = data[i]
- if di and extras.link(handle,element,detail,n,di.fulltag,di) then
- return true
+ if di then
+ local fulltag = di.fulltag
+ if fulltag and extras.link(result,element,detail,n,fulltag,di) then
+ return true
+ end
end
end
end
end
end
-function extras.section(handle,element,detail,n,fulltag,di)
+-- no settings, as these are obscure ones
+
+local automathrows = true directives.register("backend.export.math.autorows", function(v) automathrows = v end)
+local automathapply = true directives.register("backend.export.math.autoapply", function(v) automathapply = v end)
+local automathnumber = true directives.register("backend.export.math.autonumber", function(v) automathnumber = v end)
+local automathstrip = true directives.register("backend.export.math.autostrip", function(v) automathstrip = v end)
+
+local functions = mathematics.categories.functions
+
+local function collapse(di,i,data,ndata,detail,element)
+ local collapsing = di.data
+ if data then
+ di.element = element
+ di.detail = nil
+ i = i + 1
+ while i <= ndata do
+ local dn = data[i]
+ if dn.detail == detail then
+ collapsing[#collapsing+1] = dn.data[1]
+ dn.skip = "ignore"
+ i = i + 1
+ else
+ break
+ end
+ end
+ end
+ return i
+end
+
+local function collapse_mn(di,i,data,ndata)
+ local collapsing = di.data
+ if data then
+ i = i + 1
+ while i <= ndata do
+ local dn = data[i]
+ local tg = dn.tg
+ if tg == "mn" then
+ collapsing[#collapsing+1] = dn.data[1]
+ dn.skip = "ignore"
+ i = i + 1
+ elseif tg == "mo" then
+ local d = dn.data[1]
+ if d == "." then
+ collapsing[#collapsing+1] = d
+ dn.skip = "ignore"
+ i = i + 1
+ else
+ break
+ end
+ else
+ break
+ end
+ end
+ end
+ return i
+end
+
+-- maybe delay __i__ till we need it
+
+local function checkmath(root) -- we can provide utf.toentities as an option
+ local data = root.data
+ if data then
+ local ndata = #data
+ local roottg = root.tg
+ if roottg == "msubsup" then
+ local nucleus, superscript, subscript
+ for i=1,ndata do
+ local di = data[i]
+ if not di then
+ -- weird
+ elseif di.content then
+ -- text
+ elseif not nucleus then
+ nucleus = i
+ elseif not superscript then
+ superscript = i
+ elseif not subscript then
+ subscript = i
+ else
+ -- error
+ end
+ end
+ if superscript and subscript then
+ local sup, sub = data[superscript], data[subscript]
+ data[superscript], data[subscript] = sub, sup
+ -- sub.__o__, sup.__o__ = subscript, superscript
+ sub.__i__, sup.__i__ = superscript, subscript
+ end
+ elseif roottg == "mfenced" then
+ local new, n = { }, 0
+ local attributes = { }
+ root.attributes = attributes
+ for i=1,ndata do
+ local di = data[i]
+ if not di then
+ -- weird
+ elseif di.content then
+ n = n + 1
+ new[n] = di
+ else
+ local tg = di.tg
+ if tg == "mleft" then
+ attributes.left = tostring(di.data[1].data[1].content)
+ elseif tg == "mmiddle" then
+ attributes.middle = tostring(di.data[1].data[1].content)
+ elseif tg == "mright" then
+ attributes.right = tostring(di.data[1].data[1].content)
+ else
+ n = n + 1
+ di.__i__ = n
+ new[n] = di
+ end
+ end
+ end
+ root.data = new
+ ndata = n
+ end
+ if ndata == 0 then
+ return
+ elseif ndata == 1 then
+ local d = data[1]
+ if not d then
+ return
+ elseif d.content then
+ return
+ elseif #root.data == 1 then
+ local tg = d.tg
+ if automathrows and roottg == "mrow" then
+ -- maybe just always ! check spec first
+ if tg == "mrow" or tg == "mfenced" or tg == "mfrac" or tg == "mroot" or tg == "msqrt"then
+ root.skip = "comment"
+ elseif tg == "mo" then
+ root.skip = "comment"
+ end
+ elseif roottg == "mo" then
+ if tg == "mo" then
+ root.skip = "comment"
+ end
+ end
+ end
+ end
+ local i = 1
+ while i <= ndata do -- -- -- TOO MUCH NESTED CHECKING -- -- --
+ local di = data[i]
+ if di and not di.content then
+ local tg = di.tg
+ local detail = di.detail
+ if tg == "math" then
+ -- di.element = "mrow" -- when properties
+ di.skip = "comment"
+ checkmath(di)
+ i = i + 1
+ elseif tg == "mover" or tg == "munder" or tg == "munderover" then
+ if detail == "accent" then
+ di.attributes = { accent = "true" }
+ di.detail = nil
+ end
+ checkmath(di)
+ i = i + 1
+ elseif tg == "mroot" then
+ if #di.data == 1 then
+ -- else firefox complains
+ di.element = "msqrt"
+ end
+ checkmath(di)
+ i = i + 1
+ elseif tg == "break" then
+ di.skip = "comment"
+ i = i + 1
+ elseif tg == "mrow" and detail then
+ di.detail = nil
+ checkmath(di)
+ di = {
+ element = "maction",
+ nature = "display",
+ attributes = { actiontype = detail },
+ data = { di },
+ n = 0,
+ }
+ data[i] = di
+ i = i + 1
+ elseif detail then
+ -- no checkmath(di) here
+ local category = tonumber(detail) or 0
+ if category == 1 then -- mo
+ i = collapse(di,i,data,ndata,detail,"mo")
+ elseif category == 2 then -- mi
+ i = collapse(di,i,data,ndata,detail,"mi")
+ elseif category == 3 then -- mn
+ i = collapse(di,i,data,ndata,detail,"mn")
+ elseif category == 4 then -- ms
+ i = collapse(di,i,data,ndata,detail,"ms")
+ elseif category >= 1000 then
+ local apply = category >= 2000
+ if apply then
+ category = category - 1000
+ end
+ if tg == "mi" then -- function
+ if roottg == "mrow" then
+ root.skip = "comment"
+ root.element = "function"
+ end
+ i = collapse(di,i,data,ndata,detail,"mi")
+ local tag = functions[category]
+ if tag then
+ di.data = { tag }
+ end
+ if apply then
+ di.after = {
+ {
+ element = "mo",
+ -- comment = "apply function",
+ -- data = { utfchar(0x2061) },
+ data = { "&#x2061;" },
+ nature = "mixed",
+ }
+ }
+ elseif automathapply then -- make function
+ local following
+ if i <= ndata then
+ -- normally not the case
+ following = data[i]
+ else
+ local parent = di.__p__ -- == root
+ if parent.tg == "mrow" then
+ parent = parent.__p__
+ end
+ local index = parent.__i__
+ following = parent.data[index+1]
+ end
+ if following then
+ local tg = following.tg
+ if tg == "mrow" or tg == "mfenced" then -- we need to figure out the right condition
+ di.after = {
+ {
+ element = "mo",
+ -- comment = "apply function",
+ -- data = { utfchar(0x2061) },
+ data = { "&#x2061;" },
+ nature = "mixed",
+ }
+ }
+ end
+ end
+ end
+ else -- some problem
+ checkmath(di)
+ i = i + 1
+ end
+ else
+ checkmath(di)
+ i = i + 1
+ end
+ elseif automathnumber and tg == "mn" then
+ checkmath(di)
+ i = collapse_mn(di,i,data,ndata)
+ else
+ checkmath(di)
+ i = i + 1
+ end
+ else -- can be string or boolean
+ if parenttg ~= "mtext" and di == " " then
+ data[i] = false
+ end
+ i = i + 1
+ end
+ end
+ end
+end
+
+function stripmath(di)
+ if not di then
+ --
+ elseif di.content then
+ return di
+ else
+ local tg = di.tg
+ if tg == "mtext" or tg == "ms" then
+ return di
+ else
+ local data = di.data
+ local ndata = #data
+ local n = 0
+ for i=1,ndata do
+ local di = data[i]
+ if di and not di.content then
+ di = stripmath(di)
+ end
+ if di then
+ local content = di.content
+ if not content then
+ n = n + 1
+ di.__i__ = n
+ data[n] = di
+ elseif content == " " or content == "" then
+ -- skip
+ else
+ n = n + 1
+ data[n] = di
+ end
+ end
+ end
+ for i=ndata,n+1,-1 do
+ data[i] = nil
+ end
+ if #data > 0 then
+ return di
+ end
+ end
+ end
+end
+
+function checks.math(di)
+ local hash = attributehash[di.fulltag]
+ local mode = (hash and hash.mode) == "display" and "block" or "inline"
+ di.attributes = {
+ display = mode
+ }
+ -- can be option if needed:
+ if mode == "inline" then
+ di.nature = "mixed" -- else spacing problem (maybe inline)
+ else
+ di.nature = "display"
+ end
+ if automathstrip then
+ stripmath(di)
+ end
+ checkmath(di)
+end
+
+local a, z, A, Z = 0x61, 0x7A, 0x41, 0x5A
+
+function extras.mi(result,element,detail,n,fulltag,di) -- check with content
+ local str = di.data[1].content
+ if str and sub(str,1,1) ~= "&" then -- hack but good enough (maybe gsub op eerste)
+ for v in utfvalues(str) do
+ if (v >= a and v <= z) or (v >= A and v <= Z) then
+ local a = di.attributes
+ if a then
+ a.mathvariant = "normal"
+ else
+ di.attributes = { mathvariant = "normal" }
+ end
+ end
+ end
+ end
+end
+
+function extras.section(result,element,detail,n,fulltag,di)
local data = listdata[fulltag]
if data then
- addreference(handle,data.references)
+ addreference(result,data.references)
return true
else
local data = di.data
@@ -532,7 +1118,7 @@ function extras.section(handle,element,detail,n,fulltag,di)
local di = data[i]
if di then
local ft = di.fulltag
- if ft and extras.section(handle,element,detail,n,ft,di) then
+ if ft and extras.section(result,element,detail,n,ft,di) then
return true
end
end
@@ -541,17 +1127,17 @@ function extras.section(handle,element,detail,n,fulltag,di)
end
end
-function extras.float(handle,element,detail,n,fulltag,di)
+function extras.float(result,element,detail,n,fulltag,di)
local data = listdata[fulltag]
if data then
- addreference(handle,data.references)
+ addreference(result,data.references)
return true
else
local data = di.data
if data then
for i=1,#data do
local di = data[i]
- if di and extras.section(handle,element,detail,n,di.fulltag,di) then
+ if di and extras.section(result,element,detail,n,di.fulltag,di) then
return true
end
end
@@ -559,164 +1145,492 @@ function extras.float(handle,element,detail,n,fulltag,di)
end
end
-function extras.itemgroup(handle,element,detail,n,fulltag,di)
- local data = di.data
- for i=1,#data do
- local di = data[i]
- if type(di) == "table" and di.tg == "item" then
- local ddata = di.data
- for i=1,#ddata do
- local ddi = ddata[i]
- if type(ddi) == "table" then
- local tg = ddi.tg
- if tg == "itemtag" or tg == "itemcontent" then
- local hash = attributehash[ddi.fulltag]
- if hash then
- local v = hash.packed
- if v and v == 1 then
- handle:write(" packed='yes'")
- end
- local v = hash.symbol
- if v then
- handle:write(" symbol='",snames[v],"'")
- end
- return
- end
- end
- end
- end
- end
+local tabledata = { }
+
+function structurestags.settablecell(rows,columns,align)
+ if align > 0 or rows > 1 or columns > 1 then
+ tabledata[detailedtag("tablecell")] = {
+ rows = rows,
+ columns = columns,
+ align = align,
+ }
end
end
-function extras.tablecell(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
+function extras.tablecell(result,element,detail,n,fulltag,di)
+ local hash = tabledata[fulltag]
if hash then
+ local v = hash.columns
+ if v and v > 1 then
+ result[#result+1] = format(" columns='%s'",v)
+ end
+ local v = hash.rows
+ if v and v > 1 then
+ result[#result+1] = format(" rows='%s'",v)
+ end
local v = hash.align
if not v or v == 0 then
-- normal
- elseif v == 1 then
- handle:write(" align='flushright'")
+ elseif v == 1 then -- use numbertoalign here
+ result[#result+1] = " align='flushright'"
elseif v == 2 then
- handle:write(" align='middle'")
+ result[#result+1] = " align='middle'"
elseif v == 3 then
- handle:write(" align='flushleft'")
+ result[#result+1] = " align='flushleft'"
end
- local v = hash.columns
- if v and v > 1 then
- handle:write(" columns='",v,"'")
- end
- local v = hash.rows
- if v and v > 1 then
- handle:write(" rows='",v,"'")
+ end
+end
+
+local tabulatedata = { }
+
+function structurestags.settabulatecell(align)
+ if align > 0 then
+ tabulatedata[detailedtag("tabulatecell")] = {
+ align = align,
+ }
+ end
+end
+
+function extras.tabulate(result,element,detail,n,fulltag,di)
+ local data = di.data
+ for i=1,#data do
+ local di = data[i]
+ if di.tg == "tabulaterow" then
+ local did = di.data
+ local content = false
+ for i=1,#did do
+ local d = did[i].data
+ if d and #d > 0 and d[1].content then
+ content = true
+ break
+ end
+ end
+ if not content then
+ di.element = "" -- or simply remove
+ end
end
end
end
-function extras.tabulatecell(handle,element,detail,n,fulltag,di)
- local hash = attributehash[fulltag]
+function extras.tabulatecell(result,element,detail,n,fulltag,di)
+ local hash = tabulatedata[fulltag]
if hash then
local v = hash.align
if not v or v == 0 then
-- normal
elseif v == 1 then
- handle:write(" align='flushright'")
+ result[#result+1] = " align='flushleft'"
elseif v == 2 then
- handle:write(" align='middle'")
+ result[#result+1] = " align='flushright'"
elseif v == 3 then
- handle:write(" align='flushleft'")
+ result[#result+1] = " align='middle'"
end
end
end
-local function emptytag(handle,element,nature,depth)
- handle:write("\n",spaces[depth],"<",namespaced[element],"/>\n")
+-- flusher
+
+local linedone = false -- can go ... we strip newlines anyway
+local inlinedepth = 0
+
+-- todo: #result -> nofresult
+
+local function emptytag(result,element,nature,depth,di) -- currently only break but at some point
+ local a = di.attributes -- we might add detail etc
+ if a then -- happens seldom
+ if linedone then
+ result[#result+1] = format("%s<%s",spaces[depth],namespaced[element])
+ else
+ result[#result+1] = format("\n%s<%s",spaces[depth],namespaced[element])
+ end
+ for k, v in next, a do
+ result[#result+1] = format(" %s=%q",k,v)
+ end
+ result[#result+1] = "/>\n"
+ else
+ if linedone then
+ result[#result+1] = format("%s<%s/>\n",spaces[depth],namespaced[element])
+ else
+ result[#result+1] = format("\n%s<%s/>\n",spaces[depth],namespaced[element])
+ end
+ end
+ linedone = false
end
-local function begintag(handle,element,nature,depth,di,empty)
- local detail, n, fulltag = di.detail, di.n, di.fulltag
+local function begintag(result,element,nature,depth,di,skip)
+ -- if needed we can use a local result with xresult
+ local detail = di.detail
+ local n = di.n
+ local fulltag = di.fulltag
+ local comment = di.comment
if nature == "inline" then
linedone = false
inlinedepth = inlinedepth + 1
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->",comment)
+ end
elseif nature == "mixed" then
if inlinedepth > 0 then
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->",comment)
+ end
elseif linedone then
- handle:write(spaces[depth])
+ result[#result+1] = spaces[depth]
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->",comment)
+ end
else
- handle:write("\n",spaces[depth])
+ result[#result+1] = format("\n%s",spaces[depth])
linedone = false
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->\n%s",comment,spaces[depth])
+ end
end
inlinedepth = inlinedepth + 1
else
if inlinedepth > 0 then
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->",comment)
+ end
elseif linedone then
- handle:write(spaces[depth])
+ result[#result+1] = spaces[depth]
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->",comment)
+ end
else
- handle:write("\n",spaces[depth])
+ result[#result+1] = format("\n%s",spaces[depth]) -- can introduced extra line in mixed+mixed (filtered later on)
linedone = false
+ if show_comment and comment then
+ result[#result+1] = format("<!-- %s -->\n%s",comment,spaces[depth])
+ end
end
end
- handle:write("<",namespaced[element])
- if detail then
- handle:write(" detail='",detail,"'")
- end
- if indexing and n then
- handle:write(" n='",n,"'")
- end
- local extra = extras[element]
- if extra then
- extra(handle,element,detail,n,fulltag,di)
- end
- local u = userdata[fulltag]
- if u then
- for k, v in next, u do
- handle:write(format(" %s=%q",k,v))
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = format("<!-- begin %s -->",namespaced[element])
end
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = format("<%s",namespaced[element])
+ if detail then
+ result[#result+1] = format(" detail=%q",detail)
+ end
+ if indexing and n then
+ result[#result+1] = format(" n=%q",n)
+ end
+ local extra = extras[element]
+ if extra then
+ extra(result,element,detail,n,fulltag,di)
+ end
+ local u = userdata[fulltag]
+ if u then
+ for k, v in next, u do
+ result[#result+1] = format(" %s=%q",k,v)
+ end
+ end
+ local a = di.attributes
+ if a then
+ for k, v in next, a do
+ result[#result+1] = format(" %s=%q",k,v)
+ end
+ end
+ result[#result+1] = ">"
end
- if not empty then
- handle:write(">")
- if inlinedepth > 0 then
- elseif nature == "display" then
- handle:write("\n")
+ if inlinedepth > 0 then
+ elseif nature == "display" then
+ result[#result+1] = "\n"
+ linedone = true
+ end
+ used[element][detail or ""] = nature -- for template css
+ local metadata = tagmetadata[fulltag]
+ if metadata then
+ if not linedone then
+ result[#result+1] = "\n"
linedone = true
end
+ result[#result+1] = format("%s<metadata>\n",spaces[depth])
+ for k, v in table.sortedpairs(metadata) do
+ v = utfgsub(v,".",entities)
+ result[#result+1] = format("%s<metavariable name=%q>%s</metavariable>\n",spaces[depth+1],k,v)
+ end
+ result[#result+1] = format("%s</metadata>\n",spaces[depth])
end
- used[element][detail or ""] = nature
end
-local function endtag(handle,element,nature,depth,empty)
+local function endtag(result,element,nature,depth,skip)
if nature == "display" then
if inlinedepth == 0 then
- if empty then
- handle:write("</>\n")
- else
- if not linedone then
- handle:write("\n")
+ if not linedone then
+ result[#result+1] = "\n"
+ end
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = format("%s<!-- end %s -->\n",spaces[depth],namespaced[element])
end
- handle:write(spaces[depth],"</",namespaced[element],">\n")
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = format("%s</%s>\n",spaces[depth],namespaced[element])
end
linedone = true
else
- if empty then
- handle:write("/>")
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = format("<!-- end %s -->",namespaced[element])
+ end
+ elseif skip then
+ -- ignore
else
- handle:write("</",namespaced[element],">")
+ result[#result+1] = format("</%s>",namespaced[element])
end
end
else
inlinedepth = inlinedepth - 1
- if empty then
- handle:write("/>")
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = format("<!-- end %s -->",namespaced[element])
+ end
+ elseif skip then
+ -- ignore
else
- handle:write("</",namespaced[element],">")
+ result[#result+1] = format("</%s>",namespaced[element])
end
linedone = false
end
end
-local function push(fulltag,depth,entry)
- local attribute, parnumber = entry[3], entry[4]
- -- local tg, detail, n = lpegmatch(tagsplitter,fulltag)
+local function flushtree(result,data,nature,depth)
+ depth = depth + 1
+ local nofdata = #data
+ for i=1,nofdata do
+ local di = data[i]
+ if not di then
+ -- whatever
+ elseif di.content then
+ -- already has breaks
+ local content = utfgsub(di.content,".",entities)
+ if i == nofdata and sub(content,-1) == "\n" then -- move check
+ -- can be an end of line in par but can also be the last line
+ if trace_spacing then
+ result[#result+1] = format("<c n='%s'>%s</c>",di.parnumber or 0,sub(content,1,-2))
+ else
+ result[#result+1] = sub(content,1,-2)
+ end
+ result[#result+1] = " "
+ else
+ if trace_spacing then
+ result[#result+1] = format("<c n='%s'>%s</c>",di.parnumber or 0,content)
+ else
+ result[#result+1] = content
+ end
+ end
+ linedone = false
+ elseif not di.collapsed then -- ignore collapsed data (is appended, reconstructed par)
+ local element = di.element
+ if element == "break" then -- or element == "pagebreak"
+ emptytag(result,element,nature,depth,di)
+ elseif element == "" or di.skip == "ignore" then
+ -- skip
+ else
+ if di.before then
+ flushtree(result,di.before,nature,depth)
+ end
+ local natu = di.nature
+ local skip = di.skip
+ if di.breaknode then
+ emptytag(result,"break","display",depth,di)
+ end
+ begintag(result,element,natu,depth,di,skip)
+ flushtree(result,di.data,natu,depth)
+-- if sub(result[#result],-1) == " " and natu ~= "inline" then
+-- result[#result] = sub(result[#result],1,-2)
+-- end
+ endtag(result,element,natu,depth,skip)
+ if di.after then
+ flushtree(result,di.after,nature,depth)
+ end
+ end
+ end
+ end
+end
+
+local function breaktree(tree,parent,parentelement) -- also removes double breaks
+ local data = tree.data
+ if data then
+ local nofdata = #data
+ local prevelement
+ local prevnature
+ local prevparnumber
+ local newdata = { }
+ local nofnewdata = 0
+ for i=1,nofdata do
+ local di = data[i]
+ if not di then
+ -- skip
+ elseif di.content then
+ local parnumber = di.parnumber
+ if prevnature == "inline" and prevparnumber and prevparnumber ~= parnumber then
+ nofnewdata = nofnewdata + 1
+ if trace_spacing then
+ newdata[nofnewdata] = makebreaknode { type = "a", p = prevparnumber, n = parnumber }
+ else
+ newdata[nofnewdata] = makebreaknode()
+ end
+ end
+ prevelement = nil
+ prevnature = "inline"
+ prevparnumber = parnumber
+ nofnewdata = nofnewdata + 1
+ newdata[nofnewdata] = di
+ elseif not di.collapsed then
+ local element = di.element
+ if element == "break" then -- or element == "pagebreak"
+ if prevelement == "break" then
+ di.element = ""
+ end
+ prevelement = element
+ prevnature = "display"
+ elseif element == "" or di.skip == "ignore" then
+ -- skip
+ else
+ local nature = di.nature
+ local parnumber = di.parnumber
+ if prevnature == "inline" and nature == "inline" and prevparnumber and prevparnumber ~= parnumber then
+ nofnewdata = nofnewdata + 1
+ if trace_spacing then
+ newdata[nofnewdata] = makebreaknode { type = "b", p = prevparnumber, n = parnumber }
+ else
+ newdata[nofnewdata] = makebreaknode()
+ end
+ end
+ prevnature = nature
+ prevparnumber = parnumber
+ prevelement = element
+ breaktree(di,tree,element)
+ end
+ nofnewdata = nofnewdata + 1
+ newdata[nofnewdata] = di
+ else
+ local nature = di.nature
+ local parnumber = di.parnumber
+ if prevnature == "inline" and nature == "inline" and prevparnumber and prevparnumber ~= parnumber then
+ nofnewdata = nofnewdata + 1
+ if trace_spacing then
+ newdata[nofnewdata] = makebreaknode { type = "c", p = prevparnumber, n = parnumber }
+ else
+ newdata[nofnewdata] = makebreaknode()
+ end
+ end
+ prevnature = nature
+ prevparnumber = parnumber
+ nofnewdata = nofnewdata + 1
+ newdata[nofnewdata] = di
+ end
+ end
+ tree.data = newdata
+ end
+end
+
+-- also tabulaterow reconstruction .. maybe better as a checker
+-- i.e cell attribute
+
+local function collapsetree()
+ for tag, trees in next, treehash do
+ local d = trees[1].data
+ if d then
+ local nd = #d
+ if nd > 0 then
+ for i=2,#trees do
+ local currenttree = trees[i]
+ local currentdata = currenttree.data
+ local currentpar = currenttree.parnumber
+ local previouspar = trees[i-1].parnumber
+ currenttree.collapsed = true
+ -- is the next ok?
+ if previouspar == 0 or not (di and di.content) then
+ previouspar = nil -- no need anyway so no further testing needed
+ end
+ for j=1,#currentdata do
+ local cd = currentdata[j]
+ if not cd or cd == "" then
+ -- skip
+ elseif cd.content then
+ if not currentpar then
+ -- add space ?
+ elseif not previouspar then
+ -- add space ?
+ elseif currentpar ~= previouspar then
+ nd = nd + 1
+ if trace_spacing then
+ d[nd] = makebreaknode { type = "d", p = previouspar, n = currentpar }
+ else
+ d[nd] = makebreaknode()
+ end
+ end
+ previouspar = currentpar
+ nd = nd + 1
+ d[nd] = cd
+ else
+ nd = nd + 1
+ d[nd] = cd
+ end
+ currentdata[j] = false
+ end
+ end
+ end
+ end
+ end
+end
+
+local function finalizetree(tree)
+ for _, finalizer in next, finalizers do
+ finalizer(tree)
+ end
+end
+
+local function indextree(tree)
+ local data = tree.data
+ if data then
+ local n, new = 0, { }
+ for i=1,#data do
+ local d = data[i]
+ if not d then
+ -- skip
+ elseif d.content then
+ n = n + 1
+ new[n] = d
+ elseif not d.collapsed then
+ n = n + 1
+ d.__i__ = n
+ d.__p__ = tree
+ indextree(d)
+ new[n] = d
+ end
+ end
+ tree.data = new
+ end
+end
+
+local function checktree(tree)
+ local data = tree.data
+ if data then
+ for i=1,#data do
+ local d = data[i]
+ if type(d) == "table" then
+ local check = checks[d.tg]
+ if check then
+ check(d)
+ end
+ checktree(d)
+ end
+ end
+ end
+end
+
+-- collector code
+
+local function push(fulltag,depth)
local tag, n = lpegmatch(dashsplitter,fulltag)
local tg, detail = lpegmatch(colonsplitter,tag)
local element, nature
@@ -724,468 +1638,207 @@ local function push(fulltag,depth,entry)
local pd = properties[tag]
local pt = properties[tg]
element = pd and pd.export or pt and pt.export or tg
- nature = pd and pd.nature or pt and pt.nature or "inline"
+ nature = pd and pd.nature or pt and pt.nature or defaultnature
else
local p = properties[tg]
element = p and p.export or tg
nature = p and p.nature or "inline"
end
+ local treedata = tree.data
local t = {
- -- parent = tree,
tg = tg,
fulltag = fulltag,
detail = detail,
n = tonumber(n), -- more efficient
- depth = depth,
element = element,
nature = nature,
data = { },
- attribute = attribute,
- parnumber = parnumber,
- -- node = entry[5], -- will go
+ attribute = currentattribute,
+ parnumber = currentparagraph,
}
- local treedata = tree.data
treedata[#treedata+1] = t
- insert(treestack,tree)
+ currentdepth = currentdepth + 1
+ nesting[currentdepth] = fulltag
+ treestack[currentdepth] = tree
+ if trace_export then
+ if detail and detail ~= "" then
+ report_export("%s<%s trigger='%s' paragraph='%s' index='%s' detail='%s'>",spaces[currentdepth-1],fulltag,currentattribute or 0,currentparagraph or 0,#treedata,detail)
+ else
+ report_export("%s<%s trigger='%s' paragraph='%s' index='%s'>",spaces[currentdepth-1],fulltag,currentattribute or 0,currentparagraph or 0,#treedata)
+ end
+ end
tree = t
- local h = treehash[fulltag]
- if h then
- h[#h+1] = t
+ if tg == "break" then
+ -- no need for this
else
- treehash[fulltag] = { t }
+ local h = treehash[fulltag]
+ if h then
+ h[#h+1] = t
+ else
+ treehash[fulltag] = { t }
+ end
end
end
local function pop()
- tree = remove(treestack)
+ local top = nesting[currentdepth]
+ tree = treestack[currentdepth]
+ currentdepth = currentdepth - 1
+ if trace_export then
+ if top then
+ report_export("%s</%s>",spaces[currentdepth],top)
+ else
+ report_export("</%s>",top)
+ end
+ end
end
-local function flushresult(entry)
- local current, content = entry[1], entry[2]
- if not content then
- -- skip, normally this cannot happen
- else
- local newdepth, olddepth, content = #current, #treestack, concat(content)
+local function continueexport()
+ if nofcurrentcontent > 0 then
if trace_export then
- report_export("%s => %s : handling: %s",olddepth,newdepth,current[newdepth])
+ report_export("%s<!-- injecting pagebreak space -->",spaces[currentdepth])
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " " -- pagebreak
+ end
+end
+
+local function pushentry(current)
+ if current then
+ if restart then
+ continueexport()
+ restart = false
+ end
+ local newdepth = #current
+ local olddepth = currentdepth
+ if trace_export then
+ report_export("%s<!-- moving from depth %s to %s (%s) -->",spaces[currentdepth],olddepth,newdepth,current[newdepth])
end
if olddepth <= 0 then
for i=1,newdepth do
- if trace_export then
- report_export("[1] push : %s",current[i])
- end
- push(current[i],i,entry)
+ push(current[i],i)
end
- if content then
- tree.data[#tree.data+1] = content
- end
- elseif newdepth < olddepth then
- for i=newdepth,olddepth-1 do
- if trace_export then
- report_export("[2a] pop : %s",current[i])
- end
- pop()
- end
- -- we can have a pagebreak and for instance a new chapter
- -- will mess up the structure then
- for i=newdepth,1,-1 do
- if current[i] ~= treestack[i].fulltag then -- needs checking
- if trace_export then
- report_export("[2b] pop : %s",current[i])
+ else
+ local difference
+ if olddepth < newdepth then
+ for i=1,olddepth do
+ if current[i] ~= nesting[i] then
+ difference = i
+ break
end
- pop()
- else
- break
end
- end
- olddepth = #treestack
- for i=olddepth+1,newdepth do
- if trace_export then
- report_export("[2] push : %s",current[i])
+ else
+ for i=1,newdepth do
+ if current[i] ~= nesting[i] then
+ difference = i
+ break
+ end
end
- push(current[i],i,entry)
- end
- if content then
- tree.data[#tree.data+1] = content
end
- elseif newdepth > olddepth then
- for i=olddepth,1,-1 do
- if current[i] ~= treestack[i].fulltag then
- if trace_export then
- report_export("[3] pop : %s",current[i])
- end
+ if difference then
+ for i=olddepth,difference,-1 do
pop()
- else
- break
end
- end
- olddepth = #treestack
- for i=olddepth+1,newdepth do
- if trace_export then
- report_export("[3] push : %s",current[i])
+ for i=difference,newdepth do
+ push(current[i],i)
end
- push(current[i],i,entry)
- end
- if content then
- tree.data[#tree.data+1] = content
- end
- elseif current[newdepth] == treestack[olddepth] then --move up ?
- -- continuation
- if content then
- tree.data[#tree.data+1] = content
- end
- else
- for i=olddepth,1,-1 do
- if current[i] ~= treestack[i].fulltag then
- if trace_export then
- report_export("[4] pop : %s",current[i])
- end
- pop()
- else
- break
+ elseif newdepth > olddepth then
+ for i=olddepth+1,newdepth do
+ push(current[i],i)
end
- end
- olddepth = #treestack
- for i=olddepth+1,newdepth do
- if trace_export then
- report_export("[4] push : %s",current[i])
+ elseif newdepth < olddepth then
+ for i=olddepth,newdepth,-1 do
+ pop()
end
- push(current[i],i,entry)
- end
- if content then
- tree.data[#tree.data+1] = content
+ elseif trace_export then
+ report_export("%s<!-- staying at depth %s (%s) -->",spaces[currentdepth],newdepth,nesting[newdepth] or "?")
end
end
+ return olddepth, newdepth
end
end
-local function checkinserts(data)
- local nofinserts = 0
- for i=1,#data do
- local di = data[i]
- if type(di) == "table" then -- id ~= false
- if di.element == "descriptionsymbol" then
- local hash = attributehash[di.fulltag]
- if hash then
- local i = hash.insert
- if i then
- nofinserts = nofinserts + 1
- insertids[i] = nofinserts
- end
- else
- -- something is wrong
+local function pushcontent(addbreak)
+ if nofcurrentcontent > 0 then
+ if addbreak then
+ if currentcontent[nofcurrentcontent] == "\n" then
+ if trace_export then
+ report_export("%s<!-- removing newline -->",spaces[currentdepth])
end
- end
- if di.data then
- checkinserts(di.data)
+ nofcurrentcontent = nofcurrentcontent - 1
end
end
- end
-end
-
-local function flushtree(handle,data,nature)
- local nofdata = #data
- for i=1,nofdata do
- local di = data[i]
- if not di then
- -- collapsed
- elseif type(di) == "string" then
-if i == nofdata and sub(di,-1) == joiner_7 then
- if nature == "inline" or nature == "mixed" then
- handle:write(sub(di,1,-2))
- else
- handle:write(sub(di,1,-2)," ")
- end
-else
- handle:write(di)
-end
- linedone = false
- elseif not di.collapsed then
- local element = di.element
- if element == "break" or element == "pagebreak" then
- emptytag(handle,element,nature,di.depth)
- else
- local nature, depth = di.nature, di.depth
- local did = di.data
- local nid = #did
- if nid == 0 or (nid == 1 and did[1] == "") then
- begintag(handle,element,nature,depth,di,true)
- -- no content
- endtag(handle,element,nature,depth,true)
- else
- begintag(handle,element,nature,depth,di)
- flushtree(handle,did,nature)
- endtag(handle,element,nature,depth)
- end
+ local content = concat(currentcontent,"",1,nofcurrentcontent)
+ if content == "" then
+ -- omit; when addbreak we could push, remove spaces, pop
+ elseif somespace[content] and addbreak then
+ -- omit; when addbreak we could push, remove spaces, pop
+ else
+ local olddepth, newdepth
+ local list = taglist[currentattribute]
+ if list then
+ olddepth, newdepth = pushentry(list)
end
- end
- end
-end
-
-local function collapsetree()
- for k, v in next, treehash do
- local d = v[1].data
- local nd = #d
- for i=2,#v do
- local vi = v[i]
- local vd = vi.data
- local done = false
- local lpn = v[i-1].parnumber
- if lpn and lpn == 0 then lpn = nil end
- if type(d[1]) ~= "string" then lpn = nil end -- no need anyway so no further testing needed
-local justdone = false
- for j=1,#vd do
- local vdj = vd[j]
- if type(vdj) == "string" then
---~ print(vdj)
- -- experiment, should be improved
- -- can be simplified ... lpn instead of done
- if done then
- nd = nd + 1
- d[nd] = joiner_1
- else
- done = true
- local pn = vi.parnumber
- if not pn then
- nd = nd + 1
- d[nd] = joiner_2
- lpn = nil
- elseif not lpn then
- nd = nd + 1
- d[nd] = joiner_3
- lpn = pn
- elseif pn and pn ~= lpn then
- nd = nd + 1
- d[nd] = makebreaknode(vi)
- lpn = pn
- else
- -- nd = nd + 1
- -- d[nd] = joiner_4 -- we need to be more clever
- end
- end
- else
---~ nd = nd + 1
---~ d[nd] = joiner_3
- -- lpn = nil
- end
- if vdj ~= "" then
- nd = nd + 1
- d[nd] = vdj -- hm, any?
+ local td = tree.data
+ local nd = #td
+ td[nd+1] = { parnumber = currentparagraph, content = content }
+ if trace_export then
+ report_export("%s<!-- start content with length %s -->",spaces[currentdepth],#content)
+ report_export("%s%s",spaces[currentdepth],(gsub(content,"\n","\\n")))
+ report_export("%s<!-- stop content -->",spaces[currentdepth])
+ end
+ if olddepth then
+ for i=newdepth-1,olddepth,-1 do
+ pop()
end
- vd[j] = false
end
- v[i].collapsed = true
end
+ nofcurrentcontent = 0
end
-end
-
-local function prunetree(tree)
- if not tree.collapsed then
- local data = tree.data
- if data then
- local p, np = { }, 0
- for i=1,#data do
- local d = data[i]
- if type(d) == "table" then
- if not d.collapsed then
- prunetree(d)
- np = np + 1
- p[np] = d
- end
- elseif type(d) == "string" then
- np = np + 1
- p[np] = d
- end
- end
- tree.data = np > 0 and p
+ if addbreak then
+ pushentry(makebreaklist(currentnesting))
+ if trace_export then
+ report_export("%s<!-- break added due to %s -->",spaces[currentdepth],addbreak)
end
end
end
local function finishexport()
- if entry then
- local result = entry[2]
- if result and result[#result] == " " then
- result[#result] = nil -- nicer, remove last space
+ if trace_export then
+ report_export("%s<!-- start finalizing -->",spaces[currentdepth])
+ end
+ if nofcurrentcontent > 0 then
+ if somespace[currentcontent[nofcurrentcontent]] then
+ if trace_export then
+ report_export("%s<!-- removing space -->",spaces[currentdepth])
+ end
+ nofcurrentcontent = nofcurrentcontent - 1
end
- flushresult(entry)
+ pushcontent()
end
- for i=#treestack,1,-1 do
+ for i=currentdepth,1,-1 do
pop()
end
+ currentcontent = { } -- we're nice and do a cleanup
+ if trace_export then
+ report_export("%s<!-- stop finalizing -->",spaces[currentdepth])
+ end
end
-local displaymapping = {
- inline = "inline",
- display = "block",
- mixed = "inline",
-}
+-- whatsit_code localpar_code
-local e_template = [[
-%s {
- display: %s ;
-}]]
-
-local d_template = [[
-%s[detail=%s] {
- display: %s ;
-}]]
-
--- encoding='utf-8'
-
-local xmlpreamble = [[
-<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
-
-<!-- input filename : %- 17s -->
-<!-- processing date : %- 17s -->
-<!-- context version : %- 17s -->
-<!-- exporter version : %- 17s -->
-]]
-
-local csspreamble = [[
-
-<?xml-stylesheet type="text/css" href="%s"?>
-]]
-
-local cssfile, xhtmlfile = nil, nil
-
-directives.register("backend.export.css", function(v) cssfile = v end)
-directives.register("backend.export.xhtml",function(v) xhtmlfile = v end)
-
-local function stopexport(v)
- starttiming(treehash)
- report_export("finalizing")
- finishexport()
- collapsetree()
- if trace_tree then
- prunetree(tree)
- report_export(table.serialize(tree,"root"))
- end
- checkinserts(tree.data)
- hashlistdata()
- if type(v) ~= "string" or v == variables.yes or v == "" then
- v = tex.jobname
- end
- local xmlfile = file.addsuffix(v,"export")
- local handle = io.open(xmlfile,"wb")
- if handle then
- local files = { }
- local specification = {
- name = file.removesuffix(v),
- identifier = os.uuid(),
- files = files,
- }
- report_export("saving xml data in '%s",xmlfile)
- handle:write(format(xmlpreamble,tex.jobname,os.date(),environment.version,version))
- if type(cssfile) == "string" then
- local cssfiles = settings_to_array(cssfile)
- for i=1,#cssfiles do
- local cssfile = cssfiles[i]
- files[#files+1] = cssfile
- if type(cssfile) ~= "string" or cssfile == variables.yes or cssfile == "" or cssfile == xmlfile then
- cssfile = file.replacesuffix(xmlfile,"css")
- else
- cssfile = file.addsuffix(cssfile,"css")
- end
- report_export("adding css reference '%s",cssfile)
- handle:write(format(csspreamble,cssfile))
- end
- end
- flushtree(handle,tree.data)
- handle:close()
- -- css template file
- local cssfile = file.replacesuffix(xmlfile,"template")
- report_export("saving css template in '%s",cssfile)
- local templates = { format("/* template for file %s */",xmlfile) }
- for element, details in table.sortedhash(used) do
- templates[#templates+1] = format("/* category: %s */",element)
- for detail, nature in table.sortedhash(details) do
- local d = displaymapping[nature or "display"] or "block"
- if detail == "" then
- templates[#templates+1] = format(e_template,element,d)
- else
- templates[#templates+1] = format(d_template,element,detail,d)
- end
- end
- end
- io.savedata(cssfile,concat(templates,"\n\n"))
- -- xhtml references
- if xhtmlfile then
- -- messy
- if type(v) ~= "string" or xhtmlfile == true or xhtmlfile == variables.yes or xhtmlfile == "" or xhtmlfile == xmlfile then
- xhtmlfile = file.replacesuffix(xmlfile,"xhtml")
- else
- xhtmlfile = file.addsuffix(xhtmlfile,"xhtml")
- end
- report_export("saving xhtml variant in '%s",xhtmlfile)
- local xmltree = xml.load(xmlfile)
- if xmltree then
- local xmlwrap = xml.wrap
- for e in xml.collected(xmltree,"/document") do
- e.at["xmlns:xhtml"] = "http://www.w3.org/1999/xhtml"
- break
- end
- local wrapper = { tg = "a", ns = "xhtml", at = { href = "unknown" } }
- for e in xml.collected(xmltree,"link") do
- local location = e.at.location
- if location then
- wrapper.at.href = "#" .. gsub(location,":","_")
- xmlwrap(e,wrapper)
- end
- end
- local wrapper = { tg = "a", ns = "xhtml", at = { name = "unknown" } }
- for e in xml.collected(xmltree,"!link[@location]") do
- local location = e.at.location
- if location then
- wrapper.at.name = gsub(location,":","_")
- xmlwrap(e,wrapper)
- end
- end
- xml.save(xmltree,xhtmlfile)
- end
- files[#files+1] = xhtmlfile
- specification.root = xhtmlfile
- local specfile = file.replacesuffix(xmlfile,"specification")
- report_export("saving specification in '%s' (mtxrun --script epub --make %s)",specfile,specfile)
- io.savedata(specfile,table.serialize(specification,true))
- end
+local function tracedchar(c)
+ if c == 0x20 then
+ return "[space]"
+ elseif c == 0 then
+ return "[signal]"
else
- report_export("unable to saving xml in '%s",xmlfile)
- end
- stoptiming(treehash)
-end
-
-local function startexport(v)
- if v and not exporting then
- nodes.tasks.appendaction("shipouts", "normalizers", "nodes.handlers.export")
- report_export("enabling export to xml")
- luatex.registerstopactions(function() stopexport(v) end)
- if trace_spaces then
- joiner_1 = "<S1/>" joiner_2 = "<S2/>" joiner_3 = "<S3/>" joiner_4 = "<S4/>" joiner_5 = "<S5/>"
- joiner_6 = "<S6/>" joiner_7 = "<S7/>" joiner_8 = "<S8/>" joiner_9 = "<S9/>" joiner_0 = "<S0/>"
- end
- exporting = true
+ return utfchar(c)
end
end
-directives.register("backend.export",startexport) -- maybe .name
-
-local function injectbreak()
- flushresult(entry)
- flushresult(makebreak(entry))
- result = { }
- entry = { entry[1], result, last, lastpar } -- entry[1] ?
-end
-
-local function injectspace(a,joiner)
- flushresult(entry)
- result = { joiner }
- local tl = taglist[a]
- entry = { tl , result, a, lastpar, n }
-end
-
-local function collectresults(head,list,p)
- local preceding = p or false
+local function collectresults(head,list) -- is last used (we also have currentattribute)
+ local p
for n in traverse_nodes(head) do
local id = n.id -- 14: image, 8: literal (mp)
if id == glyph_code then
@@ -1198,52 +1851,65 @@ local function collectresults(head,list,p)
-- we could add tonunicodes for ligatures (todo)
local components = n.components
if components then -- we loose data
- collectresults(components,nil,preceding)
---~ preceding = true
+ collectresults(components,nil)
else
+ local c = n.char
if last ~= at then
local tl = taglist[at]
- if entry then
- flushresult(entry)
+-- if trace_export then
+-- report_export("%s<!-- processing glyph %s (tag %s) -->",spaces[currentdepth],utfchar(c),at)
+-- end
+ pushcontent()
+ currentnesting = tl
+ currentparagraph = has_attribute(n,a_taggedpar)
+ currentattribute = at
+ last = at
+ pushentry(currentnesting)
+ if trace_export then
+ report_export("%s<!-- processing glyph %s (tag %s) -->",spaces[currentdepth],tracedchar(c),at)
+ end
+ -- We need to intercept this here; maybe I will also move this
+ -- to a regular setter at the tex end.
+ local r = has_attribute(n,a_reference)
+ if r then
+ referencehash[tl[#tl]] = r -- fulltag
end
- if preceding then
- preceding = false
- result = { joiner_5 }
- else
- result = { }
+ --
+ elseif last then
+ local ap = has_attribute(n,a_taggedpar)
+ if ap ~= currentparagraph then
+ pushcontent(format("new paragraph (%s -> %s)",tostring(currentparagraph),tostring(ap)))
+ pushentry(currentnesting)
+ currentattribute = last
+ currentparagraph = ap
end
- lastpar = has_attribute(n,a_taggedpar)
- entry = { tl, result, at, lastpar, n }
- local ah = { -- this includes detail !
- align = has_attribute(n,a_taggedalign ),
- columns = has_attribute(n,a_taggedcolumns),
- rows = has_attribute(n,a_taggedrows ),
- packed = has_attribute(n,a_taggedpacked ),
- symbol = has_attribute(n,a_taggedsymbol ),
- insert = has_attribute(n,a_taggedinsert ),
- reference = has_attribute(n,a_reference ),
- tag = has_attribute(n,a_taggedtag ), -- used for synonyms
- }
- if next(ah) then
- attributehash[tl[#tl]] = ah
+ if trace_export then
+ report_export("%s<!-- processing glyph %s (tag %s) -->",spaces[currentdepth],tracedchar(c),last)
end
- last = at
- elseif last then
- local at = has_attribute(n,a_taggedpar)
- if at ~= lastpar then
- injectbreak()
- lastpar = at
+ else
+ if trace_export then
+ report_export("%s<!-- processing glyph %s (tag %s) -->",spaces[currentdepth],tracedchar(c),at)
end
end
- local c = n.char
- if c == 0x26 then
- result[#result+1] = "&amp;"
- elseif c == 0x3E then
- result[#result+1] = "&gt;"
- elseif c == 0x3C then
- result[#result+1] = "&lt;"
- elseif c == 0 then
- result[#result+1] = "" -- utfchar(0) -- todo: check if "" is needed
+ local s = has_attribute(n,a_exportstatus)
+ if s then
+ c = s
+ end
+ if c == 0 then
+ if trace_export then
+ report_export("%s<!-- skipping last glyph -->",spaces[currentdepth])
+ end
+ elseif c == 0x20 then
+ local a = has_attribute(n,a_characters)
+ nofcurrentcontent = nofcurrentcontent + 1
+ if a then
+ if trace_export then
+ report_export("%s<!-- turning last space into special space U+%05X -->",spaces[currentdepth],a)
+ end
+ currentcontent[nofcurrentcontent] = specialspaces[a] -- special space
+ else
+ currentcontent[nofcurrentcontent] = " "
+ end
else
local fc = fontchar[n.font]
if fc then
@@ -1251,17 +1917,19 @@ local function collectresults(head,list,p)
if fc then
local u = fc.tounicode
if u and u ~= "" then
- for s in gmatch(u,"....") do -- is this ok?
- result[#result+1] = utfchar(tonumber(s,16))
- end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(fromunicode16(u))
else
- result[#result+1] = utfchar(c)
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
end
else -- weird, happens in hz (we really need to get rid of the pseudo fonts)
- result[#result+1] = utfchar(c)
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
end
else
- result[#result+1] = utfchar(c)
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
end
end
end
@@ -1270,112 +1938,483 @@ local function collectresults(head,list,p)
local ai = has_attribute(n,a_image)
if ai then
local at = has_attribute(n,a_tagged)
- if entry then
- flushresult(entry)
- result = { }
- entry[2] = result -- mess, to be sorted out, but otherwise duplicates (still some spacing issues)
+ if nofcurrentcontent > 0 then
+ pushcontent()
+ pushentry(currentnesting) -- ??
end
- local tl = taglist[at]
- local i = locate_node(n,whatsit_code,refximage_code)
- if i then
- attributehash[tl[#tl]] = { imageindex = i.index }
+ pushentry(taglist[at]) -- has an index, todo: flag empty element
+ if trace_export then
+ report_export("%s<!-- processing image (tag %s)",spaces[currentdepth],last)
end
- flushresult { tl, { }, 0, 0 } -- has an index, todo: flag empty element
last = nil
- lastpar = nil
+ currentparagraph = nil
else
---~ if result and #result > 0 then -- and n.subtype == line_code then
---~ local r = result[#result]
---~ if type(r) == "string" and r ~= " " then
---~ local s = utfsub(r,-1)
---~ if s == hyphen then
---~ result[#result] = utfsub(r,1,-2)
---~ elseif s ~= joiner_7 then
---~ result[#result] = r .. joiner_7
---~ --~ preceding = true
---~ end
---~ end
---~ preceding = false
---~ end
-- we need to determine an end-of-line
- preceding = collectresults(n.list,n,preceding)
- preceding = false
+ collectresults(n.list,n)
end
elseif id == disc_code then -- probably too late
+ if keephyphens then
+ local pre = n.pre
+ if pre and not pre.next and pre.id == glyph_code and pre.char == hyphencode then
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = hyphen
+ end
+ end
collectresults(n.replace,nil)
- preceding = false
elseif id == glue_code then
-- we need to distinguish between hskips and vskips
- local subtype = n.subtype
- if subtype == userskip_code then -- todo space_code
- if n.spec.width > threshold then
---~ preceding = true
- if result and last and #result > 0 and result[#result] ~= " " then
+ local ca = has_attribute(n,a_characters)
+ if ca == 0 then
+ -- skip this one ... already converted special character (node-acc)
+ elseif ca then
+ local a = has_attribute(n,a_tagged)
+ if a then
+ local c = specialspaces[ca]
+ if last ~= a then
+ local tl = taglist[a]
+ if trace_export then
+ report_export("%s<!-- processing space glyph U+%05X (tag %s) case 1 -->",spaces[currentdepth],ca,a)
+ end
+ pushcontent()
+ currentnesting = tl
+ currentparagraph = has_attribute(n,a_taggedpar)
+ currentattribute = a
+ last = a
+ pushentry(currentnesting)
+ -- no reference check (see above)
+ elseif last then
+ local ap = has_attribute(n,a_taggedpar)
+ if ap ~= currentparagraph then
+ pushcontent(format("new paragraph (%s -> %s)",tostring(currentparagraph),tostring(ap)))
+ pushentry(currentnesting)
+ currentattribute = last
+ currentparagraph = ap
+ end
+ if trace_export then
+ report_export("%s<!-- processing space glyph U+%05X (tag %s) case 2 -->",spaces[currentdepth],ca,last)
+ end
+ end
+ -- if somespace[currentcontent[nofcurrentcontent]] then
+ -- if trace_export then
+ -- report_export("%s<!-- removing space -->",spaces[currentdepth])
+ -- end
+ -- nofcurrentcontent = nofcurrentcontent - 1
+ -- end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = c
+ end
+ else
+ local subtype = n.subtype
+ if subtype == userskip_code then
+ if n.spec.width > threshold then
+ if last and not somespace[currentcontent[nofcurrentcontent]] then
+ local a = has_attribute(n,a_tagged)
+ if a == last then
+ if trace_export then
+ report_export("%s<!-- injecting spacing 5a -->",spaces[currentdepth])
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ elseif a then
+ -- e.g LOGO<space>LOGO
+ if trace_export then
+ report_export("%s<!-- processing glue > threshold (tag %s => %s) -->",spaces[currentdepth],last,a)
+ end
+ pushcontent()
+ if trace_export then
+ report_export("%s<!-- injecting spacing 5b -->",spaces[currentdepth])
+ end
+ last = a
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ currentnesting = taglist[last]
+ pushentry(currentnesting)
+ currentattribute = last
+ end
+ end
+ end
+ elseif subtype == spaceskip_code or subtype == xspaceskip_code then
+ if not somespace[currentcontent[nofcurrentcontent]] then
+ local a = has_attribute(n,a_tagged)
+ if a == last then
+ if trace_export then
+ report_export("%s<!-- injecting spacing 7 (stay in element) -->",spaces[currentdepth])
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ else
+ if trace_export then
+ report_export("%s<!-- injecting spacing 7 (end of element) -->",spaces[currentdepth])
+ end
+ last = a
+ pushcontent()
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ currentnesting = taglist[last]
+ pushentry(currentnesting)
+ currentattribute = last
+ end
+ end
+ elseif subtype == rightskip_code then
+ -- a line
+ if nofcurrentcontent > 0 then
+ local r = currentcontent[nofcurrentcontent]
+ if r == hyphen then
+ if not keephyphens then
+ nofcurrentcontent = nofcurrentcontent - 1
+ end
+ elseif not somespace[r] then
+ local a = has_attribute(n,a_tagged)
+ if a == last then
+ if trace_export then
+ report_export("%s<!-- injecting spacing 1 (end of line, stay in element) -->",spaces[currentdepth])
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ else
+ if trace_export then
+ report_export("%s<!-- injecting spacing 1 (end of line, end of element) -->",spaces[currentdepth])
+ end
+ last = a
+ pushcontent()
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ currentnesting = taglist[last]
+ pushentry(currentnesting)
+ currentattribute = last
+ end
+ end
+ end
+ elseif subtype == parfillskip_code then
+ -- deal with paragaph endings (crossings) elsewhere and we quit here
+ -- as we don't want the rightskip space addition
+ return
+ end
+ end
+ elseif id == kern_code then
+ local kern = n.kern
+ if kern > 0 then
+ local limit = threshold
+ if p and p.id == glyph_code then
+ limit = fontquads[p.font] / 4
+ end
+ if kern > limit then
+ if last and not somespace[currentcontent[nofcurrentcontent]] then
local a = has_attribute(n,a_tagged)
if a == last then
- result[#result+1] = joiner_6
- preceding = false
+ if not somespace[currentcontent[nofcurrentcontent]] then
+ if trace_export then
+ report_export("%s<!-- injecting spacing 8 (%s) -->",spaces[currentdepth],topoints(kern,true))
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ end
elseif a then
-- e.g LOGO<space>LOGO
- preceding = false
+ if trace_export then
+ report_export("%s<!-- processing kern, threshold %s, tag %s => %s -->",spaces[currentdepth],topoints(limit,true),last,a)
+ end
last = a
- injectspace(last,joiner_6)
+ pushcontent()
+ if trace_export then
+ report_export("%s<!-- injecting spacing 9 (%s) -->",spaces[currentdepth],topoints(kern,true))
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ currentnesting = taglist[last]
+ pushentry(currentnesting)
+ currentattribute = last
end
end
end
- elseif subtype == rightskip_code or subtype == parfillskip_code then
-if result and #result > 0 then -- and n.subtype == line_code then
- local r = result[#result]
- if type(r) == "string" and r ~= " " then
- local s = utfsub(r,-1)
- if s == hyphen then
- result[#result] = utfsub(r,1,-2)
- elseif s ~= joiner_7 then
- result[#result] = r .. joiner_7
---~ preceding = true
+ end
end
+ p = n
end
- preceding = false
end
- end
- elseif id == kern_code then
- if n.kern > threshold then
---~ preceding = true
- if result and last and #result > 0 and result[#result] ~= " " then
- local a = has_attribute(n,a_tagged)
- if a == last then
- result[#result+1] = joiner_8
- preceding = false
- elseif a then
- -- e.g LOGO<space>LOGO
- preceding = false
- last = a
- injectspace(last,joiner_8)
- end
- end
+
+function nodes.handlers.export(head) -- hooks into the page builder
+ starttiming(treehash)
+ if trace_export then
+ report_export("%s<!-- start flushing page -->",spaces[currentdepth])
+ end
+ -- continueexport()
+ restart = true
+ collectresults(head)
+ if trace_export then
+ report_export("%s<!-- stop flushing page -->",spaces[currentdepth])
+ end
+ stoptiming(treehash)
+ return head, true
+end
+
+function builders.paragraphs.tag(head)
+ noftextblocks = noftextblocks + 1
+ for n in traverse_id(hlist_code,head) do
+ local subtype = n.subtype
+ if subtype == line_code then
+ set_attribute(n,a_textblock,noftextblocks)
+ elseif subtype == glue_code or subtype == kern_code then
+ set_attribute(n,a_textblock,0)
+ end
+ end
+ return false
+end
+
+-- encoding='utf-8'
+
+local xmlpreamble = [[
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+
+<!-- input filename : %- 17s -->
+<!-- processing date : %- 17s -->
+<!-- context version : %- 17s -->
+<!-- exporter version : %- 17s -->
+
+]]
+
+local function wholepreamble()
+ return format(xmlpreamble,tex.jobname,os.date(),environment.version,exportversion)
+end
+
+
+local csspreamble = [[
+<?xml-stylesheet type="text/css" href="%s"?>
+]]
+
+local function allusedstylesheets(xmlfile,cssfiles,files)
+ local result = { }
+ for i=1,#cssfiles do
+ local cssfile = cssfiles[i]
+ if type(cssfile) ~= "string" or cssfile == variables.yes or cssfile == "" or cssfile == xmlfile then
+ cssfile = file.replacesuffix(xmlfile,"css")
+ else
+ cssfile = file.addsuffix(cssfile,"css")
+ end
+ files[#files+1] = cssfile
+ report_export("adding css reference '%s",cssfile)
+ result[#result+1] = format(csspreamble,cssfile)
+ end
+ return concat(result)
+end
+
+local e_template = [[
+%s {
+ display: %s ;
+}]]
+
+local d_template = [[
+%s[detail=%s] {
+ display: %s ;
+}]]
+
+local displaymapping = {
+ inline = "inline",
+ display = "block",
+ mixed = "inline",
+}
+
+local function allusedelements(xmlfile)
+ local result = { format("/* template for file %s */",xmlfile) }
+ for element, details in table.sortedhash(used) do
+ result[#result+1] = format("/* category: %s */",element)
+ for detail, nature in table.sortedhash(details) do
+ local d = displaymapping[nature or "display"] or "block"
+ if detail == "" then
+ result[#result+1] = format(e_template,element,d)
+ else
+ result[#result+1] = format(d_template,element,detail,d)
end
end
end
- return preceding
+ return concat(result,"\n\n")
+end
+
+local function allcontent(tree)
+ local result = { }
+ flushtree(result,tree.data,"display",0) -- we need to collect images
+ result = concat(result)
+ result = gsub(result,"\n *\n","\n")
+ result = gsub(result,"\n +([^< ])","\n%1")
+ return result
end
-function nodes.handlers.export(head)
- if result then
- -- maybe we need a better test for what is in result so far
- if page_breaks then
- joiner_0 = "<pagebreak/>"
+-- local xhtmlpreamble = [[
+-- <!DOCTYPE html PUBLIC
+-- "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
+-- "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd"
+-- >
+-- ]]
+
+local function cleanxhtmltree(xmltree)
+ if xmltree then
+ local xmlwrap = xml.wrap
+ for e in xml.collected(xmltree,"/document") do
+ e.at["xmlns:xhtml"] = "http://www.w3.org/1999/xhtml"
+ break
end
- result[#result+1] = joiner_0
+ -- todo: inject xhtmlpreamble (xmlns should have be enough)
+ local wrapper = { tg = "a", ns = "xhtml", at = { href = "unknown" } }
+ for e in xml.collected(xmltree,"link") do
+ local at = e.at
+ local href
+ if at.location then
+ href = "#" .. gsub(at.location,":","_")
+ elseif at.url then
+ href = at.url
+ elseif at.file then
+ href = at.file
+ end
+ if href then
+ wrapper.at.href = href
+ xmlwrap(e,wrapper)
+ end
+ end
+ local wrapper = { tg = "a", ns = "xhtml", at = { name = "unknown" } }
+ for e in xml.collected(xmltree,"!link[@location]") do
+ local location = e.at.location
+ if location then
+ wrapper.at.name = gsub(location,":","_")
+ xmlwrap(e,wrapper)
+ end
+ end
+ return xmltree
+ else
+ return xml.convert("<?xml version='1.0'?>\n<error>invalid xhtml tree</error>")
end
+end
+
+local cssfile, xhtmlfile = nil, nil
+
+directives.register("backend.export.css", function(v) cssfile = v end)
+directives.register("backend.export.xhtml",function(v) xhtmlfile = v end)
+
+local function stopexport(v)
starttiming(treehash)
- collectresults(head)
- -- no flush here, pending page stuff
+ --
+ finishexport()
+ --
+ collapsetree(tree)
+ indextree(tree)
+ checktree(tree)
+ breaktree(tree)
+ finalizetree(tree)
+ --
+ hashlistdata()
+ --
+ if type(v) ~= "string" or v == variables.yes or v == "" then
+ v = tex.jobname
+ end
+ local basename = file.basename(v)
+ local xmlfile = file.addsuffix(basename,"export")
+ --
+ local imagefilename = file.addsuffix(file.removesuffix(xmlfile) .. "-images","css")
+ local stylefilename = file.addsuffix(file.removesuffix(xmlfile) .. "-styles","css")
+ local templatefilename = file.replacesuffix(xmlfile,"template")
+ local specificationfilename = file.replacesuffix(xmlfile,"specification")
+ --
+ if xhtml and not cssfile then
+ cssfile = true
+ end
+ local cssfiles = { }
+ if cssfile then
+ if cssfile == true then
+ cssfiles = { "export-example.css" }
+ else
+ cssfiles = settings_to_array(cssfile or "")
+ end
+ insert(cssfiles,1,imagefilename)
+ insert(cssfiles,1,stylefilename)
+ end
+ cssfiles = table.unique(cssfiles)
+ --
+ local result = allcontent(tree) -- also does some housekeeping and data collecting
+ --
+ local files = {
+ }
+ local results = concat {
+ wholepreamble(),
+ allusedstylesheets(xmlfile,cssfiles,files), -- ads to files
+ result,
+ }
+ --
+ files = table.unique(files)
+ --
+ report_export("saving xml data in '%s",xmlfile)
+ io.savedata(xmlfile,results)
+ --
+ report_export("saving css image definitions in '%s",imagefilename)
+ io.savedata(imagefilename,allusedimages(xmlfile))
+ --
+ report_export("saving css style definitions in '%s",stylefilename)
+ io.savedata(stylefilename,allusedstyles(xmlfile))
+ --
+ report_export("saving css template in '%s",templatefilename)
+ io.savedata(templatefilename,allusedelements(xmlfile))
+ --
+ if xhtmlfile then
+ if type(v) ~= "string" or xhtmlfile == true or xhtmlfile == variables.yes or xhtmlfile == "" or xhtmlfile == xmlfile then
+ xhtmlfile = file.replacesuffix(xmlfile,"xhtml")
+ else
+ xhtmlfile = file.addsuffix(xhtmlfile,"xhtml")
+ end
+ files[#files+1] = xhtmlfile
+ report_export("saving xhtml variant in '%s",xhtmlfile)
+ local xmltree = cleanxhtmltree(xml.convert(results))
+ xml.save(xmltree,xhtmlfile)
+ local specification = {
+ name = file.removesuffix(v),
+ identifier = os.uuid(),
+ images = uniqueusedimages(),
+ root = xhtmlfile,
+ files = files,
+ }
+ report_export("saving specification in '%s' (mtxrun --script epub --make %s)",specificationfilename,specificationfilename)
+ io.savedata(specificationfilename,table.serialize(specification,true))
+ end
stoptiming(treehash)
- return head, true
end
+local appendaction = nodes.tasks.appendaction
+local enableaction = nodes.tasks.enableaction
+
+function commands.setupexport(t)
+ table.merge(finetuning,t)
+ keephyphens = finetuning.hyphen == variables.yes
+end
+
+local function startexport(v)
+ if v and not exporting then
+ report_export("enabling export to xml")
+-- not yet known in task-ini
+ appendaction("shipouts","normalizers", "nodes.handlers.export")
+-- enableaction("shipouts","nodes.handlers.export")
+ enableaction("shipouts","nodes.handlers.accessibility")
+ enableaction("math", "noads.handlers.tags")
+--~ appendaction("finalizers","lists","builders.paragraphs.tag")
+--~ enableaction("finalizers","builders.paragraphs.tag")
+ luatex.registerstopactions(function() stopexport(v) end)
+ exporting = true
+ end
+end
+
+directives.register("backend.export",startexport) -- maybe .name
+
statistics.register("xml exporting time", function()
if exporting then
- return format("%s seconds", statistics.elapsedtime(treehash))
+ return format("%s seconds, version %s", statistics.elapsedtime(treehash),exportversion)
end
end)
+
+-- These are called at the tex end:
+
+commands.settagitemgroup = structurestags.setitemgroup
+commands.settagsynonym = structurestags.setsynonym
+commands.settagsorting = structurestags.setsorting
+commands.settagdescription = structurestags.setdescription
+commands.settagdescriptionsymbol = structurestags.setdescriptionsymbol
+commands.settaghighlight = structurestags.sethighlight
+commands.settagfigure = structurestags.setfigure
+commands.settagcombination = structurestags.setcombination
+commands.settagtablecell = structurestags.settablecell
+commands.settagtabulatecell = structurestags.settabulatecell