feat: обрезка КИЗ до 31 символа, README, инструкции для AI-агентов
- VBS и Python: в итоговом CSV код КИЗ обрезается до первых 31 символа (код без криптохвоста: 01 + GTIN + 21 + серийный номер); более короткий код остаётся как есть, с замечанием в отчёте. Копия kiz_to_csv.py в портативном комплекте обновлена так же. - README.md: инструкция на русском, переписана проще и яснее; Инструкция_экспорт_CSV.docx пересобрана из того же текста, с настоящими заголовками и списками. - AGENTS.md, CLAUDE.md, .agents/ (скилл kiz-testing, папки plans и reports). - .gitignore для .DS_Store; .gitattributes хранит файлы без изменения концов строк (.bat и .vbs нужны CRLF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
set PYTHONUTF8=1
|
||||
cd /d "%~dp0"
|
||||
"%~dp0python\python.exe" "%~dp0kiz_to_csv.py" %*
|
||||
pause
|
||||
@@ -0,0 +1,33 @@
|
||||
ПОРТАТИВНЫЙ КОМПЛЕКТ «КИЗ -> CSV» (Windows 7 / 10 / 11)
|
||||
|
||||
Без установки и без прав администратора: в систему ничего не пишется,
|
||||
реестр не трогается, нужна только эта распакованная папка.
|
||||
|
||||
СОСТАВ:
|
||||
python\ - встраиваемый Python 3.8.10 (официальный, portable)
|
||||
kiz_to_csv.py - скрипт сопоставления КИЗ с товарной накладной
|
||||
KIZ_CSV.bat - запуск двойным щелчком
|
||||
|
||||
КАК РАБОТАТЬ:
|
||||
1. Скопируйте всю папку на рабочий компьютер (флешка, сеть) - в любое место.
|
||||
2. Запустите KIZ_CSV.bat двойным щелчком.
|
||||
3. В чёрном окне по очереди появятся два запроса пути:
|
||||
сначала товарная накладная, затем файл с отсканированными КИЗ.
|
||||
Путь можно ввести вручную, а проще - когда появится запрос,
|
||||
ПЕРЕТАЩИТЕ файл мышью прямо в это окно и нажмите Enter.
|
||||
Ещё проще: перетащите ОБА файла сразу на значок KIZ_CSV.bat -
|
||||
тогда запросов не будет (первый файл - накладная, второй - КИЗ).
|
||||
4. По окончании в том же окне будет виден результат и сверка количества.
|
||||
Рядом с файлом КИЗ появятся два файла:
|
||||
<имя>.csv - его загружать в УД;
|
||||
<имя>_report.txt - отчёт: сверка по каждому GTIN и все замечания.
|
||||
|
||||
ТРЕБОВАНИЯ К ФАЙЛАМ (как в инструкции Инструкция_экспорт_CSV.docx):
|
||||
Накладная: GTIN - в колонке B ("Наименование"), начиная с ряда 19,
|
||||
в конце наименования после последнего пробела;
|
||||
количество - K, цена - L, ставка НДС - N.
|
||||
Файл КИЗ: последняя заполненная колонка - коды DataMatrix.
|
||||
Старый формат .xls не подходит - откройте в Excel и сохраните как .xlsx.
|
||||
|
||||
Примечание: окна выбора файлов здесь текстовые (в портативном Python нет
|
||||
компонента графических диалогов) - поэтому запросы выводятся в чёрном окне.
|
||||
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Сопоставление отсканированных КИЗ с товарной накладной + экспорт CSV.
|
||||
Кроссплатформенный порт скрипта xlsx_to_csv_utf8.vbs (тот же алгоритм).
|
||||
|
||||
Что делает:
|
||||
1. Спрашивает два файла (окна выбора; файл КИЗ можно передать аргументом
|
||||
командной строки или перетащить на .bat / .command):
|
||||
файл 1 - товарная накладная (ТОРГ-12),
|
||||
файл 2 - таблица с отсканированными КИЗ (последняя заполненная
|
||||
колонка - коды DataMatrix).
|
||||
2. Из каждого КИЗ извлекает GTIN (DataMatrix: "01" + 14 цифр).
|
||||
3. Ищет GTIN в колонке B ("Наименование") накладной, начиная с ряда 19:
|
||||
GTIN - последнее "слово" значения (текст после последнего пробела).
|
||||
4. Из найденного ряда берет: название (B, текст до последнего пробела),
|
||||
цену (L), ставку НДС (N, со знаком %).
|
||||
5. Формирует строку CSV для каждого КИЗ:
|
||||
B - название, C - цена, D - 1, E - 796, F - ставка НДС (%), G - "КИЗ".
|
||||
CSV - чистый текст, никаких преобразований чисел не происходит.
|
||||
КИЗ в CSV обрезается до первых 31 символа (код без криптохвоста);
|
||||
более короткий КИЗ остается как есть, с замечанием в отчете.
|
||||
6. Сверяет: число КИЗ каждого GTIN в файле 2 = количеству из K накладной.
|
||||
7. Сохраняет CSV рядом с файлом 2 (то же имя, .csv) и отчет _report.txt.
|
||||
Исходные файлы Excel не изменяются.
|
||||
|
||||
Запуск:
|
||||
Windows: kiz_to_csv.bat (или: python kiz_to_csv.py)
|
||||
Mac: ./kiz_to_csv.command (или: python3 kiz_to_csv.py)
|
||||
Linux: python3 kiz_to_csv.py
|
||||
Аргументы: один файл -> он считается файлом 2 (КИЗ), файл 1 спросят;
|
||||
два файла -> первый = накладная, второй = КИЗ, без диалогов.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import openpyxl
|
||||
except ImportError:
|
||||
sys.exit("Не найден модуль openpyxl. Установите: pip install openpyxl")
|
||||
|
||||
START_ROW = 19 # в накладной поиск по колонке B начинается с этого ряда
|
||||
COL_NAME = 2 # B - "Наименование ... GTIN" (GTIN - после последнего пробела)
|
||||
COL_QTY = 11 # K - количество
|
||||
COL_PRICE = 12 # L - цена
|
||||
COL_VAT = 14 # N - ставка НДС, %
|
||||
KIZ_LEN = 31 # КИЗ без криптохвоста: "01" + GTIN (14) + "21" + серийный номер (13)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- helpers
|
||||
|
||||
def clean_text(v):
|
||||
if v is None:
|
||||
return ""
|
||||
return str(v).replace("\r", "").replace("\n", "").strip()
|
||||
|
||||
|
||||
def extract_gtin(s):
|
||||
"""Из DataMatrix-строки КИЗ извлекает GTIN: '01' + 14 цифр."""
|
||||
t = s.replace(" ", "")
|
||||
if len(t) >= 16 and t[:2] == "01" and t[2:16].isdigit():
|
||||
return t[2:16]
|
||||
return ""
|
||||
|
||||
|
||||
def split_name_gtin(v):
|
||||
"""Колонка B накладной: 'Наименование ... GTIN'.
|
||||
Возвращает (название, GTIN): GTIN - текст после последнего пробела,
|
||||
название - всё до него."""
|
||||
t = clean_text(v)
|
||||
if " " not in t:
|
||||
return "", digits_only(t)
|
||||
name, _, gtin = t.rpartition(" ")
|
||||
return name.strip(), digits_only(gtin)
|
||||
|
||||
|
||||
def digits_only(v):
|
||||
"""Только цифры из значения ячейки (числа - без экспоненты)."""
|
||||
if v is None:
|
||||
return ""
|
||||
if isinstance(v, bool):
|
||||
return ""
|
||||
if isinstance(v, int):
|
||||
return str(v)
|
||||
if isinstance(v, float):
|
||||
return str(int(v)) if float(v).is_integer() else str(v)
|
||||
return "".join(ch for ch in str(v) if ch.isdigit())
|
||||
|
||||
|
||||
def norm_gtin(g):
|
||||
"""Без ведущих нулей: GTIN может лежать как 14-значный код с нулем
|
||||
или как 13-значное число без него."""
|
||||
t = g.lstrip("0")
|
||||
return t if t else "0"
|
||||
|
||||
|
||||
def num_text(v):
|
||||
"""Число как текст с точкой в десятичной части (358.18, а не 358,18)."""
|
||||
if v is None or isinstance(v, bool):
|
||||
return ""
|
||||
if isinstance(v, (int, float)):
|
||||
t = ("%f" % v).rstrip("0").rstrip(".")
|
||||
return t
|
||||
t = str(v).strip()
|
||||
if t.endswith("%"):
|
||||
t = t[:-1].strip()
|
||||
return t
|
||||
|
||||
|
||||
def format_field(v):
|
||||
"""Форматирование значения для CSV (как FormatField в VBS)."""
|
||||
if v is None:
|
||||
return ""
|
||||
if isinstance(v, bool):
|
||||
return str(v)
|
||||
if isinstance(v, int):
|
||||
return str(v)
|
||||
if isinstance(v, float):
|
||||
return ("%f" % v).rstrip("0").rstrip(".")
|
||||
return str(v)
|
||||
|
||||
|
||||
def pick_file(title, gui):
|
||||
"""Окно выбора файла; без GUI - запрос из консоли."""
|
||||
if gui:
|
||||
from tkinter import filedialog
|
||||
res = filedialog.askopenfilename(
|
||||
title=title,
|
||||
filetypes=[("Excel файлы", "*.xlsx *.xlsm *.xls"),
|
||||
("Все файлы", "*.*")])
|
||||
return res or ""
|
||||
return input(title + " (полный путь): ").strip().strip('"').strip("'")
|
||||
|
||||
|
||||
def show_message(title, text, warning=False):
|
||||
try:
|
||||
import tkinter
|
||||
from tkinter import messagebox
|
||||
root = tkinter.Tk()
|
||||
root.withdraw()
|
||||
root.attributes("-topmost", True)
|
||||
if warning:
|
||||
messagebox.showwarning(title, text, parent=root)
|
||||
else:
|
||||
messagebox.showinfo(title, text, parent=root)
|
||||
root.destroy()
|
||||
except Exception:
|
||||
print("\n" + "=" * 60)
|
||||
print(title)
|
||||
print("=" * 60)
|
||||
print(text)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- core
|
||||
|
||||
def load_nakl(path):
|
||||
"""Читает накладную: {key GTIN -> ряд}, {key GTIN -> количество K}.
|
||||
GTIN берется из колонки B ("Наименование") - текст после последнего
|
||||
пробела."""
|
||||
try:
|
||||
wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
|
||||
except Exception as e:
|
||||
sys.exit("Не удалось открыть накладную '%s': %s" % (path, e))
|
||||
ws = wb.worksheets[0]
|
||||
dict_row, dict_qty = {}, {}
|
||||
for r in range(START_ROW, ws.max_row + 1):
|
||||
_, gtin_b = split_name_gtin(ws.cell(r, COL_NAME).value)
|
||||
key = norm_gtin(gtin_b)
|
||||
if len(key) >= 8 and key not in dict_row:
|
||||
dict_row[key] = r
|
||||
k = ws.cell(r, COL_QTY).value
|
||||
if isinstance(k, (int, float)) and not isinstance(k, bool):
|
||||
dict_qty[key] = k
|
||||
wb.close()
|
||||
if not dict_row:
|
||||
sys.exit('В колонке B ("Наименование") накладной, начиная с ряда %d, '
|
||||
'не найдено ни одного GTIN (код после последнего пробела).\n'
|
||||
'Проверьте, что это файл товарной накладной.' % START_ROW)
|
||||
return dict_row, dict_qty
|
||||
|
||||
|
||||
def load_nakl_cells(path):
|
||||
"""Вторая проходка: открывает накладную обычным режимом, чтобы по ряду
|
||||
доставать B/L/N (read_only-лист не дает произвольного доступа после close)."""
|
||||
wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
|
||||
ws = wb.worksheets[0]
|
||||
return wb, ws
|
||||
|
||||
|
||||
def main():
|
||||
args = [a for a in sys.argv[1:] if os.path.isfile(a)]
|
||||
try:
|
||||
import tkinter # noqa: F401
|
||||
gui = True
|
||||
except Exception:
|
||||
gui = False
|
||||
|
||||
if len(args) >= 2:
|
||||
file_nakl, file_kiz = args[0], args[1]
|
||||
else:
|
||||
if len(args) == 1:
|
||||
file_kiz = args[0]
|
||||
file_nakl = pick_file("Выберите файл 1 - товарная накладная (ТОРГ-12)", gui)
|
||||
else:
|
||||
file_nakl = pick_file("Выберите файл 1 - товарная накладная (ТОРГ-12)", gui)
|
||||
if not file_nakl:
|
||||
sys.exit(0)
|
||||
file_kiz = pick_file("Выберите файл 2 - таблица с отсканированными КИЗ", gui)
|
||||
if not file_nakl or not file_kiz:
|
||||
sys.exit(0)
|
||||
|
||||
for p in (file_nakl, file_kiz):
|
||||
if p.lower().endswith(".xls"):
|
||||
sys.exit("Формат .xls не поддерживается Python-версией.\n"
|
||||
"Откройте файл в Excel и сохраните как .xlsx, либо\n"
|
||||
"для .xls используйте VBS-скрипт xlsx_to_csv_utf8.vbs.")
|
||||
|
||||
dict_row, dict_qty = load_nakl(file_nakl)
|
||||
wb1, ws1 = load_nakl_cells(file_nakl)
|
||||
|
||||
try:
|
||||
wb2 = openpyxl.load_workbook(file_kiz, data_only=True)
|
||||
except Exception as e:
|
||||
wb1.close()
|
||||
sys.exit("Не удалось открыть файл КИЗ '%s': %s" % (file_kiz, e))
|
||||
ws2 = wb2.worksheets[0]
|
||||
|
||||
n_rows2 = ws2.max_row
|
||||
last_col2 = ws2.max_column # последняя колонка - коды КИЗ
|
||||
|
||||
kiz_total = 0
|
||||
kiz_matched = 0
|
||||
warnings = []
|
||||
dict_found = {} # key GTIN -> число КИЗ в файле 2
|
||||
dict_show = {} # key GTIN -> GTIN для показа
|
||||
filled = {} # ряд файла 2 -> (B, C, F) для CSV
|
||||
kiz_cut = {} # ряд файла 2 -> КИЗ без криптохвоста
|
||||
|
||||
for r in range(1, n_rows2 + 1):
|
||||
s = clean_text(ws2.cell(r, last_col2).value)
|
||||
if not s:
|
||||
continue
|
||||
gtin = extract_gtin(s)
|
||||
if not gtin:
|
||||
warnings.append("Ряд %d: не удалось извлечь GTIN из кода КИЗ." % r)
|
||||
continue
|
||||
kiz_total += 1
|
||||
kiz_cut[r] = s[:KIZ_LEN]
|
||||
if len(s) < KIZ_LEN:
|
||||
warnings.append("Ряд %d: КИЗ короче %d символов (%d), записан как есть."
|
||||
% (r, KIZ_LEN, len(s)))
|
||||
key = norm_gtin(gtin)
|
||||
dict_found[key] = dict_found.get(key, 0) + 1
|
||||
dict_show.setdefault(key, gtin)
|
||||
|
||||
if key in dict_row:
|
||||
row_n = dict_row[key]
|
||||
name, _ = split_name_gtin(ws1.cell(row_n, COL_NAME).value)
|
||||
price = num_text(ws1.cell(row_n, COL_PRICE).value)
|
||||
vat = num_text(ws1.cell(row_n, COL_VAT).value)
|
||||
if vat and not vat.endswith("%"):
|
||||
vat += "%"
|
||||
filled[r] = (str(name), str(price), str(vat))
|
||||
kiz_matched += 1
|
||||
else:
|
||||
warnings.append("Ряд %d: GTIN %s не найден в колонке B "
|
||||
"накладной (ряды с %d)." % (r, gtin, START_ROW))
|
||||
|
||||
# --- сверка количества: файл 2 vs колонка K накладной ---
|
||||
ok_lines = []
|
||||
for key, cnt in dict_found.items():
|
||||
if key not in dict_row:
|
||||
continue # уже отмечено построчно выше
|
||||
if key in dict_qty:
|
||||
if int(dict_qty[key]) == cnt:
|
||||
ok_lines.append("GTIN %s: %d шт. = накладной (K) %s - OK"
|
||||
% (dict_show[key], cnt, num_text(dict_qty[key])))
|
||||
else:
|
||||
warnings.append("GTIN %s: КИЗ в файле %d шт., в накладной (K) %s"
|
||||
" - НЕ СОВПАДАЕТ"
|
||||
% (dict_show[key], cnt, num_text(dict_qty[key])))
|
||||
else:
|
||||
warnings.append("GTIN %s: в ряде накладной не указано "
|
||||
"количество (K)." % dict_show[key])
|
||||
|
||||
# --- экспорт CSV (UTF-8; последняя колонка в кавычках при запятой) ---
|
||||
lines = []
|
||||
for r in range(1, n_rows2 + 1):
|
||||
fields = []
|
||||
for c in range(1, last_col2 + 1):
|
||||
if c == 2 and r in filled:
|
||||
fv = filled[r][0] # B - название
|
||||
elif c == 3 and r in filled:
|
||||
fv = filled[r][1] # C - цена
|
||||
elif c == 4 and r in filled:
|
||||
fv = "1" # D
|
||||
elif c == 5 and r in filled:
|
||||
fv = "796" # E
|
||||
elif c == 6 and r in filled:
|
||||
fv = filled[r][2] # F - НДС с %
|
||||
elif c == 7 and r in filled:
|
||||
fv = "КИЗ" # G
|
||||
elif c == last_col2 and r in kiz_cut:
|
||||
fv = kiz_cut[r] # КИЗ без криптохвоста
|
||||
else:
|
||||
fv = format_field(ws2.cell(r, c).value)
|
||||
if c == last_col2 and "," in fv:
|
||||
fv = '"' + fv.replace('"', '""') + '"'
|
||||
fields.append(fv)
|
||||
lines.append(",".join(fields))
|
||||
|
||||
wb1.close()
|
||||
wb2.close()
|
||||
|
||||
out = "\r\n".join(lines)
|
||||
base = os.path.splitext(file_kiz)[0]
|
||||
csv_path = base + ".csv"
|
||||
report_path = base + "_report.txt"
|
||||
|
||||
with open(csv_path, "w", encoding="utf-8", newline="") as f:
|
||||
f.write(out)
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
f.write("Отчет сопоставления КИЗ\n")
|
||||
f.write("Накладная: %s\n" % file_nakl)
|
||||
f.write("Файл КИЗ: %s\n\n" % file_kiz)
|
||||
f.write("Обработано КИЗ: %d, сопоставлено: %d\n\n" % (kiz_total, kiz_matched))
|
||||
f.write("Сверка количества (файл КИЗ vs колонка K накладной):\n")
|
||||
f.write("\n".join(ok_lines) + "\n\n")
|
||||
f.write("Замечания:\n")
|
||||
f.write("\n".join(warnings) + "\n")
|
||||
|
||||
if not warnings:
|
||||
show_message("Сопоставление КИЗ",
|
||||
"Готово. Обработано КИЗ: %d, сопоставлено: %d.\n"
|
||||
"Все количества совпали с накладной.\n\nCSV: %s"
|
||||
% (kiz_total, kiz_matched, csv_path))
|
||||
else:
|
||||
show_message("Сопоставление КИЗ",
|
||||
"Готово, но есть замечания (подробности в отчете):\n%s\n\n"
|
||||
"Обработано КИЗ: %d, сопоставлено: %d."
|
||||
% (report_path, kiz_total, kiz_matched), warning=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,650 @@
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see http://www.opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
Python software and documentation are licensed under the
|
||||
Python Software Foundation License Version 2.
|
||||
|
||||
Starting with Python 3.8.6, examples, recipes, and other code in
|
||||
the documentation are dual licensed under the PSF License Version 2
|
||||
and the Zero-Clause BSD license.
|
||||
|
||||
Some software incorporated into Python is under different licenses.
|
||||
The licenses are listed with code falling under that license.
|
||||
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
|
||||
All Rights Reserved" are retained in Python alone or in any derivative version
|
||||
prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the Internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the Internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Additional Conditions for this Windows binary build
|
||||
---------------------------------------------------
|
||||
|
||||
This program is linked with and uses Microsoft Distributable Code,
|
||||
copyrighted by Microsoft Corporation. The Microsoft Distributable Code
|
||||
is embedded in each .exe, .dll and .pyd file as a result of running
|
||||
the code through a linker.
|
||||
|
||||
If you further distribute programs that include the Microsoft
|
||||
Distributable Code, you must comply with the restrictions on
|
||||
distribution specified by Microsoft. In particular, you must require
|
||||
distributors and external end users to agree to terms that protect the
|
||||
Microsoft Distributable Code at least as much as Microsoft's own
|
||||
requirements for the Distributable Code. See Microsoft's documentation
|
||||
(included in its developer tools and on its website at microsoft.com)
|
||||
for specific details.
|
||||
|
||||
Redistribution of the Windows binary build of the Python interpreter
|
||||
complies with this agreement, provided that you do not:
|
||||
|
||||
- alter any copyright, trademark or patent notice in Microsoft's
|
||||
Distributable Code;
|
||||
|
||||
- use Microsoft's trademarks in your programs' names or in a way that
|
||||
suggests your programs come from or are endorsed by Microsoft;
|
||||
|
||||
- distribute Microsoft's Distributable Code to run on a platform other
|
||||
than Microsoft operating systems, run-time technologies or application
|
||||
platforms; or
|
||||
|
||||
- include Microsoft Distributable Code in malicious, deceptive or
|
||||
unlawful programs.
|
||||
|
||||
These restrictions apply only to the Microsoft Distributable Code as
|
||||
defined above, not to Python itself or any programs running on the
|
||||
Python interpreter. The redistribution of the Python interpreter and
|
||||
libraries is governed by the Python Software License included with this
|
||||
file, or by other licenses as marked.
|
||||
|
||||
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
This program, "bzip2", the associated library "libbzip2", and all
|
||||
documentation, are copyright (C) 1996-2010 Julian R Seward. All
|
||||
rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
3. Altered source versions must be plainly marked as such, and must
|
||||
not be misrepresented as being the original software.
|
||||
|
||||
4. The name of the author may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Julian Seward, jseward@bzip.org
|
||||
bzip2/libbzip2 version 1.0.6 of 6 September 2010
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
|
||||
LICENSE ISSUES
|
||||
==============
|
||||
|
||||
The OpenSSL toolkit stays under a double license, i.e. both the conditions of
|
||||
the OpenSSL License and the original SSLeay license apply to the toolkit.
|
||||
See below for the actual license texts.
|
||||
|
||||
OpenSSL License
|
||||
---------------
|
||||
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This product includes cryptographic software written by Eric Young
|
||||
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||
* Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
*/
|
||||
|
||||
Original SSLeay License
|
||||
-----------------------
|
||||
|
||||
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* This package is an SSL implementation written
|
||||
* by Eric Young (eay@cryptsoft.com).
|
||||
* The implementation was written so as to conform with Netscapes SSL.
|
||||
*
|
||||
* This library is free for commercial and non-commercial use as long as
|
||||
* the following conditions are aheared to. The following conditions
|
||||
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||
* included with this distribution is covered by the same copyright terms
|
||||
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||
* the code are not to be removed.
|
||||
* If this package is used in a product, Eric Young should be given attribution
|
||||
* as the author of the parts of the library used.
|
||||
* This can be in the form of a textual message at program startup or
|
||||
* in documentation (online or textual) provided with the package.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* "This product includes cryptographic software written by
|
||||
* Eric Young (eay@cryptsoft.com)"
|
||||
* The word 'cryptographic' can be left out if the rouines from the library
|
||||
* being used are not cryptographic related :-).
|
||||
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||
* the apps directory (application code) you must include an acknowledgement:
|
||||
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* The licence and distribution terms for any publically available version or
|
||||
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||
* copied and put under another distribution licence
|
||||
* [including the GNU Public Licence.]
|
||||
*/
|
||||
|
||||
|
||||
libffi - Copyright (c) 1996-2014 Anthony Green, Red Hat, Inc and others.
|
||||
See source files for details.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
``Software''), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This software is copyrighted by the Regents of the University of
|
||||
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
|
||||
Corporation and other parties. The following terms apply to all files
|
||||
associated with the software unless explicitly disclaimed in
|
||||
individual files.
|
||||
|
||||
The authors hereby grant permission to use, copy, modify, distribute,
|
||||
and license this software and its documentation for any purpose, provided
|
||||
that existing copyright notices are retained in all copies and that this
|
||||
notice is included verbatim in any distributions. No written agreement,
|
||||
license, or royalty fee is required for any of the authorized uses.
|
||||
Modifications to this software may be copyrighted by their authors
|
||||
and need not follow the licensing terms described here, provided that
|
||||
the new terms are clearly indicated on the first page of each file where
|
||||
they apply.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||||
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||||
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||||
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
|
||||
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
MODIFICATIONS.
|
||||
|
||||
GOVERNMENT USE: If you are acquiring this software on behalf of the
|
||||
U.S. government, the Government shall have only "Restricted Rights"
|
||||
in the software and related documentation as defined in the Federal
|
||||
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
|
||||
are acquiring the software on behalf of the Department of Defense, the
|
||||
software shall be classified as "Commercial Computer Software" and the
|
||||
Government shall have only "Restricted Rights" as defined in Clause
|
||||
252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the
|
||||
authors grant the U.S. Government and others acting in its behalf
|
||||
permission to use and distribute the software in accordance with the
|
||||
terms specified in this license.
|
||||
|
||||
This software is copyrighted by the Regents of the University of
|
||||
California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
|
||||
Corporation, Apple Inc. and other parties. The following terms apply to
|
||||
all files associated with the software unless explicitly disclaimed in
|
||||
individual files.
|
||||
|
||||
The authors hereby grant permission to use, copy, modify, distribute,
|
||||
and license this software and its documentation for any purpose, provided
|
||||
that existing copyright notices are retained in all copies and that this
|
||||
notice is included verbatim in any distributions. No written agreement,
|
||||
license, or royalty fee is required for any of the authorized uses.
|
||||
Modifications to this software may be copyrighted by their authors
|
||||
and need not follow the licensing terms described here, provided that
|
||||
the new terms are clearly indicated on the first page of each file where
|
||||
they apply.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||||
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||||
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||||
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
|
||||
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
MODIFICATIONS.
|
||||
|
||||
GOVERNMENT USE: If you are acquiring this software on behalf of the
|
||||
U.S. government, the Government shall have only "Restricted Rights"
|
||||
in the software and related documentation as defined in the Federal
|
||||
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
|
||||
are acquiring the software on behalf of the Department of Defense, the
|
||||
software shall be classified as "Commercial Computer Software" and the
|
||||
Government shall have only "Restricted Rights" as defined in Clause
|
||||
252.227-7013 (b) (3) of DFARs. Notwithstanding the foregoing, the
|
||||
authors grant the U.S. Government and others acting in its behalf
|
||||
permission to use and distribute the software in accordance with the
|
||||
terms specified in this license.
|
||||
|
||||
Copyright (c) 1993-1999 Ioi Kim Lam.
|
||||
Copyright (c) 2000-2001 Tix Project Group.
|
||||
Copyright (c) 2004 ActiveState
|
||||
|
||||
This software is copyrighted by the above entities
|
||||
and other parties. The following terms apply to all files associated
|
||||
with the software unless explicitly disclaimed in individual files.
|
||||
|
||||
The authors hereby grant permission to use, copy, modify, distribute,
|
||||
and license this software and its documentation for any purpose, provided
|
||||
that existing copyright notices are retained in all copies and that this
|
||||
notice is included verbatim in any distributions. No written agreement,
|
||||
license, or royalty fee is required for any of the authorized uses.
|
||||
Modifications to this software may be copyrighted by their authors
|
||||
and need not follow the licensing terms described here, provided that
|
||||
the new terms are clearly indicated on the first page of each file where
|
||||
they apply.
|
||||
|
||||
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||||
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||||
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||||
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
|
||||
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
MODIFICATIONS.
|
||||
|
||||
GOVERNMENT USE: If you are acquiring this software on behalf of the
|
||||
U.S. government, the Government shall have only "Restricted Rights"
|
||||
in the software and related documentation as defined in the Federal
|
||||
Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you
|
||||
are acquiring the software on behalf of the Department of Defense, the
|
||||
software shall be classified as "Commercial Computer Software" and the
|
||||
Government shall have only "Restricted Rights" as defined in Clause
|
||||
252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the
|
||||
authors grant the U.S. Government and others acting in its behalf
|
||||
permission to use and distribute the software in accordance with the
|
||||
terms specified in this license.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Parts of this software are based on the Tcl/Tk software copyrighted by
|
||||
the Regents of the University of California, Sun Microsystems, Inc.,
|
||||
and other parties. The original license terms of the Tcl/Tk software
|
||||
distribution is included in the file docs/license.tcltk.
|
||||
|
||||
Parts of this software are based on the HTML Library software
|
||||
copyrighted by Sun Microsystems, Inc. The original license terms of
|
||||
the HTML Library software distribution is included in the file
|
||||
docs/license.html_lib.
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
The authors in alphabetical order
|
||||
|
||||
* Charlie Clark
|
||||
* Daniel Hillier
|
||||
* Elias Rabel
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
et_xml is licensed under the MIT license; see the file LICENCE for details.
|
||||
|
||||
et_xml includes code from the Python standard library, which is licensed under
|
||||
the Python license, a permissive open source license. The copyright and license
|
||||
is included below for compliance with Python's terms.
|
||||
|
||||
This module includes corrections and new features as follows:
|
||||
- Correct handling of attributes namespaces when a default namespace
|
||||
has been registered.
|
||||
- Records the namespaces for an Element during parsing and utilises them to
|
||||
allow inspection of namespaces at specific elements in the xml tree and
|
||||
during serialisation.
|
||||
|
||||
Misc:
|
||||
- Includes the test_xml_etree with small modifications for testing the
|
||||
modifications in this package.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2001-present Python Software Foundation; All Rights Reserved
|
||||
|
||||
A. HISTORY OF THE SOFTWARE
|
||||
==========================
|
||||
|
||||
Python was created in the early 1990s by Guido van Rossum at Stichting
|
||||
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
|
||||
as a successor of a language called ABC. Guido remains Python's
|
||||
principal author, although it includes many contributions from others.
|
||||
|
||||
In 1995, Guido continued his work on Python at the Corporation for
|
||||
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
|
||||
in Reston, Virginia where he released several versions of the
|
||||
software.
|
||||
|
||||
In May 2000, Guido and the Python core development team moved to
|
||||
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
||||
year, the PythonLabs team moved to Digital Creations, which became
|
||||
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
||||
https://www.python.org/psf/) was formed, a non-profit organization
|
||||
created specifically to own Python-related Intellectual Property.
|
||||
Zope Corporation was a sponsoring member of the PSF.
|
||||
|
||||
All Python releases are Open Source (see https://opensource.org for
|
||||
the Open Source Definition). Historically, most, but not all, Python
|
||||
releases have also been GPL-compatible; the table below summarizes
|
||||
the various releases.
|
||||
|
||||
Release Derived Year Owner GPL-
|
||||
from compatible? (1)
|
||||
|
||||
0.9.0 thru 1.2 1991-1995 CWI yes
|
||||
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
||||
1.6 1.5.2 2000 CNRI no
|
||||
2.0 1.6 2000 BeOpen.com no
|
||||
1.6.1 1.6 2001 CNRI yes (2)
|
||||
2.1 2.0+1.6.1 2001 PSF no
|
||||
2.0.1 2.0+1.6.1 2001 PSF yes
|
||||
2.1.1 2.1+2.0.1 2001 PSF yes
|
||||
2.1.2 2.1.1 2002 PSF yes
|
||||
2.1.3 2.1.2 2002 PSF yes
|
||||
2.2 and above 2.1.1 2001-now PSF yes
|
||||
|
||||
Footnotes:
|
||||
|
||||
(1) GPL-compatible doesn't mean that we're distributing Python under
|
||||
the GPL. All Python licenses, unlike the GPL, let you distribute
|
||||
a modified version without making your changes open source. The
|
||||
GPL-compatible licenses make it possible to combine Python with
|
||||
other software that is released under the GPL; the others don't.
|
||||
|
||||
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
||||
because its license has a choice of law clause. According to
|
||||
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
||||
is "not incompatible" with the GPL.
|
||||
|
||||
Thanks to the many outside volunteers who have worked under Guido's
|
||||
direction to make these releases possible.
|
||||
|
||||
|
||||
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
||||
===============================================================
|
||||
|
||||
Python software and documentation are licensed under the
|
||||
Python Software Foundation License Version 2.
|
||||
|
||||
Starting with Python 3.8.6, examples, recipes, and other code in
|
||||
the documentation are dual licensed under the PSF License Version 2
|
||||
and the Zero-Clause BSD license.
|
||||
|
||||
Some software incorporated into Python is under different licenses.
|
||||
The licenses are listed with code falling under that license.
|
||||
|
||||
|
||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
--------------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
otherwise using this software ("Python") in source or binary form and
|
||||
its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
distribute, and otherwise use Python alone or in any derivative version,
|
||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
i.e., "Copyright (c) 2001-2024 Python Software Foundation; All Rights Reserved"
|
||||
are retained in Python alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python.
|
||||
|
||||
4. PSF is making Python available to Licensee on an "AS IS"
|
||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. Nothing in this License Agreement shall be deemed to create any
|
||||
relationship of agency, partnership, or joint venture between PSF and
|
||||
Licensee. This License Agreement does not grant permission to use PSF
|
||||
trademarks or trade name in a trademark sense to endorse or promote
|
||||
products or services of Licensee, or any third party.
|
||||
|
||||
8. By copying, installing or otherwise using Python, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
||||
-------------------------------------------
|
||||
|
||||
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
||||
|
||||
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
||||
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
||||
Individual or Organization ("Licensee") accessing and otherwise using
|
||||
this software in source or binary form and its associated
|
||||
documentation ("the Software").
|
||||
|
||||
2. Subject to the terms and conditions of this BeOpen Python License
|
||||
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
||||
royalty-free, world-wide license to reproduce, analyze, test, perform
|
||||
and/or display publicly, prepare derivative works, distribute, and
|
||||
otherwise use the Software alone or in any derivative version,
|
||||
provided, however, that the BeOpen Python License is retained in the
|
||||
Software, alone or in any derivative version prepared by Licensee.
|
||||
|
||||
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
||||
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
||||
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
||||
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
||||
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
5. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
6. This License Agreement shall be governed by and interpreted in all
|
||||
respects by the law of the State of California, excluding conflict of
|
||||
law provisions. Nothing in this License Agreement shall be deemed to
|
||||
create any relationship of agency, partnership, or joint venture
|
||||
between BeOpen and Licensee. This License Agreement does not grant
|
||||
permission to use BeOpen trademarks or trade names in a trademark
|
||||
sense to endorse or promote products or services of Licensee, or any
|
||||
third party. As an exception, the "BeOpen Python" logos available at
|
||||
http://www.pythonlabs.com/logos.html may be used according to the
|
||||
permissions granted on that web page.
|
||||
|
||||
7. By copying, installing or otherwise using the software, Licensee
|
||||
agrees to be bound by the terms and conditions of this License
|
||||
Agreement.
|
||||
|
||||
|
||||
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
||||
---------------------------------------
|
||||
|
||||
1. This LICENSE AGREEMENT is between the Corporation for National
|
||||
Research Initiatives, having an office at 1895 Preston White Drive,
|
||||
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
||||
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
||||
source or binary form and its associated documentation.
|
||||
|
||||
2. Subject to the terms and conditions of this License Agreement, CNRI
|
||||
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||
license to reproduce, analyze, test, perform and/or display publicly,
|
||||
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
||||
alone or in any derivative version, provided, however, that CNRI's
|
||||
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
||||
1995-2001 Corporation for National Research Initiatives; All Rights
|
||||
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
||||
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
||||
Agreement, Licensee may substitute the following text (omitting the
|
||||
quotes): "Python 1.6.1 is made available subject to the terms and
|
||||
conditions in CNRI's License Agreement. This Agreement together with
|
||||
Python 1.6.1 may be located on the internet using the following
|
||||
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
||||
Agreement may also be obtained from a proxy server on the internet
|
||||
using the following URL: http://hdl.handle.net/1895.22/1013".
|
||||
|
||||
3. In the event Licensee prepares a derivative work that is based on
|
||||
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
||||
the derivative work available to others as provided herein, then
|
||||
Licensee hereby agrees to include in any such work a brief summary of
|
||||
the changes made to Python 1.6.1.
|
||||
|
||||
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
||||
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
|
||||
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
|
||||
6. This License Agreement will automatically terminate upon a material
|
||||
breach of its terms and conditions.
|
||||
|
||||
7. This License Agreement shall be governed by the federal
|
||||
intellectual property law of the United States, including without
|
||||
limitation the federal copyright law, and, to the extent such
|
||||
U.S. federal law does not apply, by the law of the Commonwealth of
|
||||
Virginia, excluding Virginia's conflict of law provisions.
|
||||
Notwithstanding the foregoing, with regard to derivative works based
|
||||
on Python 1.6.1 that incorporate non-separable material that was
|
||||
previously distributed under the GNU General Public License (GPL), the
|
||||
law of the Commonwealth of Virginia shall govern this License
|
||||
Agreement only as to issues arising under or with respect to
|
||||
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
||||
License Agreement shall be deemed to create any relationship of
|
||||
agency, partnership, or joint venture between CNRI and Licensee. This
|
||||
License Agreement does not grant permission to use CNRI trademarks or
|
||||
trade name in a trademark sense to endorse or promote products or
|
||||
services of Licensee, or any third party.
|
||||
|
||||
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
||||
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
||||
bound by the terms and conditions of this License Agreement.
|
||||
|
||||
ACCEPT
|
||||
|
||||
|
||||
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
||||
--------------------------------------------------
|
||||
|
||||
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
||||
The Netherlands. All rights reserved.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation, and that the name of Stichting Mathematisch
|
||||
Centrum or CWI not be used in advertising or publicity pertaining to
|
||||
distribution of the software without specific, written prior
|
||||
permission.
|
||||
|
||||
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
||||
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
This software is under the MIT Licence
|
||||
======================================
|
||||
|
||||
Copyright (c) 2010 openpyxl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: et_xmlfile
|
||||
Version: 2.0.0
|
||||
Summary: An implementation of lxml.xmlfile for the standard library
|
||||
Home-page: https://foss.heptapod.net/openpyxl/et_xmlfile
|
||||
Author: See AUTHORS.txt
|
||||
Author-email: charlie.clark@clark-consulting.eu
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://openpyxl.pages.heptapod.net/et_xmlfile/
|
||||
Project-URL: Source, https://foss.heptapod.net/openpyxl/et_xmlfile
|
||||
Project-URL: Tracker, https://foss.heptapod.net/openpyxl/et_xmfile/-/issues
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENCE.python
|
||||
License-File: LICENCE.rst
|
||||
License-File: AUTHORS.txt
|
||||
|
||||
.. image:: https://foss.heptapod.net/openpyxl/et_xmlfile/badges/branch/default/coverage.svg
|
||||
:target: https://coveralls.io/bitbucket/openpyxl/et_xmlfile?branch=default
|
||||
:alt: coverage status
|
||||
|
||||
et_xmfile
|
||||
=========
|
||||
|
||||
XML can use lots of memory, and et_xmlfile is a low memory library for creating large XML files
|
||||
And, although the standard library already includes an incremental parser, `iterparse` it has no equivalent when writing XML. Once an element has been added to the tree, it is written to
|
||||
the file or stream and the memory is then cleared.
|
||||
|
||||
This module is based upon the `xmlfile module from lxml <http://lxml.de/api.html#incremental-xml-generation>`_ with the aim of allowing code to be developed that will work with both libraries.
|
||||
It was developed initially for the openpyxl project, but is now a standalone module.
|
||||
|
||||
The code was written by Elias Rabel as part of the `Python Düsseldorf <http://pyddf.de>`_ openpyxl sprint in September 2014.
|
||||
|
||||
Proper support for incremental writing was provided by Daniel Hillier in 2024
|
||||
|
||||
Note on performance
|
||||
-------------------
|
||||
|
||||
The code was not developed with performance in mind, but turned out to be faster than the existing SAX-based implementation but is generally slower than lxml's xmlfile.
|
||||
There is one area where an optimisation for lxml may negatively affect the performance of et_xmfile and that is when using the `.element()` method on the xmlfile context manager. It is, therefore, recommended simply to create Elements write these directly, as in the sample code.
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
et_xmlfile/__init__.py,sha256=AQ4_2cNUEyUHlHo-Y3Gd6-8S_6eyKd55jYO4eh23UHw,228
|
||||
et_xmlfile/incremental_tree.py,sha256=lX4VStfzUNK0jtrVsvshPENu7E_zQirglkyRtzGDwEg,34534
|
||||
et_xmlfile/xmlfile.py,sha256=6QdxBq2P0Cf35R-oyXjLl5wOItfJJ4Yy6AlIF9RX7Bg,4886
|
||||
et_xmlfile-2.0.0.dist-info/AUTHORS.txt,sha256=fwOAKepUY2Bd0ieNMACZo4G86ekN2oPMqyBCNGtsgQc,82
|
||||
et_xmlfile-2.0.0.dist-info/LICENCE.python,sha256=TM2q68D0S4NyDsA5m7erMprc4GfdYvc8VTWi3AViirI,14688
|
||||
et_xmlfile-2.0.0.dist-info/LICENCE.rst,sha256=DIS7QvXTZ-Xr-fwt3jWxYUHfXuD9wYklCFi8bFVg9p4,1131
|
||||
et_xmlfile-2.0.0.dist-info/METADATA,sha256=DpfX6pCe0PvgPYi8i29YZ3zuGwe9M1PONhzSQFkVIE4,2711
|
||||
et_xmlfile-2.0.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
||||
et_xmlfile-2.0.0.dist-info/top_level.txt,sha256=34-74d5NNARgTsPxCMta5o28XpBNmSN0iCZhtmx2Fk8,11
|
||||
et_xmlfile-2.0.0.dist-info/RECORD,,
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (72.2.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
et_xmlfile
|
||||
@@ -0,0 +1,8 @@
|
||||
from .xmlfile import xmlfile
|
||||
|
||||
# constants
|
||||
__version__ = '2.0.0'
|
||||
__author__ = 'See AUTHORS.txt'
|
||||
__license__ = 'MIT'
|
||||
__author_email__ = 'charlie.clark@clark-consulting.eu'
|
||||
__url__ = 'https://foss.heptapod.net/openpyxl/et_xmlfile'
|
||||
+917
@@ -0,0 +1,917 @@
|
||||
# Code modified from cPython's Lib/xml/etree/ElementTree.py
|
||||
# The write() code is modified to allow specifying a particular namespace
|
||||
# uri -> prefix mapping.
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
# Licensed to PSF under a Contributor Agreement.
|
||||
# See https://www.python.org/psf/license for licensing details.
|
||||
#
|
||||
# ElementTree
|
||||
# Copyright (c) 1999-2008 by Fredrik Lundh. All rights reserved.
|
||||
#
|
||||
# fredrik@pythonware.com
|
||||
# http://www.pythonware.com
|
||||
# --------------------------------------------------------------------
|
||||
# The ElementTree toolkit is
|
||||
#
|
||||
# Copyright (c) 1999-2008 by Fredrik Lundh
|
||||
#
|
||||
# By obtaining, using, and/or copying this software and/or its
|
||||
# associated documentation, you agree that you have read, understood,
|
||||
# and will comply with the following terms and conditions:
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and
|
||||
# its associated documentation for any purpose and without fee is
|
||||
# hereby granted, provided that the above copyright notice appears in
|
||||
# all copies, and that both that copyright notice and this permission
|
||||
# notice appear in supporting documentation, and that the name of
|
||||
# Secret Labs AB or the author not be used in advertising or publicity
|
||||
# pertaining to distribution of the software without specific, written
|
||||
# prior permission.
|
||||
#
|
||||
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
|
||||
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
|
||||
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
|
||||
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||||
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||
# OF THIS SOFTWARE.
|
||||
# --------------------------------------------------------------------
|
||||
import contextlib
|
||||
import io
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def current_global_nsmap():
|
||||
return {
|
||||
prefix: uri for uri, prefix in ET._namespace_map.items()
|
||||
}
|
||||
|
||||
|
||||
class IncrementalTree(ET.ElementTree):
|
||||
|
||||
def write(
|
||||
self,
|
||||
file_or_filename,
|
||||
encoding=None,
|
||||
xml_declaration=None,
|
||||
default_namespace=None,
|
||||
method=None,
|
||||
*,
|
||||
short_empty_elements=True,
|
||||
nsmap=None,
|
||||
root_ns_only=False,
|
||||
minimal_ns_only=False,
|
||||
):
|
||||
"""Write element tree to a file as XML.
|
||||
|
||||
Arguments:
|
||||
*file_or_filename* -- file name or a file object opened for writing
|
||||
|
||||
*encoding* -- the output encoding (default: US-ASCII)
|
||||
|
||||
*xml_declaration* -- bool indicating if an XML declaration should be
|
||||
added to the output. If None, an XML declaration
|
||||
is added if encoding IS NOT either of:
|
||||
US-ASCII, UTF-8, or Unicode
|
||||
|
||||
*default_namespace* -- sets the default XML namespace (for "xmlns").
|
||||
Takes precedence over any default namespace
|
||||
provided in nsmap or
|
||||
xml.etree.ElementTree.register_namespace().
|
||||
|
||||
*method* -- either "xml" (default), "html, "text", or "c14n"
|
||||
|
||||
*short_empty_elements* -- controls the formatting of elements
|
||||
that contain no content. If True (default)
|
||||
they are emitted as a single self-closed
|
||||
tag, otherwise they are emitted as a pair
|
||||
of start/end tags
|
||||
|
||||
*nsmap* -- a mapping of namespace prefixes to URIs. These take
|
||||
precedence over any mappings registered using
|
||||
xml.etree.ElementTree.register_namespace(). The
|
||||
default_namespace argument, if supplied, takes precedence
|
||||
over any default namespace supplied in nsmap. All supplied
|
||||
namespaces will be declared on the root element, even if
|
||||
unused in the document.
|
||||
|
||||
*root_ns_only* -- bool indicating namespace declrations should only
|
||||
be written on the root element. This requires two
|
||||
passes of the xml tree adding additional time to
|
||||
the writing process. This is primarily meant to
|
||||
mimic xml.etree.ElementTree's behaviour.
|
||||
|
||||
*minimal_ns_only* -- bool indicating only namespaces that were used
|
||||
to qualify elements or attributes should be
|
||||
declared. All namespace declarations will be
|
||||
written on the root element regardless of the
|
||||
value of the root_ns_only arg. Requires two
|
||||
passes of the xml tree adding additional time to
|
||||
the writing process.
|
||||
|
||||
"""
|
||||
if not method:
|
||||
method = "xml"
|
||||
elif method not in ("text", "xml", "html"):
|
||||
raise ValueError("unknown method %r" % method)
|
||||
if not encoding:
|
||||
encoding = "us-ascii"
|
||||
|
||||
with _get_writer(file_or_filename, encoding) as (write, declared_encoding):
|
||||
if method == "xml" and (
|
||||
xml_declaration
|
||||
or (
|
||||
xml_declaration is None
|
||||
and encoding.lower() != "unicode"
|
||||
and declared_encoding.lower() not in ("utf-8", "us-ascii")
|
||||
)
|
||||
):
|
||||
write("<?xml version='1.0' encoding='%s'?>\n" % (declared_encoding,))
|
||||
if method == "text":
|
||||
ET._serialize_text(write, self._root)
|
||||
else:
|
||||
if method == "xml":
|
||||
is_html = False
|
||||
else:
|
||||
is_html = True
|
||||
if nsmap:
|
||||
if None in nsmap:
|
||||
raise ValueError(
|
||||
'Found None as default nsmap prefix in nsmap. '
|
||||
'Use "" as the default namespace prefix.'
|
||||
)
|
||||
new_nsmap = nsmap.copy()
|
||||
else:
|
||||
new_nsmap = {}
|
||||
if default_namespace:
|
||||
new_nsmap[""] = default_namespace
|
||||
if root_ns_only or minimal_ns_only:
|
||||
# _namespaces returns a mapping of only the namespaces that
|
||||
# were used.
|
||||
new_nsmap = _namespaces(
|
||||
self._root,
|
||||
default_namespace,
|
||||
new_nsmap,
|
||||
)
|
||||
if not minimal_ns_only:
|
||||
if nsmap:
|
||||
# We want all namespaces defined in the provided
|
||||
# nsmap to be declared regardless of whether
|
||||
# they've been used.
|
||||
new_nsmap.update(nsmap)
|
||||
if default_namespace:
|
||||
new_nsmap[""] = default_namespace
|
||||
global_nsmap = {
|
||||
prefix: uri for uri, prefix in ET._namespace_map.items()
|
||||
}
|
||||
if None in global_nsmap:
|
||||
raise ValueError(
|
||||
'Found None as default nsmap prefix in nsmap registered with '
|
||||
'register_namespace. Use "" for the default namespace prefix.'
|
||||
)
|
||||
nsmap_scope = {}
|
||||
_serialize_ns_xml(
|
||||
write,
|
||||
self._root,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
is_html=is_html,
|
||||
is_root=True,
|
||||
short_empty_elements=short_empty_elements,
|
||||
new_nsmap=new_nsmap,
|
||||
)
|
||||
|
||||
|
||||
def _make_new_ns_prefix(
|
||||
nsmap_scope,
|
||||
global_prefixes,
|
||||
local_nsmap=None,
|
||||
default_namespace=None,
|
||||
):
|
||||
i = len(nsmap_scope)
|
||||
if default_namespace is not None and "" not in nsmap_scope:
|
||||
# Keep the same numbering scheme as python which assumes the default
|
||||
# namespace is present if supplied.
|
||||
i += 1
|
||||
|
||||
while True:
|
||||
prefix = f"ns{i}"
|
||||
if (
|
||||
prefix not in nsmap_scope
|
||||
and prefix not in global_prefixes
|
||||
and (
|
||||
not local_nsmap or prefix not in local_nsmap
|
||||
)
|
||||
):
|
||||
return prefix
|
||||
i += 1
|
||||
|
||||
|
||||
def _get_or_create_prefix(
|
||||
uri,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
for_default_namespace_attr_prefix=False,
|
||||
):
|
||||
"""Find a prefix that doesn't conflict with the ns scope or create a new prefix
|
||||
|
||||
This function mutates nsmap_scope, global_nsmap, new_namespace_prefixes and
|
||||
uri_to_prefix. It is intended to keep state in _serialize_ns_xml consistent
|
||||
while deduplicating the house keeping code or updating these dictionaries.
|
||||
"""
|
||||
# Check if we can reuse an existing (global) prefix within the current
|
||||
# namespace scope. There maybe many prefixes pointing to a single URI by
|
||||
# this point and we need to select a prefix that is not in use in the
|
||||
# current scope.
|
||||
for global_prefix, global_uri in global_nsmap.items():
|
||||
if uri == global_uri and global_prefix not in nsmap_scope:
|
||||
prefix = global_prefix
|
||||
break
|
||||
else: # no break
|
||||
# We couldn't find a suitable existing prefix for this namespace scope,
|
||||
# let's create a new one.
|
||||
prefix = _make_new_ns_prefix(nsmap_scope, global_prefixes=global_nsmap)
|
||||
global_nsmap[prefix] = uri
|
||||
nsmap_scope[prefix] = uri
|
||||
if not for_default_namespace_attr_prefix:
|
||||
# Don't override the actual default namespace prefix
|
||||
uri_to_prefix[uri] = prefix
|
||||
if prefix != "xml":
|
||||
new_namespace_prefixes.add(prefix)
|
||||
return prefix
|
||||
|
||||
|
||||
def _find_default_namespace_attr_prefix(
|
||||
default_namespace,
|
||||
nsmap,
|
||||
local_nsmap,
|
||||
global_prefixes,
|
||||
provided_default_namespace=None,
|
||||
):
|
||||
# Search the provided nsmap for any prefixes for this uri that aren't the
|
||||
# default namespace ""
|
||||
for prefix, uri in nsmap.items():
|
||||
if uri == default_namespace and prefix != "":
|
||||
return prefix
|
||||
|
||||
for prefix, uri in local_nsmap.items():
|
||||
if uri == default_namespace and prefix != "":
|
||||
return prefix
|
||||
|
||||
# _namespace_map is a 1:1 mapping of uri -> prefix
|
||||
prefix = ET._namespace_map.get(default_namespace)
|
||||
if prefix and prefix not in nsmap:
|
||||
return prefix
|
||||
|
||||
return _make_new_ns_prefix(
|
||||
nsmap,
|
||||
global_prefixes,
|
||||
local_nsmap,
|
||||
provided_default_namespace,
|
||||
)
|
||||
|
||||
|
||||
def process_attribs(
|
||||
elem,
|
||||
is_nsmap_scope_changed,
|
||||
default_ns_attr_prefix,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
):
|
||||
item_parts = []
|
||||
for k, v in elem.items():
|
||||
if isinstance(k, ET.QName):
|
||||
k = k.text
|
||||
try:
|
||||
if k[:1] == "{":
|
||||
uri_and_name = k[1:].rsplit("}", 1)
|
||||
try:
|
||||
prefix = uri_to_prefix[uri_and_name[0]]
|
||||
except KeyError:
|
||||
if not is_nsmap_scope_changed:
|
||||
# We're about to mutate the these dicts so
|
||||
# let's copy them first. We don't have to
|
||||
# recompute other mappings as we're looking up
|
||||
# or creating a new prefix
|
||||
nsmap_scope = nsmap_scope.copy()
|
||||
uri_to_prefix = uri_to_prefix.copy()
|
||||
is_nsmap_scope_changed = True
|
||||
prefix = _get_or_create_prefix(
|
||||
uri_and_name[0],
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
)
|
||||
|
||||
if not prefix:
|
||||
if default_ns_attr_prefix:
|
||||
prefix = default_ns_attr_prefix
|
||||
else:
|
||||
for prefix, known_uri in nsmap_scope.items():
|
||||
if known_uri == uri_and_name[0] and prefix != "":
|
||||
default_ns_attr_prefix = prefix
|
||||
break
|
||||
else: # no break
|
||||
if not is_nsmap_scope_changed:
|
||||
# We're about to mutate the these dicts so
|
||||
# let's copy them first. We don't have to
|
||||
# recompute other mappings as we're looking up
|
||||
# or creating a new prefix
|
||||
nsmap_scope = nsmap_scope.copy()
|
||||
uri_to_prefix = uri_to_prefix.copy()
|
||||
is_nsmap_scope_changed = True
|
||||
prefix = _get_or_create_prefix(
|
||||
uri_and_name[0],
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
for_default_namespace_attr_prefix=True,
|
||||
)
|
||||
default_ns_attr_prefix = prefix
|
||||
k = f"{prefix}:{uri_and_name[1]}"
|
||||
except TypeError:
|
||||
ET._raise_serialization_error(k)
|
||||
|
||||
if isinstance(v, ET.QName):
|
||||
if v.text[:1] != "{":
|
||||
v = v.text
|
||||
else:
|
||||
uri_and_name = v.text[1:].rsplit("}", 1)
|
||||
try:
|
||||
prefix = uri_to_prefix[uri_and_name[0]]
|
||||
except KeyError:
|
||||
if not is_nsmap_scope_changed:
|
||||
# We're about to mutate the these dicts so
|
||||
# let's copy them first. We don't have to
|
||||
# recompute other mappings as we're looking up
|
||||
# or creating a new prefix
|
||||
nsmap_scope = nsmap_scope.copy()
|
||||
uri_to_prefix = uri_to_prefix.copy()
|
||||
is_nsmap_scope_changed = True
|
||||
prefix = _get_or_create_prefix(
|
||||
uri_and_name[0],
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
)
|
||||
v = f"{prefix}:{uri_and_name[1]}"
|
||||
item_parts.append((k, v))
|
||||
return item_parts, default_ns_attr_prefix, nsmap_scope
|
||||
|
||||
|
||||
def write_elem_start(
|
||||
write,
|
||||
elem,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
short_empty_elements,
|
||||
is_html,
|
||||
is_root=False,
|
||||
uri_to_prefix=None,
|
||||
default_ns_attr_prefix=None,
|
||||
new_nsmap=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Write the opening tag (including self closing) and element text.
|
||||
|
||||
Refer to _serialize_ns_xml for description of arguments.
|
||||
|
||||
nsmap_scope should be an empty dictionary on first call. All nsmap prefixes
|
||||
must be strings with the default namespace prefix represented by "".
|
||||
|
||||
eg.
|
||||
- <foo attr1="one"> (returns tag = 'foo')
|
||||
- <foo attr1="one">text (returns tag = 'foo')
|
||||
- <foo attr1="one" /> (returns tag = None)
|
||||
|
||||
Returns:
|
||||
tag:
|
||||
The tag name to be closed or None if no closing required.
|
||||
nsmap_scope:
|
||||
The current nsmap after any prefix to uri additions from this
|
||||
element. This is the input dict if unmodified or an updated copy.
|
||||
default_ns_attr_prefix:
|
||||
The prefix for the default namespace to use with attrs.
|
||||
uri_to_prefix:
|
||||
The current uri to prefix map after any uri to prefix additions
|
||||
from this element. This is the input dict if unmodified or an
|
||||
updated copy.
|
||||
next_remains_root:
|
||||
A bool indicating if the child element(s) should be treated as
|
||||
their own roots.
|
||||
"""
|
||||
tag = elem.tag
|
||||
text = elem.text
|
||||
|
||||
if tag is ET.Comment:
|
||||
write("<!--%s-->" % text)
|
||||
tag = None
|
||||
next_remains_root = False
|
||||
elif tag is ET.ProcessingInstruction:
|
||||
write("<?%s?>" % text)
|
||||
tag = None
|
||||
next_remains_root = False
|
||||
else:
|
||||
if new_nsmap:
|
||||
is_nsmap_scope_changed = True
|
||||
nsmap_scope = nsmap_scope.copy()
|
||||
nsmap_scope.update(new_nsmap)
|
||||
new_namespace_prefixes = set(new_nsmap.keys())
|
||||
new_namespace_prefixes.discard("xml")
|
||||
# We need to recompute the uri to prefixes
|
||||
uri_to_prefix = None
|
||||
default_ns_attr_prefix = None
|
||||
else:
|
||||
is_nsmap_scope_changed = False
|
||||
new_namespace_prefixes = set()
|
||||
|
||||
if uri_to_prefix is None:
|
||||
if None in nsmap_scope:
|
||||
raise ValueError(
|
||||
'Found None as a namespace prefix. Use "" as the default namespace prefix.'
|
||||
)
|
||||
uri_to_prefix = {uri: prefix for prefix, uri in nsmap_scope.items()}
|
||||
if "" in nsmap_scope:
|
||||
# There may be multiple prefixes for the default namespace but
|
||||
# we want to make sure we preferentially use "" (for elements)
|
||||
uri_to_prefix[nsmap_scope[""]] = ""
|
||||
|
||||
if tag is None:
|
||||
# tag supression where tag is set to None
|
||||
# Don't change is_root so namespaces can be passed down
|
||||
next_remains_root = is_root
|
||||
if text:
|
||||
write(ET._escape_cdata(text))
|
||||
else:
|
||||
next_remains_root = False
|
||||
if isinstance(tag, ET.QName):
|
||||
tag = tag.text
|
||||
try:
|
||||
# These splits / fully qualified tag creationg are the
|
||||
# bottleneck in this implementation vs the python
|
||||
# implementation.
|
||||
# The following split takes ~42ns with no uri and ~85ns if a
|
||||
# prefix is present. If the uri was present, we then need to
|
||||
# look up a prefix (~14ns) and create the fully qualified
|
||||
# string (~41ns). This gives a total of ~140ns where a uri is
|
||||
# present.
|
||||
# Python's implementation needs to preprocess the tree to
|
||||
# create a dict of qname -> tag by traversing the tree which
|
||||
# takes a bit of extra time but it quickly makes that back by
|
||||
# only having to do a dictionary look up (~14ns) for each tag /
|
||||
# attrname vs our splitting (~140ns).
|
||||
# So here we have the flexibility of being able to redefine the
|
||||
# uri a prefix points to midway through serialisation at the
|
||||
# expense of performance (~10% slower for a 1mb file on my
|
||||
# machine).
|
||||
if tag[:1] == "{":
|
||||
uri_and_name = tag[1:].rsplit("}", 1)
|
||||
try:
|
||||
prefix = uri_to_prefix[uri_and_name[0]]
|
||||
except KeyError:
|
||||
if not is_nsmap_scope_changed:
|
||||
# We're about to mutate the these dicts so let's
|
||||
# copy them first. We don't have to recompute other
|
||||
# mappings as we're looking up or creating a new
|
||||
# prefix
|
||||
nsmap_scope = nsmap_scope.copy()
|
||||
uri_to_prefix = uri_to_prefix.copy()
|
||||
is_nsmap_scope_changed = True
|
||||
prefix = _get_or_create_prefix(
|
||||
uri_and_name[0],
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
)
|
||||
if prefix:
|
||||
tag = f"{prefix}:{uri_and_name[1]}"
|
||||
else:
|
||||
tag = uri_and_name[1]
|
||||
elif "" in nsmap_scope:
|
||||
raise ValueError(
|
||||
"cannot use non-qualified names with default_namespace option"
|
||||
)
|
||||
except TypeError:
|
||||
ET._raise_serialization_error(tag)
|
||||
|
||||
write("<" + tag)
|
||||
|
||||
if elem.attrib:
|
||||
item_parts, default_ns_attr_prefix, nsmap_scope = process_attribs(
|
||||
elem,
|
||||
is_nsmap_scope_changed,
|
||||
default_ns_attr_prefix,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
new_namespace_prefixes,
|
||||
uri_to_prefix,
|
||||
)
|
||||
else:
|
||||
item_parts = []
|
||||
if new_namespace_prefixes:
|
||||
ns_attrs = []
|
||||
for k in sorted(new_namespace_prefixes):
|
||||
v = nsmap_scope[k]
|
||||
if k:
|
||||
k = "xmlns:" + k
|
||||
else:
|
||||
k = "xmlns"
|
||||
ns_attrs.append((k, v))
|
||||
if is_html:
|
||||
write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in ns_attrs]))
|
||||
else:
|
||||
write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in ns_attrs]))
|
||||
if item_parts:
|
||||
if is_html:
|
||||
write("".join([f' {k}="{ET._escape_attrib_html(v)}"' for k, v in item_parts]))
|
||||
else:
|
||||
write("".join([f' {k}="{ET._escape_attrib(v)}"' for k, v in item_parts]))
|
||||
if is_html:
|
||||
write(">")
|
||||
ltag = tag.lower()
|
||||
if text:
|
||||
if ltag == "script" or ltag == "style":
|
||||
write(text)
|
||||
else:
|
||||
write(ET._escape_cdata(text))
|
||||
if ltag in ET.HTML_EMPTY:
|
||||
tag = None
|
||||
elif text or len(elem) or not short_empty_elements:
|
||||
write(">")
|
||||
if text:
|
||||
write(ET._escape_cdata(text))
|
||||
else:
|
||||
tag = None
|
||||
write(" />")
|
||||
return (
|
||||
tag,
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
next_remains_root,
|
||||
)
|
||||
|
||||
|
||||
def _serialize_ns_xml(
|
||||
write,
|
||||
elem,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
short_empty_elements,
|
||||
is_html,
|
||||
is_root=False,
|
||||
uri_to_prefix=None,
|
||||
default_ns_attr_prefix=None,
|
||||
new_nsmap=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Serialize an element or tree using 'write' for output.
|
||||
|
||||
Args:
|
||||
write:
|
||||
A function to write the xml to its destination.
|
||||
elem:
|
||||
The element to serialize.
|
||||
nsmap_scope:
|
||||
The current prefix to uri mapping for this element. This should be
|
||||
an empty dictionary for the root element. Additional namespaces are
|
||||
progressively added using the new_nsmap arg.
|
||||
global_nsmap:
|
||||
A dict copy of the globally registered _namespace_map in uri to
|
||||
prefix form
|
||||
short_empty_elements:
|
||||
Controls the formatting of elements that contain no content. If True
|
||||
(default) they are emitted as a single self-closed tag, otherwise
|
||||
they are emitted as a pair of start/end tags.
|
||||
is_html:
|
||||
Set to True to serialize as HTML otherwise XML.
|
||||
is_root:
|
||||
Boolean indicating if this is a root element.
|
||||
uri_to_prefix:
|
||||
Current state of the mapping of uri to prefix.
|
||||
default_ns_attr_prefix:
|
||||
new_nsmap:
|
||||
New prefix -> uri mapping to be applied to this element.
|
||||
"""
|
||||
(
|
||||
tag,
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
next_remains_root,
|
||||
) = write_elem_start(
|
||||
write,
|
||||
elem,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
short_empty_elements,
|
||||
is_html,
|
||||
is_root,
|
||||
uri_to_prefix,
|
||||
default_ns_attr_prefix,
|
||||
new_nsmap=new_nsmap,
|
||||
)
|
||||
for e in elem:
|
||||
_serialize_ns_xml(
|
||||
write,
|
||||
e,
|
||||
nsmap_scope,
|
||||
global_nsmap,
|
||||
short_empty_elements,
|
||||
is_html,
|
||||
next_remains_root,
|
||||
uri_to_prefix,
|
||||
default_ns_attr_prefix,
|
||||
new_nsmap=None,
|
||||
)
|
||||
if tag:
|
||||
write(f"</{tag}>")
|
||||
if elem.tail:
|
||||
write(ET._escape_cdata(elem.tail))
|
||||
|
||||
|
||||
def _qnames_iter(elem):
|
||||
"""Iterate through all the qualified names in elem"""
|
||||
seen_el_qnames = set()
|
||||
seen_other_qnames = set()
|
||||
for this_elem in elem.iter():
|
||||
tag = this_elem.tag
|
||||
if isinstance(tag, str):
|
||||
if tag not in seen_el_qnames:
|
||||
seen_el_qnames.add(tag)
|
||||
yield tag, True
|
||||
elif isinstance(tag, ET.QName):
|
||||
tag = tag.text
|
||||
if tag not in seen_el_qnames:
|
||||
seen_el_qnames.add(tag)
|
||||
yield tag, True
|
||||
elif (
|
||||
tag is not None
|
||||
and tag is not ET.ProcessingInstruction
|
||||
and tag is not ET.Comment
|
||||
):
|
||||
ET._raise_serialization_error(tag)
|
||||
|
||||
for key, value in this_elem.items():
|
||||
if isinstance(key, ET.QName):
|
||||
key = key.text
|
||||
if key not in seen_other_qnames:
|
||||
seen_other_qnames.add(key)
|
||||
yield key, False
|
||||
|
||||
if isinstance(value, ET.QName):
|
||||
if value.text not in seen_other_qnames:
|
||||
seen_other_qnames.add(value.text)
|
||||
yield value.text, False
|
||||
|
||||
text = this_elem.text
|
||||
if isinstance(text, ET.QName):
|
||||
if text.text not in seen_other_qnames:
|
||||
seen_other_qnames.add(text.text)
|
||||
yield text.text, False
|
||||
|
||||
|
||||
def _namespaces(
|
||||
elem,
|
||||
default_namespace=None,
|
||||
nsmap=None,
|
||||
):
|
||||
"""Find all namespaces used in the document and return a prefix to uri map"""
|
||||
if nsmap is None:
|
||||
nsmap = {}
|
||||
|
||||
out_nsmap = {}
|
||||
|
||||
seen_uri_to_prefix = {}
|
||||
# Multiple prefixes may be present for a single uri. This will select the
|
||||
# last prefix found in nsmap for a given uri.
|
||||
local_prefix_map = {uri: prefix for prefix, uri in nsmap.items()}
|
||||
if default_namespace is not None:
|
||||
local_prefix_map[default_namespace] = ""
|
||||
elif "" in nsmap:
|
||||
# but we make sure the default prefix always take precedence
|
||||
local_prefix_map[nsmap[""]] = ""
|
||||
|
||||
global_prefixes = set(ET._namespace_map.values())
|
||||
has_unqual_el = False
|
||||
default_namespace_attr_prefix = None
|
||||
for qname, is_el in _qnames_iter(elem):
|
||||
try:
|
||||
if qname[:1] == "{":
|
||||
uri_and_name = qname[1:].rsplit("}", 1)
|
||||
|
||||
prefix = seen_uri_to_prefix.get(uri_and_name[0])
|
||||
if prefix is None:
|
||||
prefix = local_prefix_map.get(uri_and_name[0])
|
||||
if prefix is None or prefix in out_nsmap:
|
||||
prefix = ET._namespace_map.get(uri_and_name[0])
|
||||
if prefix is None or prefix in out_nsmap:
|
||||
prefix = _make_new_ns_prefix(
|
||||
out_nsmap,
|
||||
global_prefixes,
|
||||
nsmap,
|
||||
default_namespace,
|
||||
)
|
||||
if prefix or is_el:
|
||||
out_nsmap[prefix] = uri_and_name[0]
|
||||
seen_uri_to_prefix[uri_and_name[0]] = prefix
|
||||
|
||||
if not is_el and not prefix and not default_namespace_attr_prefix:
|
||||
# Find the alternative prefix to use with non-element
|
||||
# names
|
||||
default_namespace_attr_prefix = _find_default_namespace_attr_prefix(
|
||||
uri_and_name[0],
|
||||
out_nsmap,
|
||||
nsmap,
|
||||
global_prefixes,
|
||||
default_namespace,
|
||||
)
|
||||
out_nsmap[default_namespace_attr_prefix] = uri_and_name[0]
|
||||
# Don't add this uri to prefix mapping as it might override
|
||||
# the uri -> "" default mapping. We'll fix this up at the
|
||||
# end of the fn.
|
||||
# local_prefix_map[uri_and_name[0]] = default_namespace_attr_prefix
|
||||
else:
|
||||
if is_el:
|
||||
has_unqual_el = True
|
||||
except TypeError:
|
||||
ET._raise_serialization_error(qname)
|
||||
|
||||
if "" in out_nsmap and has_unqual_el:
|
||||
# FIXME: can this be handled in XML 1.0?
|
||||
raise ValueError(
|
||||
"cannot use non-qualified names with default_namespace option"
|
||||
)
|
||||
|
||||
# The xml prefix doesn't need to be declared but may have been used to
|
||||
# prefix names. Let's remove it if it has been used
|
||||
out_nsmap.pop("xml", None)
|
||||
return out_nsmap
|
||||
|
||||
|
||||
def tostring(
|
||||
element,
|
||||
encoding=None,
|
||||
method=None,
|
||||
*,
|
||||
xml_declaration=None,
|
||||
default_namespace=None,
|
||||
short_empty_elements=True,
|
||||
nsmap=None,
|
||||
root_ns_only=False,
|
||||
minimal_ns_only=False,
|
||||
tree_cls=IncrementalTree,
|
||||
):
|
||||
"""Generate string representation of XML element.
|
||||
|
||||
All subelements are included. If encoding is "unicode", a string
|
||||
is returned. Otherwise a bytestring is returned.
|
||||
|
||||
*element* is an Element instance, *encoding* is an optional output
|
||||
encoding defaulting to US-ASCII, *method* is an optional output which can
|
||||
be one of "xml" (default), "html", "text" or "c14n", *default_namespace*
|
||||
sets the default XML namespace (for "xmlns").
|
||||
|
||||
Returns an (optionally) encoded string containing the XML data.
|
||||
|
||||
"""
|
||||
stream = io.StringIO() if encoding == "unicode" else io.BytesIO()
|
||||
tree_cls(element).write(
|
||||
stream,
|
||||
encoding,
|
||||
xml_declaration=xml_declaration,
|
||||
default_namespace=default_namespace,
|
||||
method=method,
|
||||
short_empty_elements=short_empty_elements,
|
||||
nsmap=nsmap,
|
||||
root_ns_only=root_ns_only,
|
||||
minimal_ns_only=minimal_ns_only,
|
||||
)
|
||||
return stream.getvalue()
|
||||
|
||||
|
||||
def tostringlist(
|
||||
element,
|
||||
encoding=None,
|
||||
method=None,
|
||||
*,
|
||||
xml_declaration=None,
|
||||
default_namespace=None,
|
||||
short_empty_elements=True,
|
||||
nsmap=None,
|
||||
root_ns_only=False,
|
||||
minimal_ns_only=False,
|
||||
tree_cls=IncrementalTree,
|
||||
):
|
||||
lst = []
|
||||
stream = ET._ListDataStream(lst)
|
||||
tree_cls(element).write(
|
||||
stream,
|
||||
encoding,
|
||||
xml_declaration=xml_declaration,
|
||||
default_namespace=default_namespace,
|
||||
method=method,
|
||||
short_empty_elements=short_empty_elements,
|
||||
nsmap=nsmap,
|
||||
root_ns_only=root_ns_only,
|
||||
minimal_ns_only=minimal_ns_only,
|
||||
)
|
||||
return lst
|
||||
|
||||
|
||||
def compat_tostring(
|
||||
element,
|
||||
encoding=None,
|
||||
method=None,
|
||||
*,
|
||||
xml_declaration=None,
|
||||
default_namespace=None,
|
||||
short_empty_elements=True,
|
||||
nsmap=None,
|
||||
root_ns_only=True,
|
||||
minimal_ns_only=False,
|
||||
tree_cls=IncrementalTree,
|
||||
):
|
||||
"""tostring with options that produce the same results as xml.etree.ElementTree.tostring
|
||||
|
||||
root_ns_only=True is a bit slower than False as it needs to traverse the
|
||||
tree one more time to collect all the namespaces.
|
||||
"""
|
||||
return tostring(
|
||||
element,
|
||||
encoding=encoding,
|
||||
method=method,
|
||||
xml_declaration=xml_declaration,
|
||||
default_namespace=default_namespace,
|
||||
short_empty_elements=short_empty_elements,
|
||||
nsmap=nsmap,
|
||||
root_ns_only=root_ns_only,
|
||||
minimal_ns_only=minimal_ns_only,
|
||||
tree_cls=tree_cls,
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# serialization support
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _get_writer(file_or_filename, encoding):
|
||||
# Copied from Python 3.12
|
||||
# returns text write method and release all resources after using
|
||||
try:
|
||||
write = file_or_filename.write
|
||||
except AttributeError:
|
||||
# file_or_filename is a file name
|
||||
if encoding.lower() == "unicode":
|
||||
encoding = "utf-8"
|
||||
with open(file_or_filename, "w", encoding=encoding,
|
||||
errors="xmlcharrefreplace") as file:
|
||||
yield file.write, encoding
|
||||
else:
|
||||
# file_or_filename is a file-like object
|
||||
# encoding determines if it is a text or binary writer
|
||||
if encoding.lower() == "unicode":
|
||||
# use a text writer as is
|
||||
yield write, getattr(file_or_filename, "encoding", None) or "utf-8"
|
||||
else:
|
||||
# wrap a binary writer with TextIOWrapper
|
||||
with contextlib.ExitStack() as stack:
|
||||
if isinstance(file_or_filename, io.BufferedIOBase):
|
||||
file = file_or_filename
|
||||
elif isinstance(file_or_filename, io.RawIOBase):
|
||||
file = io.BufferedWriter(file_or_filename)
|
||||
# Keep the original file open when the BufferedWriter is
|
||||
# destroyed
|
||||
stack.callback(file.detach)
|
||||
else:
|
||||
# This is to handle passed objects that aren't in the
|
||||
# IOBase hierarchy, but just have a write method
|
||||
file = io.BufferedIOBase()
|
||||
file.writable = lambda: True
|
||||
file.write = write
|
||||
try:
|
||||
# TextIOWrapper uses this methods to determine
|
||||
# if BOM (for UTF-16, etc) should be added
|
||||
file.seekable = file_or_filename.seekable
|
||||
file.tell = file_or_filename.tell
|
||||
except AttributeError:
|
||||
pass
|
||||
file = io.TextIOWrapper(file,
|
||||
encoding=encoding,
|
||||
errors="xmlcharrefreplace",
|
||||
newline="\n")
|
||||
# Keep the original file open when the TextIOWrapper is
|
||||
# destroyed
|
||||
stack.callback(file.detach)
|
||||
yield file.write, encoding
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
from __future__ import absolute_import
|
||||
# Copyright (c) 2010-2015 openpyxl
|
||||
|
||||
"""Implements the lxml.etree.xmlfile API using the standard library xml.etree"""
|
||||
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from xml.etree.ElementTree import (
|
||||
Element,
|
||||
_escape_cdata,
|
||||
)
|
||||
|
||||
from . import incremental_tree
|
||||
|
||||
|
||||
class LxmlSyntaxError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _IncrementalFileWriter(object):
|
||||
"""Replacement for _IncrementalFileWriter of lxml"""
|
||||
def __init__(self, output_file):
|
||||
self._element_stack = []
|
||||
self._file = output_file
|
||||
self._have_root = False
|
||||
self.global_nsmap = incremental_tree.current_global_nsmap()
|
||||
self.is_html = False
|
||||
|
||||
@contextmanager
|
||||
def element(self, tag, attrib=None, nsmap=None, **_extra):
|
||||
"""Create a new xml element using a context manager."""
|
||||
if nsmap and None in nsmap:
|
||||
# Normalise None prefix (lxml's default namespace prefix) -> "", as
|
||||
# required for incremental_tree
|
||||
if "" in nsmap and nsmap[""] != nsmap[None]:
|
||||
raise ValueError(
|
||||
'Found None and "" as default nsmap prefixes with different URIs'
|
||||
)
|
||||
nsmap = nsmap.copy()
|
||||
nsmap[""] = nsmap.pop(None)
|
||||
|
||||
# __enter__ part
|
||||
self._have_root = True
|
||||
if attrib is None:
|
||||
attrib = {}
|
||||
elem = Element(tag, attrib=attrib, **_extra)
|
||||
elem.text = ''
|
||||
elem.tail = ''
|
||||
if self._element_stack:
|
||||
is_root = False
|
||||
(
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
) = self._element_stack[-1]
|
||||
else:
|
||||
is_root = True
|
||||
nsmap_scope = {}
|
||||
default_ns_attr_prefix = None
|
||||
uri_to_prefix = {}
|
||||
(
|
||||
tag,
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
next_remains_root,
|
||||
) = incremental_tree.write_elem_start(
|
||||
self._file,
|
||||
elem,
|
||||
nsmap_scope=nsmap_scope,
|
||||
global_nsmap=self.global_nsmap,
|
||||
short_empty_elements=False,
|
||||
is_html=self.is_html,
|
||||
is_root=is_root,
|
||||
uri_to_prefix=uri_to_prefix,
|
||||
default_ns_attr_prefix=default_ns_attr_prefix,
|
||||
new_nsmap=nsmap,
|
||||
)
|
||||
self._element_stack.append(
|
||||
(
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
)
|
||||
)
|
||||
yield
|
||||
|
||||
# __exit__ part
|
||||
self._element_stack.pop()
|
||||
self._file(f"</{tag}>")
|
||||
if elem.tail:
|
||||
self._file(_escape_cdata(elem.tail))
|
||||
|
||||
def write(self, arg):
|
||||
"""Write a string or subelement."""
|
||||
|
||||
if isinstance(arg, str):
|
||||
# it is not allowed to write a string outside of an element
|
||||
if not self._element_stack:
|
||||
raise LxmlSyntaxError()
|
||||
self._file(_escape_cdata(arg))
|
||||
|
||||
else:
|
||||
if not self._element_stack and self._have_root:
|
||||
raise LxmlSyntaxError()
|
||||
|
||||
if self._element_stack:
|
||||
is_root = False
|
||||
(
|
||||
nsmap_scope,
|
||||
default_ns_attr_prefix,
|
||||
uri_to_prefix,
|
||||
) = self._element_stack[-1]
|
||||
else:
|
||||
is_root = True
|
||||
nsmap_scope = {}
|
||||
default_ns_attr_prefix = None
|
||||
uri_to_prefix = {}
|
||||
incremental_tree._serialize_ns_xml(
|
||||
self._file,
|
||||
arg,
|
||||
nsmap_scope=nsmap_scope,
|
||||
global_nsmap=self.global_nsmap,
|
||||
short_empty_elements=True,
|
||||
is_html=self.is_html,
|
||||
is_root=is_root,
|
||||
uri_to_prefix=uri_to_prefix,
|
||||
default_ns_attr_prefix=default_ns_attr_prefix,
|
||||
)
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
# without root the xml document is incomplete
|
||||
if not self._have_root:
|
||||
raise LxmlSyntaxError()
|
||||
|
||||
|
||||
class xmlfile(object):
|
||||
"""Context manager that can replace lxml.etree.xmlfile."""
|
||||
def __init__(self, output_file, buffered=False, encoding="utf-8", close=False):
|
||||
self._file = output_file
|
||||
self._close = close
|
||||
self.encoding = encoding
|
||||
self.writer_cm = None
|
||||
|
||||
def __enter__(self):
|
||||
self.writer_cm = incremental_tree._get_writer(self._file, encoding=self.encoding)
|
||||
writer, declared_encoding = self.writer_cm.__enter__()
|
||||
return _IncrementalFileWriter(writer)
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if self.writer_cm:
|
||||
self.writer_cm.__exit__(type, value, traceback)
|
||||
if self._close:
|
||||
self._file.close()
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
This software is under the MIT Licence
|
||||
======================================
|
||||
|
||||
Copyright (c) 2010 openpyxl
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: openpyxl
|
||||
Version: 3.1.5
|
||||
Summary: A Python library to read/write Excel 2010 xlsx/xlsm files
|
||||
Home-page: https://openpyxl.readthedocs.io
|
||||
Author: See AUTHORS
|
||||
Author-email: charlie.clark@clark-consulting.eu
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://openpyxl.readthedocs.io/en/stable/
|
||||
Project-URL: Source, https://foss.heptapod.net/openpyxl/openpyxl
|
||||
Project-URL: Tracker, https://foss.heptapod.net/openpyxl/openpyxl/-/issues
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENCE.rst
|
||||
Requires-Dist: et-xmlfile
|
||||
|
||||
.. image:: https://coveralls.io/repos/bitbucket/openpyxl/openpyxl/badge.svg?branch=default
|
||||
:target: https://coveralls.io/bitbucket/openpyxl/openpyxl?branch=default
|
||||
:alt: coverage status
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
openpyxl is a Python library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files.
|
||||
|
||||
It was born from lack of existing library to read/write natively from Python
|
||||
the Office Open XML format.
|
||||
|
||||
All kudos to the PHPExcel team as openpyxl was initially based on PHPExcel.
|
||||
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
By default openpyxl does not guard against quadratic blowup or billion laughs
|
||||
xml attacks. To guard against these attacks install defusedxml.
|
||||
|
||||
Mailing List
|
||||
------------
|
||||
|
||||
The user list can be found on http://groups.google.com/group/openpyxl-users
|
||||
|
||||
|
||||
Sample code::
|
||||
|
||||
from openpyxl import Workbook
|
||||
wb = Workbook()
|
||||
|
||||
# grab the active worksheet
|
||||
ws = wb.active
|
||||
|
||||
# Data can be assigned directly to cells
|
||||
ws['A1'] = 42
|
||||
|
||||
# Rows can also be appended
|
||||
ws.append([1, 2, 3])
|
||||
|
||||
# Python types will automatically be converted
|
||||
import datetime
|
||||
ws['A2'] = datetime.datetime.now()
|
||||
|
||||
# Save the file
|
||||
wb.save("sample.xlsx")
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The documentation is at: https://openpyxl.readthedocs.io
|
||||
|
||||
* installation methods
|
||||
* code examples
|
||||
* instructions for contributing
|
||||
|
||||
Release notes: https://openpyxl.readthedocs.io/en/stable/changes.html
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
openpyxl/__init__.py,sha256=s2sXcp8ThXXHswNSh-UuQi5BHsoasuczUyjNNz0Vupc,603
|
||||
openpyxl/_constants.py,sha256=rhOeQ6wNH6jw73G4I242VtbmyM8fvdNVwOsOjJlJ6TU,306
|
||||
openpyxl/cell/__init__.py,sha256=OXNzFFR9dlxUXiuWXyKSVQRJiQhZFel-_RQS3mHNnrQ,122
|
||||
openpyxl/cell/_writer.py,sha256=3I6WLKEJGuFe8rOjxdAVuDT4sZYjcYo57-6velGepdQ,4015
|
||||
openpyxl/cell/cell.py,sha256=hVJsMC9kJAxxb_CspJlBrwDt2qzfccO6YDfPHK3BBCQ,8922
|
||||
openpyxl/cell/read_only.py,sha256=ApXkofmUK5QISsuTgZvmZKsU8PufSQtqe2xmYWTgLnc,3097
|
||||
openpyxl/cell/rich_text.py,sha256=uAZmGB7bYDUnanHI0vJmKbfSF8riuIYS5CwlVU_3_fM,5628
|
||||
openpyxl/cell/text.py,sha256=acU6BZQNSmVx4bBXPgFavoxmfoPbVYrm_ztp1bGeOmc,4367
|
||||
openpyxl/chart/_3d.py,sha256=Sdm0TNpXHXNoOLUwiOSccv7yFwrel_-rjQhkrDqAAF4,3104
|
||||
openpyxl/chart/__init__.py,sha256=ag4YCN1B3JH0lkS7tiiZCohVAA51x_pejGdAMuxaI1Y,564
|
||||
openpyxl/chart/_chart.py,sha256=j5xn6mQYmZ4E7y2V1Xvx1jwhX2_O68Mp-8zeXRteS7E,5746
|
||||
openpyxl/chart/area_chart.py,sha256=uROD3fdus6yD1TGu87j4z7KtOEH7tI-3Z5NFK73wwgw,2890
|
||||
openpyxl/chart/axis.py,sha256=yommy5q2mQWKmmLRouWBpimiBZDBM1K-UKAIwCwKDNc,12580
|
||||
openpyxl/chart/bar_chart.py,sha256=_TQHleMT3gSa6B1BkKD_FkLFcv8LRaoiHbpy2yflLO4,4142
|
||||
openpyxl/chart/bubble_chart.py,sha256=KL7VZYFyLDpA8MC-IFtRAUIN262xK6MzjU41DrSVgpY,2004
|
||||
openpyxl/chart/chartspace.py,sha256=PuPGBsVbpK5JagbB7SWgp4JwdQtTrZzIm8mf3kfGAuY,6069
|
||||
openpyxl/chart/data_source.py,sha256=GAuWoCOJ4k7RZNJZkZck0zt_-D5UfDEwqwQ3ND4-s34,5782
|
||||
openpyxl/chart/descriptors.py,sha256=uj-qptwKOBeg7U5xBN4QJQ2OwQvFQ7o4n5eMXXIWS7M,736
|
||||
openpyxl/chart/error_bar.py,sha256=GS_L7PiyKNnJVHvQqG2hLxEW237igLLCatCNC-xGMxk,1832
|
||||
openpyxl/chart/label.py,sha256=IjvI-CZjTY8ydoUzUOihcbxoRWiSpFb_ipD6C2I8Pu4,4133
|
||||
openpyxl/chart/layout.py,sha256=QHakp_CIcoNuvjyZMsQ2p_qP44DIQs4aquy7yln94JM,2040
|
||||
openpyxl/chart/legend.py,sha256=iPMycOhYDAVYd05OU_QDB-GSavdw_1L9CMuJIETOoGI,2040
|
||||
openpyxl/chart/line_chart.py,sha256=6tAyDCzFiuiBFuUDTWhQepH8xVCx2s57lH951cEcwn0,3951
|
||||
openpyxl/chart/marker.py,sha256=kfybMkshK3qefOUW7OX-Os0vfl5OCXfg8MytwHC2i-w,2600
|
||||
openpyxl/chart/picture.py,sha256=Q4eBNQMKQDHR91RnPc7tM-YZVdcnWncedUlfagj67gk,1156
|
||||
openpyxl/chart/pie_chart.py,sha256=UOvkjrBpNd_rT-rvKcpPeVd9dK-ELdMIaHjAUEr6oN8,4793
|
||||
openpyxl/chart/pivot.py,sha256=9kVDmnxnR0uQRQ-Wbl6qw8eew9LGhqomaDBaXqQGZY4,1741
|
||||
openpyxl/chart/plotarea.py,sha256=em7yorXFz9SmJruqOR4Pn-2oEj0Su4rnzyNc5e0IZ_U,5805
|
||||
openpyxl/chart/print_settings.py,sha256=UwB6Kn6xkLRBejXScl-utF8dkNhV7Lm3Lfk7ACpbRgs,1454
|
||||
openpyxl/chart/radar_chart.py,sha256=93I1Y1dmXZ6Y0F1VKXz9I3x1ufgwygBOdbPZumR5n3s,1521
|
||||
openpyxl/chart/reader.py,sha256=oQD-29oxSLW2yzXdyXNhzQYNXgM64Y3kVSOIkrPZCuU,802
|
||||
openpyxl/chart/reference.py,sha256=N3T4qYMH9BVrtbDRiKIZz-qGvPAdfquWTGL0XKxD9G8,3098
|
||||
openpyxl/chart/scatter_chart.py,sha256=JMU32jjxTj7txPJ2TebBHPS5UcMsRHVqLz_psnN2YZs,1563
|
||||
openpyxl/chart/series.py,sha256=k8eR8cviH9EPllRjjr_2a-lH5S3_HWBTLyE7XKghzWc,5896
|
||||
openpyxl/chart/series_factory.py,sha256=ey1zgNwM1g4bQwB9lLhM6E-ctLIM2kLWM3X7CPw8SDs,1368
|
||||
openpyxl/chart/shapes.py,sha256=JkgMy3DUWDKLV6JZHKb_pUBvWpzTAQ3biUMr-1fJWZU,2815
|
||||
openpyxl/chart/stock_chart.py,sha256=YJ7eElBX5omHziKo41ygTA7F_NEkyIlFUfdDJXZuKhM,1604
|
||||
openpyxl/chart/surface_chart.py,sha256=_-yGEX-Ou2NJVmJCA_K_bSLyzk-RvbPupyQLmjfCWj0,2914
|
||||
openpyxl/chart/text.py,sha256=voJCf4PK5olmX0g_5u9aQo8B5LpCUlOeq4j4pnOy_A0,1847
|
||||
openpyxl/chart/title.py,sha256=L-7KxwcpMb2aZk4ikgMsIgFPVtBafIppx9ykd5FPJ4w,1952
|
||||
openpyxl/chart/trendline.py,sha256=9pWSJa9Adwtd6v_i7dPT7qNKzhOrSMWZ4QuAOntZWVg,3045
|
||||
openpyxl/chart/updown_bars.py,sha256=QA4lyEMtMVvZCrYUpHZYMVS1xsnaN4_T5UBi6E7ilQ0,897
|
||||
openpyxl/chartsheet/__init__.py,sha256=3Ony1WNbxxWuddTW-peuUPvO3xqIWFWe3Da2OUzsVnI,71
|
||||
openpyxl/chartsheet/chartsheet.py,sha256=GTXNfQPYBaS4B7XB4f7gDkAo2kCjtZqidl6iDxp-JQ8,3911
|
||||
openpyxl/chartsheet/custom.py,sha256=qVgeCzT7t1tN_pDwaLqtR3ubuPDLeTR5KKlcxwnTWa8,1691
|
||||
openpyxl/chartsheet/properties.py,sha256=dR1nrp22FsPkyDrwQaZV7t-p-Z2Jc88Y2IhIGbBvFhk,679
|
||||
openpyxl/chartsheet/protection.py,sha256=eJixEBmdoTDO2_0h6g51sdSdfSdCaP8UUNsbEqHds6U,1265
|
||||
openpyxl/chartsheet/publish.py,sha256=PrwqsUKn2SK67ZM3NEGT9FH4nOKC1cOxxm3322hHawQ,1587
|
||||
openpyxl/chartsheet/relation.py,sha256=ZAAfEZb639ve0k6ByRwmHdjBrjqVC0bHOLgIcBwRx6o,2731
|
||||
openpyxl/chartsheet/views.py,sha256=My3Au-DEAcC4lwBARhrCcwsN7Lp9H6cFQT-SiAcJlko,1341
|
||||
openpyxl/comments/__init__.py,sha256=k_QJ-OPRme8HgAYQlyxbbRhmS1n2FyowqIeekBW-7vw,67
|
||||
openpyxl/comments/author.py,sha256=PZB_fjQqiEm8BdHDblbfzB0gzkFvECWq5i1jSHeJZco,388
|
||||
openpyxl/comments/comment_sheet.py,sha256=Uv2RPpIxrikDPHBr5Yj1dDkusZB97yVE-NQTM0-EnBk,5753
|
||||
openpyxl/comments/comments.py,sha256=CxurAWM7WbCdbeya-DQklbiWSFaxhtrUNBZEzulTyxc,1466
|
||||
openpyxl/comments/shape_writer.py,sha256=Ls1d0SscfxGM9H2spjxMNHeJSaZJuLawlXs4t4qH7v4,3809
|
||||
openpyxl/compat/__init__.py,sha256=fltF__CdGK97l2V3MtIDxbwgV_p1AZvLdyqcEtXKsqs,1592
|
||||
openpyxl/compat/abc.py,sha256=Y-L6pozzgjr81OfXsjDkGDeKEq6BOfMr6nvrFps_o6Q,155
|
||||
openpyxl/compat/numbers.py,sha256=2dckE0PHT7eB89Sc2BdlWOH4ZLXWt3_eo73-CzRujUY,1617
|
||||
openpyxl/compat/product.py,sha256=-bDgNMHGDgbahgw0jqale8TeIARLw7HO0soQAL9b_4k,264
|
||||
openpyxl/compat/singleton.py,sha256=R1HiH7XpjaW4kr3GILWMc4hRGZkXyc0yK7T1jcg_QWg,1023
|
||||
openpyxl/compat/strings.py,sha256=D_TWf8QnMH6WMx6xuCDfXl0boc1k9q7j8hGalVQ2RUk,604
|
||||
openpyxl/descriptors/__init__.py,sha256=eISTR0Sa1ZKKNQPxMZtqlE39JugYzkjxiZf7u9fttiw,1952
|
||||
openpyxl/descriptors/base.py,sha256=-CuNfswEGazgOoX3GuM2Bs2zkBImT992TvR2R1xsnXM,7135
|
||||
openpyxl/descriptors/container.py,sha256=IcO91M02hR0vXZtWGurz0IH1Vi2PoEECP1PEbz62FJQ,889
|
||||
openpyxl/descriptors/excel.py,sha256=d6a6mtoZ-33jwMGlgvNTL54cqLANKyhMihG6887j8r0,2412
|
||||
openpyxl/descriptors/namespace.py,sha256=LjI4e9R09NSbClr_ewv0YmHgWY8RO5xq1s-SpAvz2wo,313
|
||||
openpyxl/descriptors/nested.py,sha256=5LSsf2uvTKsrGEEQF1KVXMLHZFoRgmLfL_lzW0lWQjI,2603
|
||||
openpyxl/descriptors/sequence.py,sha256=OqF34K_nUC46XD5B_6xzGHeEICz_82hkFkNFXpBkSSE,3490
|
||||
openpyxl/descriptors/serialisable.py,sha256=U_7wMEGQRIOiimUUL4AbdOiWMc_aLyKeaRnj_Z7dVO8,7361
|
||||
openpyxl/descriptors/slots.py,sha256=xNj5vLWWoounpYqbP2JDnnhlTiTLRn-uTfQxncpFfn0,824
|
||||
openpyxl/drawing/__init__.py,sha256=xlXVaT3Fs9ltvbbRIGTSRow9kw9nhLY3Zj1Mm6vXRHE,66
|
||||
openpyxl/drawing/colors.py,sha256=d92d6LQv2xi4xVt0F6bEJz-kpe4ahghNsOIY0_cxgQI,15251
|
||||
openpyxl/drawing/connector.py,sha256=4be6kFwDmixqYX6ko22JE3cqJ9xUM7lRonSer1BDVgY,3863
|
||||
openpyxl/drawing/drawing.py,sha256=Wbv24TZbNaPngDR3adOj6jUBg-iyMYyfvgEPg-5IPu8,2339
|
||||
openpyxl/drawing/effect.py,sha256=vZ5r9k3JfyaAoBggFzN9wyvsEDnMnAmkQZsdVQN1-wo,9435
|
||||
openpyxl/drawing/fill.py,sha256=Z_kAY5bncgu1WkZNvgjiX5ucrYI6GLXyUi6H3_mne2k,13092
|
||||
openpyxl/drawing/geometry.py,sha256=0UM5hMHYy_R3C-lHt5x3NECDn7O1tfbKu5BweLwdLlg,17523
|
||||
openpyxl/drawing/graphic.py,sha256=013KhmTqp1PFKht9lRRA6SHjznxq9EL4u_ybA88OuCk,4811
|
||||
openpyxl/drawing/image.py,sha256=ROO0YJjzH9eqjPUKU5bMtt4bXnHFK9uofDa2__R3G2k,1455
|
||||
openpyxl/drawing/line.py,sha256=CRxV0NUpce4RfXPDllodcneoHk8vr2Ind8HaWnUv2HE,3904
|
||||
openpyxl/drawing/picture.py,sha256=tDYob2x4encQ9rUWOe29PqtiRSDEj746j-SvQ7rVV10,4205
|
||||
openpyxl/drawing/properties.py,sha256=TyLOF3ehp38XJvuupNZdsOqZ0HNXkVPBDYwU5O1GhBM,4948
|
||||
openpyxl/drawing/relation.py,sha256=InbM75ymWUjICXhjyCcYqp1FWcfCFp9q9vecYLptzk4,344
|
||||
openpyxl/drawing/spreadsheet_drawing.py,sha256=CUWSpIYWOHUEp-USOAGVNlLfXBQObcGdg_RZ_bggPYM,10721
|
||||
openpyxl/drawing/text.py,sha256=6_ShIu9FLG7MJvMLs_G_tTatTaBqxpaX5KMKxSfTY7Y,22421
|
||||
openpyxl/drawing/xdr.py,sha256=XE2yRzlCqoJBWg3TPRxelzZ4GmBV9dDFTtiJgJZku-U,626
|
||||
openpyxl/formatting/__init__.py,sha256=vpkL3EimMa-moJjcWk4l3bIWdJ3c7a8pKOfGlnPte9c,59
|
||||
openpyxl/formatting/formatting.py,sha256=AdXlrhic4CPvyJ300oFJPJUH-2vS0VNOLiNudt3U26c,2701
|
||||
openpyxl/formatting/rule.py,sha256=96Fc5-hSByCrvkC1O0agEoZyL9G_AdeulrjRXnf_rZ8,9288
|
||||
openpyxl/formula/__init__.py,sha256=AgvEdunVryhzwecuFVO2EezdJT3h5gCXpw2j3f5VUWA,69
|
||||
openpyxl/formula/tokenizer.py,sha256=o1jDAOl79YiCWr-2LmSICyAbhm2hdb-37jriasmv4dc,15088
|
||||
openpyxl/formula/translate.py,sha256=Zs9adqfZTAuo8J_QNbqK3vjQDlSFhWc0vWc6TCMDYrI,6653
|
||||
openpyxl/packaging/__init__.py,sha256=KcNtO2zoYizOgG-iZzayZffSL1WeZR98i1Q8QYTRhfI,90
|
||||
openpyxl/packaging/core.py,sha256=OSbSFGZrKYcZszcHe3LhQEyiAf2Wylwxm4_6N8WO-Yo,4061
|
||||
openpyxl/packaging/custom.py,sha256=uCEl7IwITFX2pOxiAITnvNbfsav80uHB0wXUFvjIRUQ,6738
|
||||
openpyxl/packaging/extended.py,sha256=JFksxDd67rA57n-vxg48tbeZh2g2LEOb0fgJLeqbTWM,4810
|
||||
openpyxl/packaging/interface.py,sha256=vlGVt4YvyUR4UX9Tr9xmkn1G8s_ynYVtAx4okJ6-g_8,920
|
||||
openpyxl/packaging/manifest.py,sha256=y5zoDQnhJ1aW_HPLItY_WE94fSLS4jxvfIqn_J2zJ6Q,5366
|
||||
openpyxl/packaging/relationship.py,sha256=jLhvFvDVZBRTZTXokRrrsEiLI9CmFlulhGzA_OYKM0Q,3974
|
||||
openpyxl/packaging/workbook.py,sha256=s4jl4gqqMkaUHmMAR52dc9ZoNTieuXcq1OG3cgNDYjw,6495
|
||||
openpyxl/pivot/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35
|
||||
openpyxl/pivot/cache.py,sha256=kKQMEcoYb9scl_CNNWfmNOTewD5S3hpBGwViMtDCyx0,27840
|
||||
openpyxl/pivot/fields.py,sha256=0CQLdTOBhYAa9gfEZb_bvkgCx8feASYp64dqFskDkqU,7057
|
||||
openpyxl/pivot/record.py,sha256=c45ft1YsPAVRneMVh_WvUQ1nZt9RJQ_josRuolKx3qE,2671
|
||||
openpyxl/pivot/table.py,sha256=riKBeb1aICXWipnhpSaSx9iqP-AkfcyOSm3Dfl407dA,40756
|
||||
openpyxl/reader/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35
|
||||
openpyxl/reader/drawings.py,sha256=iZPok8Dc_mZMyRPk_EfDXDQvZdwfHwbYjvxfK2cXtag,2209
|
||||
openpyxl/reader/excel.py,sha256=kgStQtO1j0vV56GWaXxo3GA2EXuouGtnFrRVMocq8EY,12357
|
||||
openpyxl/reader/strings.py,sha256=oG2Mq6eBD0-ElFOxPdHTBUmshUxTNrK1sns1UJRaVis,1113
|
||||
openpyxl/reader/workbook.py,sha256=4w0LRV7qNNGHDnYd19zUgWnJOEX8tHjm3vlkxwllzv4,4352
|
||||
openpyxl/styles/__init__.py,sha256=2QNNdlz4CjhnkBQVNhZ-12Yz73_uHIinqRKWo_TjNwg,363
|
||||
openpyxl/styles/alignment.py,sha256=wQOEtmYhPJFtnuBq0juMe5EsCp9DNSVS1ieBhlAnwWE,2198
|
||||
openpyxl/styles/borders.py,sha256=BLUTOyBbxWQzv8Kuh1u4sWfJiIPJc8QExb7nGwdSmXc,3302
|
||||
openpyxl/styles/builtins.py,sha256=cMtJverVSjdIdCckP6L-AlI0OLMRPgbQwaJWUkldA0U,31182
|
||||
openpyxl/styles/cell_style.py,sha256=8Ol5F6ktKeSqhDVF-10w5eIh7W-jkzijpPPHqqv1qDs,5414
|
||||
openpyxl/styles/colors.py,sha256=Ss3QqNS5YISVkJxlNfd4q_YSrFKdKjATWLDSu2rPMBc,4612
|
||||
openpyxl/styles/differential.py,sha256=dqEGny_ou1jC3tegBal1w_UbONyQEJXvGPURs8xWwfU,2267
|
||||
openpyxl/styles/fills.py,sha256=LmR4H00GzKDWyYjzDEayzKZN28S_muD65DvAFWlbaCI,6380
|
||||
openpyxl/styles/fonts.py,sha256=nkeiJUgKYnWaETvn51sOo9zQXJiOEJKHDTqvxt0JiBc,3516
|
||||
openpyxl/styles/named_styles.py,sha256=nfL1KPpd6b0Y0qBrGJQ15EUOebfeO1eZBQhPVpcZW-o,7254
|
||||
openpyxl/styles/numbers.py,sha256=6kK7mdBD-0xs7bjYDFNGsUAvoFvRu5wSMjOF9J5j-Go,5097
|
||||
openpyxl/styles/protection.py,sha256=BUHgARq7SjOVfW_ST53hKCUofVBEWXn3Lnn_c5n4i_I,394
|
||||
openpyxl/styles/proxy.py,sha256=ajsvzRp_MOeV_rZSEfVoti6-3tW8aowo5_Hjwp2AlfA,1432
|
||||
openpyxl/styles/styleable.py,sha256=Yl_-oPljEuFzg9tXKSSCuvWRL4L0HC5bHMFJVhex6Oc,4499
|
||||
openpyxl/styles/stylesheet.py,sha256=7kZpzyavLrOJcdZqZzl3WZTyM60CqWP8i_OQ0J_1xy0,8790
|
||||
openpyxl/styles/table.py,sha256=VexRqPPQmjRzWe1rVTOgyOQgvlCBuEYTif5MEV_0qsk,2801
|
||||
openpyxl/utils/__init__.py,sha256=wCMNXgIoA4aF4tpSuSzxm1k3SmJJGOEjtdbqdJZZG7I,324
|
||||
openpyxl/utils/bound_dictionary.py,sha256=zfzflQom1FqfEw8uexBqI8eExCeAWELzSk4TqqpD-w8,717
|
||||
openpyxl/utils/cell.py,sha256=P7og4c4JcSN__amIsubIMgSMlQ4SrAA5eZ0cjkoXlaQ,6967
|
||||
openpyxl/utils/dataframe.py,sha256=d3SPeb4p9YKFwlFTUWhdVUYYyMLNrd9atC6iSf2QB6w,2957
|
||||
openpyxl/utils/datetime.py,sha256=xQ8zHJFb-n4nlN6fA_fFZKHlHeNOB7El48p9-YOPvGE,4529
|
||||
openpyxl/utils/escape.py,sha256=4dgcSlSdPNk0vkJNHRUK9poEe8pn4sBIQ5Rjz-7H1Uk,790
|
||||
openpyxl/utils/exceptions.py,sha256=WT40gTyd9YUhg1MeqZNzHp9qJnL5eXzbCEb_VtHp3Kk,889
|
||||
openpyxl/utils/formulas.py,sha256=-I0zyvicBZMaAH1XzsmmEEzE4GB-NA605aArWVt9ik4,4248
|
||||
openpyxl/utils/indexed_list.py,sha256=hBsQP9gunTit7iKdMGw_tM3y5uIpXDjUx7jswbQF6Dc,1257
|
||||
openpyxl/utils/inference.py,sha256=dM1FBW_Rx_xE7P8vGo6WNhbBe-2eqpGuJj4eqdS7UjE,1583
|
||||
openpyxl/utils/protection.py,sha256=opm7GVM2ePQvpNzKT-W56u-0yP8liS9WJkxpzpG_tE0,830
|
||||
openpyxl/utils/units.py,sha256=eGpGrdzyoKlqLs99eALNC5c1gSLXRo4GdUNAqdB4wzg,2642
|
||||
openpyxl/workbook/__init__.py,sha256=yKMikN8VqoVZJcoZSVW3p9Smt88ibeqNq9NHhGBJqEM,68
|
||||
openpyxl/workbook/_writer.py,sha256=pB4s05erNEBJFT_w5LT-2DlxqXkZLOutXWVgewRLVds,6506
|
||||
openpyxl/workbook/child.py,sha256=r_5V9DNkGSYZhzi62P10ZnsO5iT518YopcTdmSvtAUc,4052
|
||||
openpyxl/workbook/defined_name.py,sha256=EAF1WvGYU4WG7dusDi29yBAr15BhkYtkF_GrFym1DDY,5394
|
||||
openpyxl/workbook/external_reference.py,sha256=9bKX9_QgNJxv7fEUd0G-ocXyZajMAsDzG11d0miguxY,348
|
||||
openpyxl/workbook/function_group.py,sha256=x5QfUpFdsjtbFbAJzZof7SrZ376nufNY92mpCcaSPiQ,803
|
||||
openpyxl/workbook/properties.py,sha256=vMUriu67iQU11xIos37ayv73gjq1kdHgI27ncJ3Vk24,5261
|
||||
openpyxl/workbook/protection.py,sha256=LhiyuoOchdrun9xMwq_pxGzbkysziThfKivk0dHHOLw,6008
|
||||
openpyxl/workbook/smart_tags.py,sha256=xHHXCrUPnHeRoM_eakrCOz-eCpct3Y7xKHShr9wGv7s,1181
|
||||
openpyxl/workbook/views.py,sha256=uwQqZCrRavAoBDLZIBtgz7riOEhEaHplybV4cX_TMgY,5214
|
||||
openpyxl/workbook/web.py,sha256=87B5mEZ6vfHTwywcGtcYL6u7D3RyJVDCJxV0nHZeS-w,2642
|
||||
openpyxl/workbook/workbook.py,sha256=oaErvSH1qUphUAPOZTCHj2UHyKeDqsj2DycKCDcgo7M,13232
|
||||
openpyxl/workbook/external_link/__init__.py,sha256=YOkLI226nyopB6moShzGIfBRckdQgPiFXjVZwXW-DpE,71
|
||||
openpyxl/workbook/external_link/external.py,sha256=LXXuej0-d0iRnwlJ-13S81kbuDxvhAWo3qfnxpsClvM,4509
|
||||
openpyxl/worksheet/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35
|
||||
openpyxl/worksheet/_read_only.py,sha256=6Kd4Q-73UoJDY66skRJy_ks-wCHNttlGhsDxvB99PuY,5709
|
||||
openpyxl/worksheet/_reader.py,sha256=vp_D7w4DiADMdyNrYpQglrCVvVLT9_DsSZikOd--n2c,16375
|
||||
openpyxl/worksheet/_write_only.py,sha256=yqW-DtBDDYTwGCBHRVIwkheSB7SSLO3xlw-RsXtPorE,4232
|
||||
openpyxl/worksheet/_writer.py,sha256=bDtw6BV5tdztARQEkQPprExRr8hZVFkj0DyolqxVu2k,10283
|
||||
openpyxl/worksheet/cell_range.py,sha256=YP8AUnqUFP5wOV_avMDFRSZ0Qi2p78RWFuwyyCua7m8,15013
|
||||
openpyxl/worksheet/cell_watch.py,sha256=LdxGcTmXbZ4sxm6inasFgZPld1ijdL5_ODSUvvz13DU,608
|
||||
openpyxl/worksheet/controls.py,sha256=FPLg4N94T-IL27NLg8Le_U4WYDT_6Aa25LDG_kiEDVA,2735
|
||||
openpyxl/worksheet/copier.py,sha256=0Di1qSks0g7Jtgmpc_M20O-KPCW81Yr2myC5j458nyU,2319
|
||||
openpyxl/worksheet/custom.py,sha256=CRlQ98GwqqKmEDkv8gPUCa0ApNM2Vz-BLs_-RMu3jLA,639
|
||||
openpyxl/worksheet/datavalidation.py,sha256=m-O7NOoTDr_bAfxB9xEeY5QttFiuPtzs-IFAlF0j4FE,6131
|
||||
openpyxl/worksheet/dimensions.py,sha256=HzM77FrYixiQDCugRT-C9ZpKq7GNFaGchxT73U4cisY,9102
|
||||
openpyxl/worksheet/drawing.py,sha256=2nfrLyTX0kAizPIINF12KwDW9mRnaq8hs-NrSBcWpmE,275
|
||||
openpyxl/worksheet/errors.py,sha256=KkFC4bnckvCp74XsVXA7JUCi4MIimEFu3uAddcQpjo0,2435
|
||||
openpyxl/worksheet/filters.py,sha256=8eUj2LuP8Qbz1R1gkK1c6W_UKS8-__6XlFMVkunIua0,13854
|
||||
openpyxl/worksheet/formula.py,sha256=5yuul6s1l-K_78KXHC6HrF_pLhxypoldh5jMg7zmlyY,1045
|
||||
openpyxl/worksheet/header_footer.py,sha256=91F6NUDUEwrhgeWrxG9XtDPyPD03XAtGU_ONBpkAfUc,7886
|
||||
openpyxl/worksheet/hyperlink.py,sha256=sXzPkkjl9BWNzCxwwEEaSS53J37jIXPmnnED-j8MIBo,1103
|
||||
openpyxl/worksheet/merge.py,sha256=gNOIH6EJ8wVcJpibAv4CMc7UpD7_DrGvgaCSvG2im5A,4125
|
||||
openpyxl/worksheet/ole.py,sha256=khVvqMt4GPc9Yr6whLDfkUo51euyLXfJe1p4zFee4no,3530
|
||||
openpyxl/worksheet/page.py,sha256=4jeSRcDE0S2RPzIAmA3Bh-uXRyq0hnbO5h5pJdGHbbQ,4901
|
||||
openpyxl/worksheet/pagebreak.py,sha256=XXFIMOY4VdPQCd86nGPghA6hOfLGK5G_KFuvjBNPRsw,1811
|
||||
openpyxl/worksheet/picture.py,sha256=72TctCxzk2JU8uFfjiEbTBufEe5eQxIieSPBRhU6m1Q,185
|
||||
openpyxl/worksheet/print_settings.py,sha256=k_g4fkrs9bfz-S-RIKIBGqzVgubufMdryWQ3ejXQoRI,5215
|
||||
openpyxl/worksheet/properties.py,sha256=9iXTOVC8B9C-2pp_iU5l0r5Fjf3Uzv0SIOUKRrZ2hw4,3087
|
||||
openpyxl/worksheet/protection.py,sha256=vj5M6WWC5xKiHeWS_tJqXxrlOJHJ7GpW2JdPw7r9jjE,3758
|
||||
openpyxl/worksheet/related.py,sha256=ZLDpgcrW6DWl8vvh2sSVB_r1JyG8bC8EicCBKjfssTs,335
|
||||
openpyxl/worksheet/scenario.py,sha256=VlJW4pi1OTy1cJ9m7ZxazIy8PSlo17BGpnUYixmNotQ,2401
|
||||
openpyxl/worksheet/smart_tag.py,sha256=nLbt04IqeJllk7TmNS1eTNdb7On5jMf3llfyy3otDSk,1608
|
||||
openpyxl/worksheet/table.py,sha256=gjt-jNP8dhVy8w5g-gMJpfHO-eV1EoxJy91yi-5HG64,11671
|
||||
openpyxl/worksheet/views.py,sha256=DkZcptwpbpklHILSlvK-a2LmJ7BWb1wbDcz2JVl7404,4974
|
||||
openpyxl/worksheet/worksheet.py,sha256=4JM5qjoJumtcqftHFkimtFEQrz7E2DBmXnkVo7R3WX8,27572
|
||||
openpyxl/writer/__init__.py,sha256=c12-9kMPWlUdjwSoZPsFpmeW8KVXH0HCGpO3dlCTVqI,35
|
||||
openpyxl/writer/excel.py,sha256=6ioXn3hSHHIUnkW2wCyBgPA4CncO6FXL5yGSAzsqp6Y,9572
|
||||
openpyxl/writer/theme.py,sha256=5Hhq-0uP55sf_Zhw7i3M9azCfCjALQxoo7CV_9QPmTA,10320
|
||||
openpyxl/xml/__init__.py,sha256=A5Kj0GWk5XI-zJxbAL5vIppV_AgEHLRveGu8RK5c7U0,1016
|
||||
openpyxl/xml/constants.py,sha256=HDNnhcj-WO9ayO4Mqwca3Au0ZTNfsDqWDtleREs_Wto,4833
|
||||
openpyxl/xml/functions.py,sha256=jBtfa8__w4gBlEPGHLGCAtJiaNKPyihTLsfmigyq2_Q,2025
|
||||
openpyxl-3.1.5.dist-info/LICENCE.rst,sha256=DIS7QvXTZ-Xr-fwt3jWxYUHfXuD9wYklCFi8bFVg9p4,1131
|
||||
openpyxl-3.1.5.dist-info/METADATA,sha256=I_gMqYMN2JQ12hcQ8m3tqPgeVAkofnRUAhDHJiekrZY,2510
|
||||
openpyxl-3.1.5.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
||||
openpyxl-3.1.5.dist-info/top_level.txt,sha256=mKJO5QFAsUEDtJ_c97F-IbmVtHYEDymqD7d5X0ULkVs,9
|
||||
openpyxl-3.1.5.dist-info/RECORD,,
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.43.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
openpyxl
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
DEBUG = False
|
||||
|
||||
from openpyxl.compat.numbers import NUMPY
|
||||
from openpyxl.xml import DEFUSEDXML, LXML
|
||||
from openpyxl.workbook import Workbook
|
||||
from openpyxl.reader.excel import load_workbook as open
|
||||
from openpyxl.reader.excel import load_workbook
|
||||
import openpyxl._constants as constants
|
||||
|
||||
# Expose constants especially the version number
|
||||
|
||||
__author__ = constants.__author__
|
||||
__author_email__ = constants.__author_email__
|
||||
__license__ = constants.__license__
|
||||
__maintainer_email__ = constants.__maintainer_email__
|
||||
__url__ = constants.__url__
|
||||
__version__ = constants.__version__
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Package metadata
|
||||
"""
|
||||
|
||||
__author__ = "See AUTHORS"
|
||||
__author_email__ = "charlie.clark@clark-consulting.eu"
|
||||
__license__ = "MIT"
|
||||
__maintainer_email__ = "openpyxl-users@googlegroups.com"
|
||||
__url__ = "https://openpyxl.readthedocs.io"
|
||||
__version__ = "3.1.5"
|
||||
__python__ = "3.8"
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .cell import Cell, WriteOnlyCell, MergedCell
|
||||
from .read_only import ReadOnlyCell
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS
|
||||
from openpyxl import LXML
|
||||
from openpyxl.utils.datetime import to_excel, to_ISO8601
|
||||
from datetime import timedelta
|
||||
|
||||
from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula
|
||||
from openpyxl.cell.rich_text import CellRichText
|
||||
|
||||
def _set_attributes(cell, styled=None):
|
||||
"""
|
||||
Set coordinate and datatype
|
||||
"""
|
||||
coordinate = cell.coordinate
|
||||
attrs = {'r': coordinate}
|
||||
if styled:
|
||||
attrs['s'] = f"{cell.style_id}"
|
||||
|
||||
if cell.data_type == "s":
|
||||
attrs['t'] = "inlineStr"
|
||||
elif cell.data_type != 'f':
|
||||
attrs['t'] = cell.data_type
|
||||
|
||||
value = cell._value
|
||||
|
||||
if cell.data_type == "d":
|
||||
if hasattr(value, "tzinfo") and value.tzinfo is not None:
|
||||
raise TypeError("Excel does not support timezones in datetimes. "
|
||||
"The tzinfo in the datetime/time object must be set to None.")
|
||||
|
||||
if cell.parent.parent.iso_dates and not isinstance(value, timedelta):
|
||||
value = to_ISO8601(value)
|
||||
else:
|
||||
attrs['t'] = "n"
|
||||
value = to_excel(value, cell.parent.parent.epoch)
|
||||
|
||||
if cell.hyperlink:
|
||||
cell.parent._hyperlinks.append(cell.hyperlink)
|
||||
|
||||
return value, attrs
|
||||
|
||||
|
||||
def etree_write_cell(xf, worksheet, cell, styled=None):
|
||||
|
||||
value, attributes = _set_attributes(cell, styled)
|
||||
|
||||
el = Element("c", attributes)
|
||||
if value is None or value == "":
|
||||
xf.write(el)
|
||||
return
|
||||
|
||||
if cell.data_type == 'f':
|
||||
attrib = {}
|
||||
|
||||
if isinstance(value, ArrayFormula):
|
||||
attrib = dict(value)
|
||||
value = value.text
|
||||
|
||||
elif isinstance(value, DataTableFormula):
|
||||
attrib = dict(value)
|
||||
value = None
|
||||
|
||||
formula = SubElement(el, 'f', attrib)
|
||||
if value is not None and not attrib.get('t') == "dataTable":
|
||||
formula.text = value[1:]
|
||||
value = None
|
||||
|
||||
if cell.data_type == 's':
|
||||
if isinstance(value, CellRichText):
|
||||
el.append(value.to_tree())
|
||||
else:
|
||||
inline_string = Element("is")
|
||||
text = Element('t')
|
||||
text.text = value
|
||||
whitespace(text)
|
||||
inline_string.append(text)
|
||||
el.append(inline_string)
|
||||
|
||||
else:
|
||||
cell_content = SubElement(el, 'v')
|
||||
if value is not None:
|
||||
cell_content.text = safe_string(value)
|
||||
|
||||
xf.write(el)
|
||||
|
||||
|
||||
def lxml_write_cell(xf, worksheet, cell, styled=False):
|
||||
value, attributes = _set_attributes(cell, styled)
|
||||
|
||||
if value == '' or value is None:
|
||||
with xf.element("c", attributes):
|
||||
return
|
||||
|
||||
with xf.element('c', attributes):
|
||||
if cell.data_type == 'f':
|
||||
attrib = {}
|
||||
|
||||
if isinstance(value, ArrayFormula):
|
||||
attrib = dict(value)
|
||||
value = value.text
|
||||
|
||||
elif isinstance(value, DataTableFormula):
|
||||
attrib = dict(value)
|
||||
value = None
|
||||
|
||||
with xf.element('f', attrib):
|
||||
if value is not None and not attrib.get('t') == "dataTable":
|
||||
xf.write(value[1:])
|
||||
value = None
|
||||
|
||||
if cell.data_type == 's':
|
||||
if isinstance(value, CellRichText):
|
||||
el = value.to_tree()
|
||||
xf.write(el)
|
||||
else:
|
||||
with xf.element("is"):
|
||||
if isinstance(value, str):
|
||||
attrs = {}
|
||||
if value != value.strip():
|
||||
attrs["{%s}space" % XML_NS] = "preserve"
|
||||
el = Element("t", attrs) # lxml can't handle xml-ns
|
||||
el.text = value
|
||||
xf.write(el)
|
||||
|
||||
else:
|
||||
with xf.element("v"):
|
||||
if value is not None:
|
||||
xf.write(safe_string(value))
|
||||
|
||||
|
||||
if LXML:
|
||||
write_cell = lxml_write_cell
|
||||
else:
|
||||
write_cell = etree_write_cell
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""Manage individual cells in a spreadsheet.
|
||||
|
||||
The Cell class is required to know its value and type, display options,
|
||||
and any other features of an Excel cell. Utilities for referencing
|
||||
cells using Excel's 'A1' column/row nomenclature are also provided.
|
||||
|
||||
"""
|
||||
|
||||
__docformat__ = "restructuredtext en"
|
||||
|
||||
# Python stdlib imports
|
||||
from copy import copy
|
||||
import datetime
|
||||
import re
|
||||
|
||||
|
||||
from openpyxl.compat import (
|
||||
NUMERIC_TYPES,
|
||||
)
|
||||
|
||||
from openpyxl.utils.exceptions import IllegalCharacterError
|
||||
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.styles import numbers, is_date_format
|
||||
from openpyxl.styles.styleable import StyleableObject
|
||||
from openpyxl.worksheet.hyperlink import Hyperlink
|
||||
from openpyxl.worksheet.formula import DataTableFormula, ArrayFormula
|
||||
from openpyxl.cell.rich_text import CellRichText
|
||||
|
||||
# constants
|
||||
|
||||
TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
|
||||
TIME_FORMATS = {
|
||||
datetime.datetime:numbers.FORMAT_DATE_DATETIME,
|
||||
datetime.date:numbers.FORMAT_DATE_YYYYMMDD2,
|
||||
datetime.time:numbers.FORMAT_DATE_TIME6,
|
||||
datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA,
|
||||
}
|
||||
|
||||
STRING_TYPES = (str, bytes, CellRichText)
|
||||
KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None))
|
||||
|
||||
ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
|
||||
ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!',
|
||||
'#N/A')
|
||||
|
||||
TYPE_STRING = 's'
|
||||
TYPE_FORMULA = 'f'
|
||||
TYPE_NUMERIC = 'n'
|
||||
TYPE_BOOL = 'b'
|
||||
TYPE_NULL = 'n'
|
||||
TYPE_INLINE = 'inlineStr'
|
||||
TYPE_ERROR = 'e'
|
||||
TYPE_FORMULA_CACHE_STRING = 'str'
|
||||
|
||||
VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
|
||||
TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING)
|
||||
|
||||
|
||||
_TYPES = {int:'n', float:'n', str:'s', bool:'b'}
|
||||
|
||||
|
||||
def get_type(t, value):
|
||||
if isinstance(value, NUMERIC_TYPES):
|
||||
dt = 'n'
|
||||
elif isinstance(value, STRING_TYPES):
|
||||
dt = 's'
|
||||
elif isinstance(value, TIME_TYPES):
|
||||
dt = 'd'
|
||||
elif isinstance(value, (DataTableFormula, ArrayFormula)):
|
||||
dt = 'f'
|
||||
else:
|
||||
return
|
||||
_TYPES[t] = dt
|
||||
return dt
|
||||
|
||||
|
||||
def get_time_format(t):
|
||||
value = TIME_FORMATS.get(t)
|
||||
if value:
|
||||
return value
|
||||
for base in t.mro()[1:]:
|
||||
value = TIME_FORMATS.get(base)
|
||||
if value:
|
||||
TIME_FORMATS[t] = value
|
||||
return value
|
||||
raise ValueError("Could not get time format for {0!r}".format(value))
|
||||
|
||||
|
||||
class Cell(StyleableObject):
|
||||
"""Describes cell associated properties.
|
||||
|
||||
Properties of interest include style, type, value, and address.
|
||||
|
||||
"""
|
||||
__slots__ = (
|
||||
'row',
|
||||
'column',
|
||||
'_value',
|
||||
'data_type',
|
||||
'parent',
|
||||
'_hyperlink',
|
||||
'_comment',
|
||||
)
|
||||
|
||||
def __init__(self, worksheet, row=None, column=None, value=None, style_array=None):
|
||||
super().__init__(worksheet, style_array)
|
||||
self.row = row
|
||||
"""Row number of this cell (1-based)"""
|
||||
self.column = column
|
||||
"""Column number of this cell (1-based)"""
|
||||
# _value is the stored value, while value is the displayed value
|
||||
self._value = None
|
||||
self._hyperlink = None
|
||||
self.data_type = 'n'
|
||||
if value is not None:
|
||||
self.value = value
|
||||
self._comment = None
|
||||
|
||||
|
||||
@property
|
||||
def coordinate(self):
|
||||
"""This cell's coordinate (ex. 'A5')"""
|
||||
col = get_column_letter(self.column)
|
||||
return f"{col}{self.row}"
|
||||
|
||||
|
||||
@property
|
||||
def col_idx(self):
|
||||
"""The numerical index of the column"""
|
||||
return self.column
|
||||
|
||||
|
||||
@property
|
||||
def column_letter(self):
|
||||
return get_column_letter(self.column)
|
||||
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return self.parent.encoding
|
||||
|
||||
@property
|
||||
def base_date(self):
|
||||
return self.parent.parent.epoch
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<Cell {0!r}.{1}>".format(self.parent.title, self.coordinate)
|
||||
|
||||
def check_string(self, value):
|
||||
"""Check string coding, length, and line break character"""
|
||||
if value is None:
|
||||
return
|
||||
# convert to str string
|
||||
if not isinstance(value, str):
|
||||
value = str(value, self.encoding)
|
||||
value = str(value)
|
||||
# string must never be longer than 32,767 characters
|
||||
# truncate if necessary
|
||||
value = value[:32767]
|
||||
if next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
|
||||
raise IllegalCharacterError(f"{value} cannot be used in worksheets.")
|
||||
return value
|
||||
|
||||
def check_error(self, value):
|
||||
"""Tries to convert Error" else N/A"""
|
||||
try:
|
||||
return str(value)
|
||||
except UnicodeDecodeError:
|
||||
return u'#N/A'
|
||||
|
||||
|
||||
def _bind_value(self, value):
|
||||
"""Given a value, infer the correct data type"""
|
||||
|
||||
self.data_type = "n"
|
||||
t = type(value)
|
||||
try:
|
||||
dt = _TYPES[t]
|
||||
except KeyError:
|
||||
dt = get_type(t, value)
|
||||
|
||||
if dt is None and value is not None:
|
||||
raise ValueError("Cannot convert {0!r} to Excel".format(value))
|
||||
|
||||
if dt:
|
||||
self.data_type = dt
|
||||
|
||||
if dt == 'd':
|
||||
if not is_date_format(self.number_format):
|
||||
self.number_format = get_time_format(t)
|
||||
|
||||
elif dt == "s" and not isinstance(value, CellRichText):
|
||||
value = self.check_string(value)
|
||||
if len(value) > 1 and value.startswith("="):
|
||||
self.data_type = 'f'
|
||||
elif value in ERROR_CODES:
|
||||
self.data_type = 'e'
|
||||
|
||||
self._value = value
|
||||
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""Get or set the value held in the cell.
|
||||
|
||||
:type: depends on the value (string, float, int or
|
||||
:class:`datetime.datetime`)
|
||||
"""
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
"""Set the value and infer type and display options."""
|
||||
self._bind_value(value)
|
||||
|
||||
@property
|
||||
def internal_value(self):
|
||||
"""Always returns the value for excel."""
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def hyperlink(self):
|
||||
"""Return the hyperlink target or an empty string"""
|
||||
return self._hyperlink
|
||||
|
||||
|
||||
@hyperlink.setter
|
||||
def hyperlink(self, val):
|
||||
"""Set value and display for hyperlinks in a cell.
|
||||
Automatically sets the `value` of the cell with link text,
|
||||
but you can modify it afterwards by setting the `value`
|
||||
property, and the hyperlink will remain.
|
||||
Hyperlink is removed if set to ``None``."""
|
||||
if val is None:
|
||||
self._hyperlink = None
|
||||
else:
|
||||
if not isinstance(val, Hyperlink):
|
||||
val = Hyperlink(ref="", target=val)
|
||||
val.ref = self.coordinate
|
||||
self._hyperlink = val
|
||||
if self._value is None:
|
||||
self.value = val.target or val.location
|
||||
|
||||
|
||||
@property
|
||||
def is_date(self):
|
||||
"""True if the value is formatted as a date
|
||||
|
||||
:type: bool
|
||||
"""
|
||||
return self.data_type == 'd' or (
|
||||
self.data_type == 'n' and is_date_format(self.number_format)
|
||||
)
|
||||
|
||||
|
||||
def offset(self, row=0, column=0):
|
||||
"""Returns a cell location relative to this cell.
|
||||
|
||||
:param row: number of rows to offset
|
||||
:type row: int
|
||||
|
||||
:param column: number of columns to offset
|
||||
:type column: int
|
||||
|
||||
:rtype: :class:`openpyxl.cell.Cell`
|
||||
"""
|
||||
offset_column = self.col_idx + column
|
||||
offset_row = self.row + row
|
||||
return self.parent.cell(column=offset_column, row=offset_row)
|
||||
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
""" Returns the comment associated with this cell
|
||||
|
||||
:type: :class:`openpyxl.comments.Comment`
|
||||
"""
|
||||
return self._comment
|
||||
|
||||
|
||||
@comment.setter
|
||||
def comment(self, value):
|
||||
"""
|
||||
Assign a comment to a cell
|
||||
"""
|
||||
|
||||
if value is not None:
|
||||
if value.parent:
|
||||
value = copy(value)
|
||||
value.bind(self)
|
||||
elif value is None and self._comment:
|
||||
self._comment.unbind()
|
||||
self._comment = value
|
||||
|
||||
|
||||
class MergedCell(StyleableObject):
|
||||
|
||||
"""
|
||||
Describes the properties of a cell in a merged cell and helps to
|
||||
display the borders of the merged cell.
|
||||
|
||||
The value of a MergedCell is always None.
|
||||
"""
|
||||
|
||||
__slots__ = ('row', 'column')
|
||||
|
||||
_value = None
|
||||
data_type = "n"
|
||||
comment = None
|
||||
hyperlink = None
|
||||
|
||||
|
||||
def __init__(self, worksheet, row=None, column=None):
|
||||
super().__init__(worksheet)
|
||||
self.row = row
|
||||
self.column = column
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<MergedCell {0!r}.{1}>".format(self.parent.title, self.coordinate)
|
||||
|
||||
coordinate = Cell.coordinate
|
||||
_comment = comment
|
||||
value = _value
|
||||
|
||||
|
||||
def WriteOnlyCell(ws=None, value=None):
|
||||
return Cell(worksheet=ws, column=1, row=1, value=value)
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.cell import Cell
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.utils.datetime import from_excel
|
||||
from openpyxl.styles import is_date_format
|
||||
from openpyxl.styles.numbers import BUILTIN_FORMATS, BUILTIN_FORMATS_MAX_SIZE
|
||||
|
||||
|
||||
class ReadOnlyCell:
|
||||
|
||||
__slots__ = ('parent', 'row', 'column', '_value', 'data_type', '_style_id')
|
||||
|
||||
def __init__(self, sheet, row, column, value, data_type='n', style_id=0):
|
||||
self.parent = sheet
|
||||
self._value = None
|
||||
self.row = row
|
||||
self.column = column
|
||||
self.data_type = data_type
|
||||
self.value = value
|
||||
self._style_id = style_id
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
for a in self.__slots__:
|
||||
if getattr(self, a) != getattr(other, a):
|
||||
return
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<ReadOnlyCell {0!r}.{1}>".format(self.parent.title, self.coordinate)
|
||||
|
||||
|
||||
@property
|
||||
def coordinate(self):
|
||||
column = get_column_letter(self.column)
|
||||
return "{1}{0}".format(self.row, column)
|
||||
|
||||
|
||||
@property
|
||||
def coordinate(self):
|
||||
return Cell.coordinate.__get__(self)
|
||||
|
||||
|
||||
@property
|
||||
def column_letter(self):
|
||||
return Cell.column_letter.__get__(self)
|
||||
|
||||
|
||||
@property
|
||||
def style_array(self):
|
||||
return self.parent.parent._cell_styles[self._style_id]
|
||||
|
||||
|
||||
@property
|
||||
def has_style(self):
|
||||
return self._style_id != 0
|
||||
|
||||
|
||||
@property
|
||||
def number_format(self):
|
||||
_id = self.style_array.numFmtId
|
||||
if _id < BUILTIN_FORMATS_MAX_SIZE:
|
||||
return BUILTIN_FORMATS.get(_id, "General")
|
||||
else:
|
||||
return self.parent.parent._number_formats[
|
||||
_id - BUILTIN_FORMATS_MAX_SIZE]
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
_id = self.style_array.fontId
|
||||
return self.parent.parent._fonts[_id]
|
||||
|
||||
@property
|
||||
def fill(self):
|
||||
_id = self.style_array.fillId
|
||||
return self.parent.parent._fills[_id]
|
||||
|
||||
@property
|
||||
def border(self):
|
||||
_id = self.style_array.borderId
|
||||
return self.parent.parent._borders[_id]
|
||||
|
||||
@property
|
||||
def alignment(self):
|
||||
_id = self.style_array.alignmentId
|
||||
return self.parent.parent._alignments[_id]
|
||||
|
||||
@property
|
||||
def protection(self):
|
||||
_id = self.style_array.protectionId
|
||||
return self.parent.parent._protections[_id]
|
||||
|
||||
|
||||
@property
|
||||
def is_date(self):
|
||||
return Cell.is_date.__get__(self)
|
||||
|
||||
|
||||
@property
|
||||
def internal_value(self):
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
if self._value is not None:
|
||||
raise AttributeError("Cell is read only")
|
||||
self._value = value
|
||||
|
||||
|
||||
class EmptyCell:
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
value = None
|
||||
is_date = False
|
||||
font = None
|
||||
border = None
|
||||
fill = None
|
||||
number_format = None
|
||||
alignment = None
|
||||
data_type = 'n'
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<EmptyCell>"
|
||||
|
||||
EMPTY_CELL = EmptyCell()
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
RichText definition
|
||||
"""
|
||||
from copy import copy
|
||||
from openpyxl.compat import NUMERIC_TYPES
|
||||
from openpyxl.cell.text import InlineFont, Text
|
||||
from openpyxl.descriptors import (
|
||||
Strict,
|
||||
String,
|
||||
Typed
|
||||
)
|
||||
|
||||
from openpyxl.xml.functions import Element, whitespace
|
||||
|
||||
class TextBlock(Strict):
|
||||
""" Represents text string in a specific format
|
||||
|
||||
This class is used as part of constructing a rich text strings.
|
||||
"""
|
||||
font = Typed(expected_type=InlineFont)
|
||||
text = String()
|
||||
|
||||
def __init__(self, font, text):
|
||||
self.font = font
|
||||
self.text = text
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.text == other.text and self.font == other.font
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""Just retun the text"""
|
||||
return self.text
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
font = self.font != InlineFont() and self.font or "default"
|
||||
return f"{self.__class__.__name__} text={self.text}, font={font}"
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
el = Element("r")
|
||||
el.append(self.font.to_tree(tagname="rPr"))
|
||||
t = Element("t")
|
||||
t.text = self.text
|
||||
whitespace(t)
|
||||
el.append(t)
|
||||
return el
|
||||
|
||||
#
|
||||
# Rich Text class.
|
||||
# This class behaves just like a list whose members are either simple strings, or TextBlock() instances.
|
||||
# In addition, it can be initialized in several ways:
|
||||
# t = CellRFichText([...]) # initialize with a list.
|
||||
# t = CellRFichText((...)) # initialize with a tuple.
|
||||
# t = CellRichText(node) # where node is an Element() from either lxml or xml.etree (has a 'tag' element)
|
||||
class CellRichText(list):
|
||||
"""Represents a rich text string.
|
||||
|
||||
Initialize with a list made of pure strings or :class:`TextBlock` elements
|
||||
Can index object to access or modify individual rich text elements
|
||||
it also supports the + and += operators between rich text strings
|
||||
There are no user methods for this class
|
||||
|
||||
operations which modify the string will generally call an optimization pass afterwards,
|
||||
that merges text blocks with identical formats, consecutive pure text strings,
|
||||
and remove empty strings and empty text blocks
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
args = args[0]
|
||||
if isinstance(args, (list, tuple)):
|
||||
CellRichText._check_rich_text(args)
|
||||
else:
|
||||
CellRichText._check_element(args)
|
||||
args = [args]
|
||||
else:
|
||||
CellRichText._check_rich_text(args)
|
||||
super().__init__(args)
|
||||
|
||||
|
||||
@classmethod
|
||||
def _check_element(cls, value):
|
||||
if not isinstance(value, (str, TextBlock, NUMERIC_TYPES)):
|
||||
raise TypeError(f"Illegal CellRichText element {value}")
|
||||
|
||||
|
||||
@classmethod
|
||||
def _check_rich_text(cls, rich_text):
|
||||
for t in rich_text:
|
||||
CellRichText._check_element(t)
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
text = Text.from_tree(node)
|
||||
if text.t:
|
||||
return (text.t.replace('x005F_', ''),)
|
||||
s = []
|
||||
for r in text.r:
|
||||
t = ""
|
||||
if r.t:
|
||||
t = r.t.replace('x005F_', '')
|
||||
if r.rPr:
|
||||
s.append(TextBlock(r.rPr, t))
|
||||
else:
|
||||
s.append(t)
|
||||
return cls(s)
|
||||
|
||||
# Merge TextBlocks with identical formatting
|
||||
# remove empty elements
|
||||
def _opt(self):
|
||||
last_t = None
|
||||
l = CellRichText(tuple())
|
||||
for t in self:
|
||||
if isinstance(t, str):
|
||||
if not t:
|
||||
continue
|
||||
elif not t.text:
|
||||
continue
|
||||
if type(last_t) == type(t):
|
||||
if isinstance(t, str):
|
||||
last_t += t
|
||||
continue
|
||||
elif last_t.font == t.font:
|
||||
last_t.text += t.text
|
||||
continue
|
||||
if last_t:
|
||||
l.append(last_t)
|
||||
last_t = t
|
||||
if last_t:
|
||||
# Add remaining TextBlock at end of rich text
|
||||
l.append(last_t)
|
||||
super().__setitem__(slice(None), l)
|
||||
return self
|
||||
|
||||
|
||||
def __iadd__(self, arg):
|
||||
# copy used here to create new TextBlock() so we don't modify the right hand side in _opt()
|
||||
CellRichText._check_rich_text(arg)
|
||||
super().__iadd__([copy(e) for e in list(arg)])
|
||||
return self._opt()
|
||||
|
||||
|
||||
def __add__(self, arg):
|
||||
return CellRichText([copy(e) for e in list(self) + list(arg)])._opt()
|
||||
|
||||
|
||||
def __setitem__(self, indx, val):
|
||||
CellRichText._check_element(val)
|
||||
super().__setitem__(indx, val)
|
||||
self._opt()
|
||||
|
||||
|
||||
def append(self, arg):
|
||||
CellRichText._check_element(arg)
|
||||
super().append(arg)
|
||||
|
||||
|
||||
def extend(self, arg):
|
||||
CellRichText._check_rich_text(arg)
|
||||
super().extend(arg)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "CellRichText([{}])".format(', '.join((repr(s) for s in self)))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return ''.join([str(s) for s in self])
|
||||
|
||||
|
||||
def as_list(self):
|
||||
"""
|
||||
Returns a list of the strings contained.
|
||||
The main reason for this is to make editing easier.
|
||||
"""
|
||||
return [str(s) for s in self]
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
"""
|
||||
Return the full XML representation
|
||||
"""
|
||||
container = Element("is")
|
||||
for obj in self:
|
||||
if isinstance(obj, TextBlock):
|
||||
container.append(obj.to_tree())
|
||||
|
||||
else:
|
||||
el = Element("r")
|
||||
t = Element("t")
|
||||
t.text = obj
|
||||
whitespace(t)
|
||||
el.append(t)
|
||||
container.append(el)
|
||||
|
||||
return container
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Richtext definition
|
||||
"""
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Typed,
|
||||
Integer,
|
||||
Set,
|
||||
NoneSet,
|
||||
Bool,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedString,
|
||||
NestedText,
|
||||
)
|
||||
from openpyxl.styles.fonts import Font
|
||||
|
||||
|
||||
class PhoneticProperties(Serialisable):
|
||||
|
||||
tagname = "phoneticPr"
|
||||
|
||||
fontId = Integer()
|
||||
type = NoneSet(values=(['halfwidthKatakana', 'fullwidthKatakana',
|
||||
'Hiragana', 'noConversion']))
|
||||
alignment = NoneSet(values=(['noControl', 'left', 'center', 'distributed']))
|
||||
|
||||
def __init__(self,
|
||||
fontId=None,
|
||||
type=None,
|
||||
alignment=None,
|
||||
):
|
||||
self.fontId = fontId
|
||||
self.type = type
|
||||
self.alignment = alignment
|
||||
|
||||
|
||||
class PhoneticText(Serialisable):
|
||||
|
||||
tagname = "rPh"
|
||||
|
||||
sb = Integer()
|
||||
eb = Integer()
|
||||
t = NestedText(expected_type=str)
|
||||
text = Alias('t')
|
||||
|
||||
def __init__(self,
|
||||
sb=None,
|
||||
eb=None,
|
||||
t=None,
|
||||
):
|
||||
self.sb = sb
|
||||
self.eb = eb
|
||||
self.t = t
|
||||
|
||||
|
||||
class InlineFont(Font):
|
||||
|
||||
"""
|
||||
Font for inline text because, yes what you need are different objects with the same elements but different constraints.
|
||||
"""
|
||||
|
||||
tagname = "RPrElt"
|
||||
|
||||
rFont = NestedString(allow_none=True)
|
||||
charset = Font.charset
|
||||
family = Font.family
|
||||
b =Font.b
|
||||
i = Font.i
|
||||
strike = Font.strike
|
||||
outline = Font.outline
|
||||
shadow = Font.shadow
|
||||
condense = Font.condense
|
||||
extend = Font.extend
|
||||
color = Font.color
|
||||
sz = Font.sz
|
||||
u = Font.u
|
||||
vertAlign = Font.vertAlign
|
||||
scheme = Font.scheme
|
||||
|
||||
__elements__ = ('rFont', 'charset', 'family', 'b', 'i', 'strike',
|
||||
'outline', 'shadow', 'condense', 'extend', 'color', 'sz', 'u',
|
||||
'vertAlign', 'scheme')
|
||||
|
||||
def __init__(self,
|
||||
rFont=None,
|
||||
charset=None,
|
||||
family=None,
|
||||
b=None,
|
||||
i=None,
|
||||
strike=None,
|
||||
outline=None,
|
||||
shadow=None,
|
||||
condense=None,
|
||||
extend=None,
|
||||
color=None,
|
||||
sz=None,
|
||||
u=None,
|
||||
vertAlign=None,
|
||||
scheme=None,
|
||||
):
|
||||
self.rFont = rFont
|
||||
self.charset = charset
|
||||
self.family = family
|
||||
self.b = b
|
||||
self.i = i
|
||||
self.strike = strike
|
||||
self.outline = outline
|
||||
self.shadow = shadow
|
||||
self.condense = condense
|
||||
self.extend = extend
|
||||
self.color = color
|
||||
self.sz = sz
|
||||
self.u = u
|
||||
self.vertAlign = vertAlign
|
||||
self.scheme = scheme
|
||||
|
||||
|
||||
class RichText(Serialisable):
|
||||
|
||||
tagname = "RElt"
|
||||
|
||||
rPr = Typed(expected_type=InlineFont, allow_none=True)
|
||||
font = Alias("rPr")
|
||||
t = NestedText(expected_type=str, allow_none=True)
|
||||
text = Alias("t")
|
||||
|
||||
__elements__ = ('rPr', 't')
|
||||
|
||||
def __init__(self,
|
||||
rPr=None,
|
||||
t=None,
|
||||
):
|
||||
self.rPr = rPr
|
||||
self.t = t
|
||||
|
||||
|
||||
class Text(Serialisable):
|
||||
|
||||
tagname = "text"
|
||||
|
||||
t = NestedText(allow_none=True, expected_type=str)
|
||||
plain = Alias("t")
|
||||
r = Sequence(expected_type=RichText, allow_none=True)
|
||||
formatted = Alias("r")
|
||||
rPh = Sequence(expected_type=PhoneticText, allow_none=True)
|
||||
phonetic = Alias("rPh")
|
||||
phoneticPr = Typed(expected_type=PhoneticProperties, allow_none=True)
|
||||
PhoneticProperties = Alias("phoneticPr")
|
||||
|
||||
__elements__ = ('t', 'r', 'rPh', 'phoneticPr')
|
||||
|
||||
def __init__(self,
|
||||
t=None,
|
||||
r=(),
|
||||
rPh=(),
|
||||
phoneticPr=None,
|
||||
):
|
||||
self.t = t
|
||||
self.r = r
|
||||
self.rPh = rPh
|
||||
self.phoneticPr = phoneticPr
|
||||
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""
|
||||
Text stripped of all formatting
|
||||
"""
|
||||
snippets = []
|
||||
if self.plain is not None:
|
||||
snippets.append(self.plain)
|
||||
for block in self.formatted:
|
||||
if block.t is not None:
|
||||
snippets.append(block.t)
|
||||
return u"".join(snippets)
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import Typed, Alias
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedMinMax,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from .marker import PictureOptions
|
||||
from .shapes import GraphicalProperties
|
||||
|
||||
|
||||
class View3D(Serialisable):
|
||||
|
||||
tagname = "view3D"
|
||||
|
||||
rotX = NestedMinMax(min=-90, max=90, allow_none=True)
|
||||
x_rotation = Alias('rotX')
|
||||
hPercent = NestedMinMax(min=5, max=500, allow_none=True)
|
||||
height_percent = Alias('hPercent')
|
||||
rotY = NestedInteger(min=-90, max=90, allow_none=True)
|
||||
y_rotation = Alias('rotY')
|
||||
depthPercent = NestedInteger(allow_none=True)
|
||||
rAngAx = NestedBool(allow_none=True)
|
||||
right_angle_axes = Alias('rAngAx')
|
||||
perspective = NestedInteger(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx',
|
||||
'perspective',)
|
||||
|
||||
def __init__(self,
|
||||
rotX=15,
|
||||
hPercent=None,
|
||||
rotY=20,
|
||||
depthPercent=None,
|
||||
rAngAx=True,
|
||||
perspective=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.rotX = rotX
|
||||
self.hPercent = hPercent
|
||||
self.rotY = rotY
|
||||
self.depthPercent = depthPercent
|
||||
self.rAngAx = rAngAx
|
||||
self.perspective = perspective
|
||||
|
||||
|
||||
class Surface(Serialisable):
|
||||
|
||||
tagname = "surface"
|
||||
|
||||
thickness = NestedInteger(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('thickness', 'spPr', 'pictureOptions',)
|
||||
|
||||
def __init__(self,
|
||||
thickness=None,
|
||||
spPr=None,
|
||||
pictureOptions=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.thickness = thickness
|
||||
self.spPr = spPr
|
||||
self.pictureOptions = pictureOptions
|
||||
|
||||
|
||||
class _3DBase(Serialisable):
|
||||
|
||||
"""
|
||||
Base class for 3D charts
|
||||
"""
|
||||
|
||||
tagname = "ChartBase"
|
||||
|
||||
view3D = Typed(expected_type=View3D, allow_none=True)
|
||||
floor = Typed(expected_type=Surface, allow_none=True)
|
||||
sideWall = Typed(expected_type=Surface, allow_none=True)
|
||||
backWall = Typed(expected_type=Surface, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
view3D=None,
|
||||
floor=None,
|
||||
sideWall=None,
|
||||
backWall=None,
|
||||
):
|
||||
if view3D is None:
|
||||
view3D = View3D()
|
||||
self.view3D = view3D
|
||||
if floor is None:
|
||||
floor = Surface()
|
||||
self.floor = floor
|
||||
if sideWall is None:
|
||||
sideWall = Surface()
|
||||
self.sideWall = sideWall
|
||||
if backWall is None:
|
||||
backWall = Surface()
|
||||
self.backWall = backWall
|
||||
super(_3DBase, self).__init__()
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .area_chart import AreaChart, AreaChart3D
|
||||
from .bar_chart import BarChart, BarChart3D
|
||||
from .bubble_chart import BubbleChart
|
||||
from .line_chart import LineChart, LineChart3D
|
||||
from .pie_chart import (
|
||||
PieChart,
|
||||
PieChart3D,
|
||||
DoughnutChart,
|
||||
ProjectedPieChart
|
||||
)
|
||||
from .radar_chart import RadarChart
|
||||
from .scatter_chart import ScatterChart
|
||||
from .stock_chart import StockChart
|
||||
from .surface_chart import SurfaceChart, SurfaceChart3D
|
||||
|
||||
from .series_factory import SeriesFactory as Series
|
||||
from .reference import Reference
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from collections import OrderedDict
|
||||
from operator import attrgetter
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
Alias,
|
||||
MinMax,
|
||||
Bool,
|
||||
Set,
|
||||
)
|
||||
from openpyxl.descriptors.sequence import ValueSequence
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
from ._3d import _3DBase
|
||||
from .data_source import AxDataSource, NumRef
|
||||
from .layout import Layout
|
||||
from .legend import Legend
|
||||
from .reference import Reference
|
||||
from .series_factory import SeriesFactory
|
||||
from .series import attribute_mapping
|
||||
from .shapes import GraphicalProperties
|
||||
from .title import TitleDescriptor
|
||||
|
||||
class AxId(Serialisable):
|
||||
|
||||
val = Integer()
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
|
||||
def PlotArea():
|
||||
from .chartspace import PlotArea
|
||||
return PlotArea()
|
||||
|
||||
|
||||
class ChartBase(Serialisable):
|
||||
|
||||
"""
|
||||
Base class for all charts
|
||||
"""
|
||||
|
||||
legend = Typed(expected_type=Legend, allow_none=True)
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
roundedCorners = Bool(allow_none=True)
|
||||
axId = ValueSequence(expected_type=int)
|
||||
visible_cells_only = Bool(allow_none=True)
|
||||
display_blanks = Set(values=['span', 'gap', 'zero'])
|
||||
graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
|
||||
_series_type = ""
|
||||
ser = ()
|
||||
series = Alias('ser')
|
||||
title = TitleDescriptor()
|
||||
anchor = "E15" # default anchor position
|
||||
width = 15 # in cm, approx 5 rows
|
||||
height = 7.5 # in cm, approx 14 rows
|
||||
_id = 1
|
||||
_path = "/xl/charts/chart{0}.xml"
|
||||
style = MinMax(allow_none=True, min=1, max=48)
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
|
||||
graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) # mapped to chartspace
|
||||
|
||||
__elements__ = ()
|
||||
|
||||
|
||||
def __init__(self, axId=(), **kw):
|
||||
self._charts = [self]
|
||||
self.title = None
|
||||
self.layout = None
|
||||
self.roundedCorners = None
|
||||
self.legend = Legend()
|
||||
self.graphical_properties = None
|
||||
self.style = None
|
||||
self.plot_area = PlotArea()
|
||||
self.axId = axId
|
||||
self.display_blanks = 'gap'
|
||||
self.pivotSource = None
|
||||
self.pivotFormats = ()
|
||||
self.visible_cells_only = True
|
||||
self.idx_base = 0
|
||||
self.graphical_properties = None
|
||||
super().__init__()
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Just need to check for identity
|
||||
"""
|
||||
return id(self)
|
||||
|
||||
def __iadd__(self, other):
|
||||
"""
|
||||
Combine the chart with another one
|
||||
"""
|
||||
if not isinstance(other, ChartBase):
|
||||
raise TypeError("Only other charts can be added")
|
||||
self._charts.append(other)
|
||||
return self
|
||||
|
||||
|
||||
def to_tree(self, namespace=None, tagname=None, idx=None):
|
||||
self.axId = [id for id in self._axes]
|
||||
if self.ser is not None:
|
||||
for s in self.ser:
|
||||
s.__elements__ = attribute_mapping[self._series_type]
|
||||
return super().to_tree(tagname, idx)
|
||||
|
||||
|
||||
def _reindex(self):
|
||||
"""
|
||||
Normalise and rebase series: sort by order and then rebase order
|
||||
|
||||
"""
|
||||
# sort data series in order and rebase
|
||||
ds = sorted(self.series, key=attrgetter("order"))
|
||||
for idx, s in enumerate(ds):
|
||||
s.order = idx
|
||||
self.series = ds
|
||||
|
||||
|
||||
def _write(self):
|
||||
from .chartspace import ChartSpace, ChartContainer
|
||||
self.plot_area.layout = self.layout
|
||||
|
||||
idx_base = self.idx_base
|
||||
for chart in self._charts:
|
||||
if chart not in self.plot_area._charts:
|
||||
chart.idx_base = idx_base
|
||||
idx_base += len(chart.series)
|
||||
self.plot_area._charts = self._charts
|
||||
|
||||
container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title)
|
||||
if isinstance(chart, _3DBase):
|
||||
container.view3D = chart.view3D
|
||||
container.floor = chart.floor
|
||||
container.sideWall = chart.sideWall
|
||||
container.backWall = chart.backWall
|
||||
container.plotVisOnly = self.visible_cells_only
|
||||
container.dispBlanksAs = self.display_blanks
|
||||
container.pivotFmts = self.pivotFormats
|
||||
cs = ChartSpace(chart=container)
|
||||
cs.style = self.style
|
||||
cs.roundedCorners = self.roundedCorners
|
||||
cs.pivotSource = self.pivotSource
|
||||
cs.spPr = self.graphical_properties
|
||||
return cs.to_tree()
|
||||
|
||||
|
||||
@property
|
||||
def _axes(self):
|
||||
x = getattr(self, "x_axis", None)
|
||||
y = getattr(self, "y_axis", None)
|
||||
z = getattr(self, "z_axis", None)
|
||||
return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis])
|
||||
|
||||
|
||||
def set_categories(self, labels):
|
||||
"""
|
||||
Set the categories / x-axis values
|
||||
"""
|
||||
if not isinstance(labels, Reference):
|
||||
labels = Reference(range_string=labels)
|
||||
for s in self.ser:
|
||||
s.cat = AxDataSource(numRef=NumRef(f=labels))
|
||||
|
||||
|
||||
def add_data(self, data, from_rows=False, titles_from_data=False):
|
||||
"""
|
||||
Add a range of data in a single pass.
|
||||
The default is to treat each column as a data series.
|
||||
"""
|
||||
if not isinstance(data, Reference):
|
||||
data = Reference(range_string=data)
|
||||
|
||||
if from_rows:
|
||||
values = data.rows
|
||||
|
||||
else:
|
||||
values = data.cols
|
||||
|
||||
for ref in values:
|
||||
series = SeriesFactory(ref, title_from_data=titles_from_data)
|
||||
self.series.append(series)
|
||||
|
||||
|
||||
def append(self, value):
|
||||
"""Append a data series to the chart"""
|
||||
l = self.series[:]
|
||||
l.append(value)
|
||||
self.series = l
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path.format(self._id)
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Set,
|
||||
Bool,
|
||||
Integer,
|
||||
Sequence,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedMinMax,
|
||||
NestedSet,
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .descriptors import NestedGapAmount
|
||||
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
|
||||
from .label import DataLabelList
|
||||
from .series import Series
|
||||
|
||||
|
||||
class _AreaChartBase(ChartBase):
|
||||
|
||||
grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
|
||||
varyColors = NestedBool(nested=True, allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
dropLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
|
||||
_series_type = "area"
|
||||
|
||||
__elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
|
||||
|
||||
def __init__(self,
|
||||
grouping="standard",
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
dropLines=None,
|
||||
):
|
||||
self.grouping = grouping
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.dropLines = dropLines
|
||||
super().__init__()
|
||||
|
||||
|
||||
class AreaChart(_AreaChartBase):
|
||||
|
||||
tagname = "areaChart"
|
||||
|
||||
grouping = _AreaChartBase.grouping
|
||||
varyColors = _AreaChartBase.varyColors
|
||||
ser = _AreaChartBase.ser
|
||||
dLbls = _AreaChartBase.dLbls
|
||||
dropLines = _AreaChartBase.dropLines
|
||||
|
||||
# chart properties actually used by containing classes
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _AreaChartBase.__elements__ + ('axId',)
|
||||
|
||||
def __init__(self,
|
||||
axId=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class AreaChart3D(AreaChart):
|
||||
|
||||
tagname = "area3DChart"
|
||||
|
||||
grouping = _AreaChartBase.grouping
|
||||
varyColors = _AreaChartBase.varyColors
|
||||
ser = _AreaChartBase.ser
|
||||
dLbls = _AreaChartBase.dLbls
|
||||
dropLines = _AreaChartBase.dropLines
|
||||
|
||||
gapDepth = NestedGapAmount()
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
|
||||
|
||||
__elements__ = AreaChart.__elements__ + ('gapDepth', )
|
||||
|
||||
def __init__(self, gapDepth=None, **kw):
|
||||
self.gapDepth = gapDepth
|
||||
super(AreaChart3D, self).__init__(**kw)
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
self.z_axis = SeriesAxis()
|
||||
+401
@@ -0,0 +1,401 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Float,
|
||||
NoneSet,
|
||||
Bool,
|
||||
Integer,
|
||||
MinMax,
|
||||
NoneSet,
|
||||
Set,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import (
|
||||
ExtensionList,
|
||||
Percentage,
|
||||
_explicit_none,
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedValue,
|
||||
NestedSet,
|
||||
NestedBool,
|
||||
NestedNoneSet,
|
||||
NestedFloat,
|
||||
NestedInteger,
|
||||
NestedMinMax,
|
||||
)
|
||||
from openpyxl.xml.constants import CHART_NS
|
||||
|
||||
from .descriptors import NumberFormatDescriptor
|
||||
from .layout import Layout
|
||||
from .text import Text, RichText
|
||||
from .shapes import GraphicalProperties
|
||||
from .title import Title, TitleDescriptor
|
||||
|
||||
|
||||
class ChartLines(Serialisable):
|
||||
|
||||
tagname = "chartLines"
|
||||
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
|
||||
def __init__(self, spPr=None):
|
||||
self.spPr = spPr
|
||||
|
||||
|
||||
class Scaling(Serialisable):
|
||||
|
||||
tagname = "scaling"
|
||||
|
||||
logBase = NestedFloat(allow_none=True)
|
||||
orientation = NestedSet(values=(['maxMin', 'minMax']))
|
||||
max = NestedFloat(allow_none=True)
|
||||
min = NestedFloat(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('logBase', 'orientation', 'max', 'min',)
|
||||
|
||||
def __init__(self,
|
||||
logBase=None,
|
||||
orientation="minMax",
|
||||
max=None,
|
||||
min=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.logBase = logBase
|
||||
self.orientation = orientation
|
||||
self.max = max
|
||||
self.min = min
|
||||
|
||||
|
||||
class _BaseAxis(Serialisable):
|
||||
|
||||
axId = NestedInteger(expected_type=int)
|
||||
scaling = Typed(expected_type=Scaling)
|
||||
delete = NestedBool(allow_none=True)
|
||||
axPos = NestedSet(values=(['b', 'l', 'r', 't']))
|
||||
majorGridlines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
minorGridlines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
title = TitleDescriptor()
|
||||
numFmt = NumberFormatDescriptor()
|
||||
number_format = Alias("numFmt")
|
||||
majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
|
||||
minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
|
||||
tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo']))
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textProperties = Alias('txPr')
|
||||
crossAx = NestedInteger(expected_type=int) # references other axis
|
||||
crosses = NestedNoneSet(values=(['autoZero', 'max', 'min']))
|
||||
crossesAt = NestedFloat(allow_none=True)
|
||||
|
||||
# crosses & crossesAt are mutually exclusive
|
||||
|
||||
__elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines',
|
||||
'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark',
|
||||
'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt')
|
||||
|
||||
def __init__(self,
|
||||
axId=None,
|
||||
scaling=None,
|
||||
delete=None,
|
||||
axPos='l',
|
||||
majorGridlines=None,
|
||||
minorGridlines=None,
|
||||
title=None,
|
||||
numFmt=None,
|
||||
majorTickMark=None,
|
||||
minorTickMark=None,
|
||||
tickLblPos=None,
|
||||
spPr=None,
|
||||
txPr= None,
|
||||
crossAx=None,
|
||||
crosses=None,
|
||||
crossesAt=None,
|
||||
):
|
||||
self.axId = axId
|
||||
if scaling is None:
|
||||
scaling = Scaling()
|
||||
self.scaling = scaling
|
||||
self.delete = delete
|
||||
self.axPos = axPos
|
||||
self.majorGridlines = majorGridlines
|
||||
self.minorGridlines = minorGridlines
|
||||
self.title = title
|
||||
self.numFmt = numFmt
|
||||
self.majorTickMark = majorTickMark
|
||||
self.minorTickMark = minorTickMark
|
||||
self.tickLblPos = tickLblPos
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
self.crossAx = crossAx
|
||||
self.crosses = crosses
|
||||
self.crossesAt = crossesAt
|
||||
|
||||
|
||||
class DisplayUnitsLabel(Serialisable):
|
||||
|
||||
tagname = "dispUnitsLbl"
|
||||
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
tx = Typed(expected_type=Text, allow_none=True)
|
||||
text = Alias("tx")
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textPropertes = Alias("txPr")
|
||||
|
||||
__elements__ = ('layout', 'tx', 'spPr', 'txPr')
|
||||
|
||||
def __init__(self,
|
||||
layout=None,
|
||||
tx=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
):
|
||||
self.layout = layout
|
||||
self.tx = tx
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
|
||||
|
||||
class DisplayUnitsLabelList(Serialisable):
|
||||
|
||||
tagname = "dispUnits"
|
||||
|
||||
custUnit = NestedFloat(allow_none=True)
|
||||
builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands',
|
||||
'tenThousands', 'hundredThousands', 'millions', 'tenMillions',
|
||||
'hundredMillions', 'billions', 'trillions']))
|
||||
dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',)
|
||||
|
||||
def __init__(self,
|
||||
custUnit=None,
|
||||
builtInUnit=None,
|
||||
dispUnitsLbl=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.custUnit = custUnit
|
||||
self.builtInUnit = builtInUnit
|
||||
self.dispUnitsLbl = dispUnitsLbl
|
||||
|
||||
|
||||
class NumericAxis(_BaseAxis):
|
||||
|
||||
tagname = "valAx"
|
||||
|
||||
axId = _BaseAxis.axId
|
||||
scaling = _BaseAxis.scaling
|
||||
delete = _BaseAxis.delete
|
||||
axPos = _BaseAxis.axPos
|
||||
majorGridlines = _BaseAxis.majorGridlines
|
||||
minorGridlines = _BaseAxis.minorGridlines
|
||||
title = _BaseAxis.title
|
||||
numFmt = _BaseAxis.numFmt
|
||||
majorTickMark = _BaseAxis.majorTickMark
|
||||
minorTickMark = _BaseAxis.minorTickMark
|
||||
tickLblPos = _BaseAxis.tickLblPos
|
||||
spPr = _BaseAxis.spPr
|
||||
txPr = _BaseAxis.txPr
|
||||
crossAx = _BaseAxis.crossAx
|
||||
crosses = _BaseAxis.crosses
|
||||
crossesAt = _BaseAxis.crossesAt
|
||||
|
||||
crossBetween = NestedNoneSet(values=(['between', 'midCat']))
|
||||
majorUnit = NestedFloat(allow_none=True)
|
||||
minorUnit = NestedFloat(allow_none=True)
|
||||
dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
|
||||
'minorUnit', 'dispUnits',)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
crossBetween=None,
|
||||
majorUnit=None,
|
||||
minorUnit=None,
|
||||
dispUnits=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.crossBetween = crossBetween
|
||||
self.majorUnit = majorUnit
|
||||
self.minorUnit = minorUnit
|
||||
self.dispUnits = dispUnits
|
||||
kw.setdefault('majorGridlines', ChartLines())
|
||||
kw.setdefault('axId', 100)
|
||||
kw.setdefault('crossAx', 10)
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
"""
|
||||
Special case value axes with no gridlines
|
||||
"""
|
||||
self = super().from_tree(node)
|
||||
gridlines = node.find("{%s}majorGridlines" % CHART_NS)
|
||||
if gridlines is None:
|
||||
self.majorGridlines = None
|
||||
return self
|
||||
|
||||
|
||||
|
||||
class TextAxis(_BaseAxis):
|
||||
|
||||
tagname = "catAx"
|
||||
|
||||
axId = _BaseAxis.axId
|
||||
scaling = _BaseAxis.scaling
|
||||
delete = _BaseAxis.delete
|
||||
axPos = _BaseAxis.axPos
|
||||
majorGridlines = _BaseAxis.majorGridlines
|
||||
minorGridlines = _BaseAxis.minorGridlines
|
||||
title = _BaseAxis.title
|
||||
numFmt = _BaseAxis.numFmt
|
||||
majorTickMark = _BaseAxis.majorTickMark
|
||||
minorTickMark = _BaseAxis.minorTickMark
|
||||
tickLblPos = _BaseAxis.tickLblPos
|
||||
spPr = _BaseAxis.spPr
|
||||
txPr = _BaseAxis.txPr
|
||||
crossAx = _BaseAxis.crossAx
|
||||
crosses = _BaseAxis.crosses
|
||||
crossesAt = _BaseAxis.crossesAt
|
||||
|
||||
auto = NestedBool(allow_none=True)
|
||||
lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r']))
|
||||
lblOffset = NestedMinMax(min=0, max=1000)
|
||||
tickLblSkip = NestedInteger(allow_none=True)
|
||||
tickMarkSkip = NestedInteger(allow_none=True)
|
||||
noMultiLvlLbl = NestedBool(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset',
|
||||
'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl')
|
||||
|
||||
def __init__(self,
|
||||
auto=None,
|
||||
lblAlgn=None,
|
||||
lblOffset=100,
|
||||
tickLblSkip=None,
|
||||
tickMarkSkip=None,
|
||||
noMultiLvlLbl=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.auto = auto
|
||||
self.lblAlgn = lblAlgn
|
||||
self.lblOffset = lblOffset
|
||||
self.tickLblSkip = tickLblSkip
|
||||
self.tickMarkSkip = tickMarkSkip
|
||||
self.noMultiLvlLbl = noMultiLvlLbl
|
||||
kw.setdefault('axId', 10)
|
||||
kw.setdefault('crossAx', 100)
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class DateAxis(TextAxis):
|
||||
|
||||
tagname = "dateAx"
|
||||
|
||||
axId = _BaseAxis.axId
|
||||
scaling = _BaseAxis.scaling
|
||||
delete = _BaseAxis.delete
|
||||
axPos = _BaseAxis.axPos
|
||||
majorGridlines = _BaseAxis.majorGridlines
|
||||
minorGridlines = _BaseAxis.minorGridlines
|
||||
title = _BaseAxis.title
|
||||
numFmt = _BaseAxis.numFmt
|
||||
majorTickMark = _BaseAxis.majorTickMark
|
||||
minorTickMark = _BaseAxis.minorTickMark
|
||||
tickLblPos = _BaseAxis.tickLblPos
|
||||
spPr = _BaseAxis.spPr
|
||||
txPr = _BaseAxis.txPr
|
||||
crossAx = _BaseAxis.crossAx
|
||||
crosses = _BaseAxis.crosses
|
||||
crossesAt = _BaseAxis.crossesAt
|
||||
|
||||
auto = NestedBool(allow_none=True)
|
||||
lblOffset = NestedInteger(allow_none=True)
|
||||
baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
|
||||
majorUnit = NestedFloat(allow_none=True)
|
||||
majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
|
||||
minorUnit = NestedFloat(allow_none=True)
|
||||
minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset',
|
||||
'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit',
|
||||
'minorTimeUnit')
|
||||
|
||||
def __init__(self,
|
||||
auto=None,
|
||||
lblOffset=None,
|
||||
baseTimeUnit=None,
|
||||
majorUnit=None,
|
||||
majorTimeUnit=None,
|
||||
minorUnit=None,
|
||||
minorTimeUnit=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.auto = auto
|
||||
self.lblOffset = lblOffset
|
||||
self.baseTimeUnit = baseTimeUnit
|
||||
self.majorUnit = majorUnit
|
||||
self.majorTimeUnit = majorTimeUnit
|
||||
self.minorUnit = minorUnit
|
||||
self.minorTimeUnit = minorTimeUnit
|
||||
kw.setdefault('axId', 500)
|
||||
kw.setdefault('lblOffset', lblOffset)
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class SeriesAxis(_BaseAxis):
|
||||
|
||||
tagname = "serAx"
|
||||
|
||||
axId = _BaseAxis.axId
|
||||
scaling = _BaseAxis.scaling
|
||||
delete = _BaseAxis.delete
|
||||
axPos = _BaseAxis.axPos
|
||||
majorGridlines = _BaseAxis.majorGridlines
|
||||
minorGridlines = _BaseAxis.minorGridlines
|
||||
title = _BaseAxis.title
|
||||
numFmt = _BaseAxis.numFmt
|
||||
majorTickMark = _BaseAxis.majorTickMark
|
||||
minorTickMark = _BaseAxis.minorTickMark
|
||||
tickLblPos = _BaseAxis.tickLblPos
|
||||
spPr = _BaseAxis.spPr
|
||||
txPr = _BaseAxis.txPr
|
||||
crossAx = _BaseAxis.crossAx
|
||||
crosses = _BaseAxis.crosses
|
||||
crossesAt = _BaseAxis.crossesAt
|
||||
|
||||
tickLblSkip = NestedInteger(allow_none=True)
|
||||
tickMarkSkip = NestedInteger(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip')
|
||||
|
||||
def __init__(self,
|
||||
tickLblSkip=None,
|
||||
tickMarkSkip=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.tickLblSkip = tickLblSkip
|
||||
self.tickMarkSkip = tickMarkSkip
|
||||
kw.setdefault('axId', 1000)
|
||||
kw.setdefault('crossAx', 10)
|
||||
super().__init__(**kw)
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
Integer,
|
||||
Sequence,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedSet,
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedMinMax,
|
||||
)
|
||||
|
||||
from .descriptors import (
|
||||
NestedGapAmount,
|
||||
NestedOverlap,
|
||||
)
|
||||
from ._chart import ChartBase
|
||||
from ._3d import _3DBase
|
||||
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
|
||||
from .shapes import GraphicalProperties
|
||||
from .series import Series
|
||||
from .legend import Legend
|
||||
from .label import DataLabelList
|
||||
|
||||
|
||||
class _BarChartBase(ChartBase):
|
||||
|
||||
barDir = NestedSet(values=(['bar', 'col']))
|
||||
type = Alias("barDir")
|
||||
grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard',
|
||||
'stacked']))
|
||||
varyColors = NestedBool(nested=True, allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
|
||||
__elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls')
|
||||
|
||||
_series_type = "bar"
|
||||
|
||||
def __init__(self,
|
||||
barDir="col",
|
||||
grouping="clustered",
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
**kw
|
||||
):
|
||||
self.barDir = barDir
|
||||
self.grouping = grouping
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class BarChart(_BarChartBase):
|
||||
|
||||
tagname = "barChart"
|
||||
|
||||
barDir = _BarChartBase.barDir
|
||||
grouping = _BarChartBase.grouping
|
||||
varyColors = _BarChartBase.varyColors
|
||||
ser = _BarChartBase.ser
|
||||
dLbls = _BarChartBase.dLbls
|
||||
|
||||
gapWidth = NestedGapAmount()
|
||||
overlap = NestedOverlap()
|
||||
serLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
# chart properties actually used by containing classes
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
__elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
gapWidth=150,
|
||||
overlap=None,
|
||||
serLines=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.gapWidth = gapWidth
|
||||
self.overlap = overlap
|
||||
self.serLines = serLines
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
self.legend = Legend()
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class BarChart3D(_BarChartBase, _3DBase):
|
||||
|
||||
tagname = "bar3DChart"
|
||||
|
||||
barDir = _BarChartBase.barDir
|
||||
grouping = _BarChartBase.grouping
|
||||
varyColors = _BarChartBase.varyColors
|
||||
ser = _BarChartBase.ser
|
||||
dLbls = _BarChartBase.dLbls
|
||||
|
||||
view3D = _3DBase.view3D
|
||||
floor = _3DBase.floor
|
||||
sideWall = _3DBase.sideWall
|
||||
backWall = _3DBase.backWall
|
||||
|
||||
gapWidth = NestedGapAmount()
|
||||
gapDepth = NestedGapAmount()
|
||||
shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
|
||||
serLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
|
||||
|
||||
__elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
gapWidth=150,
|
||||
gapDepth=150,
|
||||
shape=None,
|
||||
serLines=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.gapWidth = gapWidth
|
||||
self.gapDepth = gapDepth
|
||||
self.shape = shape
|
||||
self.serLines = serLines
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
self.z_axis = SeriesAxis()
|
||||
|
||||
super(BarChart3D, self).__init__(**kw)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Set,
|
||||
MinMax,
|
||||
Bool,
|
||||
Integer,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedMinMax,
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .axis import TextAxis, NumericAxis
|
||||
from .series import XYSeries
|
||||
from .label import DataLabelList
|
||||
|
||||
|
||||
class BubbleChart(ChartBase):
|
||||
|
||||
tagname = "bubbleChart"
|
||||
|
||||
varyColors = NestedBool(allow_none=True)
|
||||
ser = Sequence(expected_type=XYSeries, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
bubble3D = NestedBool(allow_none=True)
|
||||
bubbleScale = NestedMinMax(min=0, max=300, allow_none=True)
|
||||
showNegBubbles = NestedBool(allow_none=True)
|
||||
sizeRepresents = NestedNoneSet(values=(['area', 'w']))
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=NumericAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
_series_type = "bubble"
|
||||
|
||||
__elements__ = ('varyColors', 'ser', 'dLbls', 'bubble3D', 'bubbleScale',
|
||||
'showNegBubbles', 'sizeRepresents', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
bubble3D=None,
|
||||
bubbleScale=None,
|
||||
showNegBubbles=None,
|
||||
sizeRepresents=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.bubble3D = bubble3D
|
||||
self.bubbleScale = bubbleScale
|
||||
self.showNegBubbles = showNegBubbles
|
||||
self.sizeRepresents = sizeRepresents
|
||||
self.x_axis = NumericAxis(axId=10, crossAx=20)
|
||||
self.y_axis = NumericAxis(axId=20, crossAx=10)
|
||||
super().__init__(**kw)
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Enclosing chart object. The various chart types are actually child objects.
|
||||
Will probably need to call this indirectly
|
||||
"""
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import (
|
||||
ExtensionList,
|
||||
Relation
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedNoneSet,
|
||||
NestedString,
|
||||
NestedMinMax,
|
||||
)
|
||||
from openpyxl.descriptors.sequence import NestedSequence
|
||||
from openpyxl.xml.constants import CHART_NS
|
||||
|
||||
from openpyxl.drawing.colors import ColorMapping
|
||||
from .text import RichText
|
||||
from .shapes import GraphicalProperties
|
||||
from .legend import Legend
|
||||
from ._3d import _3DBase
|
||||
from .plotarea import PlotArea
|
||||
from .title import Title
|
||||
from .pivot import (
|
||||
PivotFormat,
|
||||
PivotSource,
|
||||
)
|
||||
from .print_settings import PrintSettings
|
||||
|
||||
|
||||
class ChartContainer(Serialisable):
|
||||
|
||||
tagname = "chart"
|
||||
|
||||
title = Typed(expected_type=Title, allow_none=True)
|
||||
autoTitleDeleted = NestedBool(allow_none=True)
|
||||
pivotFmts = NestedSequence(expected_type=PivotFormat)
|
||||
view3D = _3DBase.view3D
|
||||
floor = _3DBase.floor
|
||||
sideWall = _3DBase.sideWall
|
||||
backWall = _3DBase.backWall
|
||||
plotArea = Typed(expected_type=PlotArea, )
|
||||
legend = Typed(expected_type=Legend, allow_none=True)
|
||||
plotVisOnly = NestedBool()
|
||||
dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero']))
|
||||
showDLblsOverMax = NestedBool(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D',
|
||||
'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly',
|
||||
'dispBlanksAs', 'showDLblsOverMax')
|
||||
|
||||
def __init__(self,
|
||||
title=None,
|
||||
autoTitleDeleted=None,
|
||||
pivotFmts=(),
|
||||
view3D=None,
|
||||
floor=None,
|
||||
sideWall=None,
|
||||
backWall=None,
|
||||
plotArea=None,
|
||||
legend=None,
|
||||
plotVisOnly=True,
|
||||
dispBlanksAs="gap",
|
||||
showDLblsOverMax=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.title = title
|
||||
self.autoTitleDeleted = autoTitleDeleted
|
||||
self.pivotFmts = pivotFmts
|
||||
self.view3D = view3D
|
||||
self.floor = floor
|
||||
self.sideWall = sideWall
|
||||
self.backWall = backWall
|
||||
if plotArea is None:
|
||||
plotArea = PlotArea()
|
||||
self.plotArea = plotArea
|
||||
self.legend = legend
|
||||
self.plotVisOnly = plotVisOnly
|
||||
self.dispBlanksAs = dispBlanksAs
|
||||
self.showDLblsOverMax = showDLblsOverMax
|
||||
|
||||
|
||||
class Protection(Serialisable):
|
||||
|
||||
tagname = "protection"
|
||||
|
||||
chartObject = NestedBool(allow_none=True)
|
||||
data = NestedBool(allow_none=True)
|
||||
formatting = NestedBool(allow_none=True)
|
||||
selection = NestedBool(allow_none=True)
|
||||
userInterface = NestedBool(allow_none=True)
|
||||
|
||||
__elements__ = ("chartObject", "data", "formatting", "selection", "userInterface")
|
||||
|
||||
def __init__(self,
|
||||
chartObject=None,
|
||||
data=None,
|
||||
formatting=None,
|
||||
selection=None,
|
||||
userInterface=None,
|
||||
):
|
||||
self.chartObject = chartObject
|
||||
self.data = data
|
||||
self.formatting = formatting
|
||||
self.selection = selection
|
||||
self.userInterface = userInterface
|
||||
|
||||
|
||||
class ExternalData(Serialisable):
|
||||
|
||||
tagname = "externalData"
|
||||
|
||||
autoUpdate = NestedBool(allow_none=True)
|
||||
id = String() # Needs namespace
|
||||
|
||||
def __init__(self,
|
||||
autoUpdate=None,
|
||||
id=None
|
||||
):
|
||||
self.autoUpdate = autoUpdate
|
||||
self.id = id
|
||||
|
||||
|
||||
class ChartSpace(Serialisable):
|
||||
|
||||
tagname = "chartSpace"
|
||||
|
||||
date1904 = NestedBool(allow_none=True)
|
||||
lang = NestedString(allow_none=True)
|
||||
roundedCorners = NestedBool(allow_none=True)
|
||||
style = NestedMinMax(allow_none=True, min=1, max=48)
|
||||
clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True)
|
||||
pivotSource = Typed(expected_type=PivotSource, allow_none=True)
|
||||
protection = Typed(expected_type=Protection, allow_none=True)
|
||||
chart = Typed(expected_type=ChartContainer)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphical_properties = Alias("spPr")
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textProperties = Alias("txPr")
|
||||
externalData = Typed(expected_type=ExternalData, allow_none=True)
|
||||
printSettings = Typed(expected_type=PrintSettings, allow_none=True)
|
||||
userShapes = Relation()
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('date1904', 'lang', 'roundedCorners', 'style',
|
||||
'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr',
|
||||
'externalData', 'printSettings', 'userShapes')
|
||||
|
||||
def __init__(self,
|
||||
date1904=None,
|
||||
lang=None,
|
||||
roundedCorners=None,
|
||||
style=None,
|
||||
clrMapOvr=None,
|
||||
pivotSource=None,
|
||||
protection=None,
|
||||
chart=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
externalData=None,
|
||||
printSettings=None,
|
||||
userShapes=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.date1904 = date1904
|
||||
self.lang = lang
|
||||
self.roundedCorners = roundedCorners
|
||||
self.style = style
|
||||
self.clrMapOvr = clrMapOvr
|
||||
self.pivotSource = pivotSource
|
||||
self.protection = protection
|
||||
self.chart = chart
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
self.externalData = externalData
|
||||
self.printSettings = printSettings
|
||||
self.userShapes = userShapes
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
tree = super().to_tree()
|
||||
tree.set("xmlns", CHART_NS)
|
||||
return tree
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
Collection of utility primitives for charts.
|
||||
"""
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Typed,
|
||||
Alias,
|
||||
String,
|
||||
Integer,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedString,
|
||||
NestedText,
|
||||
NestedInteger,
|
||||
)
|
||||
|
||||
|
||||
class NumFmt(Serialisable):
|
||||
|
||||
formatCode = String()
|
||||
sourceLinked = Bool()
|
||||
|
||||
def __init__(self,
|
||||
formatCode=None,
|
||||
sourceLinked=False
|
||||
):
|
||||
self.formatCode = formatCode
|
||||
self.sourceLinked = sourceLinked
|
||||
|
||||
|
||||
class NumberValueDescriptor(NestedText):
|
||||
"""
|
||||
Data should be numerical but isn't always :-/
|
||||
"""
|
||||
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value == "#N/A":
|
||||
self.expected_type = str
|
||||
else:
|
||||
self.expected_type = float
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class NumVal(Serialisable):
|
||||
|
||||
idx = Integer()
|
||||
formatCode = NestedText(allow_none=True, expected_type=str)
|
||||
v = NumberValueDescriptor()
|
||||
|
||||
def __init__(self,
|
||||
idx=None,
|
||||
formatCode=None,
|
||||
v=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.formatCode = formatCode
|
||||
self.v = v
|
||||
|
||||
|
||||
class NumData(Serialisable):
|
||||
|
||||
formatCode = NestedText(expected_type=str, allow_none=True)
|
||||
ptCount = NestedInteger(allow_none=True)
|
||||
pt = Sequence(expected_type=NumVal)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('formatCode', 'ptCount', 'pt')
|
||||
|
||||
def __init__(self,
|
||||
formatCode=None,
|
||||
ptCount=None,
|
||||
pt=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.formatCode = formatCode
|
||||
self.ptCount = ptCount
|
||||
self.pt = pt
|
||||
|
||||
|
||||
class NumRef(Serialisable):
|
||||
|
||||
f = NestedText(expected_type=str)
|
||||
ref = Alias('f')
|
||||
numCache = Typed(expected_type=NumData, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('f', 'numCache')
|
||||
|
||||
def __init__(self,
|
||||
f=None,
|
||||
numCache=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.f = f
|
||||
self.numCache = numCache
|
||||
|
||||
|
||||
class StrVal(Serialisable):
|
||||
|
||||
tagname = "strVal"
|
||||
|
||||
idx = Integer()
|
||||
v = NestedText(expected_type=str)
|
||||
|
||||
def __init__(self,
|
||||
idx=0,
|
||||
v=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.v = v
|
||||
|
||||
|
||||
class StrData(Serialisable):
|
||||
|
||||
tagname = "strData"
|
||||
|
||||
ptCount = NestedInteger(allow_none=True)
|
||||
pt = Sequence(expected_type=StrVal)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('ptCount', 'pt')
|
||||
|
||||
def __init__(self,
|
||||
ptCount=None,
|
||||
pt=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.ptCount = ptCount
|
||||
self.pt = pt
|
||||
|
||||
|
||||
class StrRef(Serialisable):
|
||||
|
||||
tagname = "strRef"
|
||||
|
||||
f = NestedText(expected_type=str, allow_none=True)
|
||||
strCache = Typed(expected_type=StrData, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('f', 'strCache')
|
||||
|
||||
def __init__(self,
|
||||
f=None,
|
||||
strCache=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.f = f
|
||||
self.strCache = strCache
|
||||
|
||||
|
||||
class NumDataSource(Serialisable):
|
||||
|
||||
numRef = Typed(expected_type=NumRef, allow_none=True)
|
||||
numLit = Typed(expected_type=NumData, allow_none=True)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
numRef=None,
|
||||
numLit=None,
|
||||
):
|
||||
self.numRef = numRef
|
||||
self.numLit = numLit
|
||||
|
||||
|
||||
class Level(Serialisable):
|
||||
|
||||
tagname = "lvl"
|
||||
|
||||
pt = Sequence(expected_type=StrVal)
|
||||
|
||||
__elements__ = ('pt',)
|
||||
|
||||
def __init__(self,
|
||||
pt=(),
|
||||
):
|
||||
self.pt = pt
|
||||
|
||||
|
||||
class MultiLevelStrData(Serialisable):
|
||||
|
||||
tagname = "multiLvlStrData"
|
||||
|
||||
ptCount = Integer(allow_none=True)
|
||||
lvl = Sequence(expected_type=Level)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('ptCount', 'lvl',)
|
||||
|
||||
def __init__(self,
|
||||
ptCount=None,
|
||||
lvl=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.ptCount = ptCount
|
||||
self.lvl = lvl
|
||||
|
||||
|
||||
class MultiLevelStrRef(Serialisable):
|
||||
|
||||
tagname = "multiLvlStrRef"
|
||||
|
||||
f = NestedText(expected_type=str)
|
||||
multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('multiLvlStrCache', 'f')
|
||||
|
||||
def __init__(self,
|
||||
f=None,
|
||||
multiLvlStrCache=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.f = f
|
||||
self.multiLvlStrCache = multiLvlStrCache
|
||||
|
||||
|
||||
class AxDataSource(Serialisable):
|
||||
|
||||
tagname = "cat"
|
||||
|
||||
numRef = Typed(expected_type=NumRef, allow_none=True)
|
||||
numLit = Typed(expected_type=NumData, allow_none=True)
|
||||
strRef = Typed(expected_type=StrRef, allow_none=True)
|
||||
strLit = Typed(expected_type=StrData, allow_none=True)
|
||||
multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
numRef=None,
|
||||
numLit=None,
|
||||
strRef=None,
|
||||
strLit=None,
|
||||
multiLvlStrRef=None,
|
||||
):
|
||||
if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]):
|
||||
raise TypeError("A data source must be provided")
|
||||
self.numRef = numRef
|
||||
self.numLit = numLit
|
||||
self.strRef = strRef
|
||||
self.strLit = strLit
|
||||
self.multiLvlStrRef = multiLvlStrRef
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedMinMax
|
||||
)
|
||||
|
||||
from openpyxl.descriptors import Typed
|
||||
|
||||
from .data_source import NumFmt
|
||||
|
||||
"""
|
||||
Utility descriptors for the chart module.
|
||||
For convenience but also clarity.
|
||||
"""
|
||||
|
||||
class NestedGapAmount(NestedMinMax):
|
||||
|
||||
allow_none = True
|
||||
min = 0
|
||||
max = 500
|
||||
|
||||
|
||||
class NestedOverlap(NestedMinMax):
|
||||
|
||||
allow_none = True
|
||||
min = -100
|
||||
max = 100
|
||||
|
||||
|
||||
class NumberFormatDescriptor(Typed):
|
||||
"""
|
||||
Allow direct assignment of format code
|
||||
"""
|
||||
|
||||
expected_type = NumFmt
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str):
|
||||
value = NumFmt(value)
|
||||
super().__set__(instance, value)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Float,
|
||||
Set,
|
||||
Alias
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedSet,
|
||||
NestedBool,
|
||||
NestedFloat,
|
||||
)
|
||||
|
||||
from .data_source import NumDataSource
|
||||
from .shapes import GraphicalProperties
|
||||
|
||||
|
||||
class ErrorBars(Serialisable):
|
||||
|
||||
tagname = "errBars"
|
||||
|
||||
errDir = NestedNoneSet(values=(['x', 'y']))
|
||||
direction = Alias("errDir")
|
||||
errBarType = NestedSet(values=(['both', 'minus', 'plus']))
|
||||
style = Alias("errBarType")
|
||||
errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr']))
|
||||
size = Alias("errValType")
|
||||
noEndCap = NestedBool(nested=True, allow_none=True)
|
||||
plus = Typed(expected_type=NumDataSource, allow_none=True)
|
||||
minus = Typed(expected_type=NumDataSource, allow_none=True)
|
||||
val = NestedFloat(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr')
|
||||
|
||||
|
||||
def __init__(self,
|
||||
errDir=None,
|
||||
errBarType="both",
|
||||
errValType="fixedVal",
|
||||
noEndCap=None,
|
||||
plus=None,
|
||||
minus=None,
|
||||
val=None,
|
||||
spPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.errDir = errDir
|
||||
self.errBarType = errBarType
|
||||
self.errValType = errValType
|
||||
self.noEndCap = noEndCap
|
||||
self.plus = plus
|
||||
self.minus = minus
|
||||
self.val = val
|
||||
self.spPr = spPr
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Sequence,
|
||||
Alias,
|
||||
Typed
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedBool,
|
||||
NestedString,
|
||||
NestedInteger,
|
||||
)
|
||||
|
||||
from .shapes import GraphicalProperties
|
||||
from .text import RichText
|
||||
|
||||
|
||||
class _DataLabelBase(Serialisable):
|
||||
|
||||
numFmt = NestedString(allow_none=True, attribute="formatCode")
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textProperties = Alias('txPr')
|
||||
dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd',
|
||||
'l', 'outEnd', 'r', 't'])
|
||||
position = Alias('dLblPos')
|
||||
showLegendKey = NestedBool(allow_none=True)
|
||||
showVal = NestedBool(allow_none=True)
|
||||
showCatName = NestedBool(allow_none=True)
|
||||
showSerName = NestedBool(allow_none=True)
|
||||
showPercent = NestedBool(allow_none=True)
|
||||
showBubbleSize = NestedBool(allow_none=True)
|
||||
showLeaderLines = NestedBool(allow_none=True)
|
||||
separator = NestedString(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey",
|
||||
"showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize",
|
||||
"showLeaderLines", "separator")
|
||||
|
||||
def __init__(self,
|
||||
numFmt=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
dLblPos=None,
|
||||
showLegendKey=None,
|
||||
showVal=None,
|
||||
showCatName=None,
|
||||
showSerName=None,
|
||||
showPercent=None,
|
||||
showBubbleSize=None,
|
||||
showLeaderLines=None,
|
||||
separator=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.numFmt = numFmt
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
self.dLblPos = dLblPos
|
||||
self.showLegendKey = showLegendKey
|
||||
self.showVal = showVal
|
||||
self.showCatName = showCatName
|
||||
self.showSerName = showSerName
|
||||
self.showPercent = showPercent
|
||||
self.showBubbleSize = showBubbleSize
|
||||
self.showLeaderLines = showLeaderLines
|
||||
self.separator = separator
|
||||
|
||||
|
||||
class DataLabel(_DataLabelBase):
|
||||
|
||||
tagname = "dLbl"
|
||||
|
||||
idx = NestedInteger()
|
||||
|
||||
numFmt = _DataLabelBase.numFmt
|
||||
spPr = _DataLabelBase.spPr
|
||||
txPr = _DataLabelBase.txPr
|
||||
dLblPos = _DataLabelBase.dLblPos
|
||||
showLegendKey = _DataLabelBase.showLegendKey
|
||||
showVal = _DataLabelBase.showVal
|
||||
showCatName = _DataLabelBase.showCatName
|
||||
showSerName = _DataLabelBase.showSerName
|
||||
showPercent = _DataLabelBase.showPercent
|
||||
showBubbleSize = _DataLabelBase.showBubbleSize
|
||||
showLeaderLines = _DataLabelBase.showLeaderLines
|
||||
separator = _DataLabelBase.separator
|
||||
extLst = _DataLabelBase.extLst
|
||||
|
||||
__elements__ = ("idx",) + _DataLabelBase.__elements__
|
||||
|
||||
def __init__(self, idx=0, **kw ):
|
||||
self.idx = idx
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class DataLabelList(_DataLabelBase):
|
||||
|
||||
tagname = "dLbls"
|
||||
|
||||
dLbl = Sequence(expected_type=DataLabel, allow_none=True)
|
||||
|
||||
delete = NestedBool(allow_none=True)
|
||||
numFmt = _DataLabelBase.numFmt
|
||||
spPr = _DataLabelBase.spPr
|
||||
txPr = _DataLabelBase.txPr
|
||||
dLblPos = _DataLabelBase.dLblPos
|
||||
showLegendKey = _DataLabelBase.showLegendKey
|
||||
showVal = _DataLabelBase.showVal
|
||||
showCatName = _DataLabelBase.showCatName
|
||||
showSerName = _DataLabelBase.showSerName
|
||||
showPercent = _DataLabelBase.showPercent
|
||||
showBubbleSize = _DataLabelBase.showBubbleSize
|
||||
showLeaderLines = _DataLabelBase.showLeaderLines
|
||||
separator = _DataLabelBase.separator
|
||||
extLst = _DataLabelBase.extLst
|
||||
|
||||
__elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__
|
||||
|
||||
def __init__(self, dLbl=(), delete=None, **kw):
|
||||
self.dLbl = dLbl
|
||||
self.delete = delete
|
||||
super().__init__(**kw)
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
NoneSet,
|
||||
Float,
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedSet,
|
||||
NestedMinMax,
|
||||
)
|
||||
|
||||
class ManualLayout(Serialisable):
|
||||
|
||||
tagname = "manualLayout"
|
||||
|
||||
layoutTarget = NestedNoneSet(values=(['inner', 'outer']))
|
||||
xMode = NestedNoneSet(values=(['edge', 'factor']))
|
||||
yMode = NestedNoneSet(values=(['edge', 'factor']))
|
||||
wMode = NestedSet(values=(['edge', 'factor']))
|
||||
hMode = NestedSet(values=(['edge', 'factor']))
|
||||
x = NestedMinMax(min=-1, max=1, allow_none=True)
|
||||
y = NestedMinMax(min=-1, max=1, allow_none=True)
|
||||
w = NestedMinMax(min=0, max=1, allow_none=True)
|
||||
width = Alias('w')
|
||||
h = NestedMinMax(min=0, max=1, allow_none=True)
|
||||
height = Alias('h')
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x',
|
||||
'y', 'w', 'h')
|
||||
|
||||
def __init__(self,
|
||||
layoutTarget=None,
|
||||
xMode=None,
|
||||
yMode=None,
|
||||
wMode="factor",
|
||||
hMode="factor",
|
||||
x=None,
|
||||
y=None,
|
||||
w=None,
|
||||
h=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.layoutTarget = layoutTarget
|
||||
self.xMode = xMode
|
||||
self.yMode = yMode
|
||||
self.wMode = wMode
|
||||
self.hMode = hMode
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
|
||||
|
||||
class Layout(Serialisable):
|
||||
|
||||
tagname = "layout"
|
||||
|
||||
manualLayout = Typed(expected_type=ManualLayout, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('manualLayout',)
|
||||
|
||||
def __init__(self,
|
||||
manualLayout=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.manualLayout = manualLayout
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedSet,
|
||||
NestedInteger
|
||||
)
|
||||
|
||||
from .layout import Layout
|
||||
from .shapes import GraphicalProperties
|
||||
from .text import RichText
|
||||
|
||||
|
||||
class LegendEntry(Serialisable):
|
||||
|
||||
tagname = "legendEntry"
|
||||
|
||||
idx = NestedInteger()
|
||||
delete = NestedBool()
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('idx', 'delete', 'txPr')
|
||||
|
||||
def __init__(self,
|
||||
idx=0,
|
||||
delete=False,
|
||||
txPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.delete = delete
|
||||
self.txPr = txPr
|
||||
|
||||
|
||||
class Legend(Serialisable):
|
||||
|
||||
tagname = "legend"
|
||||
|
||||
legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't']))
|
||||
position = Alias('legendPos')
|
||||
legendEntry = Sequence(expected_type=LegendEntry)
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
overlay = NestedBool(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textProperties = Alias('txPr')
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',)
|
||||
|
||||
def __init__(self,
|
||||
legendPos="r",
|
||||
legendEntry=(),
|
||||
layout=None,
|
||||
overlay=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.legendPos = legendPos
|
||||
self.legendEntry = legendEntry
|
||||
self.layout = layout
|
||||
self.overlay = overlay
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Sequence,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedSet,
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .updown_bars import UpDownBars
|
||||
from .descriptors import NestedGapAmount
|
||||
from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines, _BaseAxis
|
||||
from .label import DataLabelList
|
||||
from .series import Series
|
||||
|
||||
|
||||
class _LineChartBase(ChartBase):
|
||||
|
||||
grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
|
||||
varyColors = NestedBool(allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
dropLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
|
||||
_series_type = "line"
|
||||
|
||||
__elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
|
||||
|
||||
def __init__(self,
|
||||
grouping="standard",
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
dropLines=None,
|
||||
**kw
|
||||
):
|
||||
self.grouping = grouping
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.dropLines = dropLines
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class LineChart(_LineChartBase):
|
||||
|
||||
tagname = "lineChart"
|
||||
|
||||
grouping = _LineChartBase.grouping
|
||||
varyColors = _LineChartBase.varyColors
|
||||
ser = _LineChartBase.ser
|
||||
dLbls = _LineChartBase.dLbls
|
||||
dropLines =_LineChartBase.dropLines
|
||||
|
||||
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
|
||||
marker = NestedBool(allow_none=True)
|
||||
smooth = NestedBool(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=_BaseAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
__elements__ = _LineChartBase.__elements__ + ('hiLowLines', 'upDownBars', 'marker', 'smooth', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
hiLowLines=None,
|
||||
upDownBars=None,
|
||||
marker=None,
|
||||
smooth=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.hiLowLines = hiLowLines
|
||||
self.upDownBars = upDownBars
|
||||
self.marker = marker
|
||||
self.smooth = smooth
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class LineChart3D(_LineChartBase):
|
||||
|
||||
tagname = "line3DChart"
|
||||
|
||||
grouping = _LineChartBase.grouping
|
||||
varyColors = _LineChartBase.varyColors
|
||||
ser = _LineChartBase.ser
|
||||
dLbls = _LineChartBase.dLbls
|
||||
dropLines =_LineChartBase.dropLines
|
||||
|
||||
gapDepth = NestedGapAmount()
|
||||
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
|
||||
marker = NestedBool(allow_none=True)
|
||||
smooth = NestedBool(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
z_axis = Typed(expected_type=SeriesAxis)
|
||||
|
||||
__elements__ = _LineChartBase.__elements__ + ('gapDepth', 'hiLowLines',
|
||||
'upDownBars', 'marker', 'smooth', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
gapDepth=None,
|
||||
hiLowLines=None,
|
||||
upDownBars=None,
|
||||
marker=None,
|
||||
smooth=None,
|
||||
**kw
|
||||
):
|
||||
self.gapDepth = gapDepth
|
||||
self.hiLowLines = hiLowLines
|
||||
self.upDownBars = upDownBars
|
||||
self.marker = marker
|
||||
self.smooth = smooth
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
self.z_axis = SeriesAxis()
|
||||
super(LineChart3D, self).__init__(**kw)
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import(
|
||||
ExtensionList,
|
||||
_explicit_none,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedMinMax,
|
||||
NestedNoneSet,
|
||||
)
|
||||
|
||||
from .layout import Layout
|
||||
from .picture import PictureOptions
|
||||
from .shapes import *
|
||||
from .text import *
|
||||
from .error_bar import *
|
||||
|
||||
|
||||
class Marker(Serialisable):
|
||||
|
||||
tagname = "marker"
|
||||
|
||||
symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture',
|
||||
'plus', 'square', 'star', 'triangle', 'x', 'auto']),
|
||||
to_tree=_explicit_none)
|
||||
size = NestedMinMax(min=2, max=72, allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('symbol', 'size', 'spPr')
|
||||
|
||||
def __init__(self,
|
||||
symbol=None,
|
||||
size=None,
|
||||
spPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.symbol = symbol
|
||||
self.size = size
|
||||
if spPr is None:
|
||||
spPr = GraphicalProperties()
|
||||
self.spPr = spPr
|
||||
|
||||
|
||||
class DataPoint(Serialisable):
|
||||
|
||||
tagname = "dPt"
|
||||
|
||||
idx = NestedInteger()
|
||||
invertIfNegative = NestedBool(allow_none=True)
|
||||
marker = Typed(expected_type=Marker, allow_none=True)
|
||||
bubble3D = NestedBool(allow_none=True)
|
||||
explosion = NestedInteger(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D',
|
||||
'explosion', 'spPr', 'pictureOptions')
|
||||
|
||||
def __init__(self,
|
||||
idx=None,
|
||||
invertIfNegative=None,
|
||||
marker=None,
|
||||
bubble3D=None,
|
||||
explosion=None,
|
||||
spPr=None,
|
||||
pictureOptions=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.invertIfNegative = invertIfNegative
|
||||
self.marker = marker
|
||||
self.bubble3D = bubble3D
|
||||
self.explosion = explosion
|
||||
if spPr is None:
|
||||
spPr = GraphicalProperties()
|
||||
self.spPr = spPr
|
||||
self.pictureOptions = pictureOptions
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedFloat,
|
||||
NestedMinMax,
|
||||
NestedNoneSet,
|
||||
)
|
||||
|
||||
class PictureOptions(Serialisable):
|
||||
|
||||
tagname = "pictureOptions"
|
||||
|
||||
applyToFront = NestedBool(allow_none=True, nested=True)
|
||||
applyToSides = NestedBool(allow_none=True, nested=True)
|
||||
applyToEnd = NestedBool(allow_none=True, nested=True)
|
||||
pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True)
|
||||
pictureStackUnit = NestedFloat(allow_none=True, nested=True)
|
||||
|
||||
__elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit')
|
||||
|
||||
def __init__(self,
|
||||
applyToFront=None,
|
||||
applyToSides=None,
|
||||
applyToEnd=None,
|
||||
pictureFormat=None,
|
||||
pictureStackUnit=None,
|
||||
):
|
||||
self.applyToFront = applyToFront
|
||||
self.applyToSides = applyToSides
|
||||
self.applyToEnd = applyToEnd
|
||||
self.pictureFormat = pictureFormat
|
||||
self.pictureStackUnit = pictureStackUnit
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
#Autogenerated schema
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
MinMax,
|
||||
Integer,
|
||||
NoneSet,
|
||||
Float,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList, Percentage
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedMinMax,
|
||||
NestedInteger,
|
||||
NestedFloat,
|
||||
NestedNoneSet,
|
||||
NestedSet,
|
||||
)
|
||||
from openpyxl.descriptors.sequence import ValueSequence
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .axis import ChartLines
|
||||
from .descriptors import NestedGapAmount
|
||||
from .series import Series
|
||||
from .label import DataLabelList
|
||||
|
||||
|
||||
class _PieChartBase(ChartBase):
|
||||
|
||||
varyColors = NestedBool(allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
|
||||
_series_type = "pie"
|
||||
|
||||
__elements__ = ('varyColors', 'ser', 'dLbls')
|
||||
|
||||
def __init__(self,
|
||||
varyColors=True,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
):
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
super().__init__()
|
||||
|
||||
|
||||
|
||||
class PieChart(_PieChartBase):
|
||||
|
||||
tagname = "pieChart"
|
||||
|
||||
varyColors = _PieChartBase.varyColors
|
||||
ser = _PieChartBase.ser
|
||||
dLbls = _PieChartBase.dLbls
|
||||
|
||||
firstSliceAng = NestedMinMax(min=0, max=360)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _PieChartBase.__elements__ + ('firstSliceAng', )
|
||||
|
||||
def __init__(self,
|
||||
firstSliceAng=0,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.firstSliceAng = firstSliceAng
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class PieChart3D(_PieChartBase):
|
||||
|
||||
tagname = "pie3DChart"
|
||||
|
||||
varyColors = _PieChartBase.varyColors
|
||||
ser = _PieChartBase.ser
|
||||
dLbls = _PieChartBase.dLbls
|
||||
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _PieChartBase.__elements__
|
||||
|
||||
|
||||
class DoughnutChart(_PieChartBase):
|
||||
|
||||
tagname = "doughnutChart"
|
||||
|
||||
varyColors = _PieChartBase.varyColors
|
||||
ser = _PieChartBase.ser
|
||||
dLbls = _PieChartBase.dLbls
|
||||
|
||||
firstSliceAng = NestedMinMax(min=0, max=360)
|
||||
holeSize = NestedMinMax(min=1, max=90, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _PieChartBase.__elements__ + ('firstSliceAng', 'holeSize')
|
||||
|
||||
def __init__(self,
|
||||
firstSliceAng=0,
|
||||
holeSize=10,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.firstSliceAng = firstSliceAng
|
||||
self.holeSize = holeSize
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class CustomSplit(Serialisable):
|
||||
|
||||
tagname = "custSplit"
|
||||
|
||||
secondPiePt = ValueSequence(expected_type=int)
|
||||
|
||||
__elements__ = ('secondPiePt',)
|
||||
|
||||
def __init__(self,
|
||||
secondPiePt=(),
|
||||
):
|
||||
self.secondPiePt = secondPiePt
|
||||
|
||||
|
||||
class ProjectedPieChart(_PieChartBase):
|
||||
|
||||
"""
|
||||
From the spec 21.2.2.126
|
||||
|
||||
This element contains the pie of pie or bar of pie series on this
|
||||
chart. Only the first series shall be displayed. The splitType element
|
||||
shall determine whether the splitPos and custSplit elements apply.
|
||||
"""
|
||||
|
||||
tagname = "ofPieChart"
|
||||
|
||||
varyColors = _PieChartBase.varyColors
|
||||
ser = _PieChartBase.ser
|
||||
dLbls = _PieChartBase.dLbls
|
||||
|
||||
ofPieType = NestedSet(values=(['pie', 'bar']))
|
||||
type = Alias('ofPieType')
|
||||
gapWidth = NestedGapAmount()
|
||||
splitType = NestedNoneSet(values=(['auto', 'cust', 'percent', 'pos', 'val']))
|
||||
splitPos = NestedFloat(allow_none=True)
|
||||
custSplit = Typed(expected_type=CustomSplit, allow_none=True)
|
||||
secondPieSize = NestedMinMax(min=5, max=200, allow_none=True)
|
||||
serLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
join_lines = Alias('serLines')
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = _PieChartBase.__elements__ + ('ofPieType', 'gapWidth',
|
||||
'splitType', 'splitPos', 'custSplit', 'secondPieSize', 'serLines')
|
||||
|
||||
def __init__(self,
|
||||
ofPieType="pie",
|
||||
gapWidth=None,
|
||||
splitType="auto",
|
||||
splitPos=None,
|
||||
custSplit=None,
|
||||
secondPieSize=75,
|
||||
serLines=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.ofPieType = ofPieType
|
||||
self.gapWidth = gapWidth
|
||||
self.splitType = splitType
|
||||
self.splitPos = splitPos
|
||||
self.custSplit = custSplit
|
||||
self.secondPieSize = secondPieSize
|
||||
if serLines is None:
|
||||
self.serLines = ChartLines()
|
||||
super().__init__(**kw)
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Typed,
|
||||
)
|
||||
from openpyxl.descriptors.nested import NestedInteger, NestedText
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
|
||||
from .label import DataLabel
|
||||
from .marker import Marker
|
||||
from .shapes import GraphicalProperties
|
||||
from .text import RichText
|
||||
|
||||
|
||||
class PivotSource(Serialisable):
|
||||
|
||||
tagname = "pivotSource"
|
||||
|
||||
name = NestedText(expected_type=str)
|
||||
fmtId = NestedInteger(expected_type=int)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('name', 'fmtId')
|
||||
|
||||
def __init__(self,
|
||||
name=None,
|
||||
fmtId=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.name = name
|
||||
self.fmtId = fmtId
|
||||
|
||||
|
||||
class PivotFormat(Serialisable):
|
||||
|
||||
tagname = "pivotFmt"
|
||||
|
||||
idx = NestedInteger(nested=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
TextBody = Alias("txPr")
|
||||
marker = Typed(expected_type=Marker, allow_none=True)
|
||||
dLbl = Typed(expected_type=DataLabel, allow_none=True)
|
||||
DataLabel = Alias("dLbl")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl')
|
||||
|
||||
def __init__(self,
|
||||
idx=0,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
marker=None,
|
||||
dLbl=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
self.marker = marker
|
||||
self.dLbl = dLbl
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import (
|
||||
ExtensionList,
|
||||
)
|
||||
from openpyxl.descriptors.sequence import (
|
||||
MultiSequence,
|
||||
MultiSequencePart,
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._3d import _3DBase
|
||||
from .area_chart import AreaChart, AreaChart3D
|
||||
from .bar_chart import BarChart, BarChart3D
|
||||
from .bubble_chart import BubbleChart
|
||||
from .line_chart import LineChart, LineChart3D
|
||||
from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart
|
||||
from .radar_chart import RadarChart
|
||||
from .scatter_chart import ScatterChart
|
||||
from .stock_chart import StockChart
|
||||
from .surface_chart import SurfaceChart, SurfaceChart3D
|
||||
from .layout import Layout
|
||||
from .shapes import GraphicalProperties
|
||||
from .text import RichText
|
||||
|
||||
from .axis import (
|
||||
NumericAxis,
|
||||
TextAxis,
|
||||
SeriesAxis,
|
||||
DateAxis,
|
||||
)
|
||||
|
||||
|
||||
class DataTable(Serialisable):
|
||||
|
||||
tagname = "dTable"
|
||||
|
||||
showHorzBorder = NestedBool(allow_none=True)
|
||||
showVertBorder = NestedBool(allow_none=True)
|
||||
showOutline = NestedBool(allow_none=True)
|
||||
showKeys = NestedBool(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline',
|
||||
'showKeys', 'spPr', 'txPr')
|
||||
|
||||
def __init__(self,
|
||||
showHorzBorder=None,
|
||||
showVertBorder=None,
|
||||
showOutline=None,
|
||||
showKeys=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.showHorzBorder = showHorzBorder
|
||||
self.showVertBorder = showVertBorder
|
||||
self.showOutline = showOutline
|
||||
self.showKeys = showKeys
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
|
||||
|
||||
class PlotArea(Serialisable):
|
||||
|
||||
tagname = "plotArea"
|
||||
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
dTable = Typed(expected_type=DataTable, allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
# at least one chart
|
||||
_charts = MultiSequence()
|
||||
areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts")
|
||||
area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts")
|
||||
lineChart = MultiSequencePart(expected_type=LineChart, store="_charts")
|
||||
line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts")
|
||||
stockChart = MultiSequencePart(expected_type=StockChart, store="_charts")
|
||||
radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts")
|
||||
scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts")
|
||||
pieChart = MultiSequencePart(expected_type=PieChart, store="_charts")
|
||||
pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts")
|
||||
doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts")
|
||||
barChart = MultiSequencePart(expected_type=BarChart, store="_charts")
|
||||
bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts")
|
||||
ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts")
|
||||
surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts")
|
||||
surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts")
|
||||
bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts")
|
||||
|
||||
# axes
|
||||
_axes = MultiSequence()
|
||||
valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes")
|
||||
catAx = MultiSequencePart(expected_type=TextAxis, store="_axes")
|
||||
dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes")
|
||||
serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes")
|
||||
|
||||
__elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr')
|
||||
|
||||
def __init__(self,
|
||||
layout=None,
|
||||
dTable=None,
|
||||
spPr=None,
|
||||
_charts=(),
|
||||
_axes=(),
|
||||
extLst=None,
|
||||
):
|
||||
self.layout = layout
|
||||
self.dTable = dTable
|
||||
self.spPr = spPr
|
||||
self._charts = _charts
|
||||
self._axes = _axes
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
axIds = {ax.axId for ax in self._axes}
|
||||
for chart in self._charts:
|
||||
for id, axis in chart._axes.items():
|
||||
if id not in axIds:
|
||||
setattr(self, axis.tagname, axis)
|
||||
axIds.add(id)
|
||||
|
||||
return super().to_tree(tagname)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
self = super().from_tree(node)
|
||||
axes = dict((axis.axId, axis) for axis in self._axes)
|
||||
for chart in self._charts:
|
||||
if isinstance(chart, (ScatterChart, BubbleChart)):
|
||||
x, y = (axes[axId] for axId in chart.axId)
|
||||
chart.x_axis = x
|
||||
chart.y_axis = y
|
||||
continue
|
||||
|
||||
for axId in chart.axId:
|
||||
axis = axes.get(axId)
|
||||
if axis is None and isinstance(chart, _3DBase):
|
||||
# Series Axis can be optional
|
||||
chart.z_axis = None
|
||||
continue
|
||||
if axis.tagname in ("catAx", "dateAx"):
|
||||
chart.x_axis = axis
|
||||
elif axis.tagname == "valAx":
|
||||
chart.y_axis = axis
|
||||
elif axis.tagname == "serAx":
|
||||
chart.z_axis = axis
|
||||
|
||||
return self
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Float,
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.worksheet.page import PrintPageSetup
|
||||
from openpyxl.worksheet.header_footer import HeaderFooter
|
||||
|
||||
|
||||
class PageMargins(Serialisable):
|
||||
"""
|
||||
Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/
|
||||
"""
|
||||
tagname = "pageMargins"
|
||||
|
||||
l = Float()
|
||||
left = Alias('l')
|
||||
r = Float()
|
||||
right = Alias('r')
|
||||
t = Float()
|
||||
top = Alias('t')
|
||||
b = Float()
|
||||
bottom = Alias('b')
|
||||
header = Float()
|
||||
footer = Float()
|
||||
|
||||
def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5):
|
||||
self.l = l
|
||||
self.r = r
|
||||
self.t = t
|
||||
self.b = b
|
||||
self.header = header
|
||||
self.footer = footer
|
||||
|
||||
|
||||
class PrintSettings(Serialisable):
|
||||
|
||||
tagname = "printSettings"
|
||||
|
||||
headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
|
||||
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
|
||||
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
|
||||
|
||||
__elements__ = ("headerFooter", "pageMargins", "pageMargins")
|
||||
|
||||
def __init__(self,
|
||||
headerFooter=None,
|
||||
pageMargins=None,
|
||||
pageSetup=None,
|
||||
):
|
||||
self.headerFooter = headerFooter
|
||||
self.pageMargins = pageMargins
|
||||
self.pageSetup = pageSetup
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Sequence,
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedSet
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .axis import TextAxis, NumericAxis
|
||||
from .series import Series
|
||||
from .label import DataLabelList
|
||||
|
||||
|
||||
class RadarChart(ChartBase):
|
||||
|
||||
tagname = "radarChart"
|
||||
|
||||
radarStyle = NestedSet(values=(['standard', 'marker', 'filled']))
|
||||
type = Alias("radarStyle")
|
||||
varyColors = NestedBool(nested=True, allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
_series_type = "radar"
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
__elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId')
|
||||
|
||||
def __init__(self,
|
||||
radarStyle="standard",
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.radarStyle = radarStyle
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
super().__init__(**kw)
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Read a chart
|
||||
"""
|
||||
|
||||
def read_chart(chartspace):
|
||||
cs = chartspace
|
||||
plot = cs.chart.plotArea
|
||||
|
||||
chart = plot._charts[0]
|
||||
chart._charts = plot._charts
|
||||
|
||||
chart.title = cs.chart.title
|
||||
chart.display_blanks = cs.chart.dispBlanksAs
|
||||
chart.visible_cells_only = cs.chart.plotVisOnly
|
||||
chart.layout = plot.layout
|
||||
chart.legend = cs.chart.legend
|
||||
|
||||
# 3d attributes
|
||||
chart.floor = cs.chart.floor
|
||||
chart.sideWall = cs.chart.sideWall
|
||||
chart.backWall = cs.chart.backWall
|
||||
chart.pivotSource = cs.pivotSource
|
||||
chart.pivotFormats = cs.chart.pivotFmts
|
||||
chart.idx_base = min((s.idx for s in chart.series), default=0)
|
||||
chart._reindex()
|
||||
|
||||
# Border, fill, etc.
|
||||
chart.graphical_properties = cs.graphical_properties
|
||||
|
||||
return chart
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
MinMax,
|
||||
Typed,
|
||||
String,
|
||||
Strict,
|
||||
)
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
from openpyxl.utils import (
|
||||
get_column_letter,
|
||||
range_to_tuple,
|
||||
quote_sheetname
|
||||
)
|
||||
|
||||
|
||||
class DummyWorksheet:
|
||||
|
||||
|
||||
def __init__(self, title):
|
||||
self.title = title
|
||||
|
||||
|
||||
class Reference(Strict):
|
||||
|
||||
"""
|
||||
Normalise cell range references
|
||||
"""
|
||||
|
||||
min_row = MinMax(min=1, max=1000000, expected_type=int)
|
||||
max_row = MinMax(min=1, max=1000000, expected_type=int)
|
||||
min_col = MinMax(min=1, max=16384, expected_type=int)
|
||||
max_col = MinMax(min=1, max=16384, expected_type=int)
|
||||
range_string = String(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
worksheet=None,
|
||||
min_col=None,
|
||||
min_row=None,
|
||||
max_col=None,
|
||||
max_row=None,
|
||||
range_string=None
|
||||
):
|
||||
if range_string is not None:
|
||||
sheetname, boundaries = range_to_tuple(range_string)
|
||||
min_col, min_row, max_col, max_row = boundaries
|
||||
worksheet = DummyWorksheet(sheetname)
|
||||
|
||||
self.worksheet = worksheet
|
||||
self.min_col = min_col
|
||||
self.min_row = min_row
|
||||
if max_col is None:
|
||||
max_col = min_col
|
||||
self.max_col = max_col
|
||||
if max_row is None:
|
||||
max_row = min_row
|
||||
self.max_row = max_row
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
fmt = u"{0}!${1}${2}:${3}${4}"
|
||||
if (self.min_col == self.max_col
|
||||
and self.min_row == self.max_row):
|
||||
fmt = u"{0}!${1}${2}"
|
||||
return fmt.format(self.sheetname,
|
||||
get_column_letter(self.min_col), self.min_row,
|
||||
get_column_letter(self.max_col), self.max_row
|
||||
)
|
||||
|
||||
|
||||
__str__ = __str__
|
||||
|
||||
|
||||
|
||||
def __len__(self):
|
||||
if self.min_row == self.max_row:
|
||||
return 1 + self.max_col - self.min_col
|
||||
return 1 + self.max_row - self.min_row
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return str(self) == str(other)
|
||||
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
"""
|
||||
Return all rows in the range
|
||||
"""
|
||||
for row in range(self.min_row, self.max_row+1):
|
||||
yield Reference(self.worksheet, self.min_col, row, self.max_col, row)
|
||||
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
"""
|
||||
Return all columns in the range
|
||||
"""
|
||||
for col in range(self.min_col, self.max_col+1):
|
||||
yield Reference(self.worksheet, col, self.min_row, col, self.max_row)
|
||||
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Return and remove the first cell
|
||||
"""
|
||||
cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row)
|
||||
if self.min_row == self.max_row:
|
||||
self.min_col += 1
|
||||
else:
|
||||
self.min_row += 1
|
||||
return cell
|
||||
|
||||
|
||||
@property
|
||||
def sheetname(self):
|
||||
return quote_sheetname(self.worksheet.title)
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Sequence,
|
||||
Alias
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .axis import NumericAxis, TextAxis
|
||||
from .series import XYSeries
|
||||
from .label import DataLabelList
|
||||
|
||||
|
||||
class ScatterChart(ChartBase):
|
||||
|
||||
tagname = "scatterChart"
|
||||
|
||||
scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker']))
|
||||
varyColors = NestedBool(allow_none=True)
|
||||
ser = Sequence(expected_type=XYSeries, allow_none=True)
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias("dLbls")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=(NumericAxis, TextAxis))
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
_series_type = "scatter"
|
||||
|
||||
__elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',)
|
||||
|
||||
def __init__(self,
|
||||
scatterStyle=None,
|
||||
varyColors=None,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.scatterStyle = scatterStyle
|
||||
self.varyColors = varyColors
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.x_axis = NumericAxis(axId=10, crossAx=20)
|
||||
self.y_axis = NumericAxis(axId=20, crossAx=10)
|
||||
super().__init__(**kw)
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
String,
|
||||
Integer,
|
||||
Bool,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedInteger,
|
||||
NestedBool,
|
||||
NestedNoneSet,
|
||||
NestedText,
|
||||
)
|
||||
|
||||
from .shapes import GraphicalProperties
|
||||
from .data_source import (
|
||||
AxDataSource,
|
||||
NumDataSource,
|
||||
NumRef,
|
||||
StrRef,
|
||||
)
|
||||
from .error_bar import ErrorBars
|
||||
from .label import DataLabelList
|
||||
from .marker import DataPoint, PictureOptions, Marker
|
||||
from .trendline import Trendline
|
||||
|
||||
attribute_mapping = {
|
||||
'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars',
|
||||
'trendline', 'cat', 'val',),
|
||||
'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt',
|
||||
'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'),
|
||||
'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls',
|
||||
'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'),
|
||||
'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
|
||||
'errBars', 'cat', 'val', 'smooth'),
|
||||
'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'),
|
||||
'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'),
|
||||
'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
|
||||
'errBars', 'xVal', 'yVal', 'smooth'),
|
||||
'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'),
|
||||
}
|
||||
|
||||
|
||||
class SeriesLabel(Serialisable):
|
||||
|
||||
tagname = "tx"
|
||||
|
||||
strRef = Typed(expected_type=StrRef, allow_none=True)
|
||||
v = NestedText(expected_type=str, allow_none=True)
|
||||
value = Alias('v')
|
||||
|
||||
__elements__ = ('strRef', 'v')
|
||||
|
||||
def __init__(self,
|
||||
strRef=None,
|
||||
v=None):
|
||||
self.strRef = strRef
|
||||
self.v = v
|
||||
|
||||
|
||||
class Series(Serialisable):
|
||||
|
||||
"""
|
||||
Generic series object. Should not be instantiated directly.
|
||||
User the chart.Series factory instead.
|
||||
"""
|
||||
|
||||
tagname = "ser"
|
||||
|
||||
idx = NestedInteger()
|
||||
order = NestedInteger()
|
||||
tx = Typed(expected_type=SeriesLabel, allow_none=True)
|
||||
title = Alias('tx')
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
|
||||
# area chart
|
||||
pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
|
||||
dPt = Sequence(expected_type=DataPoint, allow_none=True)
|
||||
data_points = Alias("dPt")
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
labels = Alias("dLbls")
|
||||
trendline = Typed(expected_type=Trendline, allow_none=True)
|
||||
errBars = Typed(expected_type=ErrorBars, allow_none=True)
|
||||
cat = Typed(expected_type=AxDataSource, allow_none=True)
|
||||
identifiers = Alias("cat")
|
||||
val = Typed(expected_type=NumDataSource, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
#bar chart
|
||||
invertIfNegative = NestedBool(allow_none=True)
|
||||
shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
|
||||
|
||||
#bubble chart
|
||||
xVal = Typed(expected_type=AxDataSource, allow_none=True)
|
||||
yVal = Typed(expected_type=NumDataSource, allow_none=True)
|
||||
bubbleSize = Typed(expected_type=NumDataSource, allow_none=True)
|
||||
zVal = Alias("bubbleSize")
|
||||
bubble3D = NestedBool(allow_none=True)
|
||||
|
||||
#line chart
|
||||
marker = Typed(expected_type=Marker, allow_none=True)
|
||||
smooth = NestedBool(allow_none=True)
|
||||
|
||||
#pie chart
|
||||
explosion = NestedInteger(allow_none=True)
|
||||
|
||||
__elements__ = ()
|
||||
|
||||
|
||||
def __init__(self,
|
||||
idx=0,
|
||||
order=0,
|
||||
tx=None,
|
||||
spPr=None,
|
||||
pictureOptions=None,
|
||||
dPt=(),
|
||||
dLbls=None,
|
||||
trendline=None,
|
||||
errBars=None,
|
||||
cat=None,
|
||||
val=None,
|
||||
invertIfNegative=None,
|
||||
shape=None,
|
||||
xVal=None,
|
||||
yVal=None,
|
||||
bubbleSize=None,
|
||||
bubble3D=None,
|
||||
marker=None,
|
||||
smooth=None,
|
||||
explosion=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.order = order
|
||||
self.tx = tx
|
||||
if spPr is None:
|
||||
spPr = GraphicalProperties()
|
||||
self.spPr = spPr
|
||||
self.pictureOptions = pictureOptions
|
||||
self.dPt = dPt
|
||||
self.dLbls = dLbls
|
||||
self.trendline = trendline
|
||||
self.errBars = errBars
|
||||
self.cat = cat
|
||||
self.val = val
|
||||
self.invertIfNegative = invertIfNegative
|
||||
self.shape = shape
|
||||
self.xVal = xVal
|
||||
self.yVal = yVal
|
||||
self.bubbleSize = bubbleSize
|
||||
self.bubble3D = bubble3D
|
||||
if marker is None:
|
||||
marker = Marker()
|
||||
self.marker = marker
|
||||
self.smooth = smooth
|
||||
self.explosion = explosion
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None):
|
||||
"""The index can need rebasing"""
|
||||
if idx is not None:
|
||||
if self.order == self.idx:
|
||||
self.order = idx # rebase the order if the index has been rebased
|
||||
self.idx = idx
|
||||
return super().to_tree(tagname)
|
||||
|
||||
|
||||
class XYSeries(Series):
|
||||
|
||||
"""Dedicated series for charts that have x and y series"""
|
||||
|
||||
idx = Series.idx
|
||||
order = Series.order
|
||||
tx = Series.tx
|
||||
spPr = Series.spPr
|
||||
|
||||
dPt = Series.dPt
|
||||
dLbls = Series.dLbls
|
||||
trendline = Series.trendline
|
||||
errBars = Series.errBars
|
||||
xVal = Series.xVal
|
||||
yVal = Series.yVal
|
||||
|
||||
invertIfNegative = Series.invertIfNegative
|
||||
|
||||
bubbleSize = Series.bubbleSize
|
||||
bubble3D = Series.bubble3D
|
||||
|
||||
marker = Series.marker
|
||||
smooth = Series.smooth
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .data_source import NumDataSource, NumRef, AxDataSource
|
||||
from .reference import Reference
|
||||
from .series import Series, XYSeries, SeriesLabel, StrRef
|
||||
from openpyxl.utils import rows_from_range, quote_sheetname
|
||||
|
||||
|
||||
def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False):
|
||||
"""
|
||||
Convenience Factory for creating chart data series.
|
||||
"""
|
||||
|
||||
if not isinstance(values, Reference):
|
||||
values = Reference(range_string=values)
|
||||
|
||||
if title_from_data:
|
||||
cell = values.pop()
|
||||
title = u"{0}!{1}".format(values.sheetname, cell)
|
||||
title = SeriesLabel(strRef=StrRef(title))
|
||||
elif title is not None:
|
||||
title = SeriesLabel(v=title)
|
||||
|
||||
source = NumDataSource(numRef=NumRef(f=values))
|
||||
if xvalues is not None:
|
||||
if not isinstance(xvalues, Reference):
|
||||
xvalues = Reference(range_string=xvalues)
|
||||
series = XYSeries()
|
||||
series.yVal = source
|
||||
series.xVal = AxDataSource(numRef=NumRef(f=xvalues))
|
||||
if zvalues is not None:
|
||||
if not isinstance(zvalues, Reference):
|
||||
zvalues = Reference(range_string=zvalues)
|
||||
series.zVal = NumDataSource(NumRef(f=zvalues))
|
||||
else:
|
||||
series = Series()
|
||||
series.val = source
|
||||
|
||||
if title is not None:
|
||||
series.title = title
|
||||
return series
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Alias
|
||||
)
|
||||
from openpyxl.descriptors.nested import (
|
||||
EmptyTag
|
||||
)
|
||||
from openpyxl.drawing.colors import ColorChoiceDescriptor
|
||||
from openpyxl.drawing.fill import *
|
||||
from openpyxl.drawing.line import LineProperties
|
||||
from openpyxl.drawing.geometry import (
|
||||
Shape3D,
|
||||
Scene3D,
|
||||
Transform2D,
|
||||
CustomGeometry2D,
|
||||
PresetGeometry2D,
|
||||
)
|
||||
|
||||
|
||||
class GraphicalProperties(Serialisable):
|
||||
|
||||
"""
|
||||
Somewhat vaguely 21.2.2.197 says this:
|
||||
|
||||
This element specifies the formatting for the parent chart element. The
|
||||
custGeom, prstGeom, scene3d, and xfrm elements are not supported. The
|
||||
bwMode attribute is not supported.
|
||||
|
||||
This doesn't leave much. And the element is used in different places.
|
||||
"""
|
||||
|
||||
tagname = "spPr"
|
||||
|
||||
bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
|
||||
'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']
|
||||
)
|
||||
)
|
||||
|
||||
xfrm = Typed(expected_type=Transform2D, allow_none=True)
|
||||
transform = Alias('xfrm')
|
||||
custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or
|
||||
prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True)
|
||||
|
||||
# fills one of
|
||||
noFill = EmptyTag(namespace=DRAWING_NS)
|
||||
solidFill = ColorChoiceDescriptor()
|
||||
gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
|
||||
pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
|
||||
|
||||
ln = Typed(expected_type=LineProperties, allow_none=True)
|
||||
line = Alias('ln')
|
||||
scene3d = Typed(expected_type=Scene3D, allow_none=True)
|
||||
sp3d = Typed(expected_type=Shape3D, allow_none=True)
|
||||
shape3D = Alias('sp3d')
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill',
|
||||
'ln', 'scene3d', 'sp3d')
|
||||
|
||||
def __init__(self,
|
||||
bwMode=None,
|
||||
xfrm=None,
|
||||
noFill=None,
|
||||
solidFill=None,
|
||||
gradFill=None,
|
||||
pattFill=None,
|
||||
ln=None,
|
||||
scene3d=None,
|
||||
custGeom=None,
|
||||
prstGeom=None,
|
||||
sp3d=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.bwMode = bwMode
|
||||
self.xfrm = xfrm
|
||||
self.noFill = noFill
|
||||
self.solidFill = solidFill
|
||||
self.gradFill = gradFill
|
||||
self.pattFill = pattFill
|
||||
if ln is None:
|
||||
ln = LineProperties()
|
||||
self.ln = ln
|
||||
self.custGeom = custGeom
|
||||
self.prstGeom = prstGeom
|
||||
self.scene3d = scene3d
|
||||
self.sp3d = sp3d
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Sequence,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
|
||||
from ._chart import ChartBase
|
||||
from .axis import TextAxis, NumericAxis, ChartLines
|
||||
from .updown_bars import UpDownBars
|
||||
from .label import DataLabelList
|
||||
from .series import Series
|
||||
|
||||
|
||||
class StockChart(ChartBase):
|
||||
|
||||
tagname = "stockChart"
|
||||
|
||||
ser = Sequence(expected_type=Series) #min 3, max4
|
||||
dLbls = Typed(expected_type=DataLabelList, allow_none=True)
|
||||
dataLabels = Alias('dLbls')
|
||||
dropLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
|
||||
upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
|
||||
_series_type = "line"
|
||||
|
||||
__elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars',
|
||||
'axId')
|
||||
|
||||
def __init__(self,
|
||||
ser=(),
|
||||
dLbls=None,
|
||||
dropLines=None,
|
||||
hiLowLines=None,
|
||||
upDownBars=None,
|
||||
extLst=None,
|
||||
**kw
|
||||
):
|
||||
self.ser = ser
|
||||
self.dLbls = dLbls
|
||||
self.dropLines = dropLines
|
||||
self.hiLowLines = hiLowLines
|
||||
self.upDownBars = upDownBars
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
super().__init__(**kw)
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
Bool,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedInteger,
|
||||
NestedBool,
|
||||
)
|
||||
|
||||
from ._chart import ChartBase
|
||||
from ._3d import _3DBase
|
||||
from .axis import TextAxis, NumericAxis, SeriesAxis
|
||||
from .shapes import GraphicalProperties
|
||||
from .series import Series
|
||||
|
||||
|
||||
class BandFormat(Serialisable):
|
||||
|
||||
tagname = "bandFmt"
|
||||
|
||||
idx = NestedInteger()
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
|
||||
__elements__ = ('idx', 'spPr')
|
||||
|
||||
def __init__(self,
|
||||
idx=0,
|
||||
spPr=None,
|
||||
):
|
||||
self.idx = idx
|
||||
self.spPr = spPr
|
||||
|
||||
|
||||
class BandFormatList(Serialisable):
|
||||
|
||||
tagname = "bandFmts"
|
||||
|
||||
bandFmt = Sequence(expected_type=BandFormat, allow_none=True)
|
||||
|
||||
__elements__ = ('bandFmt',)
|
||||
|
||||
def __init__(self,
|
||||
bandFmt=(),
|
||||
):
|
||||
self.bandFmt = bandFmt
|
||||
|
||||
|
||||
class _SurfaceChartBase(ChartBase):
|
||||
|
||||
wireframe = NestedBool(allow_none=True)
|
||||
ser = Sequence(expected_type=Series, allow_none=True)
|
||||
bandFmts = Typed(expected_type=BandFormatList, allow_none=True)
|
||||
|
||||
_series_type = "surface"
|
||||
|
||||
__elements__ = ('wireframe', 'ser', 'bandFmts')
|
||||
|
||||
def __init__(self,
|
||||
wireframe=None,
|
||||
ser=(),
|
||||
bandFmts=None,
|
||||
**kw
|
||||
):
|
||||
self.wireframe = wireframe
|
||||
self.ser = ser
|
||||
self.bandFmts = bandFmts
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class SurfaceChart3D(_SurfaceChartBase, _3DBase):
|
||||
|
||||
tagname = "surface3DChart"
|
||||
|
||||
wireframe = _SurfaceChartBase.wireframe
|
||||
ser = _SurfaceChartBase.ser
|
||||
bandFmts = _SurfaceChartBase.bandFmts
|
||||
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
x_axis = Typed(expected_type=TextAxis)
|
||||
y_axis = Typed(expected_type=NumericAxis)
|
||||
z_axis = Typed(expected_type=SeriesAxis)
|
||||
|
||||
__elements__ = _SurfaceChartBase.__elements__ + ('axId',)
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.x_axis = TextAxis()
|
||||
self.y_axis = NumericAxis()
|
||||
self.z_axis = SeriesAxis()
|
||||
super(SurfaceChart3D, self).__init__(**kw)
|
||||
|
||||
|
||||
class SurfaceChart(SurfaceChart3D):
|
||||
|
||||
tagname = "surfaceChart"
|
||||
|
||||
wireframe = _SurfaceChartBase.wireframe
|
||||
ser = _SurfaceChartBase.ser
|
||||
bandFmts = _SurfaceChartBase.bandFmts
|
||||
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = SurfaceChart3D.__elements__
|
||||
|
||||
def __init__(self, **kw):
|
||||
super().__init__(**kw)
|
||||
self.y_axis.delete = True
|
||||
self.view3D.x_rotation = 90
|
||||
self.view3D.y_rotation = 0
|
||||
self.view3D.perspective = False
|
||||
self.view3D.right_angle_axes = False
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Alias,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
|
||||
from openpyxl.drawing.text import (
|
||||
RichTextProperties,
|
||||
ListStyle,
|
||||
Paragraph,
|
||||
)
|
||||
|
||||
from .data_source import StrRef
|
||||
|
||||
|
||||
class RichText(Serialisable):
|
||||
|
||||
"""
|
||||
From the specification: 21.2.2.216
|
||||
|
||||
This element specifies text formatting. The lstStyle element is not supported.
|
||||
"""
|
||||
|
||||
tagname = "rich"
|
||||
|
||||
bodyPr = Typed(expected_type=RichTextProperties)
|
||||
properties = Alias("bodyPr")
|
||||
lstStyle = Typed(expected_type=ListStyle, allow_none=True)
|
||||
p = Sequence(expected_type=Paragraph)
|
||||
paragraphs = Alias('p')
|
||||
|
||||
__elements__ = ("bodyPr", "lstStyle", "p")
|
||||
|
||||
def __init__(self,
|
||||
bodyPr=None,
|
||||
lstStyle=None,
|
||||
p=None,
|
||||
):
|
||||
if bodyPr is None:
|
||||
bodyPr = RichTextProperties()
|
||||
self.bodyPr = bodyPr
|
||||
self.lstStyle = lstStyle
|
||||
if p is None:
|
||||
p = [Paragraph()]
|
||||
self.p = p
|
||||
|
||||
|
||||
class Text(Serialisable):
|
||||
|
||||
"""
|
||||
The value can be either a cell reference or a text element
|
||||
If both are present then the reference will be used.
|
||||
"""
|
||||
|
||||
tagname = "tx"
|
||||
|
||||
strRef = Typed(expected_type=StrRef, allow_none=True)
|
||||
rich = Typed(expected_type=RichText, allow_none=True)
|
||||
|
||||
__elements__ = ("strRef", "rich")
|
||||
|
||||
def __init__(self,
|
||||
strRef=None,
|
||||
rich=None
|
||||
):
|
||||
self.strRef = strRef
|
||||
if rich is None:
|
||||
rich = RichText()
|
||||
self.rich = rich
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
if self.strRef and self.rich:
|
||||
self.rich = None # can only have one
|
||||
return super().to_tree(tagname, idx, namespace)
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Alias,
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import NestedBool
|
||||
|
||||
from .text import Text, RichText
|
||||
from .layout import Layout
|
||||
from .shapes import GraphicalProperties
|
||||
|
||||
from openpyxl.drawing.text import (
|
||||
Paragraph,
|
||||
RegularTextRun,
|
||||
LineBreak,
|
||||
ParagraphProperties,
|
||||
CharacterProperties,
|
||||
)
|
||||
|
||||
|
||||
class Title(Serialisable):
|
||||
tagname = "title"
|
||||
|
||||
tx = Typed(expected_type=Text, allow_none=True)
|
||||
text = Alias('tx')
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
overlay = NestedBool(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
body = Alias('txPr')
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr')
|
||||
|
||||
def __init__(self,
|
||||
tx=None,
|
||||
layout=None,
|
||||
overlay=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
if tx is None:
|
||||
tx = Text()
|
||||
self.tx = tx
|
||||
self.layout = layout
|
||||
self.overlay = overlay
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
|
||||
|
||||
|
||||
def title_maker(text):
|
||||
title = Title()
|
||||
paraprops = ParagraphProperties()
|
||||
paraprops.defRPr = CharacterProperties()
|
||||
paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")]
|
||||
|
||||
title.tx.rich.paragraphs = paras
|
||||
return title
|
||||
|
||||
|
||||
class TitleDescriptor(Typed):
|
||||
|
||||
expected_type = Title
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str):
|
||||
value = title_maker(value)
|
||||
super().__set__(instance, value)
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
String,
|
||||
Alias
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedBool,
|
||||
NestedInteger,
|
||||
NestedFloat,
|
||||
NestedSet
|
||||
)
|
||||
|
||||
from .data_source import NumFmt
|
||||
from .shapes import GraphicalProperties
|
||||
from .text import RichText, Text
|
||||
from .layout import Layout
|
||||
|
||||
|
||||
class TrendlineLabel(Serialisable):
|
||||
|
||||
tagname = "trendlineLbl"
|
||||
|
||||
layout = Typed(expected_type=Layout, allow_none=True)
|
||||
tx = Typed(expected_type=Text, allow_none=True)
|
||||
numFmt = Typed(expected_type=NumFmt, allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias("spPr")
|
||||
txPr = Typed(expected_type=RichText, allow_none=True)
|
||||
textProperties = Alias("txPr")
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr')
|
||||
|
||||
def __init__(self,
|
||||
layout=None,
|
||||
tx=None,
|
||||
numFmt=None,
|
||||
spPr=None,
|
||||
txPr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.layout = layout
|
||||
self.tx = tx
|
||||
self.numFmt = numFmt
|
||||
self.spPr = spPr
|
||||
self.txPr = txPr
|
||||
|
||||
|
||||
class Trendline(Serialisable):
|
||||
|
||||
tagname = "trendline"
|
||||
|
||||
name = String(allow_none=True)
|
||||
spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
|
||||
graphicalProperties = Alias('spPr')
|
||||
trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power']))
|
||||
order = NestedInteger(allow_none=True)
|
||||
period = NestedInteger(allow_none=True)
|
||||
forward = NestedFloat(allow_none=True)
|
||||
backward = NestedFloat(allow_none=True)
|
||||
intercept = NestedFloat(allow_none=True)
|
||||
dispRSqr = NestedBool(allow_none=True)
|
||||
dispEq = NestedBool(allow_none=True)
|
||||
trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('spPr', 'trendlineType', 'order', 'period', 'forward',
|
||||
'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl')
|
||||
|
||||
def __init__(self,
|
||||
name=None,
|
||||
spPr=None,
|
||||
trendlineType='linear',
|
||||
order=None,
|
||||
period=None,
|
||||
forward=None,
|
||||
backward=None,
|
||||
intercept=None,
|
||||
dispRSqr=None,
|
||||
dispEq=None,
|
||||
trendlineLbl=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.name = name
|
||||
self.spPr = spPr
|
||||
self.trendlineType = trendlineType
|
||||
self.order = order
|
||||
self.period = period
|
||||
self.forward = forward
|
||||
self.backward = backward
|
||||
self.intercept = intercept
|
||||
self.dispRSqr = dispRSqr
|
||||
self.dispEq = dispEq
|
||||
self.trendlineLbl = trendlineLbl
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import Typed
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
|
||||
from .shapes import GraphicalProperties
|
||||
from .axis import ChartLines
|
||||
from .descriptors import NestedGapAmount
|
||||
|
||||
|
||||
class UpDownBars(Serialisable):
|
||||
|
||||
tagname = "upbars"
|
||||
|
||||
gapWidth = NestedGapAmount()
|
||||
upBars = Typed(expected_type=ChartLines, allow_none=True)
|
||||
downBars = Typed(expected_type=ChartLines, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('gapWidth', 'upBars', 'downBars')
|
||||
|
||||
def __init__(self,
|
||||
gapWidth=150,
|
||||
upBars=None,
|
||||
downBars=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.gapWidth = gapWidth
|
||||
self.upBars = upBars
|
||||
self.downBars = downBars
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .chartsheet import Chartsheet
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
from openpyxl.descriptors import Typed, Set, Alias
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.drawing.spreadsheet_drawing import (
|
||||
AbsoluteAnchor,
|
||||
SpreadsheetDrawing,
|
||||
)
|
||||
from openpyxl.worksheet.page import (
|
||||
PageMargins,
|
||||
PrintPageSetup
|
||||
)
|
||||
from openpyxl.worksheet.drawing import Drawing
|
||||
from openpyxl.worksheet.header_footer import HeaderFooter
|
||||
from openpyxl.workbook.child import _WorkbookChild
|
||||
from openpyxl.xml.constants import SHEET_MAIN_NS, REL_NS
|
||||
|
||||
from .relation import DrawingHF, SheetBackgroundPicture
|
||||
from .properties import ChartsheetProperties
|
||||
from .protection import ChartsheetProtection
|
||||
from .views import ChartsheetViewList
|
||||
from .custom import CustomChartsheetViews
|
||||
from .publish import WebPublishItems
|
||||
|
||||
|
||||
class Chartsheet(_WorkbookChild, Serialisable):
|
||||
|
||||
tagname = "chartsheet"
|
||||
_default_title = "Chart"
|
||||
_rel_type = "chartsheet"
|
||||
_path = "/xl/chartsheets/sheet{0}.xml"
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
|
||||
|
||||
sheetPr = Typed(expected_type=ChartsheetProperties, allow_none=True)
|
||||
sheetViews = Typed(expected_type=ChartsheetViewList)
|
||||
sheetProtection = Typed(expected_type=ChartsheetProtection, allow_none=True)
|
||||
customSheetViews = Typed(expected_type=CustomChartsheetViews, allow_none=True)
|
||||
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
|
||||
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
|
||||
drawing = Typed(expected_type=Drawing, allow_none=True)
|
||||
drawingHF = Typed(expected_type=DrawingHF, allow_none=True)
|
||||
picture = Typed(expected_type=SheetBackgroundPicture, allow_none=True)
|
||||
webPublishItems = Typed(expected_type=WebPublishItems, allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
sheet_state = Set(values=('visible', 'hidden', 'veryHidden'))
|
||||
headerFooter = Typed(expected_type=HeaderFooter)
|
||||
HeaderFooter = Alias('headerFooter')
|
||||
|
||||
__elements__ = (
|
||||
'sheetPr', 'sheetViews', 'sheetProtection', 'customSheetViews',
|
||||
'pageMargins', 'pageSetup', 'headerFooter', 'drawing', 'drawingHF',
|
||||
'picture', 'webPublishItems')
|
||||
|
||||
__attrs__ = ()
|
||||
|
||||
def __init__(self,
|
||||
sheetPr=None,
|
||||
sheetViews=None,
|
||||
sheetProtection=None,
|
||||
customSheetViews=None,
|
||||
pageMargins=None,
|
||||
pageSetup=None,
|
||||
headerFooter=None,
|
||||
drawing=None,
|
||||
drawingHF=None,
|
||||
picture=None,
|
||||
webPublishItems=None,
|
||||
extLst=None,
|
||||
parent=None,
|
||||
title="",
|
||||
sheet_state='visible',
|
||||
):
|
||||
super().__init__(parent, title)
|
||||
self._charts = []
|
||||
self.sheetPr = sheetPr
|
||||
if sheetViews is None:
|
||||
sheetViews = ChartsheetViewList()
|
||||
self.sheetViews = sheetViews
|
||||
self.sheetProtection = sheetProtection
|
||||
self.customSheetViews = customSheetViews
|
||||
self.pageMargins = pageMargins
|
||||
self.pageSetup = pageSetup
|
||||
if headerFooter is not None:
|
||||
self.headerFooter = headerFooter
|
||||
self.drawing = Drawing("rId1")
|
||||
self.drawingHF = drawingHF
|
||||
self.picture = picture
|
||||
self.webPublishItems = webPublishItems
|
||||
self.sheet_state = sheet_state
|
||||
|
||||
|
||||
def add_chart(self, chart):
|
||||
chart.anchor = AbsoluteAnchor()
|
||||
self._charts.append(chart)
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
self._drawing = SpreadsheetDrawing()
|
||||
self._drawing.charts = self._charts
|
||||
tree = super().to_tree()
|
||||
if not self.headerFooter:
|
||||
el = tree.find('headerFooter')
|
||||
tree.remove(el)
|
||||
tree.set("xmlns", SHEET_MAIN_NS)
|
||||
return tree
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.worksheet.header_footer import HeaderFooter
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Integer,
|
||||
Set,
|
||||
Typed,
|
||||
Sequence
|
||||
)
|
||||
from openpyxl.descriptors.excel import Guid
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.worksheet.page import (
|
||||
PageMargins,
|
||||
PrintPageSetup
|
||||
)
|
||||
|
||||
|
||||
class CustomChartsheetView(Serialisable):
|
||||
tagname = "customSheetView"
|
||||
|
||||
guid = Guid()
|
||||
scale = Integer()
|
||||
state = Set(values=(['visible', 'hidden', 'veryHidden']))
|
||||
zoomToFit = Bool(allow_none=True)
|
||||
pageMargins = Typed(expected_type=PageMargins, allow_none=True)
|
||||
pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
|
||||
headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
|
||||
|
||||
__elements__ = ('pageMargins', 'pageSetup', 'headerFooter')
|
||||
|
||||
def __init__(self,
|
||||
guid=None,
|
||||
scale=None,
|
||||
state='visible',
|
||||
zoomToFit=None,
|
||||
pageMargins=None,
|
||||
pageSetup=None,
|
||||
headerFooter=None,
|
||||
):
|
||||
self.guid = guid
|
||||
self.scale = scale
|
||||
self.state = state
|
||||
self.zoomToFit = zoomToFit
|
||||
self.pageMargins = pageMargins
|
||||
self.pageSetup = pageSetup
|
||||
self.headerFooter = headerFooter
|
||||
|
||||
|
||||
class CustomChartsheetViews(Serialisable):
|
||||
tagname = "customSheetViews"
|
||||
|
||||
customSheetView = Sequence(expected_type=CustomChartsheetView, allow_none=True)
|
||||
|
||||
__elements__ = ('customSheetView',)
|
||||
|
||||
def __init__(self,
|
||||
customSheetView=None,
|
||||
):
|
||||
self.customSheetView = customSheetView
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
String,
|
||||
Typed
|
||||
)
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.styles import Color
|
||||
|
||||
|
||||
class ChartsheetProperties(Serialisable):
|
||||
tagname = "sheetPr"
|
||||
|
||||
published = Bool(allow_none=True)
|
||||
codeName = String(allow_none=True)
|
||||
tabColor = Typed(expected_type=Color, allow_none=True)
|
||||
|
||||
__elements__ = ('tabColor',)
|
||||
|
||||
def __init__(self,
|
||||
published=None,
|
||||
codeName=None,
|
||||
tabColor=None,
|
||||
):
|
||||
self.published = published
|
||||
self.codeName = codeName
|
||||
self.tabColor = tabColor
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import hashlib
|
||||
|
||||
from openpyxl.descriptors import (Bool, Integer, String)
|
||||
from openpyxl.descriptors.excel import Base64Binary
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
from openpyxl.worksheet.protection import (
|
||||
hash_password,
|
||||
_Protected
|
||||
)
|
||||
|
||||
|
||||
class ChartsheetProtection(Serialisable, _Protected):
|
||||
tagname = "sheetProtection"
|
||||
|
||||
algorithmName = String(allow_none=True)
|
||||
hashValue = Base64Binary(allow_none=True)
|
||||
saltValue = Base64Binary(allow_none=True)
|
||||
spinCount = Integer(allow_none=True)
|
||||
content = Bool(allow_none=True)
|
||||
objects = Bool(allow_none=True)
|
||||
|
||||
__attrs__ = ("content", "objects", "password", "hashValue", "spinCount", "saltValue", "algorithmName")
|
||||
|
||||
def __init__(self,
|
||||
content=None,
|
||||
objects=None,
|
||||
hashValue=None,
|
||||
spinCount=None,
|
||||
saltValue=None,
|
||||
algorithmName=None,
|
||||
password=None,
|
||||
):
|
||||
self.content = content
|
||||
self.objects = objects
|
||||
self.hashValue = hashValue
|
||||
self.spinCount = spinCount
|
||||
self.saltValue = saltValue
|
||||
self.algorithmName = algorithmName
|
||||
if password is not None:
|
||||
self.password = password
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Set,
|
||||
Sequence
|
||||
)
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
|
||||
class WebPublishItem(Serialisable):
|
||||
tagname = "webPublishItem"
|
||||
|
||||
id = Integer()
|
||||
divId = String()
|
||||
sourceType = Set(values=(['sheet', 'printArea', 'autoFilter', 'range', 'chart', 'pivotTable', 'query', 'label']))
|
||||
sourceRef = String()
|
||||
sourceObject = String(allow_none=True)
|
||||
destinationFile = String()
|
||||
title = String(allow_none=True)
|
||||
autoRepublish = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
id=None,
|
||||
divId=None,
|
||||
sourceType=None,
|
||||
sourceRef=None,
|
||||
sourceObject=None,
|
||||
destinationFile=None,
|
||||
title=None,
|
||||
autoRepublish=None,
|
||||
):
|
||||
self.id = id
|
||||
self.divId = divId
|
||||
self.sourceType = sourceType
|
||||
self.sourceRef = sourceRef
|
||||
self.sourceObject = sourceObject
|
||||
self.destinationFile = destinationFile
|
||||
self.title = title
|
||||
self.autoRepublish = autoRepublish
|
||||
|
||||
|
||||
class WebPublishItems(Serialisable):
|
||||
tagname = "WebPublishItems"
|
||||
|
||||
count = Integer(allow_none=True)
|
||||
webPublishItem = Sequence(expected_type=WebPublishItem, )
|
||||
|
||||
__elements__ = ('webPublishItem',)
|
||||
|
||||
def __init__(self,
|
||||
count=None,
|
||||
webPublishItem=None,
|
||||
):
|
||||
self.count = len(webPublishItem)
|
||||
self.webPublishItem = webPublishItem
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Integer,
|
||||
Alias
|
||||
)
|
||||
from openpyxl.descriptors.excel import Relation
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
|
||||
class SheetBackgroundPicture(Serialisable):
|
||||
tagname = "picture"
|
||||
id = Relation()
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
|
||||
class DrawingHF(Serialisable):
|
||||
id = Relation()
|
||||
lho = Integer(allow_none=True)
|
||||
leftHeaderOddPages = Alias('lho')
|
||||
lhe = Integer(allow_none=True)
|
||||
leftHeaderEvenPages = Alias('lhe')
|
||||
lhf = Integer(allow_none=True)
|
||||
leftHeaderFirstPage = Alias('lhf')
|
||||
cho = Integer(allow_none=True)
|
||||
centerHeaderOddPages = Alias('cho')
|
||||
che = Integer(allow_none=True)
|
||||
centerHeaderEvenPages = Alias('che')
|
||||
chf = Integer(allow_none=True)
|
||||
centerHeaderFirstPage = Alias('chf')
|
||||
rho = Integer(allow_none=True)
|
||||
rightHeaderOddPages = Alias('rho')
|
||||
rhe = Integer(allow_none=True)
|
||||
rightHeaderEvenPages = Alias('rhe')
|
||||
rhf = Integer(allow_none=True)
|
||||
rightHeaderFirstPage = Alias('rhf')
|
||||
lfo = Integer(allow_none=True)
|
||||
leftFooterOddPages = Alias('lfo')
|
||||
lfe = Integer(allow_none=True)
|
||||
leftFooterEvenPages = Alias('lfe')
|
||||
lff = Integer(allow_none=True)
|
||||
leftFooterFirstPage = Alias('lff')
|
||||
cfo = Integer(allow_none=True)
|
||||
centerFooterOddPages = Alias('cfo')
|
||||
cfe = Integer(allow_none=True)
|
||||
centerFooterEvenPages = Alias('cfe')
|
||||
cff = Integer(allow_none=True)
|
||||
centerFooterFirstPage = Alias('cff')
|
||||
rfo = Integer(allow_none=True)
|
||||
rightFooterOddPages = Alias('rfo')
|
||||
rfe = Integer(allow_none=True)
|
||||
rightFooterEvenPages = Alias('rfe')
|
||||
rff = Integer(allow_none=True)
|
||||
rightFooterFirstPage = Alias('rff')
|
||||
|
||||
def __init__(self,
|
||||
id=None,
|
||||
lho=None,
|
||||
lhe=None,
|
||||
lhf=None,
|
||||
cho=None,
|
||||
che=None,
|
||||
chf=None,
|
||||
rho=None,
|
||||
rhe=None,
|
||||
rhf=None,
|
||||
lfo=None,
|
||||
lfe=None,
|
||||
lff=None,
|
||||
cfo=None,
|
||||
cfe=None,
|
||||
cff=None,
|
||||
rfo=None,
|
||||
rfe=None,
|
||||
rff=None,
|
||||
):
|
||||
self.id = id
|
||||
self.lho = lho
|
||||
self.lhe = lhe
|
||||
self.lhf = lhf
|
||||
self.cho = cho
|
||||
self.che = che
|
||||
self.chf = chf
|
||||
self.rho = rho
|
||||
self.rhe = rhe
|
||||
self.rhf = rhf
|
||||
self.lfo = lfo
|
||||
self.lfe = lfe
|
||||
self.lff = lff
|
||||
self.cfo = cfo
|
||||
self.cfe = cfe
|
||||
self.cff = cff
|
||||
self.rfo = rfo
|
||||
self.rfe = rfe
|
||||
self.rff = rff
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors import (
|
||||
Bool,
|
||||
Integer,
|
||||
Typed,
|
||||
Sequence
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
|
||||
|
||||
class ChartsheetView(Serialisable):
|
||||
tagname = "sheetView"
|
||||
|
||||
tabSelected = Bool(allow_none=True)
|
||||
zoomScale = Integer(allow_none=True)
|
||||
workbookViewId = Integer()
|
||||
zoomToFit = Bool(allow_none=True)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ()
|
||||
|
||||
def __init__(self,
|
||||
tabSelected=None,
|
||||
zoomScale=None,
|
||||
workbookViewId=0,
|
||||
zoomToFit=True,
|
||||
extLst=None,
|
||||
):
|
||||
self.tabSelected = tabSelected
|
||||
self.zoomScale = zoomScale
|
||||
self.workbookViewId = workbookViewId
|
||||
self.zoomToFit = zoomToFit
|
||||
|
||||
|
||||
class ChartsheetViewList(Serialisable):
|
||||
tagname = "sheetViews"
|
||||
|
||||
sheetView = Sequence(expected_type=ChartsheetView, )
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('sheetView',)
|
||||
|
||||
def __init__(self,
|
||||
sheetView=None,
|
||||
extLst=None,
|
||||
):
|
||||
if sheetView is None:
|
||||
sheetView = [ChartsheetView()]
|
||||
self.sheetView = sheetView
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
from .comments import Comment
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Sequence,
|
||||
Alias
|
||||
)
|
||||
|
||||
|
||||
class AuthorList(Serialisable):
|
||||
|
||||
tagname = "authors"
|
||||
|
||||
author = Sequence(expected_type=str)
|
||||
authors = Alias("author")
|
||||
|
||||
def __init__(self,
|
||||
author=(),
|
||||
):
|
||||
self.author = author
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
## Incomplete!
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
Set,
|
||||
String,
|
||||
Bool,
|
||||
)
|
||||
from openpyxl.descriptors.excel import Guid, ExtensionList
|
||||
from openpyxl.descriptors.sequence import NestedSequence
|
||||
|
||||
from openpyxl.utils.indexed_list import IndexedList
|
||||
from openpyxl.xml.constants import SHEET_MAIN_NS
|
||||
|
||||
from openpyxl.cell.text import Text
|
||||
from .author import AuthorList
|
||||
from .comments import Comment
|
||||
from .shape_writer import ShapeWriter
|
||||
|
||||
|
||||
class Properties(Serialisable):
|
||||
|
||||
locked = Bool(allow_none=True)
|
||||
defaultSize = Bool(allow_none=True)
|
||||
_print = Bool(allow_none=True)
|
||||
disabled = Bool(allow_none=True)
|
||||
uiObject = Bool(allow_none=True)
|
||||
autoFill = Bool(allow_none=True)
|
||||
autoLine = Bool(allow_none=True)
|
||||
altText = String(allow_none=True)
|
||||
textHAlign = Set(values=(['left', 'center', 'right', 'justify', 'distributed']))
|
||||
textVAlign = Set(values=(['top', 'center', 'bottom', 'justify', 'distributed']))
|
||||
lockText = Bool(allow_none=True)
|
||||
justLastX = Bool(allow_none=True)
|
||||
autoScale = Bool(allow_none=True)
|
||||
rowHidden = Bool(allow_none=True)
|
||||
colHidden = Bool(allow_none=True)
|
||||
# anchor = Typed(expected_type=ObjectAnchor, )
|
||||
|
||||
__elements__ = ('anchor',)
|
||||
|
||||
def __init__(self,
|
||||
locked=None,
|
||||
defaultSize=None,
|
||||
_print=None,
|
||||
disabled=None,
|
||||
uiObject=None,
|
||||
autoFill=None,
|
||||
autoLine=None,
|
||||
altText=None,
|
||||
textHAlign=None,
|
||||
textVAlign=None,
|
||||
lockText=None,
|
||||
justLastX=None,
|
||||
autoScale=None,
|
||||
rowHidden=None,
|
||||
colHidden=None,
|
||||
anchor=None,
|
||||
):
|
||||
self.locked = locked
|
||||
self.defaultSize = defaultSize
|
||||
self._print = _print
|
||||
self.disabled = disabled
|
||||
self.uiObject = uiObject
|
||||
self.autoFill = autoFill
|
||||
self.autoLine = autoLine
|
||||
self.altText = altText
|
||||
self.textHAlign = textHAlign
|
||||
self.textVAlign = textVAlign
|
||||
self.lockText = lockText
|
||||
self.justLastX = justLastX
|
||||
self.autoScale = autoScale
|
||||
self.rowHidden = rowHidden
|
||||
self.colHidden = colHidden
|
||||
self.anchor = anchor
|
||||
|
||||
|
||||
class CommentRecord(Serialisable):
|
||||
|
||||
tagname = "comment"
|
||||
|
||||
ref = String()
|
||||
authorId = Integer()
|
||||
guid = Guid(allow_none=True)
|
||||
shapeId = Integer(allow_none=True)
|
||||
text = Typed(expected_type=Text)
|
||||
commentPr = Typed(expected_type=Properties, allow_none=True)
|
||||
author = String(allow_none=True)
|
||||
|
||||
__elements__ = ('text', 'commentPr')
|
||||
__attrs__ = ('ref', 'authorId', 'guid', 'shapeId')
|
||||
|
||||
def __init__(self,
|
||||
ref="",
|
||||
authorId=0,
|
||||
guid=None,
|
||||
shapeId=0,
|
||||
text=None,
|
||||
commentPr=None,
|
||||
author=None,
|
||||
height=79,
|
||||
width=144
|
||||
):
|
||||
self.ref = ref
|
||||
self.authorId = authorId
|
||||
self.guid = guid
|
||||
self.shapeId = shapeId
|
||||
if text is None:
|
||||
text = Text()
|
||||
self.text = text
|
||||
self.commentPr = commentPr
|
||||
self.author = author
|
||||
self.height = height
|
||||
self.width = width
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_cell(cls, cell):
|
||||
"""
|
||||
Class method to convert cell comment
|
||||
"""
|
||||
comment = cell._comment
|
||||
ref = cell.coordinate
|
||||
self = cls(ref=ref, author=comment.author)
|
||||
self.text.t = comment.content
|
||||
self.height = comment.height
|
||||
self.width = comment.width
|
||||
return self
|
||||
|
||||
|
||||
@property
|
||||
def content(self):
|
||||
"""
|
||||
Remove all inline formatting and stuff
|
||||
"""
|
||||
return self.text.content
|
||||
|
||||
|
||||
class CommentSheet(Serialisable):
|
||||
|
||||
tagname = "comments"
|
||||
|
||||
authors = Typed(expected_type=AuthorList)
|
||||
commentList = NestedSequence(expected_type=CommentRecord, count=0)
|
||||
extLst = Typed(expected_type=ExtensionList, allow_none=True)
|
||||
|
||||
_id = None
|
||||
_path = "/xl/comments/comment{0}.xml"
|
||||
mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
|
||||
_rel_type = "comments"
|
||||
_rel_id = None
|
||||
|
||||
__elements__ = ('authors', 'commentList')
|
||||
|
||||
def __init__(self,
|
||||
authors=None,
|
||||
commentList=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.authors = authors
|
||||
self.commentList = commentList
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
tree = super().to_tree()
|
||||
tree.set("xmlns", SHEET_MAIN_NS)
|
||||
return tree
|
||||
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
"""
|
||||
Return a dictionary of comments keyed by coord
|
||||
"""
|
||||
authors = self.authors.author
|
||||
|
||||
for c in self.commentList:
|
||||
yield c.ref, Comment(c.content, authors[c.authorId], c.height, c.width)
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_comments(cls, comments):
|
||||
"""
|
||||
Create a comment sheet from a list of comments for a particular worksheet
|
||||
"""
|
||||
authors = IndexedList()
|
||||
|
||||
# dedupe authors and get indexes
|
||||
for comment in comments:
|
||||
comment.authorId = authors.add(comment.author)
|
||||
|
||||
return cls(authors=AuthorList(authors), commentList=comments)
|
||||
|
||||
|
||||
def write_shapes(self, vml=None):
|
||||
"""
|
||||
Create the VML for comments
|
||||
"""
|
||||
sw = ShapeWriter(self.comments)
|
||||
return sw.write(vml)
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
Return path within the archive
|
||||
"""
|
||||
return self._path.format(self._id)
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
class Comment:
|
||||
|
||||
_parent = None
|
||||
|
||||
def __init__(self, text, author, height=79, width=144):
|
||||
self.content = text
|
||||
self.author = author
|
||||
self.height = height
|
||||
self.width = width
|
||||
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.content == other.content
|
||||
and self.author == other.author
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "Comment: {0} by {1}".format(self.content, self.author)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
"""Create a detached copy of this comment."""
|
||||
clone = self.__class__(self.content, self.author, self.height, self.width)
|
||||
return clone
|
||||
|
||||
|
||||
def bind(self, cell):
|
||||
"""
|
||||
Bind comment to a particular cell
|
||||
"""
|
||||
if cell is not None and self._parent is not None and self._parent != cell:
|
||||
fmt = "Comment already assigned to {0} in worksheet {1}. Cannot assign a comment to more than one cell"
|
||||
raise AttributeError(fmt.format(cell.coordinate, cell.parent.title))
|
||||
self._parent = cell
|
||||
|
||||
|
||||
def unbind(self):
|
||||
"""
|
||||
Unbind a comment from a cell
|
||||
"""
|
||||
self._parent = None
|
||||
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
Any comment text stripped of all formatting.
|
||||
"""
|
||||
return self.content
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
self.content = value
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.xml.functions import (
|
||||
Element,
|
||||
SubElement,
|
||||
tostring,
|
||||
)
|
||||
|
||||
from openpyxl.utils import coordinate_to_tuple
|
||||
|
||||
vmlns = "urn:schemas-microsoft-com:vml"
|
||||
officens = "urn:schemas-microsoft-com:office:office"
|
||||
excelns = "urn:schemas-microsoft-com:office:excel"
|
||||
|
||||
|
||||
class ShapeWriter:
|
||||
"""
|
||||
Create VML for comments
|
||||
"""
|
||||
|
||||
vml = None
|
||||
vml_path = None
|
||||
|
||||
|
||||
def __init__(self, comments):
|
||||
self.comments = comments
|
||||
|
||||
|
||||
def add_comment_shapetype(self, root):
|
||||
shape_layout = SubElement(root, "{%s}shapelayout" % officens,
|
||||
{"{%s}ext" % vmlns: "edit"})
|
||||
SubElement(shape_layout,
|
||||
"{%s}idmap" % officens,
|
||||
{"{%s}ext" % vmlns: "edit", "data": "1"})
|
||||
shape_type = SubElement(root,
|
||||
"{%s}shapetype" % vmlns,
|
||||
{"id": "_x0000_t202",
|
||||
"coordsize": "21600,21600",
|
||||
"{%s}spt" % officens: "202",
|
||||
"path": "m,l,21600r21600,l21600,xe"})
|
||||
SubElement(shape_type, "{%s}stroke" % vmlns, {"joinstyle": "miter"})
|
||||
SubElement(shape_type,
|
||||
"{%s}path" % vmlns,
|
||||
{"gradientshapeok": "t",
|
||||
"{%s}connecttype" % officens: "rect"})
|
||||
|
||||
|
||||
def add_comment_shape(self, root, idx, coord, height, width):
|
||||
row, col = coordinate_to_tuple(coord)
|
||||
row -= 1
|
||||
col -= 1
|
||||
shape = _shape_factory(row, col, height, width)
|
||||
|
||||
shape.set('id', "_x0000_s%04d" % idx)
|
||||
root.append(shape)
|
||||
|
||||
|
||||
def write(self, root):
|
||||
|
||||
if not hasattr(root, "findall"):
|
||||
root = Element("xml")
|
||||
|
||||
# Remove any existing comment shapes
|
||||
comments = root.findall("{%s}shape[@type='#_x0000_t202']" % vmlns)
|
||||
for c in comments:
|
||||
root.remove(c)
|
||||
|
||||
# check whether comments shape type already exists
|
||||
shape_types = root.find("{%s}shapetype[@id='_x0000_t202']" % vmlns)
|
||||
if shape_types is None:
|
||||
self.add_comment_shapetype(root)
|
||||
|
||||
for idx, (coord, comment) in enumerate(self.comments, 1026):
|
||||
self.add_comment_shape(root, idx, coord, comment.height, comment.width)
|
||||
|
||||
return tostring(root)
|
||||
|
||||
|
||||
def _shape_factory(row, column, height, width):
|
||||
style = ("position:absolute; "
|
||||
"margin-left:59.25pt;"
|
||||
"margin-top:1.5pt;"
|
||||
"width:{width}px;"
|
||||
"height:{height}px;"
|
||||
"z-index:1;"
|
||||
"visibility:hidden").format(height=height,
|
||||
width=width)
|
||||
attrs = {
|
||||
"type": "#_x0000_t202",
|
||||
"style": style,
|
||||
"fillcolor": "#ffffe1",
|
||||
"{%s}insetmode" % officens: "auto"
|
||||
}
|
||||
shape = Element("{%s}shape" % vmlns, attrs)
|
||||
|
||||
SubElement(shape, "{%s}fill" % vmlns,
|
||||
{"color2": "#ffffe1"})
|
||||
SubElement(shape, "{%s}shadow" % vmlns,
|
||||
{"color": "black", "obscured": "t"})
|
||||
SubElement(shape, "{%s}path" % vmlns,
|
||||
{"{%s}connecttype" % officens: "none"})
|
||||
textbox = SubElement(shape, "{%s}textbox" % vmlns,
|
||||
{"style": "mso-direction-alt:auto"})
|
||||
SubElement(textbox, "div", {"style": "text-align:left"})
|
||||
client_data = SubElement(shape, "{%s}ClientData" % excelns,
|
||||
{"ObjectType": "Note"})
|
||||
SubElement(client_data, "{%s}MoveWithCells" % excelns)
|
||||
SubElement(client_data, "{%s}SizeWithCells" % excelns)
|
||||
SubElement(client_data, "{%s}AutoFill" % excelns).text = "False"
|
||||
SubElement(client_data, "{%s}Row" % excelns).text = str(row)
|
||||
SubElement(client_data, "{%s}Column" % excelns).text = str(column)
|
||||
return shape
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .numbers import NUMERIC_TYPES
|
||||
from .strings import safe_string
|
||||
|
||||
import warnings
|
||||
from functools import wraps
|
||||
import inspect
|
||||
|
||||
|
||||
class DummyCode:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# from https://github.com/tantale/deprecated/blob/master/deprecated/__init__.py
|
||||
# with an enhancement to update docstrings of deprecated functions
|
||||
string_types = (type(b''), type(u''))
|
||||
def deprecated(reason):
|
||||
|
||||
if isinstance(reason, string_types):
|
||||
|
||||
def decorator(func1):
|
||||
|
||||
if inspect.isclass(func1):
|
||||
fmt1 = "Call to deprecated class {name} ({reason})."
|
||||
else:
|
||||
fmt1 = "Call to deprecated function {name} ({reason})."
|
||||
|
||||
@wraps(func1)
|
||||
def new_func1(*args, **kwargs):
|
||||
#warnings.simplefilter('default', DeprecationWarning)
|
||||
warnings.warn(
|
||||
fmt1.format(name=func1.__name__, reason=reason),
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return func1(*args, **kwargs)
|
||||
|
||||
# Enhance docstring with a deprecation note
|
||||
deprecationNote = "\n\n.. note::\n Deprecated: " + reason
|
||||
if new_func1.__doc__:
|
||||
new_func1.__doc__ += deprecationNote
|
||||
else:
|
||||
new_func1.__doc__ = deprecationNote
|
||||
return new_func1
|
||||
|
||||
return decorator
|
||||
|
||||
elif inspect.isclass(reason) or inspect.isfunction(reason):
|
||||
raise TypeError("Reason for deprecation must be supplied")
|
||||
|
||||
else:
|
||||
raise TypeError(repr(type(reason)))
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
try:
|
||||
from abc import ABC
|
||||
except ImportError:
|
||||
from abc import ABCMeta
|
||||
ABC = ABCMeta('ABC', (object, ), {})
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
NUMERIC_TYPES = (int, float, Decimal)
|
||||
|
||||
|
||||
try:
|
||||
import numpy
|
||||
NUMPY = True
|
||||
except ImportError:
|
||||
NUMPY = False
|
||||
|
||||
|
||||
if NUMPY:
|
||||
NUMERIC_TYPES = NUMERIC_TYPES + (numpy.short,
|
||||
numpy.ushort,
|
||||
numpy.intc,
|
||||
numpy.uintc,
|
||||
numpy.int_,
|
||||
numpy.uint,
|
||||
numpy.longlong,
|
||||
numpy.ulonglong,
|
||||
numpy.half,
|
||||
numpy.float16,
|
||||
numpy.single,
|
||||
numpy.double,
|
||||
numpy.longdouble,
|
||||
numpy.int8,
|
||||
numpy.int16,
|
||||
numpy.int32,
|
||||
numpy.int64,
|
||||
numpy.uint8,
|
||||
numpy.uint16,
|
||||
numpy.uint32,
|
||||
numpy.uint64,
|
||||
numpy.intp,
|
||||
numpy.uintp,
|
||||
numpy.float32,
|
||||
numpy.float64,
|
||||
numpy.bool_,
|
||||
numpy.floating,
|
||||
numpy.integer)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
math.prod equivalent for < Python 3.8
|
||||
"""
|
||||
|
||||
import functools
|
||||
import operator
|
||||
|
||||
def product(sequence):
|
||||
return functools.reduce(operator.mul, sequence)
|
||||
|
||||
|
||||
try:
|
||||
from math import prod
|
||||
except ImportError:
|
||||
prod = product
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import weakref
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
"""
|
||||
Singleton metaclass
|
||||
Based on Python Cookbook 3rd Edition Recipe 9.13
|
||||
Only one instance of a class can exist. Does not work with __slots__
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.__instance = None
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
if self.__instance is None:
|
||||
self.__instance = super().__call__(*args, **kw)
|
||||
return self.__instance
|
||||
|
||||
|
||||
class Cached(type):
|
||||
"""
|
||||
Caching metaclass
|
||||
Child classes will only create new instances of themselves if
|
||||
one doesn't already exist. Does not work with __slots__
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.__cache = weakref.WeakValueDictionary()
|
||||
|
||||
def __call__(self, *args):
|
||||
if args in self.__cache:
|
||||
return self.__cache[args]
|
||||
|
||||
obj = super().__call__(*args)
|
||||
self.__cache[args] = obj
|
||||
return obj
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from datetime import datetime
|
||||
from math import isnan, isinf
|
||||
import sys
|
||||
|
||||
VER = sys.version_info
|
||||
|
||||
from .numbers import NUMERIC_TYPES
|
||||
|
||||
|
||||
def safe_string(value):
|
||||
"""Safely and consistently format numeric values"""
|
||||
if isinstance(value, NUMERIC_TYPES):
|
||||
if isnan(value) or isinf(value):
|
||||
value = ""
|
||||
else:
|
||||
value = "%.16g" % value
|
||||
elif value is None:
|
||||
value = "none"
|
||||
elif isinstance(value, datetime):
|
||||
value = value.isoformat()
|
||||
elif not isinstance(value, str):
|
||||
value = str(value)
|
||||
return value
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from .base import *
|
||||
from .sequence import Sequence
|
||||
|
||||
|
||||
class MetaStrict(type):
|
||||
|
||||
def __new__(cls, clsname, bases, methods):
|
||||
for k, v in methods.items():
|
||||
if isinstance(v, Descriptor):
|
||||
v.name = k
|
||||
return type.__new__(cls, clsname, bases, methods)
|
||||
|
||||
|
||||
class Strict(metaclass=MetaStrict):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MetaSerialisable(type):
|
||||
|
||||
def __new__(cls, clsname, bases, methods):
|
||||
attrs = []
|
||||
nested = []
|
||||
elements = []
|
||||
namespaced = []
|
||||
for k, v in methods.items():
|
||||
if isinstance(v, Descriptor):
|
||||
ns= getattr(v, 'namespace', None)
|
||||
if ns:
|
||||
namespaced.append((k, "{%s}%s" % (ns, k)))
|
||||
if getattr(v, 'nested', False):
|
||||
nested.append(k)
|
||||
elements.append(k)
|
||||
elif isinstance(v, Sequence):
|
||||
elements.append(k)
|
||||
elif isinstance(v, Typed):
|
||||
if hasattr(v.expected_type, 'to_tree'):
|
||||
elements.append(k)
|
||||
elif isinstance(v.expected_type, tuple):
|
||||
if any((hasattr(el, "to_tree") for el in v.expected_type)):
|
||||
# don't bind elements as attrs
|
||||
continue
|
||||
else:
|
||||
attrs.append(k)
|
||||
else:
|
||||
if not isinstance(v, Alias):
|
||||
attrs.append(k)
|
||||
|
||||
if methods.get('__attrs__') is None:
|
||||
methods['__attrs__'] = tuple(attrs)
|
||||
methods['__namespaced__'] = tuple(namespaced)
|
||||
if methods.get('__nested__') is None:
|
||||
methods['__nested__'] = tuple(sorted(nested))
|
||||
if methods.get('__elements__') is None:
|
||||
methods['__elements__'] = tuple(sorted(elements))
|
||||
return MetaStrict.__new__(cls, clsname, bases, methods)
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
"""
|
||||
Based on Python Cookbook 3rd Edition, 8.13
|
||||
http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from openpyxl import DEBUG
|
||||
from openpyxl.utils.datetime import from_ISO8601
|
||||
|
||||
from .namespace import namespaced
|
||||
|
||||
class Descriptor:
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
self.name = name
|
||||
for k, v in kw.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.name] = value
|
||||
|
||||
|
||||
class Typed(Descriptor):
|
||||
"""Values must of a particular type"""
|
||||
|
||||
expected_type = type(None)
|
||||
allow_none = False
|
||||
nested = False
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
self.__doc__ = f"Values must be of type {self.expected_type}"
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if not isinstance(value, self.expected_type):
|
||||
if (not self.allow_none
|
||||
or (self.allow_none and value is not None)):
|
||||
msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}"
|
||||
if DEBUG:
|
||||
msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}"
|
||||
raise TypeError(msg)
|
||||
super().__set__(instance, value)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__doc__
|
||||
|
||||
|
||||
def _convert(expected_type, value):
|
||||
"""
|
||||
Check value is of or can be converted to expected type.
|
||||
"""
|
||||
if not isinstance(value, expected_type):
|
||||
try:
|
||||
value = expected_type(value)
|
||||
except:
|
||||
raise TypeError('expected ' + str(expected_type))
|
||||
return value
|
||||
|
||||
|
||||
class Convertible(Typed):
|
||||
"""Values must be convertible to a particular type"""
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Max(Convertible):
|
||||
"""Values must be less than a `max` value"""
|
||||
|
||||
expected_type = float
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'max' not in kw and not hasattr(self, 'max'):
|
||||
raise TypeError('missing max value')
|
||||
super().__init__(**kw)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
if value > self.max:
|
||||
raise ValueError('Max value is {0}'.format(self.max))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Min(Convertible):
|
||||
"""Values must be greater than a `min` value"""
|
||||
|
||||
expected_type = float
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, **kw):
|
||||
if 'min' not in kw and not hasattr(self, 'min'):
|
||||
raise TypeError('missing min value')
|
||||
super().__init__(**kw)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
value = _convert(self.expected_type, value)
|
||||
if value < self.min:
|
||||
raise ValueError('Min value is {0}'.format(self.min))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class MinMax(Min, Max):
|
||||
"""Values must be greater than `min` value and less than a `max` one"""
|
||||
pass
|
||||
|
||||
|
||||
class Set(Descriptor):
|
||||
"""Value can only be from a set of know values"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if not 'values' in kw:
|
||||
raise TypeError("missing set of values")
|
||||
kw['values'] = set(kw['values'])
|
||||
super().__init__(name, **kw)
|
||||
self.__doc__ = "Value must be one of {0}".format(self.values)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value not in self.values:
|
||||
raise ValueError(self.__doc__)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class NoneSet(Set):
|
||||
|
||||
"""'none' will be treated as None"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
super().__init__(name, **kw)
|
||||
self.values.add(None)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value == 'none':
|
||||
value = None
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Integer(Convertible):
|
||||
|
||||
expected_type = int
|
||||
|
||||
|
||||
class Float(Convertible):
|
||||
|
||||
expected_type = float
|
||||
|
||||
|
||||
class Bool(Convertible):
|
||||
|
||||
expected_type = bool
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str):
|
||||
if value in ('false', 'f', '0'):
|
||||
value = False
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class String(Typed):
|
||||
|
||||
expected_type = str
|
||||
|
||||
|
||||
class Text(String, Convertible):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ASCII(Typed):
|
||||
|
||||
expected_type = bytes
|
||||
|
||||
|
||||
class Tuple(Typed):
|
||||
|
||||
expected_type = tuple
|
||||
|
||||
|
||||
class Length(Descriptor):
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if "length" not in kw:
|
||||
raise TypeError("value length must be supplied")
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if len(value) != self.length:
|
||||
raise ValueError("Value must be length {0}".format(self.length))
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Default(Typed):
|
||||
"""
|
||||
When called returns an instance of the expected type.
|
||||
Additional default values can be passed in to the descriptor
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if "defaults" not in kw:
|
||||
kw['defaults'] = {}
|
||||
super().__init__(**kw)
|
||||
|
||||
def __call__(self):
|
||||
return self.expected_type()
|
||||
|
||||
|
||||
class Alias(Descriptor):
|
||||
"""
|
||||
Aliases can be used when either the desired attribute name is not allowed
|
||||
or confusing in Python (eg. "type") or a more descriptive name is desired
|
||||
(eg. "underline" for "u")
|
||||
"""
|
||||
|
||||
def __init__(self, alias):
|
||||
self.alias = alias
|
||||
|
||||
def __set__(self, instance, value):
|
||||
setattr(instance, self.alias, value)
|
||||
|
||||
def __get__(self, instance, cls):
|
||||
return getattr(instance, self.alias)
|
||||
|
||||
|
||||
class MatchPattern(Descriptor):
|
||||
"""Values must match a regex pattern """
|
||||
allow_none = False
|
||||
|
||||
def __init__(self, name=None, **kw):
|
||||
if 'pattern' not in kw and not hasattr(self, 'pattern'):
|
||||
raise TypeError('missing pattern value')
|
||||
|
||||
super().__init__(name, **kw)
|
||||
self.test_pattern = re.compile(self.pattern, re.VERBOSE)
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
||||
if value is None and not self.allow_none:
|
||||
raise ValueError("Value must not be none")
|
||||
|
||||
if ((self.allow_none and value is not None)
|
||||
or not self.allow_none):
|
||||
if not self.test_pattern.match(value):
|
||||
raise ValueError('Value does not match pattern {0}'.format(self.pattern))
|
||||
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class DateTime(Typed):
|
||||
|
||||
expected_type = datetime.datetime
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if value is not None and isinstance(value, str):
|
||||
try:
|
||||
value = from_ISO8601(value)
|
||||
except ValueError:
|
||||
raise ValueError("Value must be ISO datetime format")
|
||||
super().__set__(instance, value)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Utility list for top level containers that contain one type of element
|
||||
|
||||
Provides the necessary API to read and write XML
|
||||
"""
|
||||
|
||||
from openpyxl.xml.functions import Element
|
||||
|
||||
|
||||
class ElementList(list):
|
||||
|
||||
|
||||
@property
|
||||
def tagname(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@property
|
||||
def expected_type(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, tree):
|
||||
l = [cls.expected_type.from_tree(el) for el in tree]
|
||||
return cls(l)
|
||||
|
||||
|
||||
def to_tree(self):
|
||||
container = Element(self.tagname)
|
||||
for el in self:
|
||||
container.append(el.to_tree())
|
||||
return container
|
||||
|
||||
|
||||
def append(self, value):
|
||||
if not isinstance(value, self.expected_type):
|
||||
raise TypeError(f"Value must of type {self.expected_type} {type(value)} provided")
|
||||
super().append(value)
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Excel specific descriptors
|
||||
"""
|
||||
|
||||
from openpyxl.xml.constants import REL_NS
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element
|
||||
|
||||
from . import (
|
||||
MatchPattern,
|
||||
MinMax,
|
||||
Integer,
|
||||
String,
|
||||
Sequence,
|
||||
)
|
||||
from .serialisable import Serialisable
|
||||
|
||||
|
||||
class HexBinary(MatchPattern):
|
||||
|
||||
pattern = "[0-9a-fA-F]+$"
|
||||
|
||||
|
||||
class UniversalMeasure(MatchPattern):
|
||||
|
||||
pattern = r"[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"
|
||||
|
||||
|
||||
class TextPoint(MinMax):
|
||||
"""
|
||||
Size in hundredths of points.
|
||||
In theory other units of measurement can be used but these are unbounded
|
||||
"""
|
||||
expected_type = int
|
||||
|
||||
min = -400000
|
||||
max = 400000
|
||||
|
||||
|
||||
Coordinate = Integer
|
||||
|
||||
|
||||
class Percentage(MinMax):
|
||||
|
||||
pattern = r"((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%" # strict
|
||||
min = -1000000
|
||||
max = 1000000
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str) and "%" in value:
|
||||
value = value.replace("%", "")
|
||||
value = int(float(value) * 1000)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
class Extension(Serialisable):
|
||||
|
||||
uri = String()
|
||||
|
||||
def __init__(self,
|
||||
uri=None,
|
||||
):
|
||||
self.uri = uri
|
||||
|
||||
|
||||
class ExtensionList(Serialisable):
|
||||
|
||||
ext = Sequence(expected_type=Extension)
|
||||
|
||||
def __init__(self,
|
||||
ext=(),
|
||||
):
|
||||
self.ext = ext
|
||||
|
||||
|
||||
class Relation(String):
|
||||
|
||||
namespace = REL_NS
|
||||
allow_none = True
|
||||
|
||||
|
||||
class Base64Binary(MatchPattern):
|
||||
# http://www.w3.org/TR/xmlschema11-2/#nt-Base64Binary
|
||||
pattern = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$"
|
||||
|
||||
|
||||
class Guid(MatchPattern):
|
||||
# https://msdn.microsoft.com/en-us/library/dd946381(v=office.12).aspx
|
||||
pattern = r"{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"
|
||||
|
||||
|
||||
class CellRange(MatchPattern):
|
||||
|
||||
pattern = r"^[$]?([A-Za-z]{1,3})[$]?(\d+)(:[$]?([A-Za-z]{1,3})[$]?(\d+)?)?$|^[A-Za-z]{1,3}:[A-Za-z]{1,3}$"
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
|
||||
if value is not None:
|
||||
value = value.upper()
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
def _explicit_none(tagname, value, namespace=None):
|
||||
"""
|
||||
Override serialisation because explicit none required
|
||||
"""
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return Element(tagname, val=safe_string(value))
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
def namespaced(obj, tagname, namespace=None):
|
||||
"""
|
||||
Utility to create a namespaced tag for an object
|
||||
"""
|
||||
|
||||
namespace = getattr(obj, "namespace", None) or namespace
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return tagname
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
"""
|
||||
Generic serialisable classes
|
||||
"""
|
||||
from .base import (
|
||||
Convertible,
|
||||
Bool,
|
||||
Descriptor,
|
||||
NoneSet,
|
||||
MinMax,
|
||||
Set,
|
||||
Float,
|
||||
Integer,
|
||||
String,
|
||||
)
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element, localname, whitespace
|
||||
|
||||
|
||||
class Nested(Descriptor):
|
||||
|
||||
nested = True
|
||||
attribute = "val"
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if hasattr(value, "tag"):
|
||||
tag = localname(value)
|
||||
if tag != self.name:
|
||||
raise ValueError("Tag does not match attribute")
|
||||
|
||||
value = self.from_tree(value)
|
||||
super().__set__(instance, value)
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.get(self.attribute)
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if value is not None:
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
value = safe_string(value)
|
||||
return Element(tagname, {self.attribute:value})
|
||||
|
||||
|
||||
class NestedValue(Nested, Convertible):
|
||||
"""
|
||||
Nested tag storing the value on the 'val' attribute
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NestedText(NestedValue):
|
||||
"""
|
||||
Represents any nested tag with the value as the contents of the tag
|
||||
"""
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.text
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if value is not None:
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
el = Element(tagname)
|
||||
el.text = safe_string(value)
|
||||
whitespace(el)
|
||||
return el
|
||||
|
||||
|
||||
class NestedFloat(NestedValue, Float):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedInteger(NestedValue, Integer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedString(NestedValue, String):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedBool(NestedValue, Bool):
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return node.get("val", True)
|
||||
|
||||
|
||||
class NestedNoneSet(Nested, NoneSet):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedSet(Nested, Set):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NestedMinMax(Nested, MinMax):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EmptyTag(Nested, Bool):
|
||||
|
||||
"""
|
||||
Boolean if a tag exists or not.
|
||||
"""
|
||||
|
||||
def from_tree(self, node):
|
||||
return True
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, value=None, namespace=None):
|
||||
if value:
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
if namespace is not None:
|
||||
tagname = "{%s}%s" % (namespace, tagname)
|
||||
return Element(tagname)
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import Element
|
||||
from openpyxl.utils.indexed_list import IndexedList
|
||||
|
||||
from .base import Descriptor, Alias, _convert
|
||||
from .namespace import namespaced
|
||||
|
||||
|
||||
class Sequence(Descriptor):
|
||||
"""
|
||||
A sequence (list or tuple) that may only contain objects of the declared
|
||||
type
|
||||
"""
|
||||
|
||||
expected_type = type(None)
|
||||
seq_types = (list, tuple)
|
||||
idx_base = 0
|
||||
unique = False
|
||||
container = list
|
||||
|
||||
|
||||
def __set__(self, instance, seq):
|
||||
if not isinstance(seq, self.seq_types):
|
||||
raise TypeError("Value must be a sequence")
|
||||
seq = self.container(_convert(self.expected_type, value) for value in seq)
|
||||
if self.unique:
|
||||
seq = IndexedList(seq)
|
||||
|
||||
super().__set__(instance, seq)
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
"""
|
||||
Convert the sequence represented by the descriptor to an XML element
|
||||
"""
|
||||
for idx, v in enumerate(obj, self.idx_base):
|
||||
if hasattr(v, "to_tree"):
|
||||
el = v.to_tree(tagname, idx)
|
||||
else:
|
||||
tagname = namespaced(obj, tagname, namespace)
|
||||
el = Element(tagname)
|
||||
el.text = safe_string(v)
|
||||
yield el
|
||||
|
||||
|
||||
class UniqueSequence(Sequence):
|
||||
"""
|
||||
Use a set to keep values unique
|
||||
"""
|
||||
seq_types = (list, tuple, set)
|
||||
container = set
|
||||
|
||||
|
||||
class ValueSequence(Sequence):
|
||||
"""
|
||||
A sequence of primitive types that are stored as a single attribute.
|
||||
"val" is the default attribute
|
||||
"""
|
||||
|
||||
attribute = "val"
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
for v in obj:
|
||||
yield Element(tagname, {self.attribute:safe_string(v)})
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
|
||||
return node.get(self.attribute)
|
||||
|
||||
|
||||
class NestedSequence(Sequence):
|
||||
"""
|
||||
Wrap a sequence in an containing object
|
||||
"""
|
||||
|
||||
count = False
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
container = Element(tagname)
|
||||
if self.count:
|
||||
container.set('count', str(len(obj)))
|
||||
for v in obj:
|
||||
container.append(v.to_tree())
|
||||
return container
|
||||
|
||||
|
||||
def from_tree(self, node):
|
||||
return [self.expected_type.from_tree(el) for el in node]
|
||||
|
||||
|
||||
class MultiSequence(Sequence):
|
||||
"""
|
||||
Sequences can contain objects with different tags
|
||||
"""
|
||||
|
||||
def __set__(self, instance, seq):
|
||||
if not isinstance(seq, (tuple, list)):
|
||||
raise ValueError("Value must be a sequence")
|
||||
seq = list(seq)
|
||||
Descriptor.__set__(self, instance, seq)
|
||||
|
||||
|
||||
def to_tree(self, tagname, obj, namespace=None):
|
||||
"""
|
||||
Convert the sequence represented by the descriptor to an XML element
|
||||
"""
|
||||
for v in obj:
|
||||
el = v.to_tree(namespace=namespace)
|
||||
yield el
|
||||
|
||||
|
||||
class MultiSequencePart(Alias):
|
||||
"""
|
||||
Allow a multisequence to be built up from parts
|
||||
|
||||
Excluded from the instance __elements__ or __attrs__ as is effectively an Alias
|
||||
"""
|
||||
|
||||
def __init__(self, expected_type, store):
|
||||
self.expected_type = expected_type
|
||||
self.store = store
|
||||
|
||||
|
||||
def __set__(self, instance, value):
|
||||
value = _convert(self.expected_type, value)
|
||||
instance.__dict__[self.store].append(value)
|
||||
|
||||
|
||||
def __get__(self, instance, cls):
|
||||
return self
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from copy import copy
|
||||
from keyword import kwlist
|
||||
KEYWORDS = frozenset(kwlist)
|
||||
|
||||
from . import Descriptor
|
||||
from . import MetaSerialisable
|
||||
from .sequence import (
|
||||
Sequence,
|
||||
NestedSequence,
|
||||
MultiSequencePart,
|
||||
)
|
||||
from .namespace import namespaced
|
||||
|
||||
from openpyxl.compat import safe_string
|
||||
from openpyxl.xml.functions import (
|
||||
Element,
|
||||
localname,
|
||||
)
|
||||
|
||||
seq_types = (list, tuple)
|
||||
|
||||
class Serialisable(metaclass=MetaSerialisable):
|
||||
"""
|
||||
Objects can serialise to XML their attributes and child objects.
|
||||
The following class attributes are created by the metaclass at runtime:
|
||||
__attrs__ = attributes
|
||||
__nested__ = single-valued child treated as an attribute
|
||||
__elements__ = child elements
|
||||
"""
|
||||
|
||||
__attrs__ = None
|
||||
__nested__ = None
|
||||
__elements__ = None
|
||||
__namespaced__ = None
|
||||
|
||||
idx_base = 0
|
||||
|
||||
@property
|
||||
def tagname(self):
|
||||
raise(NotImplementedError)
|
||||
|
||||
namespace = None
|
||||
|
||||
@classmethod
|
||||
def from_tree(cls, node):
|
||||
"""
|
||||
Create object from XML
|
||||
"""
|
||||
# strip known namespaces from attributes
|
||||
attrib = dict(node.attrib)
|
||||
for key, ns in cls.__namespaced__:
|
||||
if ns in attrib:
|
||||
attrib[key] = attrib[ns]
|
||||
del attrib[ns]
|
||||
|
||||
# strip attributes with unknown namespaces
|
||||
for key in list(attrib):
|
||||
if key.startswith('{'):
|
||||
del attrib[key]
|
||||
elif key in KEYWORDS:
|
||||
attrib["_" + key] = attrib[key]
|
||||
del attrib[key]
|
||||
elif "-" in key:
|
||||
n = key.replace("-", "_")
|
||||
attrib[n] = attrib[key]
|
||||
del attrib[key]
|
||||
|
||||
if node.text and "attr_text" in cls.__attrs__:
|
||||
attrib["attr_text"] = node.text
|
||||
|
||||
for el in node:
|
||||
tag = localname(el)
|
||||
if tag in KEYWORDS:
|
||||
tag = "_" + tag
|
||||
desc = getattr(cls, tag, None)
|
||||
if desc is None or isinstance(desc, property):
|
||||
continue
|
||||
|
||||
if hasattr(desc, 'from_tree'):
|
||||
#descriptor manages conversion
|
||||
obj = desc.from_tree(el)
|
||||
else:
|
||||
if hasattr(desc.expected_type, "from_tree"):
|
||||
#complex type
|
||||
obj = desc.expected_type.from_tree(el)
|
||||
else:
|
||||
#primitive
|
||||
obj = el.text
|
||||
|
||||
if isinstance(desc, NestedSequence):
|
||||
attrib[tag] = obj
|
||||
elif isinstance(desc, Sequence):
|
||||
attrib.setdefault(tag, [])
|
||||
attrib[tag].append(obj)
|
||||
elif isinstance(desc, MultiSequencePart):
|
||||
attrib.setdefault(desc.store, [])
|
||||
attrib[desc.store].append(obj)
|
||||
else:
|
||||
attrib[tag] = obj
|
||||
|
||||
return cls(**attrib)
|
||||
|
||||
|
||||
def to_tree(self, tagname=None, idx=None, namespace=None):
|
||||
|
||||
if tagname is None:
|
||||
tagname = self.tagname
|
||||
|
||||
# keywords have to be masked
|
||||
if tagname.startswith("_"):
|
||||
tagname = tagname[1:]
|
||||
|
||||
tagname = namespaced(self, tagname, namespace)
|
||||
namespace = getattr(self, "namespace", namespace)
|
||||
|
||||
attrs = dict(self)
|
||||
for key, ns in self.__namespaced__:
|
||||
if key in attrs:
|
||||
attrs[ns] = attrs[key]
|
||||
del attrs[key]
|
||||
|
||||
el = Element(tagname, attrs)
|
||||
if "attr_text" in self.__attrs__:
|
||||
el.text = safe_string(getattr(self, "attr_text"))
|
||||
|
||||
for child_tag in self.__elements__:
|
||||
desc = getattr(self.__class__, child_tag, None)
|
||||
obj = getattr(self, child_tag)
|
||||
if hasattr(desc, "namespace") and hasattr(obj, 'namespace'):
|
||||
obj.namespace = desc.namespace
|
||||
|
||||
if isinstance(obj, seq_types):
|
||||
if isinstance(desc, NestedSequence):
|
||||
# wrap sequence in container
|
||||
if not obj:
|
||||
continue
|
||||
nodes = [desc.to_tree(child_tag, obj, namespace)]
|
||||
elif isinstance(desc, Sequence):
|
||||
# sequence
|
||||
desc.idx_base = self.idx_base
|
||||
nodes = (desc.to_tree(child_tag, obj, namespace))
|
||||
else: # property
|
||||
nodes = (v.to_tree(child_tag, namespace) for v in obj)
|
||||
for node in nodes:
|
||||
el.append(node)
|
||||
else:
|
||||
if child_tag in self.__nested__:
|
||||
node = desc.to_tree(child_tag, obj, namespace)
|
||||
elif obj is None:
|
||||
continue
|
||||
else:
|
||||
node = obj.to_tree(child_tag)
|
||||
if node is not None:
|
||||
el.append(node)
|
||||
return el
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
for attr in self.__attrs__:
|
||||
value = getattr(self, attr)
|
||||
if attr.startswith("_"):
|
||||
attr = attr[1:]
|
||||
elif attr != "attr_text" and "_" in attr:
|
||||
desc = getattr(self.__class__, attr)
|
||||
if getattr(desc, "hyphenated", False):
|
||||
attr = attr.replace("_", "-")
|
||||
if attr != "attr_text" and value is not None:
|
||||
yield attr, safe_string(value)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
if not self.__class__ == other.__class__:
|
||||
return False
|
||||
elif not dict(self) == dict(other):
|
||||
return False
|
||||
for el in self.__elements__:
|
||||
if getattr(self, el) != getattr(other, el):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
s = u"<{0}.{1} object>\nParameters:".format(
|
||||
self.__module__,
|
||||
self.__class__.__name__
|
||||
)
|
||||
args = []
|
||||
for k in self.__attrs__ + self.__elements__:
|
||||
v = getattr(self, k)
|
||||
if isinstance(v, Descriptor):
|
||||
v = None
|
||||
args.append(u"{0}={1}".format(k, repr(v)))
|
||||
args = u", ".join(args)
|
||||
|
||||
return u"\n".join([s, args])
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
fields = []
|
||||
for attr in self.__attrs__ + self.__elements__:
|
||||
val = getattr(self, attr)
|
||||
if isinstance(val, list):
|
||||
val = tuple(val)
|
||||
fields.append(val)
|
||||
|
||||
return hash(tuple(fields))
|
||||
|
||||
|
||||
def __add__(self, other):
|
||||
if type(self) != type(other):
|
||||
raise TypeError("Cannot combine instances of different types")
|
||||
vals = {}
|
||||
for attr in self.__attrs__:
|
||||
vals[attr] = getattr(self, attr) or getattr(other, attr)
|
||||
for el in self.__elements__:
|
||||
a = getattr(self, el)
|
||||
b = getattr(other, el)
|
||||
if a and b:
|
||||
vals[el] = a + b
|
||||
else:
|
||||
vals[el] = a or b
|
||||
return self.__class__(**vals)
|
||||
|
||||
|
||||
def __copy__(self):
|
||||
# serialise to xml and back to avoid shallow copies
|
||||
xml = self.to_tree(tagname="dummy")
|
||||
cp = self.__class__.from_tree(xml)
|
||||
# copy any non-persisted attributed
|
||||
for k in self.__dict__:
|
||||
if k not in self.__attrs__ + self.__elements__:
|
||||
v = copy(getattr(self, k))
|
||||
setattr(cp, k, v)
|
||||
return cp
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Metaclass for mixing slots and descriptors
|
||||
# From "Programming in Python 3" by Mark Summerfield Ch.8 p. 383
|
||||
|
||||
class AutoSlotProperties(type):
|
||||
|
||||
def __new__(mcl, classname, bases, dictionary):
|
||||
slots = list(dictionary.get("__slots__", []))
|
||||
for getter_name in [key for key in dictionary if key.startswith("get_")]:
|
||||
name = getter_name
|
||||
slots.append("__" + name)
|
||||
getter = dictionary.pop(getter_name)
|
||||
setter = dictionary.get(setter_name, None)
|
||||
if (setter is not None
|
||||
and isinstance(setter, collections.Callable)):
|
||||
del dictionary[setter_name]
|
||||
dictionary[name] = property(getter. setter)
|
||||
dictionary["__slots__"] = tuple(slots)
|
||||
return super().__new__(mcl, classname, bases, dictionary)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
|
||||
from .drawing import Drawing
|
||||
+435
@@ -0,0 +1,435 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Typed,
|
||||
Integer,
|
||||
Set,
|
||||
MinMax,
|
||||
)
|
||||
from openpyxl.descriptors.excel import Percentage
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedNoneSet,
|
||||
NestedValue,
|
||||
NestedInteger,
|
||||
EmptyTag,
|
||||
)
|
||||
|
||||
from openpyxl.styles.colors import RGB
|
||||
from openpyxl.xml.constants import DRAWING_NS
|
||||
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
|
||||
PRESET_COLORS = [
|
||||
'aliceBlue', 'antiqueWhite', 'aqua', 'aquamarine',
|
||||
'azure', 'beige', 'bisque', 'black', 'blanchedAlmond', 'blue',
|
||||
'blueViolet', 'brown', 'burlyWood', 'cadetBlue', 'chartreuse',
|
||||
'chocolate', 'coral', 'cornflowerBlue', 'cornsilk', 'crimson', 'cyan',
|
||||
'darkBlue', 'darkCyan', 'darkGoldenrod', 'darkGray', 'darkGrey',
|
||||
'darkGreen', 'darkKhaki', 'darkMagenta', 'darkOliveGreen', 'darkOrange',
|
||||
'darkOrchid', 'darkRed', 'darkSalmon', 'darkSeaGreen', 'darkSlateBlue',
|
||||
'darkSlateGray', 'darkSlateGrey', 'darkTurquoise', 'darkViolet',
|
||||
'dkBlue', 'dkCyan', 'dkGoldenrod', 'dkGray', 'dkGrey', 'dkGreen',
|
||||
'dkKhaki', 'dkMagenta', 'dkOliveGreen', 'dkOrange', 'dkOrchid', 'dkRed',
|
||||
'dkSalmon', 'dkSeaGreen', 'dkSlateBlue', 'dkSlateGray', 'dkSlateGrey',
|
||||
'dkTurquoise', 'dkViolet', 'deepPink', 'deepSkyBlue', 'dimGray',
|
||||
'dimGrey', 'dodgerBlue', 'firebrick', 'floralWhite', 'forestGreen',
|
||||
'fuchsia', 'gainsboro', 'ghostWhite', 'gold', 'goldenrod', 'gray',
|
||||
'grey', 'green', 'greenYellow', 'honeydew', 'hotPink', 'indianRed',
|
||||
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderBlush', 'lawnGreen',
|
||||
'lemonChiffon', 'lightBlue', 'lightCoral', 'lightCyan',
|
||||
'lightGoldenrodYellow', 'lightGray', 'lightGrey', 'lightGreen',
|
||||
'lightPink', 'lightSalmon', 'lightSeaGreen', 'lightSkyBlue',
|
||||
'lightSlateGray', 'lightSlateGrey', 'lightSteelBlue', 'lightYellow',
|
||||
'ltBlue', 'ltCoral', 'ltCyan', 'ltGoldenrodYellow', 'ltGray', 'ltGrey',
|
||||
'ltGreen', 'ltPink', 'ltSalmon', 'ltSeaGreen', 'ltSkyBlue',
|
||||
'ltSlateGray', 'ltSlateGrey', 'ltSteelBlue', 'ltYellow', 'lime',
|
||||
'limeGreen', 'linen', 'magenta', 'maroon', 'medAquamarine', 'medBlue',
|
||||
'medOrchid', 'medPurple', 'medSeaGreen', 'medSlateBlue',
|
||||
'medSpringGreen', 'medTurquoise', 'medVioletRed', 'mediumAquamarine',
|
||||
'mediumBlue', 'mediumOrchid', 'mediumPurple', 'mediumSeaGreen',
|
||||
'mediumSlateBlue', 'mediumSpringGreen', 'mediumTurquoise',
|
||||
'mediumVioletRed', 'midnightBlue', 'mintCream', 'mistyRose', 'moccasin',
|
||||
'navajoWhite', 'navy', 'oldLace', 'olive', 'oliveDrab', 'orange',
|
||||
'orangeRed', 'orchid', 'paleGoldenrod', 'paleGreen', 'paleTurquoise',
|
||||
'paleVioletRed', 'papayaWhip', 'peachPuff', 'peru', 'pink', 'plum',
|
||||
'powderBlue', 'purple', 'red', 'rosyBrown', 'royalBlue', 'saddleBrown',
|
||||
'salmon', 'sandyBrown', 'seaGreen', 'seaShell', 'sienna', 'silver',
|
||||
'skyBlue', 'slateBlue', 'slateGray', 'slateGrey', 'snow', 'springGreen',
|
||||
'steelBlue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet',
|
||||
'wheat', 'white', 'whiteSmoke', 'yellow', 'yellowGreen'
|
||||
]
|
||||
|
||||
|
||||
SCHEME_COLORS= ['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2', 'accent3',
|
||||
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr', 'dk1', 'lt1',
|
||||
'dk2', 'lt2'
|
||||
]
|
||||
|
||||
|
||||
class Transform(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SystemColor(Serialisable):
|
||||
|
||||
tagname = "sysClr"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
# color transform options
|
||||
tint = NestedInteger(allow_none=True)
|
||||
shade = NestedInteger(allow_none=True)
|
||||
comp = Typed(expected_type=Transform, allow_none=True)
|
||||
inv = Typed(expected_type=Transform, allow_none=True)
|
||||
gray = Typed(expected_type=Transform, allow_none=True)
|
||||
alpha = NestedInteger(allow_none=True)
|
||||
alphaOff = NestedInteger(allow_none=True)
|
||||
alphaMod = NestedInteger(allow_none=True)
|
||||
hue = NestedInteger(allow_none=True)
|
||||
hueOff = NestedInteger(allow_none=True)
|
||||
hueMod = NestedInteger(allow_none=True)
|
||||
sat = NestedInteger(allow_none=True)
|
||||
satOff = NestedInteger(allow_none=True)
|
||||
satMod = NestedInteger(allow_none=True)
|
||||
lum = NestedInteger(allow_none=True)
|
||||
lumOff = NestedInteger(allow_none=True)
|
||||
lumMod = NestedInteger(allow_none=True)
|
||||
red = NestedInteger(allow_none=True)
|
||||
redOff = NestedInteger(allow_none=True)
|
||||
redMod = NestedInteger(allow_none=True)
|
||||
green = NestedInteger(allow_none=True)
|
||||
greenOff = NestedInteger(allow_none=True)
|
||||
greenMod = NestedInteger(allow_none=True)
|
||||
blue = NestedInteger(allow_none=True)
|
||||
blueOff = NestedInteger(allow_none=True)
|
||||
blueMod = NestedInteger(allow_none=True)
|
||||
gamma = Typed(expected_type=Transform, allow_none=True)
|
||||
invGamma = Typed(expected_type=Transform, allow_none=True)
|
||||
|
||||
val = Set(values=( ['scrollBar', 'background', 'activeCaption',
|
||||
'inactiveCaption', 'menu', 'window', 'windowFrame', 'menuText',
|
||||
'windowText', 'captionText', 'activeBorder', 'inactiveBorder',
|
||||
'appWorkspace', 'highlight', 'highlightText', 'btnFace', 'btnShadow',
|
||||
'grayText', 'btnText', 'inactiveCaptionText', 'btnHighlight',
|
||||
'3dDkShadow', '3dLight', 'infoText', 'infoBk', 'hotLight',
|
||||
'gradientActiveCaption', 'gradientInactiveCaption', 'menuHighlight',
|
||||
'menuBar'] )
|
||||
)
|
||||
lastClr = RGB(allow_none=True)
|
||||
|
||||
__elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', "alpha",
|
||||
"alphaOff", "alphaMod", "hue", "hueOff", "hueMod", "hueOff", "sat",
|
||||
"satOff", "satMod", "lum", "lumOff", "lumMod", "red", "redOff", "redMod",
|
||||
"green", "greenOff", "greenMod", "blue", "blueOff", "blueMod", "gamma",
|
||||
"invGamma")
|
||||
|
||||
def __init__(self,
|
||||
val="windowText",
|
||||
lastClr=None,
|
||||
tint=None,
|
||||
shade=None,
|
||||
comp=None,
|
||||
inv=None,
|
||||
gray=None,
|
||||
alpha=None,
|
||||
alphaOff=None,
|
||||
alphaMod=None,
|
||||
hue=None,
|
||||
hueOff=None,
|
||||
hueMod=None,
|
||||
sat=None,
|
||||
satOff=None,
|
||||
satMod=None,
|
||||
lum=None,
|
||||
lumOff=None,
|
||||
lumMod=None,
|
||||
red=None,
|
||||
redOff=None,
|
||||
redMod=None,
|
||||
green=None,
|
||||
greenOff=None,
|
||||
greenMod=None,
|
||||
blue=None,
|
||||
blueOff=None,
|
||||
blueMod=None,
|
||||
gamma=None,
|
||||
invGamma=None
|
||||
):
|
||||
self.val = val
|
||||
self.lastClr = lastClr
|
||||
self.tint = tint
|
||||
self.shade = shade
|
||||
self.comp = comp
|
||||
self.inv = inv
|
||||
self.gray = gray
|
||||
self.alpha = alpha
|
||||
self.alphaOff = alphaOff
|
||||
self.alphaMod = alphaMod
|
||||
self.hue = hue
|
||||
self.hueOff = hueOff
|
||||
self.hueMod = hueMod
|
||||
self.sat = sat
|
||||
self.satOff = satOff
|
||||
self.satMod = satMod
|
||||
self.lum = lum
|
||||
self.lumOff = lumOff
|
||||
self.lumMod = lumMod
|
||||
self.red = red
|
||||
self.redOff = redOff
|
||||
self.redMod = redMod
|
||||
self.green = green
|
||||
self.greenOff = greenOff
|
||||
self.greenMod = greenMod
|
||||
self.blue = blue
|
||||
self.blueOff = blueOff
|
||||
self.blueMod = blueMod
|
||||
self.gamma = gamma
|
||||
self.invGamma = invGamma
|
||||
|
||||
|
||||
class HSLColor(Serialisable):
|
||||
|
||||
tagname = "hslClr"
|
||||
|
||||
hue = Integer()
|
||||
sat = MinMax(min=0, max=100)
|
||||
lum = MinMax(min=0, max=100)
|
||||
|
||||
#TODO add color transform options
|
||||
|
||||
def __init__(self,
|
||||
hue=None,
|
||||
sat=None,
|
||||
lum=None,
|
||||
):
|
||||
self.hue = hue
|
||||
self.sat = sat
|
||||
self.lum = lum
|
||||
|
||||
|
||||
|
||||
class RGBPercent(Serialisable):
|
||||
|
||||
tagname = "rgbClr"
|
||||
|
||||
r = MinMax(min=0, max=100)
|
||||
g = MinMax(min=0, max=100)
|
||||
b = MinMax(min=0, max=100)
|
||||
|
||||
#TODO add color transform options
|
||||
|
||||
def __init__(self,
|
||||
r=None,
|
||||
g=None,
|
||||
b=None,
|
||||
):
|
||||
self.r = r
|
||||
self.g = g
|
||||
self.b = b
|
||||
|
||||
|
||||
class SchemeColor(Serialisable):
|
||||
|
||||
tagname = "schemeClr"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
tint = NestedInteger(allow_none=True)
|
||||
shade = NestedInteger(allow_none=True)
|
||||
comp = EmptyTag(allow_none=True)
|
||||
inv = NestedInteger(allow_none=True)
|
||||
gray = NestedInteger(allow_none=True)
|
||||
alpha = NestedInteger(allow_none=True)
|
||||
alphaOff = NestedInteger(allow_none=True)
|
||||
alphaMod = NestedInteger(allow_none=True)
|
||||
hue = NestedInteger(allow_none=True)
|
||||
hueOff = NestedInteger(allow_none=True)
|
||||
hueMod = NestedInteger(allow_none=True)
|
||||
sat = NestedInteger(allow_none=True)
|
||||
satOff = NestedInteger(allow_none=True)
|
||||
satMod = NestedInteger(allow_none=True)
|
||||
lum = NestedInteger(allow_none=True)
|
||||
lumOff = NestedInteger(allow_none=True)
|
||||
lumMod = NestedInteger(allow_none=True)
|
||||
red = NestedInteger(allow_none=True)
|
||||
redOff = NestedInteger(allow_none=True)
|
||||
redMod = NestedInteger(allow_none=True)
|
||||
green = NestedInteger(allow_none=True)
|
||||
greenOff = NestedInteger(allow_none=True)
|
||||
greenMod = NestedInteger(allow_none=True)
|
||||
blue = NestedInteger(allow_none=True)
|
||||
blueOff = NestedInteger(allow_none=True)
|
||||
blueMod = NestedInteger(allow_none=True)
|
||||
gamma = EmptyTag(allow_none=True)
|
||||
invGamma = EmptyTag(allow_none=True)
|
||||
val = Set(values=(['bg1', 'tx1', 'bg2', 'tx2', 'accent1', 'accent2',
|
||||
'accent3', 'accent4', 'accent5', 'accent6', 'hlink', 'folHlink', 'phClr',
|
||||
'dk1', 'lt1', 'dk2', 'lt2']))
|
||||
|
||||
__elements__ = ('tint', 'shade', 'comp', 'inv', 'gray', 'alpha',
|
||||
'alphaOff', 'alphaMod', 'hue', 'hueOff', 'hueMod', 'sat', 'satOff',
|
||||
'satMod', 'lum', 'lumMod', 'lumOff', 'red', 'redOff', 'redMod', 'green',
|
||||
'greenOff', 'greenMod', 'blue', 'blueOff', 'blueMod', 'gamma',
|
||||
'invGamma')
|
||||
|
||||
def __init__(self,
|
||||
tint=None,
|
||||
shade=None,
|
||||
comp=None,
|
||||
inv=None,
|
||||
gray=None,
|
||||
alpha=None,
|
||||
alphaOff=None,
|
||||
alphaMod=None,
|
||||
hue=None,
|
||||
hueOff=None,
|
||||
hueMod=None,
|
||||
sat=None,
|
||||
satOff=None,
|
||||
satMod=None,
|
||||
lum=None,
|
||||
lumOff=None,
|
||||
lumMod=None,
|
||||
red=None,
|
||||
redOff=None,
|
||||
redMod=None,
|
||||
green=None,
|
||||
greenOff=None,
|
||||
greenMod=None,
|
||||
blue=None,
|
||||
blueOff=None,
|
||||
blueMod=None,
|
||||
gamma=None,
|
||||
invGamma=None,
|
||||
val=None,
|
||||
):
|
||||
self.tint = tint
|
||||
self.shade = shade
|
||||
self.comp = comp
|
||||
self.inv = inv
|
||||
self.gray = gray
|
||||
self.alpha = alpha
|
||||
self.alphaOff = alphaOff
|
||||
self.alphaMod = alphaMod
|
||||
self.hue = hue
|
||||
self.hueOff = hueOff
|
||||
self.hueMod = hueMod
|
||||
self.sat = sat
|
||||
self.satOff = satOff
|
||||
self.satMod = satMod
|
||||
self.lum = lum
|
||||
self.lumOff = lumOff
|
||||
self.lumMod = lumMod
|
||||
self.red = red
|
||||
self.redOff = redOff
|
||||
self.redMod = redMod
|
||||
self.green = green
|
||||
self.greenOff = greenOff
|
||||
self.greenMod = greenMod
|
||||
self.blue = blue
|
||||
self.blueOff = blueOff
|
||||
self.blueMod = blueMod
|
||||
self.gamma = gamma
|
||||
self.invGamma = invGamma
|
||||
self.val = val
|
||||
|
||||
class ColorChoice(Serialisable):
|
||||
|
||||
tagname = "colorChoice"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
|
||||
RGBPercent = Alias('scrgbClr')
|
||||
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
|
||||
RGB = Alias('srgbClr')
|
||||
hslClr = Typed(expected_type=HSLColor, allow_none=True)
|
||||
sysClr = Typed(expected_type=SystemColor, allow_none=True)
|
||||
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
|
||||
prstClr = NestedNoneSet(values=PRESET_COLORS)
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
scrgbClr=None,
|
||||
srgbClr=None,
|
||||
hslClr=None,
|
||||
sysClr=None,
|
||||
schemeClr=None,
|
||||
prstClr=None,
|
||||
):
|
||||
self.scrgbClr = scrgbClr
|
||||
self.srgbClr = srgbClr
|
||||
self.hslClr = hslClr
|
||||
self.sysClr = sysClr
|
||||
self.schemeClr = schemeClr
|
||||
self.prstClr = prstClr
|
||||
|
||||
_COLOR_SET = ('dk1', 'lt1', 'dk2', 'lt2', 'accent1', 'accent2', 'accent3',
|
||||
'accent4', 'accent5', 'accent6', 'hlink', 'folHlink')
|
||||
|
||||
|
||||
class ColorMapping(Serialisable):
|
||||
|
||||
tagname = "clrMapOvr"
|
||||
|
||||
bg1 = Set(values=_COLOR_SET)
|
||||
tx1 = Set(values=_COLOR_SET)
|
||||
bg2 = Set(values=_COLOR_SET)
|
||||
tx2 = Set(values=_COLOR_SET)
|
||||
accent1 = Set(values=_COLOR_SET)
|
||||
accent2 = Set(values=_COLOR_SET)
|
||||
accent3 = Set(values=_COLOR_SET)
|
||||
accent4 = Set(values=_COLOR_SET)
|
||||
accent5 = Set(values=_COLOR_SET)
|
||||
accent6 = Set(values=_COLOR_SET)
|
||||
hlink = Set(values=_COLOR_SET)
|
||||
folHlink = Set(values=_COLOR_SET)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
bg1="lt1",
|
||||
tx1="dk1",
|
||||
bg2="lt2",
|
||||
tx2="dk2",
|
||||
accent1="accent1",
|
||||
accent2="accent2",
|
||||
accent3="accent3",
|
||||
accent4="accent4",
|
||||
accent5="accent5",
|
||||
accent6="accent6",
|
||||
hlink="hlink",
|
||||
folHlink="folHlink",
|
||||
extLst=None,
|
||||
):
|
||||
self.bg1 = bg1
|
||||
self.tx1 = tx1
|
||||
self.bg2 = bg2
|
||||
self.tx2 = tx2
|
||||
self.accent1 = accent1
|
||||
self.accent2 = accent2
|
||||
self.accent3 = accent3
|
||||
self.accent4 = accent4
|
||||
self.accent5 = accent5
|
||||
self.accent6 = accent6
|
||||
self.hlink = hlink
|
||||
self.folHlink = folHlink
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class ColorChoiceDescriptor(Typed):
|
||||
"""
|
||||
Objects can choose from 7 different kinds of color system.
|
||||
Assume RGBHex if a string is passed in.
|
||||
"""
|
||||
|
||||
expected_type = ColorChoice
|
||||
allow_none = True
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if isinstance(value, str):
|
||||
value = ColorChoice(srgbClr=value)
|
||||
else:
|
||||
if hasattr(self, "namespace") and value is not None:
|
||||
value.namespace = self.namespace
|
||||
super().__set__(instance, value)
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
Integer,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
from openpyxl.chart.shapes import GraphicalProperties
|
||||
from openpyxl.chart.text import RichText
|
||||
|
||||
from .properties import (
|
||||
NonVisualDrawingProps,
|
||||
NonVisualDrawingShapeProps,
|
||||
)
|
||||
from .geometry import ShapeStyle
|
||||
|
||||
class Connection(Serialisable):
|
||||
|
||||
id = Integer()
|
||||
idx = Integer()
|
||||
|
||||
def __init__(self,
|
||||
id=None,
|
||||
idx=None,
|
||||
):
|
||||
self.id = id
|
||||
self.idx = idx
|
||||
|
||||
|
||||
class ConnectorLocking(Serialisable):
|
||||
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
extLst=None,
|
||||
):
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class NonVisualConnectorProperties(Serialisable):
|
||||
|
||||
cxnSpLocks = Typed(expected_type=ConnectorLocking, allow_none=True)
|
||||
stCxn = Typed(expected_type=Connection, allow_none=True)
|
||||
endCxn = Typed(expected_type=Connection, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
cxnSpLocks=None,
|
||||
stCxn=None,
|
||||
endCxn=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.cxnSpLocks = cxnSpLocks
|
||||
self.stCxn = stCxn
|
||||
self.endCxn = endCxn
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class ConnectorNonVisual(Serialisable):
|
||||
|
||||
cNvPr = Typed(expected_type=NonVisualDrawingProps, )
|
||||
cNvCxnSpPr = Typed(expected_type=NonVisualConnectorProperties, )
|
||||
|
||||
__elements__ = ("cNvPr", "cNvCxnSpPr",)
|
||||
|
||||
def __init__(self,
|
||||
cNvPr=None,
|
||||
cNvCxnSpPr=None,
|
||||
):
|
||||
self.cNvPr = cNvPr
|
||||
self.cNvCxnSpPr = cNvCxnSpPr
|
||||
|
||||
|
||||
class ConnectorShape(Serialisable):
|
||||
|
||||
tagname = "cxnSp"
|
||||
|
||||
nvCxnSpPr = Typed(expected_type=ConnectorNonVisual)
|
||||
spPr = Typed(expected_type=GraphicalProperties)
|
||||
style = Typed(expected_type=ShapeStyle, allow_none=True)
|
||||
macro = String(allow_none=True)
|
||||
fPublished = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
nvCxnSpPr=None,
|
||||
spPr=None,
|
||||
style=None,
|
||||
macro=None,
|
||||
fPublished=None,
|
||||
):
|
||||
self.nvCxnSpPr = nvCxnSpPr
|
||||
self.spPr = spPr
|
||||
self.style = style
|
||||
self.macro = macro
|
||||
self.fPublished = fPublished
|
||||
|
||||
|
||||
class ShapeMeta(Serialisable):
|
||||
|
||||
tagname = "nvSpPr"
|
||||
|
||||
cNvPr = Typed(expected_type=NonVisualDrawingProps)
|
||||
cNvSpPr = Typed(expected_type=NonVisualDrawingShapeProps)
|
||||
|
||||
def __init__(self, cNvPr=None, cNvSpPr=None):
|
||||
self.cNvPr = cNvPr
|
||||
self.cNvSpPr = cNvSpPr
|
||||
|
||||
|
||||
class Shape(Serialisable):
|
||||
|
||||
macro = String(allow_none=True)
|
||||
textlink = String(allow_none=True)
|
||||
fPublished = Bool(allow_none=True)
|
||||
fLocksText = Bool(allow_none=True)
|
||||
nvSpPr = Typed(expected_type=ShapeMeta, allow_none=True)
|
||||
meta = Alias("nvSpPr")
|
||||
spPr = Typed(expected_type=GraphicalProperties)
|
||||
graphicalProperties = Alias("spPr")
|
||||
style = Typed(expected_type=ShapeStyle, allow_none=True)
|
||||
txBody = Typed(expected_type=RichText, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
macro=None,
|
||||
textlink=None,
|
||||
fPublished=None,
|
||||
fLocksText=None,
|
||||
nvSpPr=None,
|
||||
spPr=None,
|
||||
style=None,
|
||||
txBody=None,
|
||||
):
|
||||
self.macro = macro
|
||||
self.textlink = textlink
|
||||
self.fPublished = fPublished
|
||||
self.fLocksText = fLocksText
|
||||
self.nvSpPr = nvSpPr
|
||||
self.spPr = spPr
|
||||
self.style = style
|
||||
self.txBody = txBody
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
import math
|
||||
|
||||
from openpyxl.utils.units import pixels_to_EMU
|
||||
|
||||
|
||||
class Drawing:
|
||||
""" a drawing object - eg container for shapes or charts
|
||||
we assume user specifies dimensions in pixels; units are
|
||||
converted to EMU in the drawing part
|
||||
"""
|
||||
|
||||
count = 0
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.name = ''
|
||||
self.description = ''
|
||||
self.coordinates = ((1, 2), (16, 8))
|
||||
self.left = 0
|
||||
self.top = 0
|
||||
self._width = 21 # default in px
|
||||
self._height = 192 #default in px
|
||||
self.resize_proportional = False
|
||||
self.rotation = 0
|
||||
self.anchortype = "absolute"
|
||||
self.anchorcol = 0 # left cell
|
||||
self.anchorrow = 0 # top row
|
||||
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
|
||||
@width.setter
|
||||
def width(self, w):
|
||||
if self.resize_proportional and w:
|
||||
ratio = self._height / self._width
|
||||
self._height = round(ratio * w)
|
||||
self._width = w
|
||||
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
|
||||
@height.setter
|
||||
def height(self, h):
|
||||
if self.resize_proportional and h:
|
||||
ratio = self._width / self._height
|
||||
self._width = round(ratio * h)
|
||||
self._height = h
|
||||
|
||||
|
||||
def set_dimension(self, w=0, h=0):
|
||||
|
||||
xratio = w / self._width
|
||||
yratio = h / self._height
|
||||
|
||||
if self.resize_proportional and w and h:
|
||||
if (xratio * self._height) < h:
|
||||
self._height = math.ceil(xratio * self._height)
|
||||
self._width = w
|
||||
else:
|
||||
self._width = math.ceil(yratio * self._width)
|
||||
self._height = h
|
||||
|
||||
|
||||
@property
|
||||
def anchor(self):
|
||||
from .spreadsheet_drawing import (
|
||||
OneCellAnchor,
|
||||
TwoCellAnchor,
|
||||
AbsoluteAnchor)
|
||||
if self.anchortype == "absolute":
|
||||
anchor = AbsoluteAnchor()
|
||||
anchor.pos.x = pixels_to_EMU(self.left)
|
||||
anchor.pos.y = pixels_to_EMU(self.top)
|
||||
|
||||
elif self.anchortype == "oneCell":
|
||||
anchor = OneCellAnchor()
|
||||
anchor._from.col = self.anchorcol
|
||||
anchor._from.row = self.anchorrow
|
||||
|
||||
anchor.ext.width = pixels_to_EMU(self._width)
|
||||
anchor.ext.height = pixels_to_EMU(self._height)
|
||||
|
||||
return anchor
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
String,
|
||||
Set,
|
||||
Bool,
|
||||
Integer,
|
||||
Float,
|
||||
)
|
||||
|
||||
from .colors import ColorChoice
|
||||
|
||||
|
||||
class TintEffect(Serialisable):
|
||||
|
||||
tagname = "tint"
|
||||
|
||||
hue = Integer()
|
||||
amt = Integer()
|
||||
|
||||
def __init__(self,
|
||||
hue=0,
|
||||
amt=0,
|
||||
):
|
||||
self.hue = hue
|
||||
self.amt = amt
|
||||
|
||||
|
||||
class LuminanceEffect(Serialisable):
|
||||
|
||||
tagname = "lum"
|
||||
|
||||
bright = Integer() #Pct ?
|
||||
contrast = Integer() #Pct#
|
||||
|
||||
def __init__(self,
|
||||
bright=0,
|
||||
contrast=0,
|
||||
):
|
||||
self.bright = bright
|
||||
self.contrast = contrast
|
||||
|
||||
|
||||
class HSLEffect(Serialisable):
|
||||
|
||||
hue = Integer()
|
||||
sat = Integer()
|
||||
lum = Integer()
|
||||
|
||||
def __init__(self,
|
||||
hue=None,
|
||||
sat=None,
|
||||
lum=None,
|
||||
):
|
||||
self.hue = hue
|
||||
self.sat = sat
|
||||
self.lum = lum
|
||||
|
||||
|
||||
class GrayscaleEffect(Serialisable):
|
||||
|
||||
tagname = "grayscl"
|
||||
|
||||
|
||||
class FillOverlayEffect(Serialisable):
|
||||
|
||||
blend = Set(values=(['over', 'mult', 'screen', 'darken', 'lighten']))
|
||||
|
||||
def __init__(self,
|
||||
blend=None,
|
||||
):
|
||||
self.blend = blend
|
||||
|
||||
|
||||
class DuotoneEffect(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class ColorReplaceEffect(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class Color(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class ColorChangeEffect(Serialisable):
|
||||
|
||||
useA = Bool(allow_none=True)
|
||||
clrFrom = Typed(expected_type=Color, )
|
||||
clrTo = Typed(expected_type=Color, )
|
||||
|
||||
def __init__(self,
|
||||
useA=None,
|
||||
clrFrom=None,
|
||||
clrTo=None,
|
||||
):
|
||||
self.useA = useA
|
||||
self.clrFrom = clrFrom
|
||||
self.clrTo = clrTo
|
||||
|
||||
|
||||
class BlurEffect(Serialisable):
|
||||
|
||||
rad = Float()
|
||||
grow = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
rad=None,
|
||||
grow=None,
|
||||
):
|
||||
self.rad = rad
|
||||
self.grow = grow
|
||||
|
||||
|
||||
class BiLevelEffect(Serialisable):
|
||||
|
||||
thresh = Integer()
|
||||
|
||||
def __init__(self,
|
||||
thresh=None,
|
||||
):
|
||||
self.thresh = thresh
|
||||
|
||||
|
||||
class AlphaReplaceEffect(Serialisable):
|
||||
|
||||
a = Integer()
|
||||
|
||||
def __init__(self,
|
||||
a=None,
|
||||
):
|
||||
self.a = a
|
||||
|
||||
|
||||
class AlphaModulateFixedEffect(Serialisable):
|
||||
|
||||
amt = Integer()
|
||||
|
||||
def __init__(self,
|
||||
amt=None,
|
||||
):
|
||||
self.amt = amt
|
||||
|
||||
|
||||
class EffectContainer(Serialisable):
|
||||
|
||||
type = Set(values=(['sib', 'tree']))
|
||||
name = String(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
type=None,
|
||||
name=None,
|
||||
):
|
||||
self.type = type
|
||||
self.name = name
|
||||
|
||||
|
||||
class AlphaModulateEffect(Serialisable):
|
||||
|
||||
cont = Typed(expected_type=EffectContainer, )
|
||||
|
||||
def __init__(self,
|
||||
cont=None,
|
||||
):
|
||||
self.cont = cont
|
||||
|
||||
|
||||
class AlphaInverseEffect(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class AlphaFloorEffect(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class AlphaCeilingEffect(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class AlphaBiLevelEffect(Serialisable):
|
||||
|
||||
thresh = Integer()
|
||||
|
||||
def __init__(self,
|
||||
thresh=None,
|
||||
):
|
||||
self.thresh = thresh
|
||||
|
||||
|
||||
class GlowEffect(ColorChoice):
|
||||
|
||||
rad = Float()
|
||||
# uses element group EG_ColorChoice
|
||||
scrgbClr = ColorChoice.scrgbClr
|
||||
srgbClr = ColorChoice.srgbClr
|
||||
hslClr = ColorChoice.hslClr
|
||||
sysClr = ColorChoice.sysClr
|
||||
schemeClr = ColorChoice.schemeClr
|
||||
prstClr = ColorChoice.prstClr
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
rad=None,
|
||||
**kw
|
||||
):
|
||||
self.rad = rad
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class InnerShadowEffect(ColorChoice):
|
||||
|
||||
blurRad = Float()
|
||||
dist = Float()
|
||||
dir = Integer()
|
||||
# uses element group EG_ColorChoice
|
||||
scrgbClr = ColorChoice.scrgbClr
|
||||
srgbClr = ColorChoice.srgbClr
|
||||
hslClr = ColorChoice.hslClr
|
||||
sysClr = ColorChoice.sysClr
|
||||
schemeClr = ColorChoice.schemeClr
|
||||
prstClr = ColorChoice.prstClr
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
blurRad=None,
|
||||
dist=None,
|
||||
dir=None,
|
||||
**kw
|
||||
):
|
||||
self.blurRad = blurRad
|
||||
self.dist = dist
|
||||
self.dir = dir
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class OuterShadow(ColorChoice):
|
||||
|
||||
tagname = "outerShdw"
|
||||
|
||||
blurRad = Float(allow_none=True)
|
||||
dist = Float(allow_none=True)
|
||||
dir = Integer(allow_none=True)
|
||||
sx = Integer(allow_none=True)
|
||||
sy = Integer(allow_none=True)
|
||||
kx = Integer(allow_none=True)
|
||||
ky = Integer(allow_none=True)
|
||||
algn = Set(values=['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br'])
|
||||
rotWithShape = Bool(allow_none=True)
|
||||
# uses element group EG_ColorChoice
|
||||
scrgbClr = ColorChoice.scrgbClr
|
||||
srgbClr = ColorChoice.srgbClr
|
||||
hslClr = ColorChoice.hslClr
|
||||
sysClr = ColorChoice.sysClr
|
||||
schemeClr = ColorChoice.schemeClr
|
||||
prstClr = ColorChoice.prstClr
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
blurRad=None,
|
||||
dist=None,
|
||||
dir=None,
|
||||
sx=None,
|
||||
sy=None,
|
||||
kx=None,
|
||||
ky=None,
|
||||
algn=None,
|
||||
rotWithShape=None,
|
||||
**kw
|
||||
):
|
||||
self.blurRad = blurRad
|
||||
self.dist = dist
|
||||
self.dir = dir
|
||||
self.sx = sx
|
||||
self.sy = sy
|
||||
self.kx = kx
|
||||
self.ky = ky
|
||||
self.algn = algn
|
||||
self.rotWithShape = rotWithShape
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class PresetShadowEffect(ColorChoice):
|
||||
|
||||
prst = Set(values=(['shdw1', 'shdw2', 'shdw3', 'shdw4', 'shdw5', 'shdw6',
|
||||
'shdw7', 'shdw8', 'shdw9', 'shdw10', 'shdw11', 'shdw12', 'shdw13',
|
||||
'shdw14', 'shdw15', 'shdw16', 'shdw17', 'shdw18', 'shdw19', 'shdw20']))
|
||||
dist = Float()
|
||||
dir = Integer()
|
||||
# uses element group EG_ColorChoice
|
||||
scrgbClr = ColorChoice.scrgbClr
|
||||
srgbClr = ColorChoice.srgbClr
|
||||
hslClr = ColorChoice.hslClr
|
||||
sysClr = ColorChoice.sysClr
|
||||
schemeClr = ColorChoice.schemeClr
|
||||
prstClr = ColorChoice.prstClr
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
prst=None,
|
||||
dist=None,
|
||||
dir=None,
|
||||
**kw
|
||||
):
|
||||
self.prst = prst
|
||||
self.dist = dist
|
||||
self.dir = dir
|
||||
super().__init__(**kw)
|
||||
|
||||
|
||||
class ReflectionEffect(Serialisable):
|
||||
|
||||
blurRad = Float()
|
||||
stA = Integer()
|
||||
stPos = Integer()
|
||||
endA = Integer()
|
||||
endPos = Integer()
|
||||
dist = Float()
|
||||
dir = Integer()
|
||||
fadeDir = Integer()
|
||||
sx = Integer()
|
||||
sy = Integer()
|
||||
kx = Integer()
|
||||
ky = Integer()
|
||||
algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))
|
||||
rotWithShape = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
blurRad=None,
|
||||
stA=None,
|
||||
stPos=None,
|
||||
endA=None,
|
||||
endPos=None,
|
||||
dist=None,
|
||||
dir=None,
|
||||
fadeDir=None,
|
||||
sx=None,
|
||||
sy=None,
|
||||
kx=None,
|
||||
ky=None,
|
||||
algn=None,
|
||||
rotWithShape=None,
|
||||
):
|
||||
self.blurRad = blurRad
|
||||
self.stA = stA
|
||||
self.stPos = stPos
|
||||
self.endA = endA
|
||||
self.endPos = endPos
|
||||
self.dist = dist
|
||||
self.dir = dir
|
||||
self.fadeDir = fadeDir
|
||||
self.sx = sx
|
||||
self.sy = sy
|
||||
self.kx = kx
|
||||
self.ky = ky
|
||||
self.algn = algn
|
||||
self.rotWithShape = rotWithShape
|
||||
|
||||
|
||||
class SoftEdgesEffect(Serialisable):
|
||||
|
||||
rad = Float()
|
||||
|
||||
def __init__(self,
|
||||
rad=None,
|
||||
):
|
||||
self.rad = rad
|
||||
|
||||
|
||||
class EffectList(Serialisable):
|
||||
|
||||
blur = Typed(expected_type=BlurEffect, allow_none=True)
|
||||
fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
|
||||
glow = Typed(expected_type=GlowEffect, allow_none=True)
|
||||
innerShdw = Typed(expected_type=InnerShadowEffect, allow_none=True)
|
||||
outerShdw = Typed(expected_type=OuterShadow, allow_none=True)
|
||||
prstShdw = Typed(expected_type=PresetShadowEffect, allow_none=True)
|
||||
reflection = Typed(expected_type=ReflectionEffect, allow_none=True)
|
||||
softEdge = Typed(expected_type=SoftEdgesEffect, allow_none=True)
|
||||
|
||||
__elements__ = ('blur', 'fillOverlay', 'glow', 'innerShdw', 'outerShdw',
|
||||
'prstShdw', 'reflection', 'softEdge')
|
||||
|
||||
def __init__(self,
|
||||
blur=None,
|
||||
fillOverlay=None,
|
||||
glow=None,
|
||||
innerShdw=None,
|
||||
outerShdw=None,
|
||||
prstShdw=None,
|
||||
reflection=None,
|
||||
softEdge=None,
|
||||
):
|
||||
self.blur = blur
|
||||
self.fillOverlay = fillOverlay
|
||||
self.glow = glow
|
||||
self.innerShdw = innerShdw
|
||||
self.outerShdw = outerShdw
|
||||
self.prstShdw = prstShdw
|
||||
self.reflection = reflection
|
||||
self.softEdge = softEdge
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Alias,
|
||||
Bool,
|
||||
Integer,
|
||||
Set,
|
||||
NoneSet,
|
||||
Typed,
|
||||
MinMax,
|
||||
)
|
||||
from openpyxl.descriptors.excel import (
|
||||
Relation,
|
||||
Percentage,
|
||||
)
|
||||
from openpyxl.descriptors.nested import NestedNoneSet, NestedValue
|
||||
from openpyxl.descriptors.sequence import NestedSequence
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
from openpyxl.xml.constants import DRAWING_NS
|
||||
|
||||
from .colors import (
|
||||
ColorChoice,
|
||||
HSLColor,
|
||||
SystemColor,
|
||||
SchemeColor,
|
||||
PRESET_COLORS,
|
||||
RGBPercent,
|
||||
)
|
||||
|
||||
from .effect import (
|
||||
AlphaBiLevelEffect,
|
||||
AlphaCeilingEffect,
|
||||
AlphaFloorEffect,
|
||||
AlphaInverseEffect,
|
||||
AlphaModulateEffect,
|
||||
AlphaModulateFixedEffect,
|
||||
AlphaReplaceEffect,
|
||||
BiLevelEffect,
|
||||
BlurEffect,
|
||||
ColorChangeEffect,
|
||||
ColorReplaceEffect,
|
||||
DuotoneEffect,
|
||||
FillOverlayEffect,
|
||||
GrayscaleEffect,
|
||||
HSLEffect,
|
||||
LuminanceEffect,
|
||||
TintEffect,
|
||||
)
|
||||
|
||||
"""
|
||||
Fill elements from drawing main schema
|
||||
"""
|
||||
|
||||
class PatternFillProperties(Serialisable):
|
||||
|
||||
tagname = "pattFill"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
prst = NoneSet(values=(['pct5', 'pct10', 'pct20', 'pct25', 'pct30',
|
||||
'pct40', 'pct50', 'pct60', 'pct70', 'pct75', 'pct80', 'pct90', 'horz',
|
||||
'vert', 'ltHorz', 'ltVert', 'dkHorz', 'dkVert', 'narHorz', 'narVert',
|
||||
'dashHorz', 'dashVert', 'cross', 'dnDiag', 'upDiag', 'ltDnDiag',
|
||||
'ltUpDiag', 'dkDnDiag', 'dkUpDiag', 'wdDnDiag', 'wdUpDiag', 'dashDnDiag',
|
||||
'dashUpDiag', 'diagCross', 'smCheck', 'lgCheck', 'smGrid', 'lgGrid',
|
||||
'dotGrid', 'smConfetti', 'lgConfetti', 'horzBrick', 'diagBrick',
|
||||
'solidDmnd', 'openDmnd', 'dotDmnd', 'plaid', 'sphere', 'weave', 'divot',
|
||||
'shingle', 'wave', 'trellis', 'zigZag']))
|
||||
preset = Alias("prst")
|
||||
fgClr = Typed(expected_type=ColorChoice, allow_none=True)
|
||||
foreground = Alias("fgClr")
|
||||
bgClr = Typed(expected_type=ColorChoice, allow_none=True)
|
||||
background = Alias("bgClr")
|
||||
|
||||
__elements__ = ("fgClr", "bgClr")
|
||||
|
||||
def __init__(self,
|
||||
prst=None,
|
||||
fgClr=None,
|
||||
bgClr=None,
|
||||
):
|
||||
self.prst = prst
|
||||
self.fgClr = fgClr
|
||||
self.bgClr = bgClr
|
||||
|
||||
|
||||
class RelativeRect(Serialisable):
|
||||
|
||||
tagname = "rect"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
l = Percentage(allow_none=True)
|
||||
left = Alias('l')
|
||||
t = Percentage(allow_none=True)
|
||||
top = Alias('t')
|
||||
r = Percentage(allow_none=True)
|
||||
right = Alias('r')
|
||||
b = Percentage(allow_none=True)
|
||||
bottom = Alias('b')
|
||||
|
||||
def __init__(self,
|
||||
l=None,
|
||||
t=None,
|
||||
r=None,
|
||||
b=None,
|
||||
):
|
||||
self.l = l
|
||||
self.t = t
|
||||
self.r = r
|
||||
self.b = b
|
||||
|
||||
|
||||
class StretchInfoProperties(Serialisable):
|
||||
|
||||
tagname = "stretch"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
fillRect = Typed(expected_type=RelativeRect, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
fillRect=RelativeRect(),
|
||||
):
|
||||
self.fillRect = fillRect
|
||||
|
||||
|
||||
class GradientStop(Serialisable):
|
||||
|
||||
tagname = "gs"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
pos = MinMax(min=0, max=100000, allow_none=True)
|
||||
# Color Choice Group
|
||||
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
|
||||
RGBPercent = Alias('scrgbClr')
|
||||
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
|
||||
RGB = Alias('srgbClr')
|
||||
hslClr = Typed(expected_type=HSLColor, allow_none=True)
|
||||
sysClr = Typed(expected_type=SystemColor, allow_none=True)
|
||||
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
|
||||
prstClr = NestedNoneSet(values=PRESET_COLORS)
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
pos=None,
|
||||
scrgbClr=None,
|
||||
srgbClr=None,
|
||||
hslClr=None,
|
||||
sysClr=None,
|
||||
schemeClr=None,
|
||||
prstClr=None,
|
||||
):
|
||||
if pos is None:
|
||||
pos = 0
|
||||
self.pos = pos
|
||||
|
||||
self.scrgbClr = scrgbClr
|
||||
self.srgbClr = srgbClr
|
||||
self.hslClr = hslClr
|
||||
self.sysClr = sysClr
|
||||
self.schemeClr = schemeClr
|
||||
self.prstClr = prstClr
|
||||
|
||||
|
||||
class LinearShadeProperties(Serialisable):
|
||||
|
||||
tagname = "lin"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
ang = Integer()
|
||||
scaled = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
ang=None,
|
||||
scaled=None,
|
||||
):
|
||||
self.ang = ang
|
||||
self.scaled = scaled
|
||||
|
||||
|
||||
class PathShadeProperties(Serialisable):
|
||||
|
||||
tagname = "path"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
path = Set(values=(['shape', 'circle', 'rect']))
|
||||
fillToRect = Typed(expected_type=RelativeRect, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
path=None,
|
||||
fillToRect=None,
|
||||
):
|
||||
self.path = path
|
||||
self.fillToRect = fillToRect
|
||||
|
||||
|
||||
class GradientFillProperties(Serialisable):
|
||||
|
||||
tagname = "gradFill"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
flip = NoneSet(values=(['x', 'y', 'xy']))
|
||||
rotWithShape = Bool(allow_none=True)
|
||||
|
||||
gsLst = NestedSequence(expected_type=GradientStop, count=False)
|
||||
stop_list = Alias("gsLst")
|
||||
|
||||
lin = Typed(expected_type=LinearShadeProperties, allow_none=True)
|
||||
linear = Alias("lin")
|
||||
path = Typed(expected_type=PathShadeProperties, allow_none=True)
|
||||
|
||||
tileRect = Typed(expected_type=RelativeRect, allow_none=True)
|
||||
|
||||
__elements__ = ('gsLst', 'lin', 'path', 'tileRect')
|
||||
|
||||
def __init__(self,
|
||||
flip=None,
|
||||
rotWithShape=None,
|
||||
gsLst=(),
|
||||
lin=None,
|
||||
path=None,
|
||||
tileRect=None,
|
||||
):
|
||||
self.flip = flip
|
||||
self.rotWithShape = rotWithShape
|
||||
self.gsLst = gsLst
|
||||
self.lin = lin
|
||||
self.path = path
|
||||
self.tileRect = tileRect
|
||||
|
||||
|
||||
class SolidColorFillProperties(Serialisable):
|
||||
|
||||
tagname = "solidFill"
|
||||
|
||||
# uses element group EG_ColorChoice
|
||||
scrgbClr = Typed(expected_type=RGBPercent, allow_none=True)
|
||||
RGBPercent = Alias('scrgbClr')
|
||||
srgbClr = NestedValue(expected_type=str, allow_none=True) # needs pattern and can have transform
|
||||
RGB = Alias('srgbClr')
|
||||
hslClr = Typed(expected_type=HSLColor, allow_none=True)
|
||||
sysClr = Typed(expected_type=SystemColor, allow_none=True)
|
||||
schemeClr = Typed(expected_type=SchemeColor, allow_none=True)
|
||||
prstClr = NestedNoneSet(values=PRESET_COLORS)
|
||||
|
||||
__elements__ = ('scrgbClr', 'srgbClr', 'hslClr', 'sysClr', 'schemeClr', 'prstClr')
|
||||
|
||||
def __init__(self,
|
||||
scrgbClr=None,
|
||||
srgbClr=None,
|
||||
hslClr=None,
|
||||
sysClr=None,
|
||||
schemeClr=None,
|
||||
prstClr=None,
|
||||
):
|
||||
self.scrgbClr = scrgbClr
|
||||
self.srgbClr = srgbClr
|
||||
self.hslClr = hslClr
|
||||
self.sysClr = sysClr
|
||||
self.schemeClr = schemeClr
|
||||
self.prstClr = prstClr
|
||||
|
||||
|
||||
class Blip(Serialisable):
|
||||
|
||||
tagname = "blip"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
# Using attribute groupAG_Blob
|
||||
cstate = NoneSet(values=(['email', 'screen', 'print', 'hqprint']))
|
||||
embed = Relation() # rId
|
||||
link = Relation() # hyperlink
|
||||
noGrp = Bool(allow_none=True)
|
||||
noSelect = Bool(allow_none=True)
|
||||
noRot = Bool(allow_none=True)
|
||||
noChangeAspect = Bool(allow_none=True)
|
||||
noMove = Bool(allow_none=True)
|
||||
noResize = Bool(allow_none=True)
|
||||
noEditPoints = Bool(allow_none=True)
|
||||
noAdjustHandles = Bool(allow_none=True)
|
||||
noChangeArrowheads = Bool(allow_none=True)
|
||||
noChangeShapeType = Bool(allow_none=True)
|
||||
# some elements are choice
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
alphaBiLevel = Typed(expected_type=AlphaBiLevelEffect, allow_none=True)
|
||||
alphaCeiling = Typed(expected_type=AlphaCeilingEffect, allow_none=True)
|
||||
alphaFloor = Typed(expected_type=AlphaFloorEffect, allow_none=True)
|
||||
alphaInv = Typed(expected_type=AlphaInverseEffect, allow_none=True)
|
||||
alphaMod = Typed(expected_type=AlphaModulateEffect, allow_none=True)
|
||||
alphaModFix = Typed(expected_type=AlphaModulateFixedEffect, allow_none=True)
|
||||
alphaRepl = Typed(expected_type=AlphaReplaceEffect, allow_none=True)
|
||||
biLevel = Typed(expected_type=BiLevelEffect, allow_none=True)
|
||||
blur = Typed(expected_type=BlurEffect, allow_none=True)
|
||||
clrChange = Typed(expected_type=ColorChangeEffect, allow_none=True)
|
||||
clrRepl = Typed(expected_type=ColorReplaceEffect, allow_none=True)
|
||||
duotone = Typed(expected_type=DuotoneEffect, allow_none=True)
|
||||
fillOverlay = Typed(expected_type=FillOverlayEffect, allow_none=True)
|
||||
grayscl = Typed(expected_type=GrayscaleEffect, allow_none=True)
|
||||
hsl = Typed(expected_type=HSLEffect, allow_none=True)
|
||||
lum = Typed(expected_type=LuminanceEffect, allow_none=True)
|
||||
tint = Typed(expected_type=TintEffect, allow_none=True)
|
||||
|
||||
__elements__ = ('alphaBiLevel', 'alphaCeiling', 'alphaFloor', 'alphaInv',
|
||||
'alphaMod', 'alphaModFix', 'alphaRepl', 'biLevel', 'blur', 'clrChange',
|
||||
'clrRepl', 'duotone', 'fillOverlay', 'grayscl', 'hsl', 'lum', 'tint')
|
||||
|
||||
def __init__(self,
|
||||
cstate=None,
|
||||
embed=None,
|
||||
link=None,
|
||||
noGrp=None,
|
||||
noSelect=None,
|
||||
noRot=None,
|
||||
noChangeAspect=None,
|
||||
noMove=None,
|
||||
noResize=None,
|
||||
noEditPoints=None,
|
||||
noAdjustHandles=None,
|
||||
noChangeArrowheads=None,
|
||||
noChangeShapeType=None,
|
||||
extLst=None,
|
||||
alphaBiLevel=None,
|
||||
alphaCeiling=None,
|
||||
alphaFloor=None,
|
||||
alphaInv=None,
|
||||
alphaMod=None,
|
||||
alphaModFix=None,
|
||||
alphaRepl=None,
|
||||
biLevel=None,
|
||||
blur=None,
|
||||
clrChange=None,
|
||||
clrRepl=None,
|
||||
duotone=None,
|
||||
fillOverlay=None,
|
||||
grayscl=None,
|
||||
hsl=None,
|
||||
lum=None,
|
||||
tint=None,
|
||||
):
|
||||
self.cstate = cstate
|
||||
self.embed = embed
|
||||
self.link = link
|
||||
self.noGrp = noGrp
|
||||
self.noSelect = noSelect
|
||||
self.noRot = noRot
|
||||
self.noChangeAspect = noChangeAspect
|
||||
self.noMove = noMove
|
||||
self.noResize = noResize
|
||||
self.noEditPoints = noEditPoints
|
||||
self.noAdjustHandles = noAdjustHandles
|
||||
self.noChangeArrowheads = noChangeArrowheads
|
||||
self.noChangeShapeType = noChangeShapeType
|
||||
self.extLst = extLst
|
||||
self.alphaBiLevel = alphaBiLevel
|
||||
self.alphaCeiling = alphaCeiling
|
||||
self.alphaFloor = alphaFloor
|
||||
self.alphaInv = alphaInv
|
||||
self.alphaMod = alphaMod
|
||||
self.alphaModFix = alphaModFix
|
||||
self.alphaRepl = alphaRepl
|
||||
self.biLevel = biLevel
|
||||
self.blur = blur
|
||||
self.clrChange = clrChange
|
||||
self.clrRepl = clrRepl
|
||||
self.duotone = duotone
|
||||
self.fillOverlay = fillOverlay
|
||||
self.grayscl = grayscl
|
||||
self.hsl = hsl
|
||||
self.lum = lum
|
||||
self.tint = tint
|
||||
|
||||
|
||||
class TileInfoProperties(Serialisable):
|
||||
|
||||
tx = Integer(allow_none=True)
|
||||
ty = Integer(allow_none=True)
|
||||
sx = Integer(allow_none=True)
|
||||
sy = Integer(allow_none=True)
|
||||
flip = NoneSet(values=(['x', 'y', 'xy']))
|
||||
algn = Set(values=(['tl', 't', 'tr', 'l', 'ctr', 'r', 'bl', 'b', 'br']))
|
||||
|
||||
def __init__(self,
|
||||
tx=None,
|
||||
ty=None,
|
||||
sx=None,
|
||||
sy=None,
|
||||
flip=None,
|
||||
algn=None,
|
||||
):
|
||||
self.tx = tx
|
||||
self.ty = ty
|
||||
self.sx = sx
|
||||
self.sy = sy
|
||||
self.flip = flip
|
||||
self.algn = algn
|
||||
|
||||
|
||||
class BlipFillProperties(Serialisable):
|
||||
|
||||
tagname = "blipFill"
|
||||
|
||||
dpi = Integer(allow_none=True)
|
||||
rotWithShape = Bool(allow_none=True)
|
||||
|
||||
blip = Typed(expected_type=Blip, allow_none=True)
|
||||
srcRect = Typed(expected_type=RelativeRect, allow_none=True)
|
||||
tile = Typed(expected_type=TileInfoProperties, allow_none=True)
|
||||
stretch = Typed(expected_type=StretchInfoProperties, allow_none=True)
|
||||
|
||||
__elements__ = ("blip", "srcRect", "tile", "stretch")
|
||||
|
||||
def __init__(self,
|
||||
dpi=None,
|
||||
rotWithShape=None,
|
||||
blip=None,
|
||||
tile=None,
|
||||
stretch=StretchInfoProperties(),
|
||||
srcRect=None,
|
||||
):
|
||||
self.dpi = dpi
|
||||
self.rotWithShape = rotWithShape
|
||||
self.blip = blip
|
||||
self.tile = tile
|
||||
self.stretch = stretch
|
||||
self.srcRect = srcRect
|
||||
+584
@@ -0,0 +1,584 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Float,
|
||||
Integer,
|
||||
Bool,
|
||||
MinMax,
|
||||
Set,
|
||||
NoneSet,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import Coordinate, Percentage
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
from .line import LineProperties
|
||||
|
||||
from openpyxl.styles.colors import Color
|
||||
from openpyxl.xml.constants import DRAWING_NS
|
||||
|
||||
|
||||
class Point2D(Serialisable):
|
||||
|
||||
tagname = "off"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
x = Coordinate()
|
||||
y = Coordinate()
|
||||
|
||||
def __init__(self,
|
||||
x=None,
|
||||
y=None,
|
||||
):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
|
||||
class PositiveSize2D(Serialisable):
|
||||
|
||||
tagname = "ext"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
"""
|
||||
Dimensions in EMUs
|
||||
"""
|
||||
|
||||
cx = Integer()
|
||||
width = Alias('cx')
|
||||
cy = Integer()
|
||||
height = Alias('cy')
|
||||
|
||||
def __init__(self,
|
||||
cx=None,
|
||||
cy=None,
|
||||
):
|
||||
self.cx = cx
|
||||
self.cy = cy
|
||||
|
||||
|
||||
class Transform2D(Serialisable):
|
||||
|
||||
tagname = "xfrm"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
rot = Integer(allow_none=True)
|
||||
flipH = Bool(allow_none=True)
|
||||
flipV = Bool(allow_none=True)
|
||||
off = Typed(expected_type=Point2D, allow_none=True)
|
||||
ext = Typed(expected_type=PositiveSize2D, allow_none=True)
|
||||
chOff = Typed(expected_type=Point2D, allow_none=True)
|
||||
chExt = Typed(expected_type=PositiveSize2D, allow_none=True)
|
||||
|
||||
__elements__ = ('off', 'ext', 'chOff', 'chExt')
|
||||
|
||||
def __init__(self,
|
||||
rot=None,
|
||||
flipH=None,
|
||||
flipV=None,
|
||||
off=None,
|
||||
ext=None,
|
||||
chOff=None,
|
||||
chExt=None,
|
||||
):
|
||||
self.rot = rot
|
||||
self.flipH = flipH
|
||||
self.flipV = flipV
|
||||
self.off = off
|
||||
self.ext = ext
|
||||
self.chOff = chOff
|
||||
self.chExt = chExt
|
||||
|
||||
|
||||
class GroupTransform2D(Serialisable):
|
||||
|
||||
tagname = "xfrm"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
rot = Integer(allow_none=True)
|
||||
flipH = Bool(allow_none=True)
|
||||
flipV = Bool(allow_none=True)
|
||||
off = Typed(expected_type=Point2D, allow_none=True)
|
||||
ext = Typed(expected_type=PositiveSize2D, allow_none=True)
|
||||
chOff = Typed(expected_type=Point2D, allow_none=True)
|
||||
chExt = Typed(expected_type=PositiveSize2D, allow_none=True)
|
||||
|
||||
__elements__ = ("off", "ext", "chOff", "chExt")
|
||||
|
||||
def __init__(self,
|
||||
rot=0,
|
||||
flipH=None,
|
||||
flipV=None,
|
||||
off=None,
|
||||
ext=None,
|
||||
chOff=None,
|
||||
chExt=None,
|
||||
):
|
||||
self.rot = rot
|
||||
self.flipH = flipH
|
||||
self.flipV = flipV
|
||||
self.off = off
|
||||
self.ext = ext
|
||||
self.chOff = chOff
|
||||
self.chExt = chExt
|
||||
|
||||
|
||||
class SphereCoords(Serialisable):
|
||||
|
||||
tagname = "sphereCoords" # usually
|
||||
|
||||
lat = Integer()
|
||||
lon = Integer()
|
||||
rev = Integer()
|
||||
|
||||
def __init__(self,
|
||||
lat=None,
|
||||
lon=None,
|
||||
rev=None,
|
||||
):
|
||||
self.lat = lat
|
||||
self.lon = lon
|
||||
self.rev = rev
|
||||
|
||||
|
||||
class Camera(Serialisable):
|
||||
|
||||
tagname = "camera"
|
||||
|
||||
prst = Set(values=[
|
||||
'legacyObliqueTopLeft', 'legacyObliqueTop', 'legacyObliqueTopRight', 'legacyObliqueLeft',
|
||||
'legacyObliqueFront', 'legacyObliqueRight', 'legacyObliqueBottomLeft',
|
||||
'legacyObliqueBottom', 'legacyObliqueBottomRight', 'legacyPerspectiveTopLeft',
|
||||
'legacyPerspectiveTop', 'legacyPerspectiveTopRight', 'legacyPerspectiveLeft',
|
||||
'legacyPerspectiveFront', 'legacyPerspectiveRight', 'legacyPerspectiveBottomLeft',
|
||||
'legacyPerspectiveBottom', 'legacyPerspectiveBottomRight', 'orthographicFront',
|
||||
'isometricTopUp', 'isometricTopDown', 'isometricBottomUp', 'isometricBottomDown',
|
||||
'isometricLeftUp', 'isometricLeftDown', 'isometricRightUp', 'isometricRightDown',
|
||||
'isometricOffAxis1Left', 'isometricOffAxis1Right', 'isometricOffAxis1Top',
|
||||
'isometricOffAxis2Left', 'isometricOffAxis2Right', 'isometricOffAxis2Top',
|
||||
'isometricOffAxis3Left', 'isometricOffAxis3Right', 'isometricOffAxis3Bottom',
|
||||
'isometricOffAxis4Left', 'isometricOffAxis4Right', 'isometricOffAxis4Bottom',
|
||||
'obliqueTopLeft', 'obliqueTop', 'obliqueTopRight', 'obliqueLeft', 'obliqueRight',
|
||||
'obliqueBottomLeft', 'obliqueBottom', 'obliqueBottomRight', 'perspectiveFront',
|
||||
'perspectiveLeft', 'perspectiveRight', 'perspectiveAbove', 'perspectiveBelow',
|
||||
'perspectiveAboveLeftFacing', 'perspectiveAboveRightFacing',
|
||||
'perspectiveContrastingLeftFacing', 'perspectiveContrastingRightFacing',
|
||||
'perspectiveHeroicLeftFacing', 'perspectiveHeroicRightFacing',
|
||||
'perspectiveHeroicExtremeLeftFacing', 'perspectiveHeroicExtremeRightFacing',
|
||||
'perspectiveRelaxed', 'perspectiveRelaxedModerately'])
|
||||
fov = Integer(allow_none=True)
|
||||
zoom = Typed(expected_type=Percentage, allow_none=True)
|
||||
rot = Typed(expected_type=SphereCoords, allow_none=True)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
prst=None,
|
||||
fov=None,
|
||||
zoom=None,
|
||||
rot=None,
|
||||
):
|
||||
self.prst = prst
|
||||
self.fov = fov
|
||||
self.zoom = zoom
|
||||
self.rot = rot
|
||||
|
||||
|
||||
class LightRig(Serialisable):
|
||||
|
||||
tagname = "lightRig"
|
||||
|
||||
rig = Set(values=['legacyFlat1', 'legacyFlat2', 'legacyFlat3', 'legacyFlat4', 'legacyNormal1',
|
||||
'legacyNormal2', 'legacyNormal3', 'legacyNormal4', 'legacyHarsh1',
|
||||
'legacyHarsh2', 'legacyHarsh3', 'legacyHarsh4', 'threePt', 'balanced',
|
||||
'soft', 'harsh', 'flood', 'contrasting', 'morning', 'sunrise', 'sunset',
|
||||
'chilly', 'freezing', 'flat', 'twoPt', 'glow', 'brightRoom']
|
||||
)
|
||||
dir = Set(values=(['tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br']))
|
||||
rot = Typed(expected_type=SphereCoords, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
rig=None,
|
||||
dir=None,
|
||||
rot=None,
|
||||
):
|
||||
self.rig = rig
|
||||
self.dir = dir
|
||||
self.rot = rot
|
||||
|
||||
|
||||
class Vector3D(Serialisable):
|
||||
|
||||
tagname = "vector"
|
||||
|
||||
dx = Integer() # can be in or universl measure :-/
|
||||
dy = Integer()
|
||||
dz = Integer()
|
||||
|
||||
def __init__(self,
|
||||
dx=None,
|
||||
dy=None,
|
||||
dz=None,
|
||||
):
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
self.dz = dz
|
||||
|
||||
|
||||
class Point3D(Serialisable):
|
||||
|
||||
tagname = "anchor"
|
||||
|
||||
x = Integer()
|
||||
y = Integer()
|
||||
z = Integer()
|
||||
|
||||
def __init__(self,
|
||||
x=None,
|
||||
y=None,
|
||||
z=None,
|
||||
):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
|
||||
|
||||
class Backdrop(Serialisable):
|
||||
|
||||
anchor = Typed(expected_type=Point3D, )
|
||||
norm = Typed(expected_type=Vector3D, )
|
||||
up = Typed(expected_type=Vector3D, )
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
anchor=None,
|
||||
norm=None,
|
||||
up=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.anchor = anchor
|
||||
self.norm = norm
|
||||
self.up = up
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class Scene3D(Serialisable):
|
||||
|
||||
camera = Typed(expected_type=Camera, )
|
||||
lightRig = Typed(expected_type=LightRig, )
|
||||
backdrop = Typed(expected_type=Backdrop, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
camera=None,
|
||||
lightRig=None,
|
||||
backdrop=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.camera = camera
|
||||
self.lightRig = lightRig
|
||||
self.backdrop = backdrop
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class Bevel(Serialisable):
|
||||
|
||||
tagname = "bevel"
|
||||
|
||||
w = Integer()
|
||||
h = Integer()
|
||||
prst = NoneSet(values=
|
||||
['relaxedInset', 'circle', 'slope', 'cross', 'angle',
|
||||
'softRound', 'convex', 'coolSlant', 'divot', 'riblet',
|
||||
'hardEdge', 'artDeco']
|
||||
)
|
||||
|
||||
def __init__(self,
|
||||
w=None,
|
||||
h=None,
|
||||
prst=None,
|
||||
):
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.prst = prst
|
||||
|
||||
|
||||
class Shape3D(Serialisable):
|
||||
|
||||
namespace = DRAWING_NS
|
||||
|
||||
z = Typed(expected_type=Coordinate, allow_none=True)
|
||||
extrusionH = Integer(allow_none=True)
|
||||
contourW = Integer(allow_none=True)
|
||||
prstMaterial = NoneSet(values=[
|
||||
'legacyMatte','legacyPlastic', 'legacyMetal', 'legacyWireframe', 'matte', 'plastic',
|
||||
'metal', 'warmMatte', 'translucentPowder', 'powder', 'dkEdge',
|
||||
'softEdge', 'clear', 'flat', 'softmetal']
|
||||
)
|
||||
bevelT = Typed(expected_type=Bevel, allow_none=True)
|
||||
bevelB = Typed(expected_type=Bevel, allow_none=True)
|
||||
extrusionClr = Typed(expected_type=Color, allow_none=True)
|
||||
contourClr = Typed(expected_type=Color, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
z=None,
|
||||
extrusionH=None,
|
||||
contourW=None,
|
||||
prstMaterial=None,
|
||||
bevelT=None,
|
||||
bevelB=None,
|
||||
extrusionClr=None,
|
||||
contourClr=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.z = z
|
||||
self.extrusionH = extrusionH
|
||||
self.contourW = contourW
|
||||
self.prstMaterial = prstMaterial
|
||||
self.bevelT = bevelT
|
||||
self.bevelB = bevelB
|
||||
self.extrusionClr = extrusionClr
|
||||
self.contourClr = contourClr
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class Path2D(Serialisable):
|
||||
|
||||
w = Float()
|
||||
h = Float()
|
||||
fill = NoneSet(values=(['norm', 'lighten', 'lightenLess', 'darken', 'darkenLess']))
|
||||
stroke = Bool(allow_none=True)
|
||||
extrusionOk = Bool(allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
w=None,
|
||||
h=None,
|
||||
fill=None,
|
||||
stroke=None,
|
||||
extrusionOk=None,
|
||||
):
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.fill = fill
|
||||
self.stroke = stroke
|
||||
self.extrusionOk = extrusionOk
|
||||
|
||||
|
||||
class Path2DList(Serialisable):
|
||||
|
||||
path = Typed(expected_type=Path2D, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
path=None,
|
||||
):
|
||||
self.path = path
|
||||
|
||||
|
||||
class GeomRect(Serialisable):
|
||||
|
||||
l = Coordinate()
|
||||
t = Coordinate()
|
||||
r = Coordinate()
|
||||
b = Coordinate()
|
||||
|
||||
def __init__(self,
|
||||
l=None,
|
||||
t=None,
|
||||
r=None,
|
||||
b=None,
|
||||
):
|
||||
self.l = l
|
||||
self.t = t
|
||||
self.r = r
|
||||
self.b = b
|
||||
|
||||
|
||||
class AdjPoint2D(Serialisable):
|
||||
|
||||
x = Coordinate()
|
||||
y = Coordinate()
|
||||
|
||||
def __init__(self,
|
||||
x=None,
|
||||
y=None,
|
||||
):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
|
||||
class ConnectionSite(Serialisable):
|
||||
|
||||
ang = MinMax(min=0, max=360) # guess work, can also be a name
|
||||
pos = Typed(expected_type=AdjPoint2D, )
|
||||
|
||||
def __init__(self,
|
||||
ang=None,
|
||||
pos=None,
|
||||
):
|
||||
self.ang = ang
|
||||
self.pos = pos
|
||||
|
||||
|
||||
class ConnectionSiteList(Serialisable):
|
||||
|
||||
cxn = Typed(expected_type=ConnectionSite, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
cxn=None,
|
||||
):
|
||||
self.cxn = cxn
|
||||
|
||||
|
||||
class AdjustHandleList(Serialisable):
|
||||
|
||||
pass
|
||||
|
||||
class GeomGuide(Serialisable):
|
||||
|
||||
name = String()
|
||||
fmla = String()
|
||||
|
||||
def __init__(self,
|
||||
name=None,
|
||||
fmla=None,
|
||||
):
|
||||
self.name = name
|
||||
self.fmla = fmla
|
||||
|
||||
|
||||
class GeomGuideList(Serialisable):
|
||||
|
||||
gd = Typed(expected_type=GeomGuide, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
gd=None,
|
||||
):
|
||||
self.gd = gd
|
||||
|
||||
|
||||
class CustomGeometry2D(Serialisable):
|
||||
|
||||
avLst = Typed(expected_type=GeomGuideList, allow_none=True)
|
||||
gdLst = Typed(expected_type=GeomGuideList, allow_none=True)
|
||||
ahLst = Typed(expected_type=AdjustHandleList, allow_none=True)
|
||||
cxnLst = Typed(expected_type=ConnectionSiteList, allow_none=True)
|
||||
#rect = Typed(expected_type=GeomRect, allow_none=True)
|
||||
pathLst = Typed(expected_type=Path2DList, )
|
||||
|
||||
def __init__(self,
|
||||
avLst=None,
|
||||
gdLst=None,
|
||||
ahLst=None,
|
||||
cxnLst=None,
|
||||
rect=None,
|
||||
pathLst=None,
|
||||
):
|
||||
self.avLst = avLst
|
||||
self.gdLst = gdLst
|
||||
self.ahLst = ahLst
|
||||
self.cxnLst = cxnLst
|
||||
self.rect = None
|
||||
self.pathLst = pathLst
|
||||
|
||||
|
||||
class PresetGeometry2D(Serialisable):
|
||||
|
||||
namespace = DRAWING_NS
|
||||
|
||||
prst = Set(values=(
|
||||
['line', 'lineInv', 'triangle', 'rtTriangle', 'rect',
|
||||
'diamond', 'parallelogram', 'trapezoid', 'nonIsoscelesTrapezoid',
|
||||
'pentagon', 'hexagon', 'heptagon', 'octagon', 'decagon', 'dodecagon',
|
||||
'star4', 'star5', 'star6', 'star7', 'star8', 'star10', 'star12',
|
||||
'star16', 'star24', 'star32', 'roundRect', 'round1Rect',
|
||||
'round2SameRect', 'round2DiagRect', 'snipRoundRect', 'snip1Rect',
|
||||
'snip2SameRect', 'snip2DiagRect', 'plaque', 'ellipse', 'teardrop',
|
||||
'homePlate', 'chevron', 'pieWedge', 'pie', 'blockArc', 'donut',
|
||||
'noSmoking', 'rightArrow', 'leftArrow', 'upArrow', 'downArrow',
|
||||
'stripedRightArrow', 'notchedRightArrow', 'bentUpArrow',
|
||||
'leftRightArrow', 'upDownArrow', 'leftUpArrow', 'leftRightUpArrow',
|
||||
'quadArrow', 'leftArrowCallout', 'rightArrowCallout', 'upArrowCallout',
|
||||
'downArrowCallout', 'leftRightArrowCallout', 'upDownArrowCallout',
|
||||
'quadArrowCallout', 'bentArrow', 'uturnArrow', 'circularArrow',
|
||||
'leftCircularArrow', 'leftRightCircularArrow', 'curvedRightArrow',
|
||||
'curvedLeftArrow', 'curvedUpArrow', 'curvedDownArrow', 'swooshArrow',
|
||||
'cube', 'can', 'lightningBolt', 'heart', 'sun', 'moon', 'smileyFace',
|
||||
'irregularSeal1', 'irregularSeal2', 'foldedCorner', 'bevel', 'frame',
|
||||
'halfFrame', 'corner', 'diagStripe', 'chord', 'arc', 'leftBracket',
|
||||
'rightBracket', 'leftBrace', 'rightBrace', 'bracketPair', 'bracePair',
|
||||
'straightConnector1', 'bentConnector2', 'bentConnector3',
|
||||
'bentConnector4', 'bentConnector5', 'curvedConnector2',
|
||||
'curvedConnector3', 'curvedConnector4', 'curvedConnector5', 'callout1',
|
||||
'callout2', 'callout3', 'accentCallout1', 'accentCallout2',
|
||||
'accentCallout3', 'borderCallout1', 'borderCallout2', 'borderCallout3',
|
||||
'accentBorderCallout1', 'accentBorderCallout2', 'accentBorderCallout3',
|
||||
'wedgeRectCallout', 'wedgeRoundRectCallout', 'wedgeEllipseCallout',
|
||||
'cloudCallout', 'cloud', 'ribbon', 'ribbon2', 'ellipseRibbon',
|
||||
'ellipseRibbon2', 'leftRightRibbon', 'verticalScroll',
|
||||
'horizontalScroll', 'wave', 'doubleWave', 'plus', 'flowChartProcess',
|
||||
'flowChartDecision', 'flowChartInputOutput',
|
||||
'flowChartPredefinedProcess', 'flowChartInternalStorage',
|
||||
'flowChartDocument', 'flowChartMultidocument', 'flowChartTerminator',
|
||||
'flowChartPreparation', 'flowChartManualInput',
|
||||
'flowChartManualOperation', 'flowChartConnector', 'flowChartPunchedCard',
|
||||
'flowChartPunchedTape', 'flowChartSummingJunction', 'flowChartOr',
|
||||
'flowChartCollate', 'flowChartSort', 'flowChartExtract',
|
||||
'flowChartMerge', 'flowChartOfflineStorage', 'flowChartOnlineStorage',
|
||||
'flowChartMagneticTape', 'flowChartMagneticDisk',
|
||||
'flowChartMagneticDrum', 'flowChartDisplay', 'flowChartDelay',
|
||||
'flowChartAlternateProcess', 'flowChartOffpageConnector',
|
||||
'actionButtonBlank', 'actionButtonHome', 'actionButtonHelp',
|
||||
'actionButtonInformation', 'actionButtonForwardNext',
|
||||
'actionButtonBackPrevious', 'actionButtonEnd', 'actionButtonBeginning',
|
||||
'actionButtonReturn', 'actionButtonDocument', 'actionButtonSound',
|
||||
'actionButtonMovie', 'gear6', 'gear9', 'funnel', 'mathPlus', 'mathMinus',
|
||||
'mathMultiply', 'mathDivide', 'mathEqual', 'mathNotEqual', 'cornerTabs',
|
||||
'squareTabs', 'plaqueTabs', 'chartX', 'chartStar', 'chartPlus']))
|
||||
avLst = Typed(expected_type=GeomGuideList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
prst=None,
|
||||
avLst=None,
|
||||
):
|
||||
self.prst = prst
|
||||
self.avLst = avLst
|
||||
|
||||
|
||||
class FontReference(Serialisable):
|
||||
|
||||
idx = NoneSet(values=(['major', 'minor']))
|
||||
|
||||
def __init__(self,
|
||||
idx=None,
|
||||
):
|
||||
self.idx = idx
|
||||
|
||||
|
||||
class StyleMatrixReference(Serialisable):
|
||||
|
||||
idx = Integer()
|
||||
|
||||
def __init__(self,
|
||||
idx=None,
|
||||
):
|
||||
self.idx = idx
|
||||
|
||||
|
||||
class ShapeStyle(Serialisable):
|
||||
|
||||
lnRef = Typed(expected_type=StyleMatrixReference, )
|
||||
fillRef = Typed(expected_type=StyleMatrixReference, )
|
||||
effectRef = Typed(expected_type=StyleMatrixReference, )
|
||||
fontRef = Typed(expected_type=FontReference, )
|
||||
|
||||
def __init__(self,
|
||||
lnRef=None,
|
||||
fillRef=None,
|
||||
effectRef=None,
|
||||
fontRef=None,
|
||||
):
|
||||
self.lnRef = lnRef
|
||||
self.fillRef = fillRef
|
||||
self.effectRef = effectRef
|
||||
self.fontRef = fontRef
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.xml.constants import CHART_NS, DRAWING_NS
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
|
||||
from .effect import (
|
||||
EffectList,
|
||||
EffectContainer,
|
||||
)
|
||||
from .fill import (
|
||||
Blip,
|
||||
GradientFillProperties,
|
||||
BlipFillProperties,
|
||||
)
|
||||
from .picture import PictureFrame
|
||||
from .properties import (
|
||||
NonVisualDrawingProps,
|
||||
NonVisualGroupShape,
|
||||
GroupShapeProperties,
|
||||
)
|
||||
from .relation import ChartRelation
|
||||
from .xdr import XDRTransform2D
|
||||
|
||||
|
||||
class GraphicFrameLocking(Serialisable):
|
||||
|
||||
noGrp = Bool(allow_none=True)
|
||||
noDrilldown = Bool(allow_none=True)
|
||||
noSelect = Bool(allow_none=True)
|
||||
noChangeAspect = Bool(allow_none=True)
|
||||
noMove = Bool(allow_none=True)
|
||||
noResize = Bool(allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
noGrp=None,
|
||||
noDrilldown=None,
|
||||
noSelect=None,
|
||||
noChangeAspect=None,
|
||||
noMove=None,
|
||||
noResize=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.noGrp = noGrp
|
||||
self.noDrilldown = noDrilldown
|
||||
self.noSelect = noSelect
|
||||
self.noChangeAspect = noChangeAspect
|
||||
self.noMove = noMove
|
||||
self.noResize = noResize
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class NonVisualGraphicFrameProperties(Serialisable):
|
||||
|
||||
tagname = "cNvGraphicFramePr"
|
||||
|
||||
graphicFrameLocks = Typed(expected_type=GraphicFrameLocking, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
graphicFrameLocks=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.graphicFrameLocks = graphicFrameLocks
|
||||
self.extLst = extLst
|
||||
|
||||
|
||||
class NonVisualGraphicFrame(Serialisable):
|
||||
|
||||
tagname = "nvGraphicFramePr"
|
||||
|
||||
cNvPr = Typed(expected_type=NonVisualDrawingProps)
|
||||
cNvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrameProperties)
|
||||
|
||||
__elements__ = ('cNvPr', 'cNvGraphicFramePr')
|
||||
|
||||
def __init__(self,
|
||||
cNvPr=None,
|
||||
cNvGraphicFramePr=None,
|
||||
):
|
||||
if cNvPr is None:
|
||||
cNvPr = NonVisualDrawingProps(id=0, name="Chart 0")
|
||||
self.cNvPr = cNvPr
|
||||
if cNvGraphicFramePr is None:
|
||||
cNvGraphicFramePr = NonVisualGraphicFrameProperties()
|
||||
self.cNvGraphicFramePr = cNvGraphicFramePr
|
||||
|
||||
|
||||
class GraphicData(Serialisable):
|
||||
|
||||
tagname = "graphicData"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
uri = String()
|
||||
chart = Typed(expected_type=ChartRelation, allow_none=True)
|
||||
|
||||
|
||||
def __init__(self,
|
||||
uri=CHART_NS,
|
||||
chart=None,
|
||||
):
|
||||
self.uri = uri
|
||||
self.chart = chart
|
||||
|
||||
|
||||
class GraphicObject(Serialisable):
|
||||
|
||||
tagname = "graphic"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
graphicData = Typed(expected_type=GraphicData)
|
||||
|
||||
def __init__(self,
|
||||
graphicData=None,
|
||||
):
|
||||
if graphicData is None:
|
||||
graphicData = GraphicData()
|
||||
self.graphicData = graphicData
|
||||
|
||||
|
||||
class GraphicFrame(Serialisable):
|
||||
|
||||
tagname = "graphicFrame"
|
||||
|
||||
nvGraphicFramePr = Typed(expected_type=NonVisualGraphicFrame)
|
||||
xfrm = Typed(expected_type=XDRTransform2D)
|
||||
graphic = Typed(expected_type=GraphicObject)
|
||||
macro = String(allow_none=True)
|
||||
fPublished = Bool(allow_none=True)
|
||||
|
||||
__elements__ = ('nvGraphicFramePr', 'xfrm', 'graphic', 'macro', 'fPublished')
|
||||
|
||||
def __init__(self,
|
||||
nvGraphicFramePr=None,
|
||||
xfrm=None,
|
||||
graphic=None,
|
||||
macro=None,
|
||||
fPublished=None,
|
||||
):
|
||||
if nvGraphicFramePr is None:
|
||||
nvGraphicFramePr = NonVisualGraphicFrame()
|
||||
self.nvGraphicFramePr = nvGraphicFramePr
|
||||
if xfrm is None:
|
||||
xfrm = XDRTransform2D()
|
||||
self.xfrm = xfrm
|
||||
if graphic is None:
|
||||
graphic = GraphicObject()
|
||||
self.graphic = graphic
|
||||
self.macro = macro
|
||||
self.fPublished = fPublished
|
||||
|
||||
|
||||
class GroupShape(Serialisable):
|
||||
|
||||
nvGrpSpPr = Typed(expected_type=NonVisualGroupShape)
|
||||
nonVisualProperties = Alias("nvGrpSpPr")
|
||||
grpSpPr = Typed(expected_type=GroupShapeProperties)
|
||||
visualProperties = Alias("grpSpPr")
|
||||
pic = Typed(expected_type=PictureFrame, allow_none=True)
|
||||
|
||||
__elements__ = ["nvGrpSpPr", "grpSpPr", "pic"]
|
||||
|
||||
def __init__(self,
|
||||
nvGrpSpPr=None,
|
||||
grpSpPr=None,
|
||||
pic=None,
|
||||
):
|
||||
self.nvGrpSpPr = nvGrpSpPr
|
||||
self.grpSpPr = grpSpPr
|
||||
self.pic = pic
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
from PIL import Image as PILImage
|
||||
except ImportError:
|
||||
PILImage = False
|
||||
|
||||
|
||||
def _import_image(img):
|
||||
if not PILImage:
|
||||
raise ImportError('You must install Pillow to fetch image objects')
|
||||
|
||||
if not isinstance(img, PILImage.Image):
|
||||
img = PILImage.open(img)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
class Image:
|
||||
"""Image in a spreadsheet"""
|
||||
|
||||
_id = 1
|
||||
_path = "/xl/media/image{0}.{1}"
|
||||
anchor = "A1"
|
||||
|
||||
def __init__(self, img):
|
||||
|
||||
self.ref = img
|
||||
mark_to_close = isinstance(img, str)
|
||||
image = _import_image(img)
|
||||
self.width, self.height = image.size
|
||||
|
||||
try:
|
||||
self.format = image.format.lower()
|
||||
except AttributeError:
|
||||
self.format = "png"
|
||||
if mark_to_close:
|
||||
# PIL instances created for metadata should be closed.
|
||||
image.close()
|
||||
|
||||
|
||||
def _data(self):
|
||||
"""
|
||||
Return image data, convert to supported types if necessary
|
||||
"""
|
||||
img = _import_image(self.ref)
|
||||
# don't convert these file formats
|
||||
if self.format in ['gif', 'jpeg', 'png']:
|
||||
img.fp.seek(0)
|
||||
fp = img.fp
|
||||
else:
|
||||
fp = BytesIO()
|
||||
img.save(fp, format="png")
|
||||
fp.seek(0)
|
||||
|
||||
data = fp.read()
|
||||
fp.close()
|
||||
return data
|
||||
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path.format(self._id, self.format)
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Integer,
|
||||
MinMax,
|
||||
NoneSet,
|
||||
Alias,
|
||||
Sequence
|
||||
)
|
||||
|
||||
from openpyxl.descriptors.nested import (
|
||||
NestedInteger,
|
||||
NestedNoneSet,
|
||||
EmptyTag,
|
||||
)
|
||||
from openpyxl.xml.constants import DRAWING_NS
|
||||
|
||||
from .colors import ColorChoiceDescriptor
|
||||
from .fill import GradientFillProperties, PatternFillProperties
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
|
||||
"""
|
||||
Line elements from drawing main schema
|
||||
"""
|
||||
|
||||
|
||||
class LineEndProperties(Serialisable):
|
||||
|
||||
tagname = "end"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
type = NoneSet(values=(['none', 'triangle', 'stealth', 'diamond', 'oval', 'arrow']))
|
||||
w = NoneSet(values=(['sm', 'med', 'lg']))
|
||||
len = NoneSet(values=(['sm', 'med', 'lg']))
|
||||
|
||||
def __init__(self,
|
||||
type=None,
|
||||
w=None,
|
||||
len=None,
|
||||
):
|
||||
self.type = type
|
||||
self.w = w
|
||||
self.len = len
|
||||
|
||||
|
||||
class DashStop(Serialisable):
|
||||
|
||||
tagname = "ds"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
d = Integer()
|
||||
length = Alias('d')
|
||||
sp = Integer()
|
||||
space = Alias('sp')
|
||||
|
||||
def __init__(self,
|
||||
d=0,
|
||||
sp=0,
|
||||
):
|
||||
self.d = d
|
||||
self.sp = sp
|
||||
|
||||
|
||||
class DashStopList(Serialisable):
|
||||
|
||||
ds = Sequence(expected_type=DashStop, allow_none=True)
|
||||
|
||||
def __init__(self,
|
||||
ds=None,
|
||||
):
|
||||
self.ds = ds
|
||||
|
||||
|
||||
class LineProperties(Serialisable):
|
||||
|
||||
tagname = "ln"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
w = MinMax(min=0, max=20116800, allow_none=True) # EMU
|
||||
width = Alias('w')
|
||||
cap = NoneSet(values=(['rnd', 'sq', 'flat']))
|
||||
cmpd = NoneSet(values=(['sng', 'dbl', 'thickThin', 'thinThick', 'tri']))
|
||||
algn = NoneSet(values=(['ctr', 'in']))
|
||||
|
||||
noFill = EmptyTag()
|
||||
solidFill = ColorChoiceDescriptor()
|
||||
gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
|
||||
pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
|
||||
|
||||
prstDash = NestedNoneSet(values=(['solid', 'dot', 'dash', 'lgDash', 'dashDot',
|
||||
'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot', 'sysDashDot',
|
||||
'sysDashDotDot']), namespace=namespace)
|
||||
dashStyle = Alias('prstDash')
|
||||
|
||||
custDash = Typed(expected_type=DashStop, allow_none=True)
|
||||
|
||||
round = EmptyTag()
|
||||
bevel = EmptyTag()
|
||||
miter = NestedInteger(allow_none=True, attribute="lim")
|
||||
|
||||
headEnd = Typed(expected_type=LineEndProperties, allow_none=True)
|
||||
tailEnd = Typed(expected_type=LineEndProperties, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ('noFill', 'solidFill', 'gradFill', 'pattFill',
|
||||
'prstDash', 'custDash', 'round', 'bevel', 'miter', 'headEnd', 'tailEnd')
|
||||
|
||||
def __init__(self,
|
||||
w=None,
|
||||
cap=None,
|
||||
cmpd=None,
|
||||
algn=None,
|
||||
noFill=None,
|
||||
solidFill=None,
|
||||
gradFill=None,
|
||||
pattFill=None,
|
||||
prstDash=None,
|
||||
custDash=None,
|
||||
round=None,
|
||||
bevel=None,
|
||||
miter=None,
|
||||
headEnd=None,
|
||||
tailEnd=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.w = w
|
||||
self.cap = cap
|
||||
self.cmpd = cmpd
|
||||
self.algn = algn
|
||||
self.noFill = noFill
|
||||
self.solidFill = solidFill
|
||||
self.gradFill = gradFill
|
||||
self.pattFill = pattFill
|
||||
if prstDash is None:
|
||||
prstDash = "solid"
|
||||
self.prstDash = prstDash
|
||||
self.custDash = custDash
|
||||
self.round = round
|
||||
self.bevel = bevel
|
||||
self.miter = miter
|
||||
self.headEnd = headEnd
|
||||
self.tailEnd = tailEnd
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# Copyright (c) 2010-2024 openpyxl
|
||||
|
||||
from openpyxl.xml.constants import DRAWING_NS
|
||||
|
||||
from openpyxl.descriptors.serialisable import Serialisable
|
||||
from openpyxl.descriptors import (
|
||||
Typed,
|
||||
Bool,
|
||||
String,
|
||||
Alias,
|
||||
)
|
||||
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
|
||||
|
||||
from openpyxl.chart.shapes import GraphicalProperties
|
||||
|
||||
from .fill import BlipFillProperties
|
||||
from .properties import NonVisualDrawingProps
|
||||
from .geometry import ShapeStyle
|
||||
|
||||
|
||||
class PictureLocking(Serialisable):
|
||||
|
||||
tagname = "picLocks"
|
||||
namespace = DRAWING_NS
|
||||
|
||||
# Using attribute group AG_Locking
|
||||
noCrop = Bool(allow_none=True)
|
||||
noGrp = Bool(allow_none=True)
|
||||
noSelect = Bool(allow_none=True)
|
||||
noRot = Bool(allow_none=True)
|
||||
noChangeAspect = Bool(allow_none=True)
|
||||
noMove = Bool(allow_none=True)
|
||||
noResize = Bool(allow_none=True)
|
||||
noEditPoints = Bool(allow_none=True)
|
||||
noAdjustHandles = Bool(allow_none=True)
|
||||
noChangeArrowheads = Bool(allow_none=True)
|
||||
noChangeShapeType = Bool(allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ()
|
||||
|
||||
def __init__(self,
|
||||
noCrop=None,
|
||||
noGrp=None,
|
||||
noSelect=None,
|
||||
noRot=None,
|
||||
noChangeAspect=None,
|
||||
noMove=None,
|
||||
noResize=None,
|
||||
noEditPoints=None,
|
||||
noAdjustHandles=None,
|
||||
noChangeArrowheads=None,
|
||||
noChangeShapeType=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.noCrop = noCrop
|
||||
self.noGrp = noGrp
|
||||
self.noSelect = noSelect
|
||||
self.noRot = noRot
|
||||
self.noChangeAspect = noChangeAspect
|
||||
self.noMove = noMove
|
||||
self.noResize = noResize
|
||||
self.noEditPoints = noEditPoints
|
||||
self.noAdjustHandles = noAdjustHandles
|
||||
self.noChangeArrowheads = noChangeArrowheads
|
||||
self.noChangeShapeType = noChangeShapeType
|
||||
|
||||
|
||||
class NonVisualPictureProperties(Serialisable):
|
||||
|
||||
tagname = "cNvPicPr"
|
||||
|
||||
preferRelativeResize = Bool(allow_none=True)
|
||||
picLocks = Typed(expected_type=PictureLocking, allow_none=True)
|
||||
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
|
||||
|
||||
__elements__ = ("picLocks",)
|
||||
|
||||
def __init__(self,
|
||||
preferRelativeResize=None,
|
||||
picLocks=None,
|
||||
extLst=None,
|
||||
):
|
||||
self.preferRelativeResize = preferRelativeResize
|
||||
self.picLocks = picLocks
|
||||
|
||||
|
||||
class PictureNonVisual(Serialisable):
|
||||
|
||||
tagname = "nvPicPr"
|
||||
|
||||
cNvPr = Typed(expected_type=NonVisualDrawingProps, )
|
||||
cNvPicPr = Typed(expected_type=NonVisualPictureProperties, )
|
||||
|
||||
__elements__ = ("cNvPr", "cNvPicPr")
|
||||
|
||||
def __init__(self,
|
||||
cNvPr=None,
|
||||
cNvPicPr=None,
|
||||
):
|
||||
if cNvPr is None:
|
||||
cNvPr = NonVisualDrawingProps(id=0, name="Image 1", descr="Name of file")
|
||||
self.cNvPr = cNvPr
|
||||
if cNvPicPr is None:
|
||||
cNvPicPr = NonVisualPictureProperties()
|
||||
self.cNvPicPr = cNvPicPr
|
||||
|
||||
|
||||
|
||||
|
||||
class PictureFrame(Serialisable):
|
||||
|
||||
tagname = "pic"
|
||||
|
||||
macro = String(allow_none=True)
|
||||
fPublished = Bool(allow_none=True)
|
||||
nvPicPr = Typed(expected_type=PictureNonVisual, )
|
||||
blipFill = Typed(expected_type=BlipFillProperties, )
|
||||
spPr = Typed(expected_type=GraphicalProperties, )
|
||||
graphicalProperties = Alias('spPr')
|
||||
style = Typed(expected_type=ShapeStyle, allow_none=True)
|
||||
|
||||
__elements__ = ("nvPicPr", "blipFill", "spPr", "style")
|
||||
|
||||
def __init__(self,
|
||||
macro=None,
|
||||
fPublished=None,
|
||||
nvPicPr=None,
|
||||
blipFill=None,
|
||||
spPr=None,
|
||||
style=None,
|
||||
):
|
||||
self.macro = macro
|
||||
self.fPublished = fPublished
|
||||
if nvPicPr is None:
|
||||
nvPicPr = PictureNonVisual()
|
||||
self.nvPicPr = nvPicPr
|
||||
if blipFill is None:
|
||||
blipFill = BlipFillProperties()
|
||||
self.blipFill = blipFill
|
||||
if spPr is None:
|
||||
spPr = GraphicalProperties()
|
||||
self.spPr = spPr
|
||||
self.style = style
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user