from typing import List, Union class SSMLNode: pass class SSMLText(SSMLNode): def __init__(self, text: str): self.text = text def __eq__(self, other): return isinstance(other, SSMLText) and self.text == other.text def __repr__(self): return f"SSMLText({self.text!r})" class SSMLAttribute: def __init__(self, name: str, value: str): self.name = name self.value = value def __eq__(self, other): return isinstance(other, SSMLAttribute) and self.name == other.name and self.value == other.value def __repr__(self): return f"SSMLAttribute({self.name!r}, {self.value!r})" class SSMLElement(SSMLNode): def __init__(self, name: str, attributes: List[SSMLAttribute], children: List[SSMLNode]): self.name = name self.attributes = attributes self.children = children def __eq__(self, other): return ( isinstance(other, SSMLElement) and self.name == other.name and self.attributes == other.attributes and self.children == other.children ) def __repr__(self): return f"SSMLElement({self.name!r}, {self.attributes!r}, {self.children!r})" class Ssml: @staticmethod def parse_ssml(ssml: str) -> SSMLNode: if not Ssml.validate_input_ssml(ssml): raise ValueError("Tags could not be parsed") return Ssml.get_ssml_node(ssml) @staticmethod def validate_input_ssml(input_string: str) -> bool: return input_string.startswith("") @staticmethod def get_ssml_node(input_string: str) -> SSMLNode: if not input_string: return None if not input_string.startswith("<") or not input_string.endswith(">"): raise ValueError("Tags could not be parsed") if "<" not in input_string or ">" not in input_string: return SSMLText(Ssml.unescape_xml_chars(input_string)) name = input_string[input_string.index("<")+1:input_string.index(">")].strip() start_tag = f"<{name}>" ssml_attributes = [] if " " in name: attribute_string = name[name.index(" ")+1:] if "=" not in attribute_string: raise ValueError("Attributes could not be parsed") while "=" in attribute_string: key = attribute_string[:attribute_string.index("=")].strip() if not key: raise ValueError("Attributes could not be parsed") attribute_string = attribute_string[attribute_string.index("=")+1:].strip() end_index = len(attribute_string) if " " not in attribute_string else attribute_string.index(" ") value = attribute_string[:end_index].strip() if Ssml.count_double_quotes(value) != 2: raise ValueError("Attributes could not be parsed") value = value.replace('"', '') ssml_attributes.append(SSMLAttribute(key, value)) attribute_string = attribute_string[end_index:] name = name[:name.index(" ")] end_tag = f"" if start_tag not in input_string or end_tag not in input_string: raise ValueError("Tags could not be parsed") body = input_string[len(start_tag):input_string.rindex(end_tag)] if not body: return SSMLElement(name, ssml_attributes, []) if "<" not in body and ">" not in body: return SSMLElement(name, ssml_attributes, [SSMLText(Ssml.unescape_xml_chars(body))]) if ("<" not in body and ">" in body) or ("<" in body and ">" not in body): raise ValueError("Tags could not be parsed") before = body[:body.index("<")] after = body[body.rindex(">")+1:] children = body[len(before):body.rindex(">")+1] child_nodes = [] if before: child_nodes.append(SSMLText(Ssml.unescape_xml_chars(before))) if children: child_nodes.append(Ssml.get_ssml_node(children)) if after: child_nodes.append(SSMLText(Ssml.unescape_xml_chars(after))) return SSMLElement(name, ssml_attributes, child_nodes) @staticmethod def count_double_quotes(s: str) -> int: return s.count('"') @staticmethod def ssml_node_to_text(node: SSMLNode) -> str: ssml_text_list = Ssml.get_ssml_text_list(node) return ''.join([t.text for t in ssml_text_list]) @staticmethod def get_ssml_text_list(node: SSMLNode) -> List[SSMLText]: ssml_text_list = [] if isinstance(node, SSMLText): ssml_text_list.append(node) elif isinstance(node, SSMLElement): for child in node.children: ssml_text_list.extend(Ssml.get_ssml_text_list(child)) return ssml_text_list @staticmethod def unescape_xml_chars(text: str) -> str: return text.replace("<", "<").replace(">", ">").replace("&", "&")