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"
}
-- Todo: share properties more with tagged pdf (or thge reverse)
-- Because we run into the 200 local limit we quite some do .. end wrappers .. not always
-- that nice but it has to be.
-- Experiments demonstrated that mapping to
and classes is messy because we have to
-- package attributes (some 30) into one set of (space seperatated but prefixed classes)
-- which only makes things worse .. so if you want something else, use xslt to get there.
-- 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
-- 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)
-- problem : too many local variables
-- check setting __i__
local next, type, tonumber = next, type, tonumber
local sub, gsub, match = string.sub, string.gsub, string.match
local validstring = string.valid
local lpegmatch = lpeg.match
local utfchar, utfvalues, utflen = utf.char, utf.values, utf.len
local concat, insert, remove, merge, sort = table.concat, table.insert, table.remove, table.merge, table.sort
local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
local formatters = string.formatters
local todimen = number.todimen
local replacetemplate = utilities.templates.replace
local addsuffix, joinfile, nameonly, basename, filesuffix = file.addsuffix, file.join, file.nameonly, file.basename, file.suffix
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 trace_details = false trackers.register ("export.trace.details", function(v) trace_details = 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)
show_comment = false -- figure out why break comment
-- 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 v_yes = variables.yes
local v_no = variables.no
local v_xml = variables.xml
local v_hidden = variables.hidden
local implement = interfaces.implement
local included = backends.included
local settings_to_array = utilities.parsers.settings_to_array
local settings_to_hash = utilities.parsers.settings_to_hash
local setmetatableindex = table.setmetatableindex
local tasks = nodes.tasks
local fontchar = fonts.hashes.characters
local fontquads = fonts.hashes.quads
local languagenames = languages.numbers
local texgetcount = tex.getcount
local references = structures.references
local structurestags = structures.tags
local taglist = structurestags.taglist
local specifications = structurestags.specifications
local properties = structurestags.properties
local locatedtag = structurestags.locatedtag
structurestags.usewithcare = { }
local starttiming = statistics.starttiming
local stoptiming = statistics.stoptiming
local characterdata = characters.data
local overloads = fonts.mappings.overloads
-- todo: more locals (and optimize)
local exportversion = "0.35"
local mathmlns = "http://www.w3.org/1998/Math/MathML"
local contextns = "http://www.contextgarden.net/context/export" -- whatever suits
local cssnamespaceurl = "@namespace context url('%namespace%') ;"
local cssnamespace = "context|"
----- cssnamespacenop = "/* no namespace */"
local usecssnamespace = false
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
----- hyphencode = 0xAD
local hyphen = utfchar(0xAD) -- todo: also emdash etc
local tagsplitter = structurestags.patterns.splitter
----- colonsplitter = lpeg.splitat(":")
----- dashsplitter = lpeg.splitat("-")
local threshold = 65536
local indexing = false
local keephyphens = false
local exportproperties = false
local finetuning = { }
local treestack = { }
local nesting = { }
local currentdepth = 0
local wrapups = { }
local tree = { data = { }, fulltag == "root" } -- root
local treeroot = tree
local treehash = { }
local extras = { }
local checks = { }
local fixes = { }
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 p_entity = lpeg.replacer(entities) -- was: entityremapper = utf.remapper(entities)
local p_attribute = lpeg.replacer(attribentities)
local p_stripper = lpeg.patterns.stripper
local p_escaped = lpeg.patterns.xml.escaped
local f_tagid = formatters["%s-%04i"]
-- local alignmapping = {
-- flushright = "right",
-- middle = "center",
-- flushleft = "left",
-- }
local defaultnature = "mixed" -- "inline"
setmetatableindex(used, function(t,k)
if k then
local v = { }
t[k] = v
return v
end
end)
local f_entity = formatters["%X;"]
local f_attribute = formatters[" %s=%q"]
local f_property = formatters[" %s%s=%q"]
setmetatableindex(specialspaces, function(t,k)
local v = utfchar(k)
t[k] = v
entities[v] = f_entity(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",
-- only when testing
mstacker = "m",
mstackertop = "m",
mstackermid = "m",
mstackerbot = "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 f_attribute(key,lpegmatch(p_attribute,value))
else
return ""
end
end
local function setattribute(di,key,value,escaped)
if value and value ~= "" then
local a = di.attributes
if escaped then
value = lpegmatch(p_escaped,value)
end
if not a then
di.attributes = { [key] = value }
else
a[key] = value
end
end
end
local listdata = { } -- this has to be done otherwise: each element can just point back to ...
function wrapups.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
local t = m.kind .. ">" .. tag -- todo: use internal (see strc-lst.lua where it's set)
listdata[t] = ci
end
end
end
function structurestags.setattributehash(attr,key,value) -- public hash
local specification = taglist[attr]
if specification then
specification[key] = value
else
-- some kind of error
end
end
local usedstyles = { }
local namespacetemplate = [[
/* %what% for file %filename% */
%cssnamespaceurl%
]]
do
-- experiment: styles and images
--
-- officially we should convert to bp but we round anyway
-- /* padding : ; */
-- /* text-justify : inter-word ; */
-- /* text-align : justify ; */
local documenttemplate = [[
document,
%namespace%div.document {
font-size : %size% !important ;
max-width : %width% !important ;
text-align : %align% !important ;
hyphens : %hyphens% !important ;
}]]
local styletemplate = [[
%element%[detail="%detail%"],
%namespace%div.%element%.%detail% {
display : inline ;
font-style : %style% ;
font-variant : %variant% ;
font-weight : %weight% ;
font-family : %family% ;
color : %color% ;
}]]
local numbertoallign = {
[0] = "justify", ["0"] = "justify", [variables.normal ] = "justify",
"right", ["1"] = "right", [variables.flushright] = "right",
"center", ["2"] = "center", [variables.middle ] = "center",
"left", ["3"] = "left", [variables.flushleft ] = "left",
}
function wrapups.allusedstyles(filename)
local result = { replacetemplate(namespacetemplate, {
what = "styles",
filename = filename,
namespace = contextns,
-- cssnamespaceurl = usecssnamespace and cssnamespaceurl or cssnamespacenop,
cssnamespaceurl = cssnamespaceurl,
},false,true) }
--
local bodyfont = finetuning.bodyfont
local width = finetuning.width
local hyphen = finetuning.hyphen
local align = finetuning.align
--
if type(bodyfont) == "number" then
bodyfont = todimen(bodyfont)
else
bodyfont = "12pt"
end
if type(width) == "number" then
width = todimen(width) or "50em"
else
width = "50em"
end
if hyphen == v_yes then
hyphen = "manual"
else
hyphen = "inherited"
end
if align then
align = numbertoallign[align]
end
if not align then
align = hyphen and "justify" or "inherited"
end
--
result[#result+1] = replacetemplate(documenttemplate,{
size = bodyfont,
width = width,
align = align,
hyphens = 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)
detail = gsub(detail,"[^A-Za-z0-9]+","-")
result[#result+1] = replacetemplate(styletemplate,{
namespace = usecssnamespace and cssnamespace or "",
element = element,
detail = detail,
style = s.style or "inherit",
variant = s.variant or "inherit",
weight = s.weight or "inherit",
family = s.family or "inherit",
color = c or "inherit",
display = s.display and "block" or nil,
})
end
end
return concat(result,"\n\n")
end
end
local usedimages = { }
do
local imagetemplate = [[
%element%[id="%id%"], %namespace%div.%element%[id="%id%"] {
display : block ;
background-image : url('%url%') ;
background-size : 100%% auto ;
background-repeat : no-repeat ;
width : %width% ;
height : %height% ;
}]]
local f_svgname = formatters["%s.svg"]
local f_svgpage = formatters["%s-page-%s.svg"]
local collected = { }
local function usedname(name,page)
if filesuffix(name) == "pdf" then
-- temp hack .. we will have a remapper
if page and page > 1 then
name = f_svgpage(nameonly(name),page)
else
name = f_svgname(nameonly(name))
end
end
local scheme = url.hasscheme(name)
if not scheme or scheme == "file" then
-- or can we just use the name ?
return joinfile("../images",basename(url.filename(name)))
else
return name
end
end
function wrapups.allusedimages(filename)
local result = { replacetemplate(namespacetemplate, {
what = "images",
filename = filename,
namespace = contextns,
-- cssnamespaceurl = usecssnamespace and cssnamespaceurl or "",
cssnamespaceurl = cssnamespaceurl,
},false,true) }
for element, details in sortedhash(usedimages) do
for detail, data in sortedhash(details) do
local name = data.name
local page = tonumber(data.page) or 1
local spec = {
element = element,
id = data.id,
name = name,
page = page,
url = usedname(name,page),
width = data.width,
height = data.height,
used = data.used,
namespace = usecssnamespace and cssnamespace or "",
}
result[#result+1] = replacetemplate(imagetemplate,spec)
collected[detail] = spec
end
end
return concat(result,"\n\n")
end
function wrapups.uniqueusedimages() -- todo: combine these two
return collected
end
end
--
properties.vspace = { export = "break", nature = "display" }
----------------- = { export = "pagebreak", nature = "display" }
local function makebreaklist(list)
nofbreaks = nofbreaks + 1
local t = { }
local l = list and list.taglist
if l then
for i=1,#list do
t[i] = l[i]
end
end
t[#t+1] = "break>" .. nofbreaks -- maybe no number or 0
return { taglist = 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
do
local fields = { "title", "subtitle", "author", "keywords", "url", "version" }
local ignoredelements = false
local function checkdocument(root)
local data = root.data
if data then
for i=1,#data do
local di = data[i]
local tg = di.tg
if tg == "noexport" then
local s = specifications[di.fulltag]
local u = s and s.userdata
if u then
local comment = u.comment
if comment then
di.element = "comment"
di.data = { { content = comment } }
u.comment = nil
else
data[i] = false
end
else
data[i] = false
end
elseif di.content then
-- okay
elseif tg == "ignore" then
di.element = ""
checkdocument(di)
elseif ignoredelements and ignoredelements[tg] then
di.element = ""
checkdocument(di)
else
checkdocument(di) -- new, else no noexport handling
end
end
end
end
function extras.document(di,element,n,fulltag)
setattribute(di,"language",languagenames[texgetcount("mainlanguagenumber")])
if not less_state then
setattribute(di,"file",tex.jobname)
if included.date then
setattribute(di,"date",os.fulltime())
end
setattribute(di,"context",environment.version)
setattribute(di,"version",exportversion)
setattribute(di,"xmlns:m",mathmlns)
local identity = interactions.general.getidentity()
for i=1,#fields do
local key = fields[i]
local value = identity[key]
if value and value ~= "" then
setattribute(di,key,value)
end
end
end
checkdocument(di)
end
implement {
name = "ignoretagsinexport",
arguments = "string",
actions = function(list)
for tag in string.gmatch(list,"[a-z]+") do
if ignoredelements then
ignoredelements[tag] = true
else
ignoredelements = { [tag] = true }
end
end
end,
}
end
do
local marginanchors = { }
local margincontent = { }
implement {
name = "settagmargintext",
arguments = "integer",
actions = function(n)
marginanchors[locatedtag("margintext")] = n
end
}
implement {
name = "settagmarginanchor",
arguments = "integer",
actions = function(n)
marginanchors[locatedtag("marginanchor")] = n
end
}
function checks.margintext(di)
local i = marginanchors[di.fulltag]
margincontent[i] = di
end
function checks.marginanchor(di)
local i = marginanchors[di.fulltag]
local d = margincontent[i]
--
di.attribute = d.attribute
di.data = d.data
di.detail = d.detail
di.element = d.element
di.fulltag = d.fulltag
di.nature = d.nature
di.samepar = true
di.tg = d.tg
--
d.skip = "ignore"
end
end
do
local symbols = { }
function structurestags.settagdelimitedsymbol(symbol)
symbols[locatedtag("delimitedsymbol")] = {
symbol = symbol,
}
end
function extras.delimitedsymbol(di,element,n,fulltag)
local hash = symbols[fulltag]
if hash then
setattribute(di,"symbol",hash.symbol or nil)
end
end
end
do
local symbols = { }
function structurestags.settagsubsentencesymbol(symbol)
symbols[locatedtag("subsentencesymbol")] = {
symbol = symbol,
}
end
function extras.subsentencesymbol(di,element,n,fulltag)
local hash = symbols[fulltag]
if hash then
setattribute(di,"symbol",hash.symbol or nil)
end
end
end
do
local itemgroups = { }
function structurestags.setitemgroup(packed,level,symbol)
itemgroups[locatedtag("itemgroup")] = {
packed = packed,
symbol = symbol,
level = level,
}
end
function structurestags.setitem(kind)
itemgroups[locatedtag("item")] = {
kind = kind,
}
end
function extras.itemgroup(di,element,n,fulltag)
local hash = itemgroups[fulltag]
if hash then
setattribute(di,"packed",hash.packed and "yes" or nil)
setattribute(di,"symbol",hash.symbol)
setattribute(di,"level",hash.level)
end
end
function extras.item(di,element,n,fulltag)
local hash = itemgroups[fulltag]
if hash then
local kind = hash.kind
if kind and kind ~= "" then
setattribute(di,"kind",kind)
end
end
end
end
do
function fixes.linenumber(di,data,i)
local ni = data[i+1]
if ni then
if ni.data then
while true do
local d = ni.data[1]
if d then
local e = d.element
if e then
if e == "line" or e == "verbatimline" then
insert(d.data,1,di)
data[i] = false
return
else
ni = d
end
else
return
end
else
return
end
end
end
end
end
end
do
local synonyms = { }
local sortings = { }
function structurestags.setsynonym(tag)
synonyms[locatedtag("synonym")] = tag
end
function extras.synonym(di,element,n,fulltag)
local tag = synonyms[fulltag]
if tag then
setattribute(di,"tag",tag)
end
end
function structurestags.setsorting(tag)
sortings[locatedtag("sorting")] = tag
end
function extras.sorting(di,element,n,fulltag)
local tag = sortings[fulltag]
if tag then
setattribute(di,"tag",tag)
end
end
end
do
local strippedtag = structurestags.strip -- we assume global styles
local highlight = { }
local construct = { }
usedstyles.highlight = highlight
usedstyles.construct = construct
function structurestags.sethighlight(name,style,color,mode)
if not highlight[name] then
highlight[name] = {
style = style,
color = color,
mode = mode == 1 and "display" or nil,
}
end
end
function structurestags.setconstruct(name,style,color,mode)
if not construct[name] then
construct[name] = {
style = style,
color = color,
mode = mode == 1 and "display" or nil,
}
end
end
end
do
local descriptions = { }
local symbols = { }
local linked = { }
-- we could move the notation itself to the first reference (can be an option)
function structurestags.setnotation(tag,n) -- needs checking (is tag needed)
-- we can also use the internals hash or list
local nd = structures.notes.get(tag,n)
if nd then
local references = nd.references
descriptions[references and references.internal] = locatedtag("description")
end
end
function structurestags.setnotationsymbol(tag,n) -- needs checking (is tag needed)
local nd = structures.notes.get(tag,n) -- todo: use listdata instead
if nd then
local references = nd.references
symbols[references and references.internal] = locatedtag("descriptionsymbol")
end
end
function finalizers.descriptions(tree)
local n = 0
for id, tag in sortedhash(descriptions) do
local sym = symbols[id]
if sym then
n = n + 1
linked[tag] = n
linked[sym] = n
end
end
end
function extras.description(di,element,n,fulltag)
local id = linked[fulltag]
if id then
setattribute(di,"insert",id)
end
end
function extras.descriptionsymbol(di,element,n,fulltag)
local id = linked[fulltag]
if id then
setattribute(di,"insert",id)
end
end
end
-- -- todo: ignore breaks
--
-- function extras.verbatimline(di,element,n,fulltag)
-- inspect(di)
-- end
do
local f_id = formatters["%s-%s"]
local image = { }
usedimages.image = image
structurestags.usewithcare.images = image
function structurestags.setfigure(name,used,page,width,height,label)
local fulltag = locatedtag("image")
local spec = specifications[fulltag]
if spec then
local page = tonumber(page)
image[fulltag] = {
id = f_id(spec.tagname,spec.tagindex),
name = name,
used = used,
page = page and page > 1 and page or nil,
width = todimen(width, "cm","%0.3F%s"),
height = todimen(height,"cm","%0.3F%s"),
label = label,
}
else
-- we ignore images in layers in the background / pagebody
end
end
function extras.image(di,element,n,fulltag)
local data = image[fulltag]
if data then
setattribute(di,"name",data.name)
setattribute(di,"page",data.page)
setattribute(di,"id",data.id)
setattribute(di,"width",data.width)
setattribute(di,"height",data.height)
setattribute(di,"label",data.height)
end
end
end
do
local combinations = { }
function structurestags.setcombination(nx,ny)
combinations[locatedtag("combination")] = {
nx = nx,
ny = ny,
}
end
function extras.combination(di,element,n,fulltag)
local data = combinations[fulltag]
if data then
setattribute(di,"nx",data.nx)
setattribute(di,"ny",data.ny)
end
end
end
-- quite some code deals with exporting references --
-- links:
--
-- url :
-- file :
-- internal : automatic location
-- location : named reference
-- references:
--
-- implicit : automatic reference
-- explicit : named reference
local evaluators = { }
local specials = { }
local explicits = { }
evaluators.inner = function(di,var)
local inner = var.inner
if inner then
setattribute(di,"location",inner,true)
end
end
evaluators.outer = function(di,var)
local file, url = references.checkedfileorurl(var.outer,var.outer)
if url then
setattribute(di,"url",url,true)
elseif file then
setattribute(di,"file",file,true)
end
end
evaluators["outer with inner"] = function(di,var)
local file = references.checkedfile(var.f)
if file then
setattribute(di,"file",file,true)
end
local inner = var.inner
if inner then
setattribute(di,"inner",inner,true)
end
end
evaluators.special = function(di,var)
local handler = specials[var.special]
if handler then
handler(di,var)
end
end
local referencehash = { }
local destinationhash = { }
do
evaluators["special outer with operation"] = evaluators.special
evaluators["special operation"] = evaluators.special
evaluators["special operation with arguments"] = evaluators.special
function specials.url(di,var)
local url = references.checkedurl(var.operation)
if url and url ~= "" then
setattribute(di,"url",url,true)
end
end
function specials.file(di,var)
local file = references.checkedfile(var.operation)
if file and file ~= "" then
setattribute(di,"file",file,true)
end
end
function specials.fileorurl(di,var)
local file, url = references.checkedfileorurl(var.operation,var.operation)
if url and url ~= "" then
setattribute(di,"url",url,true)
elseif file and file ~= "" then
setattribute(di,"file",file,true)
end
end
function specials.internal(di,var)
local internal = references.checkedurl(var.operation)
if internal then
setattribute(di,"location",internal)
end
end
local function adddestination(di,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
setattribute(di,"prefix",prefix,true)
end
setattribute(di,"destination",reference,true)
for i=1,#references do
local r = references[i]
local e = evaluators[r.kind]
if e then
e(di,r)
end
end
end
end
end
function extras.addimplicit(di,references)
if references then
local internal = references.internal
if internal then
setattribute(di,"implicit",internal)
end
end
end
function extras.addinternal(di,references)
if references then
local internal = references.internal
if internal then
setattribute(di,"internal",internal)
end
end
end
local p_firstpart = lpeg.Cs((1-lpeg.P(","))^0)
local function addreference(di,references)
if references then
local reference = references.reference
if reference and reference ~= "" then
local prefix = references.prefix
if prefix and prefix ~= "" then
setattribute(di,"prefix",prefix)
end
setattribute(di,"reference",reference,true)
setattribute(di,"explicit",lpegmatch(p_firstpart,reference),true)
end
local internal = references.internal
if internal and internal ~= "" then
setattribute(di,"implicit",internal)
end
end
end
local function link(di,element,n,fulltag)
-- for instance in lists a link has nested elements and no own text
local reference = referencehash[fulltag]
if reference then
adddestination(di,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 link(di,element,n,fulltag) then
return true
end
end
end
end
end
end
local function reference(di,element,n,fulltag)
local destination = destinationhash[fulltag]
if destination then
local d = structures.references.internals[destination]
if d then
addreference(di,d.references)
return true
else
return false
end
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 reference(di,element,n,fulltag) then
return true
end
end
end
end
end
end
extras.adddestination = adddestination
extras.addreference = addreference
extras.link = link
extras.reference = reference
end
-- no settings, as these are obscure ones
do
local automathrows = true directives.register("export.math.autorows", function(v) automathrows = v end)
local automathapply = true directives.register("export.math.autoapply", function(v) automathapply = v end)
local automathnumber = true directives.register("export.math.autonumber", function(v) automathnumber = v end)
local automathstrip = true directives.register("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)
-- this is tricky ... we need to make sure that we wrap in mrows if we want
-- to bypass this one
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 dummy_nucleus = {
element = "mtext",
data = { content = "" },
nature = "inline",
comment = "dummy nucleus",
fulltag = "mtext>0"
}
local function accentchar(d)
for i=1,3 do
d = d.data
if not d then
return
end
d = d[1]
if not d then
return
end
local tg = d.tg
if tg == "mover" then
local s = specifications[d.fulltag]
local t = s.top
if t then
d = d.data[1]
local d1 = d.data[1]
d1.content = utfchar(t)
d.data = { d1 }
return d
end
elseif tg == "munder" then
local s = specifications[d.fulltag]
local b = s.bottom
if b then
d = d.data[1]
local d1 = d.data[1]
d1.content = utfchar(b)
d.data = { d1 }
return d
end
end
end
end
local no_mrow = {
mrow = true,
mfenced = true,
mfrac = true,
mroot = true,
msqrt = true,
mtable = true,
mi = true,
mo = true,
mn = true,
}
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
-- kind of tricky: we have a diufferent order in display mode
local nucleus, superscript, subscript
if ndata > 3 then
-- error
else
for i=1,ndata do
local di = data[i]
if not di then
-- weird
elseif di.content then
-- text
else
local s = specifications[di.fulltag]
if s.subscript then
subscript = i
elseif s.superscript then
superscript = i
else
nucleus = i
end
end
end
if superscript or subscript then
-- we probably always have 3 anyway ... needs checking
local nuc = nucleus and data[nucleus]
local sub = subscript and data[subscript]
local sup = superscript and data[superscript]
local n = 0 -- play safe
if nuc then n = n + 1 ; data[n] = nuc end
if sub then n = n + 1 ; data[n] = sub end
if sup then n = n + 1 ; data[n] = sup end
end
end
-- elseif roottg == "msup" or roottg == "msub" then
-- -- m$^2$
-- if ndata == 1 then
-- local d = data[1]
-- data[2] = d
-- d.__i__ = 2
-- data[1] = dummy_nucleus
-- end
elseif roottg == "mfenced" then
local s = specifications[root.fulltag]
local l, m, r = s.left, s.middle, s.right
if l then
l = utfchar(l)
end
if m then
local t = { }
for i=1,#m do
t[i] = utfchar(m[i])
end
m = concat(t)
end
if r then
r = utfchar(r)
end
root.attributes = {
open = l,
separators = m,
close = r,
}
end
if ndata == 0 then
root.skip = "comment" -- get rid of weird artefacts
root.nota = "weird"
return
elseif ndata == 1 then
local d = data[1]
if not d or d == "" then
root.skip = "comment"
return
elseif d.content then
return
else -- if ndata == 1 then
local tg = d.tg
if automathrows and (roottg == "mrow" or roottg == "mtext") then
-- maybe just always ! check spec first
-- or we can have chesks.* for each as we then can flatten
if no_mrow[tg] 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
if tg == "math" then
-- di.element = "mrow" -- when properties
di.skip = "comment"
checkmath(di)
i = i + 1
elseif tg == "mover" then
local s = specifications[di.fulltag]
if s.accent then
local t = s.top
local d = di.data
-- todo: accent = "false" (for scripts like limits)
di.attributes = {
accent = "true",
}
-- todo: p.topfixed
if t then
-- mover
d[1].data[1].content = utfchar(t)
di.data = { d[2], d[1] }
end
else
-- can't happen
end
checkmath(di)
i = i + 1
elseif tg == "munder" then
local s = specifications[di.fulltag]
if s.accent then
local b = s.bottom
local d = di.data
-- todo: accent = "false" (for scripts like limits)
di.attributes = {
accent = "true",
}
-- todo: p.bottomfixed
if b then
-- munder
d[2].data[1].content = utfchar(b)
end
else
-- can't happen
end
checkmath(di)
i = i + 1
elseif tg == "munderover" then
local s = specifications[di.fulltag]
if s.accent then
local t = s.top
local b = s.bottom
local d = di.data
-- todo: accent = "false" (for scripts like limits)
-- todo: accentunder = "false" (for scripts like limits)
di.attributes = {
accent = "true",
accentunder = "true",
}
-- todo: p.topfixed
-- todo: p.bottomfixed
if t and b then
-- munderover
d[1].data[1].content = utfchar(t)
d[3].data[1].content = utfchar(b)
di.data = { d[2], d[3], d[1] }
else
-- can't happen
end
else
-- can't happen
end
checkmath(di)
i = i + 1
elseif tg == "mstacker" then
local d = di.data
local d1 = d[1]
local d2 = d[2]
local d3 = d[3]
local t1 = d1 and d1.tg
local t2 = d2 and d2.tg
local t3 = d3 and d3.tg
local m = nil -- d1.data[1]
local t = nil
local b = nil
-- only accent when top / bot have stretch
-- normally we flush [base under over] which is better for tagged pdf
if t1 == "mstackermid" then
m = accentchar(d1) -- or m
if t2 == "mstackertop" then
if t3 == "mstackerbot" then
t = accentchar(d2)
b = accentchar(d3)
di.element = "munderover"
di.data = { m or d1.data[1], b or d3.data[1], t or d2.data[1] }
else
t = accentchar(d2)
di.element = "mover"
di.data = { m or d1.data[1], t or d2.data[1] }
end
elseif t2 == "mstackerbot" then
if t3 == "mstackertop" then
b = accentchar(d2)
t = accentchar(d3)
di.element = "munderover"
di.data = { m or d1.data[1], t or d3.data[1], m, b or d2.data[1] }
else
b = accentchar(d2)
di.element = "munder"
di.data = { m or d1.data[1], b or d2.data[1] }
end
else
-- can't happen
end
else
-- can't happen
end
if t or b then
di.attributes = {
accent = t and "true" or nil,
accentunder = b and "true" or nil,
}
di.detail = nil
end
checkmath(di)
i = i + 1
elseif tg == "mroot" then
local data = di.data
local size = #data
if size == 1 then
-- else firefox complains ... code in math-tag (for pdf tagging)
di.element = "msqrt"
elseif size == 2 then
data[1], data[2] = data[2], data[1]
end
checkmath(di)
i = i + 1
elseif tg == "break" then
di.skip = "comment"
i = i + 1
elseif tg == "mtext" then
-- this is only needed for unboxed mtexts ... all kind of special
-- tex border cases and optimizations ... trial and error
local data = di.data
if #data > 1 then
for i=1,#data do
local di = data[i]
local content = di.content
if content then
data[i] = {
element = "mtext",
nature = "inline",
data = { di },
n = 0,
}
elseif di.tg == "math" then
local di = di.data[1]
if di then
data[i] = di
checkmath(di)
end
end
end
di.element = "mrow"
-- di.tg = "mrow"
-- di.nature = "inline"
end
checkmath(di)
i = i + 1
elseif tg == "mrow" and detail then -- hm, falls through
di.detail = nil
checkmath(di)
di = {
element = "maction",
nature = "display",
attributes = { actiontype = detail },
data = { di },
n = 0,
}
data[i] = di
i = i + 1
else
local category = di.mathcategory
if category then
-- no checkmath(di) here
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
end
else -- can be string or boolean
if parenttg ~= "mtext" and di == " " then
data[i] = false
end
i = i + 1
end
end
end
end
local 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 d = data[i]
if d and not d.content then
d = stripmath(d)
end
if d then
local content = d.content
if not content then
n = n + 1
d.__i__ = n
data[n] = d
elseif content == " " or content == "" then
if d.tg == "mspace" then
-- we append or prepend a space to a preceding or following mtext
local parent = di.__p__
local index = di.__i__ -- == i
local data = parent.data
if index > 1 then
local d = data[index-1]
if d.tg == "mtext" then
local dd = d.data
local dn = dd[#dd]
local dc = dn.content
if dc then
dn.content = dc .. content
end
end
elseif index < ndata then
local d = data[index+1]
if d.tg == "mtext" then
local dd = d.data
local dn = dd[1]
local dc = dn.content
if dc then
dn.content = content .. dc
end
end
end
end
else
n = n + 1
data[n] = d
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)
if di.skip == "comment" then
-- already done, kind of weird, happens in mathmatrix, maybe some collapse
-- issue that i need to look into
else
local specification = specifications[di.fulltag]
local mode = specification and specification.mode == "display" and "block" or "inline"
di.attributes = {
["display"] = mode,
["xmlns:m"] = mathmlns,
}
-- can be option if needed:
if mode == "inline" then
-- di.nature = "mixed" -- else spacing problem (maybe inline)
di.nature = "inline" -- we need to catch x$X$x and x $X$ x
else
di.nature = "display"
end
if automathstrip then
stripmath(di)
end
checkmath(di)
end
end
-- this one can replace some of the previous code .. todo (test on mathmatrix)
-- ignore with no data can be removed
local function checked(d)
local n = #d
if n == 1 then
local di = d[1]
local tg = di.tg
if tg == "ignore" then
-- todo: we can move ignore's data one level up
return 1
elseif di.content then
return 1
else
local dd = di.data
if #dd > 0 and checked(dd) > 0 then
return 1
else
return 0
end
end
else
local m = 0
for i=1,n do
local di = d[i]
local tg = di.tg
if tg == "ignore" then
-- skip
elseif di.content then
m = m + 1
d[m] = di
else
local dd = di.data
if #dd > 0 and checked(dd) > 0 then
m = m + 1
d[m] = di
end
end
end
if m < n then
for i=n,m+1,-1 do
d[i] = nil
end
end
return m
end
end
function checks.mrow(di)
-- local d = di.data
-- if d then
-- checked(d)
-- end
end
-- we can move more checks here
local function flatten(di)
local r = di.__p__
while r do
local d = r.data
local n = #d
if d and n > 1 then
n = checked(d)
end
local tg = r.tg
if n == 1 and (tg == "mtext" or tg == "mrow") then
r.skip = "comment" -- weird error
r = r.__p__
else
break
end
end
end
function checks.mtable(di)
flatten(di)
local d = di.data
for i=1,#d do
local d = d[i]
if d.tg == "mtr" then
local d = d.data
for i=1,#d do
local d = d[i]
if d.tg == "mtd" then
-- okay
elseif d.content then
d.content = ""
else
d.skip = "comment" -- weird error
end
end
elseif d.content then
d.content = ""
else
d.skip = "comment" -- weird error
end
end
end
do
local a, z, A, Z = 0x61, 0x7A, 0x41, 0x5A
function extras.mi(di,element,n,fulltag) -- 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
end
function extras.msub(di,element,n,fulltag)
-- m$^2$
local data = di.data
if #data == 1 then
local d = data[1]
data[2] = d
d.__i__ = 2
data[1] = dummy_nucleus
end
end
extras.msup = extras.msub
end
do
local registered = { }
function structurestags.setformulacontent(n)
registered[locatedtag("formulacontent")] = {
n = n,
}
end
function extras.formulacontent(di,element,n,fulltag)
local r = registered[fulltag]
if r then
setattribute(di,"n",r.n)
end
end
end
do
local registered = structures.sections.registered
local function resolve(di,element,n,fulltag)
local data = listdata[fulltag]
if data then
extras.addreference(di,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 resolve(di,element,n,ft) then
return true
end
end
end
end
end
end
function extras.section(di,element,n,fulltag)
local r = registered[specifications[fulltag].detail]
if r then
setattribute(di,"level",r.level)
end
resolve(di,element,n,fulltag)
end
local floats = { }
function structurestags.setfloat(options,method)
floats[locatedtag("float")] = {
options = options,
method = method,
}
end
function extras.float(di,element,n,fulltag)
local hash = floats[fulltag]
if hash then
local method = hash.method
if not method or method == "" then
method = "here"
end
setattribute(di,"method",method)
local options = hash.options
if options and options ~= "" then
options = settings_to_hash(options)
options[method] = nil
options = concat(sortedkeys(options),",")
if #options > 0 then
setattribute(di,"options",options)
end
end
end
resolve(di,element,n,fulltag)
end
-- todo: internal is already hashed
function structurestags.setlist(n)
local data = structures.lists.getresult(n)
if data then
referencehash[locatedtag("listitem")] = data
end
end
function extras.listitem(di,element,n,fulltag)
local data = referencehash[fulltag]
if data then
extras.addinternal(di,data.references)
return true
end
end
end
do
-- todo: internal is already hashed
function structurestags.setregister(tag,n) -- check if tag is needed
local data = structures.registers.get(tag,n)
if data then
referencehash[locatedtag("registerlocation")] = data
end
end
function extras.registerlocation(di,element,n,fulltag)
local data = referencehash[fulltag]
if type(data) == "table" then
extras.addinternal(di,data.references)
return true
else
-- needs checking, probably bookmarks
end
end
function extras.registerpages(di,element,n,fulltag) -- ignorebreaks
local data = di.data
for i=1,#data do
local d = data[i]
if d.content == " " then
d.content = ""
end
end
end
function extras.registerseparator(di,element,n,fulltag) -- ignorespaces
local data = di.data
for i=1,#data do
local d = data[i]
local c = d.content
if type(c) == "string" then
d.content = lpegmatch(p_stripper,c)
end
end
end
end
do
local tabledata = { }
local function hascontent(data)
for i=1,#data do
local di = data[i]
if not di or di.tg == "ignore" then
--
else
local content = di.content
if content == " " then
--
elseif content then
return true
else
local d = di.data
if d and #d > 0 and hascontent(d) then
return true
end
end
end
end
end
function structurestags.settablecell(rows,columns,align)
if align > 0 or rows > 1 or columns > 1 then -- or kind > 0
tabledata[locatedtag("tablecell")] = {
rows = rows,
columns = columns,
align = align,
}
end
end
function structurestags.gettablecell(fulltag)
return tabledata[fulltag]
end
function extras.tablecell(di,element,n,fulltag)
local hash = tabledata[fulltag]
if hash then
local columns = hash.columns
if columns and columns > 1 then
setattribute(di,"columns",columns)
end
local rows = hash.rows
if rows and rows > 1 then
setattribute(di,"rows",rows)
end
local align = hash.align
if not align or align == 0 then
-- normal
elseif align == 1 then -- use numbertoalign here
setattribute(di,"align","flushright")
elseif align == 2 then
setattribute(di,"align","middle")
elseif align == 3 then
setattribute(di,"align","flushleft")
end
end
end
local tabulatedata = { }
function structurestags.settabulatecell(align,kind)
if align > 0 or kind > 0 then
tabulatedata[locatedtag("tabulatecell")] = {
align = align,
kind = kind, -- 1 = bold head
}
end
end
function structurestags.gettabulatecell(fulltag)
return tabulatedata[fulltag]
end
function extras.tabulate(di,element,n,fulltag)
local data = di.data
for i=1,#data do
local di = data[i]
if di.tg == "tabulaterow" and not hascontent(di.data) then
di.element = "" -- or simply remove
end
end
end
function extras.tabulatecell(di,element,n,fulltag)
local hash = tabulatedata[fulltag]
if hash then
local align = hash.align
if not align or align == 0 then
-- normal
elseif align == 1 then
setattribute(di,"align","flushleft")
elseif align == 2 then
setattribute(di,"align","flushright")
elseif align == 3 then
setattribute(di,"align","middle")
end
local kind = hash.kind
if kind == 1 then
setattribute(di,"kind","strong")
elseif kind == 2 then
setattribute(di,"kind","equals")
end
end
end
end
do
local usedpublications = { }
local tagsindatasets = setmetatableindex("table")
local serialize = false
function structurestags.setpublication(dataset,tag,rendering)
usedpublications[locatedtag("publication")] = {
dataset = dataset,
tag = tag,
rendering = rendering
}
tagsindatasets[dataset][tag] = true
if not serialize then
structures.tags.registerextradata("btx",function()
local t = { ""}
for dataset, used in sortedhash(tagsindatasets) do
t[#t+1] = publications.converttoxml(dataset,true,false,true,false,true,true)
end
t[#t+1] = ""
return concat(t,"\n")
end)
end
end
function extras.publication(di,element,n,fulltag)
local hash = usedpublications[fulltag]
if hash then
setattribute(di,"dataset",hash.dataset)
setattribute(di,"tag",hash.tag)
end
end
end
do
local usedparagraphs = { }
function structurestags.setparagraph(align)
if align ~= "" then
usedparagraphs[locatedtag("paragraph")] = {
align = align,
}
end
end
function extras.paragraph(di,element,n,fulltag)
local hash = usedparagraphs[fulltag]
if hash then
setattribute(di,"align",hash.align)
end
end
end
-- flusher
do
local f_detail = formatters[' detail="%s"']
local f_chain = formatters[' chain="%s"']
local f_index = formatters[' n="%s"']
local f_spacing = formatters['%s']
local f_empty_inline = formatters["<%s/>"]
local f_empty_mixed = formatters["%w<%s/>\n"]
local f_empty_display = formatters["\n%w<%s/>\n"]
local f_empty_inline_attr = formatters["<%s%s/>"]
local f_empty_mixed_attr = formatters["%w<%s%s/>"]
local f_empty_display_attr = formatters["\n%w<%s%s/>\n"]
local f_begin_inline = formatters["<%s>"]
local f_begin_mixed = formatters["%w<%s>"]
local f_begin_display = formatters["\n%w<%s>\n"]
local f_begin_inline_attr = formatters["<%s%s>"]
local f_begin_mixed_attr = formatters["%w<%s%s>"]
local f_begin_display_attr = formatters["\n%w<%s%s>\n"]
local f_end_inline = formatters["%s>"]
local f_end_mixed = formatters["%s>\n"]
local f_end_display = formatters["%w%s>\n"]
local f_begin_inline_comment = formatters["<%s>"]
local f_begin_mixed_comment = formatters["%w<%s>"]
local f_begin_display_comment = formatters["\n%w\n%w<%s>\n"]
local f_begin_inline_attr_comment = formatters["<%s%s>"]
local f_begin_mixed_attr_comment = formatters["%w<%s%s>"]
local f_begin_display_attr_comment = formatters["\n%w\n%w<%s%s>\n"]
local f_comment_begin_inline = formatters[""]
local f_comment_begin_mixed = formatters["%w"]
local f_comment_begin_display = formatters["\n%w\n"]
local f_comment_end_inline = formatters[""]
local f_comment_end_mixed = formatters["\n"]
local f_comment_end_display = formatters["%w\n"]
local f_metadata_begin = formatters["\n%w\n"]
local f_metadata = formatters["%w%s\n"]
local f_metadata_end = formatters["%w\n"]
local function attributes(a)
local r = { }
local n = 0
for k, v in next, a do
n = n + 1
r[n] = f_attribute(k,tostring(v)) -- tostring because of %q
end
sort(r)
return concat(r,"")
end
local function properties(a)
local r = { }
local n = 0
for k, v in next, a do
n = n + 1
r[n] = f_property(exportproperties,k,tostring(v)) -- tostring because of %q
end
sort(r)
return concat(r,"")
end
local depth = 0
local inline = 0
local function emptytag(result,element,nature,di) -- currently only break but at some point
local a = di.attributes -- we might add detail etc
if a then -- happens seldom
if nature == "display" then
result[#result+1] = f_empty_display_attr(depth,namespaced[element],attributes(a))
elseif nature == "mixed" then
result[#result+1] = f_empty_mixed_attr(depth,namespaced[element],attributes(a))
else
result[#result+1] = f_empty_inline_attr(namespaced[element],attributes(a))
end
else
if nature == "display" then
result[#result+1] = f_empty_display(depth,namespaced[element])
elseif nature == "mixed" then
result[#result+1] = f_empty_mixed(depth,namespaced[element])
else
result[#result+1] = f_empty_inline(namespaced[element])
end
end
end
-- local function stripspaces(di)
-- local d = di.data
-- local n = #d
-- local m = 0
-- for i=1,n do
-- local di = d[i]
-- if di.tg then
-- m = m + 1
-- d[m] = di
-- end
-- end
-- for i=n,m+1,-1 do
-- d[i] = nil
-- end
-- end
--
-- -- simpler:
local function stripspaces(di)
local d = di.data
for i=1,#d do
local di = d[i]
if not di.tg then
di.content = ""
end
end
end
local function begintag(result,element,nature,di,skip)
local index = di.n
local fulltag = di.fulltag
local specification = specifications[fulltag] or { } -- we can have a dummy
local comment = di.comment
local detail = specification.detail
if skip == "comment" then
if show_comment then
if nature == "inline" or inline > 0 then
result[#result+1] = f_comment_begin_inline(namespaced[element])
inline = inline + 1
elseif nature == "mixed" then
result[#result+1] = f_comment_begin_mixed(depth,namespaced[element])
depth = depth + 1
inline = 1
else
result[#result+1] = f_comment_begin_display(depth,namespaced[element])
depth = depth + 1
end
end
elseif skip then
-- ignore
else
local n = 0
local r = { } -- delay this
if detail then
detail = gsub(detail,"[^A-Za-z0-9]+","-")
specification.detail = detail -- we use it later in for the div
n = n + 1
r[n] = f_detail(detail)
end
local parents = specification.parents
if parents then
parents = gsub(parents,"[^A-Za-z0-9 ]+","-")
specification.parents = parents -- we use it later in for the div
n = n + 1
r[n] = f_chain(parents)
end
if indexing and index then
n = n + 1
r[n] = f_index(index)
end
--
local extra = extras[element]
if extra then
extra(di,element,index,fulltag)
end
--
if di.record then
stripspaces(di)
end
--
if exportproperties then
local p = specification.userdata
if not p then
-- skip
elseif exportproperties == v_yes then
n = n + 1
r[n] = attributes(p)
else
n = n + 1
r[n] = properties(p)
end
end
local a = di.attributes
if a then
if trace_spacing then
a.p = di.parnumber or 0
end
n = n + 1
r[n] = attributes(a)
elseif trace_spacing then
n = n + 1
r[n] = attributes { p = di.parnumber or 0 }
end
if n == 0 then
if nature == "inline" or inline > 0 then
if show_comment and comment then
result[#result+1] = f_begin_inline_comment(comment,namespaced[element])
else
result[#result+1] = f_begin_inline(namespaced[element])
end
inline = inline + 1
elseif nature == "mixed" then
if show_comment and comment then
result[#result+1] = f_begin_mixed_comment(depth,comment,namespaced[element])
else
result[#result+1] = f_begin_mixed(depth,namespaced[element])
end
depth = depth + 1
inline = 1
else
if show_comment and comment then
result[#result+1] = f_begin_display_comment(depth,comment,depth,namespaced[element])
else
result[#result+1] = f_begin_display(depth,namespaced[element])
end
depth = depth + 1
end
else
r = concat(r,"",1,n)
if nature == "inline" or inline > 0 then
if show_comment and comment then
result[#result+1] = f_begin_inline_attr_comment(comment,namespaced[element],r)
else
result[#result+1] = f_begin_inline_attr(namespaced[element],r)
end
inline = inline + 1
elseif nature == "mixed" then
if show_comment and comment then
result[#result+1] = f_begin_mixed_attr_comment(depth,comment,namespaced[element],r)
else
result[#result+1] = f_begin_mixed_attr(depth,namespaced[element],r)
end
depth = depth + 1
inline = 1
else
if show_comment and comment then
result[#result+1] = f_begin_display_attr_comment(depth,comment,depth,namespaced[element],r)
else
result[#result+1] = f_begin_display_attr(depth,namespaced[element],r)
end
depth = depth + 1
end
end
end
used[element][detail or ""] = { nature, specification.parents } -- for template css
-- also in last else ?
local metadata = specification.metadata
if metadata then
result[#result+1] = f_metadata_begin(depth)
for k, v in table.sortedpairs(metadata) do
if v ~= "" then
result[#result+1] = f_metadata(depth+1,k,lpegmatch(p_entity,v))
end
end
result[#result+1] = f_metadata_end(depth)
end
end
local function endtag(result,element,nature,di,skip)
if skip == "comment" then
if show_comment then
if nature == "display" and (inline == 0 or inline == 1) then
depth = depth - 1
result[#result+1] = f_comment_end_display(depth,namespaced[element])
inline = 0
elseif nature == "mixed" and (inline == 0 or inline == 1) then
depth = depth - 1
result[#result+1] = f_comment_end_mixed(namespaced[element])
inline = 0
else
inline = inline - 1
result[#result+1] = f_comment_end_inline(namespaced[element])
end
end
elseif skip then
-- ignore
else
if nature == "display" and (inline == 0 or inline == 1) then
depth = depth - 1
result[#result+1] = f_end_display(depth,namespaced[element])
inline = 0
elseif nature == "mixed" and (inline == 0 or inline == 1) then
depth = depth - 1
result[#result+1] = f_end_mixed(namespaced[element])
inline = 0
else
inline = inline - 1
result[#result+1] = f_end_inline(namespaced[element])
end
end
end
local function flushtree(result,data,nature)
local nofdata = #data
for i=1,nofdata do
local di = data[i]
if not di then -- hm, di can be string
-- whatever
else
local content = di.content
-- also optimize for content == "" : trace that first
if content then
-- already has breaks
local content = lpegmatch(p_entity,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] = f_spacing(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] = f_spacing(di.parnumber or 0,content)
else
result[#result+1] = content
end
end
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,di)
elseif element == "" or di.skip == "ignore" then
-- skip
else
if di.before then
flushtree(result,di.before,nature)
end
local natu = di.nature
local skip = di.skip
if di.breaknode then
emptytag(result,"break","display",di)
end
begintag(result,element,natu,di,skip)
flushtree(result,di.data,natu)
endtag(result,element,natu,di,skip)
if di.after then
flushtree(result,di.after,nature)
end
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.skip == "ignore" then
-- skip (new)
elseif di.tg == "ignore" then
-- skip (new)
elseif di.content then
if di.samepar then
prevparnumber = false
else
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
prevparnumber = parnumber
end
prevnature = "inline"
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"
nofnewdata = nofnewdata + 1
newdata[nofnewdata] = di
elseif element == "" or di.skip == "ignore" then
-- skip
else
if di.samepar then
prevnature = "inline"
prevparnumber = false
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
end
prevelement = element
breaktree(di,tree,element)
nofnewdata = nofnewdata + 1
newdata[nofnewdata] = di
end
else
if di.samepar then
prevnature = "inline"
prevparnumber = false
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
end
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(tree)
-- for tag, trees in sortedhash(treehash) do
for tag, trees in next, treehash do
local d = trees[1].data
-- print("!!!!!!!!",tag)
-- inspect(trees)
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.skip == "ignore" 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 showtree(data,when,where)
-- if data then
-- for i=1,#data do
-- local d = data[i]
-- if type(d) == "table" and d.element then
-- print(when,where,i,d.element,d.parnumber or 0)
-- end
-- end
-- end
-- end
local function indextree(tree)
local data = tree.data
if data then
local n, new = 0, { }
-- showtree(data,"before","index")
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
-- showtree(new,"after","index")
end
end
local function checktree(tree)
local data = tree.data
if data then
-- showtree(data,"before","check")
for i=1,#data do
local d = data[i]
if type(d) == "table" then
local check = checks[d.tg]
if check then
check(d,data,i)
end
checktree(d) -- so parts can pass twice
end
end
-- showtree(data,"after","check")
end
end
local function fixtree(tree)
local data = tree.data
if data then
-- showtree(data,"before","fix")
for i=1,#data do
local d = data[i]
if type(d) == "table" then
local fix = fixes[d.tg]
if fix then
fix(d,data,i)
end
fixtree(d) -- so parts can pass twice
end
end
-- showtree(data,"after","fix")
end
end
wrapups.flushtree = flushtree
wrapups.breaktree = breaktree
wrapups.collapsetree = collapsetree
wrapups.finalizetree = finalizetree
wrapups.indextree = indextree
wrapups.checktree = checktree
wrapups.fixtree = fixtree
end
-- collector code
local function push(fulltag,depth)
local tg, n, detail, element, nature, record
local specification = specifications[fulltag]
if specification then
tg = specification.tagname
n = specification.tagindex
detail = specification.detail
else
-- a break (more efficient if we don't store those in specifications)
tg, n = lpegmatch(tagsplitter,fulltag)
n = tonumber(n) -- to tonumber in tagsplitter
end
local p = properties[tg]
if p then
element = p.export or tg
nature = p.nature or "inline" -- defaultnature
record = p.record
end
local treedata = tree.data
local t = { -- maybe we can use the tag table
tg = tg,
fulltag = fulltag,
detail = detail,
n = n, -- already a number
element = element,
nature = nature,
data = { },
attribute = currentattribute,
parnumber = currentparagraph,
record = record, -- we can consider storing properties
}
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=%q n=%q paragraph=%q index=%q detail=%q>",currentdepth-1,tg,n,currentattribute or 0,currentparagraph or 0,#treedata,detail)
else
report_export("%w<%s trigger=%q n=%q paragraph=%q index=%q>",currentdepth-1,tg,n,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()
if currentdepth > 0 then
local top = nesting[currentdepth]
tree = treestack[currentdepth]
currentdepth = currentdepth - 1
if trace_export then
if top then
report_export("%w%s>",currentdepth,match(top,"[^>]+"))
else
report_export("")
end
end
else
report_export("%w",currentdepth)
end
end
local function continueexport()
if nofcurrentcontent > 0 then
if trace_export then
report_export("%w",currentdepth)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " " -- pagebreak
end
end
local function pushentry(current)
if not current then
-- bad news
return
end
current = current.taglist
if not current then
-- even worse news
return
end
if restart then
continueexport()
restart = false
end
local newdepth = #current
local olddepth = currentdepth
if trace_export then
report_export("%w",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",currentdepth,newdepth,nesting[newdepth] or "?")
end
end
return olddepth, newdepth
end
local function pushcontent(oldparagraph,newparagraph)
if nofcurrentcontent > 0 then
if oldparagraph then
if currentcontent[nofcurrentcontent] == "\n" then
if trace_export then
report_export("%w",currentdepth)
end
nofcurrentcontent = nofcurrentcontent - 1
end
end
local content = concat(currentcontent,"",1,nofcurrentcontent)
if content == "" then
-- omit; when oldparagraph we could push, remove spaces, pop
elseif somespace[content] and oldparagraph then
-- omit; when oldparagraph we could push, remove spaces, pop
else
local olddepth, newdepth
local list = taglist[currentattribute]
if list then
olddepth, newdepth = pushentry(list)
end
if tree then
local td = tree.data
local nd = #td
td[nd+1] = { parnumber = oldparagraph or currentparagraph, content = content }
if trace_export then
report_export("%w",currentdepth,utflen(content))
report_export("%w%s",currentdepth,(gsub(content,"\n","\\n")))
report_export("%w",currentdepth)
end
if olddepth then
for i=newdepth-1,olddepth,-1 do
pop()
end
end
end
end
nofcurrentcontent = 0
end
if oldparagraph then
pushentry(makebreaklist(currentnesting))
if trace_export then
report_export("%w",currentdepth,oldparagraph,newparagraph)
end
end
end
local function finishexport()
if trace_export then
report_export("%w",currentdepth)
end
if nofcurrentcontent > 0 then
if somespace[currentcontent[nofcurrentcontent]] then
if trace_export then
report_export("%w",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",currentdepth)
end
end
-- inserts ?
local collectresults do -- too many locals otherwise
local nodecodes = nodes.nodecodes
local gluecodes = nodes.gluecodes
local listcodes = nodes.listcodes
local whatsitcodes = nodes.whatsitcodes
local subtypes = nodes.subtypes
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 whatsit_code = nodecodes.whatsit
local par_code = nodecodes.par
local userskip_code = gluecodes.userskip
local rightskip_code = gluecodes.rightskip
local parfillskip_code = gluecodes.parfillskip
local spaceskip_code = gluecodes.spaceskip
local xspaceskip_code = gluecodes.xspaceskip
local linelist_code = listcodes.line
local userdefinedwhatsit_code = whatsitcodes.userdefined
local privateattribute = attributes.private
local a_image = privateattribute('image')
local a_reference = privateattribute('reference')
local a_destination = privateattribute('destination')
local a_characters = privateattribute('characters')
local a_exportstatus = privateattribute('exportstatus')
local a_tagged = privateattribute('tagged')
local a_taggedpar = privateattribute("taggedpar")
local a_textblock = privateattribute("textblock")
local inline_mark = nodes.pool.userids["margins.inline"]
local nuts = nodes.nuts
local getnext = nuts.getnext
local getdisc = nuts.getdisc
local getlist = nuts.getlist
local getid = nuts.getid
local getattr = nuts.getattr
local setattr = nuts.setattr -- maybe use properties
local isglyph = nuts.isglyph
local getkern = nuts.getkern
local getwidth = nuts.getwidth
local startofpar = nuts.startofpar
local nexthlist = nuts.traversers.hlist
local nextnode = nuts.traversers.node
local function addtomaybe(maybewrong,c,case)
if trace_export then
report_export("%w",currentdepth,c,case)
else
local s = formatters["%C"](c)
if maybewrong then
maybewrong[#maybewrong+1] = s
else
maybewrong = { s }
end
return maybewrong
end
end
local function showmaybe(maybewrong)
if not trace_export then
report_export("fuzzy paragraph: % t",maybewrong)
end
end
local function showdetail(n,id,subtype)
local a = getattr(n,a_tagged)
local t = taglist[a]
local c = nodecodes[id]
local s = subtypes[id][subtype]
if a and t then
report_export("node %a, subtype %a, tag %a, element %a, tree '% t'",c,s,a,t.tagname,t.taglist)
else
report_export("node %a, subtype %a, untagged",c,s)
end
end
local function collectresults(head,list,pat,pap) -- is last used (we also have currentattribute)
local p
local paragraph
local maybewrong
local pid
for n, id, subtype in nextnode, head do
if trace_details then
showdetail(n,id,subtype)
end
if id == glyph_code then
local c, f = isglyph(n)
local at = getattr(n,a_tagged) or pat
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
if last ~= at then
local tl = taglist[at]
local ap = getattr(n,a_taggedpar) or pap
if paragraph and (not ap or ap < paragraph) then
maybewrong = addtomaybe(maybewrong,c,1)
end
pushcontent()
currentnesting = tl
currentparagraph = ap
currentattribute = at
last = at
pushentry(currentnesting)
if trace_export then
report_export("%w",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 = getattr(n,a_reference)
if r then
local t = tl.taglist
referencehash[t[#t]] = r -- fulltag
end
local d = getattr(n,a_destination)
if d then
local t = tl.taglist
destinationhash[t[#t]] = d -- fulltag
end
--
elseif last then
-- we can consider tagging the pars (lines) in the parbuilder but then we loose some
-- information unless we inject a special node (but even then we can run into nesting
-- issues)
local ap = getattr(n,a_taggedpar) or pap
if ap ~= currentparagraph then
pushcontent(currentparagraph,ap)
pushentry(currentnesting)
currentattribute = last
currentparagraph = ap
end
if paragraph and (not ap or ap < paragraph) then
maybewrong = addtomaybe(maybewrong,c,2)
end
if trace_export then
report_export("%w",currentdepth,c,last)
end
else
if trace_export then
report_export("%w",currentdepth,c,at)
end
end
local s = getattr(n,a_exportstatus)
if s then
c = s
end
if c == 0 then
if trace_export then
report_export("%w",currentdepth)
end
elseif c == 0x20 then
local a = getattr(n,a_characters)
nofcurrentcontent = nofcurrentcontent + 1
if a then
if trace_export then
report_export("%w",currentdepth,a)
end
currentcontent[nofcurrentcontent] = specialspaces[a] -- special space
else
currentcontent[nofcurrentcontent] = " "
end
else
local fc = fontchar[f]
if fc then
fc = fc and fc[c]
if fc then
local u = fc.unicode
if not u then
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = utfchar(c)
elseif type(u) == "table" then
for i=1,#u do
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = utfchar(u[i])
end
else
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = utfchar(u)
end
elseif c > 0 then
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = utfchar(c)
else
-- we can have -1 as side effect of an explicit hyphen (unless we expand)
end
elseif c > 0 then
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = utfchar(c)
else
-- we can have -1 as side effect of an explicit hyphen (unless we expand)
end
end
end
elseif id == glue_code then
-- we need to distinguish between hskips and vskips
local ca = getattr(n,a_characters)
if ca == 0 then
-- skip this one ... already converted special character (node-acc)
elseif ca then
local a = getattr(n,a_tagged) or pat
if a then
local c = specialspaces[ca]
if last ~= a then
local tl = taglist[a]
if trace_export then
report_export("%w",currentdepth,ca,a)
end
pushcontent()
currentnesting = tl
currentparagraph = getattr(n,a_taggedpar) or pap
currentattribute = a
last = a
pushentry(currentnesting)
-- no reference check (see above)
elseif last then
local ap = getattr(n,a_taggedpar) or pap
if ap ~= currentparagraph then
pushcontent(currentparagraph,ap)
pushentry(currentnesting)
currentattribute = last
currentparagraph = ap
end
if trace_export then
report_export("%w",currentdepth,ca,last)
end
end
-- if somespace[currentcontent[nofcurrentcontent]] then
-- if trace_export then
-- report_export("%w",currentdepth)
-- end
-- nofcurrentcontent = nofcurrentcontent - 1
-- end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = c
end
elseif subtype == userskip_code then
if getwidth(n) > threshold then
if last and not somespace[currentcontent[nofcurrentcontent]] then
local a = getattr(n,a_tagged) or pat
if a == last then
if trace_export then
report_export("%w",currentdepth)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " "
elseif a then
-- e.g LOGOLOGO
if trace_export then
report_export("%w",currentdepth,last,a)
end
pushcontent()
if trace_export then
report_export("%w",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 = getattr(n,a_tagged) or pat
if a == last then
if trace_export then
report_export("%w",currentdepth)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " "
else
if trace_export then
report_export("%w",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 pid == disc_code then
-- go on .. tricky: we should mark the glyhs as coming from a disc
elseif not somespace[r] then
local a = getattr(n,a_tagged) or pat
if a == last then
if trace_export then
report_export("%w",currentdepth)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " "
else
if trace_export then
report_export("%w",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 paragraph endings (crossings) elsewhere and we quit here
-- as we don't want the rightskip space addition
if maybewrong then
showmaybe(maybewrong)
end
return
end
elseif id == hlist_code or id == vlist_code then
local ai = getattr(n,a_image)
if ai then
local at = getattr(n,a_tagged) or pat
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",currentdepth,kern)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " "
end
elseif a then
-- e.g LOGOLOGO
if trace_export then
report_export("%w",currentdepth,limit,last,a)
end
last = a
pushcontent()
if trace_export then
report_export("%w",currentdepth,kern)
end
nofcurrentcontent = nofcurrentcontent + 1
currentcontent[nofcurrentcontent] = " "
-- currentnesting = taglist[last]
currentnesting = t
pushentry(currentnesting)
currentattribute = last
end
end
end
end
end
elseif id == whatsit_code then
if subtype == userdefinedwhatsit_code then
-- similar to images, see above
local at = getattr(n,a_tagged)
if nofcurrentcontent > 0 then
pushcontent()
pushentry(currentnesting) -- ??
end
pushentry(taglist[at])
if trace_export then
report_export("%w",currentdepth)
end
-- continueexport()
restart = true
collectresults(head)
if trace_export then
report_export("%w",currentdepth)
end
stoptiming(treehash)
return head
end
function nodes.handlers.checkparcounter(p)
setattr(p,a_taggedpar,texgetcount("tagparcounter") + 1)
return p
end
function builders.paragraphs.tag(head)
noftextblocks = noftextblocks + 1
for n, subtype in nexthlist, head do
if subtype == linelist_code then
setattr(n,a_textblock,noftextblocks)
-- elseif subtype == glue_code or subtype == kern_code then -- weird, no list
-- setattr(n,a_textblock,0)
end
end
return false
end
end
do
local xmlcollected = xml.collected
local xmlsetcomment = xml.setcomment
local xmlpreamble = [[
]]
local flushtree = wrapups.flushtree
local function wholepreamble(standalone)
return replacetemplate(xmlpreamble, {
standalone = standalone and "yes" or "no",
filename = tex.jobname,
date = included.date and os.fulltime(),
contextversion = environment.version,
exportversion = exportversion,
})
end
local csspreamble = [[
]]
local cssheadlink = [[
]]
local function allusedstylesheets(cssfiles,files,path)
local done = { }
local result = { }
local extras = { }
for i=1,#cssfiles do
local cssfile = cssfiles[i]
if type(cssfile) ~= "string" then
-- error
elseif cssfile == "export-example.css" then
-- ignore
elseif not done[cssfile] then
cssfile = joinfile(path,basename(cssfile))
report_export("adding css reference '%s'",cssfile)
files[#files+1] = cssfile
result[#result+1] = replacetemplate(csspreamble, { filename = cssfile })
extras[#extras+1] = replacetemplate(cssheadlink, { filename = cssfile })
done[cssfile] = true
end
end
return concat(result), concat(extras)
end
local elementtemplate = [[
/* element="%element%" detail="%detail%" chain="%chain%" */
%element%,
%namespace%div.%element% {
display: %display% ;
}]]
local detailtemplate = [[
/* element="%element%" detail="%detail%" chain="%chain%" */
%element%[detail=%detail%],
%namespace%div.%element%.%detail% {
display: %display% ;
}]]
--
local htmltemplate = [[
%preamble%
%title%
%style%
Rendering can be suboptimal because there is no default/fallback css loaded.
%body%
]]
local displaymapping = {
inline = "inline",
display = "block",
mixed = "inline",
}
local function allusedelements(filename)
local result = { replacetemplate(namespacetemplate, {
what = "template",
filename = filename,
namespace = contextns,
-- cssnamespaceurl = usecssnamespace and cssnamespaceurl or "",
cssnamespaceurl = cssnamespaceurl,
},false,true) }
for element, details in sortedhash(used) do
if namespaces[element] then
-- skip math
else
for detail, what in sortedhash(details) do
local nature = what[1] or "display"
local chain = what[2]
local display = displaymapping[nature] or "block"
if detail == "" then
result[#result+1] = replacetemplate(elementtemplate, {
element = element,
display = display,
chain = chain,
namespace = usecssnamespace and namespace or "",
})
else
result[#result+1] = replacetemplate(detailtemplate, {
element = element,
display = display,
detail = detail,
chain = chain,
namespace = usecssnamespace and cssnamespace or "",
})
end
end
end
end
return concat(result,"\n\n")
end
local function allcontent(tree,embed)
local result = { }
flushtree(result,tree.data,"display") -- we need to collect images
result = concat(result)
-- no need to lpeg .. fast enough
result = gsub(result,"\n *\n","\n")
result = gsub(result,"\n +([^< ])","\n%1")
return result
end
-- local xhtmlpreamble = [[
--
-- ]]
local function cleanxhtmltree(xmltree)
if xmltree then
local implicits = { }
local explicits = { }
local overloads = { }
for e in xmlcollected(xmltree,"*") do
local at = e.at
if at then
local explicit = at.explicit
local implicit = at.implicit
if explicit then
if not explicits[explicit] then
explicits[explicit] = true
at.id = explicit
if implicit then
overloads[implicit] = explicit
end
end
else
if implicit and not implicits[implicit] then
implicits[implicit] = true
at.id = "aut:" .. implicit
end
end
end
end
for e in xmlcollected(xmltree,"*") do
local at = e.at
if at then
local internal = at.internal
local location = at.location
if internal then
if location then
local explicit = overloads[location]
if explicit then
at.href = "#" .. explicit
else
at.href = "#aut:" .. internal
end
else
at.href = "#aut:" .. internal
end
else
if location then
at.href = "#" .. location
else
local url = at.url
if url then
at.href = url
else
local file = at.file
if file then
at.href = file
end
end
end
end
end
end
return xmltree
else
return xml.convert('\ninvalid xhtml tree')
end
end
-- maybe the reverse: be explicit about what is permitted
local private = {
destination = true,
prefix = true,
reference = true,
--
id = true,
href = true,
--
implicit = true,
explicit = true,
--
url = true,
file = true,
internal = true,
location = true,
--
name = true, -- image name
used = true, -- image name
page = true, -- image name
width = true,
height = true,
--
}
local addclicks = true
local f_onclick = formatters[ [[location.href='%s']] ]
local f_onclick = formatters[ [[location.href='%s']] ]
local p_cleanid = lpeg.replacer { [":"] = "-" }
local p_cleanhref = lpeg.Cs(lpeg.P("#") * p_cleanid)
local p_splitter = lpeg.Ct ( (
lpeg.Carg(1) * lpeg.C((1-lpeg.P(" "))^1) / function(d,s) if not d[s] then d[s] = true return s end end
* lpeg.P(" ")^0 )^1 )
local classes = table.setmetatableindex(function(t,k)
local v = concat(lpegmatch(p_splitter,k,1,{})," ")
t[k] = v
return v
end)
local function makeclass(tg,at)
local detail = at.detail
local chain = at.chain
local extra = nil
local classes = { }
local nofclasses = 0
at.detail = nil
at.chain = nil
for k, v in next, at do
if not private[k] then
nofclasses = nofclasses + 1
classes[nofclasses] = k .. "-" .. v
end
end
if detail and detail ~= "" then
if chain and chain ~= "" then
if chain ~= detail then
extra = classes[tg .. " " .. chain .. " " .. detail]
elseif tg ~= detail then
extra = detail
end
elseif tg ~= detail then
extra = detail
end
elseif chain and chain ~= "" then
if tg ~= chain then
extra = chain
end
end
-- in this order
if nofclasses > 0 then
sort(classes)
classes = concat(classes," ")
if extra then
return tg .. " " .. extra .. " " .. classes
else
return tg .. " " .. classes
end
else
if extra then
return tg .. " " .. extra
else
return tg
end
end
end
-- Some elements are not supported (well) in css so we need to retain them. For
-- instance, tablecells have no colspan so basically that renders css table div
-- elements quite useless. A side effect is that we nwo can have conflicts when
-- we mix in with other html (as there is no reset). Of course, when it eventually
-- gets added, there is a change then that those not using the div abstraction
-- will be rediculed.
--
-- a table tr td th thead tbody tfoot
--
local crappycss = {
table = "table", tabulate = "table",
tablehead = "thead", tabulatehead = "thead",
tablebody = "tbody", tabulatebody = "tbody",
tablefoot = "tfoot", tabulatefoot = "tfoot",
tablerow = "tr", tabulaterow = "tr",
tablecell = "td", tabulatecell = "td",
}
local cssmapping = false
directives.register("export.nativetags", function(v)
cssmapping = v and crappycss or false
end)
local function remap(specification,source,target)
local comment = nil -- share comments
for c in xmlcollected(source,"*") do
if not c.special then
local tg = c.tg
local ns = c.ns
if ns == "m" then
if false then -- yes or no
c.ns = ""
c.at["xmlns:m"] = nil
end
-- elseif tg == "a" then
-- c.ns = ""
else
local dt = c.dt
local nt = #dt
if nt == 0 or (nt == 1 and dt[1] == "") then
if comment then
c.dt = comment
else
xmlsetcomment(c,"empty")
comment = c.dt
end
end
local at = c.at
local class = nil
local label = nil
if tg == "document" then
at.href = nil
at.detail = nil
at.chain = nil
elseif tg == "metavariable" then
label = at.name
at.detail = "metaname-" .. label
class = makeclass(tg,at)
else
class = makeclass(tg,at)
end
local id = at.id
local href = at.href
local attr = nil
if id then
id = lpegmatch(p_cleanid, id) or id
if href then
href = lpegmatch(p_cleanhref,href) or href
attr = {
class = class,
id = id,
href = href,
onclick = addclicks and f_onclick(href) or nil,
}
else
attr = {
class = class,
id = id,
}
end
else
if href then
href = lpegmatch(p_cleanhref,href) or href
attr = {
class = class,
href = href,
onclick = addclicks and f_onclick(href) or nil,
}
else
attr = {
class = class,
}
end
end
c.at = attr
if label then
attr.label = label
end
c.tg = cssmapping and cssmapping[tg] or "div"
end
end
end
end
-- local cssfile = nil directives.register("backend.export.css", function(v) cssfile = v end)
local embedfile = false directives.register("export.embed",function(v) embedfile = v end)
function structurestags.finishexport()
if exporting then
exporting = false
else
return
end
local onlyxml = finetuning.export == v_xml
starttiming(treehash)
--
finishexport()
--
report_export("")
if onlyxml then
report_export("exporting xml, no other files")
else
report_export("exporting xml, xhtml, html and css files")
end
report_export("")
--
wrapups.fixtree(tree)
wrapups.collapsetree(tree)
wrapups.indextree(tree)
wrapups.checktree(tree)
wrapups.breaktree(tree)
wrapups.finalizetree(tree)
--
wrapups.hashlistdata()
--
local askedname = finetuning.file
--
-- we use a dedicated subpath:
--
-- ./jobname-export
-- ./jobname-export/images
-- ./jobname-export/styles
-- ./jobname-export/styles
-- ./jobname-export/jobname-export.xml
-- ./jobname-export/jobname-export.xhtml
-- ./jobname-export/jobname-export.html
-- ./jobname-export/jobname-specification.lua
-- ./jobname-export/styles/jobname-defaults.css
-- ./jobname-export/styles/jobname-styles.css
-- ./jobname-export/styles/jobname-images.css
-- ./jobname-export/styles/jobname-templates.css
if type(askedname) ~= "string" or askedname == "" then
askedname = tex.jobname
end
local usedname = nameonly(askedname)
local basepath = usedname .. "-export"
local imagepath = joinfile(basepath,"images")
local stylepath = joinfile(basepath,"styles")
local function validpath(what,pathname)
if lfs.isdir(pathname) then
report_export("using existing %s path %a",what,pathname)
return pathname
end
lfs.mkdir(pathname)
if lfs.isdir(pathname) then
report_export("using cretated %s path %a",what,basepath)
return pathname
else
report_export("unable to create %s path %a",what,basepath)
return false
end
end
if not (validpath("export",basepath) and validpath("images",imagepath) and validpath("styles",stylepath)) then
return
end
-- we're now on the dedicated export subpath so we can't clash names
--
-- a xhtml suffix no longer seems to be work well with browsers
local xmlfilebase = addsuffix(usedname .. "-raw","xml" )
local xhtmlfilebase = addsuffix(usedname .. "-tag","xhtml")
local htmlfilebase = addsuffix(usedname .. "-div","html")
local specificationfilebase = addsuffix(usedname .. "-pub","lua" )
local xmlfilename = joinfile(basepath, xmlfilebase )
local xhtmlfilename = joinfile(basepath, xhtmlfilebase )
local htmlfilename = joinfile(basepath, htmlfilebase )
local specificationfilename = joinfile(basepath, specificationfilebase)
--
local defaultfilebase = addsuffix(usedname .. "-defaults", "css")
local imagefilebase = addsuffix(usedname .. "-images", "css")
local stylefilebase = addsuffix(usedname .. "-styles", "css")
local templatefilebase = addsuffix(usedname .. "-templates","css")
--
local defaultfilename = joinfile(stylepath,defaultfilebase )
local imagefilename = joinfile(stylepath,imagefilebase )
local stylefilename = joinfile(stylepath,stylefilebase )
local templatefilename = joinfile(stylepath,templatefilebase)
local cssfile = finetuning.cssfile
-- we keep track of all used files
local files = {
}
-- we always load the defaults and optionally extra css files; we also copy the example
-- css file so that we always have the latest version
local cssfiles = {
defaultfilebase,
imagefilebase,
stylefilebase,
}
local cssextra = cssfile and table.unique(settings_to_array(cssfile)) or { }
-- at this point we're ready for the content; the collector also does some
-- housekeeping and data collecting; at this point we still have an xml
-- representation that uses verbose element names and carries information in
-- attributes
local data = tree.data
for i=1,#data do
if data[i].tg ~= "document" then
data[i] = { }
end
end
local result = allcontent(tree,embedmath) -- embedfile is for testing
-- ugly but so be it:
local extradata = structures.tags.getextradata()
if extradata then
local t = { "" }
t[#t+1] = ""
for name, action in sortedhash(extradata) do
t[#t+1] = action()
end
t[#t+1] = ""
t[#t+1] = ""
-- we use a function because otherwise we can have a bad capture index
result = gsub(result,"",function()
return concat(t,"\n")
end)
end
-- done with ugly
if onlyxml then
os.remove(defaultfilename)
os.remove(imagefilename)
os.remove(stylefilename)
os.remove(templatefilename)
for i=1,#cssextra do
os.remove(joinfile(stylepath,basename(source)))
end
-- os.remove(xmlfilename)
os.remove(imagefilename)
os.remove(stylefilename)
os.remove(templatefilename)
os.remove(xhtmlfilename)
os.remove(specificationfilename)
os.remove(htmlfilename)
result = concat {
wholepreamble(true),
"\n",
result,
}
report_export("saving xml data in %a",xmlfilename)
io.savedata(xmlfilename,result)
return
end
local examplefilename = resolvers.findfile("export-example.css")
if examplefilename then
local data = io.loaddata(examplefilename)
if not data or data == "" then
data = "/* missing css file */"
elseif not usecssnamespace then
data = gsub(data,cssnamespace,"")
end
io.savedata(defaultfilename,data)
end
if cssfile then
for i=1,#cssextra do
local source = addsuffix(cssextra[i],"css")
local target = joinfile(stylepath,basename(source))
cssfiles[#cssfiles+1] = source
if not lfs.isfile(source) then
source = joinfile("../",source)
end
if lfs.isfile(source) then
report_export("copying %s",source)
file.copy(source,target)
end
end
end
local x_styles, h_styles = allusedstylesheets(cssfiles,files,"styles")
local attach = backends.nodeinjections.attachfile
if embedfile and attach then
-- only for testing
attach {
data = concat{ wholepreamble(true), result },
name = basename(xmlfilename),
registered = "export",
title = "raw xml export",
method = v_hidden,
mimetype = "application/mathml+xml",
}
end
result = concat {
wholepreamble(true),
x_styles, -- adds to files
result,
}
cssfiles = table.unique(cssfiles)
-- we're now ready for saving the result in the xml file
report_export("saving xml data in %a",xmlfilename)
io.savedata(xmlfilename,result)
report_export("saving css image definitions in %a",imagefilename)
io.savedata(imagefilename,wrapups.allusedimages(usedname))
report_export("saving css style definitions in %a",stylefilename)
io.savedata(stylefilename,wrapups.allusedstyles(usedname))
report_export("saving css template in %a",templatefilename)
io.savedata(templatefilename,allusedelements(usedname))
-- additionally we save an xhtml file; for that we load the file as xml tree
report_export("saving xhtml variant in %a",xhtmlfilename)
local xmltree = cleanxhtmltree(xml.convert(result))
-- local xmltree = xml.convert(result)
-- for c in xml.collected(xmltree,"m:mtext[lastindex()=1]/m:mrow") do
-- print(c)
-- end
-- for c in xml.collected(xmltree,"mtext/mrow") do
-- print(c)
-- end
-- local xmltree = cleanxhtmltree(xmltree)
xml.save(xmltree,xhtmlfilename)
-- now we save a specification file that can b eused for generating an epub file
-- looking at identity is somewhat redundant as we also inherit from interaction
-- at the tex end
local identity = interactions.general.getidentity()
local metadata = structures.tags.getmetadata()
local specification = {
name = usedname,
identifier = os.uuid(),
images = wrapups.uniqueusedimages(),
imagefile = joinfile("styles",imagefilebase),
imagepath = "images",
stylepath = "styles",
xmlfiles = { xmlfilebase },
xhtmlfiles = { xhtmlfilebase },
htmlfiles = { htmlfilebase },
styles = cssfiles,
htmlroot = htmlfilebase,
language = languagenames[texgetcount("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),
metadata = metadata,
}
report_export("saving specification in %a",specificationfilename,specificationfilename)
xml.wipe(xmltree,"metadata") -- maybe optional
io.savedata(specificationfilename,table.serialize(specification,true))
-- the html export for epub is different in the sense that it uses div's instead of
-- specific tags
report_export("saving div based alternative in %a",htmlfilename)
remap(specification,xmltree)
-- believe it or not, but a can prevent viewing in browsers
local title = specification.title
if not title or title == "" then
title = metadata.title
if not title or title == "" then
title = usedname -- was: "no title"
end
end
local variables = {
style = h_styles,
body = xml.tostring(xml.first(xmltree,"/div")),
preamble = wholepreamble(false),
title = title,
}
io.savedata(htmlfilename,replacetemplate(htmltemplate,variables,"xml"))
-- finally we report how an epub file can be made (using the specification)
report_export("")
report_export('create epub with: mtxrun --script epub --make "%s" [--purge --rename --svgmath]',usedname)
report_export("")
stoptiming(treehash)
end
local enableaction = nodes.tasks.enableaction
function structurestags.initializeexport()
if not exporting then
report_export("enabling export to xml")
enableaction("shipouts","nodes.handlers.export")
enableaction("shipouts","nodes.handlers.accessibility")
enableaction("math", "noads.handlers.tags")
enableaction("everypar","nodes.handlers.checkparcounter")
luatex.registerstopactions(structurestags.finishexport)
exporting = true
end
end
function structurestags.setupexport(t)
merge(finetuning,t)
keephyphens = finetuning.hyphen == v_yes
exportproperties = finetuning.properties
if exportproperties == v_no then
exportproperties = false
end
end
statistics.register("xml exporting time", function()
if exporting then
return string.format("%s seconds, version %s", statistics.elapsedtime(treehash),exportversion)
end
end)
end
-- These are called at the tex end:
implement {
name = "setupexport",
actions = structurestags.setupexport,
arguments = {
{
{ "align" },
{ "bodyfont", "dimen" },
{ "width", "dimen" },
{ "properties" },
{ "hyphen" },
{ "title" },
{ "subtitle" },
{ "author" },
{ "firstpage" },
{ "lastpage" },
{ "svgstyle" },
{ "cssfile" },
{ "file" },
{ "export" },
}
}
}
implement {
name = "finishexport",
actions = structurestags.finishexport,
}
implement {
name = "initializeexport",
actions = structurestags.initializeexport,
}
implement {
name = "settagitemgroup",
actions = structurestags.setitemgroup,
arguments = { "boolean", "integer", "string" }
}
implement {
name = "settagitem",
actions = structurestags.setitem,
arguments = "string"
}
implement {
name = "settagfloat",
actions = structurestags.setfloat,
arguments = "2 strings",
}
implement {
name = "settagformulacontent",
actions = structurestags.setformulacontent,
arguments = "integer",
}
implement {
name = "settagdelimitedsymbol",
actions = structurestags.settagdelimitedsymbol,
arguments = "string"
}
implement {
name = "settagsubsentencesymbol",
actions = structurestags.settagsubsentencesymbol,
arguments = "string"
}
implement {
name = "settagsynonym",
actions = structurestags.setsynonym,
arguments = "string"
}
implement {
name = "settagsorting",
actions = structurestags.setsorting,
arguments = "string"
}
implement {
name = "settagnotation",
actions = structurestags.setnotation,
arguments = { "string", "integer" }
}
implement {
name = "settagnotationsymbol",
actions = structurestags.setnotationsymbol,
arguments = { "string", "integer" }
}
implement {
name = "settaghighlight",
actions = structurestags.sethighlight,
arguments = { "string", "string", "integer", "integer" }
}
implement {
name = "settagconstruct",
actions = structurestags.setconstruct,
arguments = { "string", "string", "integer", "integer" }
}
implement {
name = "settagfigure",
actions = structurestags.setfigure,
arguments = { "string", "string", "string", "dimen", "dimen", "string" }
}
implement {
name = "settagcombination",
actions = structurestags.setcombination,
arguments = { "integer", "integer" }
}
implement {
name = "settagtablecell",
actions = structurestags.settablecell,
arguments = { "integer", "integer", "integer" }
}
implement {
name = "settagtabulatecell",
actions = structurestags.settabulatecell,
arguments = { "integer", "integer" },
}
implement {
name = "settagregister",
actions = structurestags.setregister,
arguments = { "string", "integer" }
}
implement {
name = "settaglist",
actions = structurestags.setlist,
arguments = "integer"
}
implement {
name = "settagpublication",
actions = structurestags.setpublication,
arguments = "2 strings"
}
implement {
name = "settagparagraph",
actions = structurestags.setparagraph,
arguments = "string"
}