summaryrefslogtreecommitdiff
path: root/support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua
diff options
context:
space:
mode:
Diffstat (limited to 'support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua')
-rw-r--r--support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua182
1 files changed, 182 insertions, 0 deletions
diff --git a/support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua b/support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua
new file mode 100644
index 0000000000..5c6fb2073c
--- /dev/null
+++ b/support/lualibs/lua-uca/lua-uca/lua-uca-tailoring.lua
@@ -0,0 +1,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
+