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:
Igor K.
2026-10-28 06:38:47 -04:00
co-authored by Claude Opus 5.5
commit a6a3d5d6cd
255 changed files with 32576 additions and 0 deletions
@@ -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.
@@ -0,0 +1,5 @@
The authors in alphabetical order
* Charlie Clark
* Daniel Hillier
* Elias Rabel
@@ -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.
@@ -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.
@@ -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.
@@ -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,,
@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (72.2.0)
Root-Is-Purelib: true
Tag: py3-none-any
@@ -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'
@@ -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
@@ -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()
@@ -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.
@@ -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
@@ -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,,
@@ -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
@@ -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__
@@ -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"
@@ -0,0 +1,4 @@
# Copyright (c) 2010-2024 openpyxl
from .cell import Cell, WriteOnlyCell, MergedCell
from .read_only import ReadOnlyCell
@@ -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
@@ -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)
@@ -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()
@@ -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
@@ -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)
@@ -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__()
@@ -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
@@ -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)
@@ -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()
@@ -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)
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -0,0 +1,3 @@
# Copyright (c) 2010-2024 openpyxl
from .chartsheet import Chartsheet
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
# Copyright (c) 2010-2024 openpyxl
from .comments import Comment
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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, ), {})
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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)
@@ -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)
@@ -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))
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -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)
@@ -0,0 +1,4 @@
# Copyright (c) 2010-2024 openpyxl
from .drawing import Drawing
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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
@@ -0,0 +1,174 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.xml.constants import DRAWING_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
Integer,
Set,
String,
Alias,
NoneSet,
)
from openpyxl.descriptors.excel import ExtensionList as OfficeArtExtensionList
from .geometry import GroupTransform2D, Scene3D
from .text import Hyperlink
class GroupShapeProperties(Serialisable):
tagname = "grpSpPr"
bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']))
xfrm = Typed(expected_type=GroupTransform2D, allow_none=True)
scene3d = Typed(expected_type=Scene3D, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
def __init__(self,
bwMode=None,
xfrm=None,
scene3d=None,
extLst=None,
):
self.bwMode = bwMode
self.xfrm = xfrm
self.scene3d = scene3d
self.extLst = extLst
class GroupLocking(Serialisable):
tagname = "grpSpLocks"
namespace = DRAWING_NS
noGrp = Bool(allow_none=True)
noUngrp = 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)
noChangeArrowheads = 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,
noGrp=None,
noUngrp=None,
noSelect=None,
noRot=None,
noChangeAspect=None,
noChangeArrowheads=None,
noMove=None,
noResize=None,
noEditPoints=None,
noAdjustHandles=None,
noChangeShapeType=None,
extLst=None,
):
self.noGrp = noGrp
self.noUngrp = noUngrp
self.noSelect = noSelect
self.noRot = noRot
self.noChangeAspect = noChangeAspect
self.noChangeArrowheads = noChangeArrowheads
self.noMove = noMove
self.noResize = noResize
self.noEditPoints = noEditPoints
self.noAdjustHandles = noAdjustHandles
self.noChangeShapeType = noChangeShapeType
class NonVisualGroupDrawingShapeProps(Serialisable):
tagname = "cNvGrpSpPr"
grpSpLocks = Typed(expected_type=GroupLocking, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("grpSpLocks",)
def __init__(self,
grpSpLocks=None,
extLst=None,
):
self.grpSpLocks = grpSpLocks
class NonVisualDrawingShapeProps(Serialisable):
tagname = "cNvSpPr"
spLocks = Typed(expected_type=GroupLocking, allow_none=True)
txBax = Bool(allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ("spLocks", "txBax")
def __init__(self,
spLocks=None,
txBox=None,
extLst=None,
):
self.spLocks = spLocks
self.txBox = txBox
class NonVisualDrawingProps(Serialisable):
tagname = "cNvPr"
id = Integer()
name = String()
descr = String(allow_none=True)
hidden = Bool(allow_none=True)
title = String(allow_none=True)
hlinkClick = Typed(expected_type=Hyperlink, allow_none=True)
hlinkHover = Typed(expected_type=Hyperlink, allow_none=True)
extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
__elements__ = ["hlinkClick", "hlinkHover"]
def __init__(self,
id=None,
name=None,
descr=None,
hidden=None,
title=None,
hlinkClick=None,
hlinkHover=None,
extLst=None,
):
self.id = id
self.name = name
self.descr = descr
self.hidden = hidden
self.title = title
self.hlinkClick = hlinkClick
self.hlinkHover = hlinkHover
self.extLst = extLst
class NonVisualGroupShape(Serialisable):
tagname = "nvGrpSpPr"
cNvPr = Typed(expected_type=NonVisualDrawingProps)
cNvGrpSpPr = Typed(expected_type=NonVisualGroupDrawingShapeProps)
__elements__ = ("cNvPr", "cNvGrpSpPr")
def __init__(self,
cNvPr=None,
cNvGrpSpPr=None,
):
self.cNvPr = cNvPr
self.cNvGrpSpPr = cNvGrpSpPr
@@ -0,0 +1,17 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.xml.constants import CHART_NS
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors.excel import Relation
class ChartRelation(Serialisable):
tagname = "chart"
namespace = CHART_NS
id = Relation()
def __init__(self, id):
self.id = id
@@ -0,0 +1,382 @@
# Copyright (c) 2010-2024 openpyxl
from openpyxl.descriptors.serialisable import Serialisable
from openpyxl.descriptors import (
Typed,
Bool,
NoneSet,
Integer,
Sequence,
Alias,
)
from openpyxl.descriptors.nested import (
NestedText,
NestedNoneSet,
)
from openpyxl.descriptors.excel import Relation
from openpyxl.packaging.relationship import (
Relationship,
RelationshipList,
)
from openpyxl.utils import coordinate_to_tuple
from openpyxl.utils.units import (
cm_to_EMU,
pixels_to_EMU,
)
from openpyxl.drawing.image import Image
from openpyxl.xml.constants import SHEET_DRAWING_NS
from openpyxl.chart._chart import ChartBase
from .xdr import (
XDRPoint2D,
XDRPositiveSize2D,
)
from .fill import Blip
from .connector import Shape
from .graphic import (
GroupShape,
GraphicFrame,
)
from .geometry import PresetGeometry2D
from .picture import PictureFrame
from .relation import ChartRelation
class AnchorClientData(Serialisable):
fLocksWithSheet = Bool(allow_none=True)
fPrintsWithSheet = Bool(allow_none=True)
def __init__(self,
fLocksWithSheet=None,
fPrintsWithSheet=None,
):
self.fLocksWithSheet = fLocksWithSheet
self.fPrintsWithSheet = fPrintsWithSheet
class AnchorMarker(Serialisable):
tagname = "marker"
col = NestedText(expected_type=int)
colOff = NestedText(expected_type=int)
row = NestedText(expected_type=int)
rowOff = NestedText(expected_type=int)
def __init__(self,
col=0,
colOff=0,
row=0,
rowOff=0,
):
self.col = col
self.colOff = colOff
self.row = row
self.rowOff = rowOff
class _AnchorBase(Serialisable):
#one of
sp = Typed(expected_type=Shape, allow_none=True)
shape = Alias("sp")
grpSp = Typed(expected_type=GroupShape, allow_none=True)
groupShape = Alias("grpSp")
graphicFrame = Typed(expected_type=GraphicFrame, allow_none=True)
cxnSp = Typed(expected_type=Shape, allow_none=True)
connectionShape = Alias("cxnSp")
pic = Typed(expected_type=PictureFrame, allow_none=True)
contentPart = Relation()
clientData = Typed(expected_type=AnchorClientData)
__elements__ = ('sp', 'grpSp', 'graphicFrame',
'cxnSp', 'pic', 'contentPart', 'clientData')
def __init__(self,
clientData=None,
sp=None,
grpSp=None,
graphicFrame=None,
cxnSp=None,
pic=None,
contentPart=None
):
if clientData is None:
clientData = AnchorClientData()
self.clientData = clientData
self.sp = sp
self.grpSp = grpSp
self.graphicFrame = graphicFrame
self.cxnSp = cxnSp
self.pic = pic
self.contentPart = contentPart
class AbsoluteAnchor(_AnchorBase):
tagname = "absoluteAnchor"
pos = Typed(expected_type=XDRPoint2D)
ext = Typed(expected_type=XDRPositiveSize2D)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('pos', 'ext') + _AnchorBase.__elements__
def __init__(self,
pos=None,
ext=None,
**kw
):
if pos is None:
pos = XDRPoint2D(0, 0)
self.pos = pos
if ext is None:
ext = XDRPositiveSize2D(0, 0)
self.ext = ext
super().__init__(**kw)
class OneCellAnchor(_AnchorBase):
tagname = "oneCellAnchor"
_from = Typed(expected_type=AnchorMarker)
ext = Typed(expected_type=XDRPositiveSize2D)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('_from', 'ext') + _AnchorBase.__elements__
def __init__(self,
_from=None,
ext=None,
**kw
):
if _from is None:
_from = AnchorMarker()
self._from = _from
if ext is None:
ext = XDRPositiveSize2D(0, 0)
self.ext = ext
super().__init__(**kw)
class TwoCellAnchor(_AnchorBase):
tagname = "twoCellAnchor"
editAs = NoneSet(values=(['twoCell', 'oneCell', 'absolute']))
_from = Typed(expected_type=AnchorMarker)
to = Typed(expected_type=AnchorMarker)
sp = _AnchorBase.sp
grpSp = _AnchorBase.grpSp
graphicFrame = _AnchorBase.graphicFrame
cxnSp = _AnchorBase.cxnSp
pic = _AnchorBase.pic
contentPart = _AnchorBase.contentPart
clientData = _AnchorBase.clientData
__elements__ = ('_from', 'to') + _AnchorBase.__elements__
def __init__(self,
editAs=None,
_from=None,
to=None,
**kw
):
self.editAs = editAs
if _from is None:
_from = AnchorMarker()
self._from = _from
if to is None:
to = AnchorMarker()
self.to = to
super().__init__(**kw)
def _check_anchor(obj):
"""
Check whether an object has an existing Anchor object
If not create a OneCellAnchor using the provided coordinate
"""
anchor = obj.anchor
if not isinstance(anchor, _AnchorBase):
row, col = coordinate_to_tuple(anchor.upper())
anchor = OneCellAnchor()
anchor._from.row = row -1
anchor._from.col = col -1
if isinstance(obj, ChartBase):
anchor.ext.width = cm_to_EMU(obj.width)
anchor.ext.height = cm_to_EMU(obj.height)
elif isinstance(obj, Image):
anchor.ext.width = pixels_to_EMU(obj.width)
anchor.ext.height = pixels_to_EMU(obj.height)
return anchor
class SpreadsheetDrawing(Serialisable):
tagname = "wsDr"
mime_type = "application/vnd.openxmlformats-officedocument.drawing+xml"
_rel_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
_path = PartName="/xl/drawings/drawing{0}.xml"
_id = None
twoCellAnchor = Sequence(expected_type=TwoCellAnchor, allow_none=True)
oneCellAnchor = Sequence(expected_type=OneCellAnchor, allow_none=True)
absoluteAnchor = Sequence(expected_type=AbsoluteAnchor, allow_none=True)
__elements__ = ("twoCellAnchor", "oneCellAnchor", "absoluteAnchor")
def __init__(self,
twoCellAnchor=(),
oneCellAnchor=(),
absoluteAnchor=(),
):
self.twoCellAnchor = twoCellAnchor
self.oneCellAnchor = oneCellAnchor
self.absoluteAnchor = absoluteAnchor
self.charts = []
self.images = []
self._rels = []
def __hash__(self):
"""
Just need to check for identity
"""
return id(self)
def __bool__(self):
return bool(self.charts) or bool(self.images)
def _write(self):
"""
create required structure and the serialise
"""
anchors = []
for idx, obj in enumerate(self.charts + self.images, 1):
anchor = _check_anchor(obj)
if isinstance(obj, ChartBase):
rel = Relationship(type="chart", Target=obj.path)
anchor.graphicFrame = self._chart_frame(idx)
elif isinstance(obj, Image):
rel = Relationship(type="image", Target=obj.path)
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
if not child:
anchor.pic = self._picture_frame(idx)
else:
child.blipFill.blip.embed = "rId{0}".format(idx)
anchors.append(anchor)
self._rels.append(rel)
for a in anchors:
if isinstance(a, OneCellAnchor):
self.oneCellAnchor.append(a)
elif isinstance(a, TwoCellAnchor):
self.twoCellAnchor.append(a)
else:
self.absoluteAnchor.append(a)
tree = self.to_tree()
tree.set('xmlns', SHEET_DRAWING_NS)
return tree
def _chart_frame(self, idx):
chart_rel = ChartRelation(f"rId{idx}")
frame = GraphicFrame()
nv = frame.nvGraphicFramePr.cNvPr
nv.id = idx
nv.name = "Chart {0}".format(idx)
frame.graphic.graphicData.chart = chart_rel
return frame
def _picture_frame(self, idx):
pic = PictureFrame()
pic.nvPicPr.cNvPr.descr = "Picture"
pic.nvPicPr.cNvPr.id = idx
pic.nvPicPr.cNvPr.name = "Image {0}".format(idx)
pic.blipFill.blip = Blip()
pic.blipFill.blip.embed = "rId{0}".format(idx)
pic.blipFill.blip.cstate = "print"
pic.spPr.prstGeom = PresetGeometry2D(prst="rect")
pic.spPr.ln = None
return pic
def _write_rels(self):
rels = RelationshipList()
for r in self._rels:
rels.append(r)
return rels.to_tree()
@property
def path(self):
return self._path.format(self._id)
@property
def _chart_rels(self):
"""
Get relationship information for each chart and bind anchor to it
"""
rels = []
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
for anchor in anchors:
if anchor.graphicFrame is not None:
graphic = anchor.graphicFrame.graphic
rel = graphic.graphicData.chart
if rel is not None:
rel.anchor = anchor
rel.anchor.graphicFrame = None
rels.append(rel)
return rels
@property
def _blip_rels(self):
"""
Get relationship information for each blip and bind anchor to it
Images that are not part of the XLSX package will be ignored.
"""
rels = []
anchors = self.absoluteAnchor + self.oneCellAnchor + self.twoCellAnchor
for anchor in anchors:
child = anchor.pic or anchor.groupShape and anchor.groupShape.pic
if child and child.blipFill:
rel = child.blipFill.blip
if rel is not None and rel.embed:
rel.anchor = anchor
rels.append(rel)
return rels

Some files were not shown because too many files have changed in this diff Show More