Exemple #1
0
def _convert_command(element, elements, index, iterable, parent):
    _get_prefix_element(element, parent)
    params, tag, attributes = COMMANDS[element]
    new_parent = eTree.SubElement(parent, tag, **attributes)
    alignment = None
    if element in MATRICES and (element.endswith('*') or element == r'\array'):
        index += 1
        alignment = elements[index]
        next(iterable)
    for j in range(params):
        index += 1
        param = elements[index]
        if element == r'\left' or element == r'\right':
            symbol = convert_symbol(param)
            new_parent.text = param if symbol is None else '&#x{};'.format(
                symbol)
        elif element == r'\array':
            _convert_array_content(param, new_parent, alignment)
        elif element in MATRICES:
            _convert_matrix_content(param, new_parent, alignment)
        else:
            if isinstance(param, list):
                _parent = eTree.SubElement(new_parent, 'mrow')
                _classify_subgroup(param, _parent)
            else:
                _classify(param, new_parent)
    _get_postfix_element(element, parent)
    if element == r'\overline':
        mo = eTree.SubElement(new_parent, 'mo', stretchy='true')
        mo.text = '¯'
    elif element == r'\underline':
        mo = eTree.SubElement(new_parent, 'mo', stretchy='true')
        mo.text = '̲'
    [next(iterable) for _ in range(params)]
Exemple #2
0
def _classify(_element, parent):
    symbol = convert_symbol(_element)
    if re.match(r'\d+(.\d+)?', _element):
        mn = eTree.SubElement(parent, 'mn')
        mn.text = _element
    elif _element in '+-*/()=':
        mo = eTree.SubElement(parent, 'mo')
        mo.text = _element if symbol is None else '&#x{};'.format(symbol)
    elif symbol and (int(symbol, 16) in range(int('2200', 16),
                                              int('22FF', 16) + 1)
                     or int(symbol, 16) in range(int('2190', 16),
                                                 int('21FF', 16) + 1)):
        mo = eTree.SubElement(parent, 'mo')
        mo.text = '&#x{};'.format(symbol)
    elif _element.startswith('\\'):
        if symbol:
            mi = eTree.SubElement(parent, 'mi')
            mi.text = '&#x{};'.format(symbol)
        else:
            e = _element.lstrip('\\')
            mi = eTree.SubElement(parent, 'mi')
            mi.text = e
    else:
        mi = eTree.SubElement(parent, 'mi')
        mi.text = _element
Exemple #3
0
def _convert_command(
    element: str,
    elements: List[Any],
    index: int,
    iterable: Iterator[int],
    parent: Element,
):
    _get_prefix_element(element, parent)
    if element == r"\substack":
        parent = SubElement(parent, "mstyle", scriptlevel="1")
    params, tag, attributes = COMMANDS[element]
    if len(elements) - 1 < params:
        mo = SubElement(parent, "mo")
        mo.text = element[1:]
        return
    new_parent = SubElement(parent, tag, attributes)
    alignment = ""
    if element in MATRICES and (element.endswith("*") or element == r"\array"):
        index += 1
        alignment = elements[index]
        next(iterable)
    if element in (r"\lim", r"\inf", r"\sup", r"\max", r"\min"):
        limit = SubElement(new_parent, "mo")
        limit.text = element[1:]
    for j in range(params):
        index += 1
        param = elements[index]
        if element == "_" and index == 1 and param == r"\sum":
            new_parent.tag = "munder"
            _classify(param, new_parent)
        elif element == r"\left" or element == r"\right":
            if param == ".":
                pass
            else:
                symbol = convert_symbol(param)
                new_parent.text = param if symbol is None else "&#x{};".format(
                    symbol)
        elif element == r"\array":
            _convert_array_content(param, new_parent, alignment)
        elif element in MATRICES:
            _convert_matrix_content(param, new_parent, alignment,
                                    element == r"\substack")
        else:
            if isinstance(param, list):
                _parent = SubElement(new_parent, "mrow")
                _classify_subgroup(param, _parent)
            else:
                _classify(param, new_parent)
    _get_postfix_element(element, parent)
    if element in (r"\overline", r"\bar"):
        mo = SubElement(new_parent, "mo", stretchy="true")
        mo.text = "&#x000AF;"
    elif element == r"\underline":
        mo = SubElement(new_parent, "mo", stretchy="true")
        mo.text = "&#x00332;"
    elif element in (r"\overrightarrow", r"\vec"):
        mo = SubElement(new_parent, "mo", stretchy="true")
        mo.text = "&#x02192;"
    [next(iterable) for _ in range(params)]
Exemple #4
0
def _classify(_element: str,
              parent: Element,
              is_math_mode: bool = False) -> None:
    symbol = convert_symbol(_element)
    if re.match(r"\d+(.\d+)?", _element):
        mn = SubElement(parent, "mn")
        mn.text = _element
    elif len(_element) and _element in "<>&":
        mo = SubElement(parent, "mo")
        mo.text = {"<": "&lt;", ">": "&gt;", "&": "&amp;"}[_element]
    elif len(_element) and _element in "+-*/()=":
        mo = SubElement(parent, "mo")
        mo.text = _element if symbol is None else "&#x{};".format(symbol)
        if _element in "()":
            mo.attrib["stretchy"] = "false"
    elif (symbol and (int(symbol, 16) in range(int("2200", 16),
                                               int("22FF", 16) + 1)
                      or int(symbol, 16) in range(int("2190", 16),
                                                  int("21FF", 16) + 1))
          or symbol == "."):
        mo = SubElement(parent, "mo")
        mo.text = "&#x{};".format(symbol)
    elif _element.startswith("\\"):
        tag = SubElement(parent, "mo" if is_math_mode else "mi")
        if symbol:
            tag.text = "&#x{};".format(symbol)
        elif _element in (
                r"\log",
                r"\ln",
                r"\tan",
                r"\sec",
                r"\cos",
                r"\sin",
                r"\cot",
                r"\csc",
        ):
            tag.text = _element[1:]
        else:
            tag.text = _element
    else:
        tag = SubElement(parent, "mo" if is_math_mode else "mi")
        tag.text = _element
def tokenize(latex_string: str, skip_comments: bool = True) -> Iterator[str]:
    """
    Converts Latex string into tokens.

    :param latex_string: Latex string.
    :param skip_comments: Flag to skip comments (default=True).
    """
    for match in PATTERN.finditer(latex_string):
        tokens = tuple(filter(lambda x: x is not None, match.groups()))
        if tokens[0].startswith(commands.MATH):
            full_math = "".join(tokens)
            symbol = convert_symbol(full_math)
            if symbol:
                yield f"&#x{symbol};"
                continue
        for captured in tokens:
            if skip_comments and captured.startswith("%"):
                break
            if captured.endswith(UNITS):
                yield captured.replace(" ", "")
                continue
            yield captured
Exemple #6
0
def test_convert_symbol(latex: str, expected: str) -> None:
    assert convert_symbol(latex) == expected
Exemple #7
0
def tokenize(data) -> Iterator[str]:
    iterable = iter(data)
    buffer = ""
    while True:
        try:
            char = next(iterable)
            if char == "\\":
                if buffer == "\\":
                    yield buffer + char
                    buffer = ""
                    continue
                elif len(buffer):
                    yield buffer
                buffer = char
                try:
                    buffer += next(iterable)
                    if buffer in (r"\\", r"\[", r"\]"):
                        yield buffer
                        buffer = ""
                except StopIteration:
                    break
            elif char.isalpha():
                if len(buffer):
                    if buffer.endswith("}"):
                        yield buffer
                        yield char
                        buffer = ""
                    elif buffer.startswith("\\"):
                        buffer += char
                else:
                    yield char
            elif char.isdigit():
                if len(buffer):
                    yield buffer
                buffer = char
                while True:
                    try:
                        char = next(iterable)
                    except StopIteration:
                        break
                    if char.isspace():
                        yield buffer
                        buffer = ""
                        break
                    elif char.isdigit() or char == ".":
                        buffer += char
                    else:
                        if buffer.endswith("."):
                            yield buffer[:-1]
                            yield buffer[-1]
                        else:
                            yield buffer
                        buffer = ""
                        if char == "\\":
                            buffer = char
                        else:
                            yield char
                        break
            elif char.isspace():
                if len(buffer):
                    yield buffer
                    buffer = ""
            elif char in "{}*":
                # FIXME: Anything that starts with '\math' passes. There is a huge list of math symbols in
                #  unimathsymbols.txt and hard-coding all of them is inefficient.
                if (buffer.startswith(r"\begin") or buffer.startswith(r"\end")
                        or buffer.startswith(r"\math")):
                    if buffer.endswith("}"):
                        yield buffer
                        yield char
                        buffer = ""
                        continue
                    elif buffer.startswith(r"\math") and char == "}":
                        symbol = convert_symbol(buffer + char)
                        if symbol:
                            yield "&#x{};".format(symbol)
                            buffer = ""
                            continue
                    buffer += char
                else:
                    if len(buffer):
                        yield buffer
                        buffer = ""
                    yield char
            else:
                if len(buffer):
                    if buffer.startswith(r"\math"):
                        yield buffer[:-1]
                        yield buffer[-1]
                    else:
                        yield buffer
                    buffer = ""
                if len(char):
                    yield char
        except StopIteration:
            break
    if len(buffer):
        yield buffer
def test_convert_symbol(name: str, latex: str, expected: str):
    assert convert_symbol(latex) == expected
Exemple #9
0
def _convert_and_append_operator(symbol, parent):
    symbol = convert_symbol(symbol)
    mo = eTree.SubElement(parent, 'mo')
    mo.text = '&#x{};'.format(symbol)
Exemple #10
0
def _convert_command(node: Node, parent: Element, font: Optional[Dict[str, Optional[str]]] = None) -> None:
    command = node.token
    modifier = node.modifier

    if command in (commands.SUBSTACK, commands.SMALLMATRIX):
        parent = SubElement(parent, "mstyle", scriptlevel="1")
    elif command == commands.CASES:
        lbrace = SubElement(parent, "mo", OrderedDict([("stretchy", "true"), ("fence", "true"), ("form", "prefix")]))
        lbrace.text = "&#x{};".format(convert_symbol(commands.LBRACE))
    elif command in (commands.DBINOM, commands.DFRAC):
        parent = SubElement(parent, "mstyle", displaystyle="true", scriptlevel="0")
    elif command == commands.HPHANTOM:
        parent = SubElement(parent, "mpadded", height="0", depth="0")
    elif command == commands.VPHANTOM:
        parent = SubElement(parent, "mpadded", width="0")
    elif command in (commands.TBINOM, commands.HBOX, commands.MBOX, commands.TFRAC):
        parent = SubElement(parent, "mstyle", displaystyle="false", scriptlevel="0")
    elif command in (commands.MOD, commands.PMOD):
        SubElement(parent, "mspace", width="1em")

    tag, attributes = copy.deepcopy(commands.CONVERSION_MAP[command])

    if node.attributes is not None and node.token != commands.SKEW:
        attributes.update(node.attributes)

    if command == commands.LEFT:
        parent = SubElement(parent, "mrow")

    _append_prefix_element(node, parent)

    alignment, column_lines = _get_alignment_and_column_lines(node.alignment)

    if column_lines:
        attributes["columnlines"] = column_lines

    if command == commands.SUBSUP and node.children is not None and node.children[0].token == commands.GCD:
        tag = "munderover"
    elif command == commands.SUPERSCRIPT and modifier in (commands.LIMITS, commands.OVERBRACE):
        tag = "mover"
    elif command == commands.SUBSCRIPT and modifier in (commands.LIMITS, commands.UNDERBRACE):
        tag = "munder"
    elif command == commands.SUBSUP and modifier in (commands.LIMITS, commands.OVERBRACE, commands.UNDERBRACE):
        tag = "munderover"
    elif (
        command in (commands.XLEFTARROW, commands.XRIGHTARROW) and node.children is not None and len(node.children) == 2
    ):
        tag = "munderover"

    element = SubElement(parent, tag, attributes)

    if command in commands.LIMIT:
        element.text = command[1:]
    elif command in (commands.MOD, commands.PMOD):
        element.text = "mod"
        SubElement(parent, "mspace", width="0.333em")
    elif command == commands.BMOD:
        element.text = "mod"
    elif command in (commands.XLEFTARROW, commands.XRIGHTARROW):
        style = SubElement(element, "mstyle", scriptlevel="0")
        arrow = SubElement(style, "mo")
        if command == commands.XLEFTARROW:
            arrow.text = "&#x2190;"
        elif command == commands.XRIGHTARROW:
            arrow.text = "&#x2192;"
    elif node.text is not None:
        if command == commands.MIDDLE:
            element.text = "&#x{};".format(convert_symbol(node.text))
        elif command == commands.HBOX:
            mtext: Optional[Element] = element
            for text, mode in separate_by_mode(node.text):
                if mode == Mode.TEXT:
                    if mtext is None:
                        mtext = SubElement(parent, tag, attributes)
                    mtext.text = text.replace(" ", "&#x000A0;")
                    _set_font(mtext, "mtext", font)
                    mtext = None
                else:
                    _row = SubElement(parent, "mrow")
                    _convert_group(iter(walk(text)), _row)
        else:
            if command == commands.FBOX:
                element = SubElement(element, "mtext")
            element.text = node.text.replace(" ", "&#x000A0;")
            _set_font(element, "mtext", font)
    elif node.delimiter is not None and command not in (commands.FRAC, commands.GENFRAC):
        if node.delimiter != ".":
            symbol = convert_symbol(node.delimiter)
            element.text = node.delimiter if symbol is None else "&#x{};".format(symbol)

    if node.children is not None:
        _parent = element
        if command in (commands.LEFT, commands.MOD, commands.PMOD):
            _parent = parent
        if command in commands.MATRICES:
            if command == commands.CASES:
                alignment = "l"
            elif command in (commands.SPLIT, commands.ALIGN):
                alignment = "rl"
            _convert_matrix(iter(node.children), _parent, command, alignment=alignment)
        elif command == commands.CFRAC:
            for child in node.children:
                p = SubElement(_parent, "mstyle", displaystyle="false", scriptlevel="0")
                _convert_group(iter([child]), p, font)
        elif command == commands.SIDESET:
            Node(
                r"\style",
                children=(Node(r"\mspace", attributes={"width": "-0.167em"}),),
                attributes={"scriptlevel": "0"},
            ),
            left, right = node.children
            _convert_group(iter([left]), _parent, font)
            fill = SubElement(_parent, "mstyle", scriptlevel="0")
            SubElement(fill, "mspace", width="-0.167em")
            _convert_group(iter([right]), _parent, font)
        elif command == commands.SKEW:
            child = node.children[0]
            new_node = Node(
                token=child.token,
                children=(
                    Node(
                        token=commands.BRACES,
                        children=(*child.children, Node(token=commands.MKERN, attributes=node.attributes)),
                    ),
                ),
            )
            _convert_group(iter([new_node]), _parent, font)
        elif command in (commands.XLEFTARROW, commands.XRIGHTARROW):
            for child in node.children:
                padded = SubElement(
                    _parent,
                    "mpadded",
                    OrderedDict(
                        [("width", "+0.833em"), ("lspace", "0.556em"), ("voffset", "-.2em"), ("height", "-.2em")]
                    ),
                )
                _convert_group(iter([child]), padded, font)
                SubElement(padded, "mspace", depth=".25em")
        else:
            _convert_group(iter(node.children), _parent, font)

    _add_diacritic(command, element)

    _append_postfix_element(node, parent)
Exemple #11
0
def _convert_and_append_operator(symbol: str, parent: Element) -> None:
    converted = convert_symbol(symbol)
    mo = SubElement(parent, "mo")
    mo.text = "&#x{};".format(converted)
 def test_operator_plus(self):
     self.assertEqual('0002B', symbols_parser.convert_symbol('+'))
 def test_alias_command(self):
     self.assertEqual('02192', symbols_parser.convert_symbol(r'\to'))
Exemple #14
0
def _convert_symbol(node: Node, parent: Element, font: Optional[Dict[str, Optional[str]]] = None) -> None:
    token = node.token
    attributes = node.attributes or {}
    symbol = convert_symbol(token)
    if re.match(r"\d+(.\d+)?", token):
        element = SubElement(parent, "mn", attrib=attributes)
        element.text = token
        _set_font(element, element.tag, font)
    elif token in OPERATORS:
        element = SubElement(parent, "mo", attrib=attributes)
        element.text = token if symbol is None else "&#x{};".format(symbol)
        if token == r"\|":
            element.attrib["fence"] = "false"
        if token == r"\smallint":
            element.attrib["largeop"] = "false"
        if token in ("(", ")", "[", "]", "|", r"\|", r"\{", r"\}", r"\surd"):
            element.attrib["stretchy"] = "false"
            _set_font(element, "fence", font)
        else:
            _set_font(element, element.tag, font)
    elif (
        symbol
        and (
            int(symbol, 16) in range(int("2200", 16), int("22FF", 16) + 1)
            or int(symbol, 16) in range(int("2190", 16), int("21FF", 16) + 1)
        )
        or symbol == "."
    ):
        element = SubElement(parent, "mo", attrib=attributes)
        element.text = "&#x{};".format(symbol)
        _set_font(element, element.tag, font)
    elif token in (r"\ ", "~", commands.NOBREAKSPACE, commands.SPACE):
        element = SubElement(parent, "mtext", attrib=attributes)
        element.text = "&#x000A0;"
        _set_font(element, "mtext", font)
    elif token == commands.NOT:
        mpadded = SubElement(parent, "mpadded", width="0")
        element = SubElement(mpadded, "mtext")
        element.text = "&#x029F8;"
    elif token in (
        commands.DETERMINANT,
        commands.GCD,
        commands.INTOP,
        commands.INJLIM,
        commands.LIMINF,
        commands.LIMSUP,
        commands.PR,
        commands.PROJLIM,
    ):
        element = SubElement(parent, "mo", attrib={"movablelimits": "true", **attributes})
        texts = {
            commands.INJLIM: "inj&#x02006;lim",
            commands.INTOP: "&#x0222B;",
            commands.LIMINF: "lim&#x02006;inf",
            commands.LIMSUP: "lim&#x02006;sup",
            commands.PROJLIM: "proj&#x02006;lim",
        }
        element.text = texts.get(token, token[1:])
        _set_font(element, element.tag, font)
    elif token == commands.IDOTSINT:
        _parent = SubElement(parent, "mrow", attrib=attributes)
        for s in ("&#x0222B;", "&#x022EF;", "&#x0222B;"):
            element = SubElement(_parent, "mo")
            element.text = s
    elif token in (commands.LATEX, commands.TEX):
        _parent = SubElement(parent, "mrow", attrib=attributes)
        if token == commands.LATEX:
            mi_l = SubElement(_parent, "mi")
            mi_l.text = "L"
            SubElement(_parent, "mspace", width="-.325em")
            mpadded = SubElement(_parent, "mpadded", height="+.21ex", depth="-.21ex", voffset="+.21ex")
            mstyle = SubElement(mpadded, "mstyle", displaystyle="false", scriptlevel="1")
            mrow = SubElement(mstyle, "mrow")
            mi_a = SubElement(mrow, "mi")
            mi_a.text = "A"
            SubElement(_parent, "mspace", width="-.17em")
            _set_font(mi_l, mi_l.tag, font)
            _set_font(mi_a, mi_a.tag, font)
        mi_t = SubElement(_parent, "mi")
        mi_t.text = "T"
        SubElement(_parent, "mspace", width="-.14em")
        mpadded = SubElement(_parent, "mpadded", height="-.5ex", depth="+.5ex", voffset="-.5ex")
        mrow = SubElement(mpadded, "mrow")
        mi_e = SubElement(mrow, "mi")
        mi_e.text = "E"
        SubElement(_parent, "mspace", width="-.115em")
        mi_x = SubElement(_parent, "mi")
        mi_x.text = "X"
        _set_font(mi_t, mi_t.tag, font)
        _set_font(mi_e, mi_e.tag, font)
        _set_font(mi_x, mi_x.tag, font)
    elif token.startswith(commands.OPERATORNAME):
        element = SubElement(parent, "mo", attrib=attributes)
        element.text = token[14:-1]
    elif token.startswith(commands.BACKSLASH):
        element = SubElement(parent, "mi", attrib=attributes)
        if symbol:
            element.text = "&#x{};".format(symbol)
        elif token in commands.FUNCTIONS:
            element.text = token[1:]
        else:
            element.text = token
        _set_font(element, element.tag, font)
    else:
        element = SubElement(parent, "mi", attrib=attributes)
        element.text = token
        _set_font(element, element.tag, font)
Exemple #15
0
def _convert_and_append_command(command: str, parent: Element, attributes: Optional[Dict[str, str]] = None) -> None:
    code_point = convert_symbol(command)
    mo = SubElement(parent, "mo", attributes if attributes is not None else {})
    mo.text = "&#x{};".format(code_point) if code_point else command
Exemple #16
0
def test_operator_plus():
    assert '0002B' == convert_symbol('+')
Exemple #17
0
def test_alias_command():
    assert '02192' == convert_symbol(r'\to')
Exemple #18
0
def _walk(tokens: Iterator[str], terminator: str = None, limit: int = 0) -> List[Node]:
    group: List[Node] = []
    token: str
    has_available_tokens = False
    for token in tokens:
        has_available_tokens = True
        if token == terminator:
            delimiter = None
            if terminator == commands.RIGHT:
                delimiter = next(tokens)
            group.append(Node(token=token, delimiter=delimiter))
            break
        elif (token == commands.RIGHT != terminator) or (token == commands.MIDDLE and terminator != commands.RIGHT):
            raise ExtraLeftOrMissingRightError
        elif token == commands.LEFT:
            delimiter = next(tokens)
            children = tuple(_walk(tokens, terminator=commands.RIGHT))  # make \right as a child of \left
            if len(children) == 0 or children[-1].token != commands.RIGHT:
                raise ExtraLeftOrMissingRightError
            node = Node(token=token, children=children if len(children) else None, delimiter=delimiter)
        elif token == commands.OPENING_BRACE:
            children = tuple(_walk(tokens, terminator=commands.CLOSING_BRACE))
            if len(children) and children[-1].token == commands.CLOSING_BRACE:
                children = children[:-1]
            node = Node(token=commands.BRACES, children=children)
        elif token in (commands.SUBSCRIPT, commands.SUPERSCRIPT):
            try:
                previous = group.pop()
            except IndexError:
                previous = Node(token="")  # left operand can be empty if not present

            if token == previous.token == commands.SUBSCRIPT:
                raise DoubleSubscriptsError
            if token == previous.token == commands.SUPERSCRIPT:
                raise DoubleSuperscriptsError

            modifier = None
            if previous.token == commands.LIMITS:
                modifier = commands.LIMITS
                try:
                    previous = group.pop()
                    if not previous.token.startswith("\\"):  # TODO: Complete list of operators
                        raise LimitsMustFollowMathOperatorError
                except IndexError:
                    raise LimitsMustFollowMathOperatorError

            if token == commands.SUBSCRIPT and previous.token == commands.SUPERSCRIPT and previous.children is not None:
                children = tuple(_walk(tokens, terminator=terminator, limit=1))
                node = Node(
                    token=commands.SUBSUP,
                    children=(previous.children[0], *children, previous.children[1]),
                    modifier=previous.modifier,
                )
            elif (
                token == commands.SUPERSCRIPT and previous.token == commands.SUBSCRIPT and previous.children is not None
            ):
                children = tuple(_walk(tokens, terminator=terminator, limit=1))
                node = Node(token=commands.SUBSUP, children=(*previous.children, *children), modifier=previous.modifier)
            else:
                try:
                    children = tuple(_walk(tokens, terminator=terminator, limit=1))
                except NoAvailableTokensError:
                    raise MissingSuperScriptOrSubscriptError
                if previous.token in (commands.OVERBRACE, commands.UNDERBRACE):
                    modifier = previous.token
                node = Node(token=token, children=(previous, *children), modifier=modifier)
        elif token == commands.APOSTROPHE:
            try:
                previous = group.pop()
            except IndexError:
                previous = Node(token="")  # left operand can be empty if not present

            if (
                previous.token == commands.SUPERSCRIPT
                and previous.children is not None
                and len(previous.children) >= 2
                and previous.children[1].token == commands.PRIME
            ):
                node = Node(token=commands.SUPERSCRIPT, children=(previous.children[0], Node(token=commands.DPRIME)))
            else:
                node = Node(token=commands.SUPERSCRIPT, children=(previous, Node(token=commands.PRIME)))
        elif token in commands.COMMANDS_WITH_TWO_PARAMETERS:
            attributes = None
            children = tuple(_walk(tokens, terminator=terminator, limit=2))
            if token in (commands.OVERSET, commands.UNDERSET):
                children = children[::-1]
            node = Node(token=token, children=children, attributes=attributes)
        elif token in commands.COMMANDS_WITH_ONE_PARAMETER or token.startswith(commands.MATH):
            children = tuple(_walk(tokens, terminator=terminator, limit=1))
            node = Node(token=token, children=children)
        elif token == commands.NOT:
            try:
                next_node = tuple(_walk(tokens, terminator=terminator, limit=1))[0]
                if next_node.token.startswith("\\"):
                    negated_symbol = r"\n" + next_node.token[1:]
                    symbol = convert_symbol(negated_symbol)
                    if symbol:
                        node = Node(token=negated_symbol)
                        group.append(node)
                        continue
                node = Node(token=token)
                group.extend((node, next_node))
                continue
            except NoAvailableTokensError:
                node = Node(token=token)
        elif token in (commands.XLEFTARROW, commands.XRIGHTARROW):
            children = tuple(_walk(tokens, terminator=terminator, limit=1))
            if children[0].token == commands.OPENING_BRACKET:
                children = (
                    Node(
                        token=commands.BRACES, children=tuple(_walk(tokens, terminator=commands.CLOSING_BRACKET))[:-1]
                    ),
                    *tuple(_walk(tokens, terminator=terminator, limit=1)),
                )
            node = Node(token=token, children=children)
        elif token in (commands.HSKIP, commands.HSPACE, commands.KERN, commands.MKERN, commands.MSKIP, commands.MSPACE):
            children = tuple(_walk(tokens, terminator=terminator, limit=1))
            if children[0].token == commands.BRACES and children[0].children is not None:
                children = children[0].children
            node = Node(token=token, attributes={"width": children[0].token})
        elif token == commands.COLOR:
            attributes = {"mathcolor": next(tokens)}
            children = tuple(_walk(tokens, terminator=terminator))
            sibling = None
            if len(children) and children[-1].token == terminator:
                children, sibling = children[:-1], children[-1]
            group.append(Node(token=token, children=children, attributes=attributes))
            if sibling:
                group.append(sibling)
            break
        elif token == commands.STYLE:
            attributes = {"style": next(tokens)}
            next_node = tuple(_walk(tokens, terminator=terminator, limit=1))[0]
            node = next_node._replace(attributes=attributes)
        elif token in (
            *commands.BIG.keys(),
            *commands.BIG_OPEN_CLOSE.keys(),
            commands.FBOX,
            commands.HBOX,
            commands.MBOX,
            commands.MIDDLE,
            commands.TEXT,
            commands.TEXTBF,
            commands.TEXTIT,
            commands.TEXTRM,
            commands.TEXTSF,
            commands.TEXTTT,
        ):
            node = Node(token=token, text=next(tokens))
        elif token == commands.HREF:
            attributes = {"href": next(tokens)}
            children = tuple(_walk(tokens, terminator=terminator, limit=1))
            node = Node(token=token, children=children, attributes=attributes)
        elif token in (
            commands.ABOVE,
            commands.ATOP,
            commands.ABOVEWITHDELIMS,
            commands.ATOPWITHDELIMS,
            commands.BRACE,
            commands.BRACK,
            commands.CHOOSE,
            commands.OVER,
        ):
            attributes = None
            delimiter = None

            if token == commands.ABOVEWITHDELIMS:
                delimiter = next(tokens).lstrip("\\") + next(tokens).lstrip("\\")
            elif token == commands.ATOPWITHDELIMS:
                attributes = {"linethickness": "0"}
                delimiter = next(tokens).lstrip("\\") + next(tokens).lstrip("\\")
            elif token == commands.BRACE:
                delimiter = "{}"
            elif token == commands.BRACK:
                delimiter = "[]"
            elif token == commands.CHOOSE:
                delimiter = "()"

            if token in (commands.ABOVE, commands.ABOVEWITHDELIMS):
                dimension_node = tuple(_walk(tokens, terminator=terminator, limit=1))[0]
                dimension = _get_dimension(dimension_node)
                attributes = {"linethickness": dimension}
            elif token in (commands.ATOP, commands.BRACE, commands.BRACK, commands.CHOOSE):
                attributes = {"linethickness": "0"}

            denominator = tuple(_walk(tokens, terminator=terminator))

            sibling = None
            if len(denominator) and denominator[-1].token == terminator:
                denominator, sibling = denominator[:-1], denominator[-1]

            if len(denominator) == 0:
                if token in (commands.BRACE, commands.BRACK):
                    denominator = (Node(token=commands.BRACES, children=()),)
                else:
                    raise DenominatorNotFoundError
            if len(group) == 0:
                if token in (commands.BRACE, commands.BRACK):
                    group = [Node(token=commands.BRACES, children=())]
                else:
                    raise NumeratorNotFoundError
            if len(denominator) > 1:
                denominator = (Node(token=commands.BRACES, children=denominator),)

            if len(group) == 1:
                children = (*group, *denominator)
            else:
                children = (Node(token=commands.BRACES, children=tuple(group)), *denominator)
            group = [Node(token=commands.FRAC, children=children, attributes=attributes, delimiter=delimiter)]
            if sibling is not None:
                group.append(sibling)
            break
        elif token == commands.SQRT:
            root_nodes = None
            next_node = tuple(_walk(tokens, limit=1))[0]
            if next_node.token == commands.OPENING_BRACKET:
                root_nodes = tuple(_walk(tokens, terminator=commands.CLOSING_BRACKET))[:-1]
                next_node = tuple(_walk(tokens, limit=1))[0]
                if len(root_nodes) > 1:
                    root_nodes = (Node(token=commands.BRACES, children=root_nodes),)

            if root_nodes:
                node = Node(token=commands.ROOT, children=(next_node, *root_nodes))
            else:
                node = Node(token=token, children=(next_node,))
        elif token == commands.ROOT:
            root_nodes = tuple(_walk(tokens, terminator=r"\of"))[:-1]
            next_node = tuple(_walk(tokens, limit=1))[0]
            if len(root_nodes) > 1:
                root_nodes = (Node(token=commands.BRACES, children=root_nodes),)
            if root_nodes:
                node = Node(token=token, children=(next_node, *root_nodes))
            else:
                node = Node(token=token, children=(next_node, Node(token=commands.BRACES, children=())))
        elif token in commands.MATRICES:
            children = tuple(_walk(tokens, terminator=terminator))
            sibling = None
            if len(children) and children[-1].token == terminator:
                children, sibling = children[:-1], children[-1]
            if len(children) == 1 and children[0].token == commands.BRACES and children[0].children:
                children = children[0].children
            if sibling is not None:
                group.extend([Node(token=token, children=children, alignment=""), sibling])
                break
            else:
                node = Node(token=token, children=children, alignment="")
        elif token == commands.GENFRAC:
            delimiter = next(tokens).lstrip("\\") + next(tokens).lstrip("\\")
            dimension_node, style_node = tuple(_walk(tokens, terminator=terminator, limit=2))
            dimension = _get_dimension(dimension_node)
            style = _get_style(style_node)
            attributes = {"linethickness": dimension}
            children = tuple(_walk(tokens, terminator=terminator, limit=2))
            group.extend(
                [Node(token=style), Node(token=token, children=children, delimiter=delimiter, attributes=attributes)]
            )
            break
        elif token == commands.SIDESET:
            left, right, operator = tuple(_walk(tokens, terminator=terminator, limit=3))
            left_token, left_children = _make_subsup(left)
            right_token, right_children = _make_subsup(right)
            attributes = {"movablelimits": "false"}
            node = Node(
                token=token,
                children=(
                    Node(
                        token=left_token,
                        children=(
                            Node(
                                token=commands.VPHANTOM,
                                children=(
                                    Node(token=operator.token, children=operator.children, attributes=attributes),
                                ),
                            ),
                            *left_children,
                        ),
                    ),
                    Node(
                        token=right_token,
                        children=(
                            Node(token=operator.token, children=operator.children, attributes=attributes),
                            *right_children,
                        ),
                    ),
                ),
            )
        elif token == commands.SKEW:
            width_node, child = tuple(_walk(tokens, terminator=terminator, limit=2))
            width = width_node.token
            if width == commands.BRACES:
                if width_node.children is None or len(width_node.children) == 0:
                    raise InvalidWidthError
                width = width_node.children[0].token
            if not width.isdigit():
                raise InvalidWidthError
            node = Node(token=token, children=(child,), attributes={"width": f"{0.0555 * int(width):.3f}em"})
        elif token.startswith(commands.BEGIN):
            node = _get_environment_node(token, tokens)
        else:
            node = Node(token=token)

        group.append(node)

        if limit and len(group) >= limit:
            break
    if not has_available_tokens:
        raise NoAvailableTokensError
    return group