2015-10-03 12:53:45 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2019-01-29 13:14:30 +01:00
|
|
|
# Copyright 2015-2019 Mike Fährmann
|
2015-10-03 12:53:45 +02:00
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License version 2 as
|
|
|
|
# published by the Free Software Foundation.
|
|
|
|
|
2019-01-29 13:14:30 +01:00
|
|
|
"""Collection of functions that work on strings/text"""
|
2015-10-03 12:53:45 +02:00
|
|
|
|
|
|
|
import re
|
2016-02-18 15:54:58 +01:00
|
|
|
import html
|
2018-04-20 14:53:21 +02:00
|
|
|
import os.path
|
2015-10-03 12:53:45 +02:00
|
|
|
import urllib.parse
|
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2018-04-14 20:56:21 +02:00
|
|
|
INVALID_XML_CHARS = (
|
|
|
|
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
|
|
|
|
"\x08", "\x0b", "\x0c", "\x0e", "\x0f", "\x10", "\x11", "\x12",
|
|
|
|
"\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a",
|
|
|
|
"\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
|
|
|
|
)
|
2017-03-14 09:09:04 +01:00
|
|
|
|
|
|
|
|
|
|
|
def clean_xml(xmldata, repl=""):
|
2018-04-14 20:56:21 +02:00
|
|
|
"""Replace/Remove invalid control characters in 'xmldata'"""
|
|
|
|
if not isinstance(xmldata, str):
|
|
|
|
try:
|
|
|
|
xmldata = "".join(xmldata)
|
|
|
|
except TypeError:
|
|
|
|
return ""
|
2017-03-14 09:09:04 +01:00
|
|
|
for char in INVALID_XML_CHARS:
|
|
|
|
if char in xmldata:
|
|
|
|
xmldata = xmldata.replace(char, repl)
|
|
|
|
return xmldata
|
|
|
|
|
|
|
|
|
2018-04-14 22:09:42 +02:00
|
|
|
def remove_html(txt):
|
2015-10-03 12:53:45 +02:00
|
|
|
"""Remove html-tags from a string"""
|
2018-04-14 22:09:42 +02:00
|
|
|
try:
|
|
|
|
return " ".join(re.sub("<[^>]+>", " ", txt).split())
|
|
|
|
except TypeError:
|
|
|
|
return ""
|
2015-10-03 12:53:45 +02:00
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2018-05-27 15:00:41 +02:00
|
|
|
def split_html(txt, sep=None):
|
|
|
|
"""Split input string by html-tags"""
|
|
|
|
try:
|
|
|
|
return [
|
2019-01-29 13:14:30 +01:00
|
|
|
x.strip() for x in re.split("<[^>]+>", txt)
|
2018-05-27 15:00:41 +02:00
|
|
|
if x and not x.isspace()
|
|
|
|
]
|
|
|
|
except TypeError:
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
2015-10-03 12:53:45 +02:00
|
|
|
def filename_from_url(url):
|
2019-01-31 12:23:25 +01:00
|
|
|
"""Extract the last part of an URL to use as a filename"""
|
2015-10-03 12:53:45 +02:00
|
|
|
try:
|
2018-02-18 16:50:07 +01:00
|
|
|
return urllib.parse.urlsplit(url).path.rpartition("/")[2]
|
2018-04-14 22:09:42 +02:00
|
|
|
except (TypeError, AttributeError):
|
|
|
|
return ""
|
2015-10-03 12:53:45 +02:00
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2019-01-31 12:23:25 +01:00
|
|
|
def ext_from_url(url):
|
|
|
|
"""Extract the filename extension of an URL"""
|
|
|
|
filename = filename_from_url(url)
|
|
|
|
ext = os.path.splitext(filename)[1]
|
|
|
|
return ext[1:].lower()
|
|
|
|
|
|
|
|
|
2015-11-16 02:20:22 +01:00
|
|
|
def nameext_from_url(url, data=None):
|
2019-01-31 12:23:25 +01:00
|
|
|
"""Extract the last part of an URL and fill 'data' accordingly"""
|
2015-11-16 02:20:22 +01:00
|
|
|
if data is None:
|
|
|
|
data = {}
|
|
|
|
data["filename"] = unquote(filename_from_url(url))
|
|
|
|
data["name"], ext = os.path.splitext(data["filename"])
|
2015-11-16 17:40:05 +01:00
|
|
|
data["extension"] = ext[1:].lower()
|
2015-11-16 02:20:22 +01:00
|
|
|
return data
|
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2015-10-03 12:53:45 +02:00
|
|
|
def clean_path_windows(path):
|
|
|
|
"""Remove illegal characters from a path-segment (Windows)"""
|
2015-10-11 16:21:55 +02:00
|
|
|
try:
|
|
|
|
return re.sub(r'[<>:"\\/|?*]', "_", path)
|
|
|
|
except TypeError:
|
2018-04-14 22:09:42 +02:00
|
|
|
return ""
|
2015-10-03 12:53:45 +02:00
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2015-10-03 12:53:45 +02:00
|
|
|
def clean_path_posix(path):
|
|
|
|
"""Remove illegal characters from a path-segment (Posix)"""
|
2015-10-11 16:21:55 +02:00
|
|
|
try:
|
|
|
|
return path.replace("/", "_")
|
|
|
|
except AttributeError:
|
2018-04-14 22:09:42 +02:00
|
|
|
return ""
|
2015-10-03 12:53:45 +02:00
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2015-10-03 12:53:45 +02:00
|
|
|
def extract(txt, begin, end, pos=0):
|
2015-11-02 15:52:26 +01:00
|
|
|
"""Extract the text between 'begin' and 'end' from 'txt'
|
|
|
|
|
|
|
|
Args:
|
|
|
|
txt: String to search in
|
|
|
|
begin: First string to be searched for
|
|
|
|
end: Second string to be searched for after 'begin'
|
|
|
|
pos: Starting position for searches in 'txt'
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The string between the two search-strings 'begin' and 'end' beginning
|
|
|
|
with position 'pos' in 'txt' as well as the position after 'end'.
|
|
|
|
|
|
|
|
If at least one of 'begin' or 'end' is not found, None and the original
|
|
|
|
value of 'pos' is returned
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
extract("abcde", "b", "d") -> "c" , 4
|
|
|
|
extract("abcde", "b", "d", 3) -> None, 3
|
|
|
|
"""
|
2015-10-03 12:53:45 +02:00
|
|
|
try:
|
|
|
|
first = txt.index(begin, pos) + len(begin)
|
|
|
|
last = txt.index(end, first)
|
|
|
|
return txt[first:last], last+len(end)
|
2018-04-14 22:09:42 +02:00
|
|
|
except (ValueError, TypeError, AttributeError):
|
2015-10-03 12:53:45 +02:00
|
|
|
return None, pos
|
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2015-11-03 00:05:18 +01:00
|
|
|
def extract_all(txt, rules, pos=0, values=None):
|
2015-11-02 15:51:32 +01:00
|
|
|
"""Calls extract for each rule and returns the result in a dict"""
|
2015-11-03 00:05:18 +01:00
|
|
|
if values is None:
|
|
|
|
values = {}
|
2015-11-02 15:51:32 +01:00
|
|
|
for key, begin, end in rules:
|
|
|
|
result, pos = extract(txt, begin, end, pos)
|
|
|
|
if key:
|
|
|
|
values[key] = result
|
|
|
|
return values, pos
|
2015-10-03 12:53:45 +02:00
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2015-11-28 01:46:34 +01:00
|
|
|
def extract_iter(txt, begin, end, pos=0):
|
|
|
|
"""Yield all values obtained by repeated calls to text.extract"""
|
|
|
|
while True:
|
|
|
|
value, pos = extract(txt, begin, end, pos)
|
|
|
|
if value is None:
|
|
|
|
return
|
|
|
|
yield value
|
|
|
|
|
2017-01-30 19:40:15 +01:00
|
|
|
|
2018-04-20 14:53:21 +02:00
|
|
|
def parse_bytes(value, default=0, suffixes="bkmgtp"):
|
|
|
|
"""Convert a bytes-amount ("500k", "2.5M", ...) to int"""
|
|
|
|
try:
|
|
|
|
last = value[-1].lower()
|
|
|
|
except (TypeError, KeyError, IndexError):
|
|
|
|
return default
|
|
|
|
|
|
|
|
if last in suffixes:
|
|
|
|
mul = 1024 ** suffixes.index(last)
|
|
|
|
value = value[:-1]
|
|
|
|
else:
|
|
|
|
mul = 1
|
|
|
|
|
|
|
|
try:
|
|
|
|
return round(float(value) * mul)
|
|
|
|
except ValueError:
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
|
def parse_int(value, default=0):
|
|
|
|
"""Convert 'value' to int"""
|
|
|
|
if not value:
|
|
|
|
return default
|
|
|
|
try:
|
|
|
|
return int(value)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
2019-01-29 13:14:30 +01:00
|
|
|
def parse_float(value, default=0.0):
|
|
|
|
"""Convert 'value' to float"""
|
|
|
|
if not value:
|
|
|
|
return default
|
|
|
|
try:
|
|
|
|
return float(value)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
2017-08-24 20:55:58 +02:00
|
|
|
def parse_query(qs):
|
|
|
|
"""Parse a query string into key-value pairs"""
|
2018-04-13 19:21:32 +02:00
|
|
|
result = {}
|
2018-04-14 22:09:42 +02:00
|
|
|
try:
|
|
|
|
for key, value in urllib.parse.parse_qsl(qs):
|
|
|
|
if key not in result:
|
|
|
|
result[key] = value
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2018-04-13 19:21:32 +02:00
|
|
|
return result
|
2017-08-24 20:55:58 +02:00
|
|
|
|
|
|
|
|
2016-10-25 15:44:36 +02:00
|
|
|
if os.name == "nt":
|
2015-10-03 12:53:45 +02:00
|
|
|
clean_path = clean_path_windows
|
|
|
|
else:
|
|
|
|
clean_path = clean_path_posix
|
|
|
|
|
2019-01-29 13:14:30 +01:00
|
|
|
|
2018-04-20 14:53:21 +02:00
|
|
|
urljoin = urllib.parse.urljoin
|
2019-01-29 13:14:30 +01:00
|
|
|
|
|
|
|
quote = urllib.parse.quote
|
2015-10-03 12:53:45 +02:00
|
|
|
unquote = urllib.parse.unquote
|
|
|
|
|
2019-01-29 13:14:30 +01:00
|
|
|
escape = html.escape
|
|
|
|
unescape = html.unescape
|