import logging import re from decimal import Decimal from .data import ( CURRENCY_CODES, CURRENCY_SYMBOLS, Asset, AssetAmount, FiatCurrency, ) logger = logging.getLogger(__name__) RE_PRICE_RAW = r'\b(?:dkk|sek|usd|nok|eur)?\s*([1-9][\d.]*[\d](?:,\d+)?)\s*(?:,-|:-|.-|;-)?\s*(?:(?:kr|kroner|krone|dkk|sek|usd|eur|nok)\b)?\.?' RE_PRICE = re.compile(RE_PRICE_RAW, flags=re.IGNORECASE) RE_PRODUCT_PRICE_DK = r'-?[1-9]\d{0,2}(?:\.?\d{3})*(?:,\d\d\.?)?' RE_PRODUCT_PRICE_EN = r'-?[1-9]\d{0,2}(?:,?\d{3})*(?:\.\d\d)?' RE_PRODUCT_PRICE_AMOUNT = r'(?P' + RE_PRODUCT_PRICE_DK + '|' + RE_PRODUCT_PRICE_EN + ')' def parse_amount(price: str) -> Decimal: 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) for c in CURRENCY_SYMBOLS.values()) + '])' ) 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_KR = re.compile( '(' + RE_PRODUCT_PRICE_AMOUNT + r')\s*kr\.?', flags=re.IGNORECASE, ) RE_AMOUNT_SUFFIX = re.compile( '(' + RE_PRODUCT_PRICE_AMOUNT + r')\s*(?:;-|,-|\.-)?', flags=re.IGNORECASE, ) 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().strip() if text == 'free': return AssetAmount(default_currency, Decimal(0)) code, sym, amount_text = None, None, 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_KR.fullmatch(text) or RE_AMOUNT_SUFFIX.fullmatch(text)): code, amount_text = 'DKK', m.group('amount') else: return 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))