summaryrefslogtreecommitdiff
path: root/support/lua/lua-uca/lua-uca/lua-uca-tailoring.lua
blob: 5c6fb2073c3edc045fee8859640a30af387babce (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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

local floor = math.floor

-- search value in table with ranges
-- source: Xindex
local function binary_range_search(code_point, ranges)
  local low, mid, high
  low, high = 1, #ranges
  while low <= high do
    mid = floor((low + high) / 2)
    local range = ranges[mid]
    if code_point < range[1] then
      high = mid - 1
    elseif code_point <= range[2] then
      return range, mid
    else
      low = mid + 1
    end
  end
  return nil, mid
end

local function get_range(block, blocks)
  for _,v in ipairs(blocks) do
    if v.name == block then return v.min, v.max end
  end
end

-- variables used for test whether the value lies inside range that should be moved
local inside_block = 1
local dont_move = 0 
local move = -1

local function get_search_table(min, max, max_value, blocks)
  -- prepare table with ranges for search
  local minimal_others = blocks.minimal_others
  local lower_range = min < minimal_others and  min - 1 or minimal_others
  -- construct search table
  local search = {
    {min, max, inside_block}, -- codes inside moved block
    {0, lower_range, dont_move}, -- codes that will not move
    {max + 1,  max_value, dont_move} -- codes above moved block
  }
  if min > minimal_others then -- codes need to be renumbered only when the code is moved back
    table.insert(search, {minimal_others + 1, min -1, move}) -- codes that need to be renumbered
  end
  table.sort(search, function(a,b) return a[1] < b[1] end)
  return search
end

local function clasify_blocks(blocks, search)
  -- find whether block should be moved
  for _, x in ipairs(blocks) do
    local status, mid = binary_range_search(x.min, search)
    local status_value = status[3]
    x.status = status_value
  end
end

local function renumber_block(block, min, max, move)
  local newmin, newmax = min + move, max + move
  block.move = (block.move or 0) + move
  return newmin, newmax
end

local function renumber_blocks(blocks, min, max)
  local move_offset = max - min + 1
  for k,v in ipairs(blocks) do
    -- set default value that will be used for the block moving
    v.move = v.move or 0
    if v.status == move then
      renumber_block(v, v.min, v.max, move_offset)
    elseif v.status == inside_block then
      local move = blocks.minimal_others - max + move_offset
      newmin, newmax = renumber_block(v, v.min, v.max, move)
      blocks.minimal_others = newmax + 1
    end
  end
end

local function reorder(what, blocks)
  local min, max = get_range(what, blocks)
  local max_value = blocks[#blocks].max -- find maximal value in blocks
  if what == "others" or what == "Zzzz" then 
    -- when reordering others, don't move anything, just set the
    -- minimal_others to the maximal value, so the next reordering will move 
    -- the reordered block behind all others
    blocks.minimal_others = max_value + 1
    return 
  end
  if not min then return nil, "Cannot find block for reordering" end
  local search = get_search_table(min, max, max_value, blocks)
  clasify_blocks(blocks, search)
  renumber_blocks(blocks, min, max)
  return blocks
end

local function reorder_collator(collator, blocks)
  local function prepare_search_table(blocks)
    -- make search table for collator weights
    -- initialize it with block that contain all values bellow the minimal block
    local search = {{0, blocks[1].min - 1, 0}}
    for _, block in ipairs(blocks) do
      -- return move value for each script block
      local t = {block.min, block.max, block.move}
      search[#search+1] = t
    end
    return search
  end
  local function update_value(value, search) 
    -- find the move offset for the primary weight in the reordering table
    local first = value[1][1]
    local status = binary_range_search(first, search) or {}
    local move = status[3] or 0
    if move ~= 0 then 
      -- detect if we need to recalculate the primary weight
      value[1][1] = first + move
    end
    return value
  end
  local function update_weights(entry, search)
    -- recursivelly process weighgs for the current codepoint
    if entry.value then
      entry.value = update_value(entry.value, search)
    end
    if entry.children then
      update_weights(entry.children, search)
    end
  end
  local search = prepare_search_table(blocks)
  for _, current in pairs(collator.codes) do
    update_weights(current, search)
  end
  -- clear sortkey cache
  collator.stringcache = {}
end

-----------------------
-- tailoring support --
-- --------------------
local tailoring_table = {1, 0, 0}
local secondary_tailoring = {0, 1, 0}
local tertiary_tailoring = {0, 0, 1}
local equal_tailoring= {0, 0, 0}

local function equal_string(collator_obj,base, target)
  collator_obj:equal(collator_obj:string_to_codepoints(base), collator_obj:string_to_codepoints(target))
end

local function tailor_string(collator_obj, str)
  -- cupport the cldr strings in the form: &D<<đ<<<Đ<<ð<<<Ð
  -- it is important that the strings are in the NFC normal form
  -- the CLDR XML files are in NFD, so they need to be converted
  -- for example using `uconv -x any-nfc < file.xml`
  local function tailor(a, b, tbl) 
    local autf = collator_obj:string_to_codepoints(a)
    local butf = collator_obj:string_to_codepoints(b)
    collator_obj:tailor(autf,butf, tbl) 
  end
  local function tailor_equal(base, target)
    equal_string(collator_obj, base, target)
  end
  local function tailor_primary(a,b) tailor(a,b, tailoring_table) end
  local function tailor_secondary(a,b) tailor(a,b, secondary_tailoring) end
  local function tailor_tertiary(a,b) tailor(a,b, tertiary_tailoring) end
  local functions = {["<"] = tailor_primary, ["<<"] = tailor_secondary, ["<<<"] = tailor_tertiary, ["="] = tailor_equal}
  local first = str:match("^&?([^%<^%=]+)")
  for fn, second in str:gmatch("([<=]+)([^<^%=]+)") do
    local exec = functions[fn]
    exec(first, second)
    first = second -- set the current second object as first for the next round
  end
end


local M = {}

M.reorder = reorder
M.reorder_collator = reorder_collator
M.tailor_string = tailor_string
return M