Skip to content
Snippets Groups Projects
Select Git revision
  • 18715f8a2de4074d0152cf4acf511b3dbca41b22
  • master default protected
  • dependabot/pip/py-1.10.0
  • dependabot/pip/django-2.2.13
  • dependabot/pip/bleach-3.1.4
5 results

snap_test_node.py

Blame
  • parser.py 8.48 KiB
    import json
    import re
    import sys
    import urllib
    from collections import defaultdict
    
    import bs4
    from django.utils.text import slugify
    
    from shared.utils import strip_all_html_tags
    
    from .constants import BENEFITS
    
    KNOWN_KEYS = [
        "nadpis",
        "anotace",
        "problem",
        "kontext-problemu",
        "ideal",
        "navrhovana-opatreni",
        "casovy-horizont",
        "co-jsme-uz-udelali",
        "faq",
        "souvisejici-body",
        "zdroje",
    ]
    
    BENEFIT_FOR_ALL = "společnost jako celek"
    
    MAIN_BENEFITS = {slugify(old_name): num for num, _, old_name in BENEFITS}
    
    
    def parse_program_html(fp):
        # Načteme celý dokument.
        html = bs4.BeautifulSoup(fp, "html5lib")
    
        # Vyházíme odkazy na komentáře.
        for cmnt in html.select('*[id^="cmnt"]'):
            cmnt.parent.extract()
    
        # Bod má svůj pracovní název
        nazev_bodu = html.select_one("h1, h2, h3").text.strip()
    
        # Bod má své pojmenované sekce.
        bod = {}
    
        SEKCE = [
            "Nadpis",
            "Anotace",
            "Problém",
            "Kontext problému",
            "Ideál",
            "Navrhovaná opatření",
            "Časový horizont",
            "FAQ",
            "Související body",
            "Zdroje",
            "Co jsme už udělali",
        ]
    
        # Tabulka benefitů má své pojmenované cílové skupiny.
        benefity = {}
    
        # Chceme očesat HTML na výstupu a nechat jenom tyto atributy.
        ATRIBUTY = set(["id", "href"])
    
        # Dokument má právě dvě tabulky. První je s bodem a druhá s jeho benefity.
        bod_html, bene_html = html.select("table")
    
        # Z CSS je potřeba vyvodit, které třídy odpovídají tučnému textu a
        # které superskriptu. Protože Google.
    
        strong = []
        sup = []
    
        for style in html.select("style"):
            strong = re.findall(r"(\.c[0-9]+)\{[^{]*font-weight:700", style.text)
            sup = re.findall(r"(\.c[0-9]+)\{[^{]*vertical-align:super", style.text)
    
        assert strong, "Nenašel jsem styl pro tučný text"
        assert sup, "Nenašel jsem styl pro superskript"
    
        def vycisti(sekce):
            sekce.name = "div"
            sekce.attrs.clear()
    
            # Nahradíme třídy nativními HTML prvky.
            for tag in sekce.select(", ".join(sup)):
                tag.name = "sup"
    
            for tag in sekce.select(", ".join(strong)):
                tag.name = "strong"
    
            # Zbavíme se <span>ů.
            for tag in sekce.select("span"):
                tag.unwrap()
    
            # Ořízneme nežádoucí atributy.
            for tag in sekce.find_all():
                for attr in list(tag.attrs):
                    if attr not in ATRIBUTY:
                        del tag.attrs[attr]
    
                    if tag.name != "a" and attr == "id":
                        del tag.attrs[attr]
    
            # Ořízneme prázdné tagy.
            for tag in sekce.find_all():
                if tag.text == "" and tag.name not in ["br", "hr"]:
                    tag.extract()
    
            # Opravíme odkazy, které se nakazily Googlem.
            for tag in sekce.select("*[href]"):
                _proto, _loc, _path, query, _frag = urllib.parse.urlsplit(tag.attrs["href"])
                qs = urllib.parse.parse_qs(query)
    
                if "q" in qs:
                    tag.attrs["href"] = qs["q"][0]
    
            # Opravíme odkazy, které se nakazily Facebookem.
            for tag in sekce.select("*[href]"):
                proto, loc, path, query, frag = urllib.parse.urlsplit(tag.attrs["href"])
                qs = urllib.parse.parse_qs(query)
    
                if "fbclid" in qs:
                    del qs["fbclid"]
                    query = urllib.parse.urlencode(qs, doseq=True)
                    tag.attrs["href"] = urllib.parse.urlunsplit(
                        (proto, loc, path, query, frag)
                    )
    
            # Spojíme po sobě následující prvky některých typů.
            for fst in sekce.select("ul, sup, strong"):
                if fst.parent is None:
                    continue
    
                snd = fst.next_sibling
    
                while snd is not None:
                    if snd.name == fst.name:
                        snd.extract()
                        for child in snd:
                            fst.append(child)
                    else:
                        break
    
                    snd = fst.next_sibling
    
        # Nejprve zpracujeme bod.
        radky = list(bod_html.select("tr"))
    
        for radek in radky[1:]:
            nazev, sekce = radek.select("td")
    
            nazev = nazev.text
            nazev = nazev.strip(" \u00a0\t\r\n:")
            nazev = re.sub("[ \u00a0]+", " ", nazev)
    
            if nazev not in SEKCE:
                print("Přebývá neznámá sekce: {!r}".format(nazev), file=sys.stderr)
    
            vycisti(sekce)
            bod[nazev] = sekce
    
        for nazev in SEKCE:
            if nazev not in bod:
                print("Chybí povinná sekce {!r}".format(nazev), file=sys.stderr)
    
        # Benefity
    
        for radek in bene_html.select("tr")[1:]:
            cilovka, benefit, _info = radek.select("td")
    
            cilovka = cilovka.text
            cilovka = re.sub(r"\(.*\)", "", cilovka)
            cilovka = cilovka.strip(" \u00a0\t\r\n:")
            cilovka = re.sub("[ \u00a0]+", " ", cilovka)
    
            vycisti(benefit)
    
            if benefit.text.strip() != "":
                benefity[cilovka] = benefit
    
        # Pro případnou zběžnou kontrolu:
    
        # print("<h1>" + nazev_bodu + "</h1>")
    
        # for nazev, sekce in bod.items():
        #     print("<hr/>")
        #     print("<h2>" + nazev + "</h2>")
        #     print(sekce)
    
        # print("<hr/>")
        # print("<h2>Benefity</h2>")
        # for cilovka, benefit in benefity.items():
        #     print("<h3>" + cilovka + "</h3>")
        #     print(benefit)
    
        return {
            "nazev": nazev_bodu,
            "sekce": {nazev: str(sekce) for nazev, sekce in bod.items()},
            "benefity": {cilovka: str(benefit) for cilovka, benefit in benefity.items()},
        }
    
    
    def strip_div(value):
        return value.replace("<div>", "").replace("</div>", "")
    
    
    def replace_tags(value):
        value = strip_div(value)
        if not value.startswith("<p>"):
            value = f"<p>{value}</p>"
        return value
    
    
    def set_fancy_lists(value):
        value = value.replace("<ul>", '<ul class="unordered-list unordered-list-checks">')
        value = value.replace("<li>", '<li class="mb-4">')
        return value
    
    
    def clean_point(point):
        out = {}
    
        for old_key, val in point.items():
            key = slugify(old_key)
    
            if key not in KNOWN_KEYS:
                raise ValueError(f"Unknown key: {old_key}")
    
            if key in ["nadpis"]:
                out[key] = strip_all_html_tags(val)
            else:
                out[key] = replace_tags(val)
    
        return out
    
    
    def prepare_faq(value):
        soup = bs4.BeautifulSoup(value, "html.parser")
        questions = defaultdict(list)
        for tag in soup.children:
            if tag.strong:
                key = tag.strong.string
            else:
                questions[key].append(str(tag))
    
        data = []
        for key, val in questions.items():
            data.append(
                {"type": "question", "value": {"question": key, "answer": "".join(val)}}
            )
    
        return json.dumps(data)
    
    
    def prepare_horizon(value):
        raw = strip_all_html_tags(value)
        m = re.match(r"^(\d+)\s(\w+)$", raw)
        if m:
            return None, m.group(1), m.group(2)
        return value, None, None
    
    
    def print_preview(point):
        print("")
        for key, val in point.items():
            print(key, ":", val[:120])
    
    
    def print_full(point):
        for key, val in point.items():
            print("")
            print(key)
            print("-" * 100)
            print(val)
            print("-" * 100)
    
    
    def prepare_point(source):
        point = clean_point(source)
        # print_full(point)
        return point
    
    
    def prepare_benefit_for_all(benefits):
        if BENEFIT_FOR_ALL in benefits:
            text = benefits[BENEFIT_FOR_ALL]
            return strip_div(text)
        return None
    
    
    def prepare_main_benefits(benefits):
        data = []
        for name, text in benefits.items():
            if name == BENEFIT_FOR_ALL:
                continue
            name_slug = slugify(name)
            if name_slug in MAIN_BENEFITS:
                data.append(
                    {
                        "type": "benefit",
                        "value": {
                            "variant": MAIN_BENEFITS[name_slug],
                            "text": strip_div(text),
                        },
                    }
                )
        return json.dumps(data) if data else None
    
    
    def prepare_benefits(benefits):
        data = []
        for name, text in benefits.items():
            if name == BENEFIT_FOR_ALL:
                continue
            name_slug = slugify(name)
            if name_slug not in MAIN_BENEFITS:
                data.append(
                    {
                        "type": "benefit",
                        "value": {"title": name, "text": strip_div(text)},
                    }
                )
        return json.dumps(data) if data else None