From fe41ddbc51b4b7c6ce7d958ea7622983d7805b7e Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 17 Feb 2024 13:52:16 +0000 Subject: Support attributes in HTML paths in style mappings --- mammoth/html_paths.py | 13 +++--- mammoth/styles/parser/html_path_parser.py | 74 ++++++++++++++++++++++++------- mammoth/styles/parser/token_iterator.py | 25 ++++++----- 3 files changed, 78 insertions(+), 34 deletions(-) (limited to 'mammoth') diff --git a/mammoth/html_paths.py b/mammoth/html_paths.py index c91813a..a8fb952 100644 --- a/mammoth/html_paths.py +++ b/mammoth/html_paths.py @@ -7,15 +7,16 @@ def path(elements): return HtmlPath(elements) -def element(names, class_names=None, fresh=None, separator=None): +def element(names, attributes=None, class_names=None, fresh=None, separator=None): + if attributes is None: + attributes = {} if class_names is None: class_names = [] if fresh is None: fresh = False if class_names: - attributes = {"class": " ".join(class_names)} - else: - attributes = {} + attributes["class"] = " ".join(class_names) + return HtmlPathElement(html.tag( tag_names=names, attributes=attributes, @@ -27,13 +28,13 @@ def element(names, class_names=None, fresh=None, separator=None): @cobble.data class HtmlPath(object): elements = cobble.field() - + def wrap(self, generate_nodes): nodes = generate_nodes() for element in reversed(self.elements): nodes = element.wrap_nodes(nodes) - + return nodes diff --git a/mammoth/styles/parser/html_path_parser.py b/mammoth/styles/parser/html_path_parser.py index 9e155b6..8ed6fed 100644 --- a/mammoth/styles/parser/html_path_parser.py +++ b/mammoth/styles/parser/html_path_parser.py @@ -1,6 +1,15 @@ +import cobble + from ... import html_paths from .tokeniser import TokenType -from .token_parser import parse_identifier, parse_string, try_parse_class_name +from .token_parser import parse_identifier, parse_string + + +@cobble.data +class _AttributeOrClassName(object): + name = cobble.field() + value = cobble.field() + append = cobble.field() def parse_html_path(tokens): @@ -12,26 +21,33 @@ def parse_html_path(tokens): def _parse_html_path_elements(tokens): elements = [] - + if tokens.peek_token_type() == TokenType.IDENTIFIER: elements.append(_parse_element(tokens)) - + while tokens.try_skip_many(((TokenType.WHITESPACE, None), (TokenType.SYMBOL, ">"))): tokens.skip(TokenType.WHITESPACE) elements.append(_parse_element(tokens)) - + return elements def _parse_element(tokens): tag_names = _parse_tag_names(tokens) - class_names = _parse_class_names(tokens) + attributes_list = _parse_attribute_or_class_names(tokens) is_fresh = _parse_is_fresh(tokens) separator = _parse_separator(tokens) - + + attributes = {} + for attribute in attributes_list: + if attribute.append and attributes.get(attribute.name): + attributes[attribute.name] += " " + attribute.value + else: + attributes[attribute.name] = attribute.value + return html_paths.element( tag_names, - class_names=class_names, + attributes=attributes, fresh=is_fresh, separator=separator, ) @@ -39,24 +55,48 @@ def _parse_element(tokens): def _parse_tag_names(tokens): tag_names = [parse_identifier(tokens)] - + while tokens.try_skip(TokenType.SYMBOL, "|"): tag_names.append(parse_identifier(tokens)) - + return tag_names -def _parse_class_names(tokens): - class_names = [] - +def _parse_attribute_or_class_names(tokens): + attribute_or_class_names = [] + while True: - class_name = try_parse_class_name(tokens) - if class_name is None: + attribute_or_class_name = _try_parse_attribute_or_class_name(tokens) + if attribute_or_class_name is None: break else: - class_names.append(class_name) - - return class_names + attribute_or_class_names.append(attribute_or_class_name) + + return attribute_or_class_names + + +def _try_parse_attribute_or_class_name(tokens): + if tokens.is_next(TokenType.SYMBOL, "["): + return _parse_attribute(tokens) + if tokens.is_next(TokenType.SYMBOL, "."): + return _parse_class_name(tokens) + else: + return None + + +def _parse_attribute(tokens): + tokens.skip(TokenType.SYMBOL, "[") + name = parse_identifier(tokens) + tokens.skip(TokenType.SYMBOL, "=") + value = parse_string(tokens) + tokens.skip(TokenType.SYMBOL, "]") + return _AttributeOrClassName(name=name, value=value, append=False) + + +def _parse_class_name(tokens): + tokens.skip(TokenType.SYMBOL, ".") + class_name = parse_identifier(tokens) + return _AttributeOrClassName(name="class", value=class_name, append=True) def _parse_is_fresh(tokens): diff --git a/mammoth/styles/parser/token_iterator.py b/mammoth/styles/parser/token_iterator.py index fded56d..931b419 100644 --- a/mammoth/styles/parser/token_iterator.py +++ b/mammoth/styles/parser/token_iterator.py @@ -8,13 +8,13 @@ class TokenIterator(object): def __init__(self, tokens): self._tokens = tokens self._index = 0 - + def peek_token_type(self): return self._tokens[self._index].type - + def next_value(self, token_type=None): return self._next(token_type).value - + def _next(self, token_type=None): token = self._tokens[self._index] if token_type is None or token.type == token_type: @@ -22,7 +22,7 @@ class TokenIterator(object): return token else: raise self._unexpected_token_type(token_type, token) - + def skip(self, token_type, token_value=None): token = self._tokens[self._index] if token.type == token_type and (token_value is None or token.value == token_value): @@ -30,15 +30,14 @@ class TokenIterator(object): return True else: raise self._unexpected_token_type(token_type, token) - + def try_skip(self, token_type, token_value=None): - token = self._tokens[self._index] - if token.type == token_type and (token_value is None or token.value == token_value): + if self.is_next(token_type, token_value): self._index += 1 return True else: return False - + def try_skip_many(self, tokens): start = self._index for token_type, token_value in tokens: @@ -48,9 +47,13 @@ class TokenIterator(object): return False else: self._index += 1 - + return True - + + def is_next(self, token_type, token_value=None): + token = self._tokens[self._index] + return token.type == token_type and (token_value is None or token.value == token_value) + def _unexpected_token_type(self, token_type, token): raise LineParseError() - + -- cgit v1.2.3