Skip to content

Commit 0c9f31c

Browse files
committed
LatexWalker's parse_content() now provides the expected default parser if none is provided; updated documentation
1 parent 030b8f3 commit 0c9f31c

3 files changed

Lines changed: 174 additions & 72 deletions

File tree

‎pylatexenc/latexwalker/__init__.py‎

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,26 @@
3232
``latexwalker`` was designed to extract useful text for indexing for text
3333
database searches of LaTeX content.)
3434
35+
Since `pylatexenc 3`, the parsing work itself is carried out by *parser
36+
objects*, which are specialized in parsing a given LaTeX construct and which
37+
are provided by :py:mod:`pylatexenc.latexnodes.parsers`. A
38+
:py:class:`LatexWalker` instance holds the LaTeX code along with the
39+
information required to parse it (the "latex context", see below), and it runs
40+
such parser objects on that code with
41+
:py:meth:`LatexWalker.parse_content()`.
42+
43+
The result of the parsing is a node tree, along with any information about
44+
parsing state changes that the parsed code caused (say, a ``\newcommand``).
45+
The nodes are instances of the classes provided in
46+
:py:mod:`pylatexenc.latexnodes.nodes`, and lists of nodes are wrapped in a
47+
:py:class:`~pylatexenc.latexnodes.nodes.LatexNodeList` object. (For
48+
compatibility with earlier versions, the node classes can also be imported
49+
directly from :py:mod:`pylatexenc.latexwalker`.)
50+
3551
Simple example usage::
3652
37-
>>> from pylatexenc.latexwalker import LatexWalker, LatexEnvironmentNode
53+
>>> from pylatexenc.latexwalker import LatexWalker
54+
>>> from pylatexenc.latexnodes.nodes import LatexEnvironmentNode
3855
>>> w = LatexWalker(r"""
3956
... \textbf{Hi there!} Here is \emph{a list}:
4057
... \begin{enumerate}[label=(i)]
@@ -43,37 +60,52 @@
4360
... \end{enumerate}
4461
... and $x$ is a variable.
4562
... """)
46-
>>> (nodelist, pos, len_) = w.get_latex_nodes(pos=0)
47-
>>> nodelist[0]
48-
LatexCharsNode(pos=0, len=1, chars='\n')
49-
>>> nodelist[1]
50-
LatexMacroNode(pos=1, len=18, macroname='textbf',
51-
nodeargd=ParsedMacroArgs(argnlist=[LatexGroupNode(pos=8, len=11,
52-
nodelist=[LatexCharsNode(pos=9, len=9, chars='Hi there!')],
53-
delimiters=('{', '}'))], argspec='{'), macro_post_space='')
63+
>>> nodelist, parsing_state_delta = w.parse_content()
64+
>>> nodelist[1].macroname
65+
'textbf'
66+
>>> (nodelist[1].pos, nodelist[1].pos_end)
67+
(1, 19)
68+
>>> nodelist[1].latex_verbatim()
69+
'\\textbf{Hi there!}'
5470
>>> nodelist[5].isNodeType(LatexEnvironmentNode)
5571
True
5672
>>> nodelist[5].environmentname
5773
'enumerate'
58-
>>> nodelist[5].nodeargd.argspec
59-
'['
60-
>>> nodelist[5].nodeargd.argnlist
61-
[LatexGroupNode(pos=60, len=11, nodelist=[LatexCharsNode(pos=61, len=9,
62-
chars='label=(i)')], delimiters=('[', ']'))]
6374
>>> nodelist[7].latex_verbatim()
6475
'$x$'
6576
66-
You can also use `latexwalker` directly in command-line, producing JSON or a
67-
human-readable node tree::
77+
Macro, environment and specials arguments are collected in a
78+
:py:class:`pylatexenc.latexnodes.ParsedArguments` object stored as the node's
79+
`nodeargd` attribute. Use
80+
:py:class:`pylatexenc.latexnodes.ParsedArgumentsInfo` to inspect them, by
81+
position or by argument name::
82+
83+
>>> from pylatexenc.latexnodes import ParsedArgumentsInfo
84+
>>> arguments = ParsedArgumentsInfo(node=nodelist[5]).get_all_arguments_info()
85+
>>> arguments[0].get_content_nodelist().latex_verbatim()
86+
'label=(i)'
87+
88+
You can also use `latexwalker` directly in command-line, producing a
89+
human-readable node tree or JSON::
90+
91+
$ latexwalker --code '\textit{italic} text'
92+
--- NODES ---
93+
94+
* \textit
95+
{.}: Group:
96+
* 'italic'
97+
* ' text'
6898
69-
$ echo '\textit{italic} text' | latexwalker --output-format=json
99+
-------------
100+
101+
$ latexwalker --output-format=json --code '\textit{italic} text'
70102
{
71103
"nodelist": [
72104
{
73105
"nodetype": "LatexMacroNode",
74106
"pos": 0,
75-
"len": 15,
76-
"macroname": "textit",
107+
"pos_end": 15,
108+
"parsing_state": {
77109
[...]
78110
79111
$ latexwalker --help
@@ -85,6 +117,20 @@
85117
:py:class:`pylatexenc.macrospec.EnvironmentSpec` objects in a
86118
:py:class:`pylatexenc.macrospec.LatexContextDb` object. See the doc of the
87119
module :py:mod:`pylatexenc.macrospec` for more information.
120+
121+
.. versionchanged:: 3.0
122+
123+
Parsing is now carried out by parser objects that are invoked via
124+
:py:meth:`LatexWalker.parse_content()`, in a significant API redesign. See
125+
:doc:`What's new in pylatexenc 3 <new-in-pylatexenc-3>` for the differences
126+
between the two interfaces, and the :ref:`list of changes that might affect
127+
existing code <new-in-pylatexenc-3-possible-pitfall-changes>`.
128+
129+
The `pylatexenc 2` interface of :py:class:`LatexWalker` —
130+
`get_latex_nodes()`, `get_token()`, `get_latex_expression()`,
131+
`get_latex_braced_group()`, `get_latex_environment()` and
132+
`get_latex_maybe_optional_arg()` — remains fully supported and should work in
133+
the vast majority of cases without code changes.
88134
'''
89135

90136

‎pylatexenc/latexwalker/_walker.py‎

Lines changed: 84 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -128,31 +128,33 @@ class LatexWalker(latexnodes.LatexWalkerBase):
128128
generally won't need to specify this flag, use `tolerant_parsing`
129129
instead.
130130
131-
The methods provided in this class perform various parsing of the given
132-
string `s`. These methods typically accept a `pos` parameter, which must be
133-
an integer, which defines the position in the string `s` to start parsing.
134-
135-
These are the ``get_***()`` methods inherited from `pylatexenc 2`
136-
(:py:meth:`get_latex_nodes()`, :py:meth:`get_latex_expression()`,
137-
:py:meth:`get_latex_braced_group()`, :py:meth:`get_latex_environment()`,
138-
:py:meth:`get_latex_maybe_optional_arg()`). Unless otherwise documented,
139-
they return a tuple `(node, pos, len)`, where node is a
140-
:py:class:`LatexNode` describing the parsed content, `pos` is the position
131+
132+
The main entry point for parsing is the :py:meth:`parse_content()` method.
133+
It parses the string `s` you provided in the class' constructor, using the
134+
provided parser (or the default parser, if no parser is provided). It
135+
returns a node tree, along with information about how the parsing state was
136+
updated during the parsing (e.g., new macro definitions).
137+
138+
This class also provides the ``get_***()`` methods inherited from
139+
`pylatexenc 2` (:py:meth:`get_latex_nodes()`,
140+
:py:meth:`get_latex_expression()`, :py:meth:`get_latex_braced_group()`,
141+
:py:meth:`get_latex_environment()`,
142+
:py:meth:`get_latex_maybe_optional_arg()`). While these methods remain
143+
supported, they are subject to a SOFT DEPRECATION notice — avoid their use
144+
in new code, as they are likely to become eventually deprecated. Unless
145+
otherwise documented, they return a tuple `(node, pos, len)`, where node is
146+
a :py:class:`LatexNode` describing the parsed content, `pos` is the position
141147
at which the LaTeX element of interest was encountered, and `len` is the
142148
length of the string that is considered to be part of the `node`. That is,
143149
the position in the string that is immediately after the node is `pos+len`.
144-
145-
These ``get_***()`` methods are the interface that `pylatexenc 2` offered,
146-
and they remain fully supported; you can keep using them. Since
147-
`pylatexenc 3` there is a second, more flexible way of parsing content:
148-
:py:meth:`parse_content()` together with a parser object from
149-
:py:mod:`pylatexenc.latexnodes.parsers`. Be aware that switching over is
150-
not a simple substitution of one call for another, because the return value
151-
is different: :py:meth:`parse_content()` returns a tuple `(nodes,
152-
parsing_state_delta)`, and the position information is carried by the nodes
153-
themselves in their `pos` and `pos_end` attributes rather than being
154-
returned alongside them. The documentation of each of the ``get_***()``
155-
methods points to the corresponding parser object.
150+
New code should avoid the ``get_***`` methods in favor of `pylatexenc 3`'s
151+
:py:meth:`parse_content()`, which is more powerful and more flexible. If
152+
you're porting code, note the return value is slighlty different:
153+
:py:meth:`parse_content()` returns a tuple `(nodes, parsing_state_delta)`,
154+
and the position information is carried by the nodes themselves in their
155+
`pos` and `pos_end` attributes rather than being returned alongside them.
156+
See the documentation of each of the ``get_***()`` methods for more
157+
information on migration to the new scheme.
156158
157159
The following obsolete flag is accepted by the constructor for backwards
158160
compatibility with `pylatexenc 1.x`:
@@ -475,19 +477,33 @@ def make_token_reader(self, pos=None):
475477
token_reader.move_to_pos_chars(pos)
476478
return token_reader
477479

478-
def parse_content(self, parser, token_reader=None, parsing_state=None,
480+
def parse_content(self, parser=None, token_reader=None, parsing_state=None,
479481
open_context=None):
480482
r"""
481483
The main entry point to parse the stored LaTeX code into a node structure.
482484
483485
Arguments:
484-
486+
485487
- The `parser` must be a callable object that can be called with the
486488
keyword arguments `latex_walker`, `token_reader` and `parsing_state`.
487489
The return value of `parser(...)` should be a :py:class:`LatexNode` or
488-
:py:class:`LatexNodeList` instance.
489-
490-
- `token_reader` is a :py:class:`LatexTokenReader` instance that is
490+
:py:class:`~pylatexenc.latexnodes.nodes.LatexNodeList` instance.
491+
492+
If `parser` is `None`, then a plain
493+
:py:class:`~pylatexenc.latexnodes.parsers.LatexGeneralNodesParser`
494+
instance is used. That parser reads general LaTeX content until the
495+
end of the stream, which is what one usually wants when parsing an
496+
entire document; so ``lw.parse_content()`` parses all the LaTeX code
497+
that was given to the constructor.
498+
499+
This default is a convenience offered by this class, and not part of
500+
the :py:class:`pylatexenc.latexnodes.LatexWalkerBase` interface. A
501+
latex walker class that is derived directly from
502+
:py:class:`~pylatexenc.latexnodes.LatexWalkerBase` is not required to
503+
accept `parser=None`.
504+
505+
- `token_reader` is a
506+
:py:class:`~pylatexenc.latexnodes.LatexTokenReader` instance that is
491507
tasked with converting the raw string into tokens. If `None`, then
492508
:py:meth:`make_token_reader()` is called to create a token reader
493509
instance.
@@ -505,15 +521,22 @@ def parse_content(self, parser, token_reader=None, parsing_state=None,
505521
506522
The return value is a tuple `(result, parser_parsing_state_delta)` where
507523
`result` is the return value of the parser, which is expected to be a
508-
:py:class:`LatexNode` or :py:class:`LatexNodeList` instance, and where
509-
`parsing_state_delta`, if non-`None`, is a parsing state delta object,
510-
see :py:class:`pylatexenc.latexnodes.ParsingStateDelta`. It represents
511-
changes to the parsing state for parsing subsequent content.
524+
:py:class:`LatexNode` or
525+
:py:class:`~pylatexenc.latexnodes.nodes.LatexNodeList` instance, and
526+
where `parsing_state_delta`, if non-`None`, is a parsing state delta
527+
object, see :py:class:`pylatexenc.latexnodes.ParsingStateDelta`. It
528+
represents changes to the parsing state for parsing subsequent content.
512529
"""
513530

531+
the_parser = None
514532
the_token_reader = None
515533
the_parsing_state = None
516534

535+
if parser is None:
536+
the_parser = parsers.LatexGeneralNodesParser()
537+
else:
538+
the_parser = parser
539+
517540
if token_reader is None:
518541
the_token_reader = self.make_token_reader()
519542
else:
@@ -533,29 +556,29 @@ def parse_content(self, parser, token_reader=None, parsing_state=None,
533556

534557
start_pos = the_token_reader.cur_pos()
535558
logger.debug(":: Parsing content (%s @ %r) - %r [%r]::",
536-
open_context_name, start_pos, parser, the_parsing_state)
559+
open_context_name, start_pos, the_parser, the_parsing_state)
537560

538561
with self.new_parsing_open_context(open_context_name, open_context_tok) as pc:
539562

540563
try:
541564

542-
nodes, info = parser.parse(
565+
nodes, info = the_parser.parse(
543566
latex_walker=self,
544567
token_reader=the_token_reader,
545568
parsing_state=the_parsing_state,
546569
)
547570

548571
except LatexWalkerEndOfStream:
549572
logger.warning("End of stream encountered when parsing content with %s (%s)",
550-
parser.__class__.__name__, open_context_name)
573+
the_parser.__class__.__name__, open_context_name)
551574
nodes, info = None, None
552575

553576
if pc.recovery_from_exception is not None:
554577
nodes, info = \
555578
pc.perform_recovery_nodes_and_parsing_state_delta(the_token_reader)
556579

557580
logger.debug(":: PARSED content (%s @ %r) - %r - result %r + %r DONE ::",
558-
open_context_name, start_pos, parser, nodes, info)
581+
open_context_name, start_pos, the_parser, nodes, info)
559582

560583
return nodes, info
561584

@@ -761,13 +784,13 @@ def _pyltxenc2_LatexWalker_get_token(
761784
starting at position `pos`, to parse a single "token", as defined by
762785
:py:class:`LatexToken`.
763786
764-
This method is the `pylatexenc 2` style interface and it remains fully
765-
supported. Since `pylatexenc 3` you can also read tokens with a token
766-
reader object, see :py:meth:`make_token_reader()` and
767-
:py:class:`LatexTokenReader`. A token reader keeps track of its own
768-
position in the string as it reads and it exposes several reading methods
769-
(`peek_token()`, `next_token()`, ...), so moving to it is not simply a
770-
matter of replacing one call by another.
787+
This method is the `pylatexenc 2` style interface and it remains supported;
788+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE. Since
789+
`pylatexenc 3` you can also read tokens with a token reader object, see
790+
:py:meth:`make_token_reader()` and :py:class:`LatexTokenReader`. A token
791+
reader keeps track of its own position in the string as it reads and it
792+
exposes several reading methods (`peek_token()`, `next_token()`, ...), so
793+
moving to it is not simply a matter of replacing one call by another.
771794
772795
Parse the token in the stream pointed to at position `pos`.
773796
@@ -861,8 +884,9 @@ def _pyltxenc2_LatexWalker_get_latex_nodes(
861884
Parses the latex content given to the constructor (and stored in `self.s`)
862885
into a list of nodes.
863886
864-
This method is the `pylatexenc 2` style interface and it remains fully
865-
supported. Since `pylatexenc 3` you can also parse the same content with
887+
This method is the `pylatexenc 2` style interface and it remains supported;
888+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE. Since
889+
`pylatexenc 3` you can also parse the same content with
866890
:py:meth:`parse_content()` and a parser object. The two calls do not return
867891
the same thing, so this is not a mechanical substitution; you'd write::
868892
@@ -881,6 +905,9 @@ def _pyltxenc2_LatexWalker_get_latex_nodes(
881905
npos = nodelist.pos
882906
nlen = nodelist.len # or nodelist.pos_end - nodelist.pos
883907
908+
The `LatexGeneralNodesParser()` instance shown here is also
909+
:py:meth:`parse_content()`'s default parser, so it can simply be omitted.
910+
884911
See the documentation for
885912
:py:class:`~latexnodes.parsers.LatexGeneralNodesParser` for information
886913
on how to implement similar behavior as with the `stop_upon_*=`
@@ -1096,8 +1123,9 @@ def _pyltxenc2_LatexWalker_get_latex_expression(
10961123
Returns a tuple `(node, pos, len)`, where `pos` is the position of the
10971124
first char of the expression and `len` the length of the expression.
10981125
1099-
This method is the `pylatexenc 2` style interface and it remains fully
1100-
supported. Since `pylatexenc 3` you can also parse the same content with
1126+
This method is the `pylatexenc 2` style interface and it remains supported;
1127+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE. Since
1128+
`pylatexenc 3` you can parse the same content with
11011129
``LatexWalker.parse_content(LatexExpressionParser(), ...)``. That call
11021130
returns a tuple `(nodes, parsing_state_delta)` instead of `(node, pos,
11031131
len)`, with the positions carried by the node itself, so it is not a
@@ -1231,8 +1259,9 @@ def _pyltxenc2_LatexWalker_get_latex_braced_group(
12311259
distinct single characters providing the opening and closing brace
12321260
chars (e.g., ``("<", ">")``).
12331261
1234-
This method is the `pylatexenc 2` style interface and it remains fully
1235-
supported. Since `pylatexenc 3` you can also parse the same content with
1262+
This method is the `pylatexenc 2` style interface and it remains supported;
1263+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE Since `pylatexenc
1264+
3` you can parse the same content with
12361265
``LatexWalker.parse_content(LatexDelimitedGroupParser(), ...)``. That call
12371266
returns a tuple `(nodes, parsing_state_delta)` instead of `(node, pos,
12381267
len)`, with the positions carried by the node itself, so it is not a
@@ -1327,8 +1356,9 @@ def _pyltxenc2_LatexWalker_get_latex_environment(
13271356
Returns a tuple (node, pos, len) where node is a
13281357
:py:class:`LatexEnvironmentNode`.
13291358
1330-
This method is the `pylatexenc 2` style interface and it remains fully
1331-
supported. Since `pylatexenc 3` you can also parse the same content with
1359+
This method is the `pylatexenc 2` style interface and it remains supported;
1360+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE. Since
1361+
`pylatexenc 3` you can parse the same content with
13321362
``LatexWalker.parse_content(LatexSingleNodeParser(), ...)`` positioned at
13331363
the beginning of the environment. That call returns a tuple `(nodes,
13341364
parsing_state_delta)` instead of `(node, pos, len)`, with the positions
@@ -1389,9 +1419,10 @@ def _pyltxenc2_LatexWalker_get_latex_maybe_optional_arg(self, pos, parsing_state
13891419
a tuple `(node, pos, len)` if success where `node` is a
13901420
:py:class:`LatexGroupNode`. Otherwise, this method returns None.
13911421
1392-
This method is the `pylatexenc 2` style interface and it remains fully
1393-
supported. Since `pylatexenc 3` you can also parse the same content with
1394-
the more flexible parsers mechanism, e.g.,
1422+
This method is the `pylatexenc 2` style interface and it remains supported;
1423+
it is subject to SOFT DEPRECATION - AVOID USE IN NEW CODE. Since
1424+
`pylatexenc 3` you can parse the same content with the more flexible parsers
1425+
mechanism, e.g.,
13951426
``LatexWalker.parse_content(LatexOptionalSquareBracketsParser(), ...)``.
13961427
That call returns a tuple `(nodes, parsing_state_delta)` instead of `(node,
13971428
pos, len)` or `None`, with the positions carried by the node itself, so it

0 commit comments

Comments
 (0)