|
24 | 24 | from numpy.typing import NDArray |
25 | 25 | from pyparsing import ( |
26 | 26 | Empty, Forward, Literal, Group, NotAny, OneOrMore, Optional, |
27 | | - ParseBaseException, ParseExpression, ParseFatalException, |
28 | | - ParserElement, ParseResults, QuotedString, Regex, StringEnd, ZeroOrMore, |
29 | | - pyparsing_common, nested_expr, one_of) |
| 27 | + ParseBaseException, ParseException, ParseExpression, ParseFatalException, |
| 28 | + ParserElement, ParseResults, QuotedString, Regex, StringEnd, Token, |
| 29 | + ZeroOrMore, pyparsing_common, nested_expr, one_of) |
30 | 30 |
|
31 | 31 | import matplotlib as mpl |
32 | 32 | from . import cbook |
@@ -1873,6 +1873,50 @@ def raise_error(s: str, loc: int, toks: ParseResults) -> T.Any: |
1873 | 1873 | return Empty().set_parse_action(raise_error) |
1874 | 1874 |
|
1875 | 1875 |
|
| 1876 | +class _BracedText(Token): |
| 1877 | + r""" |
| 1878 | + Match a brace-delimited literal string, allowing nested braces. |
| 1879 | +
|
| 1880 | + This is similar to ``QuotedString("{", "\\", end_quote_char="}")``, except |
| 1881 | + that brace depth is tracked, so that the string does not end at the first |
| 1882 | + ``}``. As in TeX, nested unescaped braces only group, and are not |
| 1883 | + rendered; a literal brace is written as ``\{`` or ``\}``. A backslash |
| 1884 | + escapes the following character, which therefore does not affect depth. |
| 1885 | + """ |
| 1886 | + |
| 1887 | + _escapes = {"t": "\t", "n": "\n", "f": "\f", "r": "\r"} |
| 1888 | + |
| 1889 | + def __init__(self) -> None: |
| 1890 | + super().__init__() |
| 1891 | + self.mayReturnEmpty = True |
| 1892 | + self.mayIndexError = False |
| 1893 | + |
| 1894 | + def parseImpl(self, instring: str, loc: int, |
| 1895 | + do_actions: bool = True) -> tuple[int, str]: |
| 1896 | + if loc >= len(instring) or instring[loc] != "{": |
| 1897 | + raise ParseException(instring, loc, "Expected '{'", self) |
| 1898 | + chars = [] |
| 1899 | + depth = 0 |
| 1900 | + while loc < len(instring): |
| 1901 | + char = instring[loc] |
| 1902 | + if char == "\\" and loc + 1 < len(instring): |
| 1903 | + escaped = instring[loc + 1] |
| 1904 | + chars.append(self._escapes.get(escaped, escaped)) |
| 1905 | + loc += 2 |
| 1906 | + continue |
| 1907 | + loc += 1 |
| 1908 | + if char == "{": |
| 1909 | + depth += 1 |
| 1910 | + continue |
| 1911 | + elif char == "}": |
| 1912 | + depth -= 1 |
| 1913 | + if depth == 0: |
| 1914 | + return loc, "".join(chars) |
| 1915 | + continue |
| 1916 | + chars.append(char) |
| 1917 | + raise ParseException(instring, loc, "Expected '}'", self) |
| 1918 | + |
| 1919 | + |
1876 | 1920 | class ParserState: |
1877 | 1921 | """ |
1878 | 1922 | Parser state. |
@@ -2216,7 +2260,7 @@ def csnames(group: str, names: Iterable[str]) -> Regex: |
2216 | 2260 | r"\underset", |
2217 | 2261 | p.optional_group("annotation") + p.optional_group("body")) |
2218 | 2262 |
|
2219 | | - p.text = cmd(r"\text", QuotedString('{', '\\', end_quote_char="}")) |
| 2263 | + p.text = cmd(r"\text", _BracedText()) |
2220 | 2264 |
|
2221 | 2265 | p.substack = cmd(r"\substack", |
2222 | 2266 | nested_expr(opener="{", closer="}", |
|
0 commit comments