122 lines
3.4 KiB
Python
122 lines
3.4 KiB
Python
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<amount>' + 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<code>' + '|'.join(re.escape(c) for c in CURRENCY_CODES) + ')'
|
|
|
|
RE_CURRENCY_SYMBOLS = (
|
|
r'(?P<sym>[' + ''.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))
|