112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
import datetime
|
|
import xml.dom.minidom
|
|
import xml.etree.ElementTree as ET
|
|
from typing import Iterator, Optional
|
|
|
|
def stop_before_value(iterator: Iterator[str], stop_value: str) -> Iterator[str]:
|
|
"""
|
|
Creates an iterator that yields values from the input iterator
|
|
until it encounters the stop_value (exclusive).
|
|
|
|
Args:
|
|
iterator: The source iterator
|
|
stop_value: The value to stop before
|
|
|
|
Yields:
|
|
Values from the iterator until stop_value is encountered
|
|
If stop_value is part of an item, yields the part before stop_value
|
|
"""
|
|
for item in iterator:
|
|
if stop_value in item:
|
|
split_point = item.index(stop_value)
|
|
if split_point > 0:
|
|
yield item[:split_point]
|
|
break
|
|
yield item
|
|
|
|
def skip_prefix(iterator: Iterator[str], prefix: str) -> Iterator[str]:
|
|
"""
|
|
Creates an iterator that skips a prefix from the input iterator
|
|
and yields only the content after the prefix.
|
|
|
|
Args:
|
|
iterator: The source iterator
|
|
prefix: The prefix to skip
|
|
|
|
Yields:
|
|
Values from the iterator after the prefix has been fully skipped
|
|
"""
|
|
if not prefix:
|
|
# If no prefix to skip, yield everything
|
|
yield from iterator
|
|
return
|
|
|
|
prefix_remaining = prefix
|
|
|
|
for item in iterator:
|
|
if prefix_remaining:
|
|
# If the item starts with the remaining prefix
|
|
if prefix_remaining.startswith(item):
|
|
# Skip this item entirely
|
|
prefix_remaining = prefix_remaining[len(item):]
|
|
continue
|
|
elif item.startswith(prefix_remaining):
|
|
# Yield only the part after the prefix
|
|
yield item[len(prefix_remaining):]
|
|
prefix_remaining = ""
|
|
else:
|
|
# Item doesn't match prefix pattern, yield everything
|
|
# This is unexpected but we handle it gracefully
|
|
yield item
|
|
prefix_remaining = ""
|
|
else:
|
|
# No prefix remaining, yield all content
|
|
yield item
|
|
|
|
def pretty_print_element(elem: ET.Element, level: int = 0, max_line: int = 80) -> str:
|
|
"""Convert ElementTree element to pretty-printed string with custom formatting."""
|
|
indent = ' ' * level
|
|
|
|
# Handle empty elements
|
|
if not elem.text and not elem.tail and not elem.attrib and len(elem) == 0:
|
|
return f'{indent}<{elem.tag}/>'
|
|
|
|
# Build opening tag with attributes
|
|
tag = elem.tag
|
|
attrs = elem.attrib
|
|
if attrs:
|
|
attr_strings = [f'{k}="{v}"' for k, v in sorted(attrs.items())]
|
|
attr_str = ' ' + ' '.join(attr_strings)
|
|
|
|
if len(indent) + len(tag) + len(attr_str) + 2 > max_line:
|
|
attr_indent = indent + ' '
|
|
attr_str = '\n' + '\n'.join(f'{attr_indent}{a}' for a in attr_strings)
|
|
else:
|
|
attr_str = ''
|
|
|
|
parts = [f'{indent}<{tag}{attr_str}>']
|
|
|
|
# Handle text content
|
|
if elem.text and isinstance(elem.text, str) and elem.text.strip():
|
|
text = elem.text
|
|
if ']]>' in text:
|
|
escaped = text.replace('&', '&') \
|
|
.replace('<', '<') \
|
|
.replace('>', '>') \
|
|
.replace('"', '"') \
|
|
.replace("'", ''')
|
|
parts.append(f'{indent} {escaped}')
|
|
else:
|
|
parts.append(f'{indent} <![CDATA[{text}]]>')
|
|
|
|
# Handle children
|
|
for child in elem:
|
|
parts.append(pretty_print_element(child, level + 1, max_line))
|
|
if child.tail and child.tail.strip():
|
|
parts.append(f'{indent} {child.tail}')
|
|
|
|
parts.append(f'{indent}</{tag}>')
|
|
return '\n'.join(parts)
|
|
|
|
def format_timestamp(timestamp: datetime) -> str:
|
|
return timestamp.strftime("%Y%m%d_%H%M%S_%f")[:-3] |