1if not modules then modules = { } end modules ['back-exp'] = {
2 version = 1.001,
3 comment = "companion to back-exp.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43local next, type, tonumber = next, type, tonumber
44local sub, gsub, match = string.sub, string.gsub, string.match
45local validstring = string.valid
46local lpegmatch = lpeg.match
47local utfchar, utfvalues, utflen = utf.char, utf.values, utf.len
48local concat, merge, sort, setmetatableindex = table.concat, table.merge, table.sort, table.setmetatableindex
49local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
50local formatters = string.formatters
51local todimen = number.todimen
52local replacetemplate = utilities.templates.replace
53local settings_to_array = utilities.parsers.settings_to_array
54local settings_to_hash = utilities.parsers.settings_to_hash
55
56local addsuffix, joinfile, nameonly, basename, filesuffix = file.addsuffix, file.join, file.nameonly, file.basename, file.suffix
57
58local trace_export = false trackers.register ("export.trace", function(v) trace_export = v end)
59local trace_spacing = false trackers.register ("export.trace.spacing", function(v) trace_spacing = v end)
60local trace_details = false trackers.register ("export.trace.details", function(v) trace_details = v end)
61
62local less_state = false directives.register("export.lessstate", function(v) less_state = v end)
63local show_comment = true directives.register("export.comment", function(v) show_comment = v end)
64local only_images = false directives.register("export.images", function(v) only_images = v end)
65
66
67
68
69
70
71
72
73local report_export = logs.reporter("backend","export")
74
75local nodes = nodes
76local attributes = attributes
77
78local variables = interfaces.variables
79local v_yes <const> = variables.yes
80local v_no <const> = variables.no
81local v_xml <const> = variables.xml
82local v_hidden <const> = variables.hidden
83
84local implement = interfaces.implement
85
86local tasks = nodes.tasks
87local fontchar = fonts.hashes.characters
88local fontquads = fonts.hashes.quads
89local languagenames = languages.numbers
90
91local texgetcount = tex.getcount
92
93local references = structures.references
94local structurestags = structures.tags
95local taglist = structurestags.taglist
96local specifications = structurestags.specifications
97local properties = structurestags.properties
98local locatedtag = structurestags.locatedtag
99
100structurestags.usewithcare = { }
101
102local starttiming = statistics.starttiming
103local stoptiming = statistics.stoptiming
104
105local characterdata = characters.data
106local overloads = fonts.mappings.overloads
107
108
109
110local exportversion <const> = "0.36"
111local mathmlns <const> = "http://www.w3.org/1998/Math/MathML"
112local contextns <const> = "http://www.contextgarden.net/context/export"
113local cssnamespaceurl <const> = "@namespace context url('%namespace%') ;"
114local cssnamespace <const> = "context|"
115
116
117local usecssnamespace = false
118
119local nofcurrentcontent = 0
120local currentcontent = { }
121local currentnesting = nil
122local currentattribute = nil
123local last = nil
124local currentparagraph = nil
125
126local noftextblocks = 0
127
128
129local hyphen = utfchar(0xAD)
130local tagsplitter = structurestags.patterns.splitter
131
132
133local threshold = 65536
134local indexing = false
135local keephyphens = false
136local exportproperties = false
137
138local finetuning = { }
139
140local treestack = { }
141local nesting = { }
142local currentdepth = 0
143
144local wrapups = { }
145
146local tree = { data = { }, fulltag == "root" }
147local roottree = tree
148local treehash = { }
149local extras = { }
150local checks = { }
151local fixes = { }
152local finalizers = { }
153local nofbreaks = 0
154local used = { }
155local exporting = false
156local restart = false
157local specialspaces = { [0x20] = " " }
158local somespace = { [0x20] = true, [" "] = true }
159local entities = { ["&"] = "&", [">"] = ">", ["<"] = "<" }
160local attribentities = { ["&"] = "&", [">"] = ">", ["<"] = "<", ['"'] = "quot;" }
161
162local p_entity = lpeg.replacer(entities)
163local p_attribute = lpeg.replacer(attribentities)
164local p_escaped = lpeg.patterns.xml.escaped
165
166local f_tagid = formatters["%s-%04i"]
167
168
169
170
171
172
173
174local defaultnature = "mixed"
175
176setmetatableindex(used, function(t,k)
177 if k then
178 local v = { }
179 t[k] = v
180 return v
181 end
182end)
183
184local f_entity = formatters["&#x%X;"]
185local f_attribute = formatters[" %s=%q"]
186local f_property = formatters[" %s%s=%q"]
187
188setmetatableindex(specialspaces, function(t,k)
189 local v = utfchar(k)
190 t[k] = v
191 entities[v] = f_entity(k)
192 somespace[k] = true
193 somespace[v] = true
194 return v
195end)
196
197
198
199
200local mathtags = {
201 msubsup = "mathml",
202 msub = "mathml",
203 msup = "mathml",
204 mn = "mathml",
205 mi = "mathml",
206 ms = "mathml",
207 mo = "mathml",
208 mc = "mathml",
209 mtext = "mathml",
210 mrow = "mathml",
211 mfrac = "mathml",
212 mroot = "mathml",
213 msqrt = "mathml",
214 munderover = "mathml",
215 munder = "mathml",
216 mover = "mathml",
217 merror = "mathml",
218 math = "mathml",
219 mrow = "mathml",
220 mtable = "mathml",
221 mtr = "mathml",
222 mtd = "mathml",
223 mfenced = "mathml",
224 maction = "mathml",
225 mspace = "mathml",
226 mmultiscripts = "mathml",
227}
228
229local function attribute(key,value)
230 if value and value ~= "" then
231 return f_attribute(key,lpegmatch(p_attribute,value))
232 else
233 return ""
234 end
235end
236
237local function setattribute(di,key,value,escaped)
238 if value and value ~= "" then
239 local a = di.attributes
240 if escaped then
241 value = lpegmatch(p_escaped,value)
242 end
243 if not a then
244 di.attributes = { [key] = value }
245 else
246 a[key] = value
247 end
248 end
249end
250
251local listdata = { }
252
253function wrapups.hashlistdata()
254 local c = structures.lists.collected
255 for i=1,#c do
256 local ci = c[i]
257 local tag = ci.references.tag
258 if tag then
259 local m = ci.metadata
260 local t = m.kind .. ">" .. tag
261 listdata[t] = ci
262 end
263 end
264end
265
266function structurestags.setattributehash(attr,key,value)
267 local specification = taglist[attr]
268 if specification then
269 specification[key] = value
270 else
271
272 end
273end
274
275local usedstyles = { }
276local usedimages = { }
277local referencehash = { }
278local destinationhash = { }
279
280structurestags.backend = {
281 setattribute = setattribute,
282 extras = extras,
283 checks = checks,
284 fixes = fixes,
285 listdata = listdata,
286 finalizers = finalizers,
287 usedstyles = usedstyles,
288 usedimages = usedimages,
289 referencehash = referencehash,
290 destinationhash = destinationhash,
291}
292
293local namespacetemplate <const> = [[
294/* %what% for file %filename% */
295
296%cssnamespaceurl%
297]]
298
299do
300
301local documenttemplate <const> = [[
302:root {
303 --margin: 2.5em;
304}
305
306document,
307%namespace%div.document {
308 font-family : TextNormal ;
309 font-size : %size% ;
310 max-width : calc(var(--margin) + %width%) ;
311 text-align : %align% ;
312 hyphens : %hyphens% ;
313 zoom : %zoom% ;
314}
315
316/* There is no real solution for boldening, 900 is not enough. */
317
318math {
319 font-family : MathNormal ;
320}
321
322math[data-lmtx-family="bold"] {
323 font-family : MathNormal ;
324 font-weight : 900 ;
325}]]
326
327local styletemplate <const> = [[
328%element%[detail="%detail%"],
329%namespace%div.%element%.%detail% {
330 display : inline ;
331 font-style : %style% ;
332 font-variant : %variant% ;
333 font-weight : %weight% ;
334 font-family : %family% ;
335 color : %color% ;
336}]]
337
338 local numbertoallign = { [0] =
339 "justify", ["0"] = "justify", [variables.normal ] = "justify",
340 "right", ["1"] = "right", [variables.flushright] = "right",
341 "center", ["2"] = "center", [variables.middle ] = "center",
342 "left", ["3"] = "left", [variables.flushleft ] = "left",
343 }
344
345 function wrapups.allusedstyles(filename)
346 local result = { replacetemplate(namespacetemplate, {
347 what = "styles",
348 filename = filename,
349 namespace = contextns,
350
351 cssnamespaceurl = cssnamespaceurl,
352 },false,true) }
353
354 local bodyfont = finetuning.bodyfont
355 local width = finetuning.width
356 local hyphen = finetuning.hyphen
357 local align = finetuning.align
358 local zoom = finetuning.zoom
359 local margin = finetuning.margin
360
361
362
363
364
365
366
367 if type(bodyfont) == "number" then
368 bodyfont = todimen(bodyfont)
369 else
370 bodyfont = "12pt"
371 end
372 if type(width) == "number" then
373 width = todimen(width) or "50em"
374 else
375 width = "50em"
376 end
377 if type(margin) == "number" then
378 margin = todimen(margin) or "2.5em"
379 else
380 margin = "2.5em"
381 end
382 if hyphen == v_yes then
383 hyphen = "manual"
384 else
385 hyphen = "auto"
386 end
387 if align then
388 align = numbertoallign[align]
389 end
390 if not align then
391 align = hyphen and "justify" or "inherited"
392 end
393 if not zoom then
394 zoom = tex.sp("12pt")/tex.sp(bodyfont)
395 end
396
397 result[#result+1] = replacetemplate(documenttemplate,{
398 size = bodyfont,
399 width = width,
400 align = align,
401 hyphens = hyphen,
402 zoom = formatters["%0.3f"](zoom),
403 })
404
405 local colorspecification = xml.css.colorspecification
406 local fontspecification = xml.css.fontspecification
407 for element, details in sortedhash(usedstyles) do
408 for detail, data in sortedhash(details) do
409 local color = data.color
410 local style = data.style
411 local c = colorspecification(color)
412 local s = structures.tags.backend.fontfaced(style)
413 if not c then
414 c = "inherit"
415 end
416 if s and s.family and s.family ~= "" then
417 if not s.style then s.style = "normal" end
418 if not s.variant then s.variant = "normal" end
419 if not s.weight then s.weight = "normal" end
420 else
421 s = fontspecification(style)
422 if not s.style then s.style = "inherit" end
423 if not s.variant then s.variant = "inherit" end
424 if not s.weight then s.weight = "inherit" end
425 if not s.family then s.family = "inherit" end
426 end
427 local detail = gsub(detail,"[^A-Za-z0-9]+","-")
428 result[#result+1] = replacetemplate(styletemplate,{
429 namespace = usecssnamespace and cssnamespace or "",
430 element = element,
431 detail = detail,
432 style = s.style,
433 variant = s.variant,
434 weight = s.weight,
435 family = s.family,
436 color = c,
437 display = s.display and "block" or nil,
438 })
439 end
440 end
441 return concat(result,"\n\n")
442 end
443
444 function wrapups.allusedfonts(filename)
445 local list, used = structures.tags.backend.tofontface(finetuning.fontfaces)
446 local result = { replacetemplate(namespacetemplate, {
447 what = "fonts",
448 filename = filename,
449 namespace = contextns,
450
451 cssnamespaceurl = cssnamespaceurl,
452 },false,true) }
453 result[#result+1] = list
454 result = concat(result,"\n\n")
455 for k, v in sortedhash(used) do
456 report_export("font face %s%s : %s",v[1],v[2],v[3])
457 end
458 return result
459 end
460
461end
462
463do
464
465local imagetemplate_1 <const> = [[
466[image="%id%"]%hover% {
467 display : block ;
468 content : url('%url%') ;
469 width : %width% ;
470 height : %height% ;
471}]]
472
473local imagetemplate_2 <const> = [[
474[image="%id%"]%hover% {
475 display : inline ;
476 content : url('%url%') ;
477 width : %width% ;
478 height : %height% ;
479 margin-top : -%drop% ;
480 top : %drop% ;
481 position : relative ;
482}]]
483
484 local f_svgname = formatters["%s.svg"]
485 local f_svgpage = formatters["%s-page-%s.svg"]
486 local collected = { }
487
488 local function usedname(name,page)
489 if filesuffix(name) == "pdf" then
490
491 if page and page > 1 then
492 name = f_svgpage(nameonly(name),page)
493 else
494 name = f_svgname(nameonly(name))
495 end
496 end
497 local scheme = url.hasscheme(name)
498 if not scheme or scheme == "file" then
499
500 return joinfile("../images",basename(url.filename(name)))
501 else
502 return name
503 end
504 end
505
506 function wrapups.allusedimages(filename)
507 local result = { replacetemplate(namespacetemplate, {
508 what = "images",
509 filename = filename,
510 namespace = contextns,
511
512 cssnamespaceurl = cssnamespaceurl,
513 },false,true) }
514 local hover = (finetuning.images) and finetuning.images.hover
515 for element, details in sortedhash(usedimages) do
516 for detail, data in sortedhash(details) do
517 local name = data.name
518 local page = tonumber(data.page) or 1
519 local drop = data.drop
520 local spec = {
521 element = element,
522
523id = filename .. "-" .. data.id,
524 name = name,
525 page = page,
526 url = usedname(name,page),
527 width = data.width,
528 height = data.height,
529 drop = drop,
530 used = data.used,
531 namespace = usecssnamespace and cssnamespace or "",
532 hover = hover and data.category == "formula" and ":hover" or "",
533 }
534 result[#result+1] = replacetemplate(
535 drop and imagetemplate_2 or imagetemplate_1,
536 spec
537 )
538 collected[detail] = spec
539 end
540 end
541 return concat(result,"\n\n")
542 end
543
544 function wrapups.uniqueusedimages()
545 return collected
546 end
547
548end
549
550
551
552properties.vspace = { export = "break", nature = "display" }
553
554
555local function makebreaklist(list)
556 nofbreaks = nofbreaks + 1
557 local t = { }
558 local l = list and list.taglist
559 if l then
560 for i=1,#list do
561 t[i] = l[i]
562 end
563 end
564 t[#t+1] = "break>" .. nofbreaks
565 return { taglist = t }
566end
567
568local breakattributes = {
569 type = "collapse"
570}
571
572local function makebreaknode(attributes)
573 nofbreaks = nofbreaks + 1
574 return {
575 tg = "break",
576 fulltag = "break>" .. nofbreaks,
577 n = nofbreaks,
578 element = "break",
579 nature = "display",
580 attributes = attributes or nil,
581
582
583
584 }
585end
586
587do
588
589 local fields = { "title", "subtitle", "author", "keywords", "url", "version" }
590
591 local ignoredelements = false
592
593 local function checkdocument(root)
594 local data = root.data
595 if data then
596 for i=1,#data do
597 local di = data[i]
598 local tg = di.tg
599 if tg == "noexport" then
600 local s = specifications[di.fulltag]
601 local u = s and s.userdata
602 if u then
603 local comment = u.comment
604 if comment then
605 di.element = "comment"
606 di.data = { { content = comment } }
607 u.comment = nil
608 else
609 data[i] = false
610 end
611 else
612 data[i] = false
613 end
614 elseif di.content then
615
616 elseif tg == "ignore" then
617 di.element = ""
618 checkdocument(di)
619 elseif ignoredelements and ignoredelements[tg] then
620 di.element = ""
621 checkdocument(di)
622 else
623 checkdocument(di)
624 end
625 end
626 end
627 end
628
629 function extras.document(di,element,n,fulltag)
630 local language = languagenames[texgetcount("mainlanguagenumber")]
631 setattribute(di,"language",language)
632 setattribute(di,"xml:lang",language)
633 if not less_state then
634 setattribute(di,"file",tex.jobname)
635 setattribute(di,"date",os.fulltime())
636 setattribute(di,"context",environment.version)
637 setattribute(di,"version",exportversion)
638 local identity = interactions.general.getidentity()
639 for i=1,#fields do
640 local key = fields[i]
641 local value = identity[key]
642 if value and value ~= "" then
643 setattribute(di,key,value)
644 end
645 end
646 end
647 checkdocument(di)
648 end
649
650 implement {
651 name = "ignoretagsinexport",
652 arguments = "string",
653 actions = function(list)
654 for tag in string.gmatch(list,"[a-z]+") do
655 if ignoredelements then
656 ignoredelements[tag] = true
657 else
658 ignoredelements = { [tag] = true }
659 end
660 end
661 end,
662 }
663
664end
665
666
667
668do
669
670 local f_detail = formatters[' detail="%s"']
671 local f_chain = formatters[' chain="%s"']
672 local f_index = formatters[' n="%s"']
673 local f_spacing = formatters['<c p="%s">%s</c>']
674
675 local f_empty_inline = formatters["<%s/>"]
676 local f_empty_mixed = formatters["%w<%s/>\n"]
677 local f_empty_display = formatters["\n%w<%s/>\n"]
678 local f_empty_inline_attr = formatters["<%s%s/>"]
679 local f_empty_mixed_attr = formatters["%w<%s%s/>"]
680 local f_empty_display_attr = formatters["\n%w<%s%s/>\n"]
681
682 local f_begin_inline = formatters["<%s>"]
683 local f_begin_mixed = formatters["%w<%s>"]
684 local f_begin_display = formatters["\n%w<%s>\n"]
685 local f_begin_inline_attr = formatters["<%s%s>"]
686 local f_begin_mixed_attr = formatters["%w<%s%s>"]
687 local f_begin_display_attr = formatters["\n%w<%s%s>\n"]
688
689 local f_end_inline = formatters["</%s>"]
690 local f_end_mixed = formatters["</%s>\n"]
691 local f_end_display = formatters["%w</%s>\n"]
692
693 local f_begin_inline_comment = formatters["<!-- %s --><%s>"]
694 local f_begin_mixed_comment = formatters["%w<!-- %s --><%s>"]
695 local f_begin_display_comment = formatters["\n%w<!-- %s -->\n%w<%s>\n"]
696 local f_begin_inline_attr_comment = formatters["<!-- %s --><%s%s>"]
697 local f_begin_mixed_attr_comment = formatters["%w<!-- %s --><%s%s>"]
698 local f_begin_display_attr_comment = formatters["\n%w<!-- %s -->\n%w<%s%s>\n"]
699
700 local f_comment_begin_inline = formatters["<!-- begin %s -->"]
701 local f_comment_begin_mixed = formatters["%w<!-- begin %s -->"]
702 local f_comment_begin_display = formatters["\n%w<!-- begin %s -->\n"]
703
704 local f_comment_end_inline = formatters["<!-- end %s -->"]
705 local f_comment_end_mixed = formatters["<!-- end %s -->\n"]
706 local f_comment_end_display = formatters["%w<!-- end %s -->\n"]
707
708 local f_metadata_begin = formatters["\n%w<metadata>\n"]
709 local f_metadata = formatters["%w<metavariable name=%q>%s</metavariable>\n"]
710 local f_metadata_end = formatters["%w</metadata>\n"]
711
712 local function attributes(a)
713 local r = { }
714 local n = 0
715 for k, v in next, a do
716 n = n + 1
717 r[n] = f_attribute(k,tostring(v))
718 end
719 sort(r)
720 return concat(r,"")
721 end
722
723 local function properties(a)
724 local r = { }
725 local n = 0
726 for k, v in next, a do
727 n = n + 1
728 r[n] = f_property(exportproperties,k,tostring(v))
729 end
730 sort(r)
731 return concat(r,"")
732 end
733
734 local depth = 0
735 local inline = 0
736
737 local function emptytag(result,element,nature,di)
738 local a = di.attributes
739 if a then
740 if nature == "display" then
741 result[#result+1] = f_empty_display_attr(depth,element,attributes(a))
742 elseif nature == "mixed" then
743 result[#result+1] = f_empty_mixed_attr(depth,element,attributes(a))
744 else
745 result[#result+1] = f_empty_inline_attr(element,attributes(a))
746 end
747 else
748 if nature == "display" then
749 result[#result+1] = f_empty_display(depth,element)
750 elseif nature == "mixed" then
751 result[#result+1] = f_empty_mixed(depth,element)
752 else
753 result[#result+1] = f_empty_inline(element)
754 end
755 end
756 end
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776 local function stripspaces(di)
777 local d = di.data
778 for i=1,#d do
779 local di = d[i]
780 if not di.tg then
781 di.content = ""
782 end
783 end
784 end
785
786 local function begintag(result,element,nature,di,skip)
787 local index = di.n
788 local fulltag = di.fulltag
789 local specification = specifications[fulltag] or { }
790 local comment = di.comment
791 local detail = specification.detail
792 if skip == "comment" then
793 if show_comment then
794 if nature == "inline" or inline > 0 then
795 result[#result+1] = f_comment_begin_inline(element)
796 inline = inline + 1
797 elseif nature == "mixed" then
798 result[#result+1] = f_comment_begin_mixed(depth,element)
799 depth = depth + 1
800 inline = 1
801 else
802 result[#result+1] = f_comment_begin_display(depth,element)
803 depth = depth + 1
804 end
805 end
806 elseif skip then
807
808 else
809
810 local n = 0
811 local r = { }
812 if detail then
813 detail = gsub(detail,"[^A-Za-z0-9]+","-")
814 specification.detail = detail
815 n = n + 1
816 r[n] = f_detail(detail)
817 end
818 local parents = specification.parents
819 if parents then
820 parents = gsub(parents,"[^A-Za-z0-9 ]+","-")
821 specification.parents = parents
822 n = n + 1
823 r[n] = f_chain(parents)
824 end
825 if indexing and index then
826 n = n + 1
827 r[n] = f_index(index)
828 end
829
830 local extra = extras[element]
831 if extra then
832 extra(di,element,index,fulltag)
833 end
834
835 if di.record then
836 stripspaces(di)
837 end
838
839 if exportproperties then
840 local p = specification.userdata
841 if not p then
842
843 elseif exportproperties == v_yes then
844 n = n + 1
845 r[n] = attributes(p)
846 else
847 n = n + 1
848 r[n] = properties(p)
849 end
850 end
851 local a = di.attributes
852 if a then
853 if trace_spacing then
854 a.p = di.parnumber or 0
855 end
856 n = n + 1
857 r[n] = attributes(a)
858 elseif trace_spacing then
859 n = n + 1
860 r[n] = attributes { p = di.parnumber or 0 }
861 end
862 if n == 0 then
863 if nature == "inline" or inline > 0 then
864 if show_comment and comment then
865 result[#result+1] = f_begin_inline_comment(comment,element)
866 else
867 result[#result+1] = f_begin_inline(element)
868 end
869 inline = inline + 1
870 elseif nature == "mixed" then
871 if show_comment and comment then
872 result[#result+1] = f_begin_mixed_comment(depth,comment,element)
873 else
874 result[#result+1] = f_begin_mixed(depth,element)
875 end
876 depth = depth + 1
877 inline = 1
878 else
879 if show_comment and comment then
880 result[#result+1] = f_begin_display_comment(depth,comment,depth,element)
881 else
882 result[#result+1] = f_begin_display(depth,element)
883 end
884 depth = depth + 1
885 end
886 else
887 r = concat(r,"",1,n)
888 if nature == "inline" or inline > 0 then
889 if show_comment and comment then
890 result[#result+1] = f_begin_inline_attr_comment(comment,element,r)
891 else
892 result[#result+1] = f_begin_inline_attr(element,r)
893 end
894 inline = inline + 1
895 elseif nature == "mixed" then
896 if show_comment and comment then
897 result[#result+1] = f_begin_mixed_attr_comment(depth,comment,element,r)
898 else
899 result[#result+1] = f_begin_mixed_attr(depth,element,r)
900 end
901 depth = depth + 1
902 inline = 1
903 else
904 if show_comment and comment then
905 result[#result+1] = f_begin_display_attr_comment(depth,comment,depth,element,r)
906 else
907 result[#result+1] = f_begin_display_attr(depth,element,r)
908 end
909 depth = depth + 1
910 end
911 end
912 end
913 used[element][detail or ""] = { nature, specification.parents }
914
915 local metadata = specification.metadata
916 if metadata and next(metadata) then
917 result[#result+1] = f_metadata_begin(depth)
918 for k, v in sortedhash(metadata) do
919 if v ~= "" then
920 result[#result+1] = f_metadata(depth+1,k,lpegmatch(p_entity,v))
921 end
922 end
923 result[#result+1] = f_metadata_end(depth)
924 end
925 end
926
927 local function endtag(result,element,nature,di,skip)
928 if skip == "comment" then
929 if show_comment then
930 if nature == "display" and (inline == 0 or inline == 1) then
931 depth = depth - 1
932 result[#result+1] = f_comment_end_display(depth,element)
933 inline = 0
934 elseif nature == "mixed" and (inline == 0 or inline == 1) then
935 depth = depth - 1
936 result[#result+1] = f_comment_end_mixed(element)
937 inline = 0
938 else
939 inline = inline - 1
940 result[#result+1] = f_comment_end_inline(element)
941 end
942 end
943 elseif skip then
944
945 else
946 if nature == "display" and (inline == 0 or inline == 1) then
947 depth = depth - 1
948 result[#result+1] = f_end_display(depth,element)
949 inline = 0
950 elseif nature == "mixed" and (inline == 0 or inline == 1) then
951 depth = depth - 1
952 result[#result+1] = f_end_mixed(element)
953 inline = 0
954 else
955 inline = inline - 1
956 result[#result+1] = f_end_inline(element)
957 end
958 end
959 end
960
961 local function wrapupmath(di)
962
963
964 local a = di.attributes
965 local order = a and (a["data-lmtx-blob"] or a["blob"])
966 if order then
967 local mth = mathematics.getmathblob("xml",order)
968 local txt = mathematics.gettextblob("xml","en",order)
969 if mth then
970 mth = match(mth,"<math[^>]*>(.*)</math>")
971 end
972 if txt and txt ~= "" then
973
974 if a then
975 a["data-lmtx-meaning"] = txt
976 else
977 di.attributes = {
978 ["data-lmtx-meaning"] = txt,
979 }
980 end
981 end
982 return mth
983 end
984 end
985
986 local function flushtree(result,data,nature)
987 local nofdata = #data
988 for i=1,nofdata do
989 local di = data[i]
990 if not di then
991
992 else
993 local content = di.content
994
995 if content then
996
997 local content = lpegmatch(p_entity,content)
998 if i == nofdata and sub(content,-1) == "\n" then
999
1000 if trace_spacing then
1001 result[#result+1] = f_spacing(di.parnumber or 0,sub(content,1,-2))
1002 else
1003 result[#result+1] = sub(content,1,-2)
1004 end
1005 result[#result+1] = " "
1006 else
1007 if trace_spacing then
1008 result[#result+1] = f_spacing(di.parnumber or 0,content)
1009 else
1010 result[#result+1] = content
1011 end
1012 end
1013 elseif not di.collapsed then
1014 local element = di.element
1015 if not element then
1016
1017 elseif element == "break" then
1018 emptytag(result,element,nature,di)
1019 elseif element == "mspace" then
1020
1021 emptytag(result,element,nature,di)
1022 elseif element == "mprescripts" then
1023
1024 emptytag(result,element,nature,di)
1025 elseif element == "" or di.skip == "ignore" then
1026
1027 else
1028 local mth = element == "math" and wrapupmath(di)
1029 if di.before then
1030 flushtree(result,di.before,nature)
1031 end
1032 local natu = di.nature
1033 local skip = di.skip
1034 if di.breaknode then
1035
1036 emptytag(result,"break","display",di)
1037 end
1038 begintag(result,element,natu,di,skip)
1039 if mth and mth ~= "" then
1040 result[#result+1] = mth
1041 else
1042 flushtree(result,di.data,natu)
1043 end
1044 endtag(result,element,natu,di,skip)
1045 if di.after then
1046 flushtree(result,di.after,nature)
1047 end
1048 end
1049 else
1050
1051
1052
1053
1054 end
1055 end
1056 end
1057 end
1058
1059 local function breaktree(tree,parent,parentelement)
1060 local data = tree.data
1061 if data then
1062 local nofdata = #data
1063 local prevelement
1064 local prevnature
1065 local prevparnumber
1066 local newdata = { }
1067 local nofnewdata = 0
1068 for i=1,nofdata do
1069 local di = data[i]
1070 if not di then
1071
1072 elseif di.skip == "ignore" then
1073
1074 elseif di.tg == "ignore" then
1075
1076 elseif di.content then
1077 if di.samepar then
1078 prevparnumber = false
1079 else
1080 local parnumber = di.parnumber
1081 if prevnature == "inline" and prevparnumber and prevparnumber ~= parnumber then
1082 nofnewdata = nofnewdata + 1
1083 if trace_spacing then
1084 newdata[nofnewdata] = makebreaknode { type = "a", p = prevparnumber, n = parnumber }
1085 else
1086 newdata[nofnewdata] = makebreaknode()
1087 end
1088 end
1089 prevelement = nil
1090 prevparnumber = parnumber
1091 end
1092 prevnature = "inline"
1093 nofnewdata = nofnewdata + 1
1094 newdata[nofnewdata] = di
1095 elseif not di.collapsed then
1096 local element = di.element
1097 if element == "break" then
1098 if prevelement == "break" then
1099 di.element = ""
1100 end
1101 prevelement = element
1102 prevnature = "display"
1103 nofnewdata = nofnewdata + 1
1104 newdata[nofnewdata] = di
1105 elseif element == "" or di.skip == "ignore" then
1106
1107 else
1108 if di.samepar then
1109 prevnature = "inline"
1110 prevparnumber = false
1111 else
1112 local nature = di.nature
1113 local parnumber = di.parnumber
1114 if prevnature == "inline" and nature == "inline" and prevparnumber and prevparnumber ~= parnumber then
1115 nofnewdata = nofnewdata + 1
1116 if trace_spacing then
1117 newdata[nofnewdata] = makebreaknode { type = "b", p = prevparnumber, n = parnumber }
1118 else
1119 newdata[nofnewdata] = makebreaknode()
1120 end
1121 end
1122 prevnature = nature
1123 prevparnumber = parnumber
1124 end
1125 prevelement = element
1126 breaktree(di,tree,element)
1127 nofnewdata = nofnewdata + 1
1128 newdata[nofnewdata] = di
1129 end
1130 else
1131 if di.samepar then
1132 prevnature = "inline"
1133 prevparnumber = false
1134 else
1135 local nature = di.nature
1136 local parnumber = di.parnumber
1137 if prevnature == "inline" and nature == "inline" and prevparnumber and prevparnumber ~= parnumber then
1138 nofnewdata = nofnewdata + 1
1139 if trace_spacing then
1140 newdata[nofnewdata] = makebreaknode { type = "c", p = prevparnumber, n = parnumber }
1141 else
1142 newdata[nofnewdata] = makebreaknode()
1143 end
1144 end
1145 prevnature = nature
1146 prevparnumber = parnumber
1147 end
1148 nofnewdata = nofnewdata + 1
1149 newdata[nofnewdata] = di
1150 end
1151 end
1152 tree.data = newdata
1153 end
1154 end
1155
1156
1157
1158
1159 local function showtree(data,when,where)
1160 if data then
1161 for i=1,#data do
1162 local d = data[i]
1163 if type(d) == "table" and d.element then
1164 print(when,where,i,d.element,d.parnumber or 0)
1165 end
1166 end
1167 end
1168 end
1169
1170 local function collapsetree(tree)
1171
1172
1173 for tag, trees in next, treehash do
1174 local d = trees[1].data
1175 if d then
1176 local nd = #d
1177 if nd > 0 then
1178 for i=2,#trees do
1179 local currenttree = trees[i]
1180 local currentdata = currenttree.data
1181 local currentpar = currenttree.parnumber
1182 local previouspar = trees[i-1].parnumber
1183 currenttree.collapsed = true
1184
1185 if previouspar == 0 or not (di and di.content) then
1186 previouspar = nil
1187 end
1188 for j=1,#currentdata do
1189 local cd = currentdata[j]
1190 if not cd or cd == "" then
1191
1192 elseif cd.skip == "ignore" then
1193
1194 elseif cd.content then
1195 if not currentpar then
1196
1197 elseif not previouspar then
1198
1199 elseif currentpar ~= previouspar then
1200 nd = nd + 1
1201 if trace_spacing then
1202 d[nd] = makebreaknode { type = "d", p = previouspar, n = currentpar }
1203 else
1204 d[nd] = makebreaknode()
1205 end
1206 end
1207 previouspar = currentpar
1208 nd = nd + 1
1209 d[nd] = cd
1210 else
1211 nd = nd + 1
1212 d[nd] = cd
1213 end
1214 currentdata[j] = false
1215 end
1216 end
1217 end
1218 end
1219 end
1220
1221 end
1222
1223 local function finalizetree(tree)
1224
1225 for _, finalizer in next, finalizers do
1226 finalizer(tree)
1227 end
1228
1229 end
1230
1231 local function indextree(tree)
1232 local data = tree.data
1233 if data then
1234
1235 local n, new = 0, { }
1236 for i=1,#data do
1237 local d = data[i]
1238 if not d then
1239
1240 elseif d.content then
1241 n = n + 1
1242 new[n] = d
1243 elseif not d.collapsed then
1244 n = n + 1
1245 d.__i__ = n
1246 d.__p__ = tree
1247 indextree(d)
1248 new[n] = d
1249 end
1250 end
1251 tree.data = new
1252
1253 end
1254 end
1255
1256 local function checktree(tree)
1257 local data = tree.data
1258 if data then
1259
1260 for i=1,#data do
1261 local d = data[i]
1262 if type(d) == "table" then
1263 local tg = d.tg
1264 if tg then
1265 local check = checks[tg]
1266 if check then
1267 check(d,data,i)
1268 end
1269 end
1270 checktree(d)
1271 end
1272 end
1273
1274 end
1275 end
1276
1277 local function fixtree(tree)
1278 local data = tree.data
1279 if data then
1280
1281 for i=1,#data do
1282 local d = data[i]
1283 if type(d) == "table" then
1284 local tg = d.tg
1285 if tg then
1286 local fix = fixes[tg]
1287 if fix then
1288 fix(d,data,i)
1289 end
1290 end
1291 fixtree(d)
1292 end
1293 end
1294
1295 end
1296 end
1297
1298 wrapups.flushtree = flushtree
1299 wrapups.breaktree = breaktree
1300 wrapups.collapsetree = collapsetree
1301 wrapups.finalizetree = finalizetree
1302 wrapups.indextree = indextree
1303 wrapups.checktree = checktree
1304 wrapups.fixtree = fixtree
1305
1306end
1307
1308
1309
1310local function push(fulltag,depth)
1311 local tg, n, detail, element, nature, record
1312 local specification = specifications[fulltag]
1313 if specification then
1314 tg = specification.tagname
1315 n = specification.tagindex
1316 detail = specification.detail
1317 elseif not fulltag then
1318 report_export("%w<%s trigger=%q n=%q paragraph=%q index=%q detail=%q>",
1319 currentdepth-1,tg,n,currentattribute or 0,currentparagraph or 0,"error",detail)
1320 return
1321 else
1322
1323 tg, n = lpegmatch(tagsplitter,fulltag)
1324 n = tonumber(n)
1325 end
1326 local p = properties[tg]
1327 if p then
1328 element = p.export or tg
1329 nature = p.nature or "inline"
1330 record = p.record
1331 end
1332 local treedata = tree.data
1333 if not treedata then
1334 report_export("%w<%s trigger=%q n=%q paragraph=%q index=%q detail=%q>",
1335 currentdepth-1,tg,n,currentattribute or 0,currentparagraph or 0,"error",detail)
1336 return
1337 end
1338 local t = {
1339 tg = tg,
1340 fulltag = fulltag,
1341 detail = detail,
1342 n = n,
1343 element = element,
1344 nature = nature,
1345 data = { },
1346 attribute = currentattribute,
1347 parnumber = currentparagraph,
1348 record = record,
1349 }
1350 treedata[#treedata+1] = t
1351 currentdepth = currentdepth + 1
1352 nesting[currentdepth] = fulltag
1353 treestack[currentdepth] = tree
1354 if trace_export then
1355 if detail and detail ~= "" then
1356 report_export("%w<%s trigger=%q n=%q paragraph=%q index=%q detail=%q>",
1357 currentdepth-1,tg,n,currentattribute or 0,currentparagraph or 0,#treedata,detail)
1358 else
1359 report_export("%w<%s trigger=%q n=%q paragraph=%q index=%q>",
1360 currentdepth-1,tg,n,currentattribute or 0,currentparagraph or 0,#treedata)
1361 end
1362 end
1363 tree = t
1364 if tg == "break" then
1365
1366 else
1367 local h = treehash[fulltag]
1368 if h then
1369 h[#h+1] = t
1370 else
1371 treehash[fulltag] = { t }
1372 end
1373 end
1374end
1375
1376local function pop()
1377 if currentdepth > 0 then
1378 local top = nesting[currentdepth]
1379 tree = treestack[currentdepth]
1380 currentdepth = currentdepth - 1
1381 if trace_export then
1382 if top then
1383 report_export("%w</%s>",currentdepth,match(top,"[^>]+"))
1384 else
1385 report_export("</BAD>")
1386 end
1387 end
1388 else
1389 report_export("%w<!-- too many pops -->",currentdepth)
1390 end
1391end
1392
1393local function continueexport()
1394 if nofcurrentcontent > 0 then
1395 if trace_export then
1396 report_export("%w<!-- injecting pagebreak space -->",currentdepth)
1397 end
1398 nofcurrentcontent = nofcurrentcontent + 1
1399 currentcontent[nofcurrentcontent] = " "
1400 end
1401end
1402
1403local function pushentry(current)
1404 if not current then
1405
1406 return
1407 end
1408 current = current.taglist
1409 if not current then
1410
1411 return
1412 end
1413 if restart then
1414 continueexport()
1415 restart = false
1416 end
1417 local newdepth = #current
1418 local olddepth = currentdepth
1419 if trace_export then
1420 report_export("%w<!-- moving from depth %s to %s (%s) -->",currentdepth,olddepth,newdepth,current[newdepth])
1421 end
1422 if olddepth <= 0 then
1423 for i=1,newdepth do
1424 push(current[i],i)
1425 end
1426 else
1427 local difference
1428 if olddepth < newdepth then
1429 for i=1,olddepth do
1430 if current[i] ~= nesting[i] then
1431 difference = i
1432 break
1433 end
1434 end
1435 else
1436 for i=1,newdepth do
1437 if current[i] ~= nesting[i] then
1438 difference = i
1439 break
1440 end
1441 end
1442 end
1443 if difference then
1444 for i=olddepth,difference,-1 do
1445 pop()
1446 end
1447 for i=difference,newdepth do
1448 push(current[i],i)
1449 end
1450 elseif newdepth > olddepth then
1451 for i=olddepth+1,newdepth do
1452 push(current[i],i)
1453 end
1454 elseif newdepth < olddepth then
1455 for i=olddepth,newdepth,-1 do
1456 pop()
1457 end
1458 elseif trace_export then
1459 report_export("%w<!-- staying at depth %s (%s) -->",currentdepth,newdepth,nesting[newdepth] or "?")
1460 end
1461 end
1462 return olddepth, newdepth
1463end
1464
1465local function pushcontent(oldparagraph,newparagraph)
1466 if nofcurrentcontent > 0 then
1467 if oldparagraph then
1468 if currentcontent[nofcurrentcontent] == "\n" then
1469 if trace_export then
1470 report_export("%w<!-- removing newline -->",currentdepth)
1471 end
1472 nofcurrentcontent = nofcurrentcontent - 1
1473 end
1474 end
1475 local content = concat(currentcontent,"",1,nofcurrentcontent)
1476
1477 if content == "" then
1478
1479 elseif somespace[content] and oldparagraph then
1480
1481
1482 else
1483 local olddepth, newdepth
1484 local list = taglist[currentattribute]
1485 if list then
1486 olddepth, newdepth = pushentry(list)
1487 end
1488 if tree then
1489 local td = tree.data
1490 local nd = #td
1491 td[nd+1] = { parnumber = oldparagraph or currentparagraph, content = content }
1492 if trace_export then
1493 report_export("%w<!-- start content with length %s -->",currentdepth,utflen(content))
1494 report_export("%w%s",currentdepth,(gsub(content,"\n","\\n")))
1495 report_export("%w<!-- stop content -->",currentdepth)
1496 end
1497 if olddepth then
1498 for i=newdepth-1,olddepth,-1 do
1499 pop()
1500 end
1501 end
1502 end
1503 end
1504 nofcurrentcontent = 0
1505 end
1506 if oldparagraph then
1507 pushentry(makebreaklist(currentnesting))
1508 if trace_export then
1509 report_export("%w<!-- break added between paragraph %a and %a -->",currentdepth,oldparagraph,newparagraph)
1510 end
1511 end
1512end
1513
1514local function finishexport()
1515 if trace_export then
1516 report_export("%w<!-- start finalizing -->",currentdepth)
1517 end
1518 if nofcurrentcontent > 0 then
1519 if somespace[currentcontent[nofcurrentcontent]] then
1520 if trace_export then
1521 report_export("%w<!-- removing space -->",currentdepth)
1522 end
1523 nofcurrentcontent = nofcurrentcontent - 1
1524 end
1525 pushcontent()
1526 end
1527 for i=currentdepth,1,-1 do
1528 pop()
1529 end
1530 currentcontent = { }
1531 if trace_export then
1532 report_export("%w<!-- stop finalizing -->",currentdepth)
1533 end
1534end
1535
1536
1537
1538local collectresults do
1539
1540 local nodecodes = nodes.nodecodes
1541 local gluecodes = nodes.gluecodes
1542 local listcodes = nodes.listcodes
1543 local whatsitcodes = nodes.whatsitcodes
1544
1545 local subtypes = nodes.subtypes
1546
1547 local userkern_code <const> = nodes.kerncodes.userkern
1548
1549 local hlist_code <const> = nodecodes.hlist
1550 local vlist_code <const> = nodecodes.vlist
1551 local glyph_code <const> = nodecodes.glyph
1552 local glue_code <const> = nodecodes.glue
1553 local kern_code <const> = nodecodes.kern
1554 local disc_code <const> = nodecodes.disc
1555 local whatsit_code <const> = nodecodes.whatsit
1556 local par_code <const> = nodecodes.par
1557
1558 local userskip_code <const> = gluecodes.userskip
1559 local rightskip_code <const> = gluecodes.rightskip
1560 local parfillskip_code <const> = gluecodes.parfillskip
1561 local spaceskip_code <const> = gluecodes.spaceskip
1562 local xspaceskip_code <const> = gluecodes.xspaceskip
1563 local intermathskip_code <const> = gluecodes.intermathskip
1564
1565 local linelist_code <const> = listcodes.line
1566
1567 local userdefinedwhatsit_code <const> = whatsitcodes.userdefined
1568
1569 local privateattribute = attributes.private
1570
1571 local a_image <const> = privateattribute('image')
1572 local a_reference <const> = privateattribute('reference')
1573 local a_destination <const> = privateattribute('destination')
1574 local a_characters <const> = privateattribute('characters')
1575 local a_exportstatus <const> = privateattribute('exportstatus')
1576 local a_tagged <const> = privateattribute('tagged')
1577 local a_taggedpar <const> = privateattribute("taggedpar")
1578 local a_textblock <const> = privateattribute("textblock")
1579
1580 local inline_mark <const> = nodes.pool.userids["margins.inline"]
1581
1582 local nuts = nodes.nuts
1583
1584 local getnext = nuts.getnext
1585 local getdisc = nuts.getdisc
1586 local getlist = nuts.getlist
1587 local getid = nuts.getid
1588 local getattrs = nuts.getattrs
1589 local getattr = nuts.getattr
1590 local setattr = nuts.setattr
1591 local isglyph = nuts.isglyph
1592 local getkern = nuts.getkern
1593 local getwidth = nuts.getwidth
1594 local getclass = nuts.getclass
1595
1596 local startofpar = nuts.startofpar
1597
1598 local nexthlist = nuts.traversers.hlist
1599 local nextnode = nuts.traversers.node
1600
1601 local function addtomaybe(maybewrong,c,paragraph,ap,case)
1602 if trace_export then
1603 report_export("%w<!-- possible paragraph (%i,%i) mixup at %C case %i -->",currentdepth,paragraph or 0,ap or 0,c,case)
1604 else
1605 local s = formatters["%C"](c)
1606 if maybewrong then
1607 maybewrong[#maybewrong+1] = s
1608 else
1609 maybewrong = { s }
1610 end
1611 return maybewrong
1612 end
1613 end
1614
1615 local function showmaybe(maybewrong)
1616 if not trace_export then
1617
1618 end
1619 end
1620
1621 local function showdetail(n,id,subtype)
1622 local a = getattr(n,a_tagged)
1623 local t = taglist[a]
1624 local c = nodecodes[id]
1625 local s = subtypes[id][subtype]
1626 if a and t then
1627 report_export("node %a, subtype %a, tag %a, element %a, tree '% t'",c,s,a,t.tagname,t.taglist)
1628 else
1629 report_export("node %a, subtype %a, untagged",c,s)
1630 end
1631 end
1632
1633 local function collectresults(head,list,pat,pap)
1634 local p
1635 local paragraph
1636 local maybewrong
1637 local pid
1638 for n, id, subtype in nextnode, head do
1639 if trace_details then
1640 showdetail(n,id,subtype)
1641 end
1642 if id == glyph_code then
1643 local c, f = isglyph(n)
1644 local at, ap = getattrs(n,a_tagged,a_taggedpar)
1645 if not at then
1646 at = pat
1647 end
1648 if not at then
1649
1650
1651
1652 elseif at == 0 then
1653
1654 elseif at < 1 then
1655
1656 else
1657 if last ~= at then
1658 local tl = taglist[at]
1659 if not ap then
1660 ap = pap
1661 end
1662
1663
1664
1665 tl.class = getclass(n)
1666 pushcontent()
1667 currentnesting = tl
1668 currentparagraph = ap
1669 currentattribute = at
1670 last = at
1671 pushentry(currentnesting)
1672 if trace_export then
1673 report_export("%w<!-- processing glyph %C tagged %a -->",currentdepth,c,at)
1674 end
1675
1676
1677 local r, d = getattrs(n,a_reference,a_destination)
1678 if r then
1679 local t = tl.taglist
1680 referencehash[t[#t]] = r
1681 end
1682 if d then
1683 local t = tl.taglist
1684 destinationhash[t[#t]] = d
1685 end
1686
1687 elseif last then
1688
1689
1690
1691 if not ap then
1692 ap = pap
1693 end
1694 if ap ~= currentparagraph then
1695 pushcontent(currentparagraph,ap)
1696 pushentry(currentnesting)
1697 currentattribute = last
1698 currentparagraph = ap
1699 end
1700
1701
1702
1703 if trace_export then
1704 report_export("%w<!-- processing glyph %C tagged %a -->",currentdepth,c,last)
1705 end
1706 else
1707 if trace_export then
1708 report_export("%w<!-- processing glyph %C tagged %a -->",currentdepth,c,at)
1709 end
1710 end
1711 local s = getattr(n,a_exportstatus)
1712 if s then
1713 c = s
1714 end
1715 if c == 0 or c == 0xFFFD then
1716 if trace_export then
1717 report_export("%w<!-- skipping glyph %U -->",currentdepth,c)
1718 end
1719 elseif c == 0x20 then
1720 local a = getattr(n,a_characters)
1721 nofcurrentcontent = nofcurrentcontent + 1
1722 if a then
1723 if trace_export then
1724 report_export("%w<!-- turning last space into special space %U -->",currentdepth,a)
1725 end
1726 currentcontent[nofcurrentcontent] = specialspaces[a]
1727 else
1728 currentcontent[nofcurrentcontent] = " "
1729 end
1730 else
1731 local fc = fontchar[f]
1732 if fc then
1733 fc = fc and fc[c]
1734 if fc then
1735 local u = fc.unicode
1736 if u == 0 or u == 0xFFFD then
1737
1738 elseif not u then
1739 nofcurrentcontent = nofcurrentcontent + 1
1740 currentcontent[nofcurrentcontent] = utfchar(c)
1741 elseif type(u) == "table" then
1742 for i=1,#u do
1743 nofcurrentcontent = nofcurrentcontent + 1
1744 currentcontent[nofcurrentcontent] = utfchar(u[i])
1745 end
1746 else
1747 nofcurrentcontent = nofcurrentcontent + 1
1748 currentcontent[nofcurrentcontent] = utfchar(u)
1749 end
1750 elseif c > 0 then
1751 nofcurrentcontent = nofcurrentcontent + 1
1752 currentcontent[nofcurrentcontent] = utfchar(c)
1753 else
1754
1755 end
1756 elseif c > 0 then
1757 nofcurrentcontent = nofcurrentcontent + 1
1758 currentcontent[nofcurrentcontent] = utfchar(c)
1759 else
1760
1761 end
1762 end
1763 end
1764 elseif id == glue_code then
1765
1766 local ca, a = getattrs(n,a_characters,a_tagged)
1767 if ca == 0 then
1768
1769 elseif ca then
1770 if not a then
1771 a = pat
1772 end
1773 if a then
1774 local c = specialspaces[ca]
1775 if last ~= a then
1776 local tl = taglist[a]
1777 if trace_export then
1778 report_export("%w<!-- processing space glyph %U tagged %a case 1 -->",currentdepth,ca,a)
1779 end
1780 pushcontent()
1781 currentnesting = tl
1782 currentparagraph = getattr(n,a_taggedpar) or pap
1783 currentattribute = a
1784 last = a
1785 pushentry(currentnesting)
1786
1787 elseif last then
1788 local ap = getattr(n,a_taggedpar) or pap
1789 if ap ~= currentparagraph then
1790 pushcontent(currentparagraph,ap)
1791 pushentry(currentnesting)
1792 currentattribute = last
1793 currentparagraph = ap
1794 end
1795 if trace_export then
1796 report_export("%w<!-- processing space glyph %U tagged %a case 2 -->",currentdepth,ca,last)
1797 end
1798 end
1799
1800
1801
1802
1803
1804
1805 nofcurrentcontent = nofcurrentcontent + 1
1806 currentcontent[nofcurrentcontent] = c
1807 end
1808 elseif subtype == userskip_code then
1809 if getwidth(n) > threshold then
1810 if last and not somespace[currentcontent[nofcurrentcontent]] then
1811 local a = getattr(n,a_tagged) or pat
1812 if a == last then
1813 if trace_export then
1814 report_export("%w<!-- injecting spacing 5a -->",currentdepth)
1815 end
1816 nofcurrentcontent = nofcurrentcontent + 1
1817 currentcontent[nofcurrentcontent] = " "
1818 elseif a then
1819
1820 if trace_export then
1821 report_export("%w<!-- processing glue > threshold tagged %s becomes %s -->",currentdepth,last,a)
1822 end
1823 pushcontent()
1824 if trace_export then
1825 report_export("%w<!-- injecting spacing 5b -->",currentdepth)
1826 end
1827 last = a
1828 nofcurrentcontent = nofcurrentcontent + 1
1829 currentcontent[nofcurrentcontent] = " "
1830 currentnesting = taglist[last]
1831 pushentry(currentnesting)
1832 currentattribute = last
1833 end
1834 end
1835 end
1836 elseif subtype == spaceskip_code or subtype == xspaceskip_code then
1837 if not somespace[currentcontent[nofcurrentcontent]] then
1838 local a = getattr(n,a_tagged) or pat
1839 if a == last then
1840 if trace_export then
1841 report_export("%w<!-- injecting spacing 7 (stay in element) -->",currentdepth)
1842 end
1843 nofcurrentcontent = nofcurrentcontent + 1
1844 currentcontent[nofcurrentcontent] = " "
1845 else
1846 if trace_export then
1847 report_export("%w<!-- injecting spacing 7 (end of element) -->",currentdepth)
1848 end
1849 last = a
1850 pushcontent()
1851 nofcurrentcontent = nofcurrentcontent + 1
1852 currentcontent[nofcurrentcontent] = " "
1853 currentnesting = taglist[last]
1854 pushentry(currentnesting)
1855 currentattribute = last
1856 end
1857 end
1858 elseif subtype == intermathskip_code then
1859
1860 elseif subtype == rightskip_code then
1861
1862 if nofcurrentcontent > 0 then
1863 local r = currentcontent[nofcurrentcontent]
1864 if r == hyphen then
1865 if not keephyphens then
1866 nofcurrentcontent = nofcurrentcontent - 1
1867 end
1868 elseif pid == disc_code then
1869
1870 elseif not somespace[r] then
1871 local a = getattr(n,a_tagged) or pat
1872 if a == last then
1873 if trace_export then
1874 report_export("%w<!-- injecting spacing 1 (end of line, stay in element) -->",currentdepth)
1875 end
1876 nofcurrentcontent = nofcurrentcontent + 1
1877 currentcontent[nofcurrentcontent] = " "
1878 else
1879 if trace_export then
1880 report_export("%w<!-- injecting spacing 1 (end of line, end of element) -->",currentdepth)
1881 end
1882 last = a
1883 pushcontent()
1884 nofcurrentcontent = nofcurrentcontent + 1
1885 currentcontent[nofcurrentcontent] = " "
1886 currentnesting = taglist[last]
1887 pushentry(currentnesting)
1888 currentattribute = last
1889 end
1890 end
1891 end
1892 elseif subtype == parfillskip_code then
1893
1894
1895 if maybewrong then
1896 showmaybe(maybewrong)
1897 end
1898
1899 end
1900 elseif id == hlist_code or id == vlist_code then
1901 local ai = getattr(n,a_image)
1902 if ai then
1903 local at = getattr(n,a_tagged) or pat
1904 if nofcurrentcontent > 0 then
1905 pushcontent()
1906 pushentry(currentnesting)
1907 end
1908 pushentry(taglist[at])
1909 if trace_export then
1910 report_export("%w<!-- processing image tagged %a",currentdepth,last)
1911 end
1912 last = nil
1913 currentparagraph = nil
1914 else
1915
1916 local list = getlist(n)
1917 if list then
1918
1919 local at = getattr(n,a_tagged) or pat
1920 collectresults(list,n,at)
1921 end
1922 end
1923 elseif id == kern_code then
1924 if subtype == userkern_code then
1925 local kern = getkern(n)
1926 if kern > 0 then
1927 local a = getattr(n,a_tagged) or pat
1928 local t = taglist[a]
1929 if not t or t.tagname ~= "ignore" then
1930 local limit = threshold
1931 if p then
1932 local c, f = isglyph(p)
1933 if c then
1934 limit = fontquads[f] / 4
1935 end
1936 end
1937 if kern > limit then
1938 if last and not somespace[currentcontent[nofcurrentcontent]] then
1939
1940 if a == last then
1941 if not somespace[currentcontent[nofcurrentcontent]] then
1942 if trace_export then
1943 report_export("%w<!-- injecting spacing 8 (kern %p) -->",currentdepth,kern)
1944 end
1945 nofcurrentcontent = nofcurrentcontent + 1
1946 currentcontent[nofcurrentcontent] = " "
1947 end
1948 elseif a then
1949
1950 if trace_export then
1951 report_export("%w<!-- processing kern, threshold %p, tag %s => %s -->",currentdepth,limit,last,a)
1952 end
1953 last = a
1954 pushcontent()
1955 if trace_export then
1956 report_export("%w<!-- injecting spacing 9 (kern %p) -->",currentdepth,kern)
1957 end
1958 nofcurrentcontent = nofcurrentcontent + 1
1959 currentcontent[nofcurrentcontent] = " "
1960
1961 currentnesting = t
1962 pushentry(currentnesting)
1963 currentattribute = last
1964 end
1965 end
1966 end
1967 end
1968 end
1969 end
1970 elseif id == whatsit_code then
1971
1972 if subtype == userdefinedwhatsit_code then
1973
1974 local at = getattr(n,a_tagged)
1975 if nofcurrentcontent > 0 then
1976 pushcontent()
1977 pushentry(currentnesting)
1978 end
1979 pushentry(taglist[at])
1980 if trace_export then
1981 report_export("%w<!-- processing anchor tagged %a",currentdepth,last)
1982 end
1983 last = nil
1984 currentparagraph = nil
1985 end
1986 elseif id == par_code then
1987 if startofpar(n) then
1988 paragraph = getattr(n,a_taggedpar)
1989 end
1990 elseif id == disc_code then
1991
1992 local pre, post, replace = getdisc(n)
1993 if keephyphens then
1994 if pre and not getnext(pre) and isglyph(pre) == 0xAD then
1995 nofcurrentcontent = nofcurrentcontent + 1
1996 currentcontent[nofcurrentcontent] = hyphen
1997 end
1998 end
1999 if replace then
2000 collectresults(replace,nil)
2001 end
2002 end
2003 p = n
2004 pid = id
2005 end
2006 if maybewrong then
2007 showmaybe(maybewrong)
2008 end
2009 end
2010
2011 local enabled = true
2012
2013 updaters.register("tagging.state.disable",function() enabled = false end)
2014 updaters.register("tagging.state.enable", function() enabled = true end)
2015
2016
2017
2018 function nodes.handlers.export(head)
2019 if enabled then
2020 starttiming(treehash)
2021 if trace_export then
2022 report_export("%w<!-- start flushing page -->",currentdepth)
2023 end
2024
2025 restart = true
2026 collectresults(head)
2027 if trace_export then
2028 report_export("%w<!-- stop flushing page -->",currentdepth)
2029 end
2030 stoptiming(treehash)
2031 end
2032 return head
2033 end
2034
2035 local c_tagparcounter <const> = tex.iscount("tagparcounter")
2036
2037 function nodes.handlers.checkparcounter(p,mode)
2038 setattr(p,a_taggedpar,texgetcount(c_tagparcounter) + 1)
2039 return p
2040 end
2041
2042
2043
2044 function builders.paragraphs.tag(head)
2045 noftextblocks = noftextblocks + 1
2046 for n, subtype in nexthlist, head do
2047 if subtype == linelist_code then
2048 setattr(n,a_textblock,noftextblocks)
2049
2050
2051 end
2052 end
2053 return false
2054 end
2055
2056end
2057
2058do
2059
2060 local xmlcollected = xml.collected
2061 local xmlsetcomment = xml.setcomment
2062
2063local xmlpreamble_nop = [[
2064<?xml version="1.0" encoding="UTF-8" standalone="%standalone%" ?>
2065]]
2066
2067local xmlpreamble_yes = [[
2068<?xml version="1.0" encoding="UTF-8" standalone="%standalone%" ?>
2069
2070<!--
2071
2072 input filename : %filename%
2073 processing date : %date%
2074 context version : %contextversion%
2075 exporter version : %exportversion%
2076
2077-->
2078
2079]]
2080
2081 local flushtree = wrapups.flushtree
2082
2083 local function wholepreamble(standalone,nocomment)
2084 return replacetemplate(nocomment and xmlpreamble_nop or xmlpreamble_yes, {
2085 standalone = standalone and "yes" or "no",
2086 filename = tex.jobname,
2087 date = os.fulltime(),
2088 contextversion = environment.version,
2089 exportversion = exportversion,
2090 })
2091 end
2092
2093
2094local csspreamble = [[
2095<?xml-stylesheet type="text/css" href="%filename%" ?>
2096]]
2097
2098local cssheadlink = [[
2099<link type="text/css" rel="stylesheet" href="%filename%" />
2100]]
2101
2102
2103
2104
2105local mathmlheadscript = [[
2106<script
2107 type="text/javascript"
2108 id="MathJax-script"
2109 async="async"
2110 src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js">
2111</script>
2112]]
2113
2114 local function allusedstylesheets(cssfiles,files,path,extra)
2115 local done = { }
2116 local result = { }
2117 local extras = { }
2118 for i=1,#cssfiles do
2119 local cssfile = cssfiles[i]
2120 if type(cssfile) ~= "string" then
2121
2122 elseif cssfile == "export-example.css" then
2123
2124 elseif not done[cssfile] then
2125 cssfile = joinfile(path,basename(cssfile))
2126 report_export("adding css reference '%s'",cssfile)
2127 files[#files+1] = cssfile
2128 result[#result+1] = replacetemplate(csspreamble, { filename = cssfile })
2129 extras[#extras+1] = replacetemplate(cssheadlink, { filename = cssfile })
2130 done[cssfile] = true
2131 end
2132 end
2133 if extra then
2134 extras[#extras+1] = extra
2135 end
2136 return concat(result), concat(extras)
2137 end
2138
2139local elementtemplate <const> = [[
2140/* element="%element%" detail="%detail%" chain="%chain%" */
2141
2142%element%,
2143%namespace%div.%element% {
2144 display: %display% ;
2145}]]
2146
2147local detailtemplate <const> = [[
2148/* element="%element%" detail="%detail%" chain="%chain%" */
2149
2150%element%[detail=%detail%],
2151%namespace%div.%element%.%detail% {
2152 display: %display% ;
2153}]]
2154
2155
2156
2157local htmltemplate <const> = [[
2158%preamble%
2159
2160<html xmlns="http://www.w3.org/1999/xhtml"%?mathmlns: xmlns:m="%mathmlns%" lang="%language%" xml:lang="language%" ?%>
2161
2162 <head>
2163
2164 <meta charset="utf-8"/>
2165
2166 <title>%title%</title>
2167
2168%style%
2169
2170 </head>
2171 <body>
2172 <div class="document" xmlns="http://www.pragma-ade.com/context/export">
2173
2174<div class="warning">Rendering can be suboptimal because there is no default/fallback css loaded.</div>
2175
2176%body%
2177
2178 </div>
2179 </body>
2180</html>
2181]]
2182
2183 local displaymapping = {
2184 inline = "inline",
2185 display = "block",
2186 mixed = "inline",
2187 }
2188
2189 local function allusedelements(filename)
2190 local result = { replacetemplate(namespacetemplate, {
2191 what = "template",
2192 filename = filename,
2193 namespace = contextns,
2194
2195 cssnamespaceurl = cssnamespaceurl,
2196 },false,true) }
2197 for element, details in sortedhash(used) do
2198 if mathtags[element] then
2199
2200 else
2201 for detail, what in sortedhash(details) do
2202 local nature = what[1] or "display"
2203 local chain = what[2]
2204 local display = displaymapping[nature] or "block"
2205 if detail == "" then
2206 result[#result+1] = replacetemplate(elementtemplate, {
2207 element = element,
2208 display = display,
2209 chain = chain,
2210 namespace = usecssnamespace and namespace or "",
2211 })
2212 else
2213 result[#result+1] = replacetemplate(detailtemplate, {
2214 element = element,
2215 display = display,
2216 detail = detail,
2217 chain = chain,
2218 namespace = usecssnamespace and cssnamespace or "",
2219 })
2220 end
2221 end
2222 end
2223 end
2224 return concat(result,"\n\n")
2225 end
2226
2227 local function allcontent(tree,nocheck,final)
2228 local result = { }
2229 local data = tree.data
2230 if nocheck then
2231
2232 else
2233
2234
2235 local d = { }
2236 for i=1,#data do
2237 local di = data[i]
2238 if di.tg == "document" then
2239 local d = di.data
2240 for i=1,#d do
2241 local ddi = d[i]
2242 if ddi.tg == "documentpart" then
2243 di.data = ddi.data
2244 break
2245 end
2246 end
2247 break
2248 end
2249 end
2250 end
2251if final then
2252
2253end
2254 flushtree(result,tree.data,"display")
2255
2256 local r = result[1]
2257 if r then
2258 result[1] = gsub(result[1],"^%s+","")
2259 result[#result] = gsub(result[#result],"%s+$","")
2260 result = concat(result)
2261 result = gsub(result,"\n *\n","\n")
2262 result = gsub(result,"\n +([^< ])","\n%1")
2263 return result
2264 else
2265
2266 end
2267 end
2268
2269
2270
2271
2272
2273
2274
2275
2276 local function cleanxhtmltree(xmltree)
2277 if xmltree then
2278 local implicits = { }
2279 local explicits = { }
2280 local overloads = { }
2281 for e in xmlcollected(xmltree,"*") do
2282 local at = e.at
2283 if at then
2284 local explicit = at.explicit
2285 local implicit = at.implicit
2286 if explicit then
2287 if not explicits[explicit] then
2288 explicits[explicit] = true
2289 at.id = explicit
2290 if implicit then
2291 overloads[implicit] = explicit
2292 end
2293 end
2294 else
2295 if implicit and not implicits[implicit] then
2296 implicits[implicit] = true
2297 at.id = "aut:" .. implicit
2298 end
2299 end
2300 end
2301 end
2302 for e in xmlcollected(xmltree,"*") do
2303 local at = e.at
2304 if at then
2305 local internal = at.internal
2306 local location = at.location
2307 if internal then
2308 if location then
2309 local explicit = overloads[location]
2310 if explicit then
2311 at.href = "#" .. explicit
2312 else
2313 at.href = "#aut:" .. internal
2314 end
2315 else
2316 at.href = "#aut:" .. internal
2317 end
2318 else
2319 if location then
2320 at.href = "#" .. location
2321 else
2322 local url = at.url
2323 if url then
2324 at.href = url
2325 else
2326 local file = at.file
2327 if file then
2328 at.href = file
2329 end
2330 end
2331 end
2332 end
2333 end
2334 end
2335 return xmltree
2336 else
2337 return xml.convert('<?xml version="1.0"?>\n<error>invalid xhtml tree</error>')
2338 end
2339 end
2340
2341
2342
2343 local private = {
2344 destination = true,
2345 prefix = true,
2346 reference = true,
2347
2348 id = true,
2349 href = true,
2350
2351 implicit = true,
2352 explicit = true,
2353
2354 url = true,
2355 file = true,
2356 internal = true,
2357 location = true,
2358
2359 name = true,
2360 used = true,
2361 page = true,
2362 width = true,
2363 height = true,
2364
2365 image = true,
2366 }
2367
2368 local addclicks = true
2369 local f_onclick = formatters[ [[location.href='%s']] ]
2370
2371 local p_cleanid = lpeg.replacer { [":"] = "-" }
2372 local p_cleanhref = lpeg.Cs(lpeg.P("#") * p_cleanid)
2373
2374 local p_splitter = lpeg.Ct ( (
2375 lpeg.Carg(1) * lpeg.C((1-lpeg.P(" "))^1) / function(d,s) if not d[s] then d[s] = true return s end end
2376 * lpeg.P(" ")^0 )^1 )
2377
2378
2379 local classes = setmetatableindex(function(t,k)
2380 local v = concat(lpegmatch(p_splitter,k,1,{})," ")
2381 t[k] = v
2382 return v
2383 end)
2384
2385 local function makeclass(tg,at)
2386 local detail = at.detail
2387 local chain = at.chain
2388 local extra = nil
2389 local classes = { }
2390 local nofclasses = 0
2391 at.detail = nil
2392 at.chain = nil
2393 for k, v in next, at do
2394 if not private[k] then
2395 nofclasses = nofclasses + 1
2396 classes[nofclasses] = k .. "-" .. v
2397 end
2398 end
2399 if detail and detail ~= "" then
2400 if chain and chain ~= "" then
2401 if chain ~= detail then
2402 extra = classes[tg .. " " .. chain .. " " .. detail]
2403 elseif tg ~= detail then
2404 extra = detail
2405 end
2406 elseif tg ~= detail then
2407 extra = detail
2408 end
2409 elseif chain and chain ~= "" then
2410 if tg ~= chain then
2411 extra = chain
2412 end
2413 end
2414
2415 if nofclasses > 0 then
2416 sort(classes)
2417 classes = concat(classes," ")
2418 if extra then
2419 return tg .. " " .. extra .. " " .. classes
2420 else
2421 return tg .. " " .. classes
2422 end
2423 else
2424 if extra then
2425 return tg .. " " .. extra
2426 else
2427 return tg
2428 end
2429 end
2430 end
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441 local crappycss = {
2442 table = "table", tabulate = "table",
2443 tablehead = "thead", tabulatehead = "thead",
2444 tablebody = "tbody", tabulatebody = "tbody",
2445 tablefoot = "tfoot", tabulatefoot = "tfoot",
2446 tablerow = "tr", tabulaterow = "tr",
2447 tablecell = "td", tabulatecell = "td",
2448 }
2449
2450 local cssmapping = false
2451
2452 directives.register("export.nativetags", function(v)
2453 cssmapping = v and crappycss or false
2454 end)
2455
2456 local function remap(specification,source,target)
2457 local comment = nil
2458 for c in xmlcollected(source,"*") do
2459 if not c.special then
2460 local tg = c.tg
2461
2462 local at = c.at
2463 if tg == "math" then
2464 at["xmlns"] = mathmlns
2465 c.ns = ""
2466 elseif mathtags[tg] then
2467
2468 else
2469 local dt = c.dt
2470 local nt = #dt
2471 if nt == 0 or (nt == 1 and dt[1] == "") then
2472 if comment then
2473 c.dt = comment
2474 else
2475 xmlsetcomment(c,"empty")
2476 comment = c.dt
2477 end
2478 end
2479 local class = nil
2480 local label = nil
2481local image = at.image
2482 if tg == "document" then
2483 at.href = nil
2484 at.detail = nil
2485 at.chain = nil
2486 elseif tg == "metavariable" then
2487 label = at.name
2488 at.detail = "metaname-" .. label
2489 class = makeclass(tg,at)
2490 else
2491 class = makeclass(tg,at)
2492 end
2493 local id = at.id
2494 local href = at.href
2495 local attr = nil
2496 if id then
2497 id = lpegmatch(p_cleanid, id) or id
2498 if href then
2499 href = lpegmatch(p_cleanhref,href) or href
2500 attr = {
2501 class = class,
2502 id = id,
2503 href = href,
2504 onclick = addclicks and f_onclick(href) or nil,
2505 }
2506 else
2507 attr = {
2508 class = class,
2509 id = id,
2510 }
2511 end
2512 else
2513 if href then
2514 href = lpegmatch(p_cleanhref,href) or href
2515 attr = {
2516 class = class,
2517 href = href,
2518 onclick = addclicks and f_onclick(href) or nil,
2519 }
2520 else
2521 attr = {
2522 class = class,
2523 }
2524 end
2525 end
2526if image then
2527 attr.image = image
2528end
2529 c.at = attr
2530 if label then
2531 attr.label = label
2532 end
2533 c.tg = cssmapping and cssmapping[tg] or "div"
2534 end
2535 end
2536 end
2537 end
2538
2539
2540
2541 local embedfile = false directives.register("export.embed",function(v) embedfile = v end)
2542
2543 local justexport = nodes.handlers.export
2544
2545 local function wrapuptree(tree)
2546 wrapups.fixtree(tree)
2547 wrapups.collapsetree(tree)
2548 wrapups.indextree(tree)
2549 wrapups.checktree(tree)
2550 wrapups.breaktree(tree)
2551 wrapups.finalizetree(tree)
2552
2553 wrapups.hashlistdata()
2554
2555 end
2556
2557 local function locate(tree,simple,level)
2558 if tree then
2559 if tree.tg == simple then
2560 return tree.__p__
2561 end
2562 local data = tree.data
2563 if data then
2564 for i=1,#data do
2565 local d = locate(data[i],simple,level+1)
2566 if d then
2567 return d
2568 end
2569 end
2570 end
2571 end
2572 end
2573
2574 local function localexport(head,simple)
2575 starttiming(treehash)
2576
2577 local saved_treestack = treestack ; treestack = { }
2578 local saved_nesting = nesting ; nesting = { }
2579 local saved_currentdepth = currentdepth ; currentdepth = 0
2580 local saved_tree = tree ; tree = { data = { }, fulltag == "root" }
2581 local saved_roottree = roottree ; roottree = tree
2582 local saved_treehash = treehash ; treehash = { }
2583 local saved_nofbreaks = nofbreaks ; nofbreaks = 0
2584 local saved_show_comment = show_comment ; show_comment = false
2585
2586local saved_nofcurrentcontent = nofcurrentcontent ; nofcurrentcontent = 0
2587local saved_currentcontent = currentcontent ; currentcontent = { }
2588local saved_currentnesting = currentnesting ; currentnesting = nil
2589local saved_currentattribute = currentattribute ; currentattribute = nil
2590local saved_last = last ; last = nil
2591local saved_currentparagraph = currentparagraph ; currentparagraph = nil
2592
2593 justexport(head)
2594 finishexport()
2595 wrapuptree(tree)
2596
2597 local result
2598
2599 if simple then
2600 local d = locate(tree,simple,1)
2601 if d then
2602 result = allcontent(d,true)
2603 end
2604 else
2605 result = concat {
2606 wholepreamble(true),
2607 allcontent(tree),
2608 }
2609 end
2610
2611 treestack = saved_treestack
2612 nesting = saved_nesting
2613 currentdepth = saved_currentdepth
2614 tree = saved_tree
2615 roottree = saved_roottree
2616 treehash = saved_treehash
2617 nofbreaks = saved_nofbreaks
2618 show_comment = saved_show_comment
2619
2620nofcurrentcontent = saved_nofcurrentcontent
2621currentcontent = saved_currentcontent
2622currentnesting = saved_currentnesting
2623currentattribute = saved_currentattribute
2624last = saved_last
2625currentparagraph = saved_currentparagraph
2626
2627 stoptiming(treehash)
2628
2629 return result
2630
2631 end
2632
2633 structurestags.localexport = localexport
2634
2635 function structures.tags.exportbox(n,filename,buffername)
2636 local list = nodes.nuts.getbox(n)
2637 if n then
2638 local e = localexport(list)
2639 if filename and filename ~= "" then
2640 io.savedata(filename,e)
2641 elseif buffername then
2642 buffers.assign(buffername == interfaces.variables.yes and "" or buffername,e)
2643 else
2644 return e
2645 end
2646 end
2647 end
2648
2649 interfaces.implement {
2650 name = "exportbox",
2651 arguments = { "integer", "string", "string" },
2652 actions = structures.tags.exportbox
2653 }
2654
2655 function structurestags.finishexport()
2656
2657 if only_images then
2658 exporting = false
2659 end
2660
2661 if exporting then
2662 exporting = false
2663 else
2664 return
2665 end
2666
2667 local onlyxml = finetuning.export == v_xml
2668
2669 starttiming(treehash)
2670
2671 finishexport()
2672
2673 report_export("")
2674 if onlyxml then
2675 report_export("exporting xml, no other files")
2676 else
2677 report_export("exporting xml, xhtml, html and css files")
2678 end
2679 report_export("")
2680
2681 wrapuptree(tree)
2682
2683 local exportxml = true
2684 local exportxhtml = true
2685 local exporthtml = true
2686 local useextra = true
2687
2688 if finetuning.output == "xml" then
2689 exporthtml = false
2690 exportxhtml = false
2691 useextra = false
2692 elseif finetuning.output == "xhtml" then
2693 exportxml = false
2694 exporthtml = false
2695 useextra = false
2696 elseif finetuning.output == "html" then
2697 exportxml = false
2698 exportxhtml = false
2699 useextra = false
2700 end
2701
2702 local askedname = finetuning.file
2703 local pathname = finetuning.path
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720 if type(askedname) ~= "string" or askedname == "" then
2721 askedname = tex.jobname
2722 end
2723 if type(pathname) ~= "string" or pathname == "" then
2724 pathname = false
2725 end
2726
2727 local usedname = nameonly(askedname)
2728 local basepath = usedname .. "-export"
2729
2730if not useextra then
2731 basepath = "."
2732end
2733
2734 local imagepath = joinfile(basepath,"images")
2735 local stylepath = joinfile(basepath,"styles")
2736
2737 if pathname then
2738 basepath = joinfile(pathname,basepath)
2739 imagepath = joinfile(pathname,imagepath)
2740 stylepath = joinfile(pathname,stylepath)
2741 end
2742
2743 local function validpath(what,pathname)
2744 if lfs.isdir(pathname) then
2745 report_export("using existing %s path %a",what,pathname)
2746 return pathname
2747 end
2748 lfs.mkdirs(pathname)
2749 if lfs.isdir(pathname) then
2750 report_export("using cretated %s path %a",what,basepath)
2751 return pathname
2752 else
2753 report_export("unable to create %s path %a",what,basepath)
2754 return false
2755 end
2756 end
2757
2758 if not (validpath("export",basepath )
2759 and validpath("images",imagepath)
2760 and validpath("styles",stylepath)) then
2761 return
2762 end
2763
2764
2765
2766
2767
2768 local xmlfilebase = addsuffix(usedname .. (useextra and "-raw" or ""),"xml" )
2769 local xhtmlfilebase = addsuffix(usedname .. (useextra and "-tag" or ""),"xhtml")
2770 local htmlfilebase = addsuffix(usedname .. (useextra and "-div" or ""),"html")
2771 local specificationfilebase = addsuffix(usedname .. (useextra and "-pub" or ""),"lua" )
2772
2773 local xmlfilename = joinfile(basepath, xmlfilebase )
2774 local xhtmlfilename = joinfile(basepath, xhtmlfilebase )
2775 local htmlfilename = joinfile(basepath, htmlfilebase )
2776 local specificationfilename = joinfile(basepath, specificationfilebase)
2777
2778 local defaultfilebase = addsuffix(usedname .. "-defaults", "css")
2779 local imagefilebase = addsuffix(usedname .. "-images", "css")
2780 local fontfilebase = addsuffix(usedname .. "-fonts", "css")
2781 local stylefilebase = addsuffix(usedname .. "-styles", "css")
2782 local templatefilebase = addsuffix(usedname .. "-templates","css")
2783
2784 local defaultfilename = joinfile(stylepath,defaultfilebase )
2785 local imagefilename = joinfile(stylepath,imagefilebase )
2786 local stylefilename = joinfile(stylepath,stylefilebase )
2787 local fontfilename = joinfile(stylepath,fontfilebase )
2788 local templatefilename = joinfile(stylepath,templatefilebase)
2789
2790 local cssfile = finetuning.cssfile
2791
2792
2793
2794 local files = {
2795 }
2796
2797
2798
2799
2800 local cssfiles = {
2801 defaultfilebase,
2802 imagefilebase,
2803 fontfilebase,
2804 stylefilebase,
2805 }
2806
2807 local cssextra = cssfile and table.unique(settings_to_array(cssfile)) or { }
2808
2809
2810
2811
2812
2813
2814 local result = allcontent(tree,false,true)
2815
2816
2817 local extradata = structures.tags.getextradata()
2818
2819 if extradata then
2820 local t = { "" }
2821 t[#t+1] = "<extradata>"
2822 for name, action in sortedhash(extradata) do
2823 t[#t+1] = action()
2824 end
2825 t[#t+1] = "</extradata>"
2826 t[#t+1] = "</document>"
2827
2828 result = gsub(result,"</document>",function()
2829 return concat(t,"\n")
2830 end)
2831 end
2832
2833
2834
2835 if onlyxml then
2836
2837 os.remove(defaultfilename)
2838
2839
2840
2841
2842
2843 for i=1,#cssextra do
2844 os.remove(joinfile(stylepath,basename(source)))
2845 end
2846
2847
2848
2849 os.remove(imagefilename)
2850 os.remove(fontfilename)
2851 os.remove(stylefilename)
2852 os.remove(templatefilename)
2853 os.remove(xhtmlfilename)
2854 os.remove(specificationfilename)
2855 os.remove(htmlfilename)
2856
2857 result = concat {
2858 wholepreamble(true,true),
2859 "<!-- This export file is used for filtering runtime only! -->\n",
2860 result,
2861 }
2862
2863 report_export("saving xml data in %a",xmlfilename)
2864 io.savedata(xmlfilename,result)
2865
2866 return
2867
2868 end
2869
2870 local examplefilename = resolvers.findfile("export-example.css")
2871 if examplefilename then
2872 local data = io.loaddata(examplefilename)
2873 if not data or data == "" then
2874 data = "/* missing css file */"
2875 elseif not usecssnamespace then
2876 data = gsub(data,cssnamespace,"")
2877 end
2878 io.savedata(defaultfilename,data)
2879 end
2880
2881 if cssfile then
2882 for i=1,#cssextra do
2883 local source = addsuffix(cssextra[i],"css")
2884 local target = joinfile(stylepath,basename(source))
2885 cssfiles[#cssfiles+1] = source
2886 if not lfs.isfile(source) then
2887 source = joinfile("../",source)
2888 end
2889 if lfs.isfile(source) then
2890 report_export("copying %s",source)
2891 file.copy(source,target)
2892 end
2893 end
2894 end
2895
2896
2897 local script = settings_to_hash(finetuning.option or "").mathjax and mathmlheadscript or nil
2898
2899 local x_styles, h_styles = allusedstylesheets(cssfiles,files,"styles",script)
2900
2901 local attach = backends.nodeinjections.attachfile
2902
2903 if embedfile and attach then
2904
2905 attach {
2906 data = concat{ wholepreamble(true), result },
2907 name = basename(xmlfilename),
2908 registered = "export",
2909 title = "raw xml export",
2910 method = v_hidden,
2911 mimetype = "application/mathml+xml",
2912 }
2913 end
2914
2915 result = concat {
2916 wholepreamble(true),
2917 x_styles,
2918 result,
2919 }
2920
2921 cssfiles = table.unique(cssfiles)
2922
2923
2924
2925 if exportxml then
2926
2927 report_export("saving xml data in %a",xmlfilename)
2928 io.savedata(xmlfilename,result)
2929
2930 end
2931
2932 report_export("saving css image definitions in %a",imagefilename)
2933 io.savedata(imagefilename,wrapups.allusedimages(usedname))
2934
2935 report_export("saving css font definitions in %a",fontfilename)
2936 io.savedata(fontfilename,wrapups.allusedfonts(usedname))
2937
2938 report_export("saving css style definitions in %a",stylefilename)
2939 io.savedata(stylefilename,wrapups.allusedstyles(usedname))
2940
2941 report_export("saving css template in %a",templatefilename)
2942 io.savedata(templatefilename,allusedelements(usedname))
2943
2944
2945
2946 local xmltree = cleanxhtmltree(xml.convert(result))
2947
2948 if exportxhtml then
2949
2950 report_export("saving xhtml variant in %a",xhtmlfilename)
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962 xml.save(xmltree,xhtmlfilename)
2963
2964
2965
2966 end
2967
2968
2969
2970
2971
2972
2973 if exporthtml then
2974
2975 local identity = interactions.general.getidentity()
2976 local metadata = structures.tags.getmetadata()
2977
2978 local specification = {
2979 name = usedname,
2980 identifier = os.uuid(),
2981 images = wrapups.uniqueusedimages(),
2982 imagefile = joinfile("styles",imagefilebase),
2983 imagepath = "images",
2984 stylepath = "styles",
2985 xmlfiles = { xmlfilebase },
2986 xhtmlfiles = { xhtmlfilebase },
2987 htmlfiles = { htmlfilebase },
2988 styles = cssfiles,
2989 htmlroot = htmlfilebase,
2990 language = languagenames[texgetcount("mainlanguagenumber")],
2991 title = validstring(finetuning.title) or validstring(identity.title),
2992 subtitle = validstring(finetuning.subtitle) or validstring(identity.subtitle),
2993 author = validstring(finetuning.author) or validstring(identity.author),
2994 firstpage = validstring(finetuning.firstpage),
2995 lastpage = validstring(finetuning.lastpage),
2996 metadata = metadata,
2997 }
2998
2999 report_export("saving specification in %a",specificationfilename,specificationfilename)
3000
3001 xml.wipe(xmltree,"metadata")
3002
3003 io.savedata(specificationfilename,table.serialize(specification,true))
3004
3005
3006
3007
3008 report_export("saving div based alternative in %a",htmlfilename)
3009
3010 remap(specification,xmltree)
3011
3012
3013
3014 local title = specification.title
3015
3016 if not title or title == "" then
3017 title = metadata.title
3018 if not title or title == "" then
3019 title = usedname
3020 end
3021 end
3022
3023 local language = languagenames[texgetcount("mainlanguagenumber")]
3024
3025 local variables = {
3026 style = h_styles,
3027 body = xml.tostring(xml.first(xmltree,"/div")),
3028 preamble = wholepreamble(false),
3029 title = title,
3030 language = languagenames[texgetcount("mainlanguagenumber")] or "en",
3031 }
3032
3033 io.savedata(htmlfilename,replacetemplate(htmltemplate,variables,"xml"))
3034
3035
3036
3037 report_export("")
3038 report_export('create epub with: mtxrun --script epub --make "%s" [--purge --rename --svgmath]',usedname)
3039 report_export("")
3040
3041 end
3042
3043 stoptiming(treehash)
3044 end
3045
3046 local enableaction = nodes.tasks.enableaction
3047
3048 function structurestags.initializeexport()
3049 if not exporting then
3050 report_export("enabling export to xml")
3051 enableaction("shipouts","nodes.handlers.export")
3052
3053 enableaction("math", "noads.handlers.tags")
3054 enableaction("everypar","nodes.handlers.checkparcounter")
3055 luatex.registerstopactions(structurestags.finishexport)
3056 exporting = true
3057 end
3058 end
3059
3060 function structurestags.setupexport(t)
3061 merge(finetuning,t)
3062 keephyphens = finetuning.hyphen == v_yes
3063 exportproperties = finetuning.properties
3064 if exportproperties == v_no then
3065 exportproperties = false
3066 end
3067 end
3068
3069 statistics.register("xml exporting time", function()
3070 if exporting then
3071 return string.format("%s seconds, version %s", statistics.elapsedtime(treehash),exportversion)
3072 end
3073 end)
3074
3075end
3076
3077
3078
3079implement {
3080 name = "setupexport",
3081 actions = structurestags.setupexport,
3082 arguments = {
3083 {
3084 { "align" },
3085 { "bodyfont", "dimen" },
3086 { "width", "dimen" },
3087 { "properties" },
3088 { "hyphen" },
3089 { "title" },
3090 { "subtitle" },
3091 { "author" },
3092 { "firstpage" },
3093 { "lastpage" },
3094 { "svgstyle" },
3095 { "cssfile" },
3096 { "file" },
3097 { "option" },
3098 { "export" },
3099 }
3100 }
3101}
3102
3103implement {
3104 name = "finishexport",
3105 actions = structurestags.finishexport,
3106}
3107
3108implement {
3109 name = "initializeexport",
3110 actions = structurestags.initializeexport,
3111}
3112 |