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"{name}>"
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("&", "&")