Skip to content

Utils

Collection of utilities.

Classes:

Name Description
Blob

Management of binary large objects (BLOBs).

Functions:

Name Description
alpha_to_digit

Translates a column name from alphabetic to a 0-based numeric index.

bytes_to_str

Decodes a UTF-8 byte string to a string, ignoring errors.

convert_coordinates

Translates various coordinate formats into a tuple of 0-based integers.

digit_to_alpha

Translates a 0-based column index to its alphabetic representation.

hex2rgb

Convert a “#RRGGBB” hexadecimal color to an (R, G, B) tuple.

hexa_color

Safely convert a color from a tuple or string to its hexadecimal representation.

increment

Adjusts a negative index to a positive one based on a step.

is_RFC3066

Checks if a string conforms to the RFC 3066 language tag format.

make_xpath_query

Constructs an XPath query string with attribute-based predicates.

oooc_to_ooow

Converts an OpenOffice.org Calc formula to a Writer-compatible format.

rgb2hex

Convert a color name or (R, G, B) tuple to a “#RRGGBB” hexadecimal string.

str_to_bytes

Encodes a string to UTF-8 bytes, replacing errors.

to_bytes

Encodes a string to UTF-8 bytes if the input is a string.

to_str

Decodes a UTF-8 byte string to a string if the input is bytes.

translate_from_any

Translate a coordinate from any format to a 0-based integer index.

Attributes:

Name Type Description
FALSE_FAMILY_MAP_REVERSE
FAMILY_MAPPING
FAMILY_ODF_STD
STYLES_TO_REGISTER
SUBCLASSED_STYLES

FALSE_FAMILY_MAP_REVERSE module-attribute

FALSE_FAMILY_MAP_REVERSE = {v: k for k, v in (items())}

FAMILY_MAPPING module-attribute

FAMILY_MAPPING = {
    None: _BASE_FAMILY_MAP,
    None: _FALSE_FAMILY_MAP,
}

FAMILY_ODF_STD module-attribute

FAMILY_ODF_STD = {
    "chart",
    "drawing-page",
    "graphic",
    "paragraph",
    "presentation",
    "ruby",
    "section",
    "table",
    "table-cell",
    "table-column",
    "table-row",
    "text",
}

STYLES_TO_REGISTER module-attribute

STYLES_TO_REGISTER = (
    set(values()) | OTHER_STYLES
) - SUBCLASSED_STYLES

SUBCLASSED_STYLES module-attribute

SUBCLASSED_STYLES = {
    "style:background-image",
    "style:master-page",
    "style:page-layout",
    "draw:marker",
}

__all__ module-attribute

__all__ = [
    "FALSE_FAMILY_MAP_REVERSE",
    "FAMILY_MAPPING",
    "FAMILY_ODF_STD",
    "STYLES_TO_REGISTER",
    "SUBCLASSED_STYLES",
    "Blob",
    "alpha_to_digit",
    "bytes_to_str",
    "convert_coordinates",
    "digit_to_alpha",
    "hex2rgb",
    "hexa_color",
    "increment",
    "is_RFC3066",
    "isiterable",
    "make_xpath_query",
    "oooc_to_ooow",
    "remove_tree",
    "rgb2hex",
    "str_to_bytes",
    "to_bytes",
    "to_str",
    "translate_from_any",
]

Blob

Management of binary large objects (BLOBs).

Attributes:

Name Type Description
content bytes

The binary content of the blob.

name str

The name of the blob, typically generated from a hash of the content.

mime_type str

The MIME type of the blob’s content.

Methods:

Name Description
__init__

Initialise the Blob object.

from_base64

Create a Blob from a base64 encoded string.

from_io

Create a Blob from a file-like object.

from_path

Create a Blob from a file path.

Source code in odfdo/utils/blob.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
class Blob:
    """Management of binary large objects (BLOBs).

    Attributes:
        content (bytes): The binary content of the blob.
        name (str): The name of the blob, typically generated from a hash
                    of the content.
        mime_type (str): The MIME type of the blob's content.
    """

    def __init__(self) -> None:
        """Initialise the Blob object."""
        self.content: bytes = b""
        self.name: str = ""
        self.mime_type: str = ""

    @classmethod
    def from_path(cls, path: str | Path) -> Blob:
        """Create a Blob from a file path.

        The blob's name is generated from a hash of its content, and the
        MIME type is guessed from the file extension.

        Args:
            path: The path to the file.

        Returns:
            A new Blob instance containing the file's content.
        """
        blob = cls()
        path = Path(path)
        blob.content = path.read_bytes()
        extension = path.suffix.lower()
        footprint = hashlib.shake_256(blob.content).hexdigest(16)
        blob.name = f"{footprint}{extension}"
        mime_type, _encoding = guess_type(blob.name)
        blob.mime_type = mime_type or "application/octet-stream"
        return blob

    @classmethod
    def from_io(
        cls, file_like: BinaryIO, mime_type: str = "application/octet-stream"
    ) -> Blob:
        """Create a Blob from a file-like object.

        The blob's name is generated from a hash of its content. The MIME type
        is set to a generic "application/octet-stream".

        Args:
            file_like: A file-like object opened in binary mode.
            mime_type: The MIME type of the blob's content.

        Returns:
            A new Blob instance containing the file's content.
        """
        blob = cls()
        blob.content = file_like.read()
        blob.name = hashlib.shake_256(blob.content).hexdigest(16)
        blob.mime_type = mime_type
        return blob

    @classmethod
    def from_base64(
        cls, b64string: str | bytes, mime_type: str = "application/octet-stream"
    ) -> Blob:
        """Create a Blob from a base64 encoded string.

        The blob's name is generated from a hash of its content.

        Args:
            b64string: The base64 encoded string.
            mime_type: The MIME type of the decoded content.

        Returns:
            A new Blob instance containing the decoded content.
        """
        blob = cls()
        blob.content = base64.standard_b64decode(b64string)
        blob.name = hashlib.shake_256(blob.content).hexdigest(16)
        blob.mime_type = mime_type
        return blob

content instance-attribute

content: bytes = b''

mime_type instance-attribute

mime_type: str = ''

name instance-attribute

name: str = ''

__init__

__init__() -> None

Initialise the Blob object.

Source code in odfdo/utils/blob.py
41
42
43
44
45
def __init__(self) -> None:
    """Initialise the Blob object."""
    self.content: bytes = b""
    self.name: str = ""
    self.mime_type: str = ""

from_base64 classmethod

from_base64(
    b64string: str | bytes,
    mime_type: str = "application/octet-stream",
) -> Blob

Create a Blob from a base64 encoded string.

The blob’s name is generated from a hash of its content.

Parameters:

Name Type Description Default
b64string str | bytes

The base64 encoded string.

required
mime_type str

The MIME type of the decoded content.

'application/octet-stream'

Returns:

Type Description
Blob

A new Blob instance containing the decoded content.

Source code in odfdo/utils/blob.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@classmethod
def from_base64(
    cls, b64string: str | bytes, mime_type: str = "application/octet-stream"
) -> Blob:
    """Create a Blob from a base64 encoded string.

    The blob's name is generated from a hash of its content.

    Args:
        b64string: The base64 encoded string.
        mime_type: The MIME type of the decoded content.

    Returns:
        A new Blob instance containing the decoded content.
    """
    blob = cls()
    blob.content = base64.standard_b64decode(b64string)
    blob.name = hashlib.shake_256(blob.content).hexdigest(16)
    blob.mime_type = mime_type
    return blob

from_io classmethod

from_io(
    file_like: BinaryIO,
    mime_type: str = "application/octet-stream",
) -> Blob

Create a Blob from a file-like object.

The blob’s name is generated from a hash of its content. The MIME type is set to a generic “application/octet-stream”.

Parameters:

Name Type Description Default
file_like BinaryIO

A file-like object opened in binary mode.

required
mime_type str

The MIME type of the blob’s content.

'application/octet-stream'

Returns:

Type Description
Blob

A new Blob instance containing the file’s content.

Source code in odfdo/utils/blob.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@classmethod
def from_io(
    cls, file_like: BinaryIO, mime_type: str = "application/octet-stream"
) -> Blob:
    """Create a Blob from a file-like object.

    The blob's name is generated from a hash of its content. The MIME type
    is set to a generic "application/octet-stream".

    Args:
        file_like: A file-like object opened in binary mode.
        mime_type: The MIME type of the blob's content.

    Returns:
        A new Blob instance containing the file's content.
    """
    blob = cls()
    blob.content = file_like.read()
    blob.name = hashlib.shake_256(blob.content).hexdigest(16)
    blob.mime_type = mime_type
    return blob

from_path classmethod

from_path(path: str | Path) -> Blob

Create a Blob from a file path.

The blob’s name is generated from a hash of its content, and the MIME type is guessed from the file extension.

Parameters:

Name Type Description Default
path str | Path

The path to the file.

required

Returns:

Type Description
Blob

A new Blob instance containing the file’s content.

Source code in odfdo/utils/blob.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@classmethod
def from_path(cls, path: str | Path) -> Blob:
    """Create a Blob from a file path.

    The blob's name is generated from a hash of its content, and the
    MIME type is guessed from the file extension.

    Args:
        path: The path to the file.

    Returns:
        A new Blob instance containing the file's content.
    """
    blob = cls()
    path = Path(path)
    blob.content = path.read_bytes()
    extension = path.suffix.lower()
    footprint = hashlib.shake_256(blob.content).hexdigest(16)
    blob.name = f"{footprint}{extension}"
    mime_type, _encoding = guess_type(blob.name)
    blob.mime_type = mime_type or "application/octet-stream"
    return blob

alpha_to_digit

alpha_to_digit(alpha: str) -> int

Translates a column name from alphabetic to a 0-based numeric index.

For example, “A” becomes 0, “B” becomes 1, and “AB” becomes 27.

Parameters:

Name Type Description Default
alpha str

The alphabetic column name.

required

Returns:

Name Type Description
int int

The 0-based numeric index of the column.

Raises:

Type Description
ValueError

If the input string contains non-alphabetic characters.

Source code in odfdo/utils/coordinates.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def alpha_to_digit(alpha: str) -> int:
    """Translates a column name from alphabetic to a 0-based numeric index.

    For example, "A" becomes 0, "B" becomes 1, and "AB" becomes 27.

    Args:
        alpha: The alphabetic column name.

    Returns:
        int: The 0-based numeric index of the column.

    Raises:
        ValueError: If the input string contains non-alphabetic characters.
    """
    if isinstance(alpha, int):
        return alpha
    if not alpha.isalpha():
        raise ValueError(f"Column value {alpha!r} is malformed")
    column = 0
    for c in alpha.lower():
        val = ord(c) - ord("a") + 1
        column = column * 26 + val
    return column - 1

bytes_to_str

bytes_to_str(text: bytes) -> str

Decodes a UTF-8 byte string to a string, ignoring errors.

Parameters:

Name Type Description Default
text bytes

The byte string to decode.

required

Returns:

Name Type Description
str str

The resulting decoded string.

Source code in odfdo/utils/str_convert.py
76
77
78
79
80
81
82
83
84
85
def bytes_to_str(text: bytes) -> str:
    """Decodes a UTF-8 byte string to a string, ignoring errors.

    Args:
        text: The byte string to decode.

    Returns:
        str: The resulting decoded string.
    """
    return text.decode("utf-8", "ignore")

convert_coordinates

convert_coordinates(
    obj: tuple | list | str,
) -> tuple[int | None, ...]

Translates various coordinate formats into a tuple of 0-based integers.

This function can handle formats like “A1”, “A1:C3”, or tuples like (0, 0). A single cell coordinate is returned as a (column, row) tuple. An area is returned as a (col1, row1, col2, row2) tuple.

Parameters:

Name Type Description Default
obj tuple | list | str

The coordinate representation to convert.

required

Returns:

Type Description
tuple[int | None, ...]

tuple[int | None, …]: A tuple of 0-based integer coordinates.

Raises:

Type Description
TypeError

If the input object is of an unsupported type.

ValueError

If the coordinate string is malformed.

Examples:

>>> convert_coordinates("D3")
(3, 2)
>>> convert_coordinates((1, 2))
(1, 2)
>>> convert_coordinates("A1:B3")
(0, 0, 1, 2)
Source code in odfdo/utils/coordinates.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def convert_coordinates(
    obj: tuple | list | str,
) -> tuple[int | None, ...]:
    """Translates various coordinate formats into a tuple of 0-based integers.

    This function can handle formats like "A1", "A1:C3", or tuples like (0, 0).
    A single cell coordinate is returned as a (column, row) tuple. An area is
    returned as a (col1, row1, col2, row2) tuple.

    Args:
        obj: The coordinate representation to convert.

    Returns:
        tuple[int | None, ...]: A tuple of 0-based integer coordinates.

    Raises:
        TypeError: If the input object is of an unsupported type.
        ValueError: If the coordinate string is malformed.

    Examples:
        >>> convert_coordinates("D3")
        (3, 2)
        >>> convert_coordinates((1, 2))
        (1, 2)
        >>> convert_coordinates("A1:B3")
        (0, 0, 1, 2)
    """
    # By (1, 2) ?
    if isiterable(obj):
        return tuple(obj)
    # Or by 'B3' notation ?
    if not isinstance(obj, str):
        raise TypeError(f'Bad coordinates type: "{type(obj)}"')
    coordinates = []
    for coord in [x.strip() for x in obj.split(":", 1)]:
        # First "A"
        alpha = ""
        for c in coord:
            if c.isalpha():
                alpha += c
            else:
                break
        try:
            column = alpha_to_digit(alpha)
        except ValueError:
            # raise ValueError, 'coordinates "%s" malformed' % obj
            # maybe '1:4' table row coordinates
            column = None
        coordinates.append(column)
        # Then "1"
        try:
            line = int(coord[len(alpha) :]) - 1
        except ValueError:
            # raise ValueError, 'coordinates "%s" malformed' % obj
            # maybe 'A:C' row coordinates
            line = None
        if line and line < 0:
            raise ValueError(f"Coordinates {obj!r} malformed")
        coordinates.append(line)
    return tuple(coordinates)

digit_to_alpha

digit_to_alpha(digit: int | str) -> str

Translates a 0-based column index to its alphabetic representation.

For example, 0 becomes “A”, 1 becomes “B”, and 27 becomes “AB”.

Parameters:

Name Type Description Default
digit int | str

The 0-based numeric index of the column.

required

Returns:

Name Type Description
str str

The alphabetic representation of the column.

Raises:

Type Description
TypeError

If the input is not an integer.

Source code in odfdo/utils/coordinates.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def digit_to_alpha(digit: int | str) -> str:
    """Translates a 0-based column index to its alphabetic representation.

    For example, 0 becomes "A", 1 becomes "B", and 27 becomes "AB".

    Args:
        digit: The 0-based numeric index of the column.

    Returns:
        str: The alphabetic representation of the column.

    Raises:
        TypeError: If the input is not an integer.
    """
    if isinstance(digit, str) and digit.isalpha():
        return digit
    if not isinstance(digit, int):
        raise TypeError(f'column number "{digit}" is invalid')
    digit += 1
    column = ""
    while digit:
        column = chr(65 + ((digit - 1) % 26)) + column
        digit = (digit - 1) // 26
    return column

hex2rgb

hex2rgb(color: str) -> tuple[int, int, int]

Convert a “#RRGGBB” hexadecimal color to an (R, G, B) tuple.

Parameters:

Name Type Description Default
color str

The hexadecimal color string (e.g., “#FF0000”).

required

Returns:

Type Description
tuple[int, int, int]

tuple[int, int, int]: A tuple representing the RGB values.

Raises:

Type Description
ValueError

If the input string is not a valid hexadecimal color.

Source code in odfdo/utils/color.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def hex2rgb(color: str) -> tuple[int, int, int]:
    """Convert a "#RRGGBB" hexadecimal color to an (R, G, B) tuple.

    Args:
        color: The hexadecimal color string (e.g., "#FF0000").

    Returns:
        tuple[int, int, int]: A tuple representing the RGB values.

    Raises:
        ValueError: If the input string is not a valid hexadecimal color.
    """
    code = color[1:]
    if not (len(color) == 7 and color[0] == "#" and code.isalnum()):
        raise ValueError(f'"{color}" is not a valid color')
    red = int(code[:2], 16)
    green = int(code[2:4], 16)
    blue = int(code[4:6], 16)
    return (red, green, blue)

hexa_color

hexa_color(
    color: str | tuple[int, int, int] | None = None,
) -> str | None

Safely convert a color from a tuple or string to its hexadecimal representation.

  • An empty string is converted to black (“#000000”).
  • None is returned as None.
  • A color name is converted to its hex value.
  • A hex value is returned as is.

Parameters:

Name Type Description Default
color str | tuple[int, int, int] | None

The color representation to convert. Can be a color name, an RGB tuple, a hex string, or None.

None

Returns:

Type Description
str | None

str | None: The hexadecimal color string, or None if the input was None.

Raises:

Type Description
TypeError

If the color argument is of an unsupported type.

Source code in odfdo/utils/color.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None:
    """Safely convert a color from a tuple or string to its hexadecimal representation.

    - An empty string is converted to black ("#000000").
    - None is returned as None.
    - A color name is converted to its hex value.
    - A hex value is returned as is.

    Args:
        color: The color representation to convert. Can be a color name, an
            RGB tuple, a hex string, or None.

    Returns:
        str | None: The hexadecimal color string, or None if the input was None.

    Raises:
        TypeError: If the color argument is of an unsupported type.
    """
    if color is None:
        return None
    if isinstance(color, tuple):
        return rgb2hex(color)
    if not isinstance(color, str):
        raise TypeError(f'Invalid color argument "{color!r}"')
    color = color.strip()
    if not color:
        return "#000000"
    if color.startswith("#"):
        return color
    return rgb2hex(color)

increment

increment(value: int, step: int) -> int

Adjusts a negative index to a positive one based on a step.

This is used to handle negative indexing in table coordinates.

Parameters:

Name Type Description Default
value int

The index value, which may be negative.

required
step int

The total size of the dimension (e.g., table width or height).

required

Returns:

Name Type Description
int int

The adjusted, non-negative index.

Source code in odfdo/utils/coordinates.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def increment(value: int, step: int) -> int:
    """Adjusts a negative index to a positive one based on a step.

    This is used to handle negative indexing in table coordinates.

    Args:
        value: The index value, which may be negative.
        step: The total size of the dimension (e.g., table width or height).

    Returns:
        int: The adjusted, non-negative index.
    """
    while value < 0:
        if step == 0:
            return 0
        value += step
    return value

is_RFC3066

is_RFC3066(lang: str) -> bool

Checks if a string conforms to the RFC 3066 language tag format.

Valid formats are “language” or “language-country”, where “language” is a 2 or 3-letter ASCII string, and “country” (and other subtags) are alphanumeric.

Parameters:

Name Type Description Default
lang str

The language tag string to validate.

required

Returns:

Name Type Description
bool bool

True if the tag is valid, False otherwise.

Source code in odfdo/utils/rfc3066.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def is_RFC3066(lang: str) -> bool:
    """Checks if a string conforms to the RFC 3066 language tag format.

    Valid formats are "language" or "language-country", where "language" is a
    2 or 3-letter ASCII string, and "country" (and other subtags) are
    alphanumeric.

    Args:
        lang: The language tag string to validate.

    Returns:
        bool: True if the tag is valid, False otherwise.
    """

    def test_part1(part1: str) -> bool:
        if not 2 <= len(part1) <= 3:
            return False
        return all(x in ascii_letters for x in part1)

    def test_part2(part2: str) -> bool:
        return all(x in ascii_letters or x in digits for x in part2)

    if not lang or not isinstance(lang, str):
        return False
    if "-" not in lang:
        return test_part1(lang)
    parts = lang.split("-")
    if len(parts) > 3:
        return False
    if not test_part1(parts[0]):
        return False
    return all(test_part2(p) for p in parts[1:])

make_xpath_query

make_xpath_query(
    query_string: str,
    family: str | None = None,
    text_style: str | None = None,
    draw_id: str | None = None,
    draw_name: str | None = None,
    draw_style: str | None = None,
    draw_text_style: str | None = None,
    table_name: str | None = None,
    table_style: str | None = None,
    style_name: str | None = None,
    display_name: str | None = None,
    note_class: str | None = None,
    text_id: str | None = None,
    text_name: str | None = None,
    change_id: str | None = None,
    office_name: str | None = None,
    form_name: str | None = None,
    office_title: str | None = None,
    outline_level: str | int | None = None,
    level: str | int | None = None,
    page_layout: str | None = None,
    master_page: str | None = None,
    parent_style: str | None = None,
    presentation_class: str | None = None,
    position: int | None = None,
    **kwargs: str,
) -> str

Constructs an XPath query string with attribute-based predicates.

This function builds a complex XPath query by appending predicates for each provided keyword argument. It simplifies the process of searching for elements with specific attributes.

Parameters:

Name Type Description Default
query_string str

The base XPath query string (e.g., “descendant::text:p”).

required
family str | None

The style family.

None
text_style str | None

The name of a text style.

None
draw_id str | None

The draw:id attribute.

None
draw_name str | None

The draw:name attribute.

None
draw_style str | None

The name of a draw style.

None
draw_text_style str | None

The name of a draw text style.

None
table_name str | None

The name of a table.

None
table_style str | None

The name of a table style.

None
style_name str | None

The name of a style.

None
display_name str | None

The display name of a style.

None
note_class str | None

The class of a note.

None
text_id str | None

The text:id attribute.

None
text_name str | None

The text:name attribute.

None
change_id str | None

The text:change-id attribute.

None
office_name str | None

The office:name attribute.

None
form_name str | None

The form:name attribute.

None
office_title str | None

The office:title attribute.

None
outline_level str | int | None

The text:outline-level.

None
level str | int | None

The text:level attribute.

None
page_layout str | None

The name of a page layout style.

None
master_page str | None

The name of a master page.

None
parent_style str | None

The name of a parent style.

None
presentation_class str | None

The presentation class.

None
position int | None

The 1-based index of the element to select from the results. Negative values count from the end.

None
**kwargs str

Additional attribute-value pairs to add as predicates.

{}

Returns:

Name Type Description
str str

The fully constructed XPath query string.

Source code in odfdo/utils/xpath_query.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def make_xpath_query(
    query_string: str,
    family: str | None = None,
    text_style: str | None = None,
    draw_id: str | None = None,
    draw_name: str | None = None,
    draw_style: str | None = None,
    draw_text_style: str | None = None,
    table_name: str | None = None,
    table_style: str | None = None,
    style_name: str | None = None,
    display_name: str | None = None,
    note_class: str | None = None,
    text_id: str | None = None,
    text_name: str | None = None,
    change_id: str | None = None,
    office_name: str | None = None,
    form_name: str | None = None,
    office_title: str | None = None,
    outline_level: str | int | None = None,
    level: str | int | None = None,
    page_layout: str | None = None,
    master_page: str | None = None,
    parent_style: str | None = None,
    presentation_class: str | None = None,
    position: int | None = None,
    **kwargs: str,
) -> str:
    """Constructs an XPath query string with attribute-based predicates.

    This function builds a complex XPath query by appending predicates for each
    provided keyword argument. It simplifies the process of searching for
    elements with specific attributes.

    Args:
        query_string: The base XPath query string (e.g., "descendant::text:p").
        family: The style family.
        text_style: The name of a text style.
        draw_id: The draw:id attribute.
        draw_name: The draw:name attribute.
        draw_style: The name of a draw style.
        draw_text_style: The name of a draw text style.
        table_name: The name of a table.
        table_style: The name of a table style.
        style_name: The name of a style.
        display_name: The display name of a style.
        note_class: The class of a note.
        text_id: The text:id attribute.
        text_name: The text:name attribute.
        change_id: The text:change-id attribute.
        office_name: The office:name attribute.
        form_name: The form:name attribute.
        office_title: The office:title attribute.
        outline_level: The text:outline-level.
        level: The text:level attribute.
        page_layout: The name of a page layout style.
        master_page: The name of a master page.
        parent_style: The name of a parent style.
        presentation_class: The presentation class.
        position: The 1-based index of the element to select
            from the results. Negative values count from the end.
        **kwargs: Additional attribute-value pairs to add as predicates.

    Returns:
        str: The fully constructed XPath query string.
    """
    query = [query_string]
    attributes = kwargs
    if text_style:
        attributes["text:style-name"] = text_style
    if family and family in FAMILY_ODF_STD:
        attributes["style:family"] = family
    if draw_id:
        attributes["draw:id"] = draw_id
    if draw_name:
        attributes["draw:name"] = draw_name
    if draw_style:
        attributes["draw:style-name"] = draw_style
    if draw_text_style:
        attributes["draw:text-style-name"] = draw_text_style
    if table_name:
        attributes["table:name"] = table_name
    if table_style:
        attributes["table:style-name"] = table_style
    if style_name:
        attributes["style:name"] = style_name
    if display_name:
        attributes["style:display-name"] = display_name
    if note_class:
        attributes["text:note-class"] = note_class
    if text_id:
        attributes["text:id"] = text_id
    if text_name:
        attributes["text:name"] = text_name
    if change_id:
        attributes["text:change-id"] = change_id
    if office_name:
        attributes["office:name"] = office_name
    if form_name:
        attributes["form:name"] = form_name
    if office_title:
        attributes["office:title"] = office_title
    if outline_level:
        attributes["text:outline-level"] = str(outline_level)
    if level:
        attributes["text:level"] = str(level)
    if page_layout:
        attributes["style:page-layout-name"] = page_layout
    if master_page:
        attributes["draw:master-page-name"] = master_page
    if parent_style:
        attributes["style:parent-style-name"] = parent_style
    if presentation_class:
        attributes["presentation:class"] = presentation_class
    # Sort attributes for reproducible test cases
    for qname in sorted(attributes):
        value = attributes[qname]
        if value is True:
            query.append(f"[@{qname}]")
        else:
            query.append(f'[@{qname}="{value}"]')
    query_str = "".join(query)
    if position is not None:
        # A position argument that mimics the behaviour of a python's list
        if position >= 0:
            position_str = str(position + 1)
        elif position == -1:
            position_str = "last()"
        else:
            position_str = f"last()-{abs(position) - 1}"
        query_str = f"({query_str})[{position_str}]"
    # print(query)
    return query_str

oooc_to_ooow

oooc_to_ooow(formula: str) -> str

Converts an OpenOffice.org Calc formula to a Writer-compatible format.

This function translates cell address syntax and common functions from the oooc (Calc) namespace to the ooow (Writer) namespace.

Parameters:

Name Type Description Default
formula str

The Calc formula string to convert (e.g., “oooc:=SUM([.A1:.A5])”).

required

Returns:

Name Type Description
str str

The converted formula in Writer format (e.g., “ooow:sum “).

Source code in odfdo/utils/formula.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def oooc_to_ooow(formula: str) -> str:
    """Converts an OpenOffice.org Calc formula to a Writer-compatible format.

    This function translates cell address syntax and common functions from the
    `oooc` (Calc) namespace to the `ooow` (Writer) namespace.

    Args:
        formula: The Calc formula string to convert (e.g., "oooc:=SUM([.A1:.A5])").

    Returns:
        str: The converted formula in Writer format (e.g., "ooow:sum <A1:A5>").
    """
    _prefix, formula = formula.split(":=", 1)
    # assert "oooc" in prefix
    # Convert cell addresses
    formula = formula.replace("[.", "<").replace(":.", ":").replace("]", ">")
    # Convert functions
    formula = formula.replace("SUM(", "sum ").replace(")", "")
    return f"ooow:{formula}"

rgb2hex

rgb2hex(color: str | tuple[int, int, int]) -> str

Convert a color name or (R, G, B) tuple to a “#RRGGBB” hexadecimal string.

Parameters:

Name Type Description Default
color str | tuple[int, int, int]

The color as a standard CSS color name (e.g., “yellow”) or an RGB tuple (e.g., (238, 130, 238)).

required

Returns:

Name Type Description
str str

The hexadecimal representation of the color (e.g., “#FFFF00”).

Raises:

Type Description
KeyError

If the color name is unknown.

ValueError

If the color tuple is invalid.

TypeError

If the color argument is of an unsupported type.

Examples:

>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'
Source code in odfdo/utils/color.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def rgb2hex(color: str | tuple[int, int, int]) -> str:
    """Convert a color name or (R, G, B) tuple to a "#RRGGBB" hexadecimal string.

    Args:
        color: The color as a standard CSS color name (e.g., "yellow") or an
            RGB tuple (e.g., (238, 130, 238)).

    Returns:
        str: The hexadecimal representation of the color (e.g., "#FFFF00").

    Raises:
        KeyError: If the color name is unknown.
        ValueError: If the color tuple is invalid.
        TypeError: If the color argument is of an unsupported type.

    Examples:
        >>> rgb2hex('yellow')
        '#FFFF00'
        >>> rgb2hex((238, 130, 238))
        '#EE82EE'
    """
    if isinstance(color, str):
        try:
            code = CSS3_COLORMAP[color.lower()]
        except KeyError as e:
            raise KeyError(f'Color "{color}" is unknown in CSS color list') from e
    elif isinstance(color, tuple):
        if len(color) != 3:
            raise ValueError("Color must be a 3-tuple")
        code = color
    else:
        raise TypeError(f'Invalid color "{color}"')
    for channel in code:
        if not 0 <= channel <= 255:
            raise ValueError(
                f'Invalid color "{color}", channel must be between 0 and 255'
            )
    return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"

str_to_bytes

str_to_bytes(text: str) -> bytes

Encodes a string to UTF-8 bytes, replacing errors.

Parameters:

Name Type Description Default
text str

The string to encode.

required

Returns:

Name Type Description
bytes bytes

The resulting UTF-8 encoded bytes.

Source code in odfdo/utils/str_convert.py
64
65
66
67
68
69
70
71
72
73
def str_to_bytes(text: str) -> bytes:
    """Encodes a string to UTF-8 bytes, replacing errors.

    Args:
        text: The string to encode.

    Returns:
        bytes: The resulting UTF-8 encoded bytes.
    """
    return text.encode("utf-8", "replace")

to_bytes

to_bytes(value: Any) -> Any

Encodes a string to UTF-8 bytes if the input is a string.

Parameters:

Name Type Description Default
value Any

The value to convert.

required

Returns:

Name Type Description
Any Any

The encoded bytes if the input was a string, otherwise the original value.

Source code in odfdo/utils/str_convert.py
34
35
36
37
38
39
40
41
42
43
44
45
46
def to_bytes(value: Any) -> Any:
    """Encodes a string to UTF-8 bytes if the input is a string.

    Args:
        value: The value to convert.

    Returns:
        Any: The encoded bytes if the input was a string, otherwise the
            original value.
    """
    if isinstance(value, str):
        return value.encode("utf-8")
    return value

to_str

to_str(value: Any) -> Any

Decodes a UTF-8 byte string to a string if the input is bytes.

Parameters:

Name Type Description Default
value Any

The value to convert.

required

Returns:

Name Type Description
Any Any

The decoded string if the input was bytes, otherwise the original value.

Source code in odfdo/utils/str_convert.py
49
50
51
52
53
54
55
56
57
58
59
60
61
def to_str(value: Any) -> Any:
    """Decodes a UTF-8 byte string to a string if the input is bytes.

    Args:
        value: The value to convert.

    Returns:
        Any: The decoded string if the input was bytes, otherwise the
            original value.
    """
    if isinstance(value, bytes):
        return value.decode("utf-8")
    return value

translate_from_any

translate_from_any(
    x: str | int, length: int, idx: int
) -> int

Translate a coordinate from any format to a 0-based integer index.

Parameters:

Name Type Description Default
x str | int

The coordinate, which can be a 1-based integer or a string.

required
length int

The length of the axis for handling negative indices.

required
idx int

The index in the coordinate tuple to use (0 for x, 1 for y).

required

Returns:

Name Type Description
int int

The 0-based integer coordinate.

Raises:

Type Description
TypeError

If the input value is not a string or integer.

Source code in odfdo/utils/coordinates.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def translate_from_any(x: str | int, length: int, idx: int) -> int:
    """Translate a coordinate from any format to a 0-based integer index.

    Args:
        x: The coordinate, which can be a 1-based integer or a string.
        length: The length of the axis for handling negative indices.
        idx: The index in the coordinate tuple to use (0 for x, 1 for y).

    Returns:
        int: The 0-based integer coordinate.

    Raises:
        TypeError: If the input value is not a string or integer.
    """
    if isinstance(x, str):
        value_int = convert_coordinates(x)[idx]
        if value_int is None:
            raise TypeError(f"Wrong value: {x!r}")
    elif isinstance(x, int):
        value_int = x
    else:
        raise TypeError(f"Wrong value: {x!r}")
    if value_int < 0:
        return increment(value_int, length)
    return value_int