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 would like to extract the leading number and subsequent text from a string. I have an idea of using the xstring package to gobble characters from the right until I end up with a number or an empty string, but wondering if there is an easier way to do it. The leading number is all the text up until the first character that is not a digit, period, or a plus and minus sign.

Don't really need to worry about error cases such as:

  • an additional +, - within the number
  • more digits after first non-numerical digit

So, I should get the following output with the definitions of \ExtractLeadingNumber and \ExtractTralingNonDigits completed:

enter image description here

Code:

\documentclass[border=2pt]{standalone}
\usepackage{booktabs}

\newcommand*{\ExtractLeadingNumber}[1]{#1}%
\newcommand*{\ExtractTralingNonDigits}[1]{#1}%

% ignore #2 and #3 as those are only needed to produce the desired output
\newcommand{\Test}[3]{#1&\ExtractLeadingNumber{#1}&\ExtractTralingNonDigits{#1}\\}%
%\newcommand{\Test}[3]{#1&#2&#3\\}% This produces desired output

\begin{document}
\begin{tabular}{l r r r}
 & &Number &Non-Digits\\

\midrule
Decimal:
&\Test{ 1.01abc}{ 1.01}{abc}
&\Test{+2.01abc}{+2.01}{abc}
&\Test{-3.01abc}{-3.01}{abc}

\midrule
Integer:
&\Test{  abc}{  }{abc}
&\Test{ 5abc}{ 5}{abc}
&\Test{+6abc}{+6}{abc}
&\Test{-7abc}{-7}{abc}

\midrule
Floating Point:
&\Test{ 5.34abc}{ 5.34}{abc}
&\Test{+6.34abc}{+6.34}{abc}
&\Test{-7.34abc}{-7.34}{abc}

\midrule
Number Only:
&\Test{3}{3}{}
&\Test{3.2}{3.2}{}
&\Test{-5.1}{-5.1}{}
&\Test{+5.1}{+5.1}{}

\midrule
No Digits:
&\Test{abc}{}{abc}

\midrule
Formatted Text:
&\Test{  8$abc_1$}{  8}{$abc_1$}
&\Test{-8.2$abc_1$}{-8.2}{$abc_1$}
&\Test{+$abc_1$}{+}{$abc_1$}
&\Test{$abc_1$}{}{$abc_1$}% no digits
\end{tabular}
\end{document}
share|improve this question
1  
Do you want a solution using xstring, or are you aiming for plain tex? –  cmhughes Nov 18 '11 at 6:21
    
@cmhughes: xstring would be fine –  Peter Grill Nov 18 '11 at 7:13
add comment

3 Answers

up vote 5 down vote accepted

Here is a solution with xstring:

\documentclass[border=2pt]{standalone}
\usepackage{booktabs}
\usepackage{xstring}
\makeatletter
% first, need to fix a bug in xstring:
\@xs@newmacro\IfDecimal{}{1}{0}{%
    \@xs@formatnumber{#1}\@xs@reserved@A
    \decimalpart\z@
    \afterassignment\@xs@defafterinteger\integerpart\@xs@reserved@A\relax\@xs@nil
    \expandafter\@xs@testdot\@xs@afterinteger\@xs@nil
    \ifx\@empty\@xs@afterdecimal\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}

\newcommand*\Test[1]{%
    \IfBeginWith{#1}{ }{\StrBehind{#1}{ }[\temp@@]}{\def\temp@@{#1}}%
    \IfDecimal\temp@@
        {\def\temp@{#1&#1&}}
        {\def\temp@{#1&}%
        \StrBefore{#1}\@xs@afterdecimal[\temp@@]%
        \expandafter\g@addto@macro\expandafter\temp@\expandafter{\temp@@&}%
        \expandafter\g@addto@macro\expandafter\temp@\expandafter{\@xs@afterdecimal}%
        }%
    \temp@\\}
\makeatother
\begin{document}
\begin{tabular}{l r r r}
 & &Number &Non-Digits\\
\midrule
Decimal:
&\Test{ 1.01abc}
&\Test{+2.01abc}
&\Test{-3.01abc}

\midrule
Integer:
&\Test{  abc}
&\Test{ 5abc}
&\Test{+6abc}
&\Test{-7abc}

\midrule
Floating Point:
&\Test{ 5.34abc}
&\Test{+6.34abc}
&\Test{-7.34abc}

\midrule
Number Only:
&\Test{3}
&\Test{3.2}
&\Test{-5.1}
&\Test{+5.1}

\midrule
No Digits:
&\Test{abc}

\midrule
Formatted Text:
&\Test{  8$abc_1$}
&\Test{-8.2$abc_1$}
&\Test{+$abc_1$}
&\Test{$abc_1$}
\end{tabular}
\end{document}

EDIT: here is how to do with \ExtractLeadingNumber and \ExtractTralingNonDigits

\makeatletter
% first, need to fix a bug in xstring:
\@xs@newmacro\IfDecimal{}{1}{0}{%
    \@xs@formatnumber{#1}\@xs@reserved@A
    \decimalpart\z@
    \afterassignment\@xs@defafterinteger\integerpart\@xs@reserved@A\relax\@xs@nil
    \expandafter\@xs@testdot\@xs@afterinteger\@xs@nil
    \ifx\@empty\@xs@afterdecimal\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}

\newcommand*\ExtractLeadingNumber[1]{%
    \IfBeginWith{#1}{ }{\StrBehind{#1}{ }[\temp@@]}{\def\temp@@{#1}}%
    \IfDecimal\temp@@{#1}{\StrBefore{#1}\@xs@afterdecimal}%
}
\newcommand*\ExtractTralingNonDigits[1]{%
    \IfBeginWith{#1}{ }{\StrBehind{#1}{ }[\temp@@]}{\def\temp@@{#1}}%
    \IfDecimal\temp@@{}\@xs@afterdecimal
}
\makeatother

\newcommand*\Test[1]{#1&\ExtractLeadingNumber{#1}&\ExtractTralingNonDigits{#1}\\}
share|improve this answer
    
This works pretty well. But for my application, I need to be able to extract the two parts separately which is why I had set up the \ExtractLeadingNumber, and \ExtractTralingNonDigits in the MWE. And since I have not gotten the hang of the expandafter, I am not able to adapt this code as is. So either there should be two separate commands, or perhaps do something like pgf does with math where you access \pgfmathresult immediately after (i.e., a macro which would use \renewcommand to set define the values of two macros. –  Peter Grill Nov 18 '11 at 20:47
    
I edited the the macro version so that cut-and-paste yields the complete code required. Also, how do I use this in a computation as in \pgfmathsetmacro{\result}{7 + \ExtractLeadingNumber{123K}}. Can you add some detail about the bug that you were fixing here from xstring. –  Peter Grill Nov 20 '11 at 3:58
    
You never said that you wanted to use \ExtractLeadingNumber{123K}} in \pgfmathparse (the best would be to explain exactly what you want)! Indded, since \ExtractLeadingNumber{123K}} is not purely expandable, you cannot write it in the argument of \pfgmathparse. You should store the result of \ExtractLeadingNumber{123K}} in a macro (for example \resultextract) and then write \pgfmathsetmacro{\result}{7 + \resultextract}. The "bug" in xstring should have benn fixed a year ago, but I forgot it: a \detokenize had to be removed. –  unbonpetit Nov 20 '11 at 8:24
add comment

An approach using the LaTeX3 l3regex module

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{array,booktabs,expl3,l3regex}
\ExplSyntaxOn
\tl_new:N \l_extract_tl
\regex_set:Nn \l_extract_tl { ^\s*([+-]?\d*\.?\d*)\s*(.*) }
\seq_new:N \l_extract_seq
\tl_new:N \NumberValue
\tl_new:N \OtherValue
\cs_new_protected:Npn \extract_number:n #1
  {
    \regex_extract_once:NnN  \l_extract_tl {#1} \l_extract_seq
    \tl_gset:Nx \NumberValue { \seq_item:Nn \l_extract_seq { 1 } }
    \tl_gset:Nx \OtherValue { \seq_item:Nn \l_extract_seq { 2 } }
  }
\cs_new_protected:Npn \Test #1
  {
    \extract_number:n {#1}
    & \detokenize{#1} & \NumberValue & \OtherValue
  }
\ExplSyntaxOff
\begin{document}
\begin{tabular}{l>{\ttfamily}r>{\ttfamily}r>{\ttfamily}r}
  \toprule
             & \multicolumn{1}{r}{Input} & 
               \multicolumn{1}{r}{Digit} & \multicolumn{1}{r}{Non-digit} \\
  \midrule
   Decimal:  \Test{ 1.01abc}               \\
             \Test{+2.01abc}               \\ 
             \Test{-3.01abc}               \\
  \midrule
   Integer:  \Test{  abc}                  \\
             \Test{ 5abc}                  \\ 
             \Test{+6abc}                  \\
             \Test{-7abc}                  \\
  \midrule
   Floating Point: \Test{ 5.34abc}         \\
                   \Test{+6.34abc}         \\
                   \Test{-7.34abc}         \\
  \midrule
   Number Only:    \Test{3}                \\
                   \Test{3.2}              \\ 
                   \Test{-5.1}             \\
                   \Test{+5.1}             \\
  \midrule
   No Digits:      \Test{abc}              \\
  \midrule
   Formatted Text: \Test{  8$abc_1$}       \\ 
                   \Test{-8.2$abc_1$}      \\ 
                   \Test{+$abc_1$}         \\
                   \Test{$abc_1$}          \\
  \bottomrule
\end{tabular}
\end{document}

Currently, this module is 'experimental' hence loading it separately from expl3, but I'd expect it to move to 'kernel' in the near-ish future (before the end of the year).

The way that this works is that when we do a regular expression match, the capturing groups are stored in a sequence indexed from 0 (the complete match) upward. So I've got the first capture group as the numerical part and the second as the non-numerical. Notice that I've also included \s* to remove any leading spaces from those two groups: if you miss that out then you'll also pick up the spaces as part of the match.

Also notice that the results here are detokenized, so if you want to have formatted text you'd need to \scantokens the results. (Something as simple as \scantokens\expandafter{\OtherValue} would do here.)

share|improve this answer
    
In the CTAN version, I think we have \regex_extract:NnN rather than \regex_extract_once:NnN. This will change at the weekend, as I'm due a CTAN snapshot. –  Joseph Wright Nov 18 '11 at 9:44
    
Notice that I've used a 'pre-compiled' regex here as it's used many times and performance can therefore be gained by setting up the regex first. You use \regex_extract_once:nnN with a 'one-use' regex. –  Joseph Wright Nov 18 '11 at 9:45
    
Seems to work fine once I change \regex_extract_once:NnN to \regex_extract:NnN in the MWE. I guess I am going to have to learn this syntax one of these days. –  Peter Grill Nov 18 '11 at 20:52
    
@PeterGrill As I said, I'll update CTAN probably tomorrow to deal with that. Of course, someone could convert Bruno's code to non-LaTeX3 syntax (in this case it might not be so bad as a lot of the code is primitives). –  Joseph Wright Nov 18 '11 at 22:04
    
@Joseph or someone can simply write a LaTeX2e-like wrapper for that module. \def\ExtractOnce{\@ifstar\regex_extract_once:NnN\regex_extract_once:nnN} etc. –  Bruno Le Floch Nov 19 '11 at 4:36
add comment

If you can use luatex, you may use a proper parser (the code below is in ConTeXt, just because I don't know all the details of using luatex in LaTeX).

 \startluacode
  local P, R, S, V, match = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.match
  local Ct, C, Cs, Cc = lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc

  local format = string.format

  local digit    = R("09")
  local sign     = S('+-')
  local integer  = sign^0 * digit^0 -- NOTE: I'd rather use digit^1, but
                                    -- the requirements want to capture a
                                    --  single sign as well
  local float    = sign^0 * digit^0 * P('.') * digit^1
  local space    = P(" ")^0

  local number   = Cs(float + integer)
  local any      = Cs(P(1)^0)

  local number_value = Cc("\\global\\def\\NumberValue{%s}") * number / format
  local other_value  = Cc("\\global\\def\\OtherValue{%s}")  * any    / format
  local parser = Cs(space * number_value * other_value)

  function commands.extract_number(s)
      context(match(parser,s))
  end
\stopluacode

\unprotect
\def\extract#1%
    {\let\NumberValue\relax
     \let\OtherValue \relax
     \ctxcommand{extract_number(\!!bs\detokenize{#1}\!!es)}}
\protect

You can then use this as follows.

\def\Test#1%
    {\extract{#1}%
     #1 \NC \NumberValue \NC \OtherValue}

\starttext

\starttabulate[|l|r|r|r|]
  \HL
  \NC           \NC Input \NC Digit \NC Non-Digit \NC \NR
  \HL
  \NC Decimal:  \NC \Test{ 1.01abc}               \NC \NR
  \NC           \NC \Test{+2.01abc}               \NC \NR 
  \NC           \NC \Test{-3.01abc}               \NC \NR
  \HL
  \NC Integer:  \NC \Test{  abc}                  \NC \NR
  \NC           \NC \Test{ 5abc}                  \NC \NR 
  \NC           \NC \Test{+6abc}                  \NC \NR
  \NC           \NC \Test{-7abc}                  \NC \NR
  \HL
  \NC Floating Point: \NC \Test{ 5.34abc}         \NC \NR
  \NC                 \NC \Test{+6.34abc}         \NC \NR
  \NC                 \NC \Test{-7.34abc}         \NC \NR
  \HL
  \NC Number Only:    \NC \Test{3}                \NC \NR
  \NC                 \NC \Test{3.2}              \NC \NR 
  \NC                 \NC \Test{-5.1}             \NC \NR
  \NC                 \NC \Test{+5.1}             \NC \NR
  \HL
  \NC No Digits:      \NC \Test{abc}              \NC \NR
  \HL
  \NC Formatted Text: \NC \Test{  8$abc_1$}       \NC \NR 
  \NC                 \NC \Test{-8.2$abc_1$}      \NC \NR 
  \NC                 \NC \Test{+$abc_1$}         \NC \NR
  \NC                 \NC \Test{$abc_1$}          \NC \NR
  \HL
\stoptabulate
\stoptext

which gives

enter image description here

share|improve this answer
add comment

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.