Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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?

share|improve this question
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 Feb 25 at 16:24
    
@egreg, hi, I think the % which is missing is indeed after the {}, and also after the first {\colB} (adds a space at the beginning), not after {\colB}} as this is between the two branches of ifthenelse (I managed to ssh to some place where I could compile, so this is not only guess work...) –  jfbu Feb 25 at 16:42
    
@jfbu My last edited code works perfectly, no extra space. merci bien! –  Christof Feb 25 at 16:45
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. –  jfbu Feb 25 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}. –  jfbu Feb 26 at 6:59

2 Answers 2

up vote 10 down vote accepted

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

share|improve this answer
    
Thanks a lot! I tried to make a command from the code, see above in my original post. –  Christof Feb 21 at 11:24
    
@Christof, yes looks fine. I have added a method using neither xifthen nor xstring. –  jfbu Feb 21 at 15:26
    
Wow, that's really great! Thanks again. Beeing able to pass the database name makes the command even more comfortable!! –  Christof Feb 21 at 15:40
1  
try replacing ` {}` in your code by ` {}%` (untested; the line just before \repeat) –  jfbu Feb 25 at 16:03
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] –  jfbu Feb 25 at 16:44

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

share|improve this answer
    
Thanks, that looks great and exactly what I would like to achieve. But I'm using xelatex so far ... no idea about latex3 at all. Didn't even know the development was still going on. –  Christof Feb 21 at 10:51
1  
@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 Feb 21 at 11:22
    
@Christof Please, make a followup question, stating the problem precisely. –  egreg Jul 25 at 22:46
    
Sorry, no, it doesn't work like this. Prepare an example and open a new question. –  egreg Jul 26 at 0:07
    
sorry, I can probably not reach up to this forum's level. I am not able to understand your code, though I use it so far and it works great. All I wanted was to extend its usage a bit more. I described it as good as I could and gave an example. Anyway, sorry again, and bye. –  Christof Jul 26 at 0:28

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.