core-con.lmt /size: 73 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['core-con'] = {
2    version   = 1.001,
3    comment   = "companion to core-con.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- todo: split into lang-con.lua and core-con.lua
10
11-- This module implements a bunch of conversions. Some are more efficient than their
12-- TeX counterpart, some are even slower but look nicer this way. Some code may move
13-- to a module in the language namespace.
14
15local floor = math.floor
16local osdate, ostime, ostimezone = os.date, os.time, os.timezone
17local concat, insert, reverse = table.concat, table.insert, table.reverse
18local lower, upper, rep, match, gsub, gmatch = string.lower, string.upper, string.rep, string.match, string.gsub, string.gmatch
19local utfchar, utfbyte = utf.char, utf.byte
20local tonumber, tostring, type, rawset = tonumber, tostring, type, rawset
21local P, S, R, Cc, Ct, Cs, C, V, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cc, lpeg.Ct, lpeg.Cs, lpeg.C, lpeg.V, lpeg.Carg
22----- Cf, Cg = lpeg.Cf, lpeg.Cg
23local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
24local div, mod = math.div, math.mod
25
26local context            = context
27local commands           = commands
28local implement          = interfaces.implement
29
30local settings_to_array  = utilities.parsers.settings_to_array
31local allocate           = utilities.storage.allocate
32local setmetatableindex  = table.setmetatableindex
33local formatters         = string.formatters
34local variables          = interfaces.variables
35local constants          = interfaces.constants
36local addformatter       = utilities.strings.formatters.add
37
38local texset             = tex.set
39
40converters               = converters or { }
41local converters         = converters
42
43languages                = languages  or { }
44local languages          = languages
45
46local helpers            = converters.helpers or { }
47converters.helpers       = helpers
48
49local ctx_labeltext      = context.labeltext
50local ctx_LABELTEXT      = context.LABELTEXT
51local ctx_space          = context.space
52local ctx_convertnumber  = context.convertnumber
53local ctx_highordinalstr = context.highordinalstr
54
55converters.number  = tonumber
56converters.numbers = tonumber
57
58implement { name = "number",  actions = context }
59implement { name = "numbers", actions = context }
60
61-- to be reconsidered ... languages namespace here, might become local plus a register command
62
63local counters = allocate {
64    ['default'] = { -- no metatable as we do a test on keys
65        0x0061, 0x0062, 0x0063, 0x0064, 0x0065,
66        0x0066, 0x0067, 0x0068, 0x0069, 0x006A,
67        0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
68        0x0070, 0x0071, 0x0072, 0x0073, 0x0074,
69        0x0075, 0x0076, 0x0077, 0x0078, 0x0079,
70        0x007A
71    },
72    ['slovenian'] = {
73        0x0061, 0x0062, 0x0063, 0x010D, 0x0064,
74        0x0065, 0x0066, 0x0067, 0x0068, 0x0069,
75        0x006A, 0x006B, 0x006C, 0x006D, 0x006E,
76        0x006F, 0x0070, 0x0072, 0x0073, 0x0161,
77        0x0074, 0x0075, 0x0076, 0x007A, 0x017E
78    },
79    ['spanish'] = {
80        0x0061, 0x0062, 0x0063, 0x0064, 0x0065,
81        0x0066, 0x0067, 0x0068, 0x0069, 0x006A,
82        0x006B, 0x006C, 0x006D, 0x006E, 0x00F1,
83        0x006F, 0x0070, 0x0071, 0x0072, 0x0073,
84        0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
85        0x0079, 0x007A
86    },
87    ['russian'] = {
88        0x0430, 0x0431, 0x0432, 0x0433, 0x0434,
89        0x0435, 0x0436, 0x0437, 0x0438, 0x043a,
90        0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
91        0x0440, 0x0441, 0x0442, 0x0443, 0x0444,
92        0x0445, 0x0446, 0x0447, 0x0448, 0x0449,
93        0x044d, 0x044e, 0x044f
94    },
95    ['greek'] = { -- this should be the lowercase table
96     -- 0x0391, 0x0392, 0x0393, 0x0394, 0x0395,
97     -- 0x0396, 0x0397, 0x0398, 0x0399, 0x039A,
98     -- 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
99     -- 0x03A0, 0x03A1, 0x03A3, 0x03A4, 0x03A5,
100     -- 0x03A6, 0x03A7, 0x03A8, 0x03A9
101        0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5,
102        0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA,
103        0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
104        0x03C0, 0x03C1, 0x03C3, 0x03C4, 0x03C5,
105        0x03C6, 0x03C7, 0x03C8, 0x03C9,
106    },
107    ['arabic'] = {
108        0x0627, 0x0628, 0x062C, 0x062F, 0x0647,
109        0x0648, 0x0632, 0x062D, 0x0637, 0x0649,
110        0x0643, 0x0644, 0x0645, 0x0646, 0x0633,
111        0x0639, 0x0641, 0x0635, 0x0642, 0x0631,
112        0x0634, 0x062A, 0x062B, 0x062E, 0x0630,
113        0x0636, 0x0638, 0x063A,
114    },
115    ['persian'] = {
116        0x0627, 0x0628, 0x062C, 0x062F, 0x0647,
117        0x0648, 0x0632, 0x062D, 0x0637, 0x0649,
118        0x06A9, 0x0644, 0x0645, 0x0646, 0x0633,
119        0x0639, 0x0641, 0x0635, 0x0642, 0x0631,
120        0x0634, 0x062A, 0x062B, 0x062E, 0x0630,
121        0x0636, 0x0638, 0x063A,
122    },
123    ['thai'] = {
124        0xE050, 0xE051, 0xE052, 0xE053, 0xE054,
125        0xE055, 0xE056, 0xE057, 0xE058, 0xE059
126    },
127    ['devanagari'] = {
128     -- ० १ २ ३ ४ ५ ६ ७ ८ ९
129        0x0966, 0x0967, 0x0968, 0x0969, 0x096A,
130        0x096B, 0x096C, 0x096D, 0x096E, 0x096F
131    },
132    ['gurmurkhi'] = {
133        0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A,
134        0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F
135    },
136    ['gujarati'] = {
137        0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA,
138        0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF
139    },
140    ['tibetan'] = {
141        0x0F20, 0x0F21, 0x0F22, 0x0F23, 0x0F24,
142        0x0F25, 0x0F26, 0x0F27, 0x0F28, 0x0F29
143    },
144    ['korean'] = {
145        0x3131, 0x3134, 0x3137, 0x3139, 0x3141,
146        0x3142, 0x3145, 0x3147, 0x3148, 0x314A,
147        0x314B, 0x314C, 0x314D, 0x314E
148    },
149    ['korean-parenthesis'] = { --
150        0x3200, 0x3201, 0x3202, 0x3203, 0x3204,
151        0x3205, 0x3206, 0x3207, 0x3208, 0x3209,
152        0x320A, 0x320B, 0x320C, 0x320D
153    },
154    ['korean-circle'] = { -- circled
155        0x3260, 0x3261, 0x3262, 0x3263, 0x3264,
156        0x3265, 0x3266, 0x3267, 0x3268, 0x3269,
157        0x326A, 0x326B, 0x326C, 0x326D
158    },
159    ['hiragana'] = {
160        0x3042, 0x3044, 0x3046, 0x3048, 0x304A,
161        0x304B, 0x304D, 0x304F, 0x3051, 0x3053,
162        0x3055, 0x3057, 0x3059, 0x305B, 0x305D,
163        0x305F, 0x3061, 0x3064, 0x3066, 0x3068,
164        0x306A, 0x306B, 0x306C, 0x306D, 0x306E,
165        0x306F, 0x3072, 0x3075, 0x3078, 0x307B,
166        0x307E, 0x307F, 0x3080, 0x3081, 0x3082,
167        0x3084,         0x3086,         0x3088,
168        0x3089, 0x308A, 0x308B, 0x308C, 0x308D,
169        0x308F, 0x3090,         0x3091, 0x3092,
170        0x3093
171    },
172    ['hiragana-iroha'] = {
173        0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068,
174        0x3061, 0x308A, 0x306C, 0x308B, 0x3092,
175        0x308F, 0x304B, 0x3088, 0x305F, 0x308C, 0x305D,
176        0x3064, 0x306D, 0x306A, 0x3089, 0x3080,
177        0x3046, 0x3090, 0x306E, 0x304A, 0x304F, 0x3084, 0x307E,
178        0x3051, 0x3075, 0x3053, 0x3048, 0x3066,
179        0x3042, 0x3055, 0x304D, 0x3086, 0x3081, 0x307F, 0x3057,
180        0x3091, 0x3072, 0x3082, 0x305B, 0x3059
181    },
182    ['katakana'] = {
183        0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA,
184        0x30AB, 0x30AD, 0x30AF, 0x30B1, 0x30B3,
185        0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD,
186        0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8,
187        0x30CA, 0x30CB, 0x30CC, 0x30CD, 0x30CE,
188        0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB,
189        0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2,
190        0x30E4,         0x30E6,         0x30E8,
191        0x30E9, 0x30EA, 0x30EB, 0x30EC, 0x30ED,
192        0x30EF, 0x30F0,         0x30F1, 0x30F2,
193        0x30F3
194    },
195    ['katakana-iroha'] = {
196        0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8,
197        0x30C1, 0x30EA, 0x30CC, 0x30EB, 0x30F2,
198        0x30EF, 0x30AB, 0x30E8, 0x30BF, 0x30EC, 0x30BD,
199        0x30C4, 0x30CD, 0x30CA, 0x30E9, 0x30E0,
200        0x30A6, 0x30F0, 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE,
201        0x30B1, 0x30D5, 0x30B3, 0x30A8, 0x30C6,
202        0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1, 0x30DF, 0x30B7,
203        0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x30B9
204    },
205}
206
207languages.counters = counters
208
209counters['ar']                        = counters['arabic']
210counters['gr']                        = counters['greek']
211counters['g']                         = counters['greek']
212counters['sl']                        = counters['slovenian']
213counters['es']                        = counters['spanish']
214counters['ru']                        = counters['russian']
215counters['kr']                        = counters['korean']
216counters['kr-p']                      = counters['korean-parenthesis']
217counters['kr-c']                      = counters['korean-circle']
218
219counters['thainumerals']              = counters['thai']
220counters['devanagarinumerals']        = counters['devanagari']
221counters['gurmurkhinumerals']         = counters['gurmurkhi']
222counters['gujaratinumerals']          = counters['gujarati']
223counters['tibetannumerals']           = counters['tibetan']
224counters['greeknumerals']             = counters['greek']
225counters['arabicnumerals']            = counters['arabic']
226counters['persiannumerals']           = counters['persian']
227counters['arabicexnumerals']          = counters['persian']
228counters['koreannumerals']            = counters['korean']
229counters['koreanparenthesisnumerals'] = counters['korean-parenthesis']
230counters['koreancirclenumerals']      = counters['korean-circle']
231
232counters['sloveniannumerals']         = counters['slovenian']
233counters['spanishnumerals']           = counters['spanish']
234counters['russiannumerals']           = counters['russian']
235
236local decimals = allocate {
237    ['arabic'] = {
238        ["0"] = "٠", ["1"] = "١", ["2"] = "٢", ["3"] = "٣", ["4"] = "٤",
239        ["5"] = "٥", ["6"] = "٦", ["7"] = "٧", ["8"] = "٨", ["9"] = "٩",
240    },
241    ['persian'] = {
242        ["0"] = "۰", ["1"] = "۱", ["2"] = "۲", ["3"] = "۳", ["4"] = "۴",
243        ["5"] = "۵", ["6"] = "۶", ["7"] = "۷", ["8"] = "۸", ["9"] = "۹",
244    },
245    ['devanagari'] = {
246        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
247        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
248    },
249    ['malayalam'] = {
250        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
251        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
252    },
253    ['tamil'] = {
254        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
255        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
256    },
257    ['kannada'] = {
258        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
259        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
260    },
261    ['telugu'] = {
262        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
263        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
264    },
265    ['bengali'] = {
266        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
267        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
268    },
269    ['gujarati'] = {
270        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
271        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
272    },
273    ['gurmurkhi'] = {
274        ["0"] = "", ["1"] = "", ["2"] = "", ["3"] = "", ["4"] = "",
275        ["5"] = "", ["6"] = "", ["7"] = "", ["8"] = "", ["9"] = "",
276    },
277}
278
279languages.decimals = decimals
280
281local fallback = utfbyte('0')
282
283local function chr(n,m)
284    return (n > 0 and n < 27 and utfchar(n+m)) or ""
285end
286
287local function chrs(n,m,t)
288    if not t then
289        t = { }
290    end
291    if n > 26 then
292        chrs(floor((n-1)/26),m,t)
293        n = (n-1)%26 + 1
294    end
295    if n ~= 0 then
296        t[#t+1] = utfchar(n+m)
297    end
298    if n <= 26 then
299        return concat(t)
300    end
301end
302
303local function maxchrs(n,m,cmd,t)
304    if not t then
305        t = { }
306    end
307    if n > m then
308        maxchrs(floor((n-1)/m),m,cmd)
309        n = (n-1)%m + 1
310    end
311    t[#t+1] = formatters["%s{%s}"](cmd,n)
312    if n <= m then
313        return concat(t)
314    end
315end
316
317converters.chr     = chr
318converters.chrs    = chrs
319converters.maxchrs = maxchrs
320
321local lowercharacter = characters.lcchars
322local uppercharacter = characters.ucchars
323
324local defaultcounter = counters.default
325
326local function do_alphabetic(n,mapping,mapper,t) -- todo: make zero based variant (initial n + 1)
327    if not t then
328        t = { }
329    end
330    local max = #mapping
331    if n > max then
332        do_alphabetic(floor((n-1)/max),mapping,mapper,t)
333        n = (n-1) % max + 1
334    end
335    local chr = mapping[n] or fallback
336    t[#t+1] = mapper and mapper[chr] or chr
337    if n <= max then
338        return concat(t)
339    end
340end
341
342local function alphabetic(n,code)
343    return do_alphabetic(n,code and code ~= "" and counters[code] or defaultcounter,lowercharacter)
344end
345
346local function Alphabetic(n,code)
347    return do_alphabetic(n,code and code ~= "" and counters[code] or defaultcounter,uppercharacter)
348end
349
350converters.alphabetic = alphabetic
351converters.Alphabetic = Alphabetic
352
353-- we could make a replacer
354
355local function todecimals(n,name)
356    local stream  = tostring(n)
357    local mapping = decimals[name]
358    return mapping and gsub(stream,".",mapping) or stream
359end
360
361converters.decimals = todecimals
362
363local lower_offset = 96
364local upper_offset = 64
365
366function converters.character (n) return chr (n,lower_offset) end
367function converters.Character (n) return chr (n,upper_offset) end
368function converters.characters(n) return chrs(n,lower_offset) end
369function converters.Characters(n) return chrs(n,upper_offset) end
370
371implement { name = "alphabetic", actions = { alphabetic, context }, arguments = { "integer", "string" } }
372implement { name = "Alphabetic", actions = { Alphabetic, context }, arguments = { "integer", "string" } }
373
374implement { name = "character",  actions = { chr,  context }, arguments = { "integer", lower_offset } }
375implement { name = "Character",  actions = { chr,  context }, arguments = { "integer", upper_offset } }
376implement { name = "characters", actions = { chrs, context }, arguments = { "integer", lower_offset } }
377implement { name = "Characters", actions = { chrs, context }, arguments = { "integer", upper_offset } }
378
379implement { name = "decimals",   actions = { todecimals, context }, arguments = { "integer", "string" } }
380
381local weekday    = os.weekday    -- moved to l-os
382local isleapyear = os.isleapyear -- moved to l-os
383local nofdays    = os.nofdays    -- moved to l-os
384
385local function leapyear(year)
386    return isleapyear(year) and 1 or 0
387end
388
389local function textime()
390    return tonumber(osdate("%H")) * 60 + tonumber(osdate("%M"))
391end
392
393-- For consistenty we need to add day here but that conflicts with the current
394-- serializer so then best is to have none from now on:
395
396-- function converters.year  () return osdate("%Y") end
397-- function converters.month () return osdate("%m") end -- always two digits
398-- function converters.day   () return osdate("%d") end -- conflicts
399-- function converters.hour  () return osdate("%H") end
400-- function converters.minute() return osdate("%M") end
401-- function converters.second() return osdate("%S") end
402
403converters.weekday    = weekday
404converters.isleapyear = isleapyear
405converters.leapyear   = leapyear
406converters.nofdays    = nofdays
407converters.textime    = textime
408
409implement { name = "weekday",  actions = { weekday,  context }, arguments = { "integer", "integer", "integer" } }
410implement { name = "leapyear", actions = { leapyear, context }, arguments = "integer" }
411implement { name = "nofdays",  actions = { nofdays,  context }, arguments = { "integer", "integer" } }
412
413implement { name = "year",     actions = { osdate,   context }, arguments = "'%Y'" }
414implement { name = "month",    actions = { osdate,   context }, arguments = "'%m'" }
415implement { name = "day",      actions = { osdate,   context }, arguments = "'%d'" }
416implement { name = "hour",     actions = { osdate,   context }, arguments = "'%H'" }
417implement { name = "minute",   actions = { osdate,   context }, arguments = "'%M'" }
418implement { name = "second",   actions = { osdate,   context }, arguments = "'%S'" }
419implement { name = "textime",  actions = { textime,  context } }
420
421implement {
422    name      = "doifelseleapyear",
423    actions   = { isleapyear, commands.doifelse },
424    arguments = "integer"
425}
426
427local roman = {
428    { [0] = '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX' },
429    { [0] = '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC' },
430    { [0] = '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM' },
431}
432
433local function toroman(n)
434    if n >= 4000 then
435        return toroman(floor(n/1000)) .. " " .. toroman(n%1000)
436    else
437        return rep("M",floor(n/1000)) .. roman[3][floor((n%1000)/100)] .. roman[2][floor((n%100)/10)] .. roman[1][floor((n%10)/1)]
438    end
439end
440
441converters.toroman       = toroman
442converters.Romannumerals = toroman
443converters.romannumerals = function(n) return lower(toroman(n)) end
444
445converters['i']  = converters.romannumerals
446converters['I']  = converters.Romannumerals
447converters['r']  = converters.romannumerals
448converters['R']  = converters.Romannumerals
449converters['KR'] = converters.Romannumerals
450converters['RK'] = converters.Romannumerals
451
452implement {
453    name      = "romannumerals",
454    actions   = { toroman, lower, context },
455    arguments = "integer",
456}
457
458implement {
459    name      = "Romannumerals",
460    actions   = { toroman, context },
461    arguments = "integer",
462}
463
464--~ local small = {
465--~     0x0627, 0x066E, 0x062D, 0x062F, 0x0647, 0x0648, 0x0631
466--~ }
467
468--~ local large = {
469--~     { 0x0627, 0x0628, 0x062C, 0x062F, 0x0647, 0x0648, 0x0632, 0x062D, 0x0637, },
470--~     { 0x064A, 0x0643, 0x0644, 0x0645, 0x0646, 0x0633, 0x0639, 0x0641, 0x0635, },
471--~     { 0x0642, 0x0631, 0x0634, 0x062A, 0x062B, 0x062E, 0x0630, 0x0636, 0x0638, },
472--~     { 0x063A                                                                  },
473--~ }
474
475local small = {
476    "ا", "ٮ", "ح", "د", "ه", "و", "ر",
477}
478
479local medium = {
480     "ا", "ب", "ج", "د", "ه", "و","ز", "ح", "ط" ,
481     "ي", "ك", "ل", "م", "ن", "س", "ع", "ف", "ص" ,
482     "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ" ,
483     "غ" ,
484}
485
486local large = {
487    { "ا", "ب", "ج", "د", "ه", "و","ز", "ح", "ط" },
488    { "ي", "ك", "ل", "م", "ن", "س", "ع", "ف", "ص" },
489    { "ق", "ر", "ش", "ت", "ث", "خ", "ذ", "ض", "ظ" },
490    { "غ" },
491}
492
493local function toabjad(n,what)
494    if n <= 0 or n >= 2000 then
495        return tostring(n)
496    elseif what == 2 and n <= 7 then
497        return small[n]
498    elseif what == 3 and n <= 28 then
499        return medium[n]
500    else
501        local a, b, c, d
502        a, n = floor(n/1000), n % 1000 -- mod(n,1000)
503        b, n = floor(n/ 100), n %  100 -- mod(n, 100)
504        c, n = floor(n/  10), n %   10 -- mod(n,  10)
505        d, n = floor(n/   1), n %    1 -- mod(n,   1)
506        return (large[4][a] or "") .. (large[3][b] or "") .. (large[2][c] or "") .. (large[1][d] or "")
507    end
508end
509
510converters.toabjad = toabjad
511
512function converters.abjadnumerals     (n) return toabjad(n,false) end
513function converters.abjadnodotnumerals(n) return toabjad(n,true ) end
514
515implement {
516    name      = "abjadnumerals",
517    actions   = { toabjad, context },
518    arguments = { "integer", false }
519}
520
521implement {
522    name      = "abjadnodotnumerals",
523    actions   = { toabjad, context },
524    arguments = { "integer", true }
525}
526
527-- -- - hebrew and jiddish -- -- --
528
529local trace_hebrew  trackers.register("converters.hebrew", function(v)
530    trace_hebrew = v
531end)
532
533local list = {
534    { 400, "ת" }, { 300, "ש" }, { 200, "ר" }, { 100, "ק" },
535    {  90, "צ" }, { 80, "פ" }, { 70, "ע" }, { 60, "ס "}, { 50, "נ" }, { 40, "מ" }, { 30, "ל" }, { 20, "כ" }, { 10, "י" },
536    {   9, "ט" }, { 8, "ח" }, { 7, "ז", }, { 6, "ו", }, { 5, "ה" }, { 4, "ד" }, { 3, "ג" }, { 2, "ב" }, { 1, "א" },
537}
538
539local special = {
540    [15] = "ט״ו", -- exception: avoid mixup with God יה
541    [16] = "ט״ז", -- exception: avoid mixup with God יו
542}
543
544local function tohebrew(n,gershayim,geresh)
545    local split = { }
546    local size  = 0
547    while n > 1000 do
548        size = size + 1
549        split[size] = n % 1000
550        n = floor(n/1000)
551    end
552    size = size + 1
553    split[size] = n
554    for i=1,size do
555        local t = { }
556        local n = 0
557        local s = split[i]
558        while s > 0 do
559            for i=1,#list do
560              ::again::
561                local li = list[i]
562                local l1 = li[1]
563                local s1 = special[l1]
564                if s1 then
565                    s = s - l1
566                    n = n + 1
567                    t[n] = s1
568                    goto again
569                elseif s >= l1 then
570                    s = s - l1
571                    n = n + 1
572                    t[n] = li[2]
573                    goto again
574                end
575            end
576        end
577        ::done::
578        split[i] = t
579    end
580    if gershayim then
581        for i=1,size do
582            local si = split[i]
583            local ni = #si
584            if ni >= 2 then
585                local s = "״"
586                insert(split[i],ni,trace_hebrew and ("{\\red "..s.."}") or s)
587            end
588        end
589    end
590    if geresh then
591        for i=2,#split do
592            local s = rep("׳",i-1)
593            insert(split[i],trace_hebrew and ("{\\blue "..s.."}") or s)
594        end
595    end
596    for i=1,size do
597        split[i] = concat(split[i])
598    end
599    return concat(reverse(split))
600end
601
602converters.tohebrew       = tohebrew
603converters.hebrewnumerals = converters.tohebrew
604
605-- converters['alphabetic:hb'] = converters.hebrewnumerals
606
607interfaces.implement {
608    name      = "hebrewnumerals",
609    actions   = { tohebrew, context },
610    arguments = { "integer", true, true }
611}
612
613-- -- --
614
615local vector = {
616    normal = {
617                [0] = "",
618                [1] = "",
619                [2] = "",
620                [3] = "",
621                [4] = "",
622                [5] = "",
623                [6] = "",
624                [7] = "",
625                [8] = "",
626                [9] = "",
627               [10] = "",
628              [100] = "",
629             [1000] = "",
630            [10000] = "",
631        [100000000] = "亿",
632    },
633    cap = {
634                [0] = "",
635                [1] = "",
636                [2] = "",
637                [3] = "",
638                [4] = "",
639                [5] = "",
640                [6] = "",
641                [7] = "",
642                [8] = "",
643                [9] = "",
644               [10] = "",
645              [100] = "",
646             [1000] = "",
647            [10000] = "",
648        [100000000] = "亿",
649    },
650    all = {
651                [0] = "",
652                [1] = "",
653                [2] = "",
654                [3] = "",
655                [4] = "",
656                [5] = "",
657                [6] = "",
658                [7] = "",
659                [8] = "",
660                [9] = "",
661               [10] = "",
662               [20] = "廿",
663               [30] = "",
664              [100] = "",
665             [1000] = "",
666            [10000] = "",
667        [100000000] = "亿",
668    }
669}
670
671local function tochinese(n,name) -- normal, caps, all, date
672 -- improved version by Li Yanrui, Song Yihan
673    local result, r = { }, 0
674
675    if name == "date" then
676        -- We could do some number juggling instead but this is fast enough. There is
677        -- no error checking here so we assume a proper year. Maybe a better name is
678        -- asis (so that it can be used for more than dates).
679        local vector = vector.normal
680        for s in gmatch(tostring(n),".") do
681            r = r + 1 ; result[r] = vector[tonumber(s)]
682        end
683        return concat(result)
684    end
685
686    local vector = vector[name] or vector.normal
687    while true do
688        if n == 0 then
689            break
690        elseif n >= 100000000 then
691            local m = floor(n/100000000)
692            r = r + 1 ; result[r] = tochinese(m,name)
693            r = r + 1 ; result[r] = vector[100000000]
694            local z = n - m * 100000000
695            if z > 0 and z < 10000000 then r = r + 1 ; result[r] = vector[0] end
696            n = n % 100000000
697        elseif n >= 10000000 then
698            local m = floor(n/10000)
699            r = r + 1 ; result[r] = tochinese(m,name)
700            r = r + 1 ; result[r] = vector[10000]
701            local z = n - m * 10000
702            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
703            n = n % 10000
704        elseif n >= 1000000 then
705            local m = floor(n/10000)
706            r = r + 1 ; result[r] = tochinese(m,name)
707            r = r + 1 ; result[r] = vector[10000]
708            local z = n - m * 10000
709            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
710            n = n % 10000
711        elseif n >= 100000 then
712            local m = floor(n/10000)
713            r = r + 1 ; result[r] = tochinese(m,name)
714            r = r + 1 ; result[r] = vector[10000]
715            local z = n - m * 10000
716            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
717            n = n % 10000
718         elseif n >= 10000 then
719            local m = floor(n/10000)
720            r = r + 1 ; result[r] = vector[m]
721            r = r + 1 ; result[r] = vector[10000]
722            local z = n - m * 10000
723            if z > 0 and z < 1000 then r = r + 1 ; result[r] = vector[0] end
724            n = n % 10000
725         elseif n >= 1000 then
726            local m = floor(n/1000)
727            r = r + 1 ; result[r] = vector[m]
728            r = r + 1 ; result[r] = vector[1000]
729            local z =  n - m * 1000
730            if z > 0 and z < 100 then r = r + 1 ; result[r] = vector[0] end
731            n = n % 1000
732         elseif n >= 100 then
733            local m = floor(n/100)
734            r = r + 1 ; result[r] = vector[m]
735            r = r + 1 ; result[r] = vector[100]
736            local z = n - m * 100
737            if z > 0 and z < 10 then r = r + 1 ; result[r] = vector[0] end
738            n = n % 100
739         elseif n >= 10 then
740            local m = floor(n/10)
741            if m > 1 and vector[m*10] then
742                r = r + 1 ; result[r] = vector[m*10]
743            else
744                r = r + 1 ; result[r] = vector[m]
745                r = r + 1 ; result[r] = vector[10]
746            end
747            n = n % 10
748        else
749            r = r + 1 ; result[r] = vector[n]
750            break
751        end
752    end
753    if (result[1] == vector[1] and result[2] == vector[10]) then
754        result[1] = ""
755    end
756    return concat(result)
757end
758
759-- local t = { 1,10,15,25,35,45,11,100,111,1111,10000,11111,100000,111111,1111111,11111111,111111111,100000000,1111111111,11111111111,111111111111,1111111111111 }
760-- for k=1,#t do
761-- local v = t[k]
762--     print(v,tochinese(v),tochinese(v,"all"),tochinese(v,"cap"))
763-- end
764
765converters.tochinese = tochinese
766
767function converters.chinesenumerals   (n,how) return tochinese(n,how or "normal") end
768function converters.chinesecapnumerals(n)     return tochinese(n,"cap") end
769function converters.chineseallnumerals(n)     return tochinese(n,"all") end
770function converters.chinesedatenumerals(n)    return tochinese(n,"date") end
771
772converters['cn']   = converters.chinesenumerals
773converters['cn-c'] = converters.chinesecapnumerals
774converters['cn-a'] = converters.chineseallnumerals
775converters['cn-d'] = converters.chinesedatenumerals
776
777implement {
778    name      = "chinesenumerals",
779    actions   = { tochinese, context },
780    arguments = { "integer", "string" }
781}
782
783-- this is a temporary solution: we need a better solution when we have
784-- more languages
785
786converters['a']  = converters.characters
787converters['A']  = converters.Characters
788converters['AK'] = converters.Characters -- obsolete
789converters['KA'] = converters.Characters -- obsolete
790
791function converters.spanishnumerals  (n) return alphabetic(n,"es") end
792function converters.Spanishnumerals  (n) return Alphabetic(n,"es") end
793function converters.sloveniannumerals(n) return alphabetic(n,"sl") end
794function converters.Sloveniannumerals(n) return Alphabetic(n,"sl") end
795function converters.russiannumerals  (n) return alphabetic(n,"ru") end
796function converters.Russiannumerals  (n) return Alphabetic(n,"ru") end
797
798converters['alphabetic:es'] = converters.spanishnumerals
799converters['alphabetic:sl'] = converters.sloveniannumerals
800converters['alphabetic:ru'] = converters.russiannumerals
801
802converters['Alphabetic:es'] = converters.Spanishnumerals
803converters['Alphabetic:sl'] = converters.Sloveniannumerals
804converters['Alphabetic:ru'] = converters.Russiannumerals
805
806-- bonus
807
808converters['a:es']  = converters.spanishnumerals
809converters['a:sl']  = converters.sloveniannumerals
810converters['a:ru']  = converters.russiannumerals
811converters['A:es']  = converters.Spanishnumerals
812converters['A:sl']  = converters.Sloveniannumerals
813converters['A:ru']  = converters.Russiannumerals
814
815-- end of bonus
816
817converters.sequences = converters.sequences or { }
818local sequences      = converters.sequences
819
820storage.register("converters/sequences", sequences, "converters.sequences")
821
822function converters.define(name,set) -- ,language)
823 -- if language then
824 --     name = name .. ":" .. language
825 -- end
826    sequences[name] = settings_to_array(set)
827end
828
829function converters.max(name)
830    local s = sequences[name]
831    return s and #s or 0
832end
833
834implement {
835    name      = "defineconversion",
836    actions   = converters.define,
837    arguments = "2 strings",
838}
839
840implement {
841    name      = "nofconversions",
842    actions   = { converters.max, context },
843    arguments = "string",
844}
845
846local function convert(method,n,language)
847    local converter = language and converters[method..":"..language] or converters[method]
848    if converter then
849        return converter(n)
850    else
851        local lowermethod = lower(method)
852        local linguistic  = counters[lowermethod]
853        if linguistic then
854            return do_alphabetic(n,linguistic,lowermethod == method and lowercharacter or uppercharacter)
855        end
856        local sequence = sequences[method]
857        if sequence then
858            local max = #sequence
859            if n > max then
860                return sequence[(n-1) % max + 1]
861            else
862                return sequence[n]
863            end
864        end
865        return n
866    end
867end
868
869converters.convert = convert
870
871local function valid(method,language)
872    return converters[method..":"..language] or converters[method] or sequences[method]
873end
874
875implement {
876    name      = "doifelseconverter",
877    actions   = { valid, commands.doifelse },
878    arguments = "2 strings",
879}
880
881implement {
882    name      = "checkedconversion",
883    actions   = { convert, context },
884    arguments = { "string", "integer" }
885}
886
887-- hebrew data conversion
888
889local gregorian_to_hebrew  do
890
891    -- The next code is based on the c code at https://github.com/hebcal/hebcal
892    --
893    -- Hebcal     : A Jewish Calendar Generator
894    -- Copyright  : 1994 - 2004 Danny Sadinoff, 2002 Michael J. Radwin
895    -- License    : GPL
896    --
897    -- Because we only need simple dates, we use part of the code. There is more
898    -- at that github place.
899    --
900    -- The number of days elapsed between the Gregorian date 12/31/1 BC and DATE.
901    -- The Gregorian date Sunday, December 31, 1 BC is imaginary.
902    --
903    -- The code is luafied and a little different because we have no assignments
904    -- in loops and such. We could speed up the code but then we divert from the
905    -- original. This is not that critical anyway.
906
907    local NISAN    =  1
908    local IYYAR    =  2
909    local SIVAN    =  3
910    local TAMUZ    =  4
911    local AV       =  5
912    local ELUL     =  6
913    local TISHREI  =  7
914    local CHESHVAN =  8
915    local KISLEV   =  9
916    local TEVET    = 10
917    local SHVAT    = 11
918    local ADAR_I   = 12
919    local ADAR_II  = 13
920
921    local mmap = {
922        KISLEV,
923        TEVET,
924        SHVAT,
925        ADAR_I,
926        NISAN,
927        IYYAR,
928        SIVAN,
929        TAMUZ,
930        TISHREI,
931        TISHREI,
932        TISHREI,
933        CHESHVAN
934    }
935
936    local function greg2abs(year,month,day)
937        local y = year - 1
938        return y * 365 + div(y,4)- div(y,100) + div(y,400) + os.nofdays(year,month,day)
939    end
940
941    local function abs2greg(abs)
942
943        local d0   = abs - 1
944
945        local n400 = div(d0, 146097)
946        local d1   = mod(d0, 146097)
947        local n100 = div(d1,  36524)
948        local d2   = mod(d1,  36524)
949        local n4   = div(d2,   1461)
950        local d3   = mod(d2,   1461)
951        local n1   = div(d3,    365)
952
953        local day  = mod(d3, 365) + 1
954        local year = 400 * n400 + 100 * n100 + 4 * n4 + n1
955
956        if n100 == 4 or n1 == 4 then
957            return year, 12, 31
958        else
959            year = year + 1
960            month = 1
961            while true do
962                local mlen = os.nofdays(year,month)
963                if mlen < day then
964                    day   = day - mlen
965                    month = month + 1
966                else
967                    break
968                end
969            end
970            return year, month, day
971        end
972    end
973
974    local function hebrew_leapyear(year)
975        return mod(1 + year * 7, 19) < 7
976    end
977
978    local function hebrew_months_in_year(year)
979        return hebrew_leapyear(year) and 13 or 12
980    end
981
982    local function hebrew_elapsed_days(year)
983        local y         = year - 1
984        local m_elapsed = 235 * div(y,19) + 12 * mod(y,19) + div(((mod(y,19) * 7) + 1),19)
985        local p_elapsed = 204 + 793 * mod(m_elapsed,1080)
986        local h_elapsed = 5 + 12 * m_elapsed + 793 * div(m_elapsed,1080) + div(p_elapsed,1080)
987        local parts     = mod(p_elapsed,1080) + 1080 * mod(h_elapsed,24)
988        local day       = 1 + 29 * m_elapsed + div(h_elapsed,24)
989        local d         = mod(day,7)
990        local alt_day   = day
991        if       parts >= 19440
992             or (parts >=  9924 and d == 2 and not (hebrew_leapyear(year)))
993             or (parts >= 16789 and d == 1 and      hebrew_leapyear(y   )) then
994            alt_day = alt_day + 1
995        end
996        d = mod(alt_day,7)
997        if d == 0 or d == 3 or d == 5 then
998            alt_day = alt_day + 1
999        end
1000        return alt_day
1001    end
1002
1003    local function days_in_hebrew_year(year)
1004        return hebrew_elapsed_days(year + 1) - hebrew_elapsed_days(year)
1005    end
1006
1007    local function long_cheshvan(year)
1008        return mod(days_in_hebrew_year(year),10) == 5
1009    end
1010
1011    local function short_kislev(year)
1012        return mod(days_in_hebrew_year(year),10) == 3
1013    end
1014
1015    local function max_days_in_heb_month(month,year)
1016        if month == IYYAR or month == TAMUZ or month == ELUL or month == TEVET or month == ADAR_II or
1017           (month == ADAR_I   and not hebrew_leapyear(year)) or
1018           (month == CHESHVAN and not long_cheshvan(year))   or
1019           (month == KISLEV   and     short_kislev (year))   then
1020            return 29
1021        else
1022            return 30
1023        end
1024    end
1025
1026    local function hebrew2abs(year,month,day)
1027        if month < TISHREI then
1028            for m=TISHREI, hebrew_months_in_year(year), 1 do
1029                day = day + max_days_in_heb_month(m,year)
1030            end
1031            for m=NISAN, month - 1, 1 do
1032                day = day + max_days_in_heb_month(m,year)
1033            end
1034        else
1035            for m=TISHREI, month - 1, 1 do
1036                day = day + max_days_in_heb_month(m,year)
1037            end
1038        end
1039        return hebrew_elapsed_days(year) - 1373429 + day
1040    end
1041
1042    local function abs2hebrew(abs)
1043        local yy, mm, dd = abs2greg(abs)
1044        local day   = 1
1045        local month = TISHREI
1046        local year  = 3760 + yy
1047        while abs >= hebrew2abs(year+1,month,day) do
1048            year = year + 1
1049        end
1050        if year >= 4635 and year < 10666 then
1051            month = mmap[mm]
1052        end
1053        while abs > hebrew2abs(year,month,max_days_in_heb_month(month,year)) do
1054            month = mod(month,hebrew_months_in_year(year)) + 1
1055        end
1056        day = abs - hebrew2abs(year,month,1) + 1
1057        return year, month, day
1058    end
1059
1060    -- months = { "ניסן" "אייר" "סיון" "תמוז" "אב" "אלול" "תשרי" "חשון" "כסלו" "טבת" "שבט" }
1061
1062    gregorian_to_hebrew = function(y,m,d)
1063        return abs2hebrew(greg2abs(y,m,d))
1064    end
1065
1066    converters.gregorian_to_hebrew = gregorian_to_hebrew
1067
1068end
1069
1070local gregorian_to_jalali, jalali_to_gregorian do
1071
1072    -- Well, since the one asking for this didn't test it the following code is not
1073    -- enabled.
1074    --
1075    -- -- This Lua version is based on a Javascript by Behdad Esfahbod which in turn
1076    -- -- is based on GPL'd code by Roozbeh Pournader of the The FarsiWeb Project
1077    -- -- Group: http://www.farsiweb.info/jalali/jalali.js.
1078    -- --
1079    -- -- We start tables at one, I kept it zero based in order to stay close to
1080    -- -- the original.
1081    -- --
1082    -- -- Conversion by Hans Hagen
1083
1084    local g_days_in_month = { [0] = 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
1085    local j_days_in_month = { [0] = 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29 }
1086
1087
1088    gregorian_to_jalali = function(gy,gm,gd)
1089        local jy, jm, jd, g_day_no, j_day_no, j_np, i
1090        gy, gm, gd = gy - 1600, gm - 1, gd - 1
1091        g_day_no = 365*gy + div((gy+3),4) - div((gy+99),100) + div((gy+399),400)
1092        i = 0
1093        while i < gm do
1094            g_day_no = g_day_no + g_days_in_month[i]
1095            i = i + 1
1096        end
1097        if (gm>1 and ((gy%4==0 and gy%100~=0) or (gy%400==0))) then
1098            g_day_no = g_day_no + 1
1099        end
1100        g_day_no = g_day_no + gd
1101        j_day_no = g_day_no - 79
1102        j_np = div(j_day_no,12053)
1103        j_day_no = mod(j_day_no,12053)
1104        jy = 979 + 33*j_np + 4*div(j_day_no,1461)
1105        j_day_no = mod(j_day_no,1461)
1106        if j_day_no >= 366 then
1107            jy = jy + div((j_day_no-1),365)
1108            j_day_no = mod((j_day_no-1),365)
1109        end
1110        i = 0
1111        while i < 11 and j_day_no >= j_days_in_month[i] do
1112            j_day_no = j_day_no - j_days_in_month[i]
1113            i = i + 1
1114        end
1115        jm = i + 1
1116        jd = j_day_no + 1
1117        return jy, jm, jd
1118    end
1119
1120    jalali_to_gregorian = function(jy,jm,jd)
1121        local gy, gm, gd, g_day_no, j_day_no, leap, i
1122        jy, jm, jd = jy - 979, jm - 1, jd - 1
1123        j_day_no = 365*jy + div(jy,33)*8 + div((mod(jy,33)+3),4)
1124        for i=0,jm-1,1 do
1125            j_day_no = j_day_no + j_days_in_month[i]
1126        end
1127        j_day_no = j_day_no + jd
1128        g_day_no = j_day_no + 79
1129        gy = 1600 + 400*div(g_day_no,146097)
1130        g_day_no = mod(g_day_no, 146097)
1131        leap = 1
1132        if g_day_no >= 36525 then
1133            g_day_no = g_day_no - 1
1134            gy = gy + 100*div(g_day_no,36524)
1135            g_day_no = mod(g_day_no, 36524)
1136            if g_day_no >= 365 then
1137                g_day_no = g_day_no + 1
1138            else
1139                leap = 0
1140            end
1141        end
1142        gy = gy  + 4*div(g_day_no,1461)
1143        g_day_no = mod(g_day_no, 1461)
1144        if g_day_no >= 366 then
1145            leap = 0
1146            g_day_no = g_day_no - 1
1147            gy = gy + div(g_day_no, 365)
1148            g_day_no = mod(g_day_no, 365)
1149        end
1150        i = 0
1151        while true do
1152            local d = g_days_in_month[i] + ((i == 1 and leap) or 0)
1153            if g_day_no >= d then
1154                g_day_no = g_day_no - d
1155                i = i + 1
1156            else
1157                break
1158            end
1159        end
1160        gm = i + 1
1161        gd = g_day_no + 1
1162        return gy, gm, gd
1163    end
1164
1165    -- local function test(yg,mg,dg,yj,mj,dj)
1166    --     local y1, m1, d1 = jalali_to_gregorian(yj,mj,dj)
1167    --     local y2, m2, d2 = gregorian_to_jalali(yg,mg,dg)
1168    --     print(y1 == yg and m1 == mg and d1 == dg, yg,mg,dg, y1,m1,d1)
1169    --     print(y2 == yj and m2 == mj and d2 == dj, yj,mj,dj, y2,m2,d2)
1170    -- end
1171
1172    -- test(1953,08,19, 1332,05,28)
1173    -- test(1979,02,11, 1357,11,22)
1174    -- test(2000,02,28, 1378,12,09)
1175    -- test(2000,03,01, 1378,12,11)
1176    -- test(2009,02,24, 1387,12,06)
1177    -- test(2015,03,21, 1394,01,01)
1178    -- test(2016,03,20, 1395,01,01)
1179
1180    converters.gregorian_to_jalali = gregorian_to_jalali
1181    converters.jalali_to_gregorian = jalali_to_gregorian
1182
1183end
1184
1185-- -- more efficient but needs testing
1186
1187-- local escapes = characters.filters.utf.private.escapes
1188
1189-- local function do_alphabetic(n,mapping,chr)
1190--     local max = #mapping
1191--     if n > max then
1192--         do_alphabetic(floor((n-1)/max),mapping,chr)
1193--         n = (n-1)%max+1
1194--     end
1195--     n = chr(n,mapping)
1196--     context(escapes[n] or utfchar(n))
1197-- end
1198
1199-- local lccodes, uccodes, safechar = characters.lccode, characters.uccode, commands.safechar
1200
1201-- local function do_alphabetic(n,mapping,chr)
1202--     local max = #mapping
1203--     if n > max then
1204--         do_alphabetic(floor((n-1)/max),mapping,chr)
1205--         n = (n-1)%max+1
1206--     end
1207--     safechar(chr(n,mapping))
1208-- end
1209
1210-- local function lowercased(n,mapping) return characters.lccode(mapping[n] or fallback) end
1211-- local function uppercased(n,mapping) return characters.uccode(mapping[n] or fallback) end
1212
1213-- function converters.alphabetic(n,code)
1214--     do_alphabetic(n,counters[code] or counters.default,lowercased) -- lccode catches wrong tables
1215-- end
1216
1217-- function converters.Alphabetic(n,code)
1218--     do_alphabetic(n,counters[code] or counters.default,uppercased)
1219-- end
1220
1221local ordinals = {
1222    english = function(n)
1223        local two = n % 100
1224        if two == 11 or two == 12 or two == 13 then
1225            return "th"
1226        else
1227            local one = n % 10
1228            if one == 1 then
1229                return "st"
1230            elseif one == 2 then
1231                return "nd"
1232            elseif one == 3 then
1233                return "rd"
1234            else
1235                return "th"
1236            end
1237        end
1238    end,
1239    dutch = function(n)
1240        return "e"
1241    end,
1242    french = function(n)
1243        if n == 1 then
1244            return "er"
1245        else
1246            return "e"
1247        end
1248    end,
1249}
1250
1251ordinals.en = ordinals.english
1252ordinals.nl = ordinals.dutch
1253ordinals.fr = ordinals.french
1254
1255function converters.ordinal(n,language)
1256    local t = language and ordinals[language]
1257    return t and t(n)
1258end
1259
1260local function ctxordinal(n,language)
1261    local t = language and ordinals[language]
1262    local o = t and t(n)
1263    context(n)
1264    if o then
1265        ctx_highordinalstr(o)
1266    end
1267end
1268
1269implement {
1270    name      = "ordinal",
1271    actions   = ctxordinal,
1272    arguments = { "integer", "string" }
1273}
1274
1275-- verbose numbers
1276
1277local data         = allocate()
1278local verbose      = { data = data }
1279converters.verbose = verbose
1280
1281-- verbose english
1282
1283local words = {
1284               [0] = "zero",
1285               [1] = "one",
1286               [2] = "two",
1287               [3] = "three",
1288               [4] = "four",
1289               [5] = "five",
1290               [6] = "six",
1291               [7] = "seven",
1292               [8] = "eight",
1293               [9] = "nine",
1294              [10] = "ten",
1295              [11] = "eleven",
1296              [12] = "twelve",
1297              [13] = "thirteen",
1298              [14] = "fourteen",
1299              [15] = "fifteen",
1300              [16] = "sixteen",
1301              [17] = "seventeen",
1302              [18] = "eighteen",
1303              [19] = "nineteen",
1304              [20] = "twenty",
1305              [30] = "thirty",
1306              [40] = "forty",
1307              [50] = "fifty",
1308              [60] = "sixty",
1309              [70] = "seventy",
1310              [80] = "eighty",
1311              [90] = "ninety",
1312             [100] = "hundred",
1313            [1000] = "thousand",
1314         [1000000] = "million",
1315      [1000000000] = "billion",
1316   [1000000000000] = "trillion",
1317}
1318
1319local function translate(n,connector)
1320    local w = words[n]
1321    if w then
1322        return w
1323    end
1324    local t = { }
1325    local function compose_one(n)
1326        local w = words[n]
1327        if w then
1328            t[#t+1] = w
1329            return
1330        end
1331        local a, b = floor(n/100), n % 100
1332        if a == 10 then
1333            t[#t+1] = words[1]
1334            t[#t+1] = words[1000]
1335        elseif a > 0 then
1336            t[#t+1] = words[a]
1337            t[#t+1] = words[100]
1338            -- don't say 'nine hundred zero'
1339            if b == 0 then
1340                return
1341            end
1342        end
1343        if words[b] then
1344            t[#t+1] = words[b]
1345        else
1346            a, b = floor(b/10), n % 10
1347            t[#t+1] = words[a*10]
1348            t[#t+1] = words[b]
1349        end
1350    end
1351    local function compose_two(n,m)
1352        if n > (m-1) then
1353            local a, b = floor(n/m), n % m
1354            if a > 0 then
1355                compose_one(a)
1356            end
1357            t[#t+1] = words[m]
1358            n = b
1359        end
1360        return n
1361    end
1362    n = compose_two(n,1000000000000)
1363    n = compose_two(n,1000000000)
1364    n = compose_two(n,1000000)
1365    n = compose_two(n,1000)
1366    if n > 0 then
1367        compose_one(n)
1368    end
1369    return #t > 0 and concat(t,connector or " ") or tostring(n)
1370end
1371
1372data.english = {
1373    words     = words,
1374    translate = translate,
1375}
1376
1377data.en = data.english
1378
1379-- print(translate(11111111))
1380-- print(translate(2221101))
1381-- print(translate(1111))
1382-- print(translate(1218))
1383-- print(translate(1234))
1384-- print(translate(12345))
1385-- print(translate(12345678900000))
1386
1387-- verbose spanish (unchecked)
1388
1389local words = {
1390               [1] = "uno",
1391               [2] = "dos",
1392               [3] = "tres",
1393               [4] = "cuatro",
1394               [5] = "cinco",
1395               [6] = "seis",
1396               [7] = "siete",
1397               [8] = "ocho",
1398               [9] = "nueve",
1399              [10] = "diez",
1400              [11] = "once",
1401              [12] = "doce",
1402              [13] = "trece",
1403              [14] = "catorce",
1404              [15] = "quince",
1405              [16] = "dieciséis",
1406              [17] = "diecisiete",
1407              [18] = "dieciocho",
1408              [19] = "diecinueve",
1409              [20] = "veinte",
1410              [21] = "veintiuno",
1411              [22] = "veintidós",
1412              [23] = "veintitrés",
1413              [24] = "veinticuatro",
1414              [25] = "veinticinco",
1415              [26] = "veintiséis",
1416              [27] = "veintisiete",
1417              [28] = "veintiocho",
1418              [29] = "veintinueve",
1419              [30] = "treinta",
1420              [40] = "cuarenta",
1421              [50] = "cincuenta",
1422              [60] = "sesenta",
1423              [70] = "setenta",
1424              [80] = "ochenta",
1425              [90] = "noventa",
1426             [100] = "ciento",
1427             [200] = "doscientos",
1428             [300] = "trescientos",
1429             [400] = "cuatrocientos",
1430             [500] = "quinientos",
1431             [600] = "seiscientos",
1432             [700] = "setecientos",
1433             [800] = "ochocientos",
1434             [900] = "novecientos",
1435            [1000] = "mil",
1436         [1000000] = "millón",
1437      [1000000000] = "mil millones",
1438   [1000000000000] = "billón",
1439}
1440
1441local function translate(n,connector)
1442    local w = words[n]
1443    if w then
1444        return w
1445    end
1446    local t = { }
1447    local function compose_one(n)
1448        local w = words[n]
1449        if w then
1450            t[#t+1] = w
1451            return
1452        end
1453        -- a, b = hundreds, remainder
1454        local a, b = floor(n/100), n % 100
1455        -- one thousand
1456        if a == 10 then
1457            t[#t+1] = words[1]
1458            t[#t+1] = words[1000]
1459        -- x hundred (n.b. this will not give thirteen hundred because
1460        -- compose_one(n) is only called after
1461        -- n = compose(two(n, 1000))
1462        elseif a > 0 then
1463            t[#t+1] = words[a*100]
1464        end
1465        -- the remainder
1466        if words[b] then
1467            t[#t+1] = words[b]
1468        else
1469            -- a, b = tens, remainder
1470            a, b = floor(b/10), n % 10
1471            t[#t+1] = words[a*10]
1472            t[#t+1] = "y"
1473            t[#t+1] = words[b]
1474        end
1475    end
1476    -- compose_two handles x billion, ... x thousand. When 1000 or less is
1477    -- left, compose_one takes over.
1478    local function compose_two(n,m)
1479        if n > (m-1) then
1480            local a, b = floor(n/m), n % m
1481            if a > 0 then
1482                compose_one(a)
1483            end
1484            t[#t+1] = words[m]
1485            n = b
1486        end
1487        return n
1488    end
1489    n = compose_two(n,1000000000000)
1490    n = compose_two(n,1000000000)
1491    n = compose_two(n,1000000)
1492    n = compose_two(n,1000)
1493    if n > 0 then
1494        compose_one(n)
1495    end
1496    return #t > 0 and concat(t,connector or " ") or tostring(n)
1497end
1498
1499data.spanish = {
1500    words     = words,
1501    translate = translate,
1502}
1503
1504data.es = data.spanish
1505
1506-- print(translate(31))
1507-- print(translate(101))
1508-- print(translate(199))
1509
1510-- verbose swedish by Peter Kvillegard
1511
1512do
1513
1514    local words = {
1515            [0] = "noll",
1516            [1] = "ett",
1517            [2] = "två",
1518            [3] = "tre",
1519            [4] = "fyra",
1520            [5] = "fem",
1521            [6] = "sex",
1522            [7] = "sju",
1523            [8] = "åtta",
1524            [9] = "nio",
1525           [10] = "tio",
1526           [11] = "elva",
1527           [12] = "tolv",
1528           [13] = "tretton",
1529           [14] = "fjorton",
1530           [15] = "femton",
1531           [16] = "sexton",
1532           [17] = "sjutton",
1533           [18] = "arton",
1534           [19] = "nitton",
1535           [20] = "tjugo",
1536           [30] = "trettio",
1537           [40] = "fyrtio",
1538           [50] = "femtio",
1539           [60] = "sextio",
1540           [70] = "sjuttio",
1541           [80] = "åttio",
1542           [90] = "nittio",
1543          [100] = "hundra",
1544         [10^3] = "tusen",
1545         [10^6] = "miljon",
1546         [10^9] = "miljard",
1547        [10^12] = "biljon",
1548        [10^15] = "biljard",
1549    }
1550
1551    local function translate(n,connector)
1552        local w = words[n]
1553        if w then
1554            return w
1555        else
1556            local t = { }
1557            local l = 0
1558            -- group of three digits to words, e.g. 123 -> etthundratjugotre
1559            local function triplets(n)
1560                if floor(n/100) > 0 then
1561                    l = l + 1 ; t[l] = words[floor(n/100)]
1562                    l = l + 1 ; t[l] = words[100]
1563                end
1564                if n%100 > 20 then
1565                    l = l + 1 ; t[l] = words[n%100-n%10]
1566                    if n%10 > 0 then
1567                        l = l + 1 ; t[l] = words[n%10]
1568                    end
1569                elseif n%100 > 0 then
1570                    l = l + 1 ; t[l] = words[n%100]
1571                end
1572            end
1573            -- loops through 10^15,10^12,...10^3, extracting groups of three digits
1574            -- to make words from, then adding names for order of magnitude
1575            for i=15,3,-3 do
1576                local triplet = floor(n/10^i)%10^3
1577                if triplet > 0 then
1578                    -- grammar: "en" instead of "ett"
1579                    if i > 3 and triplet == 1 then
1580                        l = l + 1 ; t[l] = "en"
1581                    else
1582                        triplets(triplet)
1583                    end
1584                    -- grammar: plural form of "millions" etc
1585                    l = l + 1 ; t[l] = words[10^i]
1586                    if i > 3 and triplet > 1 then
1587                        l = l + 1 ; t[l] = "er"
1588                    end
1589                end
1590            end
1591            -- add last group of three numbers (no word for magnitude)
1592            n = n%1000
1593            if n > 0 then
1594                triplets(n)
1595            end
1596            t = concat(t," ")
1597            -- grammar: spacing for numbers < 10^6 and repeated letters
1598            if n < 10^6 then
1599                t = gsub(t,"%stusen%s","tusen")
1600                t = gsub(t,"etttusen","ettusen")
1601            end
1602            return t
1603        end
1604    end
1605
1606    data.swedish = {
1607        words     = words,
1608        translate = translate,
1609    }
1610
1611    data.sv = data.swedish
1612
1613end
1614
1615-- verbose handler:
1616
1617function converters.verbose.translate(n,language,connector)
1618    local t = language and data[language]
1619    return t and t.translate(n,connector) or n
1620end
1621
1622local function verbose(n,language,connector)
1623    local t = language and data[language]
1624    context(t and t.translate(n,connector) or n)
1625end
1626
1627implement {
1628    name      = "verbose",
1629    actions   = verbose,
1630    arguments = { "integer", "string", "string" }
1631}
1632
1633-- These are just helpers but not really for the tex end. Do we have to
1634-- use translate here?
1635
1636local whitespace  = lpegpatterns.whitespace
1637local word        = lpegpatterns.utf8uppercharacter^-1 * (1-whitespace)^1
1638local pattern_one = Cs( whitespace^0 * word^-1 * P(1)^0)
1639local pattern_all = Cs((whitespace^1 + word)^1)
1640
1641function converters.word (s) return s end -- dummies for typos
1642function converters.words(s) return s end -- dummies for typos
1643
1644local function Word (s) return lpegmatch(pattern_one,s) or s end
1645local function Words(s) return lpegmatch(pattern_all,s) or s end
1646
1647converters.Word  = Word
1648converters.Words = Words
1649
1650converters.upper = characters.upper
1651converters.lower = characters.lower
1652
1653-- print(converters.Word("foo bar"))
1654-- print(converters.Word(" foo bar"))
1655-- print(converters.Word("123 foo bar"))
1656-- print(converters.Word(" 123 foo bar"))
1657
1658-- print(converters.Words("foo bar"))
1659-- print(converters.Words(" foo bar"))
1660-- print(converters.Words("123 foo bar"))
1661-- print(converters.Words(" 123 foo bar"))
1662
1663-- --
1664
1665local v_day      <const> = variables.day
1666local v_year     <const> = variables.year
1667local v_month    <const> = variables.month
1668local v_weekday  <const> = variables.weekday
1669local v_referral <const> = variables.referral
1670local v_space    <const> = variables.space
1671
1672local v_MONTH    = upper(v_month)
1673local v_WEEKDAY  = upper(v_weekday)
1674
1675local convert = converters.convert
1676
1677local days = { -- not variables
1678    "sunday",
1679    "monday",
1680    "tuesday",
1681    "wednesday",
1682    "thursday",
1683    "friday",
1684    "saturday",
1685}
1686
1687local months = { -- not variables
1688    "january",
1689    "february",
1690    "march",
1691    "april",
1692    "may",
1693    "june",
1694    "july",
1695    "august",
1696    "september",
1697    "october",
1698    "november",
1699    "december",
1700}
1701
1702local monthmnems = { -- not variables
1703    -- virtual table
1704}
1705
1706local daymnems = { -- not variables
1707    -- virtual table
1708}
1709
1710setmetatableindex(days,       function(t,k) return "unknown" end)
1711setmetatableindex(daymnems,   function(t,k) return days[k] .. ":mnem" end)
1712setmetatableindex(months,     function(t,k) return "unknown" end)
1713setmetatableindex(monthmnems, function(t,k) return months[k] .. ":mnem" end)
1714
1715do
1716
1717    local function dayname(n)
1718        ctx_labeltext(days[n])
1719    end
1720
1721    local function daymnem(n)
1722        ctx_labeltext(daymnems[n])
1723    end
1724
1725    local function weekdayname(day,month,year)
1726        ctx_labeltext(days[weekday(day,month,year)])
1727    end
1728
1729    local function monthname(n)
1730        ctx_labeltext(months[n])
1731    end
1732
1733    local function monthmnem(n)
1734        ctx_labeltext(monthmnems[n])
1735    end
1736
1737    implement {
1738        name      = "dayname",
1739        actions   = dayname,
1740        arguments = "integer",
1741    }
1742
1743    implement {
1744        name      = "daymnem",
1745        actions   = daymnem,
1746        arguments = "integer",
1747    }
1748
1749    implement {
1750        name      = "weekdayname",
1751        actions   = weekdayname,
1752        arguments = { "integer", "integer", "integer" }
1753    }
1754
1755    implement {
1756        name      = "monthname",
1757        actions   = monthname,
1758        arguments = "integer",
1759    }
1760
1761    implement {
1762        name      = "monthmnem",
1763        actions   = monthmnem,
1764        arguments = "integer",
1765    }
1766
1767    -- todo : short week days
1768
1769    local f_monthlong    = formatters["\\monthlong{%s}"]
1770    local f_monthshort   = formatters["\\monthshort{%s}"]
1771    local f_daylong      = formatters["\\daylong{%s}"]
1772    local f_dayshort     = formatters["\\dayshort{%s}"]
1773    local f_weekday      = formatters["\\weekday{%s}"]
1774    local f_dayoftheweek = formatters["\\dayoftheweek{%s}{%s}{%s}"]
1775
1776    local function tomonthlong (m) return f_monthlong (tonumber(m) or 1) end
1777    local function tomonthshort(m) return f_monthshort(tonumber(m) or 1) end
1778    local function todaylong   (d) return f_daylong   (tonumber(d) or 1) end
1779    local function todayshort  (d) return f_dayshort  (tonumber(d) or 1) end
1780    local function toweekday   (d) return f_weekday   (tonumber(d) or 1) end
1781
1782    local function todayoftheweek(d,m,y)
1783        return f_dayoftheweek(tonumber(d) or 1,tonumber(m) or 1,tonumber(y) or 2000)
1784    end
1785
1786    addformatter(formatters,"monthlong",   [[tomonthlong(%s)]],         { tomonthlong    = tomonthlong    })
1787    addformatter(formatters,"monthshort",  [[tomonthshort(%s)]],        { tomonthshort   = tomonthshort   })
1788    addformatter(formatters,"daylong",     [[todaylong(%s)]],           { todaylong      = todaylong      })
1789    addformatter(formatters,"dayshort",    [[todayshort(%s)]],          { todayshort     = todayshort     })
1790    addformatter(formatters,"weekday",     [[toweekday(%s)]],           { toweekday      = toweekday      })
1791    addformatter(formatters,"dayoftheweek",[[todayoftheweek(%s,%s,%s)]],{ todayoftheweek = todayoftheweek })
1792
1793    -- using %t is slower, even with caching as we seldom use > 3 items per epoch
1794
1795    local function toeyear  (e) return osdate("%Y",tonumber(e))  end
1796    local function toemonth (e) return osdate("%m",tonumber(e))  end
1797    local function toeday   (e) return osdate("%d",tonumber(e))  end
1798    local function toeminute(e) return osdate("%M",tonumber(e))  end
1799    local function toesecond(e) return osdate("%S",tonumber(e))  end
1800
1801    local function toemonthlong(e)
1802        return f_monthlong(tonumber(osdate("%m",tonumber(e))))
1803    end
1804
1805    local function toemonthshort(e)
1806        return f_monthshort(tonumber(osdate("%m",tonumber(e))))
1807    end
1808
1809    local function toedaylong(e)
1810        return f_datlong(tonumber(osdate("%w",tonumber(e))))
1811    end
1812
1813    local function toedayshort(e)
1814        return f_dayshort(tonumber(osdate("%w",tonumber(e))))
1815    end
1816
1817    local function toeweek(e) -- we run from 1-7 not 0-6
1818        return tostring(tonumber(osdate("%w",tonumber(e)))+1)
1819    end
1820
1821    local function toeweekday(e)
1822        return f_weekday(tonumber(osdate("%w",tonumber(e)))+1)
1823    end
1824
1825    local function toedate(format,e)
1826        return osdate(format,tonumber(e))
1827    end
1828
1829    addformatter(formatters,"eyear",        [[toeyear(%s)]],        { toeyear         = toeyear       })
1830    addformatter(formatters,"emonth",       [[toemonth(%s)]],       { toemonth        = toemonth      })
1831    addformatter(formatters,"eday",         [[toeday(%s)]],         { toeday          = toeday        })
1832    addformatter(formatters,"eweek",        [[toeweek(%s)]],        { toeweek         = toeweek       })
1833    addformatter(formatters,"eminute",      [[toeminute(%s)]],      { toeminute       = toeminute     })
1834    addformatter(formatters,"esecond",      [[toesecond(%s)]],      { toesecond       = toesecond     })
1835
1836    addformatter(formatters,"emonthlong",   [[toemonthlong(%s)]],   { toemonthlong    = toemonthlong  })
1837    addformatter(formatters,"emonthshort",  [[toemonthshort(%s)]],  { toemonthshort   = toemonthshort })
1838    addformatter(formatters,"edaylong",     [[toedaylong(%s)]],     { toedaylong      = toedaylong    })
1839    addformatter(formatters,"edayshort",    [[toedayshort(%s)]],    { toedayshort     = toedayshort   })
1840    addformatter(formatters,"eweekday",     [[toeweekday(%s)]],     { toeweekday      = toeweekday    })
1841
1842    addformatter(formatters,"edate",        [[toedate(%s,%s)]],     { toedate         = toedate       })
1843
1844end
1845
1846-- a prelude to a function that we can use at the lua end
1847
1848-- day:ord month:mmem
1849-- j and jj obsolete
1850
1851local spaced = {
1852    [v_year]    = true,
1853    [v_month]   = true,
1854    [v_MONTH]   = true,
1855    [v_day]     = true,
1856    [v_weekday] = true,
1857    [v_WEEKDAY] = true,
1858}
1859
1860local dateconverters = {
1861    ["hebrew:to"]   = gregorian_to_hebrew,
1862    ["jalali:to"]   = gregorian_to_jalali,
1863    ["jalali:from"] = jalali_to_gregorian,
1864}
1865
1866local variants = {
1867    mnem   = {
1868        month = monthmnems,
1869        day   = daymnems,
1870    },
1871    hebrew = {
1872        month = setmetatableindex(function(t,k) return months[k] .. ":hebrew" end),
1873        day   = setmetatableindex(function(t,k) return days  [k] .. ":hebrew" end),
1874    },
1875    jalali = {
1876        month = setmetatableindex(function(t,k) return months[k] .. ":jalali" end),
1877        day   = setmetatableindex(function(t,k) return days  [k] .. ":jalali" end),
1878    },
1879}
1880
1881local packed = {
1882    ["cn"] = true,
1883    ["jp"] = true,
1884}
1885
1886do
1887
1888    local function currentdate(str,currentlanguage,year,month,day) -- second argument false : no label
1889        local list       = utilities.parsers.settings_to_array(str)
1890        local splitlabel = languages.labels.split or string.itself -- we need to get the loading order right
1891     -- local year       = tex.year
1892     -- local month      = tex.month
1893     -- local day        = tex.day
1894        local auto       = true
1895        if currentlanguage == "" then
1896            currentlanguage = false
1897        end
1898        for i=1,#list do
1899            local entry = list[i]
1900            local convert = dateconverters[entry]
1901            if convert then
1902                year, month, day = convert(year,month,day)
1903            else
1904                local tag, plus = splitlabel(entry)
1905                local ordinal, mnemonic, whatordinal, highordinal = false, false, nil, false
1906                if not tag then
1907                    tag = entry
1908                elseif plus == "+" or plus == "ord" then
1909                    ordinal = true
1910                elseif plus == "++" or plus == "highord" then
1911                    ordinal = true
1912                    highordinal = true
1913                elseif plus then -- mnem MNEM etc
1914                    mnemonic = variants[plus]
1915                end
1916                if not auto and not packed[currentlanguage] and spaced[tag] then
1917                    ctx_space()
1918                end
1919                auto = false
1920                if tag == v_year or tag == "y" or tag == "Y" then
1921                    if plus then
1922                        plus = converters[plus]
1923                    end
1924                    if plus then
1925                        context(plus(year))
1926                    elseif currentlanguage == false then
1927                        context(year)
1928                    else
1929                        ctx_convertnumber(v_year,year)
1930                    end
1931                elseif tag == "yy" or tag == "YY" then
1932                    context("%02i",year % 100)
1933                elseif tag == v_month or tag == "m" then
1934                    if currentlanguage == false then
1935                        context(Word(months[month]))
1936                    else
1937                        if type(mnemonic) == "table" then
1938                            mnemonic = mnemonic.month
1939                        end
1940                        if mnemonic then
1941                            ctx_labeltext(variables[mnemonic[month]])
1942                        else
1943                            ctx_labeltext(variables[months[month]])
1944                        end
1945                    end
1946                elseif tag == v_MONTH then
1947                    if currentlanguage == false then
1948                        context(Word(variables[months[month]]))
1949                    else
1950                        if type(mnemonic) == "table" then
1951                            mnemonic = mnemonic.month
1952                        end
1953                        if mnemonic then
1954                            ctx_LABELTEXT(variables[mnemonic[month]])
1955                        else
1956                            ctx_LABELTEXT(variables[months[month]])
1957                        end
1958                    end
1959                elseif tag == "mm" then
1960                    context("%02i",month)
1961                elseif tag == "M" then
1962                    context(month)
1963                elseif tag == v_day or tag == "d" then
1964                    if plus then
1965                        plus = converters[plus]
1966                    end
1967                    if plus then
1968                        context(plus(day))
1969                    elseif currentlanguage == false then
1970                        context(day)
1971                    else
1972                        ctx_convertnumber(v_day,day)
1973                    end
1974                    whatordinal = day
1975                elseif tag == "dd" then
1976                    context("%02i",day)
1977                    whatordinal = day
1978                elseif tag == "D" then
1979                    context(day)
1980                    whatordinal = day
1981                elseif tag == v_weekday or tag == "w" then
1982                    local wd = weekday(day,month,year)
1983                    if currentlanguage == false then
1984                        context(Word(days[wd]))
1985                    else
1986                        if type(mnemonic) == "table" then
1987                            mnemonic = mnemonic.day
1988                        end
1989                        if mnemonic then
1990                            ctx_labeltext(variables[mnemonic[wd]])
1991                        else
1992                            ctx_labeltext(variables[days[wd]])
1993                        end
1994                    end
1995                elseif tag == v_WEEKDAY then
1996                    local wd = weekday(day,month,year)
1997                    if currentlanguage == false then
1998                        context(Word(days[wd]))
1999                    else
2000                        if type(mnemonic) == "table" then
2001                            mnemonic = mnemonic.day
2002                        end
2003                        if mnemonic then
2004                            ctx_LABELTEXT(variables[mnemonic[wd]])
2005                        else
2006                            ctx_LABELTEXT(variables[days[wd]])
2007                        end
2008                    end
2009                elseif tag == "W" then
2010                    context(weekday(day,month,year))
2011                elseif tag == v_referral then
2012                    context("%04i%02i%02i",year,month,day)
2013                elseif tag == v_space or tag == "\\ " then
2014                    ctx_space()
2015                    auto = true
2016                elseif tag ~= "" then
2017                    context(tag)
2018                    auto = true
2019                end
2020                if ordinal and whatordinal then
2021                    if currentlanguage == false then
2022                        -- ignore
2023                    else
2024                        context[highordinal and "highordinalstr" or "ordinalstr"](converters.ordinal(whatordinal,currentlanguage))
2025                    end
2026                end
2027            end
2028        end
2029    end
2030
2031    implement {
2032        name      = "currentdate",
2033        arguments = { "string", "string", "string", "integer", "integer", "integer" },
2034        actions   = function(pattern,default,language,year,month,day)
2035            currentdate(
2036                pattern  == "" and default or pattern,
2037                language == "" and false   or language,
2038                year, month, day
2039            )
2040        end,
2041    }
2042
2043    local function todate(s,y,m,d)
2044        if y or m or d then
2045            return formatters["\\date[y=%s,m=%s,d=%s][%s]\\relax"](y or "",m or "",d or "",s or "")
2046        else
2047            return formatters["\\currentdate[%s]\\relax"](s)
2048        end
2049    end
2050
2051    addformatter(formatters,"date", [[todate(...)]], { todate = todate })
2052
2053    -- context("one: %4!date!","MONTH",2020,12,11)          context.par()
2054    -- context("one: %4!date!","month",2020,12,11)          context.par()
2055    -- context("one: %4!date!","year,-,mm,-,dd",2020,12,11) context.par()
2056
2057    -- context("two: %3!date!","MONTH",false,12)          context.par()
2058    -- context("two: %3!date!","month",false,12)          context.par()
2059    -- context("two: %3!date!","year,-,mm,-,dd",false,12) context.par()
2060
2061end
2062
2063implement {
2064    name      = "unihex",
2065    arguments = "integer",
2066    actions   = { formatters["U+%05X"], context },
2067}
2068
2069-- totime might move to utilities.parsers as more general helper
2070
2071local n = R("09")^1 / tonumber -- lpegpatterns.digit
2072
2073-- local p = Cf( Ct("")
2074--     -- year is mandate, month and day are optional
2075--     * Cg(Cc("year") * n)
2076--     * S("-/")^-1
2077--     * Cg(Cc("month") * (n + Cc(1)))
2078--     * S("-/")^-1
2079--     * Cg(Cc("day") * (n + Cc(1)))
2080--     -- time is optional, hour and minuta are mandate, seconds are optional
2081--     * (
2082--           whitespace^0
2083--         * P("T")^-1
2084--         * whitespace^0
2085--         * Cg(Cc("hour") * n)
2086--         * P(":")^-1
2087--         * Cg(Cc("min") * n)
2088--         * P(":")^-1
2089--         * Cg(Cc("sec") * (n + Cc(0)))
2090--     )^-1
2091--     -- zone is optional, hour is mandate, minutes are optional
2092--     * (
2093--           whitespace^0
2094--         * Cg(Cc("tzs") * (P("+") * Cc(1) + P("-") * Cc(-1) + Cc(1)))
2095--         * whitespace^0
2096--         * Cg(Cc("tzh") * n)
2097--         * P(":")^-1
2098--         * Cg(Cc("tzm") * (n + Cc(0)))
2099--     )^-1
2100--     , rawset)
2101
2102local p = Ct("") * (
2103    -- year is mandate, month and day are optional
2104      (Cc("year") * n) % rawset
2105    * S("-/")^-1
2106    * (Cc("month") * (n + Cc(1))) % rawset
2107    * S("-/")^-1
2108    * (Cc("day") * (n + Cc(1))) % rawset
2109    -- time is optional, hour and minuta are mandate, seconds are optional
2110    * (
2111          whitespace^0
2112        * P("T")^-1
2113        * whitespace^0
2114        * (Cc("hour") * n) % rawset
2115        * P(":")^-1
2116        * (Cc("min") * n) % rawset
2117        * P(":")^-1
2118        * (Cc("sec") * (n + Cc(0))) % rawset
2119    )^-1
2120    -- zone is optional, hour is mandate, minutes are optional
2121    * (
2122          whitespace^0
2123        * (Cc("tzs") * (P("+") * Cc(1) + P("-") * Cc(-1) + Cc(1))) % rawset
2124        * whitespace^0
2125        * (Cc("tzh") * n) % rawset
2126        * P(":")^-1
2127        * (Cc("tzm") * (n + Cc(0))) % rawset
2128    )^-1
2129)
2130
2131function converters.totime(s)
2132    if not s then
2133        return
2134    elseif type(s) == "table" then
2135        return s
2136    elseif type(s) == "string" then
2137        local t = lpegmatch(p,s)
2138        if not t then
2139            logs.report("system","invalid time specification %a",s)
2140        elseif t.tzh then
2141            local localtzh, localtzm = ostimezone(true)
2142            t.hour = t.hour + localtzh - t.tzs * t.tzh
2143            t.min  = t.min  + localtzm - t.tzs * t.tzm
2144        end
2145        return t
2146    end
2147    local n = tonumber(s)
2148    if n and n >= 0 then
2149        return osdate("*t",n)
2150    end
2151end
2152
2153function converters.settime(t)
2154    if type(t) ~= "table" then
2155        t = converters.totime(t)
2156    end
2157    if t then
2158        texset("year", t.year or 1000)
2159        texset("month", t.month or 1)
2160        texset("day", t.day or 1)
2161        texset("time", (t.hour or 0) * 60 + (t.min or 0))
2162    end
2163end
2164
2165-- taken from x-asciimath (where we needed it for a project)
2166
2167local d_one         = lpegpatterns.digit
2168local d_two         = d_one * d_one
2169local d_three       = d_two * d_one
2170local d_four        = d_three * d_one
2171local d_split       = P(-1) + Carg(2) * (lpegpatterns.period /"")
2172
2173local d_spaced      = (Carg(1) * d_three)^1
2174
2175local digitized_1   = Cs ( (
2176                        d_three * d_spaced * d_split +
2177                        d_two   * d_spaced * d_split +
2178                        d_one   * d_spaced * d_split +
2179                        P(1)
2180                      )^1 )
2181
2182local p_fourbefore  = d_four * d_split
2183local p_fourafter   = d_four * P(-1)
2184
2185local p_beforesplit = d_three * d_spaced^0 * d_split
2186                    + d_two   * d_spaced^0 * d_split
2187                    + d_one   * d_spaced^0 * d_split
2188                    + d_one   * d_split
2189
2190local p_aftersplit  = p_fourafter
2191                    + d_three * d_spaced
2192                    + d_two   * d_spaced
2193                    + d_one   * d_spaced
2194
2195local digitized_2   = Cs (
2196                         p_fourbefore  *  (p_aftersplit^0) +
2197                         p_beforesplit * ((p_aftersplit + d_one^1)^0)
2198                      )
2199
2200local p_fourbefore  = d_four * d_split
2201local p_fourafter   = d_four
2202local d_spaced      = (Carg(1) * (d_three + d_two + d_one))^1
2203local p_aftersplit  = p_fourafter * P(-1)
2204                    + d_three * d_spaced * P(1)^0
2205                    + d_one^1
2206
2207local digitized_3   = Cs((p_fourbefore + p_beforesplit) * p_aftersplit^0)
2208
2209local digits_space  = utfchar(0x2008)
2210
2211local splitmethods  = {
2212    digitized_1,
2213    digitized_2,
2214    digitized_3,
2215}
2216
2217local replacers     = table.setmetatableindex(function(t,k)
2218    local v = lpeg.replacer(".",k)
2219    t[k] = v
2220    return v
2221end)
2222
2223function converters.spaceddigits(settings,data)
2224    local data = tostring(data or settings.data or "")
2225    if data ~= "" then
2226        local method = settings.method
2227        local split  = splitmethods[tonumber(method) or 1]
2228        if split then
2229            local symbol    = settings.symbol
2230            local separator = settings.separator
2231            if not symbol or symbol == "" then
2232                symbol = "."
2233            end
2234            if type(separator) ~= "string" or separator == "" then
2235                separator = digits_space
2236            end
2237            local result = lpegmatch(split,data,1,separator,symbol)
2238            if not result and symbol ~= "." then
2239                result = lpegmatch(replacers[symbol],data)
2240            end
2241            if result then
2242             -- print(method,symbol,separator,data,result)
2243                return result
2244            end
2245        end
2246    end
2247    return str
2248end
2249
2250-- method 2 : split 3 before and 3 after
2251-- method 3 : split 3 before and 3 after with > 4 before
2252
2253-- symbols is extra split (in addition to period)
2254
2255-- local setup = { splitmethod = 3, symbol = "," }
2256-- local setup = { splitmethod = 2, symbol = "," }
2257-- local setup = { splitmethod = 1, symbol = "," }
2258--
2259-- local t = {
2260--     "0.00002",
2261--     "1", "12", "123", "1234", "12345", "123456", "1234567", "12345678", "123456789",
2262--     "1.1",
2263--     "12.12",
2264--     "123.123",
2265--     "1234.123",
2266--     "1234.1234",
2267--     "12345.1234",
2268--     "1234.12345",
2269--     "12345.12345",
2270--     "123456.123456",
2271--     "1234567.1234567",
2272--     "12345678.12345678",
2273--     "123456789.123456789",
2274--     "0.1234",
2275--     "1234.0",
2276--     "1234.00",
2277--     "0.123456789",
2278--     "100.00005",
2279--     "0.80018",
2280--     "10.80018",
2281--     "100.80018",
2282--     "1000.80018",
2283--     "10000.80018",
2284-- }
2285--
2286-- for i=1,#t do
2287--     print(formatters["%-20s : [%s]"](t[i],converters.spaceddigits(setup,t[i])))
2288-- end
2289
2290implement {
2291    name      = "spaceddigits",
2292    actions   = { converters.spaceddigits, context },
2293    arguments = {
2294        {
2295            { "symbol" },
2296            { "separator" },
2297            { "data" },
2298            { "method" },
2299        }
2300    }
2301}
2302
2303local function field(n) return context(osdate("*t")[n]) end
2304
2305implement { name = "actualday",   public = true, actions = function() field("day")   end }
2306implement { name = "actualmonth", public = true, actions = function() field("month") end }
2307implement { name = "actualyear",  public = true, actions = function() field("year")  end }
2308
2309implement {
2310    name    = "uuid",
2311    public  = true,
2312    actions = { os.uuid, context },
2313}
2314