7
$\begingroup$

I have several TeX-files and they contain information of the form


\commandnameA{Bob}
blabla
\commandnameB{It wasn't Bob}
\commandnameC{2012}
bla
\commandnameD{$\frac{1}{3}$}
\commandnameE{\commandnameF{It was Bob!}}
\commandnamefilename{29}

Is it possible to read in that information via Mathematica? Like for example in a list

{Bob,It wasn't Bob,2012,$\frac{1}{3}$,\commandnameF{It was Bob!},It was Bob!,29}

The purpose would be to use the enumaration of the files (here 29) together with the other content of the files to further calculate statistics and generate graphics about these bunch of TeX-files as a whole.

$\endgroup$
3
  • $\begingroup$ Do the commands always appear literally (i.e. not as result of other command expansion?) Because otherwise I guess a better solution would be to run the LaTeX file with redefinitions of \commandnameA etc. to write into some file which you then can read into Mathematica. $\endgroup$
    – celtschk
    Apr 29, 2012 at 19:19
  • $\begingroup$ @celtschk: They appear literally. Nevertheless am I interested in what you say. I always only compile the files with the LaTeX tools, I don't even know how I can print out the file into another file for this process you're describing. $\endgroup$
    – Nikolaj-K
    Apr 29, 2012 at 19:46
  • $\begingroup$ Well, the idea would be to modify the macros commandnameA etc. to write their content into a special file (in addition to what they normally do). That way you'd even get the information if someone invoked them indirectly, e.g. using \let\cnA=\commandnameA and then later \cnA{Bob}. However, if the macros always appear literally, that would be overkill. Anyway, the process of writing to a file from LaTeX is described in stackoverflow.com/q/2115379/1032073 $\endgroup$
    – celtschk
    Apr 29, 2012 at 20:04

3 Answers 3

5
$\begingroup$

The regular expression approach is my favorite, but I would do it a little differently to make it more robust. The approach by David didn't quite get the } treated right. The approach by R.M relied on the newline characters in the file (but newlines are optional in $\TeX$). So here is what I believe fixes these problems.

First define the example $\TeX$ content:

tex = "\\commandnameA{Bob}
    blabla
    \\commandnameB{It wasn't Bob}
    \\commandnameC{2012}
    bla
    \\commandnameD{$\\frac{1}{3}$}
    \\commandnameE{\\commandnameF{It was Bob!}}
    \\commandnamefilename{29}";

Now comes the function that does the translation:

translate[t_] := 
 Module[{regex = 
    RegularExpression[
     "\\\\commandname[^{]*{([^{}]*({[^}]*})*([^{}]*))}"]},
  Flatten[{t, StringCases[t, regex :> translate["$1"]]}]
  ]

And finally the application:

Rest[translate[tex]]

{"Bob", "It wasn't Bob", "2012", "$\\frac{1}{3}$", "\\commandnameF{It \ was Bob!}", "It was Bob!", "29"}

The translate function finds matching braces following any of the \commandname keywords, and applies itself recursively to the resulting content. It returns the supplied argument plus the result of the recursive translation.

Therefore, the first entry in the result of translate is always the original text. That is why I use Rest to print the desired sub-strings.

$\endgroup$
6
  • 1
    $\begingroup$ The problem with a regex only solution (which is the reason David's solution also fails) is that regex cannot adequately handle tex, which is not a regular language (i.e., not Chomsky type 3). You cannot handle bracket matching perfectly and no matter what solution you come up with, you can always create valid tex that will break it. For example, try yours by replacing 2012 with $2012^{}$. :) Relying on newlines is not very robust either, but that was how the OP mentioned their code was (I'm very used to writing tex that way because it's easier to put it under version control that way) $\endgroup$
    – rm -rf
    Apr 29, 2012 at 23:55
  • $\begingroup$ That one is trivial to fix. I've edited it in. We're talking about a very special parsing problem, so it should be doable. Of course I'm not relying entirely on regex, that's why I have the recursion in there. $\endgroup$
    – Jens
    Apr 30, 2012 at 0:43
  • $\begingroup$ Well, it now breaks for ${2012^{}}$ ;) I'll stop here, but my point was largely that it's a very complex problem to make a truly robust solution and that even in this simple case, the regex is barely readable (you could say that about most non-trival regexes). This isn't even outrageous tex code—I've seen similar stuff in people's documents, where they used to have a superscript and then deleted it, but left the {} in just in case they needed to reinsert a superscript. Nevertheless, a +1 from me because it does answer the OP's question and doesn't have the issue that David's solution has $\endgroup$
    – rm -rf
    Apr 30, 2012 at 1:02
  • $\begingroup$ That's a valid point: if you need deeper nesting of braces then it will require additional recursion logic. It can be done, but at least the example didn't require it... $\endgroup$
    – Jens
    Apr 30, 2012 at 1:20
  • $\begingroup$ If you want to be completely robust, I guess the only way is to extract the information directly in TeX anyway. Unless you want to re-implement the TeX interpreter, of course. $\endgroup$
    – celtschk
    Apr 30, 2012 at 14:46
4
$\begingroup$

Here's a way using StringSplit and StringCases. The file test.tex is a file with your tex example.

tex = StringSplit[Import["test.tex", "Text"], "\n"]
StringCases[tex, "\\commandname" ~~ LetterCharacter .. ~~ "{" ~~ x__ ~~ "}" :> x] // Flatten
(* Out[1]= {"Bob", "It wasn't Bob", "2012", "$\frac{1}{3}$", 
    "\commandnameF{It was Bob!}", "29"} *)
$\endgroup$
3
$\begingroup$

Regex! The main problem here will be that you've got syntax-based rules in case of the nested expressions, i.e. when you're matching \comandnameE{ ... } you don't want to match until the first }, but after the parenthesis balance is even again. I don't know how to take care of that using only Regex. Anyway,

(* Your string condensed into one line *)
tex = "\\commandnameA{Bob}\nblabla\n\n\n\n \\commandnameB{It wasn't Bob}\n\n \\commandnameC{2012}\n\nbla\n\n \\commandnameD{$\\frac{1}{3}$}\n\n \\commandnameE{\\commandnameF{It was Bob!}}\n\n \\commandnamefilename{29}";
(* Regex that matches the inside of a parenthesis of
   a `commandname` instruction *)
regex = RegularExpression["\\\\commandname[a-zA-Z]+\{(.+)\}"];
(* Apply regex *)
StringCases[tex, regex -> "$1", Overlaps -> True]
List[
    Bob,
    It wasn't Bob,
    2012,
    $\frac{1}{3}$,
    \commandnameF{It was Bob!},
    It was Bob!},
    29
]

Note the trailing } in the It was Bob!} line, which you may have to take care of manually.

The regex is replaced by $1, which is the first matched sub-pattern, i.e. the first parenthesis expression in the regex. $0 would have been the entire expression, i.e. the resulting list would contain all the \commandnameX, the curly braces and so on.

The Overlaps parameter lets the regex match single characters multiple times, i.e. after matching commandE, the already matched character sequence is searched again, yielding the contents of commandF.

If you want to incorporate a dictionary of possible \commandX, simply replace the command in the regex by (command1|foo|bar|command12|...).

$\endgroup$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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