core-con.mkii / last modification: 2020-01-30 14:15
%D \module
%D   [       file=core-con,
%D        version=1997.26.08,
%D          title=\CONTEXT\ Core Macros,
%D       subtitle=Conversion,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

\writestatus{loading}{ConTeXt Core Macros / Conversion}

\unprotect

\ifx\currentlanguage\undefined \let\currentlanguage\empty \fi
\ifx\labeltext      \undefined \let\labeltext\firstofoneargument \fi

%D This module deals with all kind of conversions from numbers
%D and dates. I considered splitting this module in a support
%D one and a core one, but to keep things simple as well as
%D preserve the overview, I decided against splitting.

\let\spr\firstofoneargument % separator
\let\stp\firstofoneargument % stopper

% cleaner, some day:
%
% \def\isolateseparators % etex only, even works with list separator overloading
%   {\unexpanded\def\spr##1{{##1}}%
%    \unexpanded\def\stp##1{{##1}}}

% needed for arab :

\def\isolateseparators % even works with list separator overloading
  {\def\spr##1{{##1}}%
   \def\stp##1{{##1}}}

%D \macros
%D   {numbers}
%D
%D First we deal with the dummy conversion of numbers using the
%D \TEX\ primitive \type{\number}. The uppercase alternative is
%D only there for compatibility with the other conversion
%D macros. We could do without \type{#1} but this way we get
%D rid of unwanted braces. For the savety we also define a
%D non||sence uppercase alternative.
%D
%D \showsetup{numbers}
%D
%D \starttyping
%D \def\numbers#1{\number#1}
%D \def\Numbers#1{\number#1}
%D \stoptyping
%D
%D Due to read ahead, as in \type{[\pagenumber\space]} the space will
%D disappear, unless we use:

\def\numbers#1{\purenumber{#1}}
\def\Numbers#1{\purenumber{#1}}

%D \macros
%D   {romannumerals,Romannumerals}
%D
%D \TEX\ the program uses a rather tricky conversion from
%D numbers to their roman counterparts. This conversion could
%D of course be programmed in \TEX\ itself, but I guess Knuth
%D found the programming trick worth presenting.
%D
%D \showsetup{romannumerals}
%D \showsetup{Romannumerals}

%D When upcasing the result, we just follow the text book rules
%D of expansion. Later on we'll see some more uppercase tricks.

\def\romannumerals#1%
  {\romannumeral#1}

%D For some years we had \unknown
%D
%D \starttyping
%D \def\Romannumerals#1%
%D   {\uppercase\expandafter{\romannumeral#1}}
%D \stoptyping
%D
%D \unknown but we need to be fully expandable in order to get
%D the utility output file right, so now we have the following
%D solution. It was Patrick Gundlach who first noticed this
%D ommision.

\def\Romannumerals#1%
  {\expandafter\doRomannumerals\number#1\relax}

\def\doRomannumerals#1#2\relax % spaces after ifcase prevent \relax
  {\ifnum#1#2<10
     \ifcase0#1#2 \or I\or II\or III\or IV\or V\or VI\or VII\or VIII\or IX\fi
   \else\ifnum#1#2<100
     \ifcase0#1   \or X\or XX\or XXX\or XL\or L\or LX\or LXX\or LXXX\or XC\fi
     \doRomannumerals#2\relax
   \else\ifnum#1#2<1000
     \ifcase0#1   \or C\or CC\or CCC\or CD\or D\or DC\or DCC\or DCCC\or CM\fi
     \doRomannumerals#2\relax
   \else\ifnum#1#2<4000
     \ifcase0#1   \or M\or MM\or MMM\fi
     \doRomannumerals#2\relax
   \else
     \uppercase\expandafter{\romannumeral#1#2}%
   \fi\fi\fi\fi}

%D \macros
%D   {character,Character}
%D
%D Converting a number into a character can of course only
%D be done with numbers less or equal to~26. At the cost of
%D much more macros a faster conversion is possible, using:
%D
%D \starttyping
%D \setvalue{char1}{a} \def\character#1{\getvalue{char#1}}
%D \stoptyping
%D
%D But we prefer a simpel \type{\case}.
%D
%D \showsetup{character}
%D \showsetup{Character}

\def\unknowncharacter{-} % else in lists \relax

%D Big case statements but pretty fast:

\def\character#1%
  {\ifcase#1\unknowncharacter
     \or a\or b\or c\or d\or e\or f\or g\or h\or i\or j\or k\or l\or m%
     \or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or x\or y\or z%
   \else
     \unknowncharacter
   \fi}

\def\Character#1%
  {\ifcase#1\unknowncharacter
     \or A\or B\or C\or D\or E\or F\or G\or H\or I\or J\or K\or L\or M%
     \or N\or O\or P\or Q\or R\or S\or T\or U\or V\or W\or X\or Y\or Z%
   \else
     \unknowncharacter
   \fi}

%D \macros
%D   {characters,Characters}
%D
%D Converting large numbers is supported by the next two
%D macros. This time we just count on: $\cdots$~x, y, z, aa,
%D ab, ac~$\cdots$.
%D
%D \showsetup{characters}
%D \showsetup{Characters}

%D The fully expandable alternative:

\def\dodoconvertcharacters#1#2#3%
  {\ifcase#3\else
     \ifnum#3>#1
       \expandafter\doconvertcharacters\expandafter#2\expandafter{\the\numexpr(#3+12)/#1-1\relax}%
       \expandafter#2\expandafter{\the\numexpr#3-((#3+12)/#1-1)*#1\relax}%
     \else
       \expandafter#2\expandafter{\number#3}%
     \fi
   \fi}

\def\doconvertcharacters{\dodoconvertcharacters{26}}

\def\characters{\doconvertcharacters\character}
\def\Characters{\doconvertcharacters\Character}

%D \macros
%D   {greeknumerals,Greeknumerals}
%D
%D Why should we only honour the romans, and not the greek?

\def\greeknumerals#1%
  {% no longer needed: \mathematics
     {\ifcase#1\unknowncharacter\or
        \alpha      \or \beta  \or \gamma   \or \delta   \or
        \varepsilon \or \zeta  \or \eta     \or \theta   \or
        \iota       \or \kappa \or \lambda  \or \mu      \or
        \nu         \or \xi    \or \omicron \or \pi      \or
        \varrho     \or \sigma \or \tau     \or \upsilon \or
        \phi        \or \chi   \or \psi     \or \omega
      \else
        \unknowncharacter
      \fi}}

\def\Greeknumerals#1%
  {% no longer needed: \mathematics
     {\ifcase#1\unknowncharacter \or
        \Alpha   \or \Beta  \or \Gamma   \or \Delta   \or
        \Epsilon \or \Zeta  \or \Eta     \or \Theta   \or
        \Iota    \or \Kappa \or \Lambda  \or \Mu      \or
        \Nu      \or \Xi    \or \Omicron \or \Pi      \or
        \Rho     \or \Sigma \or \Tau     \or \Upsilon \or
        \Phi     \or \Xi    \or \Psi     \or \Omega
      \else
        \unknowncharacter
      \fi}}

%D \macros
%D   {oldstylenumerals,oldstyleromannumerals}
%D
%D These conversions are dedicated to Frans Goddijn.

\unexpanded\def\oldstylenumerals#1%
  {{\os\number#1}}

\unexpanded\def\oldstyleromannumerals#1%
  {{\leftrulefalse\rightrulefalse\ss\txx\boxrulewidth.15ex
    \ruledhbox spread .15em{\hss\uppercased{\romannumerals{#1}}\hss}}}

%D \macros
%D   {protectconversion}
%D
%D The previous two commands are not robust enough to be
%D passed to \type{\write} en \type{\message}. That's why we
%D introduce:

\def\protectconversion
  {\def\doconvertcharacters##1{##1}} % was \relax
 %{\def\doconvertcharacters##1{\ifcase0##1 0\else##1\fi}} more save

%D \macros
%D   {normaltime,normalyear,normalmonth,normalday}
%D
%D The last part of this module is dedicated to converting
%D dates. Because we want to use as meaningful commands as
%D possible, and because \TEX\ already uses up some of those,
%D we save the original meanings.

\savenormalmeaning\time
\savenormalmeaning\year
\savenormalmeaning\month
\savenormalmeaning\day

%D \macros
%D   {month,MONTH}
%D
%D Converting the month number into a month name is done
%D using a case statement, abstact values and the label
%D mechanism. This way users can easily redefine a label from
%D for instance german into austrian.
%D
%D \starttyping
%D \setuplabeltext [de] [january=J\"anner]
%D \stoptyping
%D
%D Anyhow, the conversion looks like:

\def\domonthtag#1%
  {\ifcase#1%
     \or \v!january   \or \v!february \or \v!march    \or \v!april
     \or \v!may       \or \v!june     \or \v!july     \or \v!august
     \or \v!september \or \v!october  \or \v!november \or \v!december
   \else
     \v!unknown
   \fi}

\def\doconvertmonthlong #1{\labeltext{\domonthtag{#1}}}
\def\doconvertmonthshort#1{\labeltext{\domonthtag{#1}:\s!mnem}}

\let\doconvertmonth\doconvertmonthlong

%D We redefine the \TEX\ primitive \type{\month} as:
%D
%D \showsetup{month}
%D \showsetup{MONTH}

\def\monthlong {\doconvertmonthlong}
\def\monthshort{\doconvertmonthshort}
\def\month     {\doconvertmonth}

\def\MONTH     #1{{\let\labeltext\LABELTEXT\month     {#1}}}
\def\MONTHLONG #1{{\let\labeltext\LABELTEXT\monthlong {#1}}}
\def\MONTHSHORT#1{{\let\labeltext\LABELTEXT\monthshort{#1}}}

%D We never explicitly needed this, but Tobias Burnus pointed
%D out that it would be handy to convert to the day of the
%D week. In doing so, we have to calculate the total number of
%D days, taking leapyears into account. For those who are
%D curious:
%D
%D \startitemize[packed]
%D \item  years that can be divided by 4 are leapyears
%D \item  exept years that can be divided by 100
%D \item  unless years can be divided by 400
%D \stopitemize
%D
%D This makes the year 1900 into a normal year and 1996 and
%D 2000 into leap years, right? Well, converting to string
%D looks familiar:

\def\doconvertday#1%
  {\labeltext
     {\ifcase#1
      \or \v!sunday   \or \v!monday \or \v!tuesday  \or \v!wednesday
      \or \v!thursday \or \v!friday \or \v!saturday \fi}}

%D \macros
%D   {getdayoftheweek, dayoftheweek}
%D
%D The conversion algoritm is an old one and a translation from
%D a procedure written in MODULA~2 back in the 80's. I finaly
%D found the 4--100-400 rules in some enclopedia. Look at this
%D messy low level routine that takes the day, month and year
%D as arguments:

\newcount\normalweekday

\def\getdayoftheweek#1#2#3%
  {\bgroup
   \!!counta#3\relax
   \advance\!!counta \minusone
   \!!countb\!!counta
   \multiply\!!countb 365
   \advance\!!countb \ifcase#2\relax
     0 \or  0 \or 31 \or 59 \or 90 \or120 \or151 \or
   181 \or212 \or243 \or273 \or304 \or334 \or365 \fi
   \advance\!!countb #1\relax
   \ifnum#2>2
     \doifleapyearelse{#3}{\advance\!!countb 1}{}\relax
   \fi
   \!!countc\!!counta
   \dosetdivision\!!countc4\!!countc
   \advance\!!countb \!!countc
   \!!countc\!!counta
   \dosetdivision\!!countc{100}\!!countc
   \advance\!!countb -\!!countc
   \!!countc\!!counta
   \dosetdivision\!!countc{400}\!!countc
   \advance\!!countb \!!countc
   \dosetmodulo\!!countb7\!!countb
   \advance\!!countb \plusone
   \@EA\egroup\@EA\normalweekday\the\!!countb\relax}

\def\dayoftheweek#1#2#3%
  {\getdayoftheweek{#1}{#2}{#3}\doconvertday{\normalweekday}}

%D Using this macro in
%D
%D \startbuffer
%D monday:   \dayoftheweek  {4} {5} {1992}
%D friday:   \dayoftheweek {16} {6} {1995}
%D monday:   \dayoftheweek {25} {8} {1997}
%D saturday: \dayoftheweek {30} {8} {1997}
%D tuesday:  \dayoftheweek  {2} {1} {1996}
%D tuesday:  \dayoftheweek  {7} {1} {1997}
%D tuesday:  \dayoftheweek {13} {1} {1998}
%D friday:   \dayoftheweek  {1} {1} {2000}
%D \stopbuffer
%D
%D \typebuffer
%D
%D gives
%D
%D \startvoorbeeld
%D \startlines
%D \getbuffer
%D \stoplines
%D \stopvoorbeeld
%D
%D The macro \type {\getdayoftheweek} can be used to calculate
%D the number \type {\normalweekday}.

%D \macros
%D   {weekday,WEEKDAY}
%D
%D The first one is sort of redundant. It takes the day
%D number argument.
%D
%D \showsetup{weekday}
%D \showsetup{WEEKDAY}

\def\weekday
  {\doconvertday}

\def\WEEKDAY#1%
  {{\let\labeltext\LABELTEXT\doconvertday{#1}}}

%D \macros
%D   {weekoftheday}
%D
%D {\em not yet implemented:}
%D
%D \starttyping
%D \def\weekoftheday#1#2#3%
%D  {}
%D \stoptyping

%D \macros
%D   {doifleapyearelse,
%D    getdayspermonth}
%D
%D Sometimes we need to know if we're dealing with a
%D leapyear, so here is a testmacro:
%D
%D \starttyping
%D \doifleapyearelse{year}{yes}{no}
%D \stoptyping
%D
%D An example of its use can be seen in the macro
%D
%D \starttyping
%D \getdayspermonth{year}{month}
%D \stoptyping
%D
%D The number of days is available in the macro \type
%D {\numberofdays}.

\def\doifleapyearelse#1% #2#3%
  {\bgroup
   \!!doneafalse
   \!!counta#1%
   \dosetmodulo\!!counta4\!!countb
   \ifcase\!!countb
     \dosetmodulo\!!counta{100}\!!countb
     \ifcase\!!countb \else \!!doneatrue \fi
     \dosetmodulo\!!counta{400}\!!countb
     \ifcase\!!countb \!!doneatrue \fi
   \fi
   \if!!donea
     \egroup\@EA\firstoftwoarguments  % \def\next{#2}%
   \else
     \egroup\@EA\secondoftwoarguments % \def\next{#3}%
   \fi}                               % \next}

% untested but cleaner:
%
% \def\doifleapyearelse#1% #2#3%
%   {\bgroup
%    \dosetmodulo{#1}{400}\scratchcounter
%    \ifcase\scratchcounter
%    \else
%      \dosetmodulo{#1}{100}\scratchcounter
%      \ifcase\scratchcounter
%        \scratchcounter\plusone
%      \else
%        \dosetmodulo{#1}4\scratchcounter
%      \fi
%    \fi
%    \ifcase\scratchcounter
%      \egroup\@EA\firstoftwoarguments
%    \else
%      \egroup\@EA\secondoftwoarguments
%    \fi}

\def\getdayspermonth#1#2%
  {\doifleapyearelse{#1}
     {\def\numberofdays{29}}
     {\def\numberofdays{28}}%
   \edef\numberofdays
     {\ifcase#2 \or31\or\numberofdays\or31\or30\or
        31\or30\or31\or31\or30\or31\or30\or31\fi}}

%D \macros
%D   {currentdate, date}
%D
%D We use these conversion macros in the date formatting
%D macro:
%D
%D \showsetup{currentdate}
%D
%D This macro takes care of proper spacing and delivers for
%D instance:
%D
%D \startbuffer
%D \currentdate[weekday,day,month,year] % still dutch example
%D \currentdate[WEEKDAY,day,MONTH,year] % still dutch example
%D \stopbuffer
%D
%D \startvoorbeeld
%D \startlines
%D \getbuffer
%D \stoplines
%D \stopvoorbeeld
%D
%D depending of course on the keywords. Here we gave:
%D
%D \typebuffer
%D
%D If needed one can also add non||keywords, like in
%D
%D \startbuffer
%D \currentdate[dd,--,mm,--,yy]
%D \stopbuffer
%D
%D \typebuffer
%D
%D or typeset: \getbuffer.
%D
%D When no argument is passed, the current date is given as
%D specified per language (using \type{\installlanguage}).
%D
%D \showsetup{currentdate}
%D
%D \startbuffer
%D \date
%D \date[d=12,m=12,y=1998][weekday]
%D \date[d=12,m=12,y=1998]
%D \stopbuffer
%D
%D We can also typeset arbitrary dates, using the previous
%D command.
%D
%D \typebuffer
%D
%D The date is specified by one character keys. When no date
%D is given, we get the current date.
%D
%D \startlines
%D \getbuffer
%D \stoplines

\def\kenmerkdatumpatroon{j,mm,dd} % jj,mm,dd changed at januari 1-1-2000

\newsignal\datesignal

\def\dobetweendates
  {\ifdim\lastskip=\datesignal\relax\else
     \unskip\space
     \hskip\datesignal\relax
   \fi}

\newtoks \everycurrentdate

\def\complexcurrentdate[#1]%
  {\bgroup
   \the\everycurrentdate
   \def\betweendates{\let\betweendates\dobetweendates}%
   % was \processcommacommandp[#1]\docomplexcurrentdate
   \safeedef\ascii{\empty#1}% keep encoded chars
   \@EA\processcommalist\@EA[\ascii]\docomplexcurrentdate
   \ifdim\lastskip=\datesignal\relax
     \unskip
   \fi
   \egroup}

\def\docomplexcurrentdate#1%
  {\lowercase{\edef\!!stringa{#1}}% permits usage in \smallcapped
   \expanded{\processaction[\!!stringa]}% [#1]
     [    \v!day=>\betweendates\the\normalday,
        %\v!day+=>\betweendates\ordinaldaynumber\normalday,
         \v!day+=>\betweendates\convertnumber{\v!day+}\normalday,
        \v!month=>\betweendates\month\normalmonth,
         \v!year=>\betweendates\the\normalyear,
        \v!space=>\unskip\ \hskip\datesignal,% optimization -)
              \ =>\unskip\ \hskip\datesignal,% optimization -)
               d=>\convertnumber\v!day\normalday,
             %d+=>\ordinaldaynumber\normalday,
              d+=>\convertnumber{\v!day+}\normalday,
               m=>\convertnumber\v!month\normalmonth,
               j=>\convertnumber\v!year\normalyear,
               y=>\convertnumber\v!year\normalyear,
               w=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear,
              dd=>\ifnum\normalday  >9 \else0\fi\the\normalday,
            %dd+=>\ordinaldaynumber{\ifnum\normalday >9 \else0\fi\the\normalday},
             dd+=>\convertnumber{\v!day+}{\ifnum\normalday >9 \else0\fi\the\normalday},
              mm=>\ifnum\normalmonth>9 \else0\fi\the\normalmonth,
              jj=>\expandafter\gobbletwoarguments\the\normalyear,
              yy=>\expandafter\gobbletwoarguments\the\normalyear,
      \v!weekday=>\betweendates\dayoftheweek\normalday\normalmonth\normalyear,
     \v!referral=>\expanded{\complexcurrentdate[\kenmerkdatumpatroon]},
      \s!unknown=>\unskip
                  % #1 and not the lowercased \commalistelement, vietnamese has text
                  % {} because #1 can have comma, like: {\ ,}
                  {#1}%
                  \hskip\datesignal
                  \def\betweendates{\let\betweendates\dobetweendates}]}

\def\simplecurrentdate
  {\expanded{\complexcurrentdate[\currentdatespecification]}}

\definecomplexorsimple\currentdate

\def\dodate[#1][#2]%
  {\bgroup
   \iffirstargument
     \getparameters[\??da][d=\normalday,m=\normalmonth,y=\normalyear,#1]%
     \normalday  \@@dad\relax
     \normalmonth\@@dam\relax
     \normalyear \@@day\relax
     \ifsecondargument
       \currentdate[#2]%
     \else
       \currentdate
     \fi
   \else
     \currentdate
   \fi
   \egroup}

\def\date
  {\dodoubleempty\dodate}

%D \macros
%D   {currenttime}
%D
%D The currenttime is actually the jobtime. You can specify
%D a pattern similar to the previous date macro using the
%D keys \type {h}, \type {m} and a separator.

\def\calculatecurrenttime
  {\dosetdivision\time{60}\scratchcounter
   \edef\currenthour  {\ifnum\scratchcounter<10 0\fi \the\scratchcounter}%
   \dosetmodulo  \time{60}\scratchcounter
   \edef\currentminute{\ifnum\scratchcounter<10 0\fi \the\scratchcounter}}

\let\currenthour  \!!plusone
\let\currentminute\!!plusone

\def\currenttimespecification{h,:,m}

\def\complexcurrenttime[#1]%
  {\calculatecurrenttime
   \processallactionsinset[#1]
     [h=>\currenthour,m=>\currentminute,\s!unknown=>\commalistelement]}

\def\simplecurrenttime
  {\expanded{\complexcurrenttime[\currenttimespecification]}}

\definecomplexorsimple\currenttime

%D Because we're dealing with dates, we also introduce a few
%D day loops:
%D
%D \starttyping
%D \processmonth{year}{month}{command}
%D \processyear{year}{command}{before}{after}
%D \stoptyping
%D
%D The counters \type {\normalyear}, \type {\normalmonth} and
%D \type{\normalday} can be used for for date manipulations.

\long\def\processmonth#1#2#3% year month command
  {\bgroup
   \getdayspermonth{#1}{#2}%
   \dostepwiserecurse1\numberofdays1%
     {\normalyear #1\relax
      \normalmonth#2\relax
      \normalday  \recurselevel\relax
      #3}%
   \egroup}

\def\lastmonth{12} % can be set to e.g. 1 when testing

\long\def\processyear#1#2#3#4% year command before after
  {\bgroup
   \dorecurse\lastmonth
     {\normalyear #1\relax
      \normalmonth\recurselevel\relax
      #3\processmonth\normalyear\normalmonth{#2}#4}%
   \egroup}

%D \macros
%D   {defineconversion, convertnumber}
%D
%D Conversion involves the macros that we implemented earlier
%D in this module.
%D
%D \showsetup{defineconversion}
%D \showsetup{convertnumber}
%D
%D We can feed this command with conversion macros as well as
%D a set of conversion symbols. Both need a bit different
%D treatment.
%D
%D \starttyping
%D \defineconversion [roman] [\romannumerals]
%D \defineconversion [set 1] [$\star$,$\bullet$,$\ast$]
%D \stoptyping
%D
%D You can define a language dependent conversion with:
%D
%D \starttyping
%D \defineconversion [en] [whatever] [\something]
%D \stoptyping

% \def\dodefineconversion[#1][#2]%
%   {\ConvertConstantAfter\doifinstringelse{,}{#2}
%      {\scratchcounter=0
%       \def\docommand##1%
%         {\advance\scratchcounter 1
%          \setvalue{\??cv#1\the\scratchcounter}{##1}}%
%       \processcommalist[#2]\docommand
%       \setvalue{\??cv#1}##1{\csname\??cv#1##1\endcsname}}
%      {\setvalue{\??cv#1}{#2}}}
%
% \def\defineconversion%
%   {\dodoubleargument\dodefineconversion}

\def\defineconversion
  {\dotripleempty\dodefineconversion}

\def\dodefineconversion[#1][#2][#3]%
  {\ifthirdargument
     \dododefineconversion[#1][#2][#3]%
   \else
     \dododefineconversion[][#1][#2]%
   \fi}

%D \starttyping
%D \def\dododefineconversion[#1][#2][#3]%
%D   {\ConvertConstantAfter\doifinstringelse{,}{#3}
%D      {\scratchcounter\zerocount
%D       \def\docommand##1%
%D         {\advance\scratchcounter \plusone
%D          \setvalue{\??cv#1#2\the\scratchcounter}{##1}}%
%D       \processcommalist[#3]\docommand
%D       \setvalue{\??cv#1#2}##1{\executeifdefined{\??cv#1#2##1}\unknown}} % catch out-of-range numbers
%D      {\setvalue{\??cv#1#2}{#3}}}
%D \stoptyping

%D This approach has the disadvantage that when you run out of
%D symbols you get unknown results. The following implementation
%D permits overloading of the converter:

\def\dododefineconversion[#1][#2][#3]%
  {\ConvertConstantAfter\doifinstringelse{,}{#3}
     {\scratchcounter\zerocount
      \def\docommand##1%
        {\advance\scratchcounter \plusone
         \setvalue{\??cv#1#2\the\scratchcounter}{##1}}%
      \processcommalist[#3]\docommand
      \setevalue{\??cv#1#2}##1%
        {\noexpand\docheckedconversion{#1#2}{\the\scratchcounter}{##1}}}
     {\setvalue{\??cv#1#2}{#3}}}

\def\docheckedconversion#1#2#3% class maxnumber number
  {\executeifdefined{\??cv#1#3}\unknown}

%D When Gerben reported problems with footnote numbering per page,
%D Taco came with the following wrap around solution. So, let's
%D overload the checked conversion macro:

\def\docheckedconversion#1#2#3% class maxnumber number
  {\executeifdefined{\??cv#1\modulatednumber{#2}{#3}}\unknown}

%D Taco's modulo code is implemented in the system module
%D \type {syst-con}.

%D If a conversion is just a font switch then we need to make sure
%D that the number is indeed end up as number in the input, so we
%D need to handle the second argument.

\def\convertnumber#1#2%
  {\csname\??cv
     \ifcsname\??cv\currentlanguage#1\endcsname
       \currentlanguage#1%
     \else\ifcsname\??cv#1\endcsname
       #1%
     \else
       \s!default
     \fi\fi
   \endcsname{\number#2}}

\def\doifconversiondefinedelse#1%
  {\ifcsname\??cv\currentlanguage#1\endcsname
     \@EA\firstoftwoarguments
   \else\ifcsname\??cv#1\endcsname
     \@EAEAEA\firstoftwoarguments
   \else
     \@EAEAEA\secondoftwoarguments
   \fi\fi}

\def\doifelseconversionnumber#1#2% slow but seldom used
  {\doifdefinedelse{\??cv#1#2}}

%D Handy.

\setvalue{\??cv:\c!n:\v!one  }{1}
\setvalue{\??cv:\c!n:\v!two  }{2}
\setvalue{\??cv:\c!n:\v!three}{3}
\setvalue{\??cv:\c!n:\v!four }{4}
\setvalue{\??cv:\c!n:\v!five }{5}

\def\wordtonumber#1#2{\ifcsname\??cv:\c!n:#1\endcsname\csname\??cv:\c!n:#1\endcsname\else#2\fi}

% \defineconversion[ctx][c,o,n,t,e,x,t]
%
% \doloop{\doifelseconversionnumber{ctx}{\recurselevel}{[\recurselevel]}{\exitloop}}

\defineconversion [\s!default] [\numbers]

%D As longs as symbols are linked to levels or numbers, we can
%D also use the conversion mechanism, but in for instance the
%D itemization macros, we prefer symbols because they can more
%D easier be (partially) redefined. Symbols are implemented
%D in another module.

\defineconversion []   [\numbers] % the default conversion

\defineconversion [a]  [\characters]
\defineconversion [A]  [\Characters]
\defineconversion [AK] [\smallcapped\characters]
\defineconversion [KA] [\smallcapped\characters]

\defineconversion [n]  [\numbers]
\defineconversion [N]  [\Numbers]
\defineconversion [m]  [\mediaeval]

\defineconversion [i]  [\romannumerals]
\defineconversion [I]  [\Romannumerals]
\defineconversion [r]  [\romannumerals]
\defineconversion [R]  [\Romannumerals]
\defineconversion [KR] [\smallcapped\romannumerals]
\defineconversion [RK] [\smallcapped\romannumerals]

\defineconversion [g]  [\greeknumerals]
\defineconversion [G]  [\Greeknumerals]

\defineconversion [o]  [\oldstylenumerals]
\defineconversion [O]  [\oldstylenumerals]
\defineconversion [or] [\oldstyleromannumerals]

\defineconversion [\v!character]     [\character]
\defineconversion [\v!Character]     [\Character]

\defineconversion [\v!characters]    [\characters]
\defineconversion [\v!Characters]    [\Characters]

\defineconversion [\v!numbers]       [\numbers]
\defineconversion [\v!Numbers]       [\Numbers]
\defineconversion [\v!mediaeval]     [\mediaeval]

\defineconversion [\v!romannumerals] [\romannumerals]
\defineconversion [\v!Romannumerals] [\Romannumerals]

\defineconversion [\v!greek]         [\greeknumerals]
\defineconversion [\v!Greek]         [\Greeknumerals]

\defineconversion [arabicnumerals]   [\arabicnumerals]
\defineconversion [persiannumerals]  [\arabicnumerals]

\defineconversion [month]            [\doconvertmonthlong]
\defineconversion [month:mnem]       [\doconvertmonthshort]

% Some bonus ones:

\defineconversion [\v!empty]         [\gobbleoneargument]
\defineconversion [\v!none]          [\numbers]

\ifx\symbol\undefined \def\symbol[#1]{#1} \fi % todo

\defineconversion
  [set 0]
  [{\symbol[bullet]},
   {\symbol[dash]},
   {\symbol[star]},
   {\symbol[triangle]},
   {\symbol[circle]},
   {\symbol[medcircle]},
   {\symbol[bigcircle]},
   {\symbol[square]}]

\defineconversion
  [set 1]
  [\mathematics{\star},
   \mathematics{\star\star},
   \mathematics{\star\star\star},
   \mathematics{\ddagger},
   \mathematics{\ddagger\ddagger},
   \mathematics{\ddagger\ddagger\ddagger},
   \mathematics{\ast},
   \mathematics{\ast\ast},
   \mathematics{\ast\ast\ast}]

\defineconversion
  [set 2]
  [\mathematics{*},
   \mathematics{\dag},
   \mathematics{\ddag},
   \mathematics{**},
   \mathematics{\dag\dag},
   \mathematics{\ddag\ddag},
   \mathematics{***},
   \mathematics{\dag\dag\dag},
   \mathematics{\ddag\ddag\ddag},
   \mathematics{****},
   \mathematics{\dag\dag\dag\dag},
   \mathematics{\ddag\ddag\ddag\ddag}]

\defineconversion
  [set 3]
  [\mathematics{\star},
   \mathematics{\star\star},
   \mathematics{\star\star\star},
   \mathematics{\ddagger},
   \mathematics{\ddagger\ddagger},
   \mathematics{\ddagger\ddagger\ddagger},
   \mathematics{\P},
   \mathematics{\P\P},
   \mathematics{\P\P\P},
   \mathematics{\S},
   \mathematics{\S\S},
   \mathematics{\S\S\S},
   \mathematics{\ast},
   \mathematics{\ast\ast},
   \mathematics{\ast\ast\ast}]

%D \macros
%D   {defineconversionvector,conversionnumber} % bad names so no danger for clash
%D
%D For Adam and friends \unknown
%D
%D \startitemize[persiannummerals]
%D \item test \item test \item test \item test
%D \stopitemize

\def\defineconversionvector#1#2% name base
  {\bgroup
   % dirty trick
   \uccode`\*=`\1
   % plain:
   % \uccode`\0=\numexpr#2+0\relax \uccode`\1=\numexpr#2+1\relax
   % \uccode`\2=\numexpr#2+2\relax \uccode`\3=\numexpr#2+3\relax
   % \uccode`\4=\numexpr#2+4\relax \uccode`\5=\numexpr#2+5\relax
   % \uccode`\6=\numexpr#2+6\relax \uccode`\7=\numexpr#2+7\relax
   % \uccode`\8=\numexpr#2+8\relax \uccode`\9=\numexpr#2+9\relax
   % context:
   \dostepwiserecurse091{\expandafter\uccode\expandafter`\recurselevel=\numexpr#2+\recurselevel}%
   % prepared macro
   \uppercase\expandafter{\expandafter\gdef\csname::cvn::#1::\endcsname##*%
     {\ifcase##* 0\or1\or2\or3\or4\or5\or6\or7\or8\or9\fi}}%
   \egroup}

\def\conversionnumber#1#2%
  {\ifcsname::cvn::#1::\endcsname
     \expandafter\doconversionnumber\csname::cvn::#1::\expandafter\endcsname\number#2\relax
   \else
      \number#2%
   \fi}

\def\doconversionnumber#1#2%
  {\ifx#2\relax
     \expandafter\gobbleoneargument
   \else
     #1{#2}%
     \expandafter\doconversionnumber
   \fi#1}

% actually mkiii code

\ifnum\texengine=\xetexengine

    \defineconversionvector{arabicnumerals}    {"0660}
    \defineconversionvector{persiannumerals}   {"06F0}
    \defineconversionvector{thainumerals}      {"0E50}
    \defineconversionvector{devanagarinumerals}{"0966}
    \defineconversionvector{gurmurkhinumerals} {"0A66}
    \defineconversionvector{gujaratinumerals}  {"0AE6}
    \defineconversionvector{tibetannumerals}   {"0F20}  % also "half numerals?"

    \defineconversion[arabicnumerals]    [\conversionnumber{arabicnumerals}]
    \defineconversion[persiannumerals]   [\conversionnumber{persiannumerals}]
    \defineconversion[thainumerals]      [\conversionnumber{thainumerals}]
    \defineconversion[devanagarinumerals][\conversionnumber{devanagarinumerals}]
    \defineconversion[gurmurkhinumerals] [\conversionnumber{gurmurkhinumerals}]
    \defineconversion[gujaratinumerals]  [\conversionnumber{gujaratinumerals}]
    \defineconversion[tibetannumerals]   [\conversionnumber{tibetannumerals}]

\fi

\protect \endinput