From 285c4bff675f6aa44f6fe12fef57bcabf80b910c Mon Sep 17 00:00:00 2001 From: raj-open Date: Sat, 18 Jan 2025 15:20:59 +0100 Subject: [PATCH] guid-stdlibs > v0.8.0: used underlying base64-to-hex to speed up computations NOTE: The conversions remain exactly as before. But by using standard libraries for conversion, we clean up computations. --- src/ifcopenshell-python/ifcopenshell/guid.py | 151 +++++++++++++++---- 1 file changed, 123 insertions(+), 28 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/guid.py b/src/ifcopenshell-python/ifcopenshell/guid.py index 5be73b6bd5..f0c4ff64c7 100644 --- a/src/ifcopenshell-python/ifcopenshell/guid.py +++ b/src/ifcopenshell-python/ifcopenshell/guid.py @@ -16,46 +16,141 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""Reads and writes encoded GlobalIds +""" +Reads and writes encoded GlobalIds. -IFC entities may be identified using a unique ID (called a UUID or GUID). This -128-bit label is often represented in the form -xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. However, in IFC, it is also usually -stored as a 22 character base 64 encoded string. This module lets you convert -between these representations and generate new UUIDs. +IFC entities may be identified using a unique ID (called a UUID or GUID). +This 128-bit label is often represented in the form +``` +xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +``` +However, in IFC, it is also usually stored as a +22 character base 64 encoded string. +This module lets you convert between these representations and generate new UUIDs. + +NOTE: The Ifc convention for Base64 encoding differs from the standard. + +The "digits" of a standard Base64 encoding are as follows: +``` +ABC...XYZ abc...XYZ 012...89 +/ +``` +(see , +), +whereas the Ifc convention is +``` +012...89 ABC...XYZ abc...XYZ _$ +``` +cf. . """ -import uuid +# ---------------------------------------------------------------- +# IMPORTS +# ---------------------------------------------------------------- + +from base64 import b64encode +from base64 import b64decode +from uuid import uuid4 +import re import string -from functools import reduce +# ---------------------------------------------------------------- +# EXPORTS +# ---------------------------------------------------------------- -chars = string.digits + string.ascii_uppercase + string.ascii_lowercase + "_$" +__all__ = [ + "compress", + "expand", + "split", + "new", +] + +# ---------------------------------------------------------------- +# LOCAL CONSTANTS +# ---------------------------------------------------------------- + +# standard convention +_CHARS64_STD = string.ascii_uppercase + string.ascii_lowercase + string.digits + "+/" + +# ifc convention +_CHARS64_IFC = string.digits + string.ascii_uppercase + string.ascii_lowercase + "_$" + +# translators +_TRANS_IFC_TO_STD = str.maketrans(_CHARS64_IFC, _CHARS64_STD) +_TRANS_STD_TO_IFC = str.maketrans(_CHARS64_STD, _CHARS64_IFC) + +# ---------------------------------------------------------------- +# METHODS +# ---------------------------------------------------------------- + +def compress(uuid: str, /) -> str: + """ + Converts a hex-encoded UUID to a base64-encoded GUID in IFC-format. + + See + """ + # remove possible separators + uuid = uuid.lower() + uuid = re.sub(pattern=r"\W", repl="", string=uuid) + + # pad with hex "zeroes" + uuid = "0000" + uuid + + # convert to standard base 64 + uuid_bytes = bytes.fromhex(uuid) + guid = b64encode(uuid_bytes).decode() + + # remove result of padding + guid = guid[2:] + + # translate from standard-convention to ifc-convention + guid = guid.translate(_TRANS_STD_TO_IFC) + + return guid -def compress(g: str) -> str: - bs = [int(g[i : i + 2], 16) for i in range(0, len(g), 2)] +def expand(guid: str, /) -> str: + """ + Converts a base64-encoded GUID in IFC-format to a hex-encoded UUID. - def b64(v, l=4): - return "".join([chars[(v // (64**i)) % 64] for i in range(l)][::-1]) + See + """ + # translate from ifc-convention to standard-convention + guid = guid.translate(_TRANS_IFC_TO_STD) - return "".join([b64(bs[0], 2)] + [b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]) for i in range(1, 16, 3)]) + # pad with base64 "zeroes" + guid = "AA" + guid + + # convert to hex + uuid = b64decode(guid).hex() + + # remove result of padding + uuid = uuid[4:] + + return uuid -def expand(g: str) -> str: - def b64(v): - return reduce(lambda a, b: a * 64 + b, map(lambda c: chars.index(c), v)) - - bs = [b64(g[0:2])] - for i in range(5): - d = b64(g[2 + 4 * i : 6 + 4 * i]) - bs += [(d >> (8 * (2 - j))) % 256 for j in range(3)] - return "".join(["%02x" % b for b in bs]) - - -def split(g: str) -> str: - return "{%s-%s-%s-%s-%s}" % (g[:8], g[8:12], g[12:16], g[16:20], g[20:]) +def split(uuid: str, /) -> str: + """ + Formats a UUID as + ``` + xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ``` + """ + return "-".join( + [ + uuid[:8], + uuid[8:12], + uuid[12:16], + uuid[16:20], + uuid[20:], + ] + ) def new() -> str: - return compress(uuid.uuid4().hex) + """ + Generates a random UUID and compresses it to a Base 64 IFC GUID. + """ + uuid = uuid4().hex + guid = compress(uuid) + return guid