История изменений
Исправление rtxtxtrx, (текущая версия) :
Кстати, я хоть и нафлудил с этими реализациями парсеров, но структуированный текст парсить не сложнее, я пример тоже набросал:
from __future__ import annotations
import string
import typing
from dataclasses import dataclass, field
from io import StringIO
class Node:
pass
@dataclass
class Element(Node):
name: str
attrs: dict[str, str] = field(default_factory=dict)
children: list[Node] = field(default_factory=list)
@dataclass
class Text(Node):
value: str
class SyntaxError(Exception):
pass
class MLParser:
def readch(self) -> str:
return self.fp.read(1)
def advance(self) -> None:
self.curch, self.nextch = self.nextch, self.readch()
def match(self, charset: str) -> bool:
if self.nextch and self.nextch in charset:
self.advance()
return True
return False
def expect(self, charset: str) -> None:
if not self.match(charset):
raise SyntaxError(f"syntax error at offset {self.fp.tell()}")
def parse_name(self) -> str:
rv = ""
while self.match(string.ascii_letters):
rv += self.curch
if not rv:
raise SyntaxError("required name")
return rv
def skip_spaces(self) -> None:
while self.match(string.whitespace):
pass
def parse_quouted(self) -> str:
self.expect('"')
rv = ""
while True:
if not self.nextch:
raise SyntaxError("unexpected end")
if self.match('"'):
break
self.advance()
rv += self.curch
return rv
def handle_close_tag(self) -> None:
name = self.parse_name()
self.expect(">")
if not self.open_tags or self.open_tags.pop() != name:
raise SyntaxError(f"unexpected close tag {name}")
# self.skip_spaces()
def collect_children(self, node: Element) -> None:
text = ""
while self.nextch:
if self.match("<"):
if text:
node.children.append(Text(text))
text = ""
if self.match("/"):
self.handle_close_tag()
break
child = Element(self.parse_name())
self.open_tags.append(child.name)
while True:
if self.match(">"):
break
self.expect(string.whitespace)
self.skip_spaces()
attr = self.parse_name()
self.skip_spaces()
self.expect("=")
self.skip_spaces()
child.attrs[attr] = self.parse_quouted()
self.collect_children(child)
node.children.append(child)
else:
self.advance()
text += self.curch
if text:
node.children.append(Text(text))
def parse(self, fp: typing.TextIO) -> Element:
self.fp = fp
self.curch = self.nextch = None
self.open_tags = []
self.advance()
root = Element("root")
self.collect_children(root)
if self.open_tags:
raise SyntaxError(f"unclosed tags: {', '.join(self.open_tags)}")
return root
import sys
print(MLParser().parse(StringIO(sys.argv[1])))
Вроде нормально все парсит:
python ml.py 'Hello, <span color="red"><i><b>world</b></i></span>!'
Element(name='root', attrs={}, children=[Text(value='Hello, '), Element(name='span', attrs={'color': 'red'}, children=[Element(name='i', attrs={}, children=[Element(name='b', attrs={}, children=[Text(value='world')])])]), Text(value='!')])
Для твоего диалекта текса изменения нужны минимальные. Или че ты там придумываешь. Простая задача, но малоприменимая или не знаю, что там придумать: текс в хтмл превращать? Зато показательно тотальное превосходство питона над статически-типизированными недоязыками. В нем НЕ НУЖНЫ DDD, паттерны и пр чушь, хотя я уверен, что найдутся аметисты, которые даже тут их обнаружат (считаю Java абсолютным злом как и заучивание [ее и применимых только в ней] паттернов)
Исправление rtxtxtrx, :
Кстати, я хоть и нафлудил с этими реализациями парсеров, но структуированный текст парсить не сложнее, я пример тоже набросал:
from __future__ import annotations
import string
import typing
from dataclasses import dataclass, field
from io import StringIO
class Node:
pass
@dataclass
class Element(Node):
name: str
attrs: dict[str, str] = field(default_factory=dict)
children: list[Node] = field(default_factory=list)
@dataclass
class Text(Node):
value: str
class SyntaxError(Exception):
pass
class MLParser:
def readch(self) -> str:
return self.fp.read(1)
def advance(self) -> None:
self.curch, self.nextch = self.nextch, self.readch()
def match(self, charset: str) -> bool:
if self.nextch and self.nextch in charset:
self.advance()
return True
return False
def expect(self, charset: str) -> None:
if not self.match(charset):
raise SyntaxError(f"syntax error at offset {self.fp.tell()}")
def parse_name(self) -> str:
rv = ""
while self.match(string.ascii_letters):
rv += self.curch
if not rv:
raise SyntaxError("required name")
return rv
def skip_spaces(self) -> None:
while self.match(string.whitespace):
pass
def parse_quouted(self) -> str:
self.expect('"')
rv = ""
while True:
if not self.nextch:
raise SyntaxError("unexpected end")
if self.match('"'):
break
self.advance()
rv += self.curch
return rv
def handle_close_tag(self) -> None:
name = self.parse_name()
self.expect(">")
if not self.open_tags or self.open_tags.pop() != name:
raise SyntaxError(f"unexpected close tag {name}")
# self.skip_spaces()
def collect_children(self, node: Element) -> None:
text = ""
while self.nextch:
if self.match("<"):
if text:
node.children.append(Text(text))
text = ""
if self.match("/"):
self.handle_close_tag()
break
child = Element(self.parse_name())
self.open_tags.append(child.name)
while True:
if self.match(">"):
break
self.expect(string.whitespace)
self.skip_spaces()
attr = self.parse_name()
self.skip_spaces()
self.expect("=")
self.skip_spaces()
child.attrs[attr] = self.parse_quouted()
self.collect_children(child)
node.children.append(child)
else:
self.advance()
text += self.curch
if text:
node.children.append(Text(text))
def parse(self, fp: typing.TextIO) -> Element:
self.fp = fp
self.curch = self.nextch = None
self.open_tags = []
self.advance()
root = Element("root")
self.collect_children(root)
if self.open_tags:
raise SyntaxError(f"unclosed tags: {', '.join(self.open_tags)}")
return root
import sys
print(MLParser().parse(StringIO(sys.argv[1])))
Вроде нормально все парсит:
python ml.py 'Hello, <span color="red"><i><b>world</b></i></span>!'
Element(name='root', attrs={}, children=[Text(value='Hello, '), Element(name='span', attrs={'color': 'red'}, children=[Element(name='i', attrs={}, children=[Element(name='b', attrs={}, children=[Text(value='world')])])]), Text(value='!')])
Для твоего диалекта текса изменения нужны минимальные. Или че ты там придумываешь. Простая задача, но малоприменимая. Зато показательно тотальное превосходство питона над статически-типизированными недоязыками. В нем НЕ НУЖНЫ DDD, паттерны и пр чушь, хотя я уверен, что найдутся аметисты, которые даже тут их обнаружат (считаю Java абсолютным злом как и заучивание [ее и применимых только в ней] паттернов)
Исправление rtxtxtrx, :
Кстати, я хоть и нафлудил с этими реализациями парсеров, но структуированный текст парсить не сложнее, я пример тоже набросал:
from __future__ import annotations
import string
import typing
from dataclasses import dataclass, field
from io import StringIO
class Node:
pass
@dataclass
class Element(Node):
name: str
attrs: dict[str, str] = field(default_factory=dict)
children: list[Node] = field(default_factory=list)
@dataclass
class Text(Node):
value: str
class SyntaxError(Exception):
pass
class MLParser:
def readch(self) -> str:
return self.fp.read(1)
def advance(self) -> None:
self.curch, self.nextch = self.nextch, self.readch()
def match(self, charset: str) -> bool:
if self.nextch and self.nextch in charset:
self.advance()
return True
return False
def expect(self, charset: str) -> None:
if not self.match(charset):
raise SyntaxError(f"syntax error at offset {self.fp.tell()}")
def parse_name(self) -> str:
rv = ""
while self.match(string.ascii_letters):
rv += self.curch
if not rv:
raise SyntaxError("required name")
return rv
def skip_spaces(self) -> None:
while self.match(string.whitespace):
pass
def parse_quouted(self) -> str:
self.expect('"')
rv = ""
while True:
if not self.nextch:
raise SyntaxError("unexpected end")
if self.match('"'):
break
self.advance()
rv += self.curch
return rv
def handle_close_tag(self) -> None:
name = self.parse_name()
self.expect(">")
if not self.open_tags or self.open_tags.pop() != name:
raise SyntaxError(f"unexpected close tag {name}")
# self.skip_spaces()
def collect_children(self, node: Element) -> None:
text = ""
while self.nextch:
if self.match("<"):
if text:
node.children.append(Text(text))
text = ""
if self.match("/"):
self.handle_close_tag()
break
child = Element(self.parse_name())
self.open_tags.append(child.name)
while True:
if self.match(">"):
break
self.expect(string.whitespace)
self.skip_spaces()
attr = self.parse_name()
self.skip_spaces()
self.expect("=")
self.skip_spaces()
child.attrs[attr] = self.parse_quouted()
self.collect_children(child)
node.children.append(child)
else:
self.advance()
text += self.curch
if text:
node.children.append(Text(text))
def parse(self, fp: typing.TextIO) -> Element:
self.fp = fp
self.curch = self.nextch = None
self.open_tags = []
self.advance()
root = Element("root")
self.collect_children(root)
if self.open_tags:
raise SyntaxError(f"unclosed tags: {', '.join(self.open_tags)}")
return root
import sys
print(MLParser().parse(StringIO(sys.argv[1])))
Вроде нормально все парсит:
python ml.py 'Hello, <span color="red"><i><b>world</b></i></span>!'
Element(name='root', attrs={}, children=[Text(value='Hello, '), Element(name='span', attrs={'color': 'red'}, children=[Element(name='i', attrs={}, children=[Element(name='b', attrs={}, children=[Text(value='world')])])]), Text(value='!')])
Для твоего диалекта текса изменения нужны минимальные. Или че ты там придумываешь. Простая задача, но малоприменимая. Зато показательно тотальное превосходство питона над статически-типизированными недоязыками
Исходная версия rtxtxtrx, :
Кстати, я хоть и нафлудил с этими реализациями парсеров, но структуированный текст парсить не сложнее, я пример тоже набросал:
from __future__ import annotations
import string
import typing
from dataclasses import dataclass, field
from io import StringIO
class Node:
pass
@dataclass
class Element(Node):
name: str
attrs: dict[str, str] = field(default_factory=dict)
children: list[Node] = field(default_factory=list)
@dataclass
class Text(Node):
value: str
class SyntaxError(Exception):
pass
class MLParser:
def readch(self) -> str:
return self.fp.read(1)
def advance(self) -> None:
self.curch, self.nextch = self.nextch, self.readch()
def match(self, charset: str) -> bool:
if self.nextch and self.nextch in charset:
self.advance()
return True
return False
def expect(self, charset: str) -> None:
if not self.match(charset):
raise SyntaxError(f"syntax error at offset {self.fp.tell()}")
def parse_name(self) -> str:
rv = ""
while self.match(string.ascii_letters):
rv += self.curch
if not rv:
raise SyntaxError("required name")
return rv
def skip_spaces(self) -> None:
while self.match(string.whitespace):
pass
def parse_quouted(self) -> str:
self.expect('"')
rv = ""
while True:
if not self.nextch:
raise SyntaxError("unexpected end")
if self.match('"'):
break
self.advance()
rv += self.curch
return rv
def handle_close_tag(self) -> None:
name = self.parse_name()
self.expect(">")
if not self.open_tags or self.open_tags.pop() != name:
raise SyntaxError(f"unexpected close tag {name}")
# self.skip_spaces()
def collect_children(self, node: Element) -> None:
text = ""
while self.nextch:
if self.match("<"):
if text:
node.children.append(Text(text))
text = ""
if self.match("/"):
self.handle_close_tag()
break
child = Element(self.parse_name())
self.open_tags.append(child.name)
while True:
if self.match(">"):
break
self.expect(string.whitespace)
self.skip_spaces()
attr = self.parse_name()
self.skip_spaces()
self.expect("=")
self.skip_spaces()
child.attrs[attr] = self.parse_quouted()
self.collect_children(child)
node.children.append(child)
else:
self.advance()
text += self.curch
if text:
node.children.append(Text(text))
def parse(self, fp: typing.TextIO) -> Element:
self.fp = fp
self.curch = self.nextch = None
self.open_tags = []
self.advance()
root = Element("root")
self.collect_children(root)
if self.open_tags:
raise SyntaxError(f"unclosed tags: {', '.join(self.open_tags)}")
return root
import sys
print(MLParser().parse(StringIO(sys.argv[1])))
Вроде нормально все парсит:
python ml.py 'Hello, <span color="red"><i><b>world</b></i></span>!'
Element(name='root', attrs={}, children=[Text(value='Hello, '), Element(name='span', attrs={'color': 'red'}, children=[Element(name='i', attrs={}, children=[Element(name='b', attrs={}, children=[Text(value='world')])])]), Text(value='!')])