diff options
Diffstat (limited to 'tex/context/base/back-exp.lua')
-rw-r--r-- | tex/context/base/back-exp.lua | 4822 |
1 files changed, 2411 insertions, 2411 deletions
diff --git a/tex/context/base/back-exp.lua b/tex/context/base/back-exp.lua index 4d219a18b..4d61e64c7 100644 --- a/tex/context/base/back-exp.lua +++ b/tex/context/base/back-exp.lua @@ -1,2411 +1,2411 @@ -if not modules then modules = { } end modules ['back-exp'] = { - version = 1.001, - comment = "companion to back-exp.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - --- 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 optimize the code ... currently the overhead is some 10% for xml + html so --- there is no hurry. - --- todo: move critital formatters out of functions --- todo: delay loading (apart from basic tag stuff) - -local next, type = next, type -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 validstring = string.valid -local lpegmatch = lpeg.match -local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values -local insert, remove = table.insert, table.remove -local fromunicode16 = fonts.mappings.fromunicode16 -local sortedhash = table.sortedhash -local formatters = string.formatters - -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) - --- 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") - -local nodes = nodes -local attributes = attributes -local variables = interfaces.variables - -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 -local skipcodes = nodes.skipcodes -local whatsitcodes = nodes.whatsitcodes -local listcodes = nodes.listcodes - -local hlist_code = nodecodes.hlist -local vlist_code = nodecodes.vlist -local glyph_code = nodecodes.glyph -local glue_code = nodecodes.glue -local kern_code = nodecodes.kern -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_characters = attributes.private('characters') -local a_exportstatus = attributes.private('exportstatus') - -local a_tagged = attributes.private('tagged') -local a_taggedpar = attributes.private("taggedpar") -local a_image = attributes.private('image') -local a_reference = attributes.private('reference') - -local a_textblock = attributes.private("textblock") - -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 locate_node = nodes.locate - -local references = structures.references -local structurestags = structures.tags -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 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 entityremapper = utf.remapper(entities) - -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 - local v = { } - t[k] = v - return v - end -end) - -setmetatableindex(specialspaces, function(t,k) - local v = utfchar(k) - t[k] = v - entities[v] = formatters["&#x%X;"](k) - somespace[k] = true - somespace[v] = true - return v -end) - - -local namespaced = { - -- filled on -} - -local namespaces = { - msubsup = "m", - msub = "m", - msup = "m", - mn = "m", - mi = "m", - ms = "m", - mo = "m", - mtext = "m", - mrow = "m", - mfrac = "m", - mroot = "m", - msqrt = "m", - munderover = "m", - munder = "m", - mover = "m", - merror = "m", - math = "m", - mrow = "m", - mtable = "m", - mtr = "m", - mtd = "m", - mfenced = "m", - maction = "m", - mspace = "m", -} - -setmetatableindex(namespaced, function(t,k) - 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 formatters[' %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(":") --- --- local precolon, predash, rest = P((1-colon)^1), P((1-dash )^1), P(1)^1 --- --- local tagsplitter = C(precolon) * colon * C(predash) * dash * C(rest) + --- C(predash) * dash * Cc(nil) * C(rest) - -local listdata = { } - -local function hashlistdata() - local c = structures.lists.collected - for i=1,#c do - local ci = c[i] - local tag = ci.references.tag - if tag then - local m = ci.metadata - listdata[m.kind .. ":" .. m.name .. "-" .. tag] = ci - end - end -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] = formatters[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.suffix(name) == "pdf" then - -- temp hack .. we will have a remapper - name = file.replacesuffix(name,"svg") - end - result[#result+1] = formatters[imagetemplate](element,detail,name,data.width,data.height) - end - end - return concat(result,"\n\n") -end - -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.suffix(name) == "pdf" then - unique[file.replacesuffix(name,"svg")] = name - else - unique[name] = name - end - end - end - return unique -end - --- - -properties.vspace = { export = "break", nature = "display" } ------------------ = { export = "pagebreak", nature = "display" } - -local function makebreaklist(list) - nofbreaks = nofbreaks + 1 - local t = { } - if list then - for i=1,#list do - t[i] = list[i] - end - end - t[#t+1] = "break-" .. nofbreaks -- maybe no number - return t -end - -local breakattributes = { - type = "collapse" -} - -local function makebreaknode(attributes) -- maybe no fulltag - nofbreaks = nofbreaks + 1 - return { - tg = "break", - fulltag = "break-" .. nofbreaks, - n = nofbreaks, - element = "break", - nature = "display", - attributes = attributes or nil, - -- data = { }, -- not needed - -- attribute = 0, -- not needed - -- parnumber = 0, - } -end - -local fields = { "title", "subtitle", "author", "keywords" } - -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 - 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 - result[#result+1] = formatters[" %s=%q"](key,value) - end - end - end - checkdocument(di) -end - -local itemgroups = { } - -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 -end - -local synonyms = { } - -function structurestags.setsynonym(current,tag) - synonyms[detailedtag("synonym",current)] = tag -end - -function extras.synonym(result,element,detail,n,fulltag,di) - local tag = synonyms[fulltag] - if tag then - result[#result+1] = formatters[" tag='%s'"](tag) - end -end - -local sortings = { } - -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] = formatters[" tag='%s'"](tag) - end -end - -usedstyles.highlight = { } - -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 references = nd.references - descriptions[references and references.internal] = detailedtag("description",tag) - end -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 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.description(result,element,detail,n,fulltag,di) - local id = linked[fulltag] - if id then - result[#result+1] = formatters[" insert='%s'"](id) -- maybe just fulltag - end -end - -function extras.descriptionsymbol(result,element,detail,n,fulltag,di) - local id = linked[fulltag] - if id then - result[#result+1] = formatters[" insert='%s'"](id) - 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] = formatters[" page='%s'"](data.page) - end - result[#result+1] = formatters[" 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] = formatters[" nx='%s' ny='%s'"](data.nx,data.ny) - end -end - --- quite some code deals with exporting references -- - -local evaluators = { } -local specials = { } - -evaluators.inner = function(result,var) - local inner = var.inner - if inner then - result[#result+1] = attribute("location",inner) - end -end - -evaluators.outer = function(result,var) - local file, url = references.checkedfileorurl(var.outer,var.outer) - if url then - result[#result+1] = attribute("url",url) - elseif file then - result[#result+1] = attribute("file",file) - end -end - -evaluators["outer with inner"] = function(result,var) - local file = references.checkedfile(var.f) - if file then - result[#result+1] = attribute("file",file) - end - local inner = var.inner - if inner then - result[#result+1] = attribute("location",inner) - end -end - -evaluators.special = function(result,var) - local handler = specials[var.special] - if handler then - handler(result,var) - end -end - -evaluators["special outer with operation"] = evaluators.special -evaluators["special operation"] = evaluators.special -evaluators["special operation with arguments"] = evaluators.special - -function specials.url(result,var) - local url = references.checkedurl(var.operation) - if url then - result[#result+1] = attribute("url",url) - end -end - -function specials.file(result,var) - local file = references.checkedfile(var.operation) - if file then - result[#result+1] = attribute("file",file) - end -end - -function specials.fileorurl(result,var) - local file, url = references.checkedfileorurl(var.operation,var.operation) - if url then - result[#result+1] = attribute("url",url) - elseif file then - result[#result+1] = attribute("file",file) - end -end - -function specials.internal(result,var) - local internal = references.checkedurl(var.operation) - if internal then - result[#result+1] = formatters[" location='aut:%s'"](internal) - end -end - -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 - result[#result+1] = formatters[" prefix='%s'"](prefix) - end - result[#result+1] = formatters[" destination='%s'"](reference) - for i=1,#references do - local r = references[i] - local e = evaluators[r.kind] - if e then - e(result,r) - end - end - end - end -end - -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 - result[#result+1] = formatters[" prefix='%s'"](prefix) - end - result[#result+1] = formatters[" reference='%s'"](reference) - end - local internal = references.internal - if internal and internal ~= "" then - result[#result+1] = formatters[" location='aut:%s'"](internal) - end - end -end - -function extras.link(result,element,detail,n,fulltag,di) - -- for instance in lists a link has nested elements and no own text - 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 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 - --- 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 apply_function = { - { - element = "mo", - -- comment = "apply function", - -- data = { utfchar(0x2061) }, - data = { "⁡" }, - nature = "mixed", - } -} - -local functioncontent = { } - -setmetatableindex(functioncontent,function(t,k) - local v = { { content = k } } - t[k] = v - return v -end) - -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 = functioncontent[tag] - end - if apply then - di.after = apply_function - 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 = apply_function - 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(result,data.references) - return true - else - local data = di.data - if data then - for i=1,#data do - local di = data[i] - if di then - local ft = di.fulltag - if ft and extras.section(result,element,detail,n,ft,di) then - return true - end - end - end - end - end -end - -function extras.float(result,element,detail,n,fulltag,di) - local data = listdata[fulltag] - if data then - 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(result,element,detail,n,di.fulltag,di) then - return true - 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(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] = formatters[" columns='%s'"](v) - end - local v = hash.rows - if v and v > 1 then - result[#result+1] = formatters[" rows='%s'"](v) - end - local v = hash.align - if not v or v == 0 then - -- normal - elseif v == 1 then -- use numbertoalign here - result[#result+1] = " align='flushright'" - elseif v == 2 then - result[#result+1] = " align='middle'" - elseif v == 3 then - result[#result+1] = " align='flushleft'" - end - 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(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 - result[#result+1] = " align='flushleft'" - elseif v == 2 then - result[#result+1] = " align='flushright'" - elseif v == 3 then - result[#result+1] = " align='middle'" - end - end -end - --- 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] = formatters["%w<%s"](depth,namespaced[element]) - else - result[#result+1] = formatters["\n%w<%s"](depth,namespaced[element]) - end - for k, v in next, a do - result[#result+1] = formatters[" %s=%q"](k,v) - end - result[#result+1] = "/>\n" - else - if linedone then - result[#result+1] = formatters["%w<%s/>\n"](depth,namespaced[element]) - else - result[#result+1] = formatters["\n%w<%s/>\n"](depth,namespaced[element]) - end - end - linedone = false -end - -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] = formatters["<!-- %s -->"](comment) - end - elseif nature == "mixed" then - if inlinedepth > 0 then - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->"](comment) - end - elseif linedone then - result[#result+1] = spaces[depth] - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->"](comment) - end - else - result[#result+1] = formatters["\n%w"](depth) - linedone = false - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->\n%w"](comment,depth) - end - end - inlinedepth = inlinedepth + 1 - else - if inlinedepth > 0 then - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->"](comment) - end - elseif linedone then - result[#result+1] = spaces[depth] - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->"](comment) - end - else - result[#result+1] = formatters["\n%w"](depth) -- can introduced extra line in mixed+mixed (filtered later on) - linedone = false - if show_comment and comment then - result[#result+1] = formatters["<!-- %s -->\n%w"](comment,depth) - end - end - end - if skip == "comment" then - if show_comment then - result[#result+1] = formatters["<!-- begin %s -->"](namespaced[element]) - end - elseif skip then - -- ignore - else - result[#result+1] = formatters["<%s"](namespaced[element]) - if detail then - result[#result+1] = formatters[" detail=%q"](detail) - end - if indexing and n then - result[#result+1] = formatters[" 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] = formatters[" %s=%q"](k,v) - end - end - local a = di.attributes - if a then - for k, v in next, a do - result[#result+1] = formatters[" %s=%q"](k,v) - end - end - result[#result+1] = ">" - end - 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] = formatters["%w<metadata>\n"](depth) - for k, v in table.sortedpairs(metadata) do - v = entityremapper(v) - result[#result+1] = formatters["%w<metavariable name=%q>%s</metavariable>\n"](depth+1,k,v) - end - result[#result+1] = formatters["%w</metadata>\n"](depth) - end -end - -local function endtag(result,element,nature,depth,skip) - if nature == "display" then - if inlinedepth == 0 then - if not linedone then - result[#result+1] = "\n" - end - if skip == "comment" then - if show_comment then - result[#result+1] = formatters["%w<!-- end %s -->\n"](depth,namespaced[element]) - end - elseif skip then - -- ignore - else - result[#result+1] = formatters["%w</%s>\n"](depth,namespaced[element]) - end - linedone = true - else - if skip == "comment" then - if show_comment then - result[#result+1] = formatters["<!-- end %s -->"](namespaced[element]) - end - elseif skip then - -- ignore - else - result[#result+1] = formatters["</%s>"](namespaced[element]) - end - end - else - inlinedepth = inlinedepth - 1 - if skip == "comment" then - if show_comment then - result[#result+1] = formatters["<!-- end %s -->"](namespaced[element]) - end - elseif skip then - -- ignore - else - result[#result+1] = formatters["</%s>"](namespaced[element]) - end - linedone = false - end -end - -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 -- hm, di can be string - -- whatever - elseif di.content then - -- already has breaks - local content = entityremapper(di.content) - 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] = formatters["<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] = formatters["<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 not element then - -- skip - elseif 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 - if detail then - 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 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 = { - tg = tg, - fulltag = fulltag, - detail = detail, - n = tonumber(n), -- more efficient - element = element, - nature = nature, - data = { }, - attribute = currentattribute, - parnumber = currentparagraph, - } - treedata[#treedata+1] = t - currentdepth = currentdepth + 1 - nesting[currentdepth] = fulltag - treestack[currentdepth] = tree - if trace_export then - if detail and detail ~= "" then - report_export("%w<%s trigger=%a paragraph=%a index=%a detail=%a>",currentdepth-1,fulltag,currentattribute or 0,currentparagraph or 0,#treedata,detail) - else - report_export("%w<%s trigger=%a paragraph=%a index=%a>",currentdepth-1,fulltag,currentattribute or 0,currentparagraph or 0,#treedata) - end - end - tree = t - if tg == "break" then - -- no need for this - else - local h = treehash[fulltag] - if h then - h[#h+1] = t - else - treehash[fulltag] = { t } - end - end -end - -local function pop() - local top = nesting[currentdepth] - tree = treestack[currentdepth] - currentdepth = currentdepth - 1 - if trace_export then - if top then - report_export("%w</%s>",currentdepth,top) - else - report_export("</%s>",top) - end - end -end - -local function continueexport() - if nofcurrentcontent > 0 then - if trace_export then - report_export("%w<!-- injecting pagebreak space -->",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("%w<!-- moving from depth %s to %s (%s) -->",currentdepth,olddepth,newdepth,current[newdepth]) - end - if olddepth <= 0 then - for i=1,newdepth do - push(current[i],i) - end - else - local difference - if olddepth < newdepth then - for i=1,olddepth do - if current[i] ~= nesting[i] then - difference = i - break - end - end - else - for i=1,newdepth do - if current[i] ~= nesting[i] then - difference = i - break - end - end - end - if difference then - for i=olddepth,difference,-1 do - pop() - end - for i=difference,newdepth do - push(current[i],i) - end - elseif newdepth > olddepth then - for i=olddepth+1,newdepth do - push(current[i],i) - end - elseif newdepth < olddepth then - for i=olddepth,newdepth,-1 do - pop() - end - elseif trace_export then - report_export("%w<!-- staying at depth %s (%s) -->",currentdepth,newdepth,nesting[newdepth] or "?") - end - end - return olddepth, newdepth - end -end - -local function pushcontent(currentparagraph,newparagraph) - if nofcurrentcontent > 0 then - if currentparagraph then - if currentcontent[nofcurrentcontent] == "\n" then - if trace_export then - report_export("%w<!-- removing newline -->",currentdepth) - end - nofcurrentcontent = nofcurrentcontent - 1 - end - end - local content = concat(currentcontent,"",1,nofcurrentcontent) - if content == "" then - -- omit; when currentparagraph we could push, remove spaces, pop - elseif somespace[content] and currentparagraph then - -- omit; when currentparagraph we could push, remove spaces, pop - else - local olddepth, newdepth - local list = taglist[currentattribute] - if list then - olddepth, newdepth = pushentry(list) - end - local td = tree.data - local nd = #td - td[nd+1] = { parnumber = currentparagraph, content = content } - if trace_export then - report_export("%w<!-- start content with length %s -->",currentdepth,#content) - report_export("%w%s",currentdepth,(gsub(content,"\n","\\n"))) - report_export("%w<!-- stop content -->",currentdepth) - end - if olddepth then - for i=newdepth-1,olddepth,-1 do - pop() - end - end - end - nofcurrentcontent = 0 - end - if currentparagraph then - pushentry(makebreaklist(currentnesting)) - if trace_export then - report_export("%w<!-- break added betweep paragraph %a and %a -->",currentdepth,currentparagraph,newparagraph) - end - end -end - -local function finishexport() - if trace_export then - report_export("%w<!-- start finalizing -->",currentdepth) - end - if nofcurrentcontent > 0 then - if somespace[currentcontent[nofcurrentcontent]] then - if trace_export then - report_export("%w<!-- removing space -->",currentdepth) - end - nofcurrentcontent = nofcurrentcontent - 1 - end - pushcontent() - end - for i=currentdepth,1,-1 do - pop() - end - currentcontent = { } -- we're nice and do a cleanup - if trace_export then - report_export("%w<!-- stop finalizing -->",currentdepth) - end -end - --- whatsit_code localpar_code - -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 - local at = n[a_tagged] - if not at then - -- we need to tag the pagebody stuff as being valid skippable - -- - -- report_export("skipping character: %C (no attribute)",n.char) - else - -- we could add tonunicodes for ligatures (todo) - local components = n.components - if components then -- we loose data - collectresults(components,nil) - else - local c = n.char - if last ~= at then - local tl = taglist[at] - pushcontent() - currentnesting = tl - currentparagraph = n[a_taggedpar] - currentattribute = at - last = at - pushentry(currentnesting) - if trace_export then - report_export("%w<!-- processing glyph %C tagged %a -->",currentdepth,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 = n[a_reference] - if r then - referencehash[tl[#tl]] = r -- fulltag - end - -- - elseif last then - local ap = n[a_taggedpar] - if ap ~= currentparagraph then - pushcontent(currentparagraph,ap) - pushentry(currentnesting) - currentattribute = last - currentparagraph = ap - end - if trace_export then - report_export("%w<!-- processing glyph %C tagged %a) -->",currentdepth,c,last) - end - else - if trace_export then - report_export("%w<!-- processing glyph %C tagged %a) -->",currentdepth,c,at) - end - end - local s = n[a_exportstatus] - if s then - c = s - end - if c == 0 then - if trace_export then - report_export("%w<!-- skipping last glyph -->",currentdepth) - end - elseif c == 0x20 then - local a = n[a_characters] - nofcurrentcontent = nofcurrentcontent + 1 - if a then - if trace_export then - report_export("%w<!-- turning last space into special space %U -->",currentdepth,a) - end - currentcontent[nofcurrentcontent] = specialspaces[a] -- special space - else - currentcontent[nofcurrentcontent] = " " - end - else - local fc = fontchar[n.font] - if fc then - fc = fc and fc[c] - if fc then - local u = fc.tounicode - if u and u ~= "" then - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = utfchar(fromunicode16(u)) - else - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = utfchar(c) - end - else -- weird, happens in hz (we really need to get rid of the pseudo fonts) - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = utfchar(c) - end - else - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = utfchar(c) - end - end - end - 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) - elseif id == glue_code then - -- we need to distinguish between hskips and vskips - local ca = n[a_characters] - if ca == 0 then - -- skip this one ... already converted special character (node-acc) - elseif ca then - local a = n[a_tagged] - if a then - local c = specialspaces[ca] - if last ~= a then - local tl = taglist[a] - if trace_export then - report_export("%w<!-- processing space glyph %U tagged %a case 1 -->",currentdepth,ca,a) - end - pushcontent() - currentnesting = tl - currentparagraph = n[a_taggedpar] - currentattribute = a - last = a - pushentry(currentnesting) - -- no reference check (see above) - elseif last then - local ap = n[a_taggedpar] - if ap ~= currentparagraph then - pushcontent(currentparagraph,ap) - pushentry(currentnesting) - currentattribute = last - currentparagraph = ap - end - if trace_export then - report_export("%w<!-- processing space glyph %U tagged %a case 2 -->",currentdepth,ca,last) - end - end - -- if somespace[currentcontent[nofcurrentcontent]] then - -- if trace_export then - -- report_export("%w<!-- removing space -->",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 = n[a_tagged] - if a == last then - if trace_export then - report_export("%w<!-- injecting spacing 5a -->",currentdepth) - end - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = " " - elseif a then - -- e.g LOGO<space>LOGO - if trace_export then - report_export("%w<!-- processing glue > threshold tagged %s becomes %s -->",currentdepth,last,a) - end - pushcontent() - if trace_export then - report_export("%w<!-- injecting spacing 5b -->",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 = n[a_tagged] - if a == last then - if trace_export then - report_export("%w<!-- injecting spacing 7 (stay in element) -->",currentdepth) - end - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = " " - else - if trace_export then - report_export("%w<!-- injecting spacing 7 (end of element) -->",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 = n[a_tagged] - if a == last then - if trace_export then - report_export("%w<!-- injecting spacing 1 (end of line, stay in element) -->",currentdepth) - end - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = " " - else - if trace_export then - report_export("%w<!-- injecting spacing 1 (end of line, end of element) -->",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 == hlist_code or id == vlist_code then - local ai = n[a_image] - if ai then - local at = n[a_tagged] - if nofcurrentcontent > 0 then - pushcontent() - pushentry(currentnesting) -- ?? - end - pushentry(taglist[at]) -- has an index, todo: flag empty element - if trace_export then - report_export("%w<!-- processing image tagged %a",currentdepth,last) - end - last = nil - currentparagraph = nil - else - -- we need to determine an end-of-line - collectresults(n.list,n) - 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 = n[a_tagged] - if a == last then - if not somespace[currentcontent[nofcurrentcontent]] then - if trace_export then - report_export("%w<!-- injecting spacing 8 (kern %p) -->",currentdepth,kern) - end - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = " " - end - elseif a then - -- e.g LOGO<space>LOGO - if trace_export then - report_export("%w<!-- processing kern, threshold %p, tag %s => %s -->",currentdepth,limit,last,a) - end - last = a - pushcontent() - if trace_export then - report_export("%w<!-- injecting spacing 9 (kern %p) -->",currentdepth,kern) - end - nofcurrentcontent = nofcurrentcontent + 1 - currentcontent[nofcurrentcontent] = " " - currentnesting = taglist[last] - pushentry(currentnesting) - currentattribute = last - end - end - end - end - end - p = n - end -end - -function nodes.handlers.export(head) -- hooks into the page builder - starttiming(treehash) - if trace_export then - report_export("%w<!-- start flushing page -->",currentdepth) - end - -- continueexport() - restart = true - collectresults(head) - if trace_export then - report_export("%w<!-- stop flushing page -->",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 - n[a_textblock] = noftextblocks - elseif subtype == glue_code or subtype == kern_code then - 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 sortedhash(used) do - result[#result+1] = format("/* category: %s */",element) - for detail, nature in sortedhash(details) do - local d = displaymapping[nature or "display"] or "block" - if detail == "" then - result[#result+1] = formatters[e_template](element,d) - else - result[#result+1] = formatters[d_template](element,detail,d) - end - end - end - 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 - --- 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 - -- 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) - -- - 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 %a",xmlfile) - io.savedata(xmlfile,results) - -- - report_export("saving css image definitions in %a",imagefilename) - io.savedata(imagefilename,allusedimages(xmlfile)) - -- - report_export("saving css style definitions in %a",stylefilename) - io.savedata(stylefilename,allusedstyles(xmlfile)) - -- - report_export("saving css template in %a",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 %a",xhtmlfile) - local xmltree = cleanxhtmltree(xml.convert(results)) - xml.save(xmltree,xhtmlfile) - -- looking at identity is somewhat redundant as we also inherit from interaction - -- at the tex end - local identity = interactions.general.getidentity() - local specification = { - name = file.removesuffix(v), - identifier = os.uuid(), - images = uniqueusedimages(), - root = xhtmlfile, - files = files, - language = languagenames[tex.count.mainlanguagenumber], - title = validstring(finetuning.title) or validstring(identity.title), - subtitle = validstring(finetuning.subtitle) or validstring(identity.subtitle), - author = validstring(finetuning.author) or validstring(identity.author), - firstpage = validstring(finetuning.firstpage), - lastpage = validstring(finetuning.lastpage), - } - report_export("saving specification in %a (mtxrun --script epub --make %s)",specificationfilename,specificationfilename) - io.savedata(specificationfilename,table.serialize(specification,true)) - end - stoptiming(treehash) -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, 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 +if not modules then modules = { } end modules ['back-exp'] = {
+ version = 1.001,
+ comment = "companion to back-exp.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- 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 optimize the code ... currently the overhead is some 10% for xml + html so
+-- there is no hurry.
+
+-- todo: move critital formatters out of functions
+-- todo: delay loading (apart from basic tag stuff)
+
+local next, type = next, type
+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 validstring = string.valid
+local lpegmatch = lpeg.match
+local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values
+local insert, remove = table.insert, table.remove
+local fromunicode16 = fonts.mappings.fromunicode16
+local sortedhash = table.sortedhash
+local formatters = string.formatters
+
+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)
+
+-- 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")
+
+local nodes = nodes
+local attributes = attributes
+local variables = interfaces.variables
+
+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
+local skipcodes = nodes.skipcodes
+local whatsitcodes = nodes.whatsitcodes
+local listcodes = nodes.listcodes
+
+local hlist_code = nodecodes.hlist
+local vlist_code = nodecodes.vlist
+local glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+local kern_code = nodecodes.kern
+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_characters = attributes.private('characters')
+local a_exportstatus = attributes.private('exportstatus')
+
+local a_tagged = attributes.private('tagged')
+local a_taggedpar = attributes.private("taggedpar")
+local a_image = attributes.private('image')
+local a_reference = attributes.private('reference')
+
+local a_textblock = attributes.private("textblock")
+
+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 locate_node = nodes.locate
+
+local references = structures.references
+local structurestags = structures.tags
+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 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 entityremapper = utf.remapper(entities)
+
+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
+ local v = { }
+ t[k] = v
+ return v
+ end
+end)
+
+setmetatableindex(specialspaces, function(t,k)
+ local v = utfchar(k)
+ t[k] = v
+ entities[v] = formatters["&#x%X;"](k)
+ somespace[k] = true
+ somespace[v] = true
+ return v
+end)
+
+
+local namespaced = {
+ -- filled on
+}
+
+local namespaces = {
+ msubsup = "m",
+ msub = "m",
+ msup = "m",
+ mn = "m",
+ mi = "m",
+ ms = "m",
+ mo = "m",
+ mtext = "m",
+ mrow = "m",
+ mfrac = "m",
+ mroot = "m",
+ msqrt = "m",
+ munderover = "m",
+ munder = "m",
+ mover = "m",
+ merror = "m",
+ math = "m",
+ mrow = "m",
+ mtable = "m",
+ mtr = "m",
+ mtd = "m",
+ mfenced = "m",
+ maction = "m",
+ mspace = "m",
+}
+
+setmetatableindex(namespaced, function(t,k)
+ 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 formatters[' %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(":")
+--
+-- local precolon, predash, rest = P((1-colon)^1), P((1-dash )^1), P(1)^1
+--
+-- local tagsplitter = C(precolon) * colon * C(predash) * dash * C(rest) +
+-- C(predash) * dash * Cc(nil) * C(rest)
+
+local listdata = { }
+
+local function hashlistdata()
+ local c = structures.lists.collected
+ for i=1,#c do
+ local ci = c[i]
+ local tag = ci.references.tag
+ if tag then
+ local m = ci.metadata
+ listdata[m.kind .. ":" .. m.name .. "-" .. tag] = ci
+ end
+ end
+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] = formatters[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.suffix(name) == "pdf" then
+ -- temp hack .. we will have a remapper
+ name = file.replacesuffix(name,"svg")
+ end
+ result[#result+1] = formatters[imagetemplate](element,detail,name,data.width,data.height)
+ end
+ end
+ return concat(result,"\n\n")
+end
+
+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.suffix(name) == "pdf" then
+ unique[file.replacesuffix(name,"svg")] = name
+ else
+ unique[name] = name
+ end
+ end
+ end
+ return unique
+end
+
+--
+
+properties.vspace = { export = "break", nature = "display" }
+----------------- = { export = "pagebreak", nature = "display" }
+
+local function makebreaklist(list)
+ nofbreaks = nofbreaks + 1
+ local t = { }
+ if list then
+ for i=1,#list do
+ t[i] = list[i]
+ end
+ end
+ t[#t+1] = "break-" .. nofbreaks -- maybe no number
+ return t
+end
+
+local breakattributes = {
+ type = "collapse"
+}
+
+local function makebreaknode(attributes) -- maybe no fulltag
+ nofbreaks = nofbreaks + 1
+ return {
+ tg = "break",
+ fulltag = "break-" .. nofbreaks,
+ n = nofbreaks,
+ element = "break",
+ nature = "display",
+ attributes = attributes or nil,
+ -- data = { }, -- not needed
+ -- attribute = 0, -- not needed
+ -- parnumber = 0,
+ }
+end
+
+local fields = { "title", "subtitle", "author", "keywords" }
+
+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
+ 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
+ result[#result+1] = formatters[" %s=%q"](key,value)
+ end
+ end
+ end
+ checkdocument(di)
+end
+
+local itemgroups = { }
+
+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
+end
+
+local synonyms = { }
+
+function structurestags.setsynonym(current,tag)
+ synonyms[detailedtag("synonym",current)] = tag
+end
+
+function extras.synonym(result,element,detail,n,fulltag,di)
+ local tag = synonyms[fulltag]
+ if tag then
+ result[#result+1] = formatters[" tag='%s'"](tag)
+ end
+end
+
+local sortings = { }
+
+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] = formatters[" tag='%s'"](tag)
+ end
+end
+
+usedstyles.highlight = { }
+
+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 references = nd.references
+ descriptions[references and references.internal] = detailedtag("description",tag)
+ end
+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 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.description(result,element,detail,n,fulltag,di)
+ local id = linked[fulltag]
+ if id then
+ result[#result+1] = formatters[" insert='%s'"](id) -- maybe just fulltag
+ end
+end
+
+function extras.descriptionsymbol(result,element,detail,n,fulltag,di)
+ local id = linked[fulltag]
+ if id then
+ result[#result+1] = formatters[" insert='%s'"](id)
+ 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] = formatters[" page='%s'"](data.page)
+ end
+ result[#result+1] = formatters[" 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] = formatters[" nx='%s' ny='%s'"](data.nx,data.ny)
+ end
+end
+
+-- quite some code deals with exporting references --
+
+local evaluators = { }
+local specials = { }
+
+evaluators.inner = function(result,var)
+ local inner = var.inner
+ if inner then
+ result[#result+1] = attribute("location",inner)
+ end
+end
+
+evaluators.outer = function(result,var)
+ local file, url = references.checkedfileorurl(var.outer,var.outer)
+ if url then
+ result[#result+1] = attribute("url",url)
+ elseif file then
+ result[#result+1] = attribute("file",file)
+ end
+end
+
+evaluators["outer with inner"] = function(result,var)
+ local file = references.checkedfile(var.f)
+ if file then
+ result[#result+1] = attribute("file",file)
+ end
+ local inner = var.inner
+ if inner then
+ result[#result+1] = attribute("location",inner)
+ end
+end
+
+evaluators.special = function(result,var)
+ local handler = specials[var.special]
+ if handler then
+ handler(result,var)
+ end
+end
+
+evaluators["special outer with operation"] = evaluators.special
+evaluators["special operation"] = evaluators.special
+evaluators["special operation with arguments"] = evaluators.special
+
+function specials.url(result,var)
+ local url = references.checkedurl(var.operation)
+ if url then
+ result[#result+1] = attribute("url",url)
+ end
+end
+
+function specials.file(result,var)
+ local file = references.checkedfile(var.operation)
+ if file then
+ result[#result+1] = attribute("file",file)
+ end
+end
+
+function specials.fileorurl(result,var)
+ local file, url = references.checkedfileorurl(var.operation,var.operation)
+ if url then
+ result[#result+1] = attribute("url",url)
+ elseif file then
+ result[#result+1] = attribute("file",file)
+ end
+end
+
+function specials.internal(result,var)
+ local internal = references.checkedurl(var.operation)
+ if internal then
+ result[#result+1] = formatters[" location='aut:%s'"](internal)
+ end
+end
+
+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
+ result[#result+1] = formatters[" prefix='%s'"](prefix)
+ end
+ result[#result+1] = formatters[" destination='%s'"](reference)
+ for i=1,#references do
+ local r = references[i]
+ local e = evaluators[r.kind]
+ if e then
+ e(result,r)
+ end
+ end
+ end
+ end
+end
+
+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
+ result[#result+1] = formatters[" prefix='%s'"](prefix)
+ end
+ result[#result+1] = formatters[" reference='%s'"](reference)
+ end
+ local internal = references.internal
+ if internal and internal ~= "" then
+ result[#result+1] = formatters[" location='aut:%s'"](internal)
+ end
+ end
+end
+
+function extras.link(result,element,detail,n,fulltag,di)
+ -- for instance in lists a link has nested elements and no own text
+ 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 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
+
+-- 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 apply_function = {
+ {
+ element = "mo",
+ -- comment = "apply function",
+ -- data = { utfchar(0x2061) },
+ data = { "⁡" },
+ nature = "mixed",
+ }
+}
+
+local functioncontent = { }
+
+setmetatableindex(functioncontent,function(t,k)
+ local v = { { content = k } }
+ t[k] = v
+ return v
+end)
+
+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 = functioncontent[tag]
+ end
+ if apply then
+ di.after = apply_function
+ 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 = apply_function
+ 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(result,data.references)
+ return true
+ else
+ local data = di.data
+ if data then
+ for i=1,#data do
+ local di = data[i]
+ if di then
+ local ft = di.fulltag
+ if ft and extras.section(result,element,detail,n,ft,di) then
+ return true
+ end
+ end
+ end
+ end
+ end
+end
+
+function extras.float(result,element,detail,n,fulltag,di)
+ local data = listdata[fulltag]
+ if data then
+ 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(result,element,detail,n,di.fulltag,di) then
+ return true
+ 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(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] = formatters[" columns='%s'"](v)
+ end
+ local v = hash.rows
+ if v and v > 1 then
+ result[#result+1] = formatters[" rows='%s'"](v)
+ end
+ local v = hash.align
+ if not v or v == 0 then
+ -- normal
+ elseif v == 1 then -- use numbertoalign here
+ result[#result+1] = " align='flushright'"
+ elseif v == 2 then
+ result[#result+1] = " align='middle'"
+ elseif v == 3 then
+ result[#result+1] = " align='flushleft'"
+ end
+ 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(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
+ result[#result+1] = " align='flushleft'"
+ elseif v == 2 then
+ result[#result+1] = " align='flushright'"
+ elseif v == 3 then
+ result[#result+1] = " align='middle'"
+ end
+ end
+end
+
+-- 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] = formatters["%w<%s"](depth,namespaced[element])
+ else
+ result[#result+1] = formatters["\n%w<%s"](depth,namespaced[element])
+ end
+ for k, v in next, a do
+ result[#result+1] = formatters[" %s=%q"](k,v)
+ end
+ result[#result+1] = "/>\n"
+ else
+ if linedone then
+ result[#result+1] = formatters["%w<%s/>\n"](depth,namespaced[element])
+ else
+ result[#result+1] = formatters["\n%w<%s/>\n"](depth,namespaced[element])
+ end
+ end
+ linedone = false
+end
+
+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] = formatters["<!-- %s -->"](comment)
+ end
+ elseif nature == "mixed" then
+ if inlinedepth > 0 then
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->"](comment)
+ end
+ elseif linedone then
+ result[#result+1] = spaces[depth]
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->"](comment)
+ end
+ else
+ result[#result+1] = formatters["\n%w"](depth)
+ linedone = false
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->\n%w"](comment,depth)
+ end
+ end
+ inlinedepth = inlinedepth + 1
+ else
+ if inlinedepth > 0 then
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->"](comment)
+ end
+ elseif linedone then
+ result[#result+1] = spaces[depth]
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->"](comment)
+ end
+ else
+ result[#result+1] = formatters["\n%w"](depth) -- can introduced extra line in mixed+mixed (filtered later on)
+ linedone = false
+ if show_comment and comment then
+ result[#result+1] = formatters["<!-- %s -->\n%w"](comment,depth)
+ end
+ end
+ end
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = formatters["<!-- begin %s -->"](namespaced[element])
+ end
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = formatters["<%s"](namespaced[element])
+ if detail then
+ result[#result+1] = formatters[" detail=%q"](detail)
+ end
+ if indexing and n then
+ result[#result+1] = formatters[" 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] = formatters[" %s=%q"](k,v)
+ end
+ end
+ local a = di.attributes
+ if a then
+ for k, v in next, a do
+ result[#result+1] = formatters[" %s=%q"](k,v)
+ end
+ end
+ result[#result+1] = ">"
+ end
+ 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] = formatters["%w<metadata>\n"](depth)
+ for k, v in table.sortedpairs(metadata) do
+ v = entityremapper(v)
+ result[#result+1] = formatters["%w<metavariable name=%q>%s</metavariable>\n"](depth+1,k,v)
+ end
+ result[#result+1] = formatters["%w</metadata>\n"](depth)
+ end
+end
+
+local function endtag(result,element,nature,depth,skip)
+ if nature == "display" then
+ if inlinedepth == 0 then
+ if not linedone then
+ result[#result+1] = "\n"
+ end
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = formatters["%w<!-- end %s -->\n"](depth,namespaced[element])
+ end
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = formatters["%w</%s>\n"](depth,namespaced[element])
+ end
+ linedone = true
+ else
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = formatters["<!-- end %s -->"](namespaced[element])
+ end
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = formatters["</%s>"](namespaced[element])
+ end
+ end
+ else
+ inlinedepth = inlinedepth - 1
+ if skip == "comment" then
+ if show_comment then
+ result[#result+1] = formatters["<!-- end %s -->"](namespaced[element])
+ end
+ elseif skip then
+ -- ignore
+ else
+ result[#result+1] = formatters["</%s>"](namespaced[element])
+ end
+ linedone = false
+ end
+end
+
+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 -- hm, di can be string
+ -- whatever
+ elseif di.content then
+ -- already has breaks
+ local content = entityremapper(di.content)
+ 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] = formatters["<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] = formatters["<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 not element then
+ -- skip
+ elseif 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
+ if detail then
+ 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 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 = {
+ tg = tg,
+ fulltag = fulltag,
+ detail = detail,
+ n = tonumber(n), -- more efficient
+ element = element,
+ nature = nature,
+ data = { },
+ attribute = currentattribute,
+ parnumber = currentparagraph,
+ }
+ treedata[#treedata+1] = t
+ currentdepth = currentdepth + 1
+ nesting[currentdepth] = fulltag
+ treestack[currentdepth] = tree
+ if trace_export then
+ if detail and detail ~= "" then
+ report_export("%w<%s trigger=%a paragraph=%a index=%a detail=%a>",currentdepth-1,fulltag,currentattribute or 0,currentparagraph or 0,#treedata,detail)
+ else
+ report_export("%w<%s trigger=%a paragraph=%a index=%a>",currentdepth-1,fulltag,currentattribute or 0,currentparagraph or 0,#treedata)
+ end
+ end
+ tree = t
+ if tg == "break" then
+ -- no need for this
+ else
+ local h = treehash[fulltag]
+ if h then
+ h[#h+1] = t
+ else
+ treehash[fulltag] = { t }
+ end
+ end
+end
+
+local function pop()
+ local top = nesting[currentdepth]
+ tree = treestack[currentdepth]
+ currentdepth = currentdepth - 1
+ if trace_export then
+ if top then
+ report_export("%w</%s>",currentdepth,top)
+ else
+ report_export("</%s>",top)
+ end
+ end
+end
+
+local function continueexport()
+ if nofcurrentcontent > 0 then
+ if trace_export then
+ report_export("%w<!-- injecting pagebreak space -->",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("%w<!-- moving from depth %s to %s (%s) -->",currentdepth,olddepth,newdepth,current[newdepth])
+ end
+ if olddepth <= 0 then
+ for i=1,newdepth do
+ push(current[i],i)
+ end
+ else
+ local difference
+ if olddepth < newdepth then
+ for i=1,olddepth do
+ if current[i] ~= nesting[i] then
+ difference = i
+ break
+ end
+ end
+ else
+ for i=1,newdepth do
+ if current[i] ~= nesting[i] then
+ difference = i
+ break
+ end
+ end
+ end
+ if difference then
+ for i=olddepth,difference,-1 do
+ pop()
+ end
+ for i=difference,newdepth do
+ push(current[i],i)
+ end
+ elseif newdepth > olddepth then
+ for i=olddepth+1,newdepth do
+ push(current[i],i)
+ end
+ elseif newdepth < olddepth then
+ for i=olddepth,newdepth,-1 do
+ pop()
+ end
+ elseif trace_export then
+ report_export("%w<!-- staying at depth %s (%s) -->",currentdepth,newdepth,nesting[newdepth] or "?")
+ end
+ end
+ return olddepth, newdepth
+ end
+end
+
+local function pushcontent(currentparagraph,newparagraph)
+ if nofcurrentcontent > 0 then
+ if currentparagraph then
+ if currentcontent[nofcurrentcontent] == "\n" then
+ if trace_export then
+ report_export("%w<!-- removing newline -->",currentdepth)
+ end
+ nofcurrentcontent = nofcurrentcontent - 1
+ end
+ end
+ local content = concat(currentcontent,"",1,nofcurrentcontent)
+ if content == "" then
+ -- omit; when currentparagraph we could push, remove spaces, pop
+ elseif somespace[content] and currentparagraph then
+ -- omit; when currentparagraph we could push, remove spaces, pop
+ else
+ local olddepth, newdepth
+ local list = taglist[currentattribute]
+ if list then
+ olddepth, newdepth = pushentry(list)
+ end
+ local td = tree.data
+ local nd = #td
+ td[nd+1] = { parnumber = currentparagraph, content = content }
+ if trace_export then
+ report_export("%w<!-- start content with length %s -->",currentdepth,#content)
+ report_export("%w%s",currentdepth,(gsub(content,"\n","\\n")))
+ report_export("%w<!-- stop content -->",currentdepth)
+ end
+ if olddepth then
+ for i=newdepth-1,olddepth,-1 do
+ pop()
+ end
+ end
+ end
+ nofcurrentcontent = 0
+ end
+ if currentparagraph then
+ pushentry(makebreaklist(currentnesting))
+ if trace_export then
+ report_export("%w<!-- break added betweep paragraph %a and %a -->",currentdepth,currentparagraph,newparagraph)
+ end
+ end
+end
+
+local function finishexport()
+ if trace_export then
+ report_export("%w<!-- start finalizing -->",currentdepth)
+ end
+ if nofcurrentcontent > 0 then
+ if somespace[currentcontent[nofcurrentcontent]] then
+ if trace_export then
+ report_export("%w<!-- removing space -->",currentdepth)
+ end
+ nofcurrentcontent = nofcurrentcontent - 1
+ end
+ pushcontent()
+ end
+ for i=currentdepth,1,-1 do
+ pop()
+ end
+ currentcontent = { } -- we're nice and do a cleanup
+ if trace_export then
+ report_export("%w<!-- stop finalizing -->",currentdepth)
+ end
+end
+
+-- whatsit_code localpar_code
+
+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
+ local at = n[a_tagged]
+ if not at then
+ -- we need to tag the pagebody stuff as being valid skippable
+ --
+ -- report_export("skipping character: %C (no attribute)",n.char)
+ else
+ -- we could add tonunicodes for ligatures (todo)
+ local components = n.components
+ if components then -- we loose data
+ collectresults(components,nil)
+ else
+ local c = n.char
+ if last ~= at then
+ local tl = taglist[at]
+ pushcontent()
+ currentnesting = tl
+ currentparagraph = n[a_taggedpar]
+ currentattribute = at
+ last = at
+ pushentry(currentnesting)
+ if trace_export then
+ report_export("%w<!-- processing glyph %C tagged %a -->",currentdepth,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 = n[a_reference]
+ if r then
+ referencehash[tl[#tl]] = r -- fulltag
+ end
+ --
+ elseif last then
+ local ap = n[a_taggedpar]
+ if ap ~= currentparagraph then
+ pushcontent(currentparagraph,ap)
+ pushentry(currentnesting)
+ currentattribute = last
+ currentparagraph = ap
+ end
+ if trace_export then
+ report_export("%w<!-- processing glyph %C tagged %a) -->",currentdepth,c,last)
+ end
+ else
+ if trace_export then
+ report_export("%w<!-- processing glyph %C tagged %a) -->",currentdepth,c,at)
+ end
+ end
+ local s = n[a_exportstatus]
+ if s then
+ c = s
+ end
+ if c == 0 then
+ if trace_export then
+ report_export("%w<!-- skipping last glyph -->",currentdepth)
+ end
+ elseif c == 0x20 then
+ local a = n[a_characters]
+ nofcurrentcontent = nofcurrentcontent + 1
+ if a then
+ if trace_export then
+ report_export("%w<!-- turning last space into special space %U -->",currentdepth,a)
+ end
+ currentcontent[nofcurrentcontent] = specialspaces[a] -- special space
+ else
+ currentcontent[nofcurrentcontent] = " "
+ end
+ else
+ local fc = fontchar[n.font]
+ if fc then
+ fc = fc and fc[c]
+ if fc then
+ local u = fc.tounicode
+ if u and u ~= "" then
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(fromunicode16(u))
+ else
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
+ end
+ else -- weird, happens in hz (we really need to get rid of the pseudo fonts)
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
+ end
+ else
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = utfchar(c)
+ end
+ end
+ end
+ 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)
+ elseif id == glue_code then
+ -- we need to distinguish between hskips and vskips
+ local ca = n[a_characters]
+ if ca == 0 then
+ -- skip this one ... already converted special character (node-acc)
+ elseif ca then
+ local a = n[a_tagged]
+ if a then
+ local c = specialspaces[ca]
+ if last ~= a then
+ local tl = taglist[a]
+ if trace_export then
+ report_export("%w<!-- processing space glyph %U tagged %a case 1 -->",currentdepth,ca,a)
+ end
+ pushcontent()
+ currentnesting = tl
+ currentparagraph = n[a_taggedpar]
+ currentattribute = a
+ last = a
+ pushentry(currentnesting)
+ -- no reference check (see above)
+ elseif last then
+ local ap = n[a_taggedpar]
+ if ap ~= currentparagraph then
+ pushcontent(currentparagraph,ap)
+ pushentry(currentnesting)
+ currentattribute = last
+ currentparagraph = ap
+ end
+ if trace_export then
+ report_export("%w<!-- processing space glyph %U tagged %a case 2 -->",currentdepth,ca,last)
+ end
+ end
+ -- if somespace[currentcontent[nofcurrentcontent]] then
+ -- if trace_export then
+ -- report_export("%w<!-- removing space -->",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 = n[a_tagged]
+ if a == last then
+ if trace_export then
+ report_export("%w<!-- injecting spacing 5a -->",currentdepth)
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ elseif a then
+ -- e.g LOGO<space>LOGO
+ if trace_export then
+ report_export("%w<!-- processing glue > threshold tagged %s becomes %s -->",currentdepth,last,a)
+ end
+ pushcontent()
+ if trace_export then
+ report_export("%w<!-- injecting spacing 5b -->",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 = n[a_tagged]
+ if a == last then
+ if trace_export then
+ report_export("%w<!-- injecting spacing 7 (stay in element) -->",currentdepth)
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ else
+ if trace_export then
+ report_export("%w<!-- injecting spacing 7 (end of element) -->",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 = n[a_tagged]
+ if a == last then
+ if trace_export then
+ report_export("%w<!-- injecting spacing 1 (end of line, stay in element) -->",currentdepth)
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ else
+ if trace_export then
+ report_export("%w<!-- injecting spacing 1 (end of line, end of element) -->",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 == hlist_code or id == vlist_code then
+ local ai = n[a_image]
+ if ai then
+ local at = n[a_tagged]
+ if nofcurrentcontent > 0 then
+ pushcontent()
+ pushentry(currentnesting) -- ??
+ end
+ pushentry(taglist[at]) -- has an index, todo: flag empty element
+ if trace_export then
+ report_export("%w<!-- processing image tagged %a",currentdepth,last)
+ end
+ last = nil
+ currentparagraph = nil
+ else
+ -- we need to determine an end-of-line
+ collectresults(n.list,n)
+ 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 = n[a_tagged]
+ if a == last then
+ if not somespace[currentcontent[nofcurrentcontent]] then
+ if trace_export then
+ report_export("%w<!-- injecting spacing 8 (kern %p) -->",currentdepth,kern)
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ end
+ elseif a then
+ -- e.g LOGO<space>LOGO
+ if trace_export then
+ report_export("%w<!-- processing kern, threshold %p, tag %s => %s -->",currentdepth,limit,last,a)
+ end
+ last = a
+ pushcontent()
+ if trace_export then
+ report_export("%w<!-- injecting spacing 9 (kern %p) -->",currentdepth,kern)
+ end
+ nofcurrentcontent = nofcurrentcontent + 1
+ currentcontent[nofcurrentcontent] = " "
+ currentnesting = taglist[last]
+ pushentry(currentnesting)
+ currentattribute = last
+ end
+ end
+ end
+ end
+ end
+ p = n
+ end
+end
+
+function nodes.handlers.export(head) -- hooks into the page builder
+ starttiming(treehash)
+ if trace_export then
+ report_export("%w<!-- start flushing page -->",currentdepth)
+ end
+ -- continueexport()
+ restart = true
+ collectresults(head)
+ if trace_export then
+ report_export("%w<!-- stop flushing page -->",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
+ n[a_textblock] = noftextblocks
+ elseif subtype == glue_code or subtype == kern_code then
+ 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 sortedhash(used) do
+ result[#result+1] = format("/* category: %s */",element)
+ for detail, nature in sortedhash(details) do
+ local d = displaymapping[nature or "display"] or "block"
+ if detail == "" then
+ result[#result+1] = formatters[e_template](element,d)
+ else
+ result[#result+1] = formatters[d_template](element,detail,d)
+ end
+ end
+ end
+ 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
+
+-- 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
+ -- 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)
+ --
+ 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 %a",xmlfile)
+ io.savedata(xmlfile,results)
+ --
+ report_export("saving css image definitions in %a",imagefilename)
+ io.savedata(imagefilename,allusedimages(xmlfile))
+ --
+ report_export("saving css style definitions in %a",stylefilename)
+ io.savedata(stylefilename,allusedstyles(xmlfile))
+ --
+ report_export("saving css template in %a",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 %a",xhtmlfile)
+ local xmltree = cleanxhtmltree(xml.convert(results))
+ xml.save(xmltree,xhtmlfile)
+ -- looking at identity is somewhat redundant as we also inherit from interaction
+ -- at the tex end
+ local identity = interactions.general.getidentity()
+ local specification = {
+ name = file.removesuffix(v),
+ identifier = os.uuid(),
+ images = uniqueusedimages(),
+ root = xhtmlfile,
+ files = files,
+ language = languagenames[tex.count.mainlanguagenumber],
+ title = validstring(finetuning.title) or validstring(identity.title),
+ subtitle = validstring(finetuning.subtitle) or validstring(identity.subtitle),
+ author = validstring(finetuning.author) or validstring(identity.author),
+ firstpage = validstring(finetuning.firstpage),
+ lastpage = validstring(finetuning.lastpage),
+ }
+ report_export("saving specification in %a (mtxrun --script epub --make %s)",specificationfilename,specificationfilename)
+ io.savedata(specificationfilename,table.serialize(specification,true))
+ end
+ stoptiming(treehash)
+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, 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
|