import logging import re from decimal import Decimal from .data import ( CURRENCY_CODES, ASSET_DISPLAY, Asset, AssetAmount, FiatCurrency, ) logger = logging.getLogger(__name__) RE_PRODUCT_PRICE_DK = r'-?[1-9]\d{0,2}(?:[\.\s]?\d{3})*(?:,\d\d\.?)?' RE_PRODUCT_PRICE_EN = r'-?[1-9]\d{0,2}(?:[,\s]?\d{3})*(?:\.\d\d)?' RE_PRODUCT_PRICE_AMOUNT = r'(?P' + RE_PRODUCT_PRICE_DK + '|' + RE_PRODUCT_PRICE_EN + ')' assert re.fullmatch(RE_PRODUCT_PRICE_DK, '1 000 000') def parse_amount(price: str) -> Decimal: price = re.sub(r'\s', '', price, flags=re.IGNORECASE) if re.fullmatch(RE_PRODUCT_PRICE_DK, price): price = price.replace('.', '').replace(',', '.') else: price = price.replace(',', '') return Decimal(price) RE_CURRENCY_CODES = '(?P' + '|'.join(re.escape(c) for c in CURRENCY_CODES) + ')' RE_CURRENCY_SYMBOLS = ( r'(?P[' + ''.join(re.escape(c.symbol) for c in ASSET_DISPLAY.values() if c.symbol) + '])' ) RE_SYM_AMOUNT_CODE = re.compile( RE_CURRENCY_SYMBOLS + r'\s*' + RE_PRODUCT_PRICE_AMOUNT + r'(?:\s*' + RE_CURRENCY_CODES + r')?', flags=re.IGNORECASE, ) RE_CODE_AMOUNT = re.compile( RE_CURRENCY_CODES + r'\s*' + RE_PRODUCT_PRICE_AMOUNT, flags=re.IGNORECASE, ) RE_AMOUNT_SYM_CODE = re.compile( RE_PRODUCT_PRICE_AMOUNT + r'\s*' + RE_CURRENCY_SYMBOLS + r'(?:\s+' + RE_CURRENCY_CODES + ')?', flags=re.IGNORECASE, ) RE_AMOUNT_CODE = re.compile( RE_PRODUCT_PRICE_AMOUNT + r'\s+' + RE_CURRENCY_CODES, flags=re.IGNORECASE, ) RE_KR_AMOUNT = re.compile( r'kr\.?\s*(' + RE_PRODUCT_PRICE_AMOUNT + ')', flags=re.IGNORECASE, ) RE_AMOUNT_SUFFIX_KR = re.compile( '(' + RE_PRODUCT_PRICE_AMOUNT + r')\s*(?:;-|,-|\.-)?\s*(?:kr\.?)?', flags=re.IGNORECASE, ) VARIANTS_OF_FREE = frozenset(['free', 'gratis', 'gives væk']) KRONER_USING_ASSETS = {k for k,c in ASSET_DISPLAY.items() if c.suffix == 'kr.'} def parse_price(text: str, default_currency: Asset) -> AssetAmount | None: """ Attempts to parse price from the given text. Text does not need to be stripped beforehand. """ if isinstance(text, AssetAmount): return text text = str(text).lower().replace('\n', ' ').replace('\t', ' ').strip() if text == '': return None if text in VARIANTS_OF_FREE: return AssetAmount(default_currency, Decimal(0)) code, sym, amount_text = None, None, None currency = None if m := RE_SYM_AMOUNT_CODE.fullmatch(text): code, sym, amount_text = m.group('code'), m.group('sym'), m.group('amount') elif m := RE_CODE_AMOUNT.fullmatch(text): code, amount_text = m.group('code'), m.group('amount') elif m := RE_AMOUNT_SYM_CODE.fullmatch(text): code, sym, amount_text = m.group('code'), m.group('sym'), m.group('amount') elif m := RE_AMOUNT_CODE.fullmatch(text): code, amount_text = m.group('code'), m.group('amount') elif m := (RE_KR_AMOUNT.fullmatch(text) or RE_AMOUNT_SUFFIX_KR.fullmatch(text)): amount_text = m.group('amount') currency = default_currency if default_currency in KRONER_USING_ASSETS else FiatCurrency.DKK else: logger.debug('Unknown format: %s', text) return None if currency is None: currency = ( CURRENCY_CODES[code.upper()] if code else FiatCurrency.from_currency_symbol(sym) ) assert currency is not None return AssetAmount(currency, parse_amount(amount_text))