eva-agent MVP: wiki_sync и wiki_fetch (#1) #2
31
src/agent.py
31
src/agent.py
@ -331,14 +331,31 @@ class EvaClient(Http):
|
||||
return self._retrying(lambda: self.fetch_bytes(url[len(self.base):], limit))
|
||||
|
||||
|
||||
class _ParseError(Exception):
|
||||
"""Разбор HTML споткнулся (ParserBase.error в 3.9): см. _LinkParser.error."""
|
||||
|
||||
|
||||
class _LinkParser(HTMLParser):
|
||||
"""Собирает img[src] и a[href] в порядке появления: (тег, значение атрибута после unescape, alt/title, текст ссылки)."""
|
||||
"""Собирает img[src] и a[href] в порядке появления: (тег, значение атрибута после unescape, alt/title, текст ссылки).
|
||||
|
||||
На 3.9 `_markupbase` зовёт error() на `<![foo]>` и подобном, а базовый error() бросает NotImplementedError (I-1).
|
||||
error() бросает _ParseError, а parse_marked_section пропускает такую конструкцию до `>`, как мусорный комментарий."""
|
||||
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self, convert_charrefs=True)
|
||||
self.items = [] # [tag, ref, hint, text]
|
||||
self._open = None # запись открытой <a>, в неё копится текст
|
||||
|
||||
def error(self, message):
|
||||
raise _ParseError(message)
|
||||
|
||||
def parse_marked_section(self, i, report=1):
|
||||
try:
|
||||
return HTMLParser.parse_marked_section(self, i, report)
|
||||
except _ParseError:
|
||||
j = self.rawdata.find(">", i + 3)
|
||||
return -1 if j < 0 else j + 1 # -1: ждать данных; при close() goahead сам пропустит до `>`
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
a = dict(attrs)
|
||||
if tag == "img":
|
||||
@ -374,12 +391,18 @@ def classify_links(html: str, base_url: str) -> Tuple[list, list]:
|
||||
|
||||
ref — значение атрибута после unescape (design 12). Чужой хост и static/ не передаются."""
|
||||
parser = _LinkParser()
|
||||
parser.feed(html or "")
|
||||
parser.close()
|
||||
try:
|
||||
parser.feed(html or "")
|
||||
parser.close()
|
||||
except (_ParseError, NotImplementedError, AssertionError) as e: # запасной путь: собранное до сбоя сохраняется
|
||||
log(" разбор HTML прерван (%s: %s), ссылок до сбоя: %d" % (type(e).__name__, e, len(parser.items)))
|
||||
base = urllib.parse.urlsplit(base_url)
|
||||
attachments, links = [], []
|
||||
for _tag, ref, hint, text in parser.items:
|
||||
parts = urllib.parse.urlsplit(urllib.parse.urljoin(base_url.rstrip("/") + "/", ref.strip()))
|
||||
try:
|
||||
parts = urllib.parse.urlsplit(urllib.parse.urljoin(base_url.rstrip("/") + "/", ref.strip()))
|
||||
except ValueError: # `http://[::1` — ссылка битая, пропускаем (I-1)
|
||||
continue
|
||||
if (parts.scheme, parts.netloc) != (base.scheme, base.netloc):
|
||||
continue
|
||||
path = parts.path
|
||||
|
||||
@ -78,6 +78,29 @@ class ClassifyLinksTest(unittest.TestCase):
|
||||
'<a href="%s/a.bin"><img src="%s/b.png"></a><img src="%s/c.png">' % (OBJ, OBJ, OBJ), BASE)
|
||||
self.assertEqual([a["name"] for a in att], ["a.bin", "b.png", "c.png"])
|
||||
|
||||
def test_unknown_marked_section_does_not_crash(self):
|
||||
"""I-1: на 3.9 `<![foo]>` уходит в ParserBase.error() → NotImplementedError; ссылки до и после сохраняются."""
|
||||
html = ('<img src="%s/a.png"><![foo]><p>текст</p><![if !supportLists]>x<![endif]>'
|
||||
'<a href="/project/Document/DOC-3">c</a><img src="%s/b.png">' % (OBJ, OBJ))
|
||||
att, links = agent.classify_links(html, BASE)
|
||||
self.assertEqual([a["name"] for a in att], ["a.png", "b.png"])
|
||||
self.assertEqual(links, [{"ref": "/project/Document/DOC-3", "code": "DOC-3"}])
|
||||
|
||||
def test_marked_section_bad_name_and_unclosed(self):
|
||||
for html in ('<img src="%s/a.png"><![ ]><img src="%s/b.png">' % (OBJ, OBJ),
|
||||
'<img src="%s/a.png"><img src="%s/b.png"><![foo' % (OBJ, OBJ)):
|
||||
with self.subTest(html=html):
|
||||
att, _ = agent.classify_links(html, BASE)
|
||||
self.assertEqual([a["name"] for a in att], ["a.png", "b.png"])
|
||||
|
||||
def test_bad_url_skipped(self):
|
||||
"""I-1: `http://[::1` и порт не числом дают ValueError в urljoin/urlsplit — ссылка пропускается, остальные целы."""
|
||||
html = ('<img src="http://[::1"><a href="https://[zz/files/x">x</a><img src="https://eva.test:abc/files/y.png">'
|
||||
'<img src="%s/b.png"><a href="/project/Document/DOC-4">d</a>' % OBJ)
|
||||
att, links = agent.classify_links(html, BASE)
|
||||
self.assertEqual([a["name"] for a in att], ["b.png"])
|
||||
self.assertEqual([l["code"] for l in links], ["DOC-4"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user