xtag-map.mkii / last modification: 2020-01-30 14:15
%D \module
%D   [       file=xtag-map,
%D        version=2000.12.20,
%D          title=\CONTEXT\ XML Macros,
%D       subtitle=Remapping,
%D         author=Hans Hagen,
%D           date=\currentdate,
%D      copyright={PRAGMA ADE \& \CONTEXT\ Development Team}]
%C
%C This module is part of the \CONTEXT\ macro||package and is
%C therefore copyrighted by \PRAGMA. See mreadme.pdf for
%C details.

%D There is a more versatile mapper available in \type {xtag-rem.tex}!

%D We also need something that lets content as-is, like for
%D instance XML embedded in a chemical caption.

\writestatus{loading}{ConTeXt XML Macros / Remapping}

%D A fundamental characteristic of \TEX\ is that much
%D processing depends on picking up one or more arguments and
%D acting upon them. In \type {xtag-ini} we have implemented
%D the normal (high) level interface between \XML\ and
%D \CONTEXT, and there we already saw some ways to pick up an
%D element as argument.
%D
%D In this module we will implement a preprocessor. An element
%D that feeds its content to the preprocessor, becomes a token
%D list consisting of \TEX\ macros, which in turn may expand to
%D their meanings.
%D
%D This module is only tested with \ETEX. In principle we can
%D make it work with good old \TEX, but we see no objection
%D against using \ETEX, especially since it's part of every
%D grown up \TEX\ distribution.

\unprotect

\let\normalparseXMLescape \parseXMLescape
\let\normalparseXMLelement\parseXMLelement
\let\normalparseXMLprocess\parseXMLprocess

\let\normaldoXMLelement   \doXMLelement
\let\normaldoXMLentity    \doXMLentity

\def\setnormalXMLhandler
  {\let\doXMLelement   \normaldoXMLelement
   \let\parseXMLelement\normalparseXMLelement
   \let\parseXMLescape \normalparseXMLescape
   \let\parseXMLprocess\normalparseXMLprocess
   \let\doXMLentity    \normaldoXMLentity}

%D A careful reader will notice that we do a full expansion of
%D the content of the element, although commands that are
%D protected will stay untouched. In this stage we also
%D collect key|/|value pairs and pass them onto the \TEX\
%D macros if needed. Again, we need a fully expandable parser
%D to handle this, which make the core macros slightly
%D unreadable.
%D
%D The interface presented here evolved from an older module,
%D written on top of \type {m-sgml}, that could take care of
%D \MATHML\ (version 1). The implementation here is more
%D advanced in the sense that it permits all kind of parsers.

\def\findendofXMLelement#1% space after 0/1 prevents auto \relax
  {\if#1>0                  \else
   \if#1/1 \endofXMLelementE\else
   \if#1"\endofXMLelementD  \else
   \if#1'\endofXMLelementS  \else
         \endofXMLelementN  \fi\fi\fi\fi}

\def\endofXMLelementE#1\fi\fi\fi\fi#2>{\fi\fi}
\def\endofXMLelementD#1\fi\fi\fi\fi#2"{\fi\fi\fi   \findendofXMLelement}
\def\endofXMLelementS#1\fi\fi\fi\fi#2'{\fi\fi\fi\fi\findendofXMLelement}
\def\endofXMLelementN  \fi\fi\fi\fi   {\fi\fi\fi\fi\findendofXMLelement}

% not faster
%
% \def\findendofXMLelement#1%
%   {\csname**\if#1>>\else\if#1//\else\if#1""\else\if#1''\else.\fi\fi\fi\fi\endcsname}
%
% \def\findendofXMLelement#1%
%   {\csname**\ifcsname**#1\endcsname#1\else.\fi\endcsname}
%
% \setvalue{**>}{0 }
% \setvalue{**/}#1>{1 }
% \setvalue{**"}#1"{\findendofXMLelement}
% \setvalue{**'}#1'{\findendofXMLelement}
% \letvalue{**.}\findendofXMLelement

\newif\ifremapXMLunknown

%D We need three steps to avoid namespace: tag since comment
%D and processing instructions don't have a namespace. The
%D first step distinguishes between comment, processing
%D instructions and elements. The second step (which is
%D defined in the main mapping macro) either or not grabs the
%D namespace. We may extend this model later to a more
%D versatile one, using remapping.

%D Parsing escapes is done by specific macros. For the
%D moment we assume that the sequence ends with an \type {>}
%D (which is definietly not the case for \type {CDATA}).

\long\def\remapXMLescape#1#2>{}

%D Processing instructions are remapped and only certain
%D cases are handled.

\long\def\remapXMLprocess#1#2{\xmlp{procins/}{X}{#1}{#2}}

%D This one is more efficient (although no one will notice
%D this since this macro is used seldom).

\long\def\remapXMLprocess{\xmlp{procins/}{X}}

%D Element need a bit more work; \type {#4} consumes spaces.

% \def\remapXMLunknownONE#1#2% name args
%   {\ifremapXMLunknown\remapXMLone{\s!unknown}{#1 #2}\fi}
%
% \def\remapXMLunknownTWO#1#2% name args
%   {\ifremapXMLunknown\remapXMLtwo{\s!unknown}{#1 #2}\fi}
%
% \def\remapXMLunknownTHREE#1#2% name args
%   {\ifremapXMLunknown\remapXMLthree{\s!unknown/}{#1 #2}\fi}
%
% \long\def\remapXMLelement#1#2 #3>#4% todo: we need to get rid of the end /
%   {\ifcase\findendofXMLelement#2#3>%
%      \if#1/%
%        \expandafter\ifx\csname\@@XML\@@XMLmapping:#2:M\endcsname\relax
%          \remapXMLunknownONE{#2}{}%
%        \else
%          \remapXMLone{#2}{}%
%        \fi
%      \else
%        \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2:M\endcsname\relax
%          \remapXMLunknownTWO{#1#2}{#3}%
%        \else
%          \remapXMLtwo{#1#2}{#3}%
%        \fi
%      \fi
%    \else
%      \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2:M\endcsname\relax
%        \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2/:M\endcsname\relax
%          \remapXMLunknownTHREE{#1#2}{#3}%
%        \else
%          \remapXMLthree{#1#2/}{#3}%
%        \fi
%      \else
%        \remapXMLthree{#1#2}{#3}%
%      \fi
%    \fi#4}

\def\remapXMLthreeempty#1%
  {\remapXMLthree{#1/}}

\def\remapXMLunknownONE#1#2% name args
  {\ifremapXMLunknown\remapXMLone\s!unknown{#1 #2}\fi}

\def\remapXMLunknownTWO#1#2% name args
  {\ifremapXMLunknown\remapXMLtwo\s!unknown{#1 #2}\fi}

\def\remapXMLunknownTHREE#1#2% name args
  {\ifremapXMLunknown\remapXMLthreeempty\s!unknown{#1 #2}\fi}

\long\def\remapXMLelement#1#2 #3>#4% todo: we need to get rid of the end /
  {\ifcase\findendofXMLelement#2#3>%
     \if#1/%
       \expandafter\ifx\csname\@@XML\@@XMLmapping:#2:M\endcsname\relax
         \@EA\remapXMLunknownONE
       \else
         \@EA\remapXMLone
       \fi{#2}\empty
     \else
       \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2:M\endcsname\relax
         \@EA\remapXMLunknownTWO
       \else
         \@EA\remapXMLtwo
       \fi{#1#2}{#3}%
     \fi
   \else
     \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2:M\endcsname\relax
       \expandafter\ifx\csname\@@XML\@@XMLmapping:#1#2/:M\endcsname\relax
         \@EAEAEA\remapXMLunknownTHREE
       \else
         \@EAEAEA\remapXMLthreeempty
       \fi
     \else
       \@EA\remapXMLthree
     \fi{#1#2}{#3}%
   \fi#4}

\unexpanded\def\xmlr#1#2{\csname\@@XML\@@XMLmapping:#1:#2\endcsname}
\unexpanded\def\xmlp#1#2{\csname\@@XML             :#1:#2\endcsname}
\def   \expandedxmlr#1#2{\csname\@@XML\@@XMLmapping:#1:#2\endcsname}

\def\expandXMLremapping{\let\xmlr\expandedxmlr}

\def\@@XMLremap{XMLremap}

\def\remapXMLone#1#2%
  {\ifcase\csname\@@XMLremap\csname\@@XML\@@XMLmapping:#1:M\endcsname\endcsname
   \or % GCPA
     \XMLeg\XMLeg
   \or % GCP-
     \XMLeg
   \or % GC-A
     \XMLeg\XMLeg
   \or % GC--
     \XMLeg
   \or % -CPA
     \XMLeg
   \or % -CP-
     %
   \or % -C-A
     \XMLeg
   \or % -C--
     %
   \or % G---
     \XMLeg
   \or % GLR-
     \xmlr{#1}{R}\XMLeg
   \or % -LR-
     \xmlr{#1}{R}%
   \fi}

\def\remapXMLtwo#1#2%
  {\ifcase\csname\@@XMLremap\csname\@@XML\@@XMLmapping:#1:M\endcsname\endcsname
   \or % GCPA
     \XMLbg\xmlr{#1}{X}{#2}\XMLbg
   \or % GCP-
     \XMLbg\xmlr{#1}{X}{#2}%
   \or % GC-A
     \XMLbg\xmlr{#1}{X}\XMLbg
   \or % GC--
     \XMLbg\xmlr{#1}{X}%
   \or % -CPA
     \xmlr{#1}{X}{#2}\XMLbg
   \or % -CP-
     \xmlr{#1}{X}{#2}%
   \or % -C-A
     \xmlr{#1}{X}\XMLbg
   \or % -C--
     \xmlr{#1}{X}%
   \or % G---
     \XMLbg
   \or % GLR-
     \XMLbg\xmlr{#1}{L}%
   \or % -LR-
     \xmlr{#1}{L}%
   \fi}

\def\remapXMLthree#1#2%
  {\ifcase\csname\@@XMLremap\csname\@@XML\@@XMLmapping:#1:M\endcsname\endcsname
   \or % GCPA
     \XMLbg\xmlr{#1}{X}{#2}\XMLbg\XMLeg\XMLeg
   \or % GCP-
     \XMLbg\xmlr{#1}{X}{#2}\XMLeg
   \or % GC-A
     \XMLbg\xmlr{#1}{X}\XMLbg\XMLeg\XMLeg
   \or % GC--
     \XMLbg\xmlr{#1}{X}\XMLeg
   \or % -CPA
     \xmlr{#1}{X}{#2}\XMLbg\XMLeg
   \or % -CP-
     \xmlr{#1}{X}{#2}%
   \or % -C-A
     \xmlr{#1}{X}\XMLbg\XMLeg
   \or % -C--
     \xmlr{#1}{X}%
   \or % G---
     \XMLbg\XMLeg
   \fi}

\scratchtoks\@EA{\string{}  \edef\XMLbg{\the\scratchtoks}
\scratchtoks\@EA{\string}}  \edef\XMLeg{\the\scratchtoks}

\chardef\XMLremapGCPA = 1  % {\command {arg} {   } }
\chardef\XMLremapGCP  = 2  % {\command {arg}       } %
\chardef\XMLremapGCA  = 3  % {\command       {   } }
\chardef\XMLremapGC   = 4  % {\command             } %
\chardef\XMLremapCPA  = 5  %  \command {arg} {   }
\chardef\XMLremapCP   = 6  %  \command {arg}         %
\chardef\XMLremapCA   = 7  %  \command       {   }
\chardef\XMLremapC    = 8  %  \command               %
\chardef\XMLremapG    = 9  % {                     }
\chardef\XMLremapGLR  = 10 % { \bcom            \ecom }
\chardef\XMLremapLR   = 11 %   \bcom            \ecom

%D The remapping is controlled by only a few definition
%D macros, that both deal with elements. We distinguish
%D between normal and empty elements.
%D
%D \starttyping
%D \remapXMLsequence [name] [result map] \unexpandablecommand
%D \remapXMLsequence [name] [result map] \unexpandablecommand
%D \stoptyping
%D
%D The \MATHML\ module demonstrates how these can be used.
%D The element is converted into a sequence with one or more
%D of the following components.
%D
%D \starttyping
%D { \command {parameters} {argument} }
%D \stoptyping
%D
%D The following combinations are supported.
%D
%D \starttabulate[|c|c|c|c|c|]
%D \NC GCPA \NC grouped \NC command \NC parameters \NC argument \NC \NR
%D \NC GCP  \NC grouped \NC command \NC parameters \NC          \NC \NR
%D \NC GCA  \NC grouped \NC command \NC            \NC argument \NC \NR
%D \NC GC   \NC grouped \NC command \NC            \NC          \NC \NR
%D \NC CPA  \NC         \NC command \NC parameters \NC argument \NC \NR
%D \NC CP   \NC         \NC command \NC parameters \NC          \NC \NR
%D \NC CA   \NC         \NC command \NC            \NC argument \NC \NR
%D \NC C    \NC         \NC command \NC            \NC          \NC \NR
%D \NC G    \NC grouped \NC         \NC            \NC          \NC \NR
%D \stoptabulate
%D
%D Empty elements (singular ones) never get an argument,
%D which makes sense, since they have at most parameters.

\def\remapXMLsequence{\doquadrupleargument\doremapXML []}
\def\remapXMLsingular{\doquadrupleargument\doremapXML[/]}

\def\doremapXML[#1][#2][#3][#4]%
  {\iffourthargument
     \def\next{\dodoremapXML[#2][#1][#3][#4]}%
   \else
     \def\next{\dodoremapXML[\@@XMLmapping][#1][#2][#3]}%
   \fi
   \next}

%\def\dodoremapXML[#1][#2][#3][#4]% class / name pattern
%  {\doifinstringelse{LR}{#4}
%     {\let\next\doremapXMLtwo}
%     {\let\next\doremapXMLone}%
%   \next[#1][#2][#3][#4]}%

\def\dodoremapXML[#1][#2][#3][#4]% class / name pattern
  {\doifinstringelse{LR}{#4}\doremapXMLtwo\doremapXMLone[#1][#2][#3][#4]}

\def\doremapXMLone[#1][#2][#3][#4]#5%
  {\setvalue{\@@XML#1:#3#2:M}{#4}%
   \setvalue{\@@XML#1:#3#2:X}{#5}}

\def\doremapXMLtwo[#1][#2][#3][#4]#5#6%
  {\setvalue{\@@XML#1:#3:M}{#4}%
   \setvalue{\@@XML#1:#3:L}{#5}%
   \setvalue{\@@XML#1:#3:R}{#6}}

\let\dowithentity\empty

%D We handle processing instructions and unknown elements with:

\remapXMLsingular [procins]    [CPA] \normalparseXMLprocess
\remapXMLsingular [\s!unknown] [CPA] \doXMLunknownSI
\remapXMLsequence [\s!unknown] [CPA] \doXMLunknownSE

\def\doXMLunknownSI#1#2{{\tttf[#1 #2]}}
\def\doXMLunknownSE#1#2{{\tttf[#1 #2]}}

%D In a similar way, we can remap entities.

\def\remapXMLentity#1;#2%
  {\doremapXMLentity{#1}#2}%

\def\doremapXMLentity
  {\xmlrent}

% \unexpanded\def\xmlrent#1%
%   {\getXMLentity{#1}}
%
% replaced by:

\unexpanded\def\xmlrent#1%
  {\doXMLentity#1;}

%D The remapping is taken care of by the following macro,
%D which takes three arguments.
%D
%D \starttyping
%D \XMLremapdata{before}{after}{content}
%D \stoptyping
%D
%D After the remapping, the content is executed (expanded)
%D under the normal \TEX\ catcode regime. The intermediate
%D result can be traced by turning on the following switch.

\newif\iftraceXMLremapping

\newtoks \everyXMLremapping

\appendtoks
  \defineXMLentity[tex-hash]{\letterhash}%
  \defineXMLentity[tex-bar]{\myspecialnormalvert}%
\to \everyXMLremapping

\def\setnormalXMLentities% will change ! ! ! ! !
  {\defineXMLentity[tex-hash]\letterhash
   \defineXMLentity[tex-dollar]\letterdollar
   \defineXMLentity[tex-percent]\letterpercent
   \defineXMLentity[tex-backslash]\letterbackslash
   \defineXMLentity[tex-hat]\letterhat
   \defineXMLentity[tex-underscore]\letterunderscore
   \defineXMLentity[tex-leftbrace]\letterbgroup
   \defineXMLentity[tex-rightbrace]\letteregroup
   \defineXMLentity[tex-bar]\letterbar}

\let\XMLremappedpar\empty

%D Here we implement the second step in the element grabber.

\long\def\XMLremapdata
  {\dosingleempty\doXMLremapdata}

\long\def\doXMLremapdata[#1]#2#3#4%
  {\bgroup
   \startXMLmapping[#1]%
   % enable unknown elements (should be macro)
   \doifsomething{#1}
     {\doifdefinedelse{\@@XML#1:\s!unknown:M}
        {\remapXMLunknowntrue}{\remapXMLunknownfalse}}%
   %
   \pushmacro\doXMLentity % needed ?
   % this will change, proper split in element itself
   \ifx\currentXMLnamespace\empty
     \let\parseXMLelement\remapXMLelement
   \else
     % here we need to get rid of the namespace; we also
     % have to preserve the leading / if present
     \@EA\long\@EA\def\@EA\parseXMLelement\@EA
       ##\@EA1\currentXMLnamespace:{\remapXMLelement##1}%
     % ##2 removes leading spaces
   \fi
   %
   \let\parseXMLescape \remapXMLescape
   \let\parseXMLprocess\remapXMLprocess
   %
   \let\doXMLentity    \remapXMLentity
   %
   \enableXML % sets entities
   \enableXMLexpansion
   \let\par\XMLremappedpar
   \the\everyXMLremapping
   %\ignorelines
   \catcode\tabasciicode      \spacecatcode
   \catcode\endoflineasciicode\spacecatcode
   \catcode\formfeedasciicode \spacecatcode
   \catcode\endoffileasciicode\spacecatcode
   \pushmacro\unicodechar
   \let\unicodechar\relax
   \xdef\remappedXMLdata{#4\empty}%
   \popmacro\unicodechar
   \let\par\endgraf
   \popmacro\doXMLentity % needed ?
   \disableXMLexpansion
   \catcode`\{=\begingroupcatcode
   \catcode`\}=\endgroupcatcode
   \catcode`\\=\escapecatcode
   \iftraceXMLremapping
     \ifmmode\vbox\fi\bgroup
     \defconvertedcommand\ascii\remappedXMLdata
     \tttf\veryraggedright\ascii\par
     \writestatus{xml-remap}{\ascii}%
     \egroup
   \fi
   #2\scantokens\@EA{\remappedXMLdata\empty\empty}#3%
   \stopXMLmapping
   \egroup}

% testcase:
%
% aap‒noot coördinatie – één
%
% \startXMLdata
% aap‒noot coördinatie – één
% <formula><math><mtext>aap‒noot coördinatie – één</mtext></math></formula>
% \stopXMLdata
%
% weird case:
%
% \chardef\XMLtokensreduction\zerocount
% \startXMLdata
% <formula><math><mtext>\"{a}\"{o}\"{u}\v{c}\v{s}\v{z}</mtext></math></formula>
% \stopXMLdata
%
% \chardef\XMLtokensreduction\plustwo
% \startXMLdata
% <formula><math><mtext>\"{a}\"{o}\"{u}\v{c}\v{s}\v{z}</mtext></math></formula>
% \stopXMLdata

% rename to better names

\newtoks  \XMLRtoks
\newcount \nofXMLRchildren

\def\naturalxmlr#1#2{\getvalue{\@@XML\@@XMLmapping:#1:#2}}

\def\ignoreXMLRelement#1#2{}
\def\normalXMLRelement#1#2{#2}

\let\nextXMLRelement  \empty
\let\firstXMLRelement \empty
\let\secondXMLRelement\empty

% \def\withnextXMLRelement#1%
%   {\pushmacro\dowithnextXMLRelement
%    \def\dowithnextXMLRelement##1##2##3##4##5%
%      {\popmacro\dowithnextXMLRelement
%       \def\nextXMLRelement{##1{##2}{##3}{##4}{##5}}%
%       #1}%
%    \doifnextcharelse\empty\empty\dowithnextXMLRelement}
%
% better and faster:

\def\dowithnextXMLRelement#1#2#3#4#5#6%
  {\def\nextXMLRelement{#2{#3}{#4}{#5}{#6}}#1}%

\def\withnextXMLRelement#1%
  {\doifnextcharelse\empty\empty{\dowithnextXMLRelement{#1}}}

\def\withnexttwoXMLRelements#1%
  {\pushmacro\firstXMLRelement
   \pushmacro\secondXMLRelement
   \withnextXMLRelement
     {\let\firstXMLRelement\nextXMLRelement
      \withnextXMLRelement
        {\let\secondXMLRelement\nextXMLRelement
         #1%
         \popmacro\secondXMLRelement
         \popmacro\firstXMLRelement}}}

\def\withnextthreeXMLRelements#1% korter, met two
  {\pushmacro\firstXMLRelement
   \pushmacro\secondXMLRelement
   \pushmacro\thirdXMLRelement
   \withnextXMLRelement
     {\let\firstXMLRelement\nextXMLRelement
      \withnextXMLRelement
         {\let\secondXMLRelement\nextXMLRelement
          \withnextXMLRelement
            {\let\thirdXMLRelement\nextXMLRelement
             #1%
             \popmacro\thirdXMLRelement
             \popmacro\secondXMLRelement
             \popmacro\firstXMLRelement}}}}

\def\doifXMLRchildelse#1#2#3#4%
  {\pushmacro\xmlr
   \def\next{#4}%
   \def\xmlr##1##2##3##4% the / should be sorted out in the mapper
     {\rawdoifinsetelse{##1}{#1}
        {\def\next{#3}}
        {\doif{##1}{#1/}{\def\next{#3}}}}%
   #2\empty
   \popmacro\xmlr
   \next}

\def\doifXMLRchild#1#2#3%
  {\pushmacro\xmlr
   \let\next\empty
   \def\xmlr##1##2##3##4% the / should be sorted out in the mapper
     {\rawdoifinsetelse{##1}{#1}
        {\def\next{#3}}
        {\doif{##1}{#1/}{\def\next{#3}}}}%
   #2\empty
   \popmacro\xmlr
   \next}

\def\encapsulateXMLRchild#1#2#3#4#5%
  {\pushmacro\xmlr
   \def\xmlr##1##2##3##4%
     {\doifelse{##1}{#1}
        {\def\next{\doencapsulateXMLRchild{#2}{#3}{#4}{##4}}}
        {\let\next\empty}%
      \next}%
   #5\empty
   \popmacro\xmlr}

\def\encapsulateXMLRchildren#1#2#3#4#5%
  {\pushmacro\xmlr
   \pushmacro\betweenXMLRchild
   \def\betweenXMLRchild{\def\betweenXMLRchild{#3}}%
   \def\xmlr##1##2##3##4%
     {\rawdoifinsetelse{##1}{#1}
        {\pushmacro\xmlr
         \let\xmlr\naturalxmlr
         \betweenXMLRchild\xmlr{##1}{##2}{##3}{##4}%
         \popmacro\xmlr}
        {}}%
   #2#5\empty#4%
   \popmacro\betweenXMLRchild
   \popmacro\xmlr}

\def\doencapsulateXMLRchild#1#2#3#4%
  {\pushmacro\xmlr
   \pushmacro\betweenXMLRchild
   \def\betweenXMLRchild{\def\betweenXMLRchild{#2}}%
   \def\xmlr##1##2##3##4%
     {\pushmacro\xmlr
      \let\xmlr\naturalxmlr
      \betweenXMLRchild\xmlr{##1}{##2}{##3}{##4}%
      \popmacro\xmlr}%
   #1#4\empty#3%
   \popmacro\betweenXMLRchild
   \popmacro\xmlr}

\let\encapsulateXMLR\doencapsulateXMLRchild

\def\withnextXMLRelementelse#1#2%
  {\def\xdowithnextXMLRelement##1##2##3##4##5%
     {\def\nextXMLRelement{##1{##2}{##3}{##4}{##5}}#1}%
   \def\xnowithnextXMLRelement%
     {\let\nextXMLRelement\empty#2}%
   \doifnextcharelse\xmlr\xdowithnextXMLRelement\xnowithnextXMLRelement}

\def\encapsulatenextXMLRelements#1#2#3#4% oude bewaren
  {\pushmacro\betweenXMLRchild
   \pushmacro\afterXMLRchild
   \def\betweenXMLRchild{#1\def\betweenXMLRchild{#2}}%
   \let\afterXMLRchild\empty
   \withnextXMLRelementelse
     {\betweenXMLRchild
      \def\afterXMLRchild{#3}%
      \nextXMLRelement
      \doifnextcharelse\empty\xnowithnextXMLRelement\xdowithnextXMLRelement}
     {\afterXMLRchild
      \popmacro\afterXMLRchild
      \popmacro\betweenXMLRchild}%
   #4}

\def\collectXMLRchild#1#2%
  {\XMLRtoks\emptytoks
   \pushmacro\xmlr
   \def\xmlr##1##2##3##4%
     {\doif{##1}{#1}{\appendtoks##4\to\XMLRtoks}}%
   #2\empty
   \popmacro\xmlr}

\def\doifelseXMLRneighbors#1#2%
  {\XMLRtoks\emptytoks
   \pushmacro\xmlr
   \donefalse
   \let\prevXMLRchild\empty
   \def\xmlr##1##2##3##4%
      {\doif{##1}{#1}{\doif{##1}\prevXMLRchild{\donetrue}}%
       \def\prevXMLRchild{##1}}%
   #2\empty
   \popmacro\xmlr
   \ifdone
     \expandafter\firstoftwoarguments
   \else
     \expandafter\secondoftwoarguments
   \fi}

\def\collectbetweenXMLRchild#1#2#3%
  {\XMLRtoks\emptytoks
   \pushmacro\xmlr
   \pushmacro\betweenXMLRchild
   \def\betweenXMLRchild{\def\betweenXMLRchild{\appendtoks#2\to\XMLRtoks}}%
   \def\xmlr##1##2##3##4%
     {\rawdoifinsetelse{##1}{#1}
        {\betweenXMLRchild\appendtoks\xmlr{##1}{##2}{##3}{##4}\to\XMLRtoks}{}}%
   #3\empty
   \popmacro\betweenXMLRchild
   \popmacro\xmlr}

\def\dorawcollectbetweenXMLR#1#2%
  {\pushmacro\xmlr
   \pushmacro\betweenXMLRchild
   \def\betweenXMLRchild{\def\betweenXMLRchild{#1}}%
   \def\xmlr##1##2##3##4%
     {\betweenXMLRchild\appendtoks\xmlr{##1}{##2}{##3}{##4}\to\XMLRtoks}%
   #2\empty
   \popmacro\betweenXMLRchild
   \popmacro\xmlr}

\def\rawcollectbetweenXMLR%
  {\XMLRtoks\emptytoks\dorawcollectbetweenXMLR}

\def\docollectbetweenXMLR#1%
  {\dorawcollectbetweenXMLR{\appendtoks#1\to\XMLRtoks}}

\def\collectbetweenXMLR%
  {\XMLRtoks\emptytoks\docollectbetweenXMLR}

\def\processXMLRchildren#1%
  {\pushmacro\xmlr
   \let\xmlr\naturalxmlr
   #1\empty
   \popmacro\xmlr}

\def\processXMLRchild#1#2% slow but more versatile
  {\pushmacro\xmlr
   \XMLRtoks\emptytoks
   \def\xmlr##1##2##3##4%
     {\rawdoifinsetelse{##1}{#1}
        {\appendtoks\xmlr{##1}{##2}{##3}{##4}\to\XMLRtoks}{}}%
   #2%
   \popmacro\xmlr
   \the\XMLRtoks\empty}

\def\countXMLRchildren#1%
  {\pushmacro\xmlr
   \nofXMLRchildren=0
   \def\xmlr##1##2##3##4{\advance\nofXMLRchildren\plusone}
   #1\empty
   \popmacro\xmlr}

\def\countXMLRchild#1#2%
  {\pushmacro\xmlr
   \nofXMLRchildren=0
   \def\xmlr##1##2##3##4%
     {\rawdoifinsetelse{##1}{#1}{\advance\nofXMLRchildren\plusone}{}}
   #2\empty
   \popmacro\xmlr}

\def\installXMLunknownremapping
  {\remapXMLsingular[\s!unknown][CPA]\doXMLunknownSI
   \remapXMLsequence[\s!unknown][CPA]\doXMLunknownSE}

\bgroup \catcode`<=\activecatcode

\gdef\revertXMLremapping
  {\gdef\doXMLunknownSE##1##2{<##1>##2</##1>}%
   \gdef\doXMLunknownSI##1##2{<##1>}}

\gdef\unmapXMLdata#1#2% todo: singular, evt ##2 space ervoor en ##1##2
  {\bgroup
   \revertXMLremapping
   \expandXMLremapping      % now we can roll back
   \setnormalXMLhandler     % using the normal parser
   \resetXMLmapping         % and leaving the mapping namespace
   \xdef\unmappedXMLdata{#2}% recreate the original
   \enableXMLelements       % enable normal handler
   \unmappedXMLdata         % off we go ...
   \egroup}

\egroup

\protect \endinput