1
2
3
4
5
6
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
local log = logging.new("joincharacters")
local charclasses = {
span=true,
mn = true,
}
local function update_mathvariant(curr)
-- when we join several <mi> elements, they will be rendered incorrectly
-- we must set the mathvariant attribute
local parent = curr:get_parent()
-- set mathvariant only if it haven't been set by the parent element
if not parent:get_attribute("mathvariant") then
-- curr._attr = curr._attr or {}
local mathvariant = "italic"
-- the joined elements don't have attributes
curr._attr = curr._attr or {}
curr:set_attribute("mathvariant", mathvariant)
end
end
local table_count = function(tbl)
local tbl = tbl or {}
local i = 0
for k,v in pairs(tbl) do i = i + 1 end
return i
end
local has_matching_attributes = function (el, next_el)
local el_attr = el._attr or {}
local next_attr = next_el._attr or {}
-- if the number of attributes doesn't match, elements don't match
if table_count(next_attr) ~= table_count(el_attr) then return false end
for k, v in pairs(el_attr) do
-- if any attribute doesn't match, elements don't match
if v~=next_attr[k] then return false end
end
return true
end
local function join_characters(obj,par)
-- join adjanced span and similar elements inserted by
-- tex4ht to just one object.
local par = par or {}
local options = get_filter_settings "joincharacters"
local charclasses = options.charclasses or par.charclasses or charclasses
local get_name = function(curr)
return string.lower(curr:get_element_name())
end
local get_class = function(next_el)
return next_el:get_attribute("class") or next_el:get_attribute("mathvariant")
end
local is_span = function(next_el)
return charclasses[get_name(next_el)]
end
local has_children = function(curr)
-- don't process spans that have child elements
local children = curr:get_children() or {}
-- if there is more than one child, we can be sure that it has child elements
if #children > 1 then
return true
elseif #children == 1 then
-- test if the child is an element
return children[1]:is_element()
end
return false
end
local join_elements = function(el, next_el)
-- it the following element match, copy it's children to the current element
for _, child in ipairs(next_el:get_children()) do
el:add_child_node(child)
end
-- remove the next element
next_el:remove_node()
end
local function get_next(curr, class)
local next_el = curr:get_next_node()
if next_el and next_el:is_element() and is_span(next_el) then
return next_el
-- if the next node is space followed by a matching element, we should add this space
elseif next_el and next_el:is_text() and get_next(next_el, class) then
local text = next_el._text
-- match only text containing just whitespace
if text:match("^%s+$") then return next_el end
end
end
obj:traverse_elements(function(el)
-- loop over all elements and test if the current element is in a list of
-- processed elements (charclasses) and if it doesn't contain children
if is_span(el) and not has_children(el) then
local next_el = get_next(el)
-- loop over the following elements and test whether they are of the same type
-- as the current one
while next_el do
-- save the next element because we will remove it later
local real_next = get_next(next_el)
if get_name(el) == get_name(next_el) and has_matching_attributes(el,next_el) and not el:get_attribute("id") then
join_elements(el, next_el)
-- add the whitespace
elseif next_el:is_text() then
local s = next_el._text
-- we must create a new node
el:add_child_node(el:create_text_node(s))
next_el:remove_node()
-- real_next = nil
else
real_next = nil
end
-- use the saved element as a next object
next_el = real_next
end
end
end)
-- process <mi> elements
obj:traverse_elements(function(el)
local function get_next_mi(curr)
local next_el = curr:get_next_node()
if next_el and next_el:is_element() then
return next_el
end
end
local function has_no_attributes(x)
return table_count(x._attr) == 0
end
-- join only subsequential <mi> elements with no attributes
if get_name(el) == "mi" and has_no_attributes(el) then
local next_el = get_next_mi(el)
while next_el do
local real_next = get_next_mi(next_el)
if get_name(next_el) == "mi" and has_no_attributes(next_el) then
join_elements(el, next_el)
-- set math variant to italic
-- (if the parent <mstyle> element doesn't set it to something else)
update_mathvariant(el)
else
-- break the loop otherwise
real_next = nil
end
next_el = real_next
end
end
end)
-- join text nodes in an element into one
obj:traverse_elements(function(el)
-- save the text
local t = {}
local children = el:get_children()
for _, x in ipairs(children) do
if x:is_text() then
t[#t+1] = x._text
else
return nil
end
end
el._text = table.concat(t)
return el
end)
return obj
end
return join_characters
|