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
183
184
185
186
187
188
189
190
191
|
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
if fn:match("%*$") then -- Ending with "*" like "<*"
fn = fn:sub(1, -2)
local exec = functions[fn]
for char in second:gmatch(utf8.charpattern) do
exec(first, char)
first = char
end
else
local exec = functions[fn]
exec(first, second)
first = second -- set the current second object as first for the next round
end
end
end
local M = {}
M.reorder = reorder
M.reorder_collator = reorder_collator
M.tailor_string = tailor_string
return M
|