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
|