mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 16:12:04 +02:00
18.0 vanilla
This commit is contained in:
parent
d72e748793
commit
0a7ae8db93
337 changed files with 399651 additions and 232598 deletions
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/__init__.py
Normal file
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/__init__.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# ruff: noqa: F401, PLC0415
|
||||
# ignore import not at top of the file
|
||||
import os
|
||||
import time
|
||||
from .evented import patch_evented
|
||||
|
||||
|
||||
def set_timezone_utc():
|
||||
os.environ['TZ'] = 'UTC' # Set the timezone
|
||||
if hasattr(time, 'tzset'):
|
||||
time.tzset()
|
||||
|
||||
|
||||
def patch_all():
|
||||
patch_evented()
|
||||
set_timezone_utc()
|
||||
|
||||
from .codecs import patch_codecs
|
||||
patch_codecs()
|
||||
from .email import patch_email
|
||||
patch_email()
|
||||
from .mimetypes import patch_mimetypes
|
||||
patch_mimetypes()
|
||||
from .pytz import patch_pytz
|
||||
patch_pytz()
|
||||
from .literal_eval import patch_literal_eval
|
||||
patch_literal_eval()
|
||||
from .lxml import patch_lxml
|
||||
patch_lxml()
|
||||
from .num2words import patch_num2words
|
||||
patch_num2words()
|
||||
from .stdnum import patch_stdnum
|
||||
patch_stdnum()
|
||||
from .urllib3 import patch_urllib3
|
||||
patch_urllib3()
|
||||
from .werkzeug_urls import patch_werkzeug
|
||||
patch_werkzeug()
|
||||
from .zeep import patch_zeep
|
||||
patch_zeep()
|
||||
26
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/codecs.py
Normal file
26
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/codecs.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import codecs
|
||||
import encodings.aliases
|
||||
import re
|
||||
|
||||
import babel.core
|
||||
|
||||
|
||||
def patch_codecs():
|
||||
# ---------------------------------------------------------
|
||||
# some charset are known by Python under a different name
|
||||
# ---------------------------------------------------------
|
||||
|
||||
encodings.aliases.aliases['874'] = 'cp874'
|
||||
encodings.aliases.aliases['windows_874'] = 'cp874'
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# alias hebrew iso-8859-8-i and iso-8859-8-e on iso-8859-8
|
||||
# https://bugs.python.org/issue18624
|
||||
# ---------------------------------------------------------
|
||||
|
||||
iso8859_8 = codecs.lookup('iso8859_8')
|
||||
iso8859_8ie_re = re.compile(r'iso[-_]?8859[-_]8[-_]?[ei]', re.IGNORECASE)
|
||||
codecs.register(lambda charset: iso8859_8 if iso8859_8ie_re.match(charset) else None)
|
||||
|
||||
# To remove when corrected in Babel
|
||||
babel.core.LOCALE_ALIASES['nb'] = 'nb_NO'
|
||||
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/email.py
Normal file
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/email.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from email._policybase import _PolicyBase
|
||||
|
||||
|
||||
def patch_email():
|
||||
def policy_clone(self, **kwargs):
|
||||
for arg in kwargs:
|
||||
if arg.startswith("_") or "__" in arg:
|
||||
raise AttributeError(f"{self.__class__.__name__!r} object has no attribute {arg!r}")
|
||||
return orig_policy_clone(self, **kwargs)
|
||||
|
||||
orig_policy_clone = _PolicyBase.clone
|
||||
_PolicyBase.clone = policy_clone
|
||||
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/evented.py
Normal file
39
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/evented.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
Running mode flags (gevent, prefork)
|
||||
|
||||
This should be imported as early as possible.
|
||||
It will initialize the `odoo.evented` variable.
|
||||
"""
|
||||
import odoo
|
||||
import sys
|
||||
|
||||
odoo.evented = False
|
||||
|
||||
|
||||
def patch_evented():
|
||||
if odoo.evented or not (len(sys.argv) > 1 and sys.argv[1] == 'gevent'):
|
||||
return
|
||||
sys.argv.remove('gevent')
|
||||
import gevent.monkey # noqa: PLC0415
|
||||
import psycopg2 # noqa: PLC0415
|
||||
from gevent.socket import wait_read, wait_write # noqa: PLC0415
|
||||
gevent.monkey.patch_all()
|
||||
|
||||
def gevent_wait_callback(conn, timeout=None):
|
||||
"""A wait callback useful to allow gevent to work with Psycopg."""
|
||||
# Copyright (C) 2010-2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
# This function is borrowed from psycogreen module which is licensed
|
||||
# under the BSD license (see in odoo/debian/copyright)
|
||||
while 1:
|
||||
state = conn.poll()
|
||||
if state == psycopg2.extensions.POLL_OK:
|
||||
break
|
||||
elif state == psycopg2.extensions.POLL_READ:
|
||||
wait_read(conn.fileno(), timeout=timeout)
|
||||
elif state == psycopg2.extensions.POLL_WRITE:
|
||||
wait_write(conn.fileno(), timeout=timeout)
|
||||
else:
|
||||
raise psycopg2.OperationalError(
|
||||
"Bad result from poll: %r" % state)
|
||||
psycopg2.extensions.set_wait_callback(gevent_wait_callback)
|
||||
odoo.evented = True
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# ruff: noqa: E402, PLC0415
|
||||
# ignore import not at top of the file
|
||||
import ast
|
||||
import logging
|
||||
import os
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
orig_literal_eval = ast.literal_eval
|
||||
|
||||
|
||||
def literal_eval(expr):
|
||||
# limit the size of the expression to avoid segmentation faults
|
||||
# the default limit is set to 100KiB
|
||||
# can be overridden by setting the ODOO_LIMIT_LITEVAL_BUFFER buffer_size_environment variable
|
||||
|
||||
buffer_size = 102400
|
||||
buffer_size_env = os.getenv("ODOO_LIMIT_LITEVAL_BUFFER")
|
||||
|
||||
if buffer_size_env:
|
||||
if buffer_size_env.isdigit():
|
||||
buffer_size = int(buffer_size_env)
|
||||
else:
|
||||
_logger.error("ODOO_LIMIT_LITEVAL_BUFFER has to be an integer, defaulting to 100KiB")
|
||||
|
||||
if isinstance(expr, str) and len(expr) > buffer_size:
|
||||
raise ValueError("expression can't exceed buffer limit")
|
||||
|
||||
return orig_literal_eval(expr)
|
||||
|
||||
|
||||
def patch_literal_eval():
|
||||
ast.literal_eval = literal_eval
|
||||
13
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/lxml.py
Normal file
13
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/lxml.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import lxml.html.clean
|
||||
import re
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
from odoo.tools import parse_version
|
||||
|
||||
|
||||
def patch_lxml():
|
||||
# between these versions having a couple data urls in a style attribute
|
||||
# or style node removes the attribute or node erroneously
|
||||
if parse_version("4.6.0") <= parse_version(version('lxml')) < parse_version("5.2.0"):
|
||||
lxml.html.clean._find_image_dataurls = re.compile(r'data:image/(.+?);base64,').findall
|
||||
15
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/mimetypes.py
Normal file
15
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/mimetypes.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import mimetypes
|
||||
|
||||
|
||||
def patch_mimetypes():
|
||||
# if extension is already knows, the new definition will remplace the existing one
|
||||
# Add potentially missing (older ubuntu) font mime types
|
||||
mimetypes.add_type('application/font-woff', '.woff')
|
||||
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
|
||||
mimetypes.add_type('application/x-font-ttf', '.ttf')
|
||||
mimetypes.add_type('image/webp', '.webp')
|
||||
# Add potentially wrong (detected on windows) svg mime types
|
||||
mimetypes.add_type('image/svg+xml', '.svg')
|
||||
# this one can be present on windows with the value 'text/plain' which
|
||||
# breaks loading js files from an addon's static folder
|
||||
mimetypes.add_type('text/javascript', '.js')
|
||||
984
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/num2words.py
Normal file
984
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/num2words.py
Normal file
|
|
@ -0,0 +1,984 @@
|
|||
import decimal
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from math import floor
|
||||
|
||||
from odoo import MIN_PY_VERSION
|
||||
|
||||
# The following section of the code is used to monkey patch
|
||||
# the Arabic class of num2words package as there are some problems
|
||||
# upgrading the package to the newer version that fixed the bugs
|
||||
# so a temporary fix was to patch the old version with the code
|
||||
# from the new version manually.
|
||||
# The code is taken from num2words package: https://github.com/savoirfairelinux/num2words
|
||||
|
||||
|
||||
CURRENCY_SR = [("ريال", "ريالان", "ريالات", "ريالاً"),
|
||||
("هللة", "هللتان", "هللات", "هللة")]
|
||||
CURRENCY_EGP = [("جنيه", "جنيهان", "جنيهات", "جنيهاً"),
|
||||
("قرش", "قرشان", "قروش", "قرش")]
|
||||
CURRENCY_KWD = [("دينار", "ديناران", "دينارات", "ديناراً"),
|
||||
("فلس", "فلسان", "فلس", "فلس")]
|
||||
|
||||
ARABIC_ONES = [
|
||||
"", "واحد", "اثنان", "ثلاثة", "أربعة", "خمسة", "ستة", "سبعة", "ثمانية",
|
||||
"تسعة",
|
||||
"عشرة", "أحد عشر", "اثنا عشر", "ثلاثة عشر", "أربعة عشر", "خمسة عشر",
|
||||
"ستة عشر", "سبعة عشر", "ثمانية عشر",
|
||||
"تسعة عشر"
|
||||
]
|
||||
|
||||
|
||||
class Num2Word_Base:
|
||||
CURRENCY_FORMS = {}
|
||||
CURRENCY_ADJECTIVES = {}
|
||||
|
||||
def __init__(self):
|
||||
self.is_title = False
|
||||
self.precision = 2
|
||||
self.exclude_title = []
|
||||
self.negword = "(-) "
|
||||
self.pointword = "(.)"
|
||||
self.errmsg_nonnum = "type: %s not in [long, int, float]"
|
||||
self.errmsg_floatord = "Cannot treat float %s as ordinal."
|
||||
self.errmsg_negord = "Cannot treat negative num %s as ordinal."
|
||||
self.errmsg_toobig = "abs(%s) must be less than %s."
|
||||
|
||||
self.setup()
|
||||
|
||||
# uses cards
|
||||
if any(hasattr(self, field) for field in
|
||||
['high_numwords', 'mid_numwords', 'low_numwords']):
|
||||
self.cards = OrderedDict()
|
||||
self.set_numwords()
|
||||
self.MAXVAL = 1000 * next(iter(self.cards.keys()))
|
||||
|
||||
def set_numwords(self):
|
||||
self.set_high_numwords(self.high_numwords)
|
||||
self.set_mid_numwords(self.mid_numwords)
|
||||
self.set_low_numwords(self.low_numwords)
|
||||
|
||||
def set_high_numwords(self, *args):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_mid_numwords(self, mid):
|
||||
for key, val in mid:
|
||||
self.cards[key] = val
|
||||
|
||||
def set_low_numwords(self, numwords):
|
||||
for word, n in zip(numwords, range(len(numwords) - 1, -1, -1)):
|
||||
self.cards[n] = word
|
||||
|
||||
def splitnum(self, value):
|
||||
for elem in self.cards:
|
||||
if elem > value:
|
||||
continue
|
||||
|
||||
out = []
|
||||
if value == 0:
|
||||
div, mod = 1, 0
|
||||
else:
|
||||
div, mod = divmod(value, elem)
|
||||
|
||||
if div == 1:
|
||||
out.append((self.cards[1], 1))
|
||||
else:
|
||||
if div == value: # The system tallies, eg Roman Numerals
|
||||
return [(div * self.cards[elem], div * elem)]
|
||||
out.append(self.splitnum(div))
|
||||
|
||||
out.append((self.cards[elem], elem))
|
||||
|
||||
if mod:
|
||||
out.append(self.splitnum(mod))
|
||||
|
||||
return out
|
||||
|
||||
def parse_minus(self, num_str):
|
||||
"""Detach minus and return it as symbol with new num_str."""
|
||||
if num_str.startswith('-'):
|
||||
# Extra spacing to compensate if there is no minus.
|
||||
return '%s ' % self.negword.strip(), num_str[1:]
|
||||
return '', num_str
|
||||
|
||||
def str_to_number(self, value):
|
||||
return Decimal(value)
|
||||
|
||||
def to_cardinal(self, value):
|
||||
try:
|
||||
assert int(value) == value
|
||||
except (ValueError, TypeError, AssertionError):
|
||||
return self.to_cardinal_float(value)
|
||||
|
||||
out = ""
|
||||
if value < 0:
|
||||
value = abs(value)
|
||||
out = "%s " % self.negword.strip()
|
||||
|
||||
if value >= self.MAXVAL:
|
||||
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
|
||||
|
||||
val = self.splitnum(value)
|
||||
words, _ = self.clean(val)
|
||||
return self.title(out + words)
|
||||
|
||||
def float2tuple(self, value):
|
||||
pre = int(value)
|
||||
|
||||
# Simple way of finding decimal places to update the precision
|
||||
self.precision = abs(Decimal(str(value)).as_tuple().exponent)
|
||||
|
||||
post = abs(value - pre) * 10**self.precision
|
||||
if abs(round(post) - post) < 0.01:
|
||||
# We generally floor all values beyond our precision (rather than
|
||||
# rounding), but in cases where we have something like 1.239999999,
|
||||
# which is probably due to python's handling of floats, we actually
|
||||
# want to consider it as 1.24 instead of 1.23
|
||||
post = int(round(post))
|
||||
else:
|
||||
post = int(math.floor(post))
|
||||
|
||||
return pre, post
|
||||
|
||||
def to_cardinal_float(self, value):
|
||||
try:
|
||||
float(value) == value
|
||||
except (ValueError, TypeError, AssertionError, AttributeError):
|
||||
raise TypeError(self.errmsg_nonnum % value)
|
||||
|
||||
pre, post = self.float2tuple(float(value))
|
||||
|
||||
post = str(post)
|
||||
post = '0' * (self.precision - len(post)) + post
|
||||
|
||||
out = [self.to_cardinal(pre)]
|
||||
if self.precision:
|
||||
out.append(self.title(self.pointword))
|
||||
|
||||
for i in range(self.precision):
|
||||
curr = int(post[i])
|
||||
out.append(to_s(self.to_cardinal(curr)))
|
||||
|
||||
return " ".join(out)
|
||||
|
||||
def merge(self, left, right):
|
||||
raise NotImplementedError
|
||||
|
||||
def clean(self, val):
|
||||
out = val
|
||||
while len(val) != 1:
|
||||
out = []
|
||||
left, right = val[:2]
|
||||
if isinstance(left, tuple) and isinstance(right, tuple):
|
||||
out.append(self.merge(left, right))
|
||||
if val[2:]:
|
||||
out.append(val[2:])
|
||||
else:
|
||||
for elem in val:
|
||||
if isinstance(elem, list):
|
||||
if len(elem) == 1:
|
||||
out.append(elem[0])
|
||||
else:
|
||||
out.append(self.clean(elem))
|
||||
else:
|
||||
out.append(elem)
|
||||
val = out
|
||||
return out[0]
|
||||
|
||||
def title(self, value):
|
||||
if self.is_title:
|
||||
out = []
|
||||
value = value.split()
|
||||
for word in value:
|
||||
if word in self.exclude_title:
|
||||
out.append(word)
|
||||
else:
|
||||
out.append(word[0].upper() + word[1:])
|
||||
value = " ".join(out)
|
||||
return value
|
||||
|
||||
def verify_ordinal(self, value):
|
||||
if not value == int(value):
|
||||
raise TypeError(self.errmsg_floatord % value)
|
||||
if not abs(value) == value:
|
||||
raise TypeError(self.errmsg_negord % value)
|
||||
|
||||
def to_ordinal(self, value):
|
||||
return self.to_cardinal(value)
|
||||
|
||||
def to_ordinal_num(self, value):
|
||||
return value
|
||||
|
||||
# Trivial version
|
||||
def inflect(self, value, text):
|
||||
text = text.split("/")
|
||||
if value == 1:
|
||||
return text[0]
|
||||
return "".join(text)
|
||||
|
||||
# //CHECK: generalise? Any others like pounds/shillings/pence?
|
||||
def to_splitnum(self, val, hightxt="", lowtxt="", jointxt="",
|
||||
divisor=100, longval=True, cents=True):
|
||||
out = []
|
||||
|
||||
if isinstance(val, float):
|
||||
high, low = self.float2tuple(val)
|
||||
else:
|
||||
try:
|
||||
high, low = val
|
||||
except TypeError:
|
||||
high, low = divmod(val, divisor)
|
||||
|
||||
if high:
|
||||
hightxt = self.title(self.inflect(high, hightxt))
|
||||
out.append(self.to_cardinal(high))
|
||||
if low:
|
||||
if longval:
|
||||
if hightxt:
|
||||
out.append(hightxt)
|
||||
if jointxt:
|
||||
out.append(self.title(jointxt))
|
||||
elif hightxt:
|
||||
out.append(hightxt)
|
||||
|
||||
if low:
|
||||
if cents:
|
||||
out.append(self.to_cardinal(low))
|
||||
else:
|
||||
out.append("%02d" % low)
|
||||
if lowtxt and longval:
|
||||
out.append(self.title(self.inflect(low, lowtxt)))
|
||||
|
||||
return " ".join(out)
|
||||
|
||||
def to_year(self, value, **kwargs):
|
||||
return self.to_cardinal(value)
|
||||
|
||||
def pluralize(self, n, forms):
|
||||
"""
|
||||
Should resolve gettext form:
|
||||
http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _money_verbose(self, number, currency):
|
||||
return self.to_cardinal(number)
|
||||
|
||||
def _cents_verbose(self, number, currency):
|
||||
return self.to_cardinal(number)
|
||||
|
||||
def _cents_terse(self, number, currency):
|
||||
return "%02d" % number
|
||||
|
||||
def to_currency(self, val, currency='EUR', cents=True, separator=',',
|
||||
adjective=False):
|
||||
"""
|
||||
Args:
|
||||
val: Numeric value
|
||||
currency (str): Currency code
|
||||
cents (bool): Verbose cents
|
||||
separator (str): Cent separator
|
||||
adjective (bool): Prefix currency name with adjective
|
||||
Returns:
|
||||
str: Formatted string
|
||||
|
||||
"""
|
||||
left, right, is_negative = parse_currency_parts(val)
|
||||
|
||||
try:
|
||||
cr1, cr2 = self.CURRENCY_FORMS[currency]
|
||||
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
'Currency code "%s" not implemented for "%s"' %
|
||||
(currency, self.__class__.__name__))
|
||||
|
||||
if adjective and currency in self.CURRENCY_ADJECTIVES:
|
||||
cr1 = prefix_currency(self.CURRENCY_ADJECTIVES[currency], cr1)
|
||||
|
||||
minus_str = "%s " % self.negword.strip() if is_negative else ""
|
||||
money_str = self._money_verbose(left, currency)
|
||||
cents_str = self._cents_verbose(right, currency) \
|
||||
if cents else self._cents_terse(right, currency)
|
||||
|
||||
return '%s%s %s%s %s %s' % (
|
||||
minus_str,
|
||||
money_str,
|
||||
self.pluralize(left, cr1),
|
||||
separator,
|
||||
cents_str,
|
||||
self.pluralize(right, cr2)
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
|
||||
class Num2Word_AR_Fixed(Num2Word_Base):
|
||||
errmsg_toobig = "abs(%s) must be less than %s."
|
||||
MAXVAL = 10**51
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.number = 0
|
||||
self.arabicPrefixText = ""
|
||||
self.arabicSuffixText = ""
|
||||
self.integer_value = 0
|
||||
self._decimalValue = ""
|
||||
self.partPrecision = 2
|
||||
self.currency_unit = CURRENCY_SR[0]
|
||||
self.currency_subunit = CURRENCY_SR[1]
|
||||
self.isCurrencyPartNameFeminine = True
|
||||
self.isCurrencyNameFeminine = False
|
||||
self.separator = 'و'
|
||||
|
||||
self.arabicOnes = ARABIC_ONES
|
||||
self.arabicFeminineOnes = [
|
||||
"", "إحدى", "اثنتان", "ثلاث", "أربع", "خمس", "ست", "سبع", "ثمان",
|
||||
"تسع",
|
||||
"عشر", "إحدى عشرة", "اثنتا عشرة", "ثلاث عشرة", "أربع عشرة",
|
||||
"خمس عشرة", "ست عشرة", "سبع عشرة", "ثماني عشرة",
|
||||
"تسع عشرة"
|
||||
]
|
||||
self.arabicOrdinal = [
|
||||
"", "اول", "ثاني", "ثالث", "رابع", "خامس", "سادس", "سابع", "ثامن",
|
||||
"تاسع", "عاشر", "حادي عشر", "ثاني عشر", "ثالث عشر", "رابع عشر",
|
||||
"خامس عشر", "سادس عشر", "سابع عشر", "ثامن عشر", "تاسع عشر"
|
||||
]
|
||||
self.arabicTens = [
|
||||
"عشرون", "ثلاثون", "أربعون", "خمسون", "ستون", "سبعون", "ثمانون",
|
||||
"تسعون"
|
||||
]
|
||||
self.arabicHundreds = [
|
||||
"", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة",
|
||||
"سبعمائة", "ثمانمائة", "تسعمائة"
|
||||
]
|
||||
|
||||
self.arabicAppendedTwos = [
|
||||
"مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا",
|
||||
"كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ",
|
||||
"نونيليونا", "ديسيليونا", "أندسيليونا", "دوديسيليونا",
|
||||
"تريديسيليونا", "كوادريسيليونا", "كوينتينيليونا"
|
||||
]
|
||||
self.arabicTwos = [
|
||||
"مئتان", "ألفان", "مليونان", "ملياران", "تريليونان",
|
||||
"كوادريليونان", "كوينتليونان", "سكستيليونان", "سبتيليونان",
|
||||
"أوكتيليونان ", "نونيليونان ", "ديسيليونان", "أندسيليونان",
|
||||
"دوديسيليونان", "تريديسيليونان", "كوادريسيليونان", "كوينتينيليونان"
|
||||
]
|
||||
self.arabicGroup = [
|
||||
"مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون",
|
||||
"كوينتليون", "سكستيليون", "سبتيليون", "أوكتيليون", "نونيليون",
|
||||
"ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون",
|
||||
"كوادريسيليون", "كوينتينيليون"
|
||||
]
|
||||
self.arabicAppendedGroup = [
|
||||
"", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً",
|
||||
"كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً",
|
||||
"نونيليوناً", "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً",
|
||||
"تريديسيليوناً", "كوادريسيليوناً", "كوينتينيليوناً"
|
||||
]
|
||||
self.arabicPluralGroups = [
|
||||
"", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات",
|
||||
"كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات",
|
||||
"نونيليونات", "ديسيليونات", "أندسيليونات", "دوديسيليونات",
|
||||
"تريديسيليونات", "كوادريسيليونات", "كوينتينيليونات"
|
||||
]
|
||||
assert len(self.arabicAppendedGroup) == len(self.arabicGroup)
|
||||
assert len(self.arabicPluralGroups) == len(self.arabicGroup)
|
||||
assert len(self.arabicAppendedTwos) == len(self.arabicTwos)
|
||||
|
||||
def number_to_arabic(self, arabic_prefix_text, arabic_suffix_text):
|
||||
self.arabicPrefixText = arabic_prefix_text
|
||||
self.arabicSuffixText = arabic_suffix_text
|
||||
self.extract_integer_and_decimal_parts()
|
||||
|
||||
def extract_integer_and_decimal_parts(self):
|
||||
splits = re.split('\\.', str(self.number))
|
||||
|
||||
self.integer_value = int(splits[0])
|
||||
if len(splits) > 1:
|
||||
self._decimalValue = int(self.decimal_value(splits[1]))
|
||||
else:
|
||||
self._decimalValue = 0
|
||||
|
||||
def decimal_value(self, decimal_part):
|
||||
if self.partPrecision is not len(decimal_part):
|
||||
decimal_part_length = len(decimal_part)
|
||||
|
||||
decimal_part_builder = decimal_part
|
||||
for _ in range(0, self.partPrecision - decimal_part_length):
|
||||
decimal_part_builder += '0'
|
||||
decimal_part = decimal_part_builder
|
||||
|
||||
if len(decimal_part) <= self.partPrecision:
|
||||
dec = len(decimal_part)
|
||||
else:
|
||||
dec = self.partPrecision
|
||||
result = decimal_part[0: dec]
|
||||
else:
|
||||
result = decimal_part
|
||||
|
||||
# The following is useless (never happens)
|
||||
# for i in range(len(result), self.partPrecision):
|
||||
# result += '0'
|
||||
return result
|
||||
|
||||
def digit_feminine_status(self, digit, group_level):
|
||||
if group_level == -1:
|
||||
if self.isCurrencyPartNameFeminine:
|
||||
return self.arabicFeminineOnes[int(digit)]
|
||||
else:
|
||||
# Note: this never happens
|
||||
return self.arabicOnes[int(digit)]
|
||||
elif group_level == 0:
|
||||
if self.isCurrencyNameFeminine:
|
||||
return self.arabicFeminineOnes[int(digit)]
|
||||
else:
|
||||
return self.arabicOnes[int(digit)]
|
||||
else:
|
||||
return self.arabicOnes[int(digit)]
|
||||
|
||||
def process_arabic_group(self, group_number, group_level,
|
||||
remaining_number):
|
||||
tens = Decimal(group_number) % Decimal(100)
|
||||
hundreds = Decimal(group_number) / Decimal(100)
|
||||
ret_val = ""
|
||||
|
||||
if int(hundreds) > 0:
|
||||
if tens == 0 and int(hundreds) == 2:
|
||||
ret_val = f"{self.arabicAppendedTwos[0]}"
|
||||
else:
|
||||
ret_val = f"{self.arabicHundreds[int(hundreds)]}"
|
||||
if ret_val and tens != 0:
|
||||
ret_val += " و "
|
||||
|
||||
if tens > 0:
|
||||
if tens < 20:
|
||||
# if int(group_level) >= len(self.arabicTwos):
|
||||
# raise OverflowError(self.errmsg_toobig %
|
||||
# (self.number, self.MAXVAL))
|
||||
assert int(group_level) < len(self.arabicTwos)
|
||||
if tens == 2 and int(hundreds) == 0 and group_level > 0:
|
||||
power = int(math.log10(self.integer_value))
|
||||
if self.integer_value > 10 and power % 3 == 0 and \
|
||||
self.integer_value == 2 * (10 ** power):
|
||||
ret_val = f"{self.arabicAppendedTwos[int(group_level)]}"
|
||||
else:
|
||||
ret_val = f"{self.arabicTwos[int(group_level)]}"
|
||||
else:
|
||||
if tens == 1 and group_level > 0 and hundreds == 0:
|
||||
# Note: this never happens
|
||||
# (hundreds == 0 only if group_number is 0)
|
||||
ret_val += ""
|
||||
elif (tens == 1 or tens == 2) and (
|
||||
group_level == 0 or group_level == -1) and \
|
||||
hundreds == 0 and remaining_number == 0:
|
||||
# Note: this never happens (idem)
|
||||
ret_val += ""
|
||||
elif tens == 1 and group_level > 0:
|
||||
ret_val += self.arabicGroup[int(group_level)]
|
||||
else:
|
||||
ret_val += self.digit_feminine_status(int(tens),
|
||||
group_level)
|
||||
else:
|
||||
ones = tens % 10
|
||||
tens = (tens / 10) - 2
|
||||
if ones > 0:
|
||||
ret_val += self.digit_feminine_status(ones, group_level)
|
||||
if ret_val and ones != 0:
|
||||
ret_val += " و "
|
||||
|
||||
ret_val += self.arabicTens[int(tens)]
|
||||
|
||||
return ret_val
|
||||
|
||||
# We use this instead of built-in `abs` function,
|
||||
# because `abs` suffers from loss of precision for big numbers
|
||||
def absolute(self, number):
|
||||
return number if number >= 0 else -number
|
||||
|
||||
# We use this instead of `"{:09d}".format(number)`,
|
||||
# because the string conversion suffers from loss of
|
||||
# precision for big numbers
|
||||
def to_str(self, number):
|
||||
integer = int(number)
|
||||
if integer == number:
|
||||
return str(integer)
|
||||
decimal = round((number - integer) * 10**9)
|
||||
return f"{integer}.{decimal:09d}"
|
||||
|
||||
def convert(self, value):
|
||||
self.number = self.to_str(value)
|
||||
self.number_to_arabic(self.arabicPrefixText, self.arabicSuffixText)
|
||||
return self.convert_to_arabic()
|
||||
|
||||
def convert_to_arabic(self):
|
||||
temp_number = Decimal(self.number)
|
||||
|
||||
if temp_number == Decimal(0):
|
||||
return "صفر"
|
||||
|
||||
decimal_string = self.process_arabic_group(self._decimalValue,
|
||||
-1,
|
||||
Decimal(0))
|
||||
ret_val = ""
|
||||
group = 0
|
||||
|
||||
while temp_number > Decimal(0):
|
||||
|
||||
temp_number_dec = Decimal(str(temp_number))
|
||||
try:
|
||||
number_to_process = int(temp_number_dec % Decimal(str(1000)))
|
||||
except decimal.InvalidOperation:
|
||||
decimal.getcontext().prec = len(
|
||||
temp_number_dec.as_tuple().digits
|
||||
)
|
||||
number_to_process = int(temp_number_dec % Decimal(str(1000)))
|
||||
|
||||
temp_number = int(temp_number_dec / Decimal(1000))
|
||||
|
||||
group_description = \
|
||||
self.process_arabic_group(number_to_process,
|
||||
group,
|
||||
Decimal(floor(temp_number)))
|
||||
if group_description:
|
||||
if group > 0:
|
||||
if ret_val:
|
||||
ret_val = f"و {ret_val}"
|
||||
if number_to_process != 2 and number_to_process != 1:
|
||||
# if group >= len(self.arabicGroup):
|
||||
# raise OverflowError(self.errmsg_toobig %
|
||||
# (self.number, self.MAXVAL)
|
||||
# )
|
||||
assert group < len(self.arabicGroup)
|
||||
if number_to_process % 100 != 1:
|
||||
if 3 <= number_to_process <= 10:
|
||||
ret_val = f"{self.arabicPluralGroups[group]} {ret_val}"
|
||||
else:
|
||||
if ret_val:
|
||||
ret_val = f"{self.arabicAppendedGroup[group]} {ret_val}"
|
||||
else:
|
||||
ret_val = f"{self.arabicGroup[group]} {ret_val}"
|
||||
|
||||
else:
|
||||
ret_val = f"{self.arabicGroup[group]} {ret_val}"
|
||||
|
||||
ret_val = f"{group_description} {ret_val}"
|
||||
group += 1
|
||||
formatted_number = ""
|
||||
if self.arabicPrefixText:
|
||||
formatted_number += f"{self.arabicPrefixText} "
|
||||
formatted_number += ret_val
|
||||
if self.integer_value != 0:
|
||||
remaining100 = int(self.integer_value % 100)
|
||||
|
||||
if remaining100 == 0 or remaining100 == 1:
|
||||
formatted_number += self.currency_unit[0]
|
||||
elif remaining100 == 2:
|
||||
if self.integer_value == 2:
|
||||
formatted_number += self.currency_unit[1]
|
||||
else:
|
||||
formatted_number += self.currency_unit[0]
|
||||
elif 3 <= remaining100 <= 10:
|
||||
formatted_number += self.currency_unit[2]
|
||||
elif 11 <= remaining100 <= 99:
|
||||
formatted_number += self.currency_unit[3]
|
||||
if self._decimalValue != 0:
|
||||
formatted_number += f" {self.separator} "
|
||||
formatted_number += decimal_string
|
||||
|
||||
if self._decimalValue != 0:
|
||||
formatted_number += " "
|
||||
remaining100 = int(self._decimalValue % 100)
|
||||
|
||||
if remaining100 == 0 or remaining100 == 1:
|
||||
formatted_number += self.currency_subunit[0]
|
||||
elif remaining100 == 2:
|
||||
formatted_number += self.currency_subunit[1]
|
||||
elif 3 <= remaining100 <= 10:
|
||||
formatted_number += self.currency_subunit[2]
|
||||
elif 11 <= remaining100 <= 99:
|
||||
formatted_number += self.currency_subunit[3]
|
||||
|
||||
if self.arabicSuffixText:
|
||||
formatted_number += f" {self.arabicSuffixText}"
|
||||
|
||||
return formatted_number
|
||||
|
||||
def validate_number(self, number):
|
||||
if number >= self.MAXVAL:
|
||||
raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL))
|
||||
return number
|
||||
|
||||
def set_currency_prefer(self, currency):
|
||||
if currency == 'EGP':
|
||||
self.currency_unit = CURRENCY_EGP[0]
|
||||
self.currency_subunit = CURRENCY_EGP[1]
|
||||
elif currency == 'KWD':
|
||||
self.currency_unit = CURRENCY_KWD[0]
|
||||
self.currency_subunit = CURRENCY_KWD[1]
|
||||
else:
|
||||
self.currency_unit = CURRENCY_SR[0]
|
||||
self.currency_subunit = CURRENCY_SR[1]
|
||||
|
||||
def to_currency(self, value, currency='SR', prefix='', suffix=''):
|
||||
self.set_currency_prefer(currency)
|
||||
self.isCurrencyNameFeminine = False
|
||||
self.separator = "و"
|
||||
self.arabicOnes = ARABIC_ONES
|
||||
self.arabicPrefixText = prefix
|
||||
self.arabicSuffixText = suffix
|
||||
return self.convert(value=value)
|
||||
|
||||
def to_ordinal(self, number, prefix=''):
|
||||
if number <= 19:
|
||||
return f"{self.arabicOrdinal[number]}"
|
||||
if number < 100:
|
||||
self.isCurrencyNameFeminine = True
|
||||
else:
|
||||
self.isCurrencyNameFeminine = False
|
||||
self.currency_subunit = ('', '', '', '')
|
||||
self.currency_unit = ('', '', '', '')
|
||||
self.arabicPrefixText = prefix
|
||||
self.arabicSuffixText = ""
|
||||
return f"{self.convert(self.absolute(number)).strip()}"
|
||||
|
||||
def to_year(self, value):
|
||||
value = self.validate_number(value)
|
||||
return self.to_cardinal(value)
|
||||
|
||||
def to_ordinal_num(self, value):
|
||||
return self.to_ordinal(value).strip()
|
||||
|
||||
def to_cardinal(self, number):
|
||||
self.isCurrencyNameFeminine = False
|
||||
number = self.validate_number(number)
|
||||
minus = ''
|
||||
if number < 0:
|
||||
minus = 'سالب '
|
||||
self.separator = ','
|
||||
self.currency_subunit = ('', '', '', '')
|
||||
self.currency_unit = ('', '', '', '')
|
||||
self.arabicPrefixText = ""
|
||||
self.arabicSuffixText = ""
|
||||
self.arabicOnes = ARABIC_ONES
|
||||
return minus + self.convert(value=self.absolute(number)).strip()
|
||||
|
||||
|
||||
def parse_currency_parts(value, is_int_with_cents=True):
|
||||
if isinstance(value, int):
|
||||
if is_int_with_cents:
|
||||
# assume cents if value is integer
|
||||
negative = value < 0
|
||||
value = abs(value)
|
||||
integer, cents = divmod(value, 100)
|
||||
else:
|
||||
negative = value < 0
|
||||
integer, cents = abs(value), 0
|
||||
|
||||
else:
|
||||
value = Decimal(value)
|
||||
value = value.quantize(
|
||||
Decimal('.01'),
|
||||
rounding=ROUND_HALF_UP
|
||||
)
|
||||
negative = value < 0
|
||||
value = abs(value)
|
||||
integer, fraction = divmod(value, 1)
|
||||
integer = int(integer)
|
||||
cents = int(fraction * 100)
|
||||
|
||||
return integer, cents, negative
|
||||
|
||||
|
||||
def prefix_currency(prefix, base):
|
||||
return tuple("%s %s" % (prefix, i) for i in base)
|
||||
|
||||
|
||||
try:
|
||||
strtype = basestring
|
||||
except NameError:
|
||||
strtype = str
|
||||
|
||||
|
||||
def to_s(val):
|
||||
try:
|
||||
return unicode(val)
|
||||
except NameError:
|
||||
return str(val)
|
||||
|
||||
|
||||
# Derived from num2cyrillic licensed under LGPL-3.0-only
|
||||
# Copyright 2018 ClaimCompass, Inc (num2cyrillic authored by Velizar Shulev) https://github.com/ClaimCompass/num2cyrillic
|
||||
# Copyright 1997 The PHP Group (PEAR::Numbers_Words, authored by Kouber Saparev) https://github.com/pear/Numbers_Words/blob/master/Numbers/Words/Locale/bg.php
|
||||
|
||||
|
||||
class NumberToWords_BG(Num2Word_Base):
|
||||
locale = 'bg'
|
||||
lang = 'Bulgarian'
|
||||
lang_native = 'Български'
|
||||
_misc_strings = {
|
||||
'deset': 'десет',
|
||||
'edinadeset': 'единадесет',
|
||||
'na': 'на',
|
||||
'sto': 'сто',
|
||||
'sta': 'ста',
|
||||
'stotin': 'стотин',
|
||||
'hiliadi': 'хиляди',
|
||||
}
|
||||
_digits = {
|
||||
0: [None, 'едно', 'две', 'три', 'четири', 'пет', 'шест', 'седем', 'осем', 'девет'],
|
||||
}
|
||||
_digits[1] = [None, 'един', 'два'] + _digits[0][3:]
|
||||
_digits[-1] = [None, 'една'] + _digits[0][2:]
|
||||
_last_and = False
|
||||
_zero = 'нула'
|
||||
_infinity = 'безкрайност'
|
||||
_and = 'и'
|
||||
_sep = ' '
|
||||
_minus = 'минус'
|
||||
_plural = 'а'
|
||||
_exponent = {
|
||||
0: '',
|
||||
3: 'хиляда',
|
||||
6: 'милион',
|
||||
9: 'милиард',
|
||||
12: 'трилион',
|
||||
15: 'квадрилион',
|
||||
18: 'квинтилион',
|
||||
21: 'секстилион',
|
||||
24: 'септилион',
|
||||
27: 'октилион',
|
||||
30: 'ноналион',
|
||||
33: 'декалион',
|
||||
36: 'ундекалион',
|
||||
39: 'дуодекалион',
|
||||
42: 'тредекалион',
|
||||
45: 'кватордекалион',
|
||||
48: 'квинтдекалион',
|
||||
51: 'сексдекалион',
|
||||
54: 'септдекалион',
|
||||
57: 'октодекалион',
|
||||
60: 'новемдекалион',
|
||||
63: 'вигинтилион',
|
||||
66: 'унвигинтилион',
|
||||
69: 'дуовигинтилион',
|
||||
72: 'тревигинтилион',
|
||||
75: 'кваторвигинтилион',
|
||||
78: 'квинвигинтилион',
|
||||
81: 'сексвигинтилион',
|
||||
84: 'септенвигинтилион',
|
||||
87: 'октовигинтилион',
|
||||
90: 'новемвигинтилион',
|
||||
93: 'тригинтилион',
|
||||
96: 'унтригинтилион',
|
||||
99: 'дуотригинтилион',
|
||||
102: 'третригинтилион',
|
||||
105: 'кватортригинтилион',
|
||||
108: 'квинтригинтилион',
|
||||
111: 'секстригинтилион',
|
||||
114: 'септентригинтилион',
|
||||
117: 'октотригинтилион',
|
||||
120: 'новемтригинтилион',
|
||||
123: 'квадрагинтилион',
|
||||
126: 'унквадрагинтилион',
|
||||
129: 'дуоквадрагинтилион',
|
||||
132: 'треквадрагинтилион',
|
||||
135: 'кваторквадрагинтилион',
|
||||
138: 'квинквадрагинтилион',
|
||||
141: 'сексквадрагинтилион',
|
||||
144: 'септенквадрагинтилион',
|
||||
147: 'октоквадрагинтилион',
|
||||
150: 'новемквадрагинтилион',
|
||||
153: 'квинквагинтилион',
|
||||
156: 'унквинкагинтилион',
|
||||
159: 'дуоквинкагинтилион',
|
||||
162: 'треквинкагинтилион',
|
||||
165: 'кваторквинкагинтилион',
|
||||
168: 'квинквинкагинтилион',
|
||||
171: 'сексквинкагинтилион',
|
||||
174: 'септенквинкагинтилион',
|
||||
177: 'октоквинкагинтилион',
|
||||
180: 'новемквинкагинтилион',
|
||||
183: 'сексагинтилион',
|
||||
186: 'унсексагинтилион',
|
||||
189: 'дуосексагинтилион',
|
||||
192: 'тресексагинтилион',
|
||||
195: 'кваторсексагинтилион',
|
||||
198: 'квинсексагинтилион',
|
||||
201: 'секссексагинтилион',
|
||||
204: 'септенсексагинтилион',
|
||||
207: 'октосексагинтилион',
|
||||
210: 'новемсексагинтилион',
|
||||
213: 'септагинтилион',
|
||||
216: 'унсептагинтилион',
|
||||
219: 'дуосептагинтилион',
|
||||
222: 'тресептагинтилион',
|
||||
225: 'кваторсептагинтилион',
|
||||
228: 'квинсептагинтилион',
|
||||
231: 'секссептагинтилион',
|
||||
234: 'септенсептагинтилион',
|
||||
237: 'октосептагинтилион',
|
||||
240: 'новемсептагинтилион',
|
||||
243: 'октогинтилион',
|
||||
246: 'уноктогинтилион',
|
||||
249: 'дуооктогинтилион',
|
||||
252: 'треоктогинтилион',
|
||||
255: 'кватороктогинтилион',
|
||||
258: 'квиноктогинтилион',
|
||||
261: 'сексоктогинтилион',
|
||||
264: 'септоктогинтилион',
|
||||
267: 'октооктогинтилион',
|
||||
270: 'новемоктогинтилион',
|
||||
273: 'нонагинтилион',
|
||||
276: 'уннонагинтилион',
|
||||
279: 'дуононагинтилион',
|
||||
282: 'тренонагинтилион',
|
||||
285: 'кваторнонагинтилион',
|
||||
288: 'квиннонагинтилион',
|
||||
291: 'секснонагинтилион',
|
||||
294: 'септеннонагинтилион',
|
||||
297: 'октононагинтилион',
|
||||
300: 'новемнонагинтилион',
|
||||
303: 'центилион',
|
||||
}
|
||||
|
||||
def to_cardinal(self, value):
|
||||
return '' if value is None else self._to_words(value).strip()
|
||||
|
||||
def to_ordinal(self, _):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_ordinal_num(self, _):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_year(self, _):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_currency(self, _):
|
||||
raise NotImplementedError
|
||||
|
||||
def _split_number(self, num):
|
||||
if isinstance(num, int):
|
||||
num = str(num)
|
||||
first = []
|
||||
if len(num) % 3 != 0:
|
||||
if len(num[1:]) % 3 == 0:
|
||||
first = [num[0:1]]
|
||||
num = num[1:]
|
||||
elif len(num[2:]) % 3 == 0:
|
||||
first = [num[0:2]]
|
||||
num = num[2:]
|
||||
ret = [num[i:i + 3] for i in range(0, len(num), 3)]
|
||||
return first + ret
|
||||
|
||||
def _discard_empties(self, ls):
|
||||
return list(filter(lambda x: x is not None, ls))
|
||||
|
||||
def _show_digits_group(self, num, gender=0, last=False):
|
||||
num = int(num)
|
||||
e = int(num % 10) # ones
|
||||
d = int((num - e) % 100 / 10) # tens
|
||||
s = int((num - d * 10 - e) % 1000 / 100) # hundreds
|
||||
ret = [None] * 6
|
||||
|
||||
if s:
|
||||
if s == 1:
|
||||
ret[1] = self._misc_strings['sto']
|
||||
elif s == 2 or s == 3:
|
||||
ret[1] = self._digits[0][s] + self._misc_strings['sta']
|
||||
else:
|
||||
ret[1] = self._digits[0][s] + self._misc_strings['stotin']
|
||||
|
||||
if d:
|
||||
if d == 1:
|
||||
if not e:
|
||||
ret[3] = self._misc_strings['deset']
|
||||
else:
|
||||
if e == 1:
|
||||
ret[3] = self._misc_strings['edinadeset']
|
||||
else:
|
||||
ret[3] = self._digits[1][e] + self._misc_strings['na'] + self._misc_strings['deset']
|
||||
e = 0
|
||||
else:
|
||||
ret[3] = self._digits[1][d] + self._misc_strings['deset']
|
||||
|
||||
if e:
|
||||
ret[5] = self._digits[gender][e]
|
||||
|
||||
if len(self._discard_empties(ret)) > 1:
|
||||
if e:
|
||||
ret[4] = self._and
|
||||
else:
|
||||
ret[2] = self._and
|
||||
|
||||
if last:
|
||||
if not s or len(self._discard_empties(ret)) == 1:
|
||||
ret[0] = self._and
|
||||
self._last_and = True
|
||||
|
||||
return self._sep.join(self._discard_empties(ret))
|
||||
|
||||
def _to_words(self, num=0):
|
||||
num_groups = self._split_number(num)
|
||||
sizeof_num_groups = len(num_groups)
|
||||
|
||||
ret = [None] * (sizeof_num_groups + 1)
|
||||
ret_minus = ''
|
||||
|
||||
if num < 0:
|
||||
ret_minus = self._minus + self._sep
|
||||
elif num == 0:
|
||||
return self._zero
|
||||
|
||||
i = sizeof_num_groups - 1
|
||||
j = 1
|
||||
while i >= 0:
|
||||
if ret[j] is None:
|
||||
ret[j] = ''
|
||||
|
||||
_pow = sizeof_num_groups - i
|
||||
|
||||
if num_groups[i] != '000':
|
||||
if int(num_groups[i]) > 1:
|
||||
if _pow == 1:
|
||||
ret[j] += self._show_digits_group(num_groups[i], 0, not self._last_and and i) + self._sep
|
||||
ret[j] += self._exponent[(_pow - 1) * 3]
|
||||
elif _pow == 2:
|
||||
ret[j] += self._show_digits_group(num_groups[i], -1, not self._last_and and i) + self._sep
|
||||
ret[j] += self._misc_strings['hiliadi'] + self._sep
|
||||
else:
|
||||
ret[j] += self._show_digits_group(num_groups[i], 1, not self._last_and and i) + self._sep
|
||||
ret[j] += self._exponent[(_pow - 1) * 3] + self._plural + self._sep
|
||||
else:
|
||||
if _pow == 1:
|
||||
ret[j] += self._show_digits_group(num_groups[i], 0, not self._last_and and i) + self._sep
|
||||
elif _pow == 2:
|
||||
ret[j] += self._exponent[(_pow - 1) * 3] + self._sep
|
||||
else:
|
||||
ret[j] += self._digits[1][1] + self._sep + self._exponent[(_pow - 1) * 3] + self._sep
|
||||
|
||||
i -= 1
|
||||
j += 1
|
||||
|
||||
ret = self._discard_empties(ret)
|
||||
ret.reverse()
|
||||
return ret_minus + ''.join(ret)
|
||||
|
||||
|
||||
def patch_num2words():
|
||||
try:
|
||||
import num2words # noqa: PLC0415
|
||||
except ImportError:
|
||||
_logger = logging.getLogger(__name__)
|
||||
_logger.warning("num2words is not available, Arabic number to words conversion will not work")
|
||||
return
|
||||
if MIN_PY_VERSION >= (3, 12):
|
||||
raise RuntimeError("The num2words monkey patch is obsolete. Bump the version of the library to the latest available in the official package repository, if it hasn't already been done, and remove the patch.")
|
||||
num2words.CONVERTER_CLASSES["ar"] = Num2Word_AR_Fixed()
|
||||
num2words.CONVERTER_CLASSES["bg"] = NumberToWords_BG()
|
||||
131
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/pytz.py
Normal file
131
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/pytz.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
"""
|
||||
In ubuntu noble, some timezone where removed leading to errors when trying to assign/access them.
|
||||
|
||||
This was partially fixed in the code by removing all references to old timezones but one issue remains:
|
||||
if a database contains timezones that are not defined in the os, the resolution will fail and break
|
||||
at runtime.
|
||||
|
||||
This patches proposes to alter timezone to fallback on the new canonical timezone if the timezone was removed.
|
||||
|
||||
This list was generated by checking all symlink in /usr/share/zoneinfo in ubuntu 22.04 that disapeared in ubuntu 24.04
|
||||
|
||||
This solutions will work when moving a database from one server to another, even without migration.
|
||||
This list could be improved for other purposes.
|
||||
|
||||
"""
|
||||
|
||||
import pytz
|
||||
|
||||
_tz_mapping = {
|
||||
"Africa/Asmera": "Africa/Nairobi",
|
||||
"America/Argentina/ComodRivadavia": "America/Argentina/Catamarca",
|
||||
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
|
||||
"America/Cordoba": "America/Argentina/Cordoba",
|
||||
"America/Fort_Wayne": "America/Indiana/Indianapolis",
|
||||
"America/Indianapolis": "America/Indiana/Indianapolis",
|
||||
"America/Jujuy": "America/Argentina/Jujuy",
|
||||
"America/Knox_IN": "America/Indiana/Knox",
|
||||
"America/Louisville": "America/Kentucky/Louisville",
|
||||
"America/Mendoza": "America/Argentina/Mendoza",
|
||||
"America/Rosario": "America/Argentina/Cordoba",
|
||||
"Antarctica/South_Pole": "Pacific/Auckland",
|
||||
"Asia/Ashkhabad": "Asia/Ashgabat",
|
||||
"Asia/Calcutta": "Asia/Kolkata",
|
||||
"Asia/Chungking": "Asia/Shanghai",
|
||||
"Asia/Dacca": "Asia/Dhaka",
|
||||
"Asia/Katmandu": "Asia/Kathmandu",
|
||||
"Asia/Macao": "Asia/Macau",
|
||||
"Asia/Rangoon": "Asia/Yangon",
|
||||
"Asia/Saigon": "Asia/Ho_Chi_Minh",
|
||||
"Asia/Thimbu": "Asia/Thimphu",
|
||||
"Asia/Ujung_Pandang": "Asia/Makassar",
|
||||
"Asia/Ulan_Bator": "Asia/Ulaanbaatar",
|
||||
"Atlantic/Faeroe": "Atlantic/Faroe",
|
||||
"Australia/ACT": "Australia/Sydney",
|
||||
"Australia/LHI": "Australia/Lord_Howe",
|
||||
"Australia/North": "Australia/Darwin",
|
||||
"Australia/NSW": "Australia/Sydney",
|
||||
"Australia/Queensland": "Australia/Brisbane",
|
||||
"Australia/South": "Australia/Adelaide",
|
||||
"Australia/Tasmania": "Australia/Hobart",
|
||||
"Australia/Victoria": "Australia/Melbourne",
|
||||
"Australia/West": "Australia/Perth",
|
||||
"Brazil/Acre": "America/Rio_Branco",
|
||||
"Brazil/DeNoronha": "America/Noronha",
|
||||
"Brazil/East": "America/Sao_Paulo",
|
||||
"Brazil/West": "America/Manaus",
|
||||
"Canada/Atlantic": "America/Halifax",
|
||||
"Canada/Central": "America/Winnipeg",
|
||||
"Canada/Eastern": "America/Toronto",
|
||||
"Canada/Mountain": "America/Edmonton",
|
||||
"Canada/Newfoundland": "America/St_Johns",
|
||||
"Canada/Pacific": "America/Vancouver",
|
||||
"Canada/Saskatchewan": "America/Regina",
|
||||
"Canada/Yukon": "America/Whitehorse",
|
||||
"Chile/Continental": "America/Santiago",
|
||||
"Chile/EasterIsland": "Pacific/Easter",
|
||||
"Cuba": "America/Havana",
|
||||
"Egypt": "Africa/Cairo",
|
||||
"Eire": "Europe/Dublin",
|
||||
"Europe/Kiev": "Europe/Kyiv",
|
||||
"Europe/Uzhgorod": "Europe/Kyiv",
|
||||
"Europe/Zaporozhye": "Europe/Kyiv",
|
||||
"GB": "Europe/London",
|
||||
"GB-Eire": "Europe/London",
|
||||
"GMT+0": "Etc/GMT",
|
||||
"GMT-0": "Etc/GMT",
|
||||
"GMT0": "Etc/GMT",
|
||||
"Greenwich": "Etc/GMT",
|
||||
"Hongkong": "Asia/Hong_Kong",
|
||||
"Iceland": "Africa/Abidjan",
|
||||
"Iran": "Asia/Tehran",
|
||||
"Israel": "Asia/Jerusalem",
|
||||
"Jamaica": "America/Jamaica",
|
||||
"Japan": "Asia/Tokyo",
|
||||
"Kwajalein": "Pacific/Kwajalein",
|
||||
"Libya": "Africa/Tripoli",
|
||||
"Mexico/BajaNorte": "America/Tijuana",
|
||||
"Mexico/BajaSur": "America/Mazatlan",
|
||||
"Mexico/General": "America/Mexico_City",
|
||||
"Navajo": "America/Denver",
|
||||
"NZ": "Pacific/Auckland",
|
||||
"NZ-CHAT": "Pacific/Chatham",
|
||||
"Pacific/Enderbury": "Pacific/Kanton",
|
||||
"Pacific/Ponape": "Pacific/Guadalcanal",
|
||||
"Pacific/Truk": "Pacific/Port_Moresby",
|
||||
"Poland": "Europe/Warsaw",
|
||||
"Portugal": "Europe/Lisbon",
|
||||
"PRC": "Asia/Shanghai",
|
||||
"ROC": "Asia/Taipei",
|
||||
"ROK": "Asia/Seoul",
|
||||
"Singapore": "Asia/Singapore",
|
||||
"Türkiye": "Europe/Istanbul",
|
||||
"UCT": "Etc/UTC",
|
||||
"Universal": "Etc/UTC",
|
||||
"US/Alaska": "America/Anchorage",
|
||||
"US/Aleutian": "America/Adak",
|
||||
"US/Arizona": "America/Phoenix",
|
||||
"US/Central": "America/Chicago",
|
||||
"US/Eastern": "America/New_York",
|
||||
"US/East-Indiana": "America/Indiana/Indianapolis",
|
||||
"US/Hawaii": "Pacific/Honolulu",
|
||||
"US/Indiana-Starke": "America/Indiana/Knox",
|
||||
"US/Michigan": "America/Detroit",
|
||||
"US/Mountain": "America/Denver",
|
||||
"US/Pacific": "America/Los_Angeles",
|
||||
"US/Samoa": "Pacific/Pago_Pago",
|
||||
"W-SU": "Europe/Moscow",
|
||||
"Zulu": "Etc/UTC",
|
||||
}
|
||||
|
||||
original_pytz_timezone = pytz.timezone
|
||||
|
||||
|
||||
def patch_pytz():
|
||||
def timezone(name):
|
||||
if name not in pytz.all_timezones_set and name in _tz_mapping:
|
||||
name = _tz_mapping[name]
|
||||
return original_pytz_timezone(name)
|
||||
|
||||
pytz.timezone = timezone
|
||||
57
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/stdnum.py
Normal file
57
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/stdnum.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# ruff: noqa: PLC0415
|
||||
|
||||
_soap_clients = {}
|
||||
|
||||
|
||||
def new_get_soap_client(wsdlurl, timeout=30):
|
||||
# stdnum library does not set the timeout for the zeep Transport class correctly
|
||||
# (timeout is to fetch the wsdl and operation_timeout is to perform the call),
|
||||
# requiring us to monkey patch the get_soap_client function.
|
||||
# Can be removed when https://github.com/arthurdejong/python-stdnum/issues/444 is
|
||||
# resolved and the version of the dependency is updated.
|
||||
# The code is a copy of the original apart for the line related to the Transport class.
|
||||
# This was done to keep the code as similar to the original and to reduce the possibility
|
||||
# of introducing import errors, even though some imports are not in the requirements.
|
||||
# See https://github.com/odoo/odoo/pull/173359 for a more thorough explanation.
|
||||
if (wsdlurl, timeout) not in _soap_clients:
|
||||
try:
|
||||
from zeep.transports import Transport
|
||||
transport = Transport(operation_timeout=timeout, timeout=timeout) # operational_timeout added here
|
||||
from zeep import CachingClient
|
||||
client = CachingClient(wsdlurl, transport=transport).service
|
||||
except ImportError:
|
||||
# fall back to non-caching zeep client
|
||||
try:
|
||||
from zeep import Client
|
||||
client = Client(wsdlurl, transport=transport).service
|
||||
except ImportError:
|
||||
# other implementations require passing the proxy config
|
||||
try:
|
||||
from urllib import getproxies
|
||||
except ImportError:
|
||||
from urllib.request import getproxies
|
||||
# fall back to suds
|
||||
try:
|
||||
from suds.client import Client
|
||||
client = Client(
|
||||
wsdlurl, proxy=getproxies(), timeout=timeout).service
|
||||
except ImportError:
|
||||
# use pysimplesoap as last resort
|
||||
try:
|
||||
from pysimplesoap.client import SoapClient
|
||||
client = SoapClient(
|
||||
wsdl=wsdlurl, proxy=getproxies(), timeout=timeout)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'No SOAP library (such as zeep) found')
|
||||
_soap_clients[(wsdlurl, timeout)] = client
|
||||
return _soap_clients[(wsdlurl, timeout)]
|
||||
|
||||
|
||||
def patch_stdnum():
|
||||
try:
|
||||
from stdnum import util
|
||||
except ImportError:
|
||||
return # nothing to patch
|
||||
|
||||
util.get_soap_client = new_get_soap_client
|
||||
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/urllib3.py
Normal file
12
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/urllib3.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from urllib3 import PoolManager
|
||||
|
||||
orig_pool_init = PoolManager.__init__
|
||||
|
||||
|
||||
def pool_init(self, *args, **kwargs):
|
||||
orig_pool_init(self, *args, **kwargs)
|
||||
self.pool_classes_by_scheme = {**self.pool_classes_by_scheme}
|
||||
|
||||
|
||||
def patch_urllib3():
|
||||
PoolManager.__init__ = pool_init
|
||||
1076
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/werkzeug_urls.py
Normal file
1076
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/werkzeug_urls.py
Normal file
File diff suppressed because it is too large
Load diff
9
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/zeep.py
Normal file
9
odoo-bringout-oca-ocb-base/odoo/_monkeypatches/zeep.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from zeep.xsd import visitor
|
||||
from zeep.xsd.const import xsd_ns
|
||||
|
||||
|
||||
def patch_zeep():
|
||||
# see https://github.com/mvantellingen/python-zeep/issues/1185
|
||||
if visitor.tags.notation.localname != 'notation':
|
||||
visitor.tags.notation = xsd_ns('notation')
|
||||
visitor.SchemaVisitor.visitors[visitor.tags.notation] = visitor.SchemaVisitor.visit_notation
|
||||
Loading…
Add table
Add a link
Reference in a new issue