%D \module %D [ file=supp-fil, %D version=1995.10.10, %D title=\CONTEXT\ Support Macros, %D subtitle=Files, %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. %D \TEX\ operates on files, so one wouldn't wonder that there %D is a separate module for file support. In \CONTEXT\ files %D are used for several purposes: %D %D \startitemize[packed] %D \item general textual input %D \item logging status information %D \item saving registers, lists and references %D \item buffering defered textual input %D \stopitemize %D %D When dealing with files we can load them as a whole, using %D the \type{\input} primitive or load them on a line||by||line %D basis, using \type{\read}. Writing is always done line by %D line, using \type{\write}. \writestatus{loading}{ConTeXt Support Macros / Files} \unprotect \ifx\undefined\f!pathseparator \def\f!pathseparator{/} \def\f!currentpath {.} \def\f!parentpath {..} \fi \ifnum\texengine=\luatexengine \def\openinputfile #1#2{\openin #1{#2}\relax} \def\openoutputfile#1#2{\immediate\openout#1{#2}\relax} \else \def\openinputfile #1#2{\openin #1 "#2"\relax} \def\openoutputfile#1#2{\immediate\openout#1 "#2"\relax} \fi \def\closeinputfile #1{\immediate\closein #1} \def\closeoutputfile#1{\immediate\closeout#1} %D \macros %D {pushendofline,popendofline} %D %D When we are loading files in the middle of the typesetting %D process, for instance when we load references, we have to be %D sure that the reading process does not generate so called %D 'spurious spaces'. This can be prevented by assigning the %D line ending character the \CATCODE\ comment. This is %D accomplished by %D %D \starttyping %D \pushendofline %D ... reading ... %D \popendofline %D \stoptyping %D %D Just to be sure, we save the current meaning of \type{^^M} %D in \type{\poppedendofline}. % \chardef\poppedendofline\catcode`\^^M % % \def\pushendofline % {\chardef\poppedendofline\catcode`\^^M\relax % \catcode`\^^M\@@comment\relax} % % \def\popendofline % {\catcode`\^^M\poppedendofline} % % support for nested usage: \newcount \endoflinelevel \ifx\newlinecode\undefined \chardef\newlinecode=`\^^M \fi \def\pushendofline {\advance\endoflinelevel\plusone \expandafter\chardef\csname :eol:\number\endoflinelevel\endcsname\catcode\newlinecode \catcode\newlinecode\@@comment\relax} \def\popendofline {\catcode\newlinecode\csname :eol:\number\endoflinelevel\endcsname \advance\endoflinelevel\minusone} \def\restoreendofline {\catcode\newlinecode\@@endofline} %D \macros %D {scratchread, scratchwrite} %D %D We define a scratch file for reading. Keep in mind that %D the number of files is limited to~16, so use this one when %D possible. We also define a scratch output file. \ifx\undefined\scratchread \newread \scratchread \fi \ifx\undefined\scratchwrite \newwrite\scratchwrite \fi %D \macros %D {unlinkfile} %D %D Sometimes we want to make sure a file is deleted, so here %D is a macro that does the job. It's named after the \PERL\ %D one. \def\unlinkfile#1% {\openoutputfile \scratchwrite{#1}% \closeoutputfile\scratchwrite} %D \macros %D {writeln} %D %D This saves a few tokens: \def\writeln#1{\write#1{}} \def\doiffileexistselse#1% {\doifelsenothing{#1} {\secondoftwoarguments} {\openinputfile\scratchread{#1}% \ifeof\scratchread \closeinputfile\scratchread \expandafter\secondoftwoarguments \else \closeinputfile\scratchread \expandafter\firstoftwoarguments \fi}} %D \macros %D {doprocessfile,fileline,fileprocessedtrue,dofinishfile} %D %D The next macro offers a framework for processing files on a %D line by line basis. %D %D \starttyping %D \doprocessfile \identifier {name} \action %D \stoptyping %D %D The first argument can for instance be \type{\scratchread}. %D The action must do something with \type{\fileline}, which %D holds the current line. One can halfway step out using %D \type{\dofinishfile} and ise \type{\iffileprocessed} to %D see if indeed some content was found. \newif\iffileprocessed \let\fileline\empty \def\doprocessfile#1#2#3% {\openinputfile{#1}{#2}% \ifeof#1% \fileprocessedfalse \closeinputfile#1% \else \fileprocessedtrue \gdef\dofinishfile {\closeinputfile#1% \global\let\doprocessline\relax}% \gdef\doprocessline {\ifeof#1% \expandafter\dofinishfile \else \global\read#1 to \fileline #3\relax \expandafter\doprocessline \fi}% \expandafter\doprocessline \fi} %D \macros %D {pathplusfile,assignfullfilename,sanitizefilename} %D %D Use \type{\pathplusfile} to compose a full file name, like %D in: %D %D \starttyping %D \pathplusfile{path}{file} %D \stoptyping %D %D By default, this expands into {\tt \pathplusfile{path}{file}}. \def\pathplusfile#1#2{#1\f!pathseparator#2} %D This one constructs a filename from a (possible empty) %D path and filename. \def\assignfullfilename#1#2\to#3% {\doifelsenothing{#1} {\edef#3{#2}} {\edef#3{#1\f!pathseparator#2}}} %D For the moment, we limit sanitizing to taking care of %D active \type {/}. \bgroup % todo: _ cleanup \catcode`\/=\@@active \catcode`\:=\@@active \catcode`\~=\@@active \catcode`\_=\@@active \gdef\sanitizefilename#1\to#2% {\bgroup \edef/{\string/}% \edef:{\string:}% \edef~{\string~}% \edef_{\string_}% \retainlccodes \lccode`\\=`\/ \lowercase{\expanded{\xdef\noexpand\sanitizedfilename{#1}}}% \egroup \let#2\sanitizedfilename} \egroup %D NEW: \chardef\kindoffile=0 % 0=normal 1=full path spec (or http) \def\checkfilename#1% {\doifinstringelse{@@/}{@@#1}% unix: /full/path {\chardef\kindoffile\plusone} {\doifinstringelse{:/}{#1}% windows: e:/full/path or http:// {\chardef\kindoffile\plusone} {\chardef\kindoffile\zerocount}}} %D \macros %D {input, normalinput} %D %D Sometimes we run into troubles when \type {\input} wants to get %D expanded, e.g. in a \type {\write} (which happens in the metafun %D manual when we permit long MP lines). So, instead of fixing that, %D we go for a redefinition of \type {\input}. Of course it's better %D to use \type {\readfile} or \type {\processfile}. \unexpanded\def\input{\normalinput} \ifnum\texengine=\luatexengine \def\inputgivenfile#1{\normalinput{#1}\relax} \else \def\inputgivenfile#1{\normalinput"#1"\relax} \fi %D \macros %D {readfile,ReadFile,maxreadlevel} %D %D One cannot be sure if a file exists. When no file can be %D found, the \type{\input} primitive gives an error message %D and switches to interactive mode. The macro \type{\readfile} %D takes care of non||existing files. This macro has two faces. %D %D \starttyping %D \ReadFile {filename} %D \readfile {filename} {before loading} {not found} %D \stoptyping %D %D Many \TEX\ implementations have laid out some strategy for %D locating files. This can lead to unexpected results, %D especially when one loads files that are not found in the %D current directory. Let's give an example of this. In %D \CONTEXT\ illustrations can be defined in an external file. %D The resizing macro first looks if an illustration is defined %D in the local definitions file. When no such file is found, %D it searches for a global file and when this file is not %D found either, the illustration itself is scanned for %D dimensions. One can imagine what happens if an adapted, %D localy stored illustration, is scaled according to %D dimensions stored somewhere else. %D %D When some \TEX\ implementation starts looking for a file, it %D normally first looks in the current directory. When no file %D is found, \TEX\ starts searching on the path where format %D and|/|or style files are stored. Depending on the implementation %D this can considerably slow down processing speed. %D %D In \CONTEXT, we support a project||wise ordening of files. %D In such an approach it seems feasible to store common files %D in a lower directory. When for instance searching for a %D general layout file, we therefore have to backtrack. %D %D These three considerations have lead to a more advanced %D approach for loading files. %D %D We first present an earlier implementation of %D \type{\readfile}. This command backtracks parent %D directories, upto a predefined level. Users can change this %D level, but we default to~3. %D %D \starttyping %D \def\maxreadlevel {3} %D \stoptyping %D %D This is a pseudo \COUNTER. %D %D We use \type{\normalinput} instead of \type{\input} %D because we want to be able to redefine the original %D \type{\input} when needed, for instance when loading third %D party libraries. \newevery \everybeforereadfile \EveryBeforeReadFile \newevery \everyafterreadfile \EveryAfterReadFile \let \everyreadfile \everybeforereadfile \newif\iftracefiles \newcount\readlevel \def\maxreadlevel{3} \newconditional\trackfilenames \let\trackedfilename\empty % We need to postpone loading, else we got frozen type-* files and so when % a format is generated on a source path. \def\doreadfile#1#2#3#4% {\sanitizefilename#2\to\readfilename \ifx\readfilename\empty % silently ignore \else \let\trackedfilename\readfilename \ifconditional\trackfilenames \doifundefinedelse{fn..\trackedfilename}\donetrue\donefalse \else \donetrue \fi \ifdone \checkfilename\readfilename \ifcase\kindoffile \iftracefiles\writestatus\m!systems{searching for \readfilename\space on #1}\fi % not a full path or url, check for existence \doifelsenothing{#1} {\def\next{\redoreadfile\readfilename{#3}{#4}}}% {\def\next{\redoreadfile{\pathplusfile{#1}{\readfilename}}{#3}{#4}}}% \else % a full path or url, no further checking done \doiffileexistselse\readfilename {\iftracefiles\writestatus\m!systems{located \readfilename}\fi \def\next{#3\dodoreadfile}}% {\iftracefiles\writestatus\m!systems{not found \readfilename}\fi \def\next{#4}}% \fi \else \edef\readfilename{\getvalue{fn..\readfilename}}% \iftracefiles\writestatus\m!systems{already located \readfilename}\fi \def\next{#3\dodoreadfile}% \fi \expandafter\next \fi} \def\redoreadfile#1#2#3% {\doiffileexistselse{#1}% {\edef\readfilename{#1}% \iftracefiles\writestatus\m!systems{#1 located}\fi \def\next{#2\dodoreadfile}}% {\iftracefiles\writestatus\m!systems{cannot locate #1}\fi \advance\readlevel\minusone \ifnum\readlevel>\zerocount \edef\readfilename{\pathplusfile{\f!parentpath}{\readfilename}}% \def\next{\redoreadfile\readfilename{#2}{#3}}% \else \def\next{#3}% \fi}% \next} \def\dodoreadfile % we provide hooks, for instance for \enableXML {\ifconditional\trackfilenames \setxvalue{fn..\trackedfilename}{\readfilename}% \fi \the\everybeforereadfile % \normalinput\readfilename\relax \relax\inputgivenfile\readfilename\relax \the\everyafterreadfile} % too less: % % \unexpanded\def\readfile% #1% % {\readlevel\maxreadlevel % \doreadfile\empty} % {#1} % % too much: % % \unexpanded\def\readfile#1#2#3% % {\readlocfile{#1}{#2} % {\readjobfile{#1}{#2} % {\readsysfile{#1}{#2}{#3}}}} % % just ok: \unexpanded\def\readfile#1#2#3% {\readlocfile{#1}{#2}{\readsysfile{#1}{#2}{#3}}} \def\readtexfile#1#2#3% {\pushcatcodetable \catcodetable \ctxcatcodes \readfile{#1}{#2}{#3}% \popcatcodetable} \ifdefined\xmlcatcodes \else \let\xmlcatcodes\xmlcatcodesn \fi \def\readxmlfile#1#2#3% {\pushcatcodetable \catcodetable \xmlcatcodes \readfile{#1}{#2}{#3}% \popcatcodetable} \unexpanded\def\ReadFile#1% {\readfile{#1}\donothing\donothing} %D \macros %D {readjobfile,readlocfile,readsysfile, %D readfixfile,readsetfile} %D %D This implementation honnors the third situation, but we %D still can get unwanted files loaded and/or can get involved %D in extensive searching. %D %D Due to different needs, we decided to offer four alternative %D loading commands. With \type{\readjobfile} we load a local %D file and do no backtracking, while \type{\readlocfile} %D backtracks~\number\readlevel\ directories, including the current %D one. \unexpanded\def\readjobfile % #1% current path, no backtracking {\readlevel\zerocount \doreadfile\f!currentpath} % {#1}} \unexpanded\def\readlocfile % #1% current path, backtracking {\readlevel\maxreadlevel \doreadfile\f!currentpath} % {#1}} %D System files can be anywhere and therefore %D \type{\readsysfile} is not bound to the current directory %D and obeys the \TEX\ implementation. \unexpanded\def\readsysfile % #1% current path, obeys tex search {\readlevel\zerocount \doreadfile\empty} % {#1}} %D Of the last two, \type{\readfixfile} searches on the %D directory specified and backtracks too, while %D \type{\readsetfile} does only search on the specified path. \unexpanded\def\readfixfile % #1#2% specified path, backtracking {\readlevel\maxreadlevel \doreadfile} % {#1}{#2}} \unexpanded\def\readsetfile % #1#2% specified path, no backtracking {\readlevel\zerocount \doreadfile} % {#1}{#2}} %D After having defined this commands, we reconsidered the %D previously defined \type{\readfile}. This time we more or %D less impose the search order. \unexpanded\def\readfile#1#2#3% {\readlocfile{#1}{#2} {\readjobfile{#1}{#2} {\readsysfile{#1}{#2}{#3}}}} %D So now we've got ourselves five file loading commands: %D %D \starttyping %D \readfile {filename} {before loading} {not found} %D %D \readjobfile {filename} {before loading} {not found} %D \readlocfile {filename} {before loading} {not found} %D \readfixfile {filename} {before loading} {not found} %D \readsysfile {directory} {filename} {before loading} {not found} %D \stoptyping %D \macros %D {readjobfile,readlocfile,readsysfile,readfixfile} %D %D The next four alternatives can be used for opening files %D for reading on a line||by||line basis. These commands get %D an extra argument, the filetag. Explicit closing is done %D in the normal way by \type{\closein}. \def\doopenin#1#2% {\sanitizefilename#2\to\readfilename \checkfilename\readfilename \ifcase\kindoffile \advance\readlevel\plusone \openinputfile{#1}\readfilename \ifeof#1% \relax \ifnum\readlevel>\maxreadlevel % \relax \else \closeinputfile#1% \relax \doopenin{#1}{\pathplusfile\f!parentpath{#2}}% \fi \fi \fi} \def\openjobin#1#2% {\readlevel\zerocount \doopenin{#1}{\pathplusfile\f!currentpath{#2}}} \def\opensysin % #1#2% {\readlevel\maxreadlevel \doopenin} % {#1}{#2}} \def\openlocin#1#2% {\readlevel\maxreadlevel \doopenin{#1}{\pathplusfile\f!currentpath{#2}}} \def\openfixin#1#2#3% {\readlevel\maxreadlevel \doopenin{#1}{\pathplusfile{#2}{#3}}} %D \macros %D {doiffileelse,doiflocfileelse} %D %D The next alternative only looks if a file is present. No %D loading is done. This one obeys the standard \TEX\ %D implementation method. %D %D \starttyping %D \doiffileelse {filename} {found} {not found} %D \stoptyping %D %D \starttyping %D \doiflocfileelse {filename} {before loading} {not found} %D \stoptyping \def\doiffileelse {\doiffileexistselse} \def\doiffile #1{\doiffileexistselse{#1}\firstofoneargument\gobbleoneargument} \def\doifnotfile #1{\doiffileexistselse{#1}\gobbleoneargument\firstofoneargument} \def\doiflocfileelse#1% {\makelocreadfilename{#1}% \doiffileelse\readfilename} \def\makelocreadfilename#1% {\sanitizefilename#1\to\readfilename \checkfilename\readfilename \ifcase\kindoffile \edef\readfilename{\pathplusfile\f!currentpath{#1}}% \fi} %D \macros %D {doonlyonce, doinputonce, doendinputonce} %D %D Especially macropackages need only be loaded once. %D Repetitive loading not only costs time, relocating registers %D often leads to abortion of the processing because \TEX's %D capacity is limited. One can prevent multiple execution and %D loading by using one of both: %D %D \starttyping %D \doonlyonce{actions} %D \doinputonce{filename} %D \doendinputonce{filename} %D \stoptyping %D %D This command obeys the standard method for locating files. \long\def\doonlyonce#1% {\doifundefinedelse{@@@#1@@@} {\letgvalue{@@@#1@@@}\empty \firstofoneargument} {\gobbleoneargument}} \def\doinputonce#1% % {\doonlyonce{#1}{\doiffileelse{#1}{\normalinput#1\relax}\donothing}} {\doonlyonce{#1}{\doiffileelse{#1}{\inputgivenfile{#1}}\donothing}} \def\doendinputonce#1% {\doifdefined{@@@#1@@@}\endinput} \def\forgetdoingonce#1% {\global\letbeundefined{@@@#1@@@}} %D \macros %D {doifparentfileelse} %D %D The test \type{\doifelse{\jobname}{filename}} does not give %D the desired result, simply because \type{\jobname} expands %D to characters with \CATCODE~12, while the characters in %D \type{filename} have \CATCODE~11. So we can better use: %D %D \starttyping %D \doifparentfileelse{filename}{yes}{no} %D \stoptyping %D %D Since \TEXEXEC\ (and thereby \CONTEXT) supports renaming of %D the outputfile, we also need to check on that alternative %D name. \ifx\outputfilename\undefined \def\outputfilename{\jobname} \fi \def\doifparentfileelse#1% {\doifsamestringelse{#1}{\jobname }\firstoftwoarguments {\doifsamestringelse{#1}{\jobname.\c!tex}\firstoftwoarguments {\doifsamestringelse{#1}{\outputfilename}\firstoftwoarguments\secondoftwoarguments}}} \def\normalless {<} % geen \let ! \def\normalmore {>} % geen \let ! \def\normalequal {=} % geen \let ! \def\normaldblquote{"} % geen \let ! \newcount\readingfilelevel \def\popfilecharacter#1#2% {\ifnum\catcode`#1=\@@other \ifnum#2=\@@other \else %\message{[popping catcode #1 to #2]}% \catcode`#1=#2\relax \fi \fi} \ifx\\\undefined \let\\\relax \fi %D This changing catcodes is a direct result from the fact %D that we support some long standing conventions with %D regards to active characters (german ", polish /, %D french : and ;). %D We need to redo this: catcode sets and such \newtoks \everystartreadingfile \newtoks \everystopreadingfile \def\startreadingfile% beter een every en \setnormalcatcodes {\global\advance\readingfilelevel\plusone \the\everystartreadingfile \beginrestorecatcodes \setcatcodetable\prtcatcodes} \def\stopreadingfile {\endrestorecatcodes \the\everystopreadingfile \global\advance\readingfilelevel\minusone} \let\normalstartreadingfile\startreadingfile \let\normalstopreadingfile \stopreadingfile %D \macros %D {splitfilename} %D %D I should have made this one sooner. This macro was first needed when %D ran into graphic with a period in the pathpart. %D %D \startbuffer %D \def\showfilesplit %D {\bgroup \tttf %D \hbox{(full: \splitofffull)}\space %D \hbox{(path: \splitoffpath)}\space %D \hbox{(base: \splitoffbase)}\space %D \hbox{(name: \splitoffname)}\space %D \hbox{(type: \splitofftype)}\space %D \egroup} %D %D \splitfilename{c:/aa/bb/cc/dd.ee.ff} \showfilesplit \endgraf %D \splitfilename{c:/aa/bb/cc/dd.ee} \showfilesplit \endgraf %D \splitfilename{c:/aa/bb/cc/dd} \showfilesplit \endgraf %D %D \splitfilename{dd.ee.ff} \showfilesplit \endgraf %D \splitfilename{dd.ee} \showfilesplit \endgraf %D \splitfilename{dd} \showfilesplit \endgraf %D \stopbuffer %D %D \start \typebuffer \getbuffer \stop \def\splitoffroot{.} \chardef\splitoffkind\zerocount \let\splitofffull\empty \let\splitoffpath\empty \let\splitoffbase\empty \let\splitoffname\empty \let\splitofftype\empty % \def\splitfilename#1% % {\edef\splitofffull{#1}% normally outside this call: \sanitizefilename#1\to\sanitizedfilename % \greedysplitstring\splitofffull\at/\to\splitoffpath\and\splitoffbase % \ifx\splitoffbase\empty % \let\splitoffpath\empty % \let\splitoffbase\splitofffull % \fi % \greedysplitstring\splitoffbase\at.\to\splitoffname\and\splitofftype % \chardef\splitoffkind % can be used to test if pathpart was empty % \ifx\splitoffpath\empty \zerocount \else % \ifx\splitoffpath\splitoffroot \plusone \else % \plustwo \fi\fi % \ifx\splitoffname\empty\let\splitoffname\splitoffbase\fi % \ifx\splitoffpath\empty\let\splitoffpath\splitoffroot\fi} % % better, since it also handles leading /'s % % \splitfilename{oeps/test.pdf} [\splitoffpath\quad\splitoffname\quad\splitofftype] % \splitfilename{/oeps/test.pdf} [\splitoffpath\quad\splitoffname\quad\splitofftype] % \splitfilename{/test.pdf} [\splitoffpath\quad\splitoffname\quad\splitofftype] \def\splitfilename#1% {\edef\splitofffull{#1}% normally outside this call: \sanitizefilename#1\to\sanitizedfilename \greedysplitstring\splitofffull\at/\to\splitoffpath\and\splitoffbase \ifx\splitoffpath\splitofffull \let\splitoffpath\empty \fi \ifx\splitoffbase\empty \let\splitoffpath\empty \let\splitoffbase\splitofffull \fi \greedysplitstring\splitoffbase\at.\to\splitoffname\and\splitofftype \chardef\splitoffkind % can be used to test if pathpart was empty \ifx\splitoffpath\empty \zerocount \else \ifx\splitoffpath\splitoffroot \plusone \else \plustwo \fi\fi \ifx\splitoffname\empty\let\splitoffname\splitoffbase\fi \ifx\splitoffpath\empty\let\splitoffpath\splitoffroot\fi} \def\splitfiletype#1% {\edef\splitofffull{#1}% normally outside this call: \sanitizefilename#1\to\sanitizedfilename \let\splitoffpath\empty \greedysplitstring\splitofffull\at.\to\splitoffname\and\splitofftype} \protect \endinput