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