Module:Val
Appearance
![]() | This Lua module is used on approximately 41,000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
![]() | This module depends on the following other modules: |
This module implements {{Val}}.
The following modules are developed:
- Module:Val • Main module.
- Module:Val/units • Definitions for units built-in to val.
Use {{val/sandbox}} for testing, for example:
{{val/sandbox|1234.5678|(23)|u=cm}}
→ 1234.5678(23) cm{{val/sandbox|1234.5678|1.23|u=cm}}
→ 1234.5678±1.23 cm{{val/sandbox|1234.5678|1.23|4.56|u=cm}}
→ 1234.5678+1.23
−4.56 cm{{val/sandbox|1234.5678|e=3|u=cm}}
→ 1234.5678×103 cm{{val/sandbox|1234.5678|(23)|e=3|u=cm}}
→ 1234.5678(23)×103 cm{{val/sandbox|1234.5678|1.23|e=3|u=cm}}
→ (1234.5678±1.23)×103 cm{{val/sandbox|1234.5678|1.23|4.56|e=3|u=cm}}
→ 1234.5678+1.23
−4.56×103 cm{{val/sandbox|1234.5678|1.23|4.56|e=3|u=cm|end=$|+errend=U$|-errend=L$}}
→ 1234.5678$+1.23U$
−4.56L$×103 cm{{val/sandbox|1234.5678|(23)|u=deg}}
→ 1234.5678(23)°{{val/sandbox|1234.5678|1.23|u=deg}}
→ 1234.5678°±1.23°{{val/sandbox|1234.5678|1.23|4.56|u=deg}}
→ 1234.5678°+1.23°
−4.56°{{val/sandbox|1234.5678|e=3|u=deg}}
→ 1234.5678°×103{{val/sandbox|1234.5678|(23)|e=3|u=deg}}
→ 1234.5678(23)°×103{{val/sandbox|1234.5678|1.23|e=3|u=deg}}
→ (1234.5678°±1.23°)×103{{val/sandbox|1234.5678|1.23|4.56|e=3|u=deg}}
→ 1234.5678°+1.23°
−4.56°×103{{val/sandbox|1234.5678|1.23|4.56|e=3|u=deg|end=$|+errend=U$|-errend=L$}}
→ 1234.5678$°+1.23U$°
−4.56L$°×103
-- For Template:Val, output a number and optional unit.-- Format options include scientific and uncertainty notations.localnumdot='.'-- decimal mark (use ',' for Italian)localnumsep=','-- group separator (use ' ' for Italian)localmtext={-- 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.',}localdata_module='Module:Val/units'localconvert_module='Module:Convert'localfunctionvalerror(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.localanchor='<span id="FormattingError"></span>'localbody,categoryifnocatormw.title.getCurrentTitle():inNamespaces(1,2,3,5)then-- No category in Talk, User, User_talk, or Wikipedia_talk.category=''elsecategory='[[Category:Pages with incorrect formatting templates use]]'endiswarning=false-- problems are infrequent so try showing large error so editor will noticeifiswarningthenbody='<sup class="noprint Inline-Template" style="white-space:nowrap;">'..'[[Template:Val|<span title="'..msg:gsub('"','"')..'">warning</span>]]</sup>'elsebody='<strong class="error">'..'Error in {{[[Template:val|val]]}}: '..msg..'</strong>'endreturnanchor..body..categoryendlocalrange_types={-- No need for ' ' because nowrap applies to all output.[","]=", ",["by"]=" by ",["-"]="–",["–"]="–",["and"]=" and ",["or"]=" or ",["to"]=" to ",["x"]=" × ",["×"]=" × ",["/"]="/",}localrange_repeat_unit={-- WP:UNIT wants unit repeated when a "multiply" range is used.["x"]=true,["×"]=true,}localfunctionextract_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.localwhich=indexlocalfunctionfail(msg)localdescriptionifwhich=='e'thendescription=mtext['mt-bad-exponent']elsedescription=mtext['mt-parameter']..whichendreturndescription..' '..(msgormtext['mt-not-number'])..'.'endlocalresult={}localrange=range_types[arg]ifrangetheniftype(index)=='number'and(index%2==0)thenifindex==2thenifnumbers[1]andnumbers[1].expthenreturnfail(mtext['mt-cannot-range'])endnumbers.has_ranges=trueelseifnotnumbers.has_rangesthenreturnfail(mtext['mt-need-range'])endendnumbers[index]=rangeifrange_repeat_unit[arg]then-- Any "repeat" range forces unit (if any) to be repeated for all items.numbers.isrepeat=trueendreturnnilendreturnfail(mtext['mt-not-range'])endifnumbers.has_rangesandtype(index)=='number'and(index%2==0)thenreturnfail(mtext['mt-should-range'])endifindex=='e'thenlocale=numbers[1]andnumbers[1].expifethenifargthenreturnfail(mtext['mt-cannot-with-e'])endarg=ewhich=1endendifargandarg~=''thenarg=arg:gsub(numsep,'')ifnumdot~='.'thenarg=arg:gsub(numdot,'.')endifarg:sub(1,1)=='('andarg:sub(-1)==')'thenresult.parens=truearg=arg:sub(2,-2)endlocala,b=arg:match('^(.+)[Ee](.+)$')ifathenifindex==1thenarg=aresult.exp=belsereturnfail(mtext['mt-cannot-e'])endendlocalisnegative,propersign,prefixlocalminus='−'prefix,arg=arg:match('^(.-)([%d.]+)$')localvalue=tonumber(arg)ifnotvaluethenreturnfail()endifarg:sub(1,1)=='.'thenarg='0'..argendifprefix==''then-- Ignore.elseifprefix=='±'then-- Display for first number, ignore for others.ifindex==1thenpropersign='±'endelseifprefix=='+'thenpropersign='+'elseifprefix=='-'orprefix==minusthenpropersign=minusisnegative=trueelsereturnfail()endresult.clean=argresult.sign=propersignor''result.value=isnegativeand-valueorvalueendnumbers[index]=resultreturnnil-- no errorendlocalfunctionget_args(numbers,args)-- Extract arguments and store the results in numbers.-- Return nothing (no error) if ok; otherwise, return an error message.forindex=1,99dolocalwhich=indexlocalarg=args[which]-- has been trimmedifnotargthenwhich='e'arg=args[which]endlocalmsg=extract_item(which,numbers,arg)ifmsgthenreturnmsgendifwhich=='e'thenbreakendifindex>19thenreturnmtext['mt-too-many-parameter']endendifnumbers.has_rangesand(#numbers%2==0)thenreturnmtext['mt-need-number']endendlocalfunctionget_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'.localn=tonumber(text)ifnthenreturnnendn=text:gsub('pi',math.pi)for_,opinipairs({'/','^'})dolocala,b=n:match('^(.-)'..op..'(.*)$')ifathena=tonumber(a)b=tonumber(b)ifaandbthenifop=='/'thenreturna/belseifop=='^'thenreturna^bendendbreakendenderror(string.format(mtext['mt-invalid-scale'],ucode,text))endlocalfunctionget_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)ifposthenlocalendline=definitions:find('%s*\n',pos)ifendlinethenlocalresult={}localn=0localtext=definitions:sub(pos+1,endline-1):gsub('%s%s+','\t')foritemin(text..'\t'):gmatch('(%S.-)\t')doifitem=='ALIAS'thenresult.alias=trueelseifitem=='ANGLE'thenresult.isangle=trueresult.nospace=trueelseifitem=='NOSPACE'thenresult.nospace=trueelseifitem=='SI'thenresult.si=trueelsen=n+1ifn==1thenlocallink,symbol=item:match('^%[%[([^|]+)|(.+)%]%]$')iflinkthenresult.symbol=symbolresult.link=linkn=2elseresult.symbol=itemendelseifn==2thenresult.link=itemelseifn==3thenresult.scale_text=itemresult.scale=get_scale(item,ucode)elseresult.more_ignored=itembreakendendendifresult.sithenlocals=result.symbolifucode=='mc'..sorucode=='mu'..sthenresult.ucode='µ'..s-- unit code for convert should be thisendendifn>=2or(n>=1andresult.alias)thenreturnresultend-- Ignore invalid definition, treating it as a comment.endendendlocalfunctionconvert_lookup(ucode,value,scaled_top,want_link,si,options)locallookup=require(convert_module)._unitreturnlookup(ucode,{value=value,scaled_top=scaled_top,link=want_link,si=si,sort=options.sortable,})endlocalfunctionget_unit(ucode,value,scaled_top,options)localwant_link=options.want_linkifscaled_topthenwant_link=options.want_per_linkendlocaldata=mw.loadData(data_module)localresult=options.want_longscaleandget_builtin_unit(ucode,data.builtin_units_long_scale)orget_builtin_unit(ucode,data.builtin_units)localsi,use_convertifresultthenifresult.aliasthenucode=result.symboluse_convert=trueendifresult.scalethen-- 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.scaleendifresult.sithenucode=result.ucodeorucodesi={result.symbol,result.link}use_convert=trueendelseresult={}use_convert=trueendlocalconvert_unit=convert_lookup(ucode,value,scaled_top,want_link,si,options)result.sortkey=convert_unit.sortspanifuse_convertthenresult.text=convert_unit.textresult.scaled_top=convert_unit.scaled_valueelseifwant_linkthenresult.text='[['..result.link..'|'..result.symbol..']]'elseresult.text=result.symbolendresult.scaled_top=valueendreturnresultendlocalfunctionmakeunit(value,options)-- Return table of information for the requested unit and options, or-- return nil if no unit.options=optionsor{}localunitlocalucode=options.ulocalpercode=options.perifucodethenunit=get_unit(ucode,value,nil,options)elseifpercodethenunit={nospace=true,scaled_top=value}elsereturnnilendlocaltext=unit.textor''localsortkey=unit.sortkeyifpercodethenlocalfunctionbracketed(code,text)returncode:find('[*./]')and'('..text..')'ortextendlocalperunit=get_unit(percode,1,unit.scaled_top,options)text=(ucodeandbracketed(ucode,text)or'')..'/'..bracketed(percode,perunit.text)sortkey=perunit.sortkeyendifnot(unit.nospaceoroptions.nospace)thentext=' '..textendreturn{text=text,isangle=unit.isangle,sortkey=sortkey}endlocalfunctionlist_units(mode)-- Return wikitext to list the built-in units.-- A unit code should not contain wikimarkup so don't bother escaping.localdata=mw.loadData(data_module)localdefinitions=data.builtin_units..data.builtin_units_long_scalelocallast_was_blank=truelocaln=0localresult={}localfunctionadd(line)ifline==''thenlast_was_blank=trueelseiflast_was_blankandn>0thenn=n+1result[n]=''endlast_was_blank=falsen=n+1result[n]=lineendendlocalsi_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',}localfunctionis_valid(ucode,unit)ifunitandnotunit.more_ignoredthenassert(type(unit.symbol)=='string'andunit.symbol~='')ifunit.aliasthenifunit.linkorunit.scale_textorunit.sithenreturnfalseendendifunit.sithenifunit.scale_textthenreturnfalseenducode=unit.ucodeorucodelocalbase=unit.symbolifucode==basethenunit.display=basereturntrueendlocalplen=#ucode-#baseifplen>0thenlocalprefix=si_prefixes[ucode:sub(1,plen)]ifprefixanducode:sub(plen+1)==basethenunit.display=prefix..basereturntrueendendelseunit.display=unit.symbolreturntrueendendreturnfalseendlocallookup=require(convert_module)._unitlocalfunctionshow_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.ifnot(unit.siorunit.scale_text)thenifmode=='convert'thenunit.show=notlookup(unit.aliasandunit.symbolorucode).unknownunit.show_text='CONVERT'elseifmode=='unknown'thenunit.show=lookup(unit.aliasandunit.symbolorucode).unknownunit.show_text='UNKNOWN'elseifnotunit.aliasthen-- 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.localscale=lookup(ucode,{value=1,sort='on'}).scaled_valueiftype(scale)=='number'thenscale=string.format('%.5g',scale):gsub('e%+?(%-?)0*(%d+)','e%1%2')elsescale='?'endunit.show=trueunit.show_text='['..scale..']'endendendforlineindefinitions:gmatch('([^\n]*)\n')dolocalpos,_=line:find(' ',1,true)ifposthenlocalucode=line:sub(1,pos-1)localunit=get_builtin_unit(ucode,'\n'..line..'\n')ifis_valid(ucode,unit)thenshow_convert(ucode,unit)localflags,textifunit.aliasthentext=unit.symbolelsetext='[['..unit.link..'|'..unit.display..']]'endifunit.isanglethenunit.nospace=nil-- don't show redundant flagendfor_,finipairs({{'alias','ALIAS'},{'isangle','ANGLE'},{'nospace','NOSPACE'},{'si','SI'},{'scale_text',unit.scale_text},{'show',unit.show_text},})doifunit[f[1]]thenlocalt=f[2]ift:match('^%u+$')thent='<small>'..t..'</small>'endifflagsthenflags=flags..' '..telseflags=tendendendifflagsthentext=text..' • '..flagsendadd(ucode..' = '..text..'<br />')elseadd(line..' ◆ <b>invalid definition</b><br />')endelseadd(line)endendreturntable.concat(result,'\n')endlocaldelimit_groups=require('Module:Gapnum').groupslocalfunctiondelimit(sign,numstr,fmt)-- Return sign and numstr (unsigned digits or numdot only) after formatting.-- Four-digit integers are not formatted with gaps.fmt=(fmtor''):lower()iffmt=='none'or(fmt==''and#numstr==4andnumstr:match('^%d+$'))thenreturnsign..numstrend-- Group number by integer and decimal parts.-- If there is no decimal part, delimit_groups returns only one table.localipart,dpart=delimit_groups(numstr)localresultiffmt=='commas'thenresult=sign..table.concat(ipart,numsep)ifdpartthenresult=result..numdot..table.concat(dpart)endelse-- Delimit with a small gap by default.localgroups={}groups[1]=table.remove(ipart,1)for_,vinipairs(ipart)dotable.insert(groups,'<span style="margin-left:.25em;">'..v..'</span>')endifdpartthentable.insert(groups,numdot..(table.remove(dpart,1)or''))for_,vinipairs(dpart)dotable.insert(groups,'<span style="margin-left:.25em;">'..v..'</span>')endendresult=sign..table.concat(groups)endreturnresultendlocalfunctionsup_sub(sup,sub,align)-- Return the same result as Module:Su except val defaults to align=right.ifalign=='l'oralign=='left'thenalign='left'elseifalign=='c'oralign=='center'thenalign='center'elsealign='right'endreturn'<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>'endlocalfunctionrange_text(items,unit_table,options)localfmt=options.fmtlocalnend=items.nendor''ifitems.isrepeatorunit_table.isanglethennend=nend..unit_table.textendlocaltext=''fori=1,#itemsdoifi%2==0thentext=text..items[i]elsetext=text..delimit(items[i].sign,items[i].clean,fmt)..nendendendreturntextendlocalfunctionuncertainty_text(uncertainty,unit_table,options)localangle,text,need_parensifunit_table.isanglethenangle=unit_table.textendlocalupper=uncertainty.upperor{}locallower=uncertainty.loweror{}localuncU=upper.cleanifuncUthenlocalfmt=options.fmtlocaluncL=lower.cleanifuncLthenuncU=delimit('+',uncU,fmt)..(upper.errendor'')uncL=delimit('−',uncL,fmt)..(lower.errendor'')ifanglethenuncU=uncU..angleuncL=uncL..angleendtext=(angleor'')..'<span style="margin-left:0.3em;">'..sup_sub(uncU,uncL,options.align)..'</span>'elseifupper.parensthentext='('..uncU..')'-- old template did not delimitelsetext=(angleor'')..'<span style="margin-left:0.3em;margin-right:0.15em;">±</span>'..delimit('',uncU,fmt)need_parens=trueendifuncertainty.errendthentext=text..uncertainty.errendendifanglethentext=text..angleendendelseifanglethentext=angleendendreturntext,need_parensendlocalfunction_main(values,unit_spec,options)ifoptions.sandboxthendata_module=data_module..'/sandbox'convert_module=convert_module..'/sandbox'endlocalaction=options.actionifactionthenifaction=='list'then-- Kludge: am using the align parameter (a=xxx) for type of list.returnlist_units(options.align)endreturnvalerror('invalid action "'..action..'".',options.nocat)endlocalnumber=values.numberor(values.numbersandvalues.numbers[1])or{}locale_10=options.eor{}localnovalue=(number.value==nilande_10.clean==nil)localfmt=options.fmtlocalwant_sort=truelocalsortable=options.sortableifsortable=='off'or(sortable==nilandnovalue)thenwant_sort=falseelseifsortable=='debug'then-- Same as sortable = 'on' but the sort key is displayed.elsesortable='on'endlocalsort_value=1ifwant_sortthensort_value=number.valueor1ife_10.valueandsort_value~=0then-- The 'if' avoids {{val|0|e=1234}} giving an invalid sort_value due to overflow.sort_value=sort_value*10^e_10.valueendendlocalunit_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,})localsortkeyifunit_tablethenifwant_sortthensortkey=unit_table.sortkeyendelseunit_table={text=''}ifwant_sortthensortkey=convert_lookup('dummy',sort_value,nil,nil,nil,{sortable=sortable}).sortspanendendlocalfinal_unit=unit_table.isangleand''orunit_table.textlocale_text,n_text,need_parenslocaluncertainty=values.uncertaintyifuncertaintythenifnumber.cleanthenn_text=delimit(number.sign,number.clean,fmt)..(number.nendor'')localtexttext,need_parens=uncertainty_text(uncertainty,unit_table,options)iftextthenn_text=n_text..textendelsen_text=''endelseifvalues.numbers.isrepeatthenfinal_unit=''endn_text=range_text(values.numbers,unit_table,options)need_parens=trueendife_10.cleanthenifneed_parensthenn_text='('..n_text..')'ende_text='10<sup>'..delimit(e_10.sign,e_10.clean,fmt)..'</sup>'ifnumber.cleanthene_text='<span style="margin-left:0.25em;margin-right:0.15em;">×</span>'..e_textendelsee_text=''endlocalresult=(sortkeyor'')..(options.prefixor'')..n_text..e_text..final_unit..(options.suffixor'')ifresult~=''thenresult='<span class="nowrap">'..result..'</span>'endreturnresult..(options.warningor'')endlocalfunctioncheck_parameters(args,has_ranges,nocat)-- Return warning text for the first problem parameter found, or nothing if ok.localwhitelist={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,}fork,vinpairs(args)doiftype(k)=='string'andnotwhitelist[k]thenlocalwarning=string.format(mtext['mt-val-not-supported'],k,v)returnvalerror(warning,nocat,true)endendifnothas_rangesandargs[4]thenreturnvalerror(mtext['mt-ignore-parameter4'],nocat,true)endendlocalfunctionmain(frame)localgetArgs=require('Module:Arguments').getArgslocalargs=getArgs(frame,{wrappers={'Template:Val'}})localnocat=args.nocategorylocalnumbers={}-- table of number tables, perhaps with range textlocalmsg=get_args(numbers,args)ifmsgthenreturnvalerror(msg,nocat)endifargs.uandargs.ulthenreturnvalerror(mtext['mt-both-u-ul'],nocat)endifargs.upandargs.uplthenreturnvalerror(mtext['mt-both-up-upl'],nocat)endlocalvaluesifnumbers.has_rangesthen-- Multiple values with range separators but no uncertainty.numbers.nend=args['end']values={numbers=numbers,}else-- A single value with optional uncertainty.localfunctionsetfield(i,dst,src)localv=args[src]ifvthenifnumbers[i]thennumbers[i][dst]=velsenumbers[i]={[dst]=v}endendendsetfield(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,}}endlocalunit_spec={u=args.ulorargs.u,want_link=args.ul~=nil,per=args.uplorargs.up,want_per_link=args.upl~=nil,want_longscale=(args.longscaleorargs.long_scaleorargs['long scale'])=='on',}localoptions={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.sortableor(args.debug=='yes'and'debug'ornil),suffix=args.s,warning=check_parameters(args,numbers.has_ranges,nocat),}return_main(values,unit_spec,options)endreturn{main=main,_main=_main}