File: fallback.py

package info (click to toggle)
python-nbxmpp 6.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,340 kB
  • sloc: python: 19,639; makefile: 4
file content (116 lines) | stat: -rw-r--r-- 3,180 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from __future__ import annotations

from typing import Any

import logging
import operator
from dataclasses import dataclass

from nbxmpp.exceptions import FallbackLanguageError
from nbxmpp.namespaces import Namespace
from nbxmpp.protocol import Message


@dataclass(frozen=True)
class FallbackRange:
    start: int
    end: int


FallbackLangMapT = dict[str | None, FallbackRange | None]
FallbacksForT = dict[str, FallbackLangMapT | None]


def parse_fallback_indication(
    log: logging.Logger | logging.LoggerAdapter[Any], stanza: Message
) -> FallbacksForT | None:
    fallbacks = stanza.getTags(
        "fallback",
        namespace=Namespace.FALLBACK,
    )
    if not fallbacks:
        return None

    fallbacks_for: FallbacksForT = {}
    for fallback in fallbacks:
        for_ = fallback.getAttr("for")
        if not for_:
            log.warning('Missing "for" attribute on fallback indication')
            continue

        bodies = fallback.getTags("body")
        if not bodies:
            fallbacks_for[for_] = None
            continue

        fallback_lang_map: FallbackLangMapT = {}
        for body in bodies:
            lang = body.getAttr("xml:lang") or None
            start = body.getAttr("start") or None
            end = body.getAttr("end") or None

            if type(start) is not type(end):
                log.warning("Incorrect range on fallback indication")
                continue

            range_ = None
            if start is not None:
                assert end is not None
                try:
                    range_ = FallbackRange(start=int(start), end=int(end))
                except Exception:
                    log.warning("Incorrect range on fallback indication")
                    continue

            fallback_lang_map[lang] = range_

        if fallback_lang_map:
            # Only store data for the key if there was at least one
            # valid body
            fallbacks_for[for_] = fallback_lang_map

    return fallbacks_for


def strip_fallback(
    fallbacks_for: FallbacksForT,
    fallback_ns: set[str],
    lang: str | None,
    text: str,
) -> str:
    fallbacks: list[FallbackRange] = []
    # Gather all fallbacks we support
    for ns in fallback_ns:
        try:
            fallback_lang_map = fallbacks_for[ns]
        except KeyError:
            continue

        if fallback_lang_map is None:
            # At least one of the fallbacks declared the whole
            # body as fallback
            return ""

        try:
            range_ = fallback_lang_map[lang]
        except KeyError:
            # Fallback missing for this language, assume the stanza is
            # malformed and return the whole text
            raise FallbackLanguageError

        if range_ is None:
            # No range means the whole body is considered a fallback
            return ""

        fallbacks.append(range_)

    if not fallbacks:
        return text

    fallbacks.sort(key=operator.attrgetter("start"), reverse=True)

    stripped_text = text
    for range_ in fallbacks:
        stripped_text = stripped_text[: range_.start] + stripped_text[range_.end :]

    return stripped_text