summaryrefslogtreecommitdiff
path: root/support/make4ht/domfilters/make4ht-sectionid.lua
blob: 4a1f522e6e837729b601d42194d5bcf7762ce8c8 (plain)
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
local mkutils   = require "mkutils"
local log = logging.new("tocid")
-- Unicode data distributed with ConTeXt
-- defines "characters" table
if not mkutils.isModuleAvailable("char-def") then
  log:warning("char-def module not found")
  log:warning("cannot fix section id's")
  return function(dom) return dom end
end
require "char-def"
local chardata = characters.data or {}


local toc = nil

local function is_letter(info)
  -- test if character is letter
  local category = info.category or ""
  return category:match("^l") 
end

local function is_space(info)
  local category = info.category or ""
  return category == "zs"
end

local uchar = utf8.char
local function normalize_letter(char, result)
  local info = chardata[char] or {}
  -- first get lower case of the letter
  local lowercase = info.lccode or char
  -- remove accents. the base letter is in the shcode field
  local lowerinfo = chardata[lowercase] or {}
  -- when no shcode, use the current lowercase char
  local shcode = lowerinfo.shcode or lowercase
  -- shcode can be table if it contains multiple characters
  -- normaliz it to a table, so we can add all letters to 
  -- the resulting string
  if type(shcode) ~= "table" then shcode = {shcode} end
  for _, x in ipairs(shcode) do
    result[#result+1] = uchar(x)
  end
end

local escape_name = function(name)
  local result = {}
  -- remove LaTeX commands
  name = name:gsub("\\[%a]+", "")
  name = name:gsub("^%s+", ""):gsub("%s+$", "")
  for _,char in utf8.codes(name) do
    local info = chardata[char] or {}
    if is_space(info) then
      result[#result+1] = " "
    elseif is_letter(info) then
      normalize_letter(char, result)
    end
  end
  --- convert table with normalized characters to string
  local name = table.concat(result)
  -- remove spaces
  return name:gsub("%s+", "-")
end

local function parse_toc_line(line)
  -- the section ids and titles are saved in the following format:
  -- \csname a:TocLink\endcsname{1}{x1-20001}{QQ2-1-2}{Nazdar světe}
  -- ............................... id ................. title ...
  local id, name = line:match("a:TocLink.-{.-}{(.-)}{.-}(%b{})")
  if id then
    return id, escape_name(name)
  end
end

local used = {}

local function parse_toc(filename)
  local toc = {}
  if not mkutils.file_exists(filename) then return nil, "Cannot open TOC file "  .. filename end
  for line in io.lines(filename) do
    local id, name = parse_toc_line(line)
    local orig_name = name
    -- not all lines in the .4tc file contains TOC entries
    if id then
      -- test if the same name was used already. user should be notified
      if used[name] then
        -- update 
        name = name .. used[name]
        log:debug("Duplicate id found: ".. orig_name .. ". New id: " .. name)
      end
      used[orig_name] = (used[orig_name] or 0) + 1
      toc[id] = name
    end
  end
  return toc
end

-- we don't want to change the original id, as there may be links to it from the outside
-- so we will set it to the parent element (which should be h[1-6])
local function set_id(el, id)
  local section = el:get_parent()
  local section_id = section:get_attribute("id")
  if section_id and section_id~=id then -- if it already has id, we don't override it, but create dummy child instead
    local new = section:create_element("span", {id=id})
    section:add_child_node(new,1)
  else
    section:set_attribute("id", id)
  end

end

    

return  function(dom, par)
    local msg
    toc, msg = toc or parse_toc(par.input .. ".4tc")
    msg = msg or "Cannot load TOC"
    -- don't do anyting if toc cannot be found
    if not toc then 
      log:warning(msg) 
      return dom
    end
    -- if user selects the "notoc" option on the command line, we 
    -- will not update href links
    local notoc = false
    if par["tex4ht_sty_par"]:match("notoc") then notoc = true end
    -- the HTML file can already contain ID that we want to assign
    -- we will not set duplicate id from TOC in that case
    local toc_ids = {}
    for _, el in ipairs(dom:query_selector("[id]")) do
      local id = el:get_attribute("id")
      toc_ids[id] = true
    end
    -- process all elements with id atribute or <a href>
    for _, el in ipairs(dom:query_selector "[id],a[href]") do
      local id, href = el:get_attribute("id"), el:get_attribute("href") 
      if id then
        local name = toc[id]
        -- replace id with new section id
        if name and not toc_ids[name] then
          set_id(el, name)
        else
          if name then
            log:debug("Document already contains id: " .. name)
          end
        end
      end
      if href and notoc == false then
        -- replace links to sections with new id
        local base, anchor = href:match("^(.*)%#(.+)")
        local name = toc[anchor]
        if name then
          el:set_attribute("href", base .. "#" .. name)
        end
      end
    end
    return dom
  end