Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 

















Module:Val: Difference between revisions






Аԥсшәа
العربية

Azərbaycanca
تۆرکجه
Basa Bali

Беларуская (тарашкевіца)

Буряад
Català
Чӑвашла
Čeština
الدارجة
Euskara
فارسی
Gaeilge
Galego

Hausa
Հայերեն
ि
Ilokano
Bahasa Indonesia
Italiano
Jawa

Lietuvių
Ligure
ि
Македонски


 / Mìng-dĕ̤ng-nḡ
Мокшень

Na Vosa Vakaviti


Oʻzbekcha / ўзбекча

Português
Română
Scots
Shqip
Simple English
Slovenščina
کوردی
Српски / srpski
Suomi
Svenska
ி
Taqbaylit
 
Tetun

Türkçe
Українська
اردو
Vèneto
Tiếng Vit


 

Edit links
 









Module
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
Wikidata item
 




Print/export  



Download as PDF
Printable version
 
















Appearance
   

 





Help

Permanently protected module

From Wikipedia, the free encyclopedia
 


Browse history interactively
 Previous edit
Content deleted Content added
No edit summary
use i18n from Module:Val/sandbox added by User:Moroboshi
 
(44 intermediate revisions by 6 users not shown)
Line 1: Line 1:

-- For Template:Val, output a number and optional unit.

local p = {}

-- Format options include scientific and uncertainty notations.

local getArgs

local delimit_groups = require('Module:Gapnum').groups

local makeunit = require('Module:Val/units')

local mSu = require('Module:Su')._main



local numdot = '.' -- decimal mark (use ',' for Italian)

function p.main(frame)

local numsep = ',' -- group separator (use ' ' for Italian)

if not getArgs then

local mtext = {

getArgs = require('Module:Arguments').getArgs

-- Message and other text that should be localized.

['mt-bad-exponent'] = 'exponent parameter (<b>e</b>)',

['mt-parameter'] = 'parameter ',

['mt-not-number'] = 'is not a valid number',

['mt-cannot-range'] = 'cannot use a range if the first parameter includes "e"',

['mt-need-range'] = 'needs a range in parameter 2',

['mt-should-range'] = 'should be a range',

['mt-cannot-with-e'] = 'cannot be used if the first parameter includes "e"',

['mt-not-range'] = 'does not accept a range',

['mt-cannot-e'] = 'cannot use e notation',

['mt-too-many-parameter'] = 'too many parameters',

['mt-need-number'] = 'need a number after the last parameter because it is a range.',

['mt-ignore-parameter4'] = 'Val parameter 4 ignored',

['mt-val-not-supported'] = 'Val parameter "%s=%s" is not supported',

['mt-invalid-scale'] = 'Unit "%s" has invalid scale "%s"',

['mt-both-u-ul'] = 'unit (<b>u</b>) and unit with link (<b>ul</b>) are both specified, only one is allowed.',

['mt-both-up-upl'] = 'unit per (<b>up</b>) and unit per with link (<b>upl</b>) are both specified, only one is allowed.',

}


local data_module = 'Module:Val/units'

local convert_module = 'Module:Convert'


local function valerror(msg, nocat, iswarning)

-- Return formatted message text for an error or warning.

-- Can append "#FormattingError" to URL of a page with a problem to find it.

local anchor = '<span id="FormattingError"></span>'

local body, category

if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then

-- No category in Talk, User, User_talk, or Wikipedia_talk.

category = ''

else

category = '[[Category:Pages with incorrect formatting templates use]]'

end

iswarning = false -- problems are infrequent so try showing large error so editor will notice

if iswarning then

body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..

'[[Template:Val|<span title="' ..

msg:gsub('"', '&quot;') ..

'">warning</span>]]</sup>'

else

body = '<strong class="error">' ..

'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..

msg ..

'</strong>'

end

end

return anchor .. body .. category

local args = getArgs(frame, {wrappers = 'Template:Val'})

local number = {n=args[1], nend=args['end']}

local uncertainty = {upper=args[2], lower=args[3],

errend=args.errend,

upperend=args['+errend'], lowerend=args['-errend']}

local u_tbl = {u=args.ul or args.u, ul=args.ul ~= nil,

p=args.upl or args.up, pl=args.upl ~= nil}

local misc_tbl = {e=args.e, pre=args.p, suf=args.s, fmt=args.fmt or '', nocat=args.nocategory}

return p._main(number,uncertainty,u_tbl,misc_tbl)

end

end



local range_types = {

function p._main(number,uncertainty,u_tbl,misc_tbl)

-- No need for '&nbsp;' because nowrap applies to all output.

local fmt = misc_tbl.fmt

[","] = ", ",

local n = delimit(number.n,fmt)

["by"] = " by ",

local e_10 = misc_tbl.e

["-"] = "–",

["–"] = "–",

local unc

["and"] = " and ",

local uncU, uncL = uncertainty.upper, uncertainty.lower

["or"] = " or " ,

if number.nend then

["to"] = " to " ,

n:wikitext(number.nend)

["x"] = " × ",

end

["×"] = " × ",

["/"] = "/",

}

local range_repeat_unit = {

-- WP:UNIT wants unit repeated when a "multiply" range is used.

["x"] = true,

["×"] = true,

}



local function extract_item(index, numbers, arg)

local paren_wrap = misc_tbl.e and (not uncL and (uncU and not uncU:find('%(')))

-- Extract an item from arg and store the result in numbers[index].

-- If no argument or if argument is valid, return nil (no error);

if u_tbl.u then

-- otherwise, return an error message.

units = makeunit(u_tbl.u,

-- The stored result is:

{link=u_tbl.ul,

-- * a table for a number (empty if there was no specified number); or

per=u_tbl.p,

-- * a string for range text

per_link=u_tbl.pl})

-- Input like 1e3 is regarded as invalid for all except argument 1

-- which accepts e notation as an alternative to the 'e' argument.

-- Input group separators are removed.

local which = index

local function fail(msg)

local description

if which == 'e' then

description = mtext['mt-bad-exponent']

else

description = mtext['mt-parameter'] .. which

end

return description .. ' ' .. (msg or mtext['mt-not-number']) .. '.'

end

end

local paren_uncertainty

local result = {}

local range = range_types[arg]

if uncU then

ifuncL then

ifrange then

if type(index) == 'number' and (index % 2 == 0) then

uncU = delimit(uncU,fmt)

if index == 2 then

uncL = delimit(uncL,fmt)

ifnot e_10 and units then

ifnumbers[1] and numbers[1].exp then

return fail(mtext['mt-cannot-range'])

uncU = uncU..units

end

uncL = uncL..units

numbers.has_ranges = true

else

if not numbers.has_ranges then

return fail(mtext['mt-need-range'])

end

end

end

numbers[index] = range

uncU = uncU..(uncertainty.upperend or '')

if range_repeat_unit[arg] then

uncL = uncL..(uncertainty.lowerend or '')

-- Any "repeat" range forces unit (if any) to be repeated for all items.

unc = '<span style="margin-left:0.3em;">'..mSu(uncU,uncL)..'</span>'

numbers.isrepeat = true

end

return nil

end

return fail(mtext['mt-not-range'])

end

if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then

return fail(mtext['mt-should-range'])

end

if index == 'e' then

local e = numbers[1] and numbers[1].exp

if e then

if arg then

return fail(mtext['mt-cannot-with-e'])

end

arg = e

which = 1

end

end

if arg and arg ~= '' then

arg = arg:gsub(numsep, '')

if numdot ~= '.' then

arg = arg:gsub(numdot, '.')

end

if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then

result.parens = true

arg = arg:sub(2, -2)

end

local a, b = arg:match('^(.+)[Ee](.+)$')

if a then

if index == 1 then

arg = a

result.exp = b

else

return fail(mtext['mt-cannot-e'])

end

end

local isnegative, propersign, prefix

local minus = '−'

prefix, arg = arg:match('^(.-)([%d.]+)$')

local value = tonumber(arg)

if not value then

return fail()

end

if arg:sub(1, 1) == '.' then

arg = '0' .. arg

end

if prefix == '' then

-- Ignore.

elseif prefix == '±' then

-- Display for first number, ignore for others.

if index == 1 then

propersign = '±'

end

elseif prefix == '+' then

propersign = '+'

elseif prefix == '-' or prefix == minus then

propersign = minus

isnegative = true

else

else

return fail()

local uncU_n = mw.ustring.match(uncU,('%((.+)%)')) or uncU

end

if uncU == uncU_n then

result.clean = arg

unc = '<span style="margin-left:0.3em;margin-right:0.15em">±</span>'..delimit(uncU_n,fmt)

result.sign = propersign or ''

if not e_10 and units then

result.value = isnegative and -value or value

unc = unc..units

end

numbers[index] = result

return nil -- no error

end


local function get_args(numbers, args)

-- Extract arguments and store the results in numbers.

-- Return nothing (no error) if ok; otherwise, return an error message.

for index = 1, 99 do

local which = index

local arg = args[which] -- has been trimmed

if not arg then

which = 'e'

arg = args[which]

end

local msg = extract_item(which, numbers, arg)

if msg then

return msg

end

if which == 'e' then

break

end

if index > 19 then

return mtext['mt-too-many-parameter']

end

end

if numbers.has_ranges and (#numbers % 2 == 0) then

return mtext['mt-need-number']

end

end


local function get_scale(text, ucode)

-- Return the value of text as a number, or throw an error.

-- This supports extremely basic expressions of the form:

-- a / b

-- a ^ b

-- where a and b are numbers or 'pi'.

local n = tonumber(text)

if n then

return n

end

n = text:gsub('pi', math.pi)

for _, op in ipairs({ '/', '^' }) do

local a, b = n:match('^(.-)' .. op .. '(.*)$')

if a then

a = tonumber(a)

b = tonumber(b)

if a and b then

if op == '/' then

return a / b

elseif op == '^' then

return a ^ b

end

end

unc = unc..'</span>'

else

unc = '('..delimit(uncU_n,fmt)..')'

paren_uncertainty = true

end

end

break

if not e_10 and units then

end

unc = unc..units

end

error(string.format(mtext['mt-invalid-scale'], ucode, text))

end


local function get_builtin_unit(ucode, definitions)

-- Return table of information for the specified built-in unit, or nil if not known.

-- Each defined unit code must be followed by two spaces (not tab characters).

local _, pos = definitions:find('\n' .. ucode .. ' ', 1, true)

if pos then

local endline = definitions:find('%s*\n', pos)

if endline then

local result = {}

local n = 0

local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t')

for item in (text .. '\t'):gmatch('(%S.-)\t') do

if item == 'ALIAS' then

result.alias = true

elseif item == 'ANGLE' then

result.isangle = true

result.nospace = true

elseif item == 'NOSPACE' then

result.nospace = true

elseif item == 'SI' then

result.si = true

else

n = n + 1

if n == 1 then

local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$')

if link then

result.symbol = symbol

result.link = link

n = 2

else

result.symbol = item

end

elseif n == 2 then

result.link = item

elseif n == 3 then

result.scale_text = item

result.scale = get_scale(item, ucode)

else

result.more_ignored = item

break

end

end

end

end

if result.si then

local s = result.symbol

if ucode == 'mc' .. s or ucode == 'mu' .. s then

result.ucode = 'µ' .. s -- unit code for convert should be this

end

end

if n >= 2 or (n >= 1 and result.alias) then

return result

end

-- Ignore invalid definition, treating it as a comment.

end

end

end

end

end


local function convert_lookup(ucode, value, scaled_top, want_link, si, options)

local lookup = require(convert_module)._unit

return lookup(ucode, {

value = value,

scaled_top = scaled_top,

link = want_link,

si = si,

sort = options.sortable,

})

end



local function get_unit(ucode, value, scaled_top, options)

if not e_10 and units and not paren_uncertainty then

local want_link = options.want_link

n = n..units

if scaled_top then

want_link = options.want_per_link

end

end

local data = mw.loadData(data_module)

local result = options.want_longscale and

if e_10 then

get_builtin_unit(ucode, data.builtin_units_long_scale) or

e_10 = '<span style="margin-left:0.25em;margin-right:0.15em">×</span>10<sup>'..delimit(misc_tbl.e)..'</sup>'

get_builtin_unit(ucode, data.builtin_units)

if units then

local si, use_convert

e_10 = e_10..units

if result then

if result.alias then

ucode = result.symbol

use_convert = true

end

if result.scale then

-- Setting si means convert will use the unit as given, and the sort key

-- will be calculated from the value without any extra scaling that may

-- occur if convert found the unit code. For example, if val defines the

-- unit 'year' with a scale and if si were not set, convert would also apply

-- its own scale because convert knows that a year is 31,557,600 seconds.

si = { result.symbol, result.link }

value = value * result.scale

end

if result.si then

ucode = result.ucode or ucode

si = { result.symbol, result.link }

use_convert = true

end

end

else

else

e_10 = ''

result = {}

use_convert = true

end

end

local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options)

local ret = {

result.sortkey = convert_unit.sortspan

misc_tbl.pre or '',

if use_convert then

paren_wrap and '(' or '',

result.text = convert_unit.text

n,

result.scaled_top = convert_unit.scaled_value

misc_tbl.nend or '',

else

unc or '',

if want_link then

paren_wrap and ')' or '',

result.text = '[[' .. result.link .. '|' .. result.symbol .. ']]'

e_10,

else

misc_tbl.suf or ''

result.text = result.symbol

}

end

ret = table.concat(ret)

result.scaled_top = value

return ret

end

return result

end

end



-- TODO: Add other format options

local function makeunit(value, options)

-- Return table of information for the requested unit and options, or

function delimit(n,fmt)

-- return nil if no unit.

local prefix,num

options = options or {}

if not fmt then fmt = '' end

local unit

if n:find('[%-%+]') then

local ucode = options.u

prefix,num = mw.ustring.match(n,'([-+])([%d.]+)')

local percode = options.per

if ucode then

unit = get_unit(ucode, value, nil, options)

elseif percode then

unit = { nospace = true, scaled_top = value }

else

else

num = n

return nil

end

end

local ipart, dpart = delimit_groups(num)

local text = unit.text or ''

local sortkey = unit.sortkey

if fmt:lower() == 'commas' then

if percode then

num = table.concat(ipart,',')

local function bracketed(code, text)

return code:find('[*./]') and '(' .. text .. ')' or text

end

local perunit = get_unit(percode, 1, unit.scaled_top, options)

text = (ucode and bracketed(ucode, text) or '') ..

'/' .. bracketed(percode, perunit.text)

sortkey = perunit.sortkey

end

if not (unit.nospace or options.nospace) then

text = '&nbsp;' .. text

end

return { text = text, isangle = unit.isangle, sortkey = sortkey }

end


local function list_units(mode)

-- Return wikitext to list the built-in units.

-- A unit code should not contain wikimarkup so don't bother escaping.

local data = mw.loadData(data_module)

local definitions = data.builtin_units .. data.builtin_units_long_scale

local last_was_blank = true

local n = 0

local result = {}

local function add(line)

if line == '' then

last_was_blank = true

else

if last_was_blank and n > 0 then

n = n + 1

result[n] = ''

end

last_was_blank = false

n = n + 1

result[n] = line

end

end

local si_prefixes = {

-- These are the prefixes recognized by convert; u is accepted for micro.

y = 'y',

z = 'z',

a = 'a',

f = 'f',

p = 'p',

n = 'n',

u = 'µ',

['µ'] = 'µ',

m = 'm',

c = 'c',

d = 'd',

da = 'da',

h = 'h',

k = 'k',

M = 'M',

G = 'G',

T = 'T',

P = 'P',

E = 'E',

Z = 'Z',

Y = 'Y',

}

local function is_valid(ucode, unit)

if unit and not unit.more_ignored then

assert(type(unit.symbol) == 'string' and unit.symbol ~= '')

if unit.alias then

if unit.link or unit.scale_text or unit.si then

return false

end

end

if unit.si then

if unit.scale_text then

return false

end

ucode = unit.ucode or ucode

local base = unit.symbol

if ucode == base then

unit.display = base

return true

end

local plen = #ucode - #base

if plen > 0 then

local prefix = si_prefixes[ucode:sub(1, plen)]

if prefix and ucode:sub(plen + 1) == base then

unit.display = prefix .. base

return true

end

end

else

unit.display = unit.symbol

return true

end

end

return false

end

local lookup = require(convert_module)._unit

local function show_convert(ucode, unit)

-- If a built-in unit defines a scale or sets the SI flag, any unit defined in

-- convert is not used (the scale or SI prefix's scale is used for a sort key).

-- If there is no scale or SI flag, and the unit is not defined in convert,

-- the sort key may not be correct; this allows such units to be identified.

if not (unit.si or unit.scale_text) then

if mode == 'convert' then

unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown

unit.show_text = 'CONVERT'

elseif mode == 'unknown' then

unit.show = lookup(unit.alias and unit.symbol or ucode).unknown

unit.show_text = 'UNKNOWN'

elseif not unit.alias then

-- Show convert's scale in square brackets ('[1]' for an unknown unit).

-- Don't show scale for an alias because it's misleading for temperature

-- and an alias is probably not useful for anything else.

local scale = lookup(ucode, {value=1, sort='on'}).scaled_value

if type(scale) == 'number' then

scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2')

else

scale = '?'

end

unit.show = true

unit.show_text = '[' .. scale .. ']'

end

end

end

for line in definitions:gmatch('([^\n]*)\n') do

local pos, _ = line:find(' ', 1, true)

if pos then

local ucode = line:sub(1, pos - 1)

local unit = get_builtin_unit(ucode, '\n' .. line .. '\n')

if is_valid(ucode, unit) then

show_convert(ucode, unit)

local flags, text

if unit.alias then

text = unit.symbol

else

text = '[[' .. unit.link .. '|' .. unit.display .. ']]'

end

if unit.isangle then

unit.nospace = nil -- don't show redundant flag

end

for _, f in ipairs({

{ 'alias', 'ALIAS' },

{ 'isangle', 'ANGLE' },

{ 'nospace', 'NOSPACE' },

{ 'si', 'SI' },

{ 'scale_text', unit.scale_text },

{ 'show', unit.show_text },

}) do

if unit[f[1]] then

local t = f[2]

if t:match('^%u+$') then

t = '<small>' .. t .. '</small>'

end

if flags then

flags = flags .. ' ' .. t

else

flags = t

end

end

end

if flags then

text = text .. ' • ' .. flags

end

add(ucode .. ' = ' .. text .. '<br />')

else

add(line .. ' ◆ <b>invalid definition</b><br />')

end

else

add(line)

end

end

return table.concat(result, '\n')

end


local delimit_groups = require('Module:Gapnum').groups

local function delimit(sign, numstr, fmt)

-- Return sign and numstr (unsigned digits or numdot only) after formatting.

-- Four-digit integers are not formatted with gaps.

fmt = (fmt or ''):lower()

if fmt == 'none' or (fmt == '' and #numstr == 4 and numstr:match('^%d+$')) then

return sign .. numstr

end

-- Group number by integer and decimal parts.

-- If there is no decimal part, delimit_groups returns only one table.

local ipart, dpart = delimit_groups(numstr)

local result

if fmt == 'commas' then

result = sign .. table.concat(ipart, numsep)

if dpart then

if dpart then

dpart = table.concat(dpart)

result = result .. numdot .. table.concat(dpart)

num = num..'.'..dpart

end

end

else

else

-- Delimit with a small gap by default.

num = {}

local groups = {}

num[1] = table.remove(ipart,1)

groups[1] = table.remove(ipart, 1)

for _, v in ipairs(ipart) do

for _, v in ipairs(ipart) do

table.insert(num,'<span style="margin-left:.25em">'..v..'</span>')

table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')

end

end

if dpart then

if dpart then

table.insert(num,'.'..table.remove(dpart,1))

table.insert(groups, numdot .. (table.remove(dpart, 1) or ''))

for _, v in ipairs(dpart) do

for _, v in ipairs(dpart) do

table.insert(num,'<span style="margin-left:.25em">'..v..'</span>')

table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')

end

end

end

end

num = table.concat(num)

result = sign .. table.concat(groups)

end

end

return result

if prefix then

end

if prefix == '-' then


prefix = '&minus;'

local function sup_sub(sup, sub, align)

-- Return the same result as Module:Su except val defaults to align=right.

if align == 'l' or align == 'left' then

align = 'left'

elseif align == 'c' or align == 'center' then

align = 'center'

else

align = 'right'

end

return '<span style="display:inline-block;margin-bottom:-0.3em;vertical-align:-0.4em;line-height:1.2em;font-size:85%;text-align:' ..

align .. ';">' .. sup .. '<br />' .. sub .. '</span>'

end


local function range_text(items, unit_table, options)

local fmt = options.fmt

local nend = items.nend or ''

if items.isrepeat or unit_table.isangle then

nend = nend .. unit_table.text

end

local text = ''

for i = 1, #items do

if i % 2 == 0 then

text = text .. items[i]

else

text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend

end

end

num = prefix..num

end

end

return num

return text

end


local function uncertainty_text(uncertainty, unit_table, options)

local angle, text, need_parens

if unit_table.isangle then

angle = unit_table.text

end

local upper = uncertainty.upper or {}

local lower = uncertainty.lower or {}

local uncU = upper.clean

if uncU then

local fmt = options.fmt

local uncL = lower.clean

if uncL then

uncU = delimit('+', uncU, fmt) .. (upper.errend or '')

uncL = delimit('−', uncL, fmt) .. (lower.errend or '')

if angle then

uncU = uncU .. angle

uncL = uncL .. angle

end

text = (angle or '') ..

'<span style="margin-left:0.3em;">' ..

sup_sub(uncU, uncL, options.align) ..

'</span>'

else

if upper.parens then

text = '(' .. uncU .. ')' -- old template did not delimit

else

text = (angle or '') ..

'<span style="margin-left:0.3em;margin-right:0.15em;">±</span>' ..

delimit('', uncU, fmt)

need_parens = true

end

if uncertainty.errend then

text = text .. uncertainty.errend

end

if angle then

text = text .. angle

end

end

else

if angle then

text = angle

end

end

return text, need_parens

end


local function _main(values, unit_spec, options)

if options.sandbox then

data_module = data_module .. '/sandbox'

convert_module = convert_module .. '/sandbox'

end

local action = options.action

if action then

if action == 'list' then

-- Kludge: am using the align parameter (a=xxx) for type of list.

return list_units(options.align)

end

return valerror('invalid action "' .. action .. '".', options.nocat)

end

local number = values.number or (values.numbers and values.numbers[1]) or {}

local e_10 = options.e or {}

local novalue = (number.value == nil and e_10.clean == nil)

local fmt = options.fmt

local want_sort = true

local sortable = options.sortable

if sortable == 'off' or (sortable == nil and novalue) then

want_sort = false

elseif sortable == 'debug' then

-- Same as sortable = 'on' but the sort key is displayed.

else

sortable = 'on'

end

local sort_value = 1

if want_sort then

sort_value = number.value or 1

if e_10.value and sort_value ~= 0 then

-- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.

sort_value = sort_value * 10^e_10.value

end

end

local unit_table = makeunit(sort_value, {

u = unit_spec.u,

want_link = unit_spec.want_link,

per = unit_spec.per,

want_per_link = unit_spec.want_per_link,

nospace = novalue,

want_longscale = unit_spec.want_longscale,

sortable = sortable,

})

local sortkey

if unit_table then

if want_sort then

sortkey = unit_table.sortkey

end

else

unit_table = { text = '' }

if want_sort then

sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan

end

end

local final_unit = unit_table.isangle and '' or unit_table.text

local e_text, n_text, need_parens

local uncertainty = values.uncertainty

if uncertainty then

if number.clean then

n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or '')

local text

text, need_parens = uncertainty_text(uncertainty, unit_table, options)

if text then

n_text = n_text .. text

end

else

n_text = ''

end

else

if values.numbers.isrepeat then

final_unit = ''

end

n_text = range_text(values.numbers, unit_table, options)

need_parens = true

end

if e_10.clean then

if need_parens then

n_text = '(' .. n_text .. ')'

end

e_text = '10<sup>' .. delimit(e_10.sign, e_10.clean, fmt) .. '</sup>'

if number.clean then

e_text = '<span style="margin-left:0.25em;margin-right:0.15em;">×</span>' .. e_text

end

else

e_text = ''

end

local result =

(sortkey or '') ..

(options.prefix or '') ..

n_text ..

e_text ..

final_unit ..

(options.suffix or '')

if result ~= '' then

result = '<span class="nowrap">' .. result .. '</span>'

end

return result .. (options.warning or '')

end


local function check_parameters(args, has_ranges, nocat)

-- Return warning text for the first problem parameter found, or nothing if ok.

local whitelist = {

a = true,

action = true,

debug = true,

e = true,

['end'] = true,

errend = true,

['+errend'] = true,

['-errend'] = true,

fmt = true,

['long scale'] = true,

long_scale = true,

longscale = true,

nocategory = true,

p = true,

s = true,

sortable = true,

u = true,

ul = true,

up = true,

upl = true,

}

for k, v in pairs(args) do

if type(k) == 'string' and not whitelist[k] then

local warning = string.format(mtext['mt-val-not-supported'], k, v)

return valerror(warning, nocat, true)

end

end

if not has_ranges and args[4] then

return valerror(mtext['mt-ignore-parameter4'], nocat, true)

end

end


local function main(frame)

local getArgs = require('Module:Arguments').getArgs

local args = getArgs(frame, {wrappers = { 'Template:Val' }})

local nocat = args.nocategory

local numbers = {} -- table of number tables, perhaps with range text

local msg = get_args(numbers, args)

if msg then

return valerror(msg, nocat)

end

if args.u and args.ul then

return valerror(mtext['mt-both-u-ul'], nocat)

end

if args.up and args.upl then

return valerror(mtext['mt-both-up-upl'], nocat)

end

local values

if numbers.has_ranges then

-- Multiple values with range separators but no uncertainty.

numbers.nend = args['end']

values = {

numbers = numbers,

}

else

-- A single value with optional uncertainty.

local function setfield(i, dst, src)

local v = args[src]

if v then

if numbers[i] then

numbers[i][dst] = v

else

numbers[i] = { [dst] = v }

end

end

end

setfield(1, 'nend', 'end')

setfield(2, 'errend', '+errend')

setfield(3, 'errend', '-errend')

values = {

number = numbers[1],

uncertainty = {

upper = numbers[2],

lower = numbers[3],

errend = args.errend,

}

}

end

local unit_spec = {

u = args.ul or args.u,

want_link = args.ul ~= nil,

per = args.upl or args.up,

want_per_link = args.upl ~= nil,

want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on',

}

local options = {

action = args.action,

align = args.a,

e = numbers.e,

fmt = args.fmt,

nocat = nocat,

prefix = args.p,

sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,

sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil),

suffix = args.s,

warning = check_parameters(args, numbers.has_ranges, nocat),

}

return _main(values, unit_spec, options)

end

end



return { main = main, _main = _main }

return p


Latest revision as of 03:19, 30 June 2022

This module implements {{Val}}.

The following modules are developed:

Use {{val/sandbox}} for testing, for example:

-- For Template:Val, output a number and optional unit.
-- Format options include scientific and uncertainty notations.

local numdot = '.'  -- decimal mark (use ',' for Italian)
local numsep = ','  -- group separator (use ' ' for Italian)
local mtext = {
 -- Message and other text that should be localized.
 ['mt-bad-exponent'] =       'exponent parameter (<b>e</b>)',
 ['mt-parameter'] =          'parameter ',
 ['mt-not-number'] =         'is not a valid number',
 ['mt-cannot-range'] =       'cannot use a range if the first parameter includes "e"',
 ['mt-need-range'] =         'needs a range in parameter 2',
 ['mt-should-range'] =       'should be a range',
 ['mt-cannot-with-e'] =      'cannot be used if the first parameter includes "e"',
 ['mt-not-range'] =          'does not accept a range',
 ['mt-cannot-e'] =           'cannot use e notation',
 ['mt-too-many-parameter'] = 'too many parameters',
 ['mt-need-number'] =        'need a number after the last parameter because it is a range.',
 ['mt-ignore-parameter4'] =  'Val parameter 4 ignored',
 ['mt-val-not-supported'] =  'Val parameter "%s=%s" is not supported',
 ['mt-invalid-scale'] =      'Unit "%s" has invalid scale "%s"',
 ['mt-both-u-ul'] =          'unit (<b>u</b>) and unit with link (<b>ul</b>) are both specified, only one is allowed.',
 ['mt-both-up-upl'] =        'unit per (<b>up</b>) and unit per with link (<b>upl</b>) are both specified, only one is allowed.',
}

local data_module = 'Module:Val/units'
local convert_module = 'Module:Convert'

local function valerror(msg, nocat, iswarning)
 -- Return formatted message text for an error or warning.
 -- Can append "#FormattingError" to URL of a page with a problem to find it.
 local anchor = '<span id="FormattingError"></span>'
 local body, category
 if nocat or mw.title.getCurrentTitle():inNamespaces(1, 2, 3, 5) then
  -- No category in Talk, User, User_talk, or Wikipedia_talk.
  category = ''
 else
  category = '[[Category:Pages with incorrect formatting templates use]]'
 end
 iswarning = false  -- problems are infrequent so try showing large error so editor will notice
 if iswarning then
  body = '<sup class="noprint Inline-Template" style="white-space:nowrap;">' ..
   '[[Template:Val|<span title="' ..
   msg:gsub('"', '&quot;') ..
   '">warning</span>]]</sup>'
 else
  body = '<strong class="error">' ..
   'Error in &#123;&#123;[[Template:val|val]]&#125;&#125;: ' ..
   msg ..
   '</strong>'
 end
 return anchor .. body .. category
end

local range_types = {
 -- No need for '&nbsp;' because nowrap applies to all output.
 [","]   = ", ",
 ["by"]  = " by ",
 ["-"]   = "–",
 ["–"]   = "–",
 ["and"] = " and ",
 ["or"]  = " or " ,
 ["to"]  = " to " ,
 ["x"]   = " × ",
 ["×"]   = " × ",
 ["/"]   = "/",
}
local range_repeat_unit = {
 -- WP:UNIT wants unit repeated when a "multiply" range is used.
 ["x"]   = true,
 ["×"]   = true,
}

local function extract_item(index, numbers, arg)
 -- Extract an item from arg and store the result in numbers[index].
 -- If no argument or if argument is valid, return nil (no error);
 -- otherwise, return an error message.
 -- The stored result is:
 -- * a table for a number (empty if there was no specified number); or
 -- * a string for range text
 -- Input like 1e3 is regarded as invalid for all except argument 1
 -- which accepts e notation as an alternative to the 'e' argument.
 -- Input group separators are removed.
 local which = index
 local function fail(msg)
  local description
  if which == 'e' then
   description = mtext['mt-bad-exponent']
  else
   description = mtext['mt-parameter'] .. which
  end
  return description .. ' ' .. (msg or mtext['mt-not-number']) .. '.'
 end
 local result = {}
 local range = range_types[arg]
 if range then
  if type(index) == 'number' and (index % 2 == 0) then
   if index == 2 then
    if numbers[1] and numbers[1].exp then
     return fail(mtext['mt-cannot-range'])
    end
    numbers.has_ranges = true
   else
    if not numbers.has_ranges then
     return fail(mtext['mt-need-range'])
    end
   end
   numbers[index] = range
   if range_repeat_unit[arg] then
    -- Any "repeat" range forces unit (if any) to be repeated for all items.
    numbers.isrepeat = true
   end
   return nil
  end
  return fail(mtext['mt-not-range'])
 end
 if numbers.has_ranges and type(index) == 'number' and (index % 2 == 0) then
  return fail(mtext['mt-should-range'])
 end
 if index == 'e' then
  local e = numbers[1] and numbers[1].exp
  if e then
   if arg then
    return fail(mtext['mt-cannot-with-e'])
   end
   arg = e
   which = 1
  end
 end
 if arg and arg ~= '' then
  arg = arg:gsub(numsep, '')
  if numdot ~= '.' then
   arg = arg:gsub(numdot, '.')
  end
  if arg:sub(1, 1) == '(' and arg:sub(-1) == ')' then
   result.parens = true
   arg = arg:sub(2, -2)
  end
  local a, b = arg:match('^(.+)[Ee](.+)$')
  if a then
   if index == 1 then
    arg = a
    result.exp = b
   else
    return fail(mtext['mt-cannot-e'])
   end
  end
  local isnegative, propersign, prefix
  local minus = '−'
  prefix, arg = arg:match('^(.-)([%d.]+)$')
  local value = tonumber(arg)
  if not value then
   return fail()
  end
  if arg:sub(1, 1) == '.' then
   arg = '0' .. arg
  end
  if prefix == '' then
   -- Ignore.
  elseif prefix == '±' then
   -- Display for first number, ignore for others.
   if index == 1 then
    propersign = '±'
   end
  elseif prefix == '+' then
   propersign = '+'
  elseif prefix == '-' or prefix == minus then
   propersign = minus
   isnegative = true
  else
   return fail()
  end
  result.clean = arg
  result.sign = propersign or ''
  result.value = isnegative and -value or value
 end
 numbers[index] = result
 return nil  -- no error
end

local function get_args(numbers, args)
 -- Extract arguments and store the results in numbers.
 -- Return nothing (no error) if ok; otherwise, return an error message.
 for index = 1, 99 do
  local which = index
  local arg = args[which]  -- has been trimmed
  if not arg then
   which = 'e'
   arg = args[which]
  end
  local msg = extract_item(which, numbers, arg)
  if msg then
   return msg
  end
  if which == 'e' then
   break
  end
  if index > 19 then
   return mtext['mt-too-many-parameter']
  end
 end
 if numbers.has_ranges and (#numbers % 2 == 0) then
  return mtext['mt-need-number']
 end
end

local function get_scale(text, ucode)
 -- Return the value of text as a number, or throw an error.
 -- This supports extremely basic expressions of the form:
 --   a / b
 --   a ^ b
 -- where a and b are numbers or 'pi'.
 local n = tonumber(text)
 if n then
  return n
 end
 n = text:gsub('pi', math.pi)
 for _, op in ipairs({ '/', '^' }) do
  local a, b = n:match('^(.-)' .. op .. '(.*)$')
  if a then
   a = tonumber(a)
   b = tonumber(b)
   if a and b then
    if op == '/' then
     return a / b
    elseif op == '^' then
     return a ^ b
    end
   end
   break
  end
 end
 error(string.format(mtext['mt-invalid-scale'], ucode, text))
end

local function get_builtin_unit(ucode, definitions)
 -- Return table of information for the specified built-in unit, or nil if not known.
 -- Each defined unit code must be followed by two spaces (not tab characters).
 local _, pos = definitions:find('\n' .. ucode .. '  ', 1, true)
 if pos then
  local endline = definitions:find('%s*\n', pos)
  if endline then
   local result = {}
   local n = 0
   local text = definitions:sub(pos + 1, endline - 1):gsub('%s%s+', '\t')
   for item in (text .. '\t'):gmatch('(%S.-)\t') do
    if item == 'ALIAS' then
     result.alias = true
    elseif item == 'ANGLE' then
     result.isangle = true
     result.nospace = true
    elseif item == 'NOSPACE' then
     result.nospace = true
    elseif item == 'SI' then
     result.si = true
    else
     n = n + 1
     if n == 1 then
      local link, symbol = item:match('^%[%[([^|]+)|(.+)%]%]$')
      if link then
       result.symbol = symbol
       result.link = link
       n = 2
      else
       result.symbol = item
      end
     elseif n == 2 then
      result.link = item
     elseif n == 3 then
      result.scale_text = item
      result.scale = get_scale(item, ucode)
     else
      result.more_ignored = item
      break
     end
    end
   end
   if result.si then
    local s = result.symbol
    if ucode == 'mc' .. s or ucode == 'mu' .. s then
     result.ucode = 'µ' .. s  -- unit code for convert should be this
    end
   end
   if n >= 2 or (n >= 1 and result.alias) then
    return result
   end
   -- Ignore invalid definition, treating it as a comment.
  end
 end
end

local function convert_lookup(ucode, value, scaled_top, want_link, si, options)
 local lookup = require(convert_module)._unit
 return lookup(ucode, {
   value = value,
   scaled_top = scaled_top,
   link = want_link,
   si = si,
   sort = options.sortable,
  })
end

local function get_unit(ucode, value, scaled_top, options)
 local want_link = options.want_link
 if scaled_top then
  want_link = options.want_per_link
 end
 local data = mw.loadData(data_module)
 local result = options.want_longscale and
  get_builtin_unit(ucode, data.builtin_units_long_scale) or
  get_builtin_unit(ucode, data.builtin_units)
 local si, use_convert
 if result then
  if result.alias then
   ucode = result.symbol
   use_convert = true
  end
  if result.scale then
   -- Setting si means convert will use the unit as given, and the sort key
   -- will be calculated from the value without any extra scaling that may
   -- occur if convert found the unit code. For example, if val defines the
   -- unit 'year' with a scale and if si were not set, convert would also apply
   -- its own scale because convert knows that a year is 31,557,600 seconds.
   si = { result.symbol, result.link }
   value = value * result.scale
  end
  if result.si then
   ucode = result.ucode or ucode
   si = { result.symbol, result.link }
   use_convert = true
  end
 else
  result = {}
  use_convert = true
 end
 local convert_unit = convert_lookup(ucode, value, scaled_top, want_link, si, options)
 result.sortkey = convert_unit.sortspan
 if use_convert then
  result.text = convert_unit.text
  result.scaled_top = convert_unit.scaled_value
 else
  if want_link then
   result.text = '[[' .. result.link .. '|' .. result.symbol .. ']]'
  else
   result.text = result.symbol
  end
  result.scaled_top = value
 end
 return result
end

local function makeunit(value, options)
 -- Return table of information for the requested unit and options, or
 -- return nil if no unit.
 options = options or {}
 local unit
 local ucode = options.u
 local percode = options.per
 if ucode then
  unit = get_unit(ucode, value, nil, options)
 elseif percode then
  unit = { nospace = true, scaled_top = value }
 else
  return nil
 end
 local text = unit.text or ''
 local sortkey = unit.sortkey
 if percode then
  local function bracketed(code, text)
   return code:find('[*./]') and '(' .. text .. ')' or text
  end
  local perunit = get_unit(percode, 1, unit.scaled_top, options)
  text = (ucode and bracketed(ucode, text) or '') ..
    '/' .. bracketed(percode, perunit.text)
  sortkey = perunit.sortkey
 end
 if not (unit.nospace or options.nospace) then
  text = '&nbsp;' .. text
 end
 return { text = text, isangle = unit.isangle, sortkey = sortkey }
end

local function list_units(mode)
 -- Return wikitext to list the built-in units.
 -- A unit code should not contain wikimarkup so don't bother escaping.
 local data = mw.loadData(data_module)
 local definitions = data.builtin_units .. data.builtin_units_long_scale
 local last_was_blank = true
 local n = 0
 local result = {}
 local function add(line)
  if line == '' then
   last_was_blank = true
  else
   if last_was_blank and n > 0 then
    n = n + 1
    result[n] = ''
   end
   last_was_blank = false
   n = n + 1
   result[n] = line
  end
 end
 local si_prefixes = {
  -- These are the prefixes recognized by convert; u is accepted for micro.
  y = 'y',
  z = 'z',
  a = 'a',
  f = 'f',
  p = 'p',
  n = 'n',
  u = 'µ',
  ['µ'] = 'µ',
  m = 'm',
  c = 'c',
  d = 'd',
  da = 'da',
  h = 'h',
  k = 'k',
  M = 'M',
  G = 'G',
  T = 'T',
  P = 'P',
  E = 'E',
  Z = 'Z',
  Y = 'Y',
 }
 local function is_valid(ucode, unit)
  if unit and not unit.more_ignored then
   assert(type(unit.symbol) == 'string' and unit.symbol ~= '')
   if unit.alias then
    if unit.link or unit.scale_text or unit.si then
     return false
    end
   end
   if unit.si then
    if unit.scale_text then
     return false
    end
    ucode = unit.ucode or ucode
    local base = unit.symbol
    if ucode == base then
     unit.display = base
     return true
    end
    local plen = #ucode - #base
    if plen > 0 then
     local prefix = si_prefixes[ucode:sub(1, plen)]
     if prefix and ucode:sub(plen + 1) == base then
      unit.display = prefix .. base
      return true
     end
    end
   else
    unit.display = unit.symbol
    return true
   end
  end
  return false
 end
 local lookup = require(convert_module)._unit
 local function show_convert(ucode, unit)
  -- If a built-in unit defines a scale or sets the SI flag, any unit defined in
  -- convert is not used (the scale or SI prefix's scale is used for a sort key).
  -- If there is no scale or SI flag, and the unit is not defined in convert,
  -- the sort key may not be correct; this allows such units to be identified.
  if not (unit.si or unit.scale_text) then
   if mode == 'convert' then
    unit.show = not lookup(unit.alias and unit.symbol or ucode).unknown
    unit.show_text = 'CONVERT'
   elseif mode == 'unknown' then
    unit.show = lookup(unit.alias and unit.symbol or ucode).unknown
    unit.show_text = 'UNKNOWN'
   elseif not unit.alias then
    -- Show convert's scale in square brackets ('[1]' for an unknown unit).
    -- Don't show scale for an alias because it's misleading for temperature
    -- and an alias is probably not useful for anything else.
    local scale = lookup(ucode, {value=1, sort='on'}).scaled_value
    if type(scale) == 'number' then
     scale = string.format('%.5g', scale):gsub('e%+?(%-?)0*(%d+)', 'e%1%2')
    else
     scale = '?'
    end
    unit.show = true
    unit.show_text = '[' .. scale .. ']'
   end
  end
 end
 for line in definitions:gmatch('([^\n]*)\n') do
  local pos, _ = line:find('  ', 1, true)
  if pos then
   local ucode = line:sub(1, pos - 1)
   local unit = get_builtin_unit(ucode, '\n' .. line .. '\n')
   if is_valid(ucode, unit) then
    show_convert(ucode, unit)
    local flags, text
    if unit.alias then
     text = unit.symbol
    else
     text = '[[' .. unit.link .. '|' .. unit.display .. ']]'
    end
    if unit.isangle then
     unit.nospace = nil  -- don't show redundant flag
    end
    for _, f in ipairs({
      { 'alias', 'ALIAS' },
      { 'isangle', 'ANGLE' },
      { 'nospace', 'NOSPACE' },
      { 'si', 'SI' },
      { 'scale_text', unit.scale_text },
      { 'show', unit.show_text },
     }) do
     if unit[f[1]] then
      local t = f[2]
      if t:match('^%u+$') then
       t = '<small>' .. t .. '</small>'
      end
      if flags then
       flags = flags .. ' ' .. t
      else
       flags = t
      end
     end
    end
    if flags then
     text = text .. ' • ' .. flags
    end
    add(ucode .. ' = ' .. text .. '<br />')
   else
    add(line .. ' ◆ <b>invalid definition</b><br />')
   end
  else
   add(line)
  end
 end
 return table.concat(result, '\n')
end

local delimit_groups = require('Module:Gapnum').groups
local function delimit(sign, numstr, fmt)
 -- Return sign and numstr (unsigned digits or numdot only) after formatting.
 -- Four-digit integers are not formatted with gaps.
 fmt = (fmt or ''):lower()
 if fmt == 'none' or (fmt == '' and #numstr == 4 and numstr:match('^%d+$')) then
  return sign .. numstr
 end
 -- Group number by integer and decimal parts.
 -- If there is no decimal part, delimit_groups returns only one table.
 local ipart, dpart = delimit_groups(numstr)
 local result
 if fmt == 'commas' then
  result = sign .. table.concat(ipart, numsep)
  if dpart then
   result = result .. numdot .. table.concat(dpart)
  end
 else
  -- Delimit with a small gap by default.
  local groups = {}
  groups[1] = table.remove(ipart, 1)
  for _, v in ipairs(ipart) do
   table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
  end
  if dpart then
   table.insert(groups, numdot .. (table.remove(dpart, 1) or ''))
   for _, v in ipairs(dpart) do
    table.insert(groups, '<span style="margin-left:.25em;">' .. v .. '</span>')
   end
  end
  result = sign .. table.concat(groups)
 end
 return result
end

local function sup_sub(sup, sub, align)
 -- Return the same result as Module:Su except val defaults to align=right.
 if align == 'l' or align == 'left' then
  align = 'left'
 elseif align == 'c' or align == 'center' then
  align = 'center'
 else
  align = 'right'
 end
 return '<span style="display:inline-block;margin-bottom:-0.3em;vertical-align:-0.4em;line-height:1.2em;font-size:85%;text-align:' ..
  align .. ';">' .. sup .. '<br />' .. sub .. '</span>'
end

local function range_text(items, unit_table, options)
 local fmt = options.fmt
 local nend = items.nend or ''
 if items.isrepeat or unit_table.isangle then
  nend = nend .. unit_table.text
 end
 local text = ''
 for i = 1, #items do
  if i % 2 == 0 then
   text = text .. items[i]
  else
   text = text .. delimit(items[i].sign, items[i].clean, fmt) .. nend
  end
 end
 return text
end

local function uncertainty_text(uncertainty, unit_table, options)
 local angle, text, need_parens
 if unit_table.isangle then
  angle = unit_table.text
 end
 local upper = uncertainty.upper or {}
 local lower = uncertainty.lower or {}
 local uncU = upper.clean
 if uncU then
  local fmt = options.fmt
  local uncL = lower.clean
  if uncL then
   uncU = delimit('+', uncU, fmt) .. (upper.errend or '')
   uncL = delimit('−', uncL, fmt) .. (lower.errend or '')
   if angle then
    uncU = uncU .. angle
    uncL = uncL .. angle
   end
   text = (angle or '') ..
    '<span style="margin-left:0.3em;">' ..
    sup_sub(uncU, uncL, options.align) ..
    '</span>'
  else
   if upper.parens then
    text = '(' .. uncU .. ')'  -- old template did not delimit
   else
    text = (angle or '') ..
     '<span style="margin-left:0.3em;margin-right:0.15em;">±</span>' ..
     delimit('', uncU, fmt)
    need_parens = true
   end
   if uncertainty.errend then
    text = text .. uncertainty.errend
   end
   if angle then
    text = text .. angle
   end
  end
 else
  if angle then
   text = angle
  end
 end
 return text, need_parens
end

local function _main(values, unit_spec, options)
 if options.sandbox then
  data_module = data_module .. '/sandbox'
  convert_module = convert_module .. '/sandbox'
 end
 local action = options.action
 if action then
  if action == 'list' then
   -- Kludge: am using the align parameter (a=xxx) for type of list.
   return list_units(options.align)
  end
  return valerror('invalid action "' .. action .. '".', options.nocat)
 end
 local number = values.number or (values.numbers and values.numbers[1]) or {}
 local e_10 = options.e or {}
 local novalue = (number.value == nil and e_10.clean == nil)
 local fmt = options.fmt
 local want_sort = true
 local sortable = options.sortable
 if sortable == 'off' or (sortable == nil and novalue) then
  want_sort = false
 elseif sortable == 'debug' then
  -- Same as sortable = 'on' but the sort key is displayed.
 else
  sortable = 'on'
 end
 local sort_value = 1
 if want_sort then
  sort_value = number.value or 1
  if e_10.value and sort_value ~= 0 then
   -- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.
   sort_value = sort_value * 10^e_10.value
  end
 end
 local unit_table = makeunit(sort_value, {
      u = unit_spec.u,
      want_link = unit_spec.want_link,
      per = unit_spec.per,
      want_per_link = unit_spec.want_per_link,
      nospace = novalue,
      want_longscale = unit_spec.want_longscale,
      sortable = sortable,
     })
 local sortkey
 if unit_table then
  if want_sort then
   sortkey = unit_table.sortkey
  end
 else
  unit_table = { text = '' }
  if want_sort then
   sortkey = convert_lookup('dummy', sort_value, nil, nil, nil, { sortable = sortable }).sortspan
  end
 end
 local final_unit = unit_table.isangle and '' or unit_table.text
 local e_text, n_text, need_parens
 local uncertainty = values.uncertainty
 if uncertainty then
  if number.clean then
   n_text = delimit(number.sign, number.clean, fmt) .. (number.nend or '')
   local text
   text, need_parens = uncertainty_text(uncertainty, unit_table, options)
   if text then
    n_text = n_text .. text
   end
  else
   n_text = ''
  end
 else
  if values.numbers.isrepeat then
   final_unit = ''
  end
  n_text = range_text(values.numbers, unit_table, options)
  need_parens = true
 end
 if e_10.clean then
  if need_parens then
   n_text = '(' .. n_text .. ')'
  end
  e_text = '10<sup>' .. delimit(e_10.sign, e_10.clean, fmt) .. '</sup>'
  if number.clean then
   e_text = '<span style="margin-left:0.25em;margin-right:0.15em;">×</span>' .. e_text
  end
 else
  e_text = ''
 end
 local result =
  (sortkey or '') ..
  (options.prefix or '') ..
  n_text ..
  e_text ..
  final_unit ..
  (options.suffix or '')
 if result ~= '' then
  result = '<span class="nowrap">' .. result .. '</span>'
 end
 return result .. (options.warning or '')
end

local function check_parameters(args, has_ranges, nocat)
 -- Return warning text for the first problem parameter found, or nothing if ok.
 local whitelist = {
  a = true,
  action = true,
  debug = true,
  e = true,
  ['end'] = true,
  errend = true,
  ['+errend'] = true,
  ['-errend'] = true,
  fmt = true,
  ['long scale'] = true,
  long_scale = true,
  longscale = true,
  nocategory = true,
  p = true,
  s = true,
  sortable = true,
  u = true,
  ul = true,
  up = true,
  upl = true,
 }
 for k, v in pairs(args) do
  if type(k) == 'string' and not whitelist[k] then
   local warning = string.format(mtext['mt-val-not-supported'], k, v)
   return valerror(warning, nocat, true)
  end
 end
 if not has_ranges and args[4] then
  return valerror(mtext['mt-ignore-parameter4'], nocat, true)
 end
end

local function main(frame)
 local getArgs = require('Module:Arguments').getArgs
 local args = getArgs(frame, {wrappers = { 'Template:Val' }})
 local nocat = args.nocategory
 local numbers = {}  -- table of number tables, perhaps with range text
 local msg = get_args(numbers, args)
 if msg then
  return valerror(msg, nocat)
 end
 if args.u and args.ul then
  return valerror(mtext['mt-both-u-ul'], nocat)
 end
 if args.up and args.upl then
  return valerror(mtext['mt-both-up-upl'], nocat)
 end
 local values
 if numbers.has_ranges then
  -- Multiple values with range separators but no uncertainty.
  numbers.nend = args['end']
  values = {
   numbers = numbers,
  }
 else
  -- A single value with optional uncertainty.
  local function setfield(i, dst, src)
   local v = args[src]
   if v then
    if numbers[i] then
     numbers[i][dst] = v
    else
     numbers[i] = { [dst] = v }
    end
   end
  end
  setfield(1, 'nend', 'end')
  setfield(2, 'errend', '+errend')
  setfield(3, 'errend', '-errend')
  values = {
   number = numbers[1],
   uncertainty = {
    upper = numbers[2],
    lower = numbers[3],
    errend = args.errend,
   }
  }
 end
 local unit_spec = {
   u = args.ul or args.u,
   want_link = args.ul ~= nil,
   per = args.upl or args.up,
   want_per_link = args.upl ~= nil,
   want_longscale = (args.longscale or args.long_scale or args['long scale']) == 'on',
  }
 local options = {
   action = args.action,
   align = args.a,
   e = numbers.e,
   fmt = args.fmt,
   nocat = nocat,
   prefix = args.p,
   sandbox = string.find(frame:getTitle(), 'sandbox', 1, true) ~= nil,
   sortable = args.sortable or (args.debug == 'yes' and 'debug' or nil),
   suffix = args.s,
   warning = check_parameters(args, numbers.has_ranges, nocat),
  }
 return _main(values, unit_spec, options)
end

return { main = main, _main = _main }

Retrieved from "https://en.wikipedia.org/w/index.php?title=Module:Val&oldid=1095746275"

Hidden category: 
Wikipedia template-protected modules
 



This page was last edited on 30 June 2022, at 03:19 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki