diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/back-exp.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/back-exp.lua | 2707 |
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 = { ["&"] = "&", [">"] = ">", ["<"] = "<" } +local attribentities = { ["&"] = "&", [">"] = ">", ["<"] = "<", ['"'] = "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 = { "⁡" }, + 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 = { "⁡" }, + 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] = "&" - elseif c == 0x3E then - result[#result+1] = ">" - elseif c == 0x3C then - result[#result+1] = "<" - 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 |