if not modules then modules = { } end modules ['strc-tag'] = { version = 1.001, comment = "companion to strc-tag.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- This is rather experimental code. Tagging happens on the fly and there are two analysers -- involved: the pdf backend tagger and the exporter. They share data but there are subtle -- differences. Each tag carries a specification and these can be accessed by attribute (the -- end of the chain tag) or by so called fullname which is a tagname combined with a number. local type, next = type, next local insert, remove, unpack, concat, merge, sortedhash = table.insert, table.remove, table.unpack, table.concat, table.merge, table.sortedhash local find, topattern, format = string.find, string.topattern, string.format local lpegmatch, P, S, C, Cc = lpeg.match, lpeg.P, lpeg.S, lpeg.C, lpeg.Cc local allocate = utilities.storage.allocate local settings_to_hash = utilities.parsers.settings_to_hash local setmetatableindex = table.setmetatableindex local trace_tags = false trackers.register("structures.tags", function(v) trace_tags = v end) local report_tags = logs.reporter("structure","tags") local attributes = attributes local structures = structures local implement = interfaces.implement local a_tagged = attributes.private('tagged') local unsetvalue = attributes.unsetvalue local codeinjections = backends.codeinjections local texgetattribute = tex.getattribute local texsetattribute = tex.setattribute local taglist = allocate() -- access by attribute local specifications = allocate() -- access by fulltag local labels = allocate() local stack = { } local chain = { } local ids = { } local enabled = false local tagcontext = { } local tagpatterns = { } local lasttags = { } local stacksize = 0 local metadata = nil -- applied to the next element local documentdata = { } local extradata = false local tags = structures.tags tags.taglist = taglist -- can best be hidden tags.labels = labels tags.patterns = tagpatterns tags.specifications = specifications function tags.current() if stacksize > 0 then return stack[stacksize] -- maybe copy or proxy end end -- Tags are internally stored as: -- -- tag>number tag>number tag>number local p_splitter = C((1-S(">"))^1) * P(">") * C(P(1)^1) tagpatterns.splitter = p_splitter -- Tagging is not really meant for structure and is very much driven by simple documents, -- which is why we have this H (version 1) and H* (version 2) without granularity. The fact -- that (2024) the standards are not really a showcase of how a pdf file should looks and -- the version 2 spec is at most a version 1 document means that we have to trial and error. -- -- After some testing we decided to make all NonStruct because these can nest and have no -- demands on what's inside, nor is it sensitive for Span and Div. Using the sort of generic -- Sect is no option either because it needs some content item at the innermost level. So, -- instead we wait for UA-5 to eventually come up with proper generic tagging (instead of -- this curious mix of html, long and short tags, and assumptions wrt simple usage). Maybe -- we should also adapt UA-1 support to this. -- -- MS/HH 2024 -- -- For ua-1 we now also go the NonStruct route: better this than some half-way mapping and -- better enforce the dumbest reflow possible if one runs into it. We used to distinguish -- but because the meanings have changed we can as wel ditch it for a user installable -- mapping which delegates the responsibility. -- -- The standard mentions that meaning of the built in tags differs per versions so that is a -- good reason to drop usign them. In due time we will therefore simplify the following table. -- -- Btw, we don't want this to cripple our normal structuring (including export) so we have -- some constraints as well as gatekeeping. -- -- Musical timestamp (ua-2 upgrade): Mandoki Soulmates – The Big Quit (LYRIC VIDEO, 2024) -- which between the chorus lines has this sublte "visionary garbage" line ... indeed. -- Some NonStruct might become Part, Sect or Sub in which case titles and tage become Lbl -- but only when validators don't bark on it and the main structure is kept. local properties = allocate { -- todo: more "record = true" to improve formatting document = { namespace = "context", nature = "display", pdf = "Document" }, documentpart = { namespace = "context", nature = "display", pdf = "DocumentFragment" }, division = { namespace = "context", nature = "display", pdf = "NonStruct" }, paragraph = { namespace = "context", nature = "mixed", pdf = "P" }, subparagraph = { namespace = "context", nature = "mixed", pdf = "P" }, p = { namespace = "context", nature = "mixed", pdf = "P" }, highlight = { namespace = "context", nature = "inline", pdf = "NonStruct" }, ornament = { namespace = "context", nature = "inline", pdf = "NonStruct" }, textdisplay = { namespace = "context", nature = "display", pdf = "NonStruct" }, placeholder = { namespace = "context", nature = "inline", pdf = "NonStruct" }, ["break"] = { namespace = "context", nature = "display", pdf = "NonStruct" }, construct = { namespace = "context", nature = "inline", pdf = "NonStruct" }, constructleft = { namespace = "context", nature = "inline", pdf = "NonStruct" }, constructright = { namespace = "context", nature = "inline", pdf = "NonStruct" }, constructcontent = { namespace = "context", nature = "inline", pdf = "NonStruct" }, sectionblock = { namespace = "context", nature = "display", pdf = "NonStruct" }, section = { namespace = "context", nature = "display", pdf = "NonStruct" }, sectioncaption = { namespace = "context", nature = "display", pdf = "NonStruct" , record = true }, sectiontitle = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, sectionnumber = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, sectioncontent = { namespace = "context", nature = "display", pdf = "NonStruct" }, itemgroup = { namespace = "context", nature = "display", pdf = "NonStruct" }, item = { namespace = "context", nature = "display", pdf = "NonStruct" }, itemtag = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, itemcontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, itemhead = { namespace = "context", nature = "display", pdf = "NonStruct" }, itembody = { namespace = "context", nature = "display", pdf = "NonStruct" }, items = { namespace = "context", nature = "display", pdf = "Table" }, itemsymbols = { namespace = "context", nature = "mixed", pdf = "TR" }, itemsymbol = { namespace = "context", nature = "inline", pdf = "TD" }, itemtexts = { namespace = "context", nature = "mixed", pdf = "TR" }, itemtext = { namespace = "context", nature = "inline", pdf = "TD" }, description = { namespace = "context", nature = "display", pdf = "NonStruct" }, descriptiontag = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, descriptioncontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, descriptionsymbol = { namespace = "context", nature = "inline", pdf = "NonStruct" }, verbatimblock = { namespace = "context", nature = "display", pdf = "NonStruct" }, verbatimlines = { namespace = "context", nature = "display", pdf = "NonStruct" }, verbatimline = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, verbatim = { namespace = "context", nature = "inline", pdf = "NonStruct" }, lines = { namespace = "context", nature = "display", pdf = "NonStruct" }, line = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, linenumber = { namespace = "context", nature = "inline", pdf = "NonStruct" }, synonym = { namespace = "context", nature = "inline", pdf = "NonStruct" }, sorting = { namespace = "context", nature = "inline", pdf = "NonStruct" }, register = { namespace = "context", nature = "display", pdf = "NonStruct" }, registerlocation = { namespace = "context", nature = "inline", pdf = "NonStruct" }, registersection = { namespace = "context", nature = "display", pdf = "NonStruct" }, registertag = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, registerentries = { namespace = "context", nature = "display", pdf = "NonStruct" }, registerentry = { namespace = "context", nature = "display", pdf = "NonStruct" }, registercontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, registersee = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, registerpages = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, registerpage = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, registerseparator = { namespace = "context", nature = "inline", pdf = "NonStruct" }, registerpagerange = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, table = { namespace = "context", nature = "display", pdf = "Table", }, tablerow = { namespace = "context", nature = "display", pdf = "TR", }, tablecell = { namespace = "context", nature = "mixed", pdf = "TD", }, tableheadcell = { namespace = "context", nature = "mixed", pdf = "TH", }, tablehead = { namespace = "context", nature = "display", pdf = "THEAD", }, tablebody = { namespace = "context", nature = "display", pdf = "TBODY", }, tablefoot = { namespace = "context", nature = "display", pdf = "TFOOT", }, tabulate = { namespace = "context", nature = "display", pdf = "Table", }, tabulaterow = { namespace = "context", nature = "display", pdf = "TR", }, tabulatecell = { namespace = "context", nature = "mixed", pdf = "TD", }, tabulateheadcell = { namespace = "context", nature = "mixed", pdf = "TH", }, tabulatehead = { namespace = "context", nature = "display", pdf = "THEAD", }, tabulatebody = { namespace = "context", nature = "display", pdf = "TBODY", }, tabulatefoot = { namespace = "context", nature = "display", pdf = "TFOOT", }, list = { namespace = "context", nature = "display", pdf = "NonStruct" }, listitem = { namespace = "context", nature = "display", pdf = "NonStruct" }, listtag = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, listcontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, listdata = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, listpage = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, listtext = { namespace = "context", nature = "inline", pdf = "NonStruct" }, delimitedblock = { namespace = "context", nature = "display", pdf = "NonStruct" }, delimited = { namespace = "context", nature = "inline", pdf = "NonStruct" }, delimitedcontent = { namespace = "context", nature = "inline", pdf = "NonStruct" }, delimitedsymbol = { namespace = "context", nature = "inline", pdf = "NonStruct" }, subsentence = { namespace = "context", nature = "inline", pdf = "NonStruct" }, subsentencecontent = { namespace = "context", nature = "inline", pdf = "NonStruct" }, subsentencesymbol = { namespace = "context", nature = "inline", pdf = "NonStruct" }, label = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, number = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, float = { namespace = "context", nature = "display", pdf = "NonStruct" }, floatcaption = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, floatlabel = { namespace = "context", nature = "inline", pdf = "NonStruct" }, floatnumber = { namespace = "context", nature = "inline", pdf = "NonStruct" }, floattext = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, floatcontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, image = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, mpgraphic = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, formulaset = { namespace = "context", nature = "display", pdf = "NonStruct" }, formula = { namespace = "context", nature = "display", pdf = "NonStruct" }, formulacaption = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, formulalabel = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, formulanumber = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, formulacontent = { namespace = "context", nature = "display", pdf = "NonStruct" }, subformula = { namespace = "context", nature = "display", pdf = "NonStruct" }, link = { namespace = "context", nature = "inline", pdf = "Link" }, reference = { namespace = "context", nature = "inline", pdf = "NonStruct" }, navigation = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, navigationbutton = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, navigationmenu = { namespace = "context", nature = "display", pdf = "NonStruct" }, navigationmenuitem = { namespace = "context", nature = "display", pdf = "NonStruct" }, navigationaction = { namespace = "context", nature = "display", pdf = "NonStruct" }, navigationpage = { namespace = "context", nature = "inline", pdf = "NonStruct" }, margintextblock = { namespace = "context", nature = "inline", pdf = "NonStruct" }, margintext = { namespace = "context", nature = "inline", pdf = "NonStruct" }, marginanchor = { namespace = "context", nature = "inline", pdf = "NonStruct" }, linetext = { namespace = "context", nature = "inline", pdf = "NonStruct" }, -- math = { namespace = "mathml", nature = "inline", pdf = "math" }, math = { namespace = "context", nature = "inline", pdf = "NonStruct" }, inlinemath = { namespace = "context", nature = "inline", pdf = "NonStruct" }, displaymath = { namespace = "context", nature = "display", pdf = "NonStruct" }, -- these are wrapped in math and only used in detailed math mode -- mn = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "mn" }, -- mi = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "mi" }, -- mo = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "mo" }, -- ms = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "ms" }, mrow = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mrow" }, -- msubsup = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "msubsup" }, -- msub = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "msub" }, -- msup = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "msup" }, -- merror = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "merror" }, -- munderover = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "munderover" }, -- munder = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "munder" }, -- mover = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mover" }, -- mtext = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "mtext" }, -- mfrac = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mfrac" }, -- mroot = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mroot" }, -- msqrt = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "msqrt" }, -- mfenced = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mfenced" }, -- maction = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "maction" }, -- mmultiscripts = { namespace = "mathml", nature = "display", pua = "mathml", pdf = "mmultiscripts" }, -- mprescripts = { namespace = "mathml", nature = "mixed", pua = "mathml", pdf = "mprescripts" }, mn = { namespace = "context", nature = "mixed", pdf = "Span" }, mi = { namespace = "context", nature = "mixed", pdf = "Span" }, mo = { namespace = "context", nature = "mixed", pdf = "Span" }, ms = { namespace = "context", nature = "mixed", pdf = "Span" }, mrow = { namespace = "context", nature = "display", pdf = "Span" }, msubsup = { namespace = "context", nature = "display", pdf = "Span" }, msub = { namespace = "context", nature = "display", pdf = "Span" }, msup = { namespace = "context", nature = "display", pdf = "Span" }, merror = { namespace = "context", nature = "mixed", pdf = "Span" }, munderover = { namespace = "context", nature = "display", pdf = "Span" }, munder = { namespace = "context", nature = "display", pdf = "Span" }, mover = { namespace = "context", nature = "display", pdf = "Span" }, mtext = { namespace = "context", nature = "mixed", pdf = "Span" }, mfrac = { namespace = "context", nature = "display", pdf = "Span" }, mroot = { namespace = "context", nature = "display", pdf = "Span" }, msqrt = { namespace = "context", nature = "display", pdf = "Span" }, mfenced = { namespace = "context", nature = "display", pdf = "Span" }, maction = { namespace = "context", nature = "display", pdf = "Span" }, mmultiscripts = { namespace = "context", nature = "display", pdf = "Span" }, mprescripts = { namespace = "context", nature = "mixed", pdf = "Span" }, -- these are internal ones mstack = { namespace = "context", nature = "display", pdf = "Span" }, mstacker = { namespace = "context", nature = "display", pdf = "Span" }, mstackertop = { namespace = "context", nature = "display", pdf = "Span" }, mstackerbot = { namespace = "context", nature = "display", pdf = "Span" }, mstackermid = { namespace = "context", nature = "display", pdf = "Span" }, mextensible = { namespace = "context", nature = "display", pdf = "Span" }, mdelimited = { namespace = "context", nature = "display", pdf = "Span" }, mdelimitedstack = { namespace = "context", nature = "display", pdf = "Span" }, mfunction = { namespace = "context", nature = "mixed", pdf = "Span" }, mfunctionstack = { namespace = "context", nature = "display", pdf = "Span" }, mfraction = { namespace = "context", nature = "display", pdf = "Span" }, mfractionstack = { namespace = "context", nature = "display", pdf = "Span" }, munit = { namespace = "context", nature = "mixed", pdf = "Span" }, mdigits = { namespace = "context", nature = "mixed", pdf = "Span" }, mc = { namespace = "context", nature = "mixed", pdf = "Span" }, -- these are also wrapped mtable = { namespace = "mathml", nature = "display", pdf = "mtable", }, mtr = { namespace = "mathml", nature = "display", pdf = "mtr", }, mtd = { namespace = "mathml", nature = "display", pdf = "mtd", }, ignore = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, private = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, metadata = { namespace = "context", nature = "display", pdf = "NonStruct" }, metavariable = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, mid = { namespace = "context", nature = "inline", pdf = "NonStruct" }, sub = { namespace = "context", nature = "inline", pdf = "NonStruct" }, sup = { namespace = "context", nature = "inline", pdf = "NonStruct" }, subsup = { namespace = "context", nature = "inline", pdf = "NonStruct" }, combination = { namespace = "context", nature = "display", pdf = "NonStruct" }, combinationpair = { namespace = "context", nature = "display", pdf = "NonStruct" }, combinationcontent = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, combinationcaption = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, publications = { namespace = "context", nature = "display", pdf = "NonStruct" }, publication = { namespace = "context", nature = "mixed", pdf = "NonStruct" }, pubfld = { namespace = "context", nature = "inline", pdf = "NonStruct" }, citation = { namespace = "context", nature = "inline", pdf = "NonStruct" }, cite = { namespace = "context", nature = "inline", pdf = "NonStruct" }, narrower = { namespace = "context", nature = "display", pdf = "NonStruct" }, block = { namespace = "context", nature = "display", pdf = "NonStruct" }, userdata = { namespace = "context", nature = "display", pdf = "NonStruct" }, quantity = { namespace = "context", nature = "inline", pdf = "NonStruct" }, unit = { namespace = "context", nature = "inline", pdf = "NonStruct" }, verse = { namespace = "context", nature = "display", pdf = "NonStruct" }, versetag = { namespace = "context", nature = "inline", pdf = "NonStruct" }, verseseparator = { namespace = "context", nature = "inline", pdf = "NonStruct" }, versecontent = { namespace = "context", nature = "inline", pdf = "NonStruct" }, } local overloads = allocate { -- this is needed for crappy pdf support } tags.properties = properties tags.overloads = overloads directives.register("backend.usetags", function(v) if type(v) == "string" then local fullname = resolvers.findfile("lpdf-tag-imp-"..v..".lmt") or "" if fullname ~= "" then local n = 0 local c = 0 local data = table.load(fullname) if data then local merge = data.mapping if merge then for k, v in next, merge do local p = properties[k] if p and p.pdf ~= v.pdf then p.pdf = v.pdf p.pua = v.pua or p.pua n = n + 1 end end end merge = data.overloads if merge then for maintag, details in sortedhash(merge) do local mapping = details.mapping if mapping then local okay = { } for detail, spec in sortedhash(mapping) do local tag = spec.tag local pdf = spec.pdf local pua = spec.pua if tag and pdf and pua then if properties[tag] then report_tags("tag %a can't be overloaded",tag) else okay[detail] = spec properties[tag] = spec spec.namespace = "user" spec.original = { maintag, detail } c = c + 1 end else report_tags("tag %a needs pdf and pua field",tag) end end if next(okay) then overloads[maintag] = { criterium = details.criterium, mapping = okay, } end else report_tags("tag %a overload needs mapping",tag) end end end end report_tags("%i pdf tags overloaded, %i crappy tags added, cross your fingers",n,c) end end end) local patterns = setmetatableindex(function(t,tag) local v = topattern("^" .. tag .. ">") t[tag] = v return v end) function tags.locatedtag(tag) local attribute = texgetattribute(a_tagged) if attribute >= 0 then local specification = taglist[attribute] if specification then local taglist = specification.taglist local pattern = patterns[tag] for i=#taglist,1,-1 do local t = taglist[i] if find(t,pattern) then return t end end end else -- enabled but not auto end return false -- handy as bogus index end function structures.atlocation(str) -- not used local specification = taglist[texgetattribute(a_tagged)] if specification then local list = specification.taglist if list then local pattern = patterns[str] for i=#list,1,-1 do if find(list[i],pattern) then return true end end end end end function tags.setproperty(tag,key,value) local p = properties[tag] if p then p[key] = value else properties[tag] = { [key] = value, namespace = "user" } end end function tags.setaspect(key,value) local tag = chain[stacksize] if tag then local p = properties[tag] if p then p[key] = value else properties[tag] = { [key] = value, namespace = "user" } end end end function tags.registermetadata(data) local d = settings_to_hash(data) if #chain > 1 then if metadata then merge(metadata,d) else metadata = d end else merge(documentdata,d) end end function tags.getmetadata() return documentdata or { } end function tags.registerextradata(name,serializer) if type(serializer) == "function" then if extradata then extradata[name] = serializer else extradata = { [name] = serializer } end end end function tags.getextradata() return extradata end function tags.enabled() return enabled end local ignored = { } tags.ignored = ignored local function enabletags() if not enabled then if tex.systemmodes.export then nodes.tasks.enableaction("math","noads.handlers.tags") else codeinjections.enabletags() end enabled = true end end function tags.start(tag,specification) if not enabled then enabletags() end -- labels[tag] = tag -- can go away -- local attribute = #taglist + 1 local tagindex = (ids[tag] or 0) + 1 if tag == "ignore" then ignored[attribute] = true end -- local completetag = tag .. ">" .. tagindex -- ids[tag] = tagindex lasttags[tag] = tagindex stacksize = stacksize + 1 -- chain[stacksize] = completetag stack[stacksize] = attribute tagcontext[tag] = completetag -- local tagnesting = { unpack(chain,1,stacksize) } -- a copy so we can add actualtext -- if specification then specification.attribute = attribute specification.tagindex = tagindex specification.taglist = tagnesting specification.tagname = tag if metadata then specification.metadata = metadata metadata = nil end local userdata = specification.userdata if userdata == "" then specification.userdata = nil elseif type(userdata) == "string" then specification.userdata = settings_to_hash(userdata) end local detail = specification.detail if detail == "" then specification.detail = nil end local parents = specification.parents if parents == "" then specification.parents = nil end else specification = { attribute = attribute, tagindex = tagindex, taglist = tagnesting, tagname = tag, metadata = metadata, } metadata = nil end -- taglist[attribute] = specification specifications[completetag] = specification -- if completetag == "document>1" then specification.metadata = documentdata end -- texsetattribute(a_tagged,attribute) return attribute end -- kind of messy: function tags.restart(attribute) stacksize = stacksize + 1 if type(attribute) == "number" then local taglist = taglist[attribute].taglist chain[stacksize] = taglist[#taglist] else chain[stacksize] = attribute -- a string attribute = #taglist + 1 taglist[attribute] = { taglist = { unpack(chain,1,stacksize) } } end stack[stacksize] = attribute texsetattribute(a_tagged,attribute) return attribute end do local tag_ignore_level = 1 local tag_document_level = 2 local tagstack = { } -- todo: less push if the attribute is unchanged function tags.push(attribute) if not attribute then attribute = tag_document_level end insert(tagstack, { texgetattribute(a_tagged), stacksize, stack[stacksize], chain }) chain = attribute and { unpack(taglist[attribute].taglist) } or { } stacksize = #chain stack[stacksize] = attribute end function tags.pop() local s = remove(tagstack) stacksize = s[2] chain = s[4] stack[stacksize] = s[3] texsetattribute(a_tagged,s[1]) end end function tags.stop() if stacksize > 0 then stacksize = stacksize - 1 end local t = stack[stacksize] if not t then if trace_tags then report_tags("ignoring end tag, previous chain: %s",stacksize > 0 and concat(chain," ",1,stacksize) or "none") end t = unsetvalue end texsetattribute(a_tagged,t) return t end function tags.getid(tag,detail) return ids[tag] or "?" end function tags.last(tag) return lasttags[tag] -- or false end function tags.lastinchain(tag) if tag and tag ~= "" then return tagcontext[tag] else return chain[stacksize] end end local strip = C((1-S(">"))^1) function tags.elementtag() local fulltag = chain[stacksize] if fulltag then return lpegmatch(strip,fulltag) end end function tags.strip(fulltag) return lpegmatch(strip,fulltag) end function tags.setuserproperties(tag,list) if not list or list == "" then tag, list = chain[stacksize], tag else tag = tagcontext[tag] end if tag then -- an attribute now local l = settings_to_hash(list) local s = specifications[tag] if s then local u = s.userdata if u then for k, v in next, l do u[k] = v end else s.userdata = l end else -- error end end end function tags.handler(head) -- we need a dummy return head, false end statistics.register("structure elements", function() if enabled then if stacksize > 0 then for i=1,stacksize do if not chain[i] then chain[i] = "ERROR" end end return format("%s element chains identified, open chain: %s ",#taglist,concat(chain," => ",1,stacksize)) else return format("%s element chains identified",#taglist) end end end) directives.register("backend.addtags", function(v) if not enabled then enabletags() end end) -- interface local starttag = tags.start implement { name = "strc_tags_start", public = true, protected = true, actions = starttag, arguments = "argument", } implement { name = "strc_tags_stop", public = true, protected = true, actions = tags.stop, } implement { name = "strc_tags_start_userdata", public = true, protected = true, actions = function(tag,userdata) starttag(tag,{ userdata = userdata }) end, arguments = { "optional", "optional" }, } implement { name = "strc_tags_start_detail", public = true, protected = true, actions = function(tag,detail) starttag(tag,{ detail = detail }) end, arguments = "2 arguments", } implement { name = "strc_tags_start_ignore", public = true, protected = true, actions = function(detail) starttag("ignore",{ detail = detail }) end, arguments = { "argument" }, } implement { name = "strc_tags_start_chained", public = true, protected = true, actions = function(tag,detail,parents) starttag(tag,{ detail = detail, parents = parents }) end, arguments = "3 arguments", } implement { name = "strc_tags_set_aspect", public = true, protected = true, actions = tags.setaspect, arguments = "2 arguments" } implement { name = "settagproperty", actions = tags.setproperty, arguments = "3 arguments" } implement { name = "setelementbackendtag", public = true, protected = true, actions = tags.setproperty, -- arguments = { "optional", "'backend'", "optional" }, arguments = { "optional", "'pdf'", "optional" }, } implement { name = "setelementnature", public = true, protected = true, actions = tags.setproperty, arguments = { "optional", "'nature'", "optional" }, } implement { name = "strc_tags_get_element_tag", public = true, protected = true, actions = { tags.elementtag, context } } implement { name = "strc_tags_set_element_user_properties", public = true, protected = true, actions = tags.setuserproperties, arguments = { "optional", "optional" }, } implement { name = "doifelseinelement", public = true, protected = true, actions = { structures.atlocation, commands.doifelse }, arguments = "argument", } implement { name = "settaggedmetadata", public = true, protected = true, actions = tags.registermetadata, arguments = "optional", } implement { name = "strc_tags_document_push", protected = true, public = true, actions = tags.push } implement { name = "strc_tags_document_pop", protected = true, public = true, actions = tags.pop }