126 lines
3.0 KiB
Python
126 lines
3.0 KiB
Python
"""Утилиты для обработки текста и экранирования.
|
||
|
||
Функции для транслитерации, экранирования LDAP-значений и нормализации текста.
|
||
"""
|
||
|
||
from typing import Optional
|
||
|
||
from ldap3.utils.conv import escape_filter_chars
|
||
|
||
# Словарь для транслитерации русских символов в латиницу
|
||
_RU2LAT = {
|
||
"а": "a",
|
||
"б": "b",
|
||
"в": "v",
|
||
"г": "g",
|
||
"д": "d",
|
||
"е": "e",
|
||
"ё": "e",
|
||
"ж": "zh",
|
||
"з": "z",
|
||
"и": "i",
|
||
"й": "i",
|
||
"к": "k",
|
||
"л": "l",
|
||
"м": "m",
|
||
"н": "n",
|
||
"о": "o",
|
||
"п": "p",
|
||
"р": "r",
|
||
"с": "s",
|
||
"т": "t",
|
||
"у": "u",
|
||
"ф": "f",
|
||
"х": "kh",
|
||
"ц": "ts",
|
||
"ч": "ch",
|
||
"ш": "sh",
|
||
"щ": "shch",
|
||
"ъ": "",
|
||
"ы": "y",
|
||
"ь": "",
|
||
"э": "e",
|
||
"ю": "yu",
|
||
"я": "ya",
|
||
}
|
||
|
||
|
||
def translit_to_ascii(text: str) -> str:
|
||
"""Транслитерирует строку в ascii: [a-z0-9], нижний регистр.
|
||
|
||
Args:
|
||
text (str): Входная строка.
|
||
|
||
Returns:
|
||
str: Нормализованная ASCII-строка.
|
||
|
||
Raises:
|
||
TypeError: Если text не строка.
|
||
"""
|
||
if not isinstance(text, str):
|
||
raise TypeError("text должен быть строкой")
|
||
s = (text or "").strip().lower()
|
||
out: list[str] = []
|
||
for ch in s:
|
||
if "a" <= ch <= "z" or "0" <= ch <= "9":
|
||
out.append(ch)
|
||
continue
|
||
if ch in (" ", ".", "_", "-"):
|
||
continue
|
||
if ch in _RU2LAT:
|
||
out.append(_RU2LAT[ch])
|
||
continue
|
||
return "".join(out)
|
||
|
||
|
||
def esc_filter(value: Optional[str]) -> str:
|
||
"""Экранирует строку для подстановки в LDAP-фильтры (RFC4515).
|
||
|
||
Args:
|
||
value (Optional[str]): Исходная строка.
|
||
|
||
Returns:
|
||
str: Экранированная строка.
|
||
"""
|
||
s = (value or "").strip()
|
||
try:
|
||
return escape_filter_chars(s)
|
||
except Exception:
|
||
# Fallback если ldap3 не сработал
|
||
return (
|
||
s.replace("\\", r"\5c")
|
||
.replace("*", r"\2a")
|
||
.replace("(", r"\28")
|
||
.replace(")", r"\29")
|
||
.replace("\x00", r"\00")
|
||
)
|
||
|
||
|
||
def esc_rdn(text: str) -> str:
|
||
"""Экранирует RDN-компонент Distinguished Name.
|
||
|
||
Args:
|
||
text (str): RDN-значение (например, "John, Doe").
|
||
|
||
Returns:
|
||
str: Экранированная строка (например, "John\\, Doe").
|
||
"""
|
||
return (
|
||
text.replace("\\", "\\\\")
|
||
.replace(",", "\\,")
|
||
.replace("+", "\\+")
|
||
.replace(";", "\\;")
|
||
.replace("=", "\\=")
|
||
.replace("<", "\\<")
|
||
.replace(">", "\\>")
|
||
.replace("#", "\\#")
|
||
.replace('"', '\\"')
|
||
)
|
||
|
||
|
||
__all__ = [
|
||
"translit_to_ascii",
|
||
"esc_filter",
|
||
"esc_rdn",
|
||
]
|