Как расширить содержимое файла, чтобы добавлять и подсчитывать строки?

Как расширить содержимое файла, чтобы добавлять и подсчитывать строки?

Вот два вопроса об filecontentsокружающей среде:

  1. можно ли добавить содержимое в уже существующий файл или, как минимум, оставить внешний файл открытым, чтобы добавить содержимое нескольких filecontentsсред в один и тот же внешний файл?

  2. можно ли узнать, сколько строк было записано средой во внешний файл filecontents?

решение1

Подсчет строк filecontentsпрост, потому что среда обрабатывает свое содержимое строка за строкой, считывая, проверяя, закончилась ли среда, а затем записывая. Вам просто нужно сделать счетчик и добавлять его каждый раз, когда пишется строка.

Добавление сложнее. Во-первых, TeX (по крайней мере, без Lua) не может открывать файлы с разрешением "добавить", так что это исключено. Идея оставить файл открытым, а затем продолжить запись позже кажется на первый взгляд проще, но вам нужно будет сохранить кучу переменных в разных средах, и тогда вы не сможете записать в файл a, затем b, а затем добавить в a.

В коде ниже \filecontents@preappendмакрос (при appendиспользовании опции) считывает файл в аргументе во временный макрос, а затем помещает \filecontents@appendпрочитанный макрос во входной поток перед фактическим содержимым среды, так что это работает так же, как если бы вы ввели все за один раз. Опция appendподразумевает опции overwriteи noheader.

Я пытался написать код в виде патча для компактности, но, по-видимому, при патче команды теряются символы ^^J, а затем заголовок сворачивается в одну строку, так что это полное переопределение \filec@ntents, но измененные строки отмечены.

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \FileSize \file_size:n
\ExplSyntaxOff
\makeatletter
\begingroup%
\@tempcnta=1
\loop
  \catcode\@tempcnta=12  %
  \advance\@tempcnta\@ne %
\ifnum\@tempcnta<32      %
\repeat                  %
\catcode`\^^M\active
\catcode`\^^L\active\let^^L\relax
\catcode`\^^I\active
\gdef\filec@ntents#1{%
  \set@curr@file{\filec@ntents@checkdir#1}%
  \edef\q@curr@file{\expandafter\quote@name\expandafter{\@curr@file}}%
  \filecontents@preappend% <- ADDED for appending
  \gaborit@reset@counter% <- ADDED for counting lines
  \gaborit@reset@append% <- ADDED for counting lines
  \openin\@inputcheck\q@curr@file \space %
  \ifeof\@inputcheck%
    \@latex@warning@no@line%
        {Writing file `\@currdir\@curr@file'}%
    \chardef\reserved@c15 %
    \ch@ck7\reserved@c\write%
    \immediate\openout\reserved@c\q@curr@file\relax%
  \else%
    \if@filesw%
      \@latex@warning@no@line%
          {File `\@curr@file' already \filec@ntents@where.\MessageBreak%
             Not generating it from this source}%
      \let\gaborit@step@counter\@empty% <- ADDED for counting lines
      \let\gaborit@count@header\@empty% <- ADDED for counting lines
      \let\write\@gobbletwo%
      \let\closeout\@gobble%
    \else%
      \edef\reserved@a{#1}%
      \edef\reserved@a{\detokenize\expandafter{\reserved@a}}%
      \edef\reserved@b{\detokenize\expandafter{\jobname}}%
      \ifx\reserved@a\reserved@b%
        \@fileswtrue%
      \else%
        \edef\reserved@b{\reserved@b\detokenize{.tex}}%
        \ifx\reserved@a\reserved@b
          \@fileswtrue%
        \fi%
      \fi%
      \chardef\reserved@c15 %
      \ch@ck7\reserved@c\write%
      \if@filesw%  % Foul ... trying to overwrite \jobname!
      \@latex@error{Trying to overwrite `\jobname.tex'}{You can't %
        write to the file you a reading from!\MessageBreak%
        Data is written to screen instead.}%
      \else%
        \@latex@warning@no@line%
           {Writing or overwriting file `\@currdir\@curr@file'}%
        \immediate\openout\reserved@c#1\relax%
      \fi%
    \fi%
  \fi%
  \closein\@inputcheck%
  \if@tempswa%
    \gaborit@count@header% <- ADDED for counting lines
    \immediate\write\reserved@c{%
      \@percentchar\@percentchar\space%
          \expandafter\@gobble\string\LaTeX2e file `\@curr@file'^^J%
      \@percentchar\@percentchar\space  generated by the %
        `\@currenvir' \expandafter\@gobblefour\string\newenvironment^^J%
      \@percentchar\@percentchar\space from source `\jobname' on %
         \number\year/\two@digits\month/\two@digits\day.^^J%
      \@percentchar\@percentchar}%
  \fi%
  \let\do\@makeother\dospecials%
  \count@ 128\relax%
  \loop%
    \catcode\count@ 11\relax%
    \advance\count@ \@ne%
    \ifnum\count@<\@cclvi%
  \repeat%
  \edef\E{\@backslashchar end\string{\@currenvir\string}}%
  \edef\reserved@b{%
    \def\noexpand\reserved@b%
         ####1\E####2\E####3\relax}%
  \reserved@b{%
    \ifx\gaborit@reset@append##1\relax% <- ADDED for counting lines
      \gaborit@reset@append% <- ADDED for counting lines
    \else% <- ADDED for counting lines
    \ifx\relax##3\relax%
      \immediate\write\reserved@c{##1}%
      \gaborit@step@counter% <- ADDED for counting lines
    \else%
      \edef^^M{\noexpand\end{\@currenvir}}%
      \ifx\relax##1\relax%
      \else%
          \@latex@warning{Writing text `##1' before %
             \string\end{\@currenvir}\MessageBreak as last line of \@curr@file}%
        \immediate\write\reserved@c{##1}%
        \gaborit@step@counter% <- ADDED for counting lines
      \fi%
      \ifx\relax##2\relax%
      \else%
         \@latex@warning{%
           Ignoring text `##2' after \string\end{\@currenvir}}%
      \fi%
    \fi%
    \fi% <- ADDED for counting lines
    ^^M}%
  \catcode`\^^L\active%
  \let\L\@undefined%
  \def^^L{\expandafter\ifx\csname L\endcsname\relax\fi ^^J^^J}%
  \catcode`\^^I\active%
  \let\I\@undefined%
  \def^^I{\expandafter\ifx\csname I\endcsname\relax\fi\space}%
  \catcode`\^^M\active%
  \edef^^M##1^^M{%
    \noexpand\reserved@b##1\E\E\relax}%
  \filecontents@append}% <- ADDED for appending
%
% Code for append
\gdef\gaborit@pre@append{%
  \begingroup%
    \catcode`\^^L\active%
    \catcode`\^^I\active%
    \catcode`\^^M\active%
    \let\do\@makeother\dospecials%
    \count@ 128\relax%
    \loop%
      \catcode\count@ 11\relax%
      \advance\count@ \@ne%
      \ifnum\count@<\@cclvi%
    \repeat%
    \let^^M\relax%
    \edef\gaborit@tmpa{\FileSize{\q@curr@file}}%
    \ifnum\expandafter\@car\gaborit@tmpa\@nil=0\relax%
      \endgroup \def\gaborit@append{^^M\gaborit@reset@append}%
    \else%
      \everyeof{\noexpand}%
      \edef\gaborit@tmpa{\@@input\q@curr@file \space}%
      \edef\x{\endgroup%
        \edef\noexpand\gaborit@append{%
          \noexpand\gaborit@trim@EOF\gaborit@tmpa\noexpand\gaborit@EOF}}\x%
    \fi%
  \filec@ntents@overwrite%
  \filec@ntents@noheader}%
\gdef\gaborit@trim@EOF#1^^M\gaborit@EOF{^^M#1%
  ^^M\noexpand\gaborit@reset@append}%
\endgroup%
\def\filec@ntents@append{%
  \let\filecontents@preappend\gaborit@pre@append
  \def\filecontents@append{\gaborit@append}}
\let\filecontents@preappend\@empty
\let\filecontents@append\@empty
%
% For counting lines
\newcounter{FC@total@lines}
\newcounter{FC@lines}
\def\gaborit@reset@counter{\setcounter{FC@total@lines}{0}}
\def\gaborit@reset@append{\setcounter{FC@lines}{0}}
\def\gaborit@step@counter{%
  \stepcounter{FC@total@lines}%
  \stepcounter{FC@lines}}
\def\gaborit@count@header{%
  \addtocounter{FC@total@lines}{4}%
  \addtocounter{FC@lines}{4}} % Number of lines in the header
\makeatother

\begin{filecontents}[overwrite]{testfile.tex}
a
b
c
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}

\begin{filecontents}[append]{testfile.tex}
d
e
f
g
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}

\begin{document}
\end{document}

При выполнении этого кода TeX выводит на терминал:

LaTeX Warning: Writing or overwriting file `./testfile.tex'.

7 lines written; 7 appended
(./testfile.tex)

LaTeX Warning: Writing or overwriting file `./testfile.tex'.

11 lines written; 4 appended

( FC@total@linesсодержит общее количество строк в файле, а счетчик FC@linesсодержит только количество добавленных строк) и файл testfile.texсодержит:

%% LaTeX2e file `testfile.tex'
%% generated by the `filecontents' environment
%% from source `test' on 2019/12/20.
%%
a
b
c
d
e
f
g


Как вы отметили, когда вы это делаете:

\begin{filecontents*}{testfile.tex}
\end{filecontents*}
%
\begin{filecontents}[append]{testfile.tex}
a
\end{filecontents}

(то есть ничего не пишите, а затем добавьте строку с a) файл, который вы получите:


a

Это связано с тем, что когда TeX считывает пустой файл, он обрабатывает его так, как если бы это был файл с одной пустой строкой 1 , поэтому вы не можете легко различить эти два случая и должны выбрать меньшее зло: либо добавить фиктивную пустую строку в начало файла при добавлении в пустой файл, либо удалить пустую строку при записи в файл, содержащий одну пустую строку.

Однако с помощью файловых утилит, представленных в pdfTeX (но также доступных в других движках в настоящее время), вы можете запросить размер (в байтах) файла, так что вы можете иметь другое поведение для пустого файла. В коде я использовал expl3's, \file_size:nчтобы избежать необходимости иметь дело с несуществующими файлами.


1. Вы можете увидеть это с помощью этого тестового файла:

\catcode`\@=11
\newwrite\test
\def\testit#1{\immediate\openout\test \jobname.testfile\relax #1%
  {\catcode`\^^M=13 \everyeof{\noexpand}%
   \edef\tmpa{\ifdefined\@@input \@@input \else \input \fi \jobname.testfile }\show\tmpa}%
  \immediate\closeout\test}%
\testit{}% empty file
\testit{\immediate\write\test{}}% single empty line
\csname stop\endcsname\bye

Такое поведение, я думаю, связано с тем, как TeX добавляет \endlinecharв конце каждой строки. Когда он читает пустой файл, он все равно читает строку (даже если она пустая), а затем вставляет ( \endlinechar) ^^M, и это выглядит так, как будто в файле есть одна пустая строка.

Связанный контент