5

The following MWE shows that a spurious ".pdf" is added to the URL given in the first argument of \href if (and only if, AFAICS) the latter is used inside \ExplSyntaxOn/\ExplSyntaxOff (it also shows that there is no trouble with the \url command).

Do you see what's going on and how to get rid of this trouble?

\documentclass{article}
\usepackage{expl3}
\usepackage{hyperref}
%
\begin{document}

\section{href}

\subsection{Inside ``ExplSyntax'' (NOT okay)}
\ExplSyntaxOn
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo.pdf}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo}

\section{url}

\subsection{Inside ``ExplSyntax'' (okay)}

\ExplSyntaxOn
\url{http://www.ctan.org/pkg/foo}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\url{http://www.ctan.org/pkg/foo}
\end{document}

Edit: The hack provided by egreg answers my question w.r.t. my MWE, but not in my real use case which involves a package, as shown by the following M(n)WE:

\documentclass{article}
\usepackage{filecontents}

\begin{filecontents}{mynicepackage.sty}
\RequirePackage{xparse}
\RequirePackage{hyperref}
\ProvidesExplPackage{mynicepackage}{2016/04/18} {0.1}{mynicepackage}
\ExplSyntaxOn
\cs_new_protected:Nn \__npkg_package:nnn
{
  \texorpdfstring{%
    \IfBooleanTF {#1}
    {%
      \textsf{#3}
    }{
      \tl_if_empty:nTF {#2}
      {%
        \href{http://www.ctan.org/pkg/#3}{\textsf{#3}}%
      }{%
        \href{#2}{\textsf{#3}}%
      }%
    }
  }{
    \textsf{#3}%
  }
}%
\NewDocumentCommand{\package}{ s O{} m } {
  \__npkg_package:nnn {#1}{#2}{#3}
}
\ExplSyntaxOff
% Below is the hack provided by egreg:
\begingroup
  \makeatletter
  \endlinechar=-1 %
  \catcode`\^^A=14 %
  \catcode`\^^M\active
  \catcode`\%\active
  \catcode`\#\active
  \catcode`\_\active
  \catcode`\$\active
  \catcode`\&\active
  \gdef\hyper@normalise{^^A
    \begingroup
    \@makeother{:}^^A THIS WAS MISSING
    \catcode`\^^M\active
    \def^^M{ }^^A
    \catcode`\%\active
    \let%\@percentchar
    \let\%\@percentchar
    \catcode`\#\active
    \def#{\hyper@hash}^^A
    \def\#{\hyper@hash}^^A
    \@makeother\&^^A
    \edef&{\string&}^^A
    \edef\&{\string&}^^A
    \edef\textunderscore{\string_}^^A
    \let\_\textunderscore
    \catcode`\_\active
    \let_\textunderscore
    \let~\hyper@tilde
    \let\~\hyper@tilde
    \let\textasciitilde\hyper@tilde
    \let\\\@backslashchar
    \edef${\string$}^^A
    \Hy@safe@activestrue
    \hyper@n@rmalise
  }^^A
\endgroup
\end{filecontents}
\usepackage{mynicepackage}
\begin{document}
\package{foo} $\rightarrow$ \url{http://www.ctan.org/pkg/foo.pdf}
\end{document}
  • 1
    I guess that hyperref is fooled by the fact that after \ExplSyntaxOn the colon has category code 11. – egreg Apr 18 '16 at 15:32
  • 1
    href tries to detect the link type and as the : has a different catcode it doesn't recognize the http:. You get something similar outside expl3 with \href{http//www.ctan.org/pkg/foo}{foo}. Not quite sure how one get around it. – Ulrike Fischer Apr 18 '16 at 15:32
  • Inserting an url at code level feels wrong. Through separating code level and input level this should resolve itself. (Of course this depends on your actual use case) – cgnieder Apr 18 '16 at 15:37
  • @egreg Okay but why is there no trouble with \url? – Denis Bitouzé Apr 18 '16 at 15:47
  • 1
    @DenisBitouzé Because \url makes no attempt to decide what type of link you're specifying. – egreg Apr 18 '16 at 15:50
4

Since @egreg already explained what the issue is – here is a suggestion which circumvents the problem:

\documentclass{article}
\usepackage{expl3,xparse,hyperref}

\ExplSyntaxOn
 % this saves the url with all characters catcode other:
\str_const:Nn \c__npkg_standard_url_str { http://www.ctan.org/pkg/ }

% separate formatting:
\cs_new_protected:Npn \__npkg_package_format:n #1
  { \textsf {#1} }

% the main macro
%   #1: boolean (true => no hyperlink)
%   #2: optional url
%   #3: package name
\cs_new_protected:Npn \__npkg_package:nnn #1#2#3
  {
    \texorpdfstring
      {
        \bool_if:nTF {#1}
          { \__npkg_package_format:n {#3} }
          {
            \tl_if_blank:nTF {#2}
              {
                \href
                  { \c__npkg_standard_url_str #3 }
                  { \__npkg_package_format:n {#3} }
              }
              { \href {#2} { \__npkg_package_format:n {#3} } }
          }
      }
      {#3}
  }

% the user macro:
\NewDocumentCommand {\package} {sO{}m}
  { \__npkg_package:nnn {#1} {#2} {#3} }
\ExplSyntaxOff

\begin{document}

\section{\package{acro}}

\package[http://www.ctan.org/tex-archive/graphics/pgf]{pgf}
\package*{tabularx}

\end{document}
|improve this answer|||||
  • 2
    @DenisBitouzé BTW: \texorpdfstring doesn't have any effect when \package and also \__npkg_package:nnn aren't expandable. – cgnieder Apr 18 '16 at 17:30
  • 1
    Just as a comment of style: I think it would be better to have two functions, :nn and :nnn, rather than have that \tl_if_blank:nTF. Then, use \IfNoValueTF in the \NewDocumentCommand to determine which to use. – Sean Allred Apr 18 '16 at 17:40
  • 1
    @SeanAllred I very much dislike code duplication which is why I indeed prefer the \tl_if_blank:nTF version. (Actually one could avoid code duplication and still have two macros with another layer of abstraction. So this probably a matter of taste… ) – cgnieder Apr 18 '16 at 17:44
  • 1
    Your parenthetical is exactly what I would do :-) gist.github.com/vermiculus/178fcf77764e53fdf3c0cde73779e4c5 You could take it further and abstract the * argument into another function (and I think that would be better/purer), but then it just gets a little weird. Especially for a one-off. – Sean Allred Apr 18 '16 at 17:45
5

The \hyper@normalise function applied by \href does not take into account that : might have a different category code.

\documentclass{article}
\usepackage{expl3}
\usepackage{hyperref}

\begingroup
  \makeatletter
  \endlinechar=-1 %
  \catcode`\^^A=14 %
  \catcode`\^^M\active
  \catcode`\%\active
  \catcode`\#\active
  \catcode`\_\active
  \catcode`\$\active
  \catcode`\&\active
  \gdef\hyper@normalise{^^A
    \begingroup
    \@makeother{:}^^A THIS WAS MISSING
    \catcode`\^^M\active
    \def^^M{ }^^A
    \catcode`\%\active
    \let%\@percentchar
    \let\%\@percentchar
    \catcode`\#\active
    \def#{\hyper@hash}^^A
    \def\#{\hyper@hash}^^A
    \@makeother\&^^A
    \edef&{\string&}^^A
    \edef\&{\string&}^^A
    \edef\textunderscore{\string_}^^A
    \let\_\textunderscore
    \catcode`\_\active
    \let_\textunderscore
    \let~\hyper@tilde
    \let\~\hyper@tilde
    \let\textasciitilde\hyper@tilde
    \let\\\@backslashchar
    \edef${\string$}^^A
    \Hy@safe@activestrue
    \hyper@n@rmalise
  }^^A
\endgroup

\begin{document}

\section{href}

\subsection{Inside ``ExplSyntax'' (NOT okay)}
\ExplSyntaxOn
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo.pdf}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo}

\section{url}

\subsection{Inside ``ExplSyntax'' (okay)}

\ExplSyntaxOn
\url{http://www.ctan.org/pkg/foo}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\url{http://www.ctan.org/pkg/foo}
\end{document}

You can check that the link is as expected.


The example you added after my answer is very different from the one in the original question. You're calling \href in the argument to another command and so the argument to \href would be already tokenized and not be influenced by \hyper@normalise.

You can use \tl_rescan:nn in order to force rereading of the argument.

\RequirePackage{filecontents}

\begin{filecontents*}{mynicepackage.sty}
\RequirePackage{xparse}
\RequirePackage{hyperref}
\ProvidesExplPackage{mynicepackage}{2016/04/18} {0.1}{mynicepackage}

\NewDocumentCommand{\package}{ s o m }
 {
  \IfBooleanTF { #1 }
   {
    \textsf{#3}
   }
   {
    \IfNoValueTF { #2 }
     {
      \__npkg_package:nn { http://www.ctan.org/pkg/#3 } { #3 }
     }
     {
      \__npkg_package:nn { #2 } { #3 }
     }
   }
 }

\cs_new_protected:Nn \__npkg_package:nn
 {
  \texorpdfstring { \tl_rescan:nn { } { \href{#1}{\textsf{#2}} } } { #2 }
 }
\end{filecontents*}

\documentclass{article}
\usepackage{mynicepackage}

\begin{document}

\package{foo} $\rightarrow$ \url{http://www.ctan.org/pkg/foo.pdf}

\end{document}

Note the different definition of the \package command, according to the guidelines.

If a URL contains a % you're doomed, of course, unless you escape it.

enter image description here

|improve this answer|||||
  • Should I report this to Heiko? – Denis Bitouzé Apr 18 '16 at 16:00
  • @DenisBitouzé I think you should – egreg Apr 18 '16 at 16:12
  • Trouble reported. – Denis Bitouzé Apr 18 '16 at 16:26
  • 1
    @DenisBitouzé Neither it would work if you had % in the URL – egreg Apr 18 '16 at 17:42
  • 2
    Today's upgrade of TeX Live/pretest shows that hyperref has been amended to also have \@makeother\: in \hyper@normalise; it will be in TeX Live 2016. – egreg Apr 26 '16 at 6:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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