Source code for numbers_parser.constants

from datetime import datetime
from enum import IntEnum
from math import ceil

import enum_tools.documentation

# Path to package data varies by Python version so we can
# only ever generate coverage for one
try:
    from importlib.resources import files
except ImportError:  # pragma: nocover
    from importlib_resources import files

__all__ = [
    "CellPadding",
    "CellType",
    "CellValueType",
    "ControlFormattingType",
    "DurationStyle",
    "DurationUnits",
    "FormatType",
    "FormattingType",
    "FractionAccuracy",
    "NegativeNumberStyle",
    "PaddingType",
]

DEFAULT_DOCUMENT = files("numbers_parser") / "data" / "empty.numbers"

# New document defaults
DEFAULT_COLUMN_COUNT = 8
DEFAULT_COLUMN_WIDTH = 98.0
DEFAULT_PRE_BNC_BYTES = "๐Ÿค ".encode()  # Yes, really!
DEFAULT_ROW_COUNT = 12
DEFAULT_ROW_HEIGHT = 20.0
DEFAULT_TABLE_OFFSET = 80.0
DEFAULT_TILE_SIZE = 256

# Style defaults
DEFAULT_ALIGNMENT = ("auto", "top")
DEFAULT_BORDER_WIDTH = 0.35
DEFAULT_BORDER_COLOR = (0, 0, 0)
DEFAULT_BORDER_STYLE = "solid"
DEFAULT_FONT = "Helvetica Neue"
DEFAULT_FONT_SIZE = 11.0
DEFAULT_TEXT_INSET = 4.0
DEFAULT_TEXT_WRAP = True
EMPTY_STORAGE_BUFFER = b"\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

# Formatting values and defaults
DEFAULT_DATETIME_FORMAT = "dd MMM YYY HH:MM"
CHECKBOX_FALSE_VALUE = "โ˜"
CHECKBOX_TRUE_VALUE = "โ˜‘"
STAR_RATING_VALUE = "โ˜…"
OPERATOR_PRECEDENCE = {"%": 6, "^": 5, "ร—": 4, "*": 4, "/": 4, "รท": 4, "+": 3, "-": 3, "&": 2}


# Numbers limits
MAX_TILE_SIZE = 256
MAX_ROW_COUNT = 1000000
MAX_COL_COUNT = 1000
MAX_HEADER_COUNT = 5
MAX_SIGNIFICANT_DIGITS = 15
MAX_BASE = 36

# Root object IDs
DOCUMENT_ID = 1
PACKAGE_ID = 2

# System constants
EPOCH = datetime(2001, 1, 1)  # noqa: DTZ001
SECONDS_IN_HOUR = 60 * 60
SECONDS_IN_DAY = SECONDS_IN_HOUR * 24
SECONDS_IN_WEEK = SECONDS_IN_DAY * 7
DECIMAL128_BIAS = 0x1820

# File format enumerations
DECIMAL_PLACES_AUTO = 253
CURRENCY_CELL_TYPE = 10
CUSTOM_TEXT_PLACEHOLDER = "\ue421"

# Supported versions
SUPPORTED_NUMBERS_VERSIONS = [
    "10.0",
    "10.1",
    "10.2",
    "10.3",
    "11.0",
    "11.1",
    "11.2",
    "12.0",
    "12.1",
    "12.2",
    "13.0",
    "13.1",
    "13.2",
    "14.0",
    "14.1",
    "14.2",
    "14.3",
    "14.4",
    "14.5",
    # Numbers Creator Studio
    "26.0",
    "26.1",
]


# Supported date/time directives
def _days_occurred_in_month(value: datetime) -> str:
    """Return how many times the day of the datetime value has fallen in the month."""
    n_days = int((value - value.replace(day=1)).days / 7) + 1
    return str(n_days)


def _day_of_year(value: datetime) -> int:
    """Return the day number in a year for a datetime."""
    return value.timetuple().tm_yday


def _week_of_month(value: datetime) -> int:
    """Return the week number in a month for a datetime."""
    return ceil((value.day + value.replace(day=1).weekday()) / 7.0)


# Cell formats. We reorder so that the keys are in length order to greedily
# match ss in favour of s, etc.
_DATETIME_FIELD_MAP = {
    "a": lambda x: x.strftime("%p").lower(),
    "EEEE": "%A",
    "EEE": "%a",
    "yyyy": "%Y",
    "yy": "%y",
    "y": "%Y",
    "MMMM": "%B",
    "MMM": "%b",
    "MM": "%m",
    "M": "%-m",
    "d": "%-d",
    "dd": "%d",
    "DDD": lambda x: str(_day_of_year(x)).zfill(3),
    "DD": lambda x: str(_day_of_year(x)).zfill(2),
    "D": lambda x: str(_day_of_year(x)).zfill(1),
    "HH": "%H",
    "H": "%-H",
    "hh": "%I",
    "h": "%-I",
    "k": lambda x: str(x.hour).replace("0", "24"),
    "kk": lambda x: str(x.hour).replace("0", "24").zfill(2),
    "K": lambda x: str(x.hour % 12),
    "KK": lambda x: str(x.hour % 12).zfill(2),
    "mm": lambda x: str(x.minute).zfill(2),
    "m": lambda x: str(x.minute),
    "ss": "%S",
    "s": lambda x: str(x.second),
    "W": lambda x: str(_week_of_month(x) - 1),
    "ww": "%W",
    "G": "AD",  # TODO: support BC
    "F": lambda x: _days_occurred_in_month(x),
    "S": lambda x: str(x.microsecond).zfill(6)[0],
    "SS": lambda x: str(x.microsecond).zfill(6)[0:2],
    "SSS": lambda x: str(x.microsecond).zfill(6)[0:3],
    "SSSS": lambda x: str(x.microsecond).zfill(6)[0:4],
    "SSSSS": lambda x: str(x.microsecond).zfill(6)[0:5],
    # Table category formats
    "QQQ": lambda x: "Q" + str(int(x.month / 3) + 1),
    "LLLL": "%B",
    "w": "%-W",
}
DATETIME_FIELD_MAP = {
    x: _DATETIME_FIELD_MAP[x] for x in sorted(_DATETIME_FIELD_MAP, key=len, reverse=True)
}

# From TSCEArchives
COLON_TRACT_NODE = 67


class CellType(IntEnum):
    EMPTY = 1
    NUMBER = 2
    TEXT = 3
    DATE = 4
    BOOL = 5
    DURATION = 6
    ERROR = 7
    RICH_TEXT = 8
    CURRENCY = 101
    MERGED = 102


class CellPadding(IntEnum):
    SPACE = 1
    ZERO = 2


class DurationStyle(IntEnum):
    COMPACT = 0
    SHORT = 1
    LONG = 2


class DurationUnits(IntEnum):
    NONE = 0
    WEEK = 1
    DAY = 2
    HOUR = 4
    MINUTE = 8
    SECOND = 16
    MILLISECOND = 32


class FormatType(IntEnum):
    BOOLEAN = 1
    DECIMAL = 256
    CURRENCY = 257
    PERCENT = 258
    SCIENTIFIC = 259
    TEXT = 260
    DATE = 261
    FRACTION = 262
    CHECKBOX = 263
    RATING = 267
    DURATION = 268
    BASE = 269
    CUSTOM_NUMBER = 270
    CUSTOM_TEXT = 271
    CUSTOM_DATE = 272
    CUSTOM_CURRENCY = 274


class FormattingType(IntEnum):
    BASE = 1
    CURRENCY = 2
    DATETIME = 3
    FRACTION = 4
    NUMBER = 5
    PERCENTAGE = 6
    SCIENTIFIC = 7
    TICKBOX = 8
    RATING = 9
    SLIDER = 10
    STEPPER = 11
    POPUP = 12
    TEXT = 13


class ControlFormattingType(IntEnum):
    BASE = 1
    CURRENCY = 2
    FRACTION = 4
    NUMBER = 5
    PERCENTAGE = 6
    SCIENTIFIC = 7


class OwnerKind(IntEnum):
    TABLE_MODEL = 1
    MERGE_OWNER = 5
    HAUNTED_OWNER = 35


FORMATTING_ALLOWED_CELLS = {
    "base": ["NumberCell"],
    "currency": ["NumberCell"],
    "datetime": ["DateCell"],
    "fraction": ["NumberCell"],
    "number": ["NumberCell"],
    "percentage": ["NumberCell"],
    "popup": ["NumberCell", "TextCell"],
    "rating": ["NumberCell"],
    "scientific": ["NumberCell"],
    "slider": ["NumberCell"],
    "stepper": ["NumberCell"],
    "tickbox": ["BoolCell"],
}

FORMATTING_ACTION_CELLS = ["tickbox", "rating", "popup", "slider", "stepper"]


class CustomFormattingType(IntEnum):
    NUMBER = 101
    DATETIME = 102
    TEXT = 103


CUSTOM_FORMATTING_ALLOWED_CELLS = {
    "number": ["NumberCell"],
    "datetime": ["DateCell"],
    "text": ["TextCell"],
}


[docs] @enum_tools.documentation.document_enum class NegativeNumberStyle(IntEnum): """ How negative numbers are formatted. This enum is used in cell data formats and cell custom formats using the `negative_style` keyword argument. """ MINUS = 0 """Negative numbers use a simple minus sign.""" RED = 1 """Negative numbers are red with no minus sign.""" PARENTHESES = 2 """Negative numbers are in parentheses with no minus sign.""" RED_AND_PARENTHESES = 3 """Negative numbers are red and in parentheses with no minus sign."""
[docs] @enum_tools.documentation.document_enum class FractionAccuracy(IntEnum): """ How fractions are formatted. This enum is used in cell data formats and cell custom formats using the `fraction_accuracy` keyword argument. """ THREE = 0xFFFFFFFD """Fractions are formatted with up to 3 digits in the denominator.""" TWO = 0xFFFFFFFE """Fractions are formatted with up to 2 digits in the denominator.""" ONE = 0xFFFFFFFF """Fractions are formatted with up to 1 digit in the denominator.""" HALVES = 2 """Fractions are formatted to the nearest half.""" QUARTERS = 4 """Fractions are formatted to the nearest quarter.""" EIGHTHS = 8 """Fractions are formatted to the nearest eighth.""" SIXTEENTHS = 16 """Fractions are formatted to the nearest sixteenth.""" TENTHS = 10 """Fractions are formatted to the nearest tenth.""" HUNDREDTHS = 100 """Fractions are formatted to the nearest hundredth."""
ALLOWED_FORMATTING_PARAMETERS = { FormattingType.BASE: [ "base", "base_places", "base_use_minus_sign", ], FormattingType.CURRENCY: [ "currency_code", "decimal_places", "negative_style", "show_thousands_separator", "use_accounting_style", ], FormattingType.DATETIME: ["date_time_format"], FormattingType.FRACTION: ["fraction_accuracy"], FormattingType.NUMBER: [ "decimal_places", "show_thousands_separator", "negative_style", ], FormattingType.PERCENTAGE: [ "decimal_places", "show_thousands_separator", "negative_style", ], FormattingType.SCIENTIFIC: ["decimal_places"], FormattingType.POPUP: ["popup_values", "allow_none"], FormattingType.RATING: [], FormattingType.SLIDER: [ "control_format", "increment", "maximum", "minimum", ], FormattingType.STEPPER: [ "control_format", "increment", "maximum", "minimum", ], FormattingType.TICKBOX: [], FormattingType.TEXT: [], } FORMAT_TYPE_MAP = { FormattingType.BASE: FormatType.BASE, FormattingType.CURRENCY: FormatType.CURRENCY, FormattingType.DATETIME: FormatType.DATE, FormattingType.FRACTION: FormatType.FRACTION, FormattingType.NUMBER: FormatType.DECIMAL, FormattingType.PERCENTAGE: FormatType.PERCENT, FormattingType.POPUP: FormatType.TEXT, FormattingType.RATING: FormatType.RATING, FormattingType.SCIENTIFIC: FormatType.SCIENTIFIC, FormattingType.SLIDER: FormatType.DECIMAL, FormattingType.STEPPER: FormatType.DECIMAL, FormattingType.TICKBOX: FormatType.CHECKBOX, FormattingType.TEXT: FormatType.TEXT, } CUSTOM_FORMAT_TYPE_MAP = { CustomFormattingType.NUMBER: FormatType.CUSTOM_NUMBER, CustomFormattingType.DATETIME: FormatType.CUSTOM_DATE, CustomFormattingType.TEXT: FormatType.CUSTOM_TEXT, } class CellInteractionType(IntEnum): VALUE_EDITING = 0 FORMULA_EDITING = 1 STOCK = 2 CATEGORY_SUMMARY = 3 STEPPER = 4 SLIDER = 5 RATING = 6 POPUP = 7 TOGGLE = 8 class NumberFormatConditionType(IntEnum): NONE = -1 EQUAL = 0 LESS_THAN = 1 LESS_THAN_OR_EQUAL_TO = 2 GREATER_THAN = 3 GREATER_THAN_OR_EQUAL_TO = 4 class CellValueType(IntEnum): NIL_TYPE = 1 BOOLEAN_TYPE = 2 DATE_TYPE = 3 NUMBER_TYPE = 4 STRING_TYPE = 5 CONTROL_CELL_TYPE_MAP = { FormattingType.POPUP: CellInteractionType.POPUP, FormattingType.SLIDER: CellInteractionType.SLIDER, FormattingType.STEPPER: CellInteractionType.STEPPER, }
[docs] @enum_tools.documentation.document_enum class PaddingType(IntEnum): """How integers and decimals are padded in custom number formats.""" NONE = 0 """No number padding.""" ZEROS = 1 """Pad integers with leading spaces and decimals with trailing spaces.""" SPACES = 2 """Pad integers with leading zeroes and decimals with trailing zeroes."""