core-con.tex / last modification: 2008-11-05 12:33
%D \module
%D   [       file=core-con,
%D        version=1997.26.08,
%D          title=\CONTEXT\ Core Macros,
%D       subtitle=Conversion Macros,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA / Hans Hagen \& Ton Otten}]
%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 Macros}

\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}

\let\romannumerals\gobbleoneargument
\let\Romannumerals\gobbleoneargument

%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

\let\character\gobbleoneargument
\let\Character\gobbleoneargument

%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}

\let\characters\gobbleoneargument
\let\Characters\gobbleoneargument

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

\let\greeknumerals\gobbleoneargument
\let\Greeknumerals\gobbleoneargument

%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

\let\getdayoftheweek\gobblethreearguments
\let\dayoftheweek   \gobblethreearguments

%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{\firstoftwoarguments}
\def\getdayspermonth#1#2{\let\numberofdays\!!zerocount}

%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.

\let\calculatecurrenttime\relax

\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 Plugins:

\loadmarkfile{core-con}

\protect \endinput