15

I want find a certain line of text in a data file cities.txt:

A;London
B;Berlin
C;Paris

with the xstring and [xifthen][2] packages:

\documentclass[12pt, a4paper]{article}

\usepackage{xstring}
\usepackage{xifthen}

\begin{document}

\newread\dbcities
\openin\dbcities=cities.txt

We start here:

\loop\unless\ifeof\dbcities
    \read\dbcities to \dbline 
    \ifthenelse{\isin{A}{\dbline}}   % <-- doesn't work
    {\StrCut{\dbline}{;}{\colA}{\colB}
    \colA: \colB \\}
    {}
\repeat
\closein\dbcities

\end{document}

But the string comparison doesn't work if the line of text to be compared is stored in the macro variable \dbline, although if I print \dbline it expands perfectly. That makes me crazy ... Any help?

Edit: Based on jfbu's answer (thanks!) I tried to put the search code into a command and pass the searchstring by Argument. Seems it works!

Here is how I use the function now. The extra whitespace is a problem (see discussion)

% <-*- coding: utf-8 -*- 
% ---------------------------------------------------
\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den Sugatas, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
§2§~{Etwas, das es zuvor noch nicht gegeben hat, habe auch ich hier nicht zu sagen; und ich besitze nicht die Kunstfertigkeit in der Komposition von Schriften: Deshalb habe ich auch keine Absicht, anderen zu nutzen; vielmehr schreibe ich dieses, um meinen eigenen Geist daran zu gewöhnen.}
§
§3§~{Aufgrund der Gewöhnung an das Heilsame mag die Kraft meines Vertrauens durch diese Kontemplationen zeitweilig anwachsen; und wenn andere, die mir in ihren Veranlagungen ähnlich sind, diese sehen, so mag es auch für sie von Bedeutung sein.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 

\usepackage{polyglossia} % the multilingual support package
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

% ------ Routine für Einfügung Grundtext: ---------
% !!roots-db.txt dar keine Leerzeile am Ende haben!!

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[1]{%
\openin\dbroot=verses.dat
\loop
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#1}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}
    {}
\repeat
\closein\dbroot%
}

\begin{document}

Each line of data printed in a seperate paragraph:
\medskip{}

\myprintverse{§1§} \par
\myprintverse{§2§} \par
\myprintverse{§3§} \par

\bigskip{}
Two lines in one paragraph results in ugly whitespace in between, and I don't know how to get rid of it: 
\medskip{}

\myprintverse{§1§} \myprintverse{§2§} \par
\myprintverse{§3§}\footnote{This is also a problem if I want to add a footnote.} \par

\end{document}

enter image description here

Edit: This is now the flawless version of the command that works without adding space:

\newcommand{\myprintverse}[1]{%
\openin\dbroot=verses.dat
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#1}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

Edit: Now I will pass the filename by argument, and it works perfectly for me:

\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den Sugatas, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
§2§~{Etwas, das es zuvor noch nicht gegeben hat, habe auch ich hier nicht zu sagen; und ich besitze nicht die Kunstfertigkeit in der Komposition von Schriften: Deshalb habe ich auch keine Absicht, anderen zu nutzen; vielmehr schreibe ich dieses, um meinen eigenen Geist daran zu gewöhnen.}
§
§3§~{Aufgrund der Gewöhnung an das Heilsame mag die Kraft meines Vertrauens durch diese Kontemplationen zeitweilig anwachsen; und wenn andere, die mir in ihren Veranlagungen ähnlich sind, diese sehen, so mag es auch für sie von Bedeutung sein.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 
\usepackage{polyglossia} 
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[2]{%
\openin\dbroot=#1
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#2}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

\begin{document}

\myprintverse{verses.dat}{§1§} \par

\end{document}

Edit / Question: The above version works great unless I enter some LaTeX-Code, e.g. if I want to emphasize text:

\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den \emph{Sugatas}, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 
\usepackage{polyglossia} 
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[2]{%
\openin\dbroot=#1
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#2}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

\begin{document}

\myprintverse{verses.dat}{§1§} \par

\end{document}

Compilation stops with an error:

! Use of \@xs@StrCut@@ doesn't match its definition.   
\text@command #1->\def \reserved@a {#1   
                 }\ifx \reserved@a \@empty \let \check@...    
l.43     \myprintverse{verses.dat}{§1§}

Any idea how this could be solved?

3
  • 1
    About the edit: you're missing % after {\colB}} (and the next line with {} is useless and adds another space. Next, you need to remove the trailing space due to the end-of-lines in verses.dat. The easiest way is to enclose the loop in a group where you set \endlinechar=-1
    – egreg
    Commented Feb 25, 2014 at 16:24
  • 1
    @Christof \repeat }% should not create a space. Recall that line endings in TeX get converted into a space. But if the last thing on the line is a control sequence like \endgroup then this space disappears. And again: removing the {} from the \ifthenelse was faulty; the initial {} should really be there, naturally as {}% to avoid the space from the eol. The current code works, but by luck. It would fail with some other loop constructs ending in \repeat, other than \LaTeX's own \loop.
    – user4686
    Commented Feb 25, 2014 at 22:21
  • 1
    @Christof your code has \verses.dat but the control sequence \verses is left undefined. Either use simply verses.dat or define \verses: \newcommand*{\verses}{verses}.
    – user4686
    Commented Feb 26, 2014 at 6:59

2 Answers 2

11

update: the mandatory xinttools approach has been added

There are two problems. The first one is that \isin from package xifthen does not expand its second argument, here \dbline. The second problem is that \isin uses the LaTeX kernel \in@ which can not handle a \par token; but TeX will always append a \par token at the end of file input via \read, except if \endlinechar is set to -1.

The first problem can be handled by defining a wrapper \isinxp which expands its second argument. The second problem (assuming \endlinechar is not -1) by testing the end of file after having read the line and before the test.

\documentclass[12pt, a4paper]{article}
\usepackage{filecontents}

\begin{filecontents*}{cities.txt}
A;London
B;Berlin
C;Paris
\end{filecontents*}

\usepackage{xstring}
\usepackage{xifthen}

% star form as anyhow \isin is declared short, and furthermore
% uses internally \in@ from the LaTeX kernel which is short
\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\begin{document}\thispagestyle{empty}

\newread\dbcities
\openin\dbcities=cities.txt

We start here:

\loop
    \read\dbcities to \dbline 
% (testing)   \texttt{\meaning\dbline}
\unless\ifeof\dbcities
    \ifthenelse{\isinxp{A}{\dbline}}
    {\StrCut{\dbline}{;}{\colA}{\colB}
    \colA: \colB \\}
    {}
\repeat

\closein\dbcities

\end{document}

string compare ifthen

The mandatory xinttools approach:

(I have corrected the key matching, as it stood a search for BB would have returned a false positive for a data line with B as key. Notice that the fact that a data line containing BB as key returns positive when looking for B is the expected thing, as the OP used \isin.)

\documentclass[12pt, a4paper]{article}
\usepackage{filecontents}
\begin{filecontents*}{\jobname.dat}
A;London
  B;   Berlin
BB; Hamburg
 C  ;  Paris
 A ;  London again
D;Roma
\end{filecontents*}

\usepackage{xinttools}

\newread\datastream

% to issue warnings if command names already in use, then one knows one
% has to change them (depens on loaded packages)
\newcommand {\onedataline}{}
\newcommand {\printdatakey}{}
\newcommand {\printdatatestkey}{}
\newcommand {\printdataprocessline}{}

\newcommand {\printdata}[2]{% #1 = file name, #2 string to test
  \openin\datastream=#1\relax
% the string key will be trimmed of its leading and trailing spaces
  \edef\printdatakey
  {\expandafter\xintZapSpaces\expandafter{\detokenize{#2}}}%
%
  \edef\printdatatestkey
      {\unexpanded{\def\printdatatestkey ##1}\printdatakey 
       \unexpanded{##2\relax ##3\par {\ifx\relax##2\relax\else ##3\par\fi}}}
%
  \printdatatestkey % sets up \printdatatestkey macro to seek the key.
% [added a ; to avoid false positives if for example the seeked key is BB
%  and the actual one is B.]
  \edef\printdataprocessline ##1;##2\relax
     {\noexpand\printdatatestkey ##1;\printdatakey\relax 
      \printdatakey: \noexpand\xintZapSpaces {##2}\noexpand\par }%
%
  \xintloop
    \read\datastream to \onedataline
% detokenize the line for safe handling. 
% \odef expands once the argument, this is enough
    \odef\onedataline{\detokenize\expandafter{\onedataline}}%
  \unless\ifeof\datastream
    \expandafter\printdataprocessline\onedataline\relax
% j'aurais dû faire directement
%  \expandafter\printdataprocessline\detokenize\expandafter{\onedataline}\relax
  \repeat
  \closein\datastream
}

\begin{document}\thispagestyle{empty}

Testing A:

\printdata{\jobname.dat}{ A }

\medskip

Testing B:

\printdata{\jobname.dat}{ B}

\medskip

% sans le ; faux positif sur Berlin
Testing BB:

\printdata{\jobname.dat}{BB}

\medskip

Testing D:

\printdata{\jobname.dat}{ D }

\end{document} 

key matching

5
  • I took over that interface from egreg 's answer. The use of xinttools is a bit anecdotal, mainly to remove spaces, and as expandability is not an issue this also could have been done otherwise, with no package.
    – user4686
    Commented Feb 21, 2014 at 18:37
  • @Christof I corrected the key matching process whose original version would have returned a match if the seeked key was BB and the actual one B.
    – user4686
    Commented Feb 22, 2014 at 10:44
  • 1
    try replacing ` {}` in your code by ` {}%` (untested; the line just before \repeat)
    – user4686
    Commented Feb 25, 2014 at 16:03
  • Please look at the OP for the result of {} and {}%. It helped, but not completly. I'll try to change your code and see what happens.
    – Christof
    Commented Feb 25, 2014 at 16:22
  • 1
    The remaining space is inserted not at the end of \myprintverse{§1§} but at the start of \myprintverse{§2§}, it comes from the unprotected end of line in your code after {\colB} (not the line below ending in {\colB}}) [naturally assuming you did the \endlinechar-1; if you didn't an unskip should now suffice]
    – user4686
    Commented Feb 25, 2014 at 16:44
9

A LaTeX3 implementation.

\begin{filecontents*}{\jobname.dat}
A;London
B;Berlin
C;Paris
\end{filecontents*}

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\printdata}{ mm }
 {% #1 is the file name, #2 is the key to test
  \christof_printdata:nn { #1 } { #2 }
 }

\ior_new:N \g_christof_data_stream

\cs_new_protected:Npn \christof_printdata:nn #1 #2
 {% do a mapping on file #1
  \ior_open:Nn \g_christof_data_stream { #1 }
  \ior_map_inline:Nn \g_christof_data_stream
   {% every entry is of the form "key;value", ##1 stands for the whole line
    \__christof_process_line:nww { #2 } ##1 \q_stop
   }
 }

\cs_new_protected:Npn \__christof_process_line:nww #1 #2 ; #3 \q_stop
 {% #1 is the key to test, #2 is the key, #3 is the value
  \tl_if_in:nnT { #2 } { #1 }
   {
    \tl_trim_spaces:n { #2 }: ~ \tl_trim_spaces:n { #3 } \par
   }
 }
\ExplSyntaxOff

\begin{document}

Testing A:

\printdata{\jobname.dat}{A}

\medskip

Testing B:

\printdata{\jobname.dat}{B}

\end{document}

enter image description here


Here's the implementation after showing the real needs.

\begin{filecontents*}{\jobname.dat}
§
§1§~{Mich respektvoll verneigend vor den Sugatas, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
§2§~{Etwas, das es zuvor noch nicht gegeben hat, habe auch ich hier nicht zu sagen; und ich besitze nicht die Kunstfertigkeit in der Komposition von Schriften: Deshalb habe ich auch keine Absicht, anderen zu nutzen; vielmehr schreibe ich dieses, um meinen eigenen Geist daran zu gewöhnen.}
§
§3§~{Aufgrund der Gewöhnung an das Heilsame mag die Kraft meines Vertrauens durch diese Kontemplationen zeitweilig anwachsen; und wenn andere, die mir in ihren Veranlagungen ähnlich sind, diese sehen, so mag es auch für sie von Bedeutung sein.}
§
\end{filecontents*}

\documentclass{article}
\usepackage{fontspec}

\usepackage{polyglossia} % the multilingual support package
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

\usepackage{xparse}

\usepackage{xparse}

\ExplSyntaxOn
% generic command
\NewDocumentCommand{\printdata}{ mm }
 {% #1 is the file name, #2 is the key to test
  \christof_printdata:nn { #1 } { #2 }
 }
% with fixed file name
\NewDocumentCommand{\myprintverse}{ m }
 {
  \printdata { \jobname.dat } { #1 } % <--- Fix the file name
 }

\ior_new:N \g_christof_data_stream
\seq_new:N \l__christof_entry_seq

\cs_new_protected:Npn \christof_printdata:nn #1 #2
 {
  \ior_open:Nn \g_christof_data_stream { #1 }
  \ior_map_inline:Nn \g_christof_data_stream
   {
    \tl_if_in:nnT { ##1 } { #2 }
     {
      \__christof_process_line:ww ##1 \q_stop
     }
   }
 }

\group_begin:
\char_set_catcode_active:N \^^A
\char_set_lccode:nn { `\^^A } { `\~ }
\tl_to_lowercase:n 
 {
  \group_end:
  \cs_new_protected:Npn \__christof_process_line:ww §#1§ ^^A #2 \q_stop
 }
 {% #1 is the key, #2 is the value
  (#1) \nobreakspace \tl_trim_spaces:n { #2 }
 }
\ExplSyntaxOff

\begin{document}

Each line of data printed in a seperate paragraph:
\medskip

\myprintverse{§1§}

\myprintverse{§2§}

\myprintverse{§3§}

\bigskip

Two lines in one paragraph results in proper space between them

\medskip

\myprintverse{§1§} \myprintverse{§2§}

\myprintverse{§3§}\footnote{This is also a problem if I want to add a footnote.}

\end{document}

The \tl_to_lowercase:n trick is necessary because ~ is a special character.

enter image description here

1
  • 2
    @Christof The development of LaTeX3 is going on; at present the programming layer is quite stable. You can use the code with all typesetting engines, provided you have an up-to-date TeX distribution.
    – egreg
    Commented Feb 21, 2014 at 11:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .