diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 8e17bd6..36e81d3 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -22,4 +22,4 @@ jobs: run: | pip install flake8 # stop the build if there are code styling problems. The GitHub editor is 127 chars wide. - flake8 . --count --max-line-length=127 --show-source --statistics \ No newline at end of file + flake8 . --count --max-line-length=127 --show-source --statistics diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..6861186 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: requirements.txt diff --git a/CHANGES.rst b/CHANGES.rst index d416af9..b760abe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,46 @@ +0.20.2 (2025-11-13) +------------------- +- Fix fix_tables() +- Move docxcompose to optional dependency (Thanks to Waket Zheng) + +0.20.1 (2025-07-15) +------------------- +- Fix and improve get_undeclared_template_variables() method (Thanks to Pablo Esteban) + +0.20.0 (2024-12-29) +------------------- +- Add RichTextParagraph (Thanks to ST-Imrie) +- Add RTL support for bold/italic (Thanks to bm-rana) +- Update documentation + +0.19.1 (2024-12-29) +------------------- +- PR #575 : fix unicode in footnotes (Thanks to Jonathan Pyle) + +0.19.0 (2024-11-12) +------------------- +- Support rendering variables in footnotes (Thanks to Bart Broere) + +0.18.0 (2024-07-21) +------------------- +- IMPORTANT : Remove Python 2.x support +- Add hyperlink option in InlineImage (Thanks to Jean Marcos da Rosa) +- Update index.rst (Thanks to jkpet) +- Add poetry env +- Black all files + +0.17.0 (2024-05-01) +------------------- +- Add support to python-docx 1.1.1 + +0.16.8 (2024-02-23) +------------------- +- PR #527 : upgrade Jinja2 in Pipfile.lock + +0.16.7 (2023-05-08) +------------------- +- PR #493 - thanks to AdrianVorobel + 0.16.6 (2023-03-12) ------------------- - PR #482 - thanks to dreizehnutters diff --git a/Pipfile.lock b/Pipfile.lock index 48aee29..fe9b33b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -17,11 +17,55 @@ }, "default": {}, "develop": { + "babel": { + "hashes": [ + "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", + "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2" + ], + "markers": "python_version >= '3.8'", + "version": "==2.17.0" + }, + "black": { + "hashes": [ + "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", + "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd", + "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", + "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", + "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", + "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7", + "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", + "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", + "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", + "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", + "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", + "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f", + "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", + "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", + "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", + "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", + "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800", + "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", + "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", + "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", + "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", + "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e" + ], + "markers": "python_version >= '3.9'", + "version": "==24.10.0" + }, + "click": { + "hashes": [ + "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", + "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.8" + }, "docxcompose": { "hashes": [ - "sha256:f91b6e4b136937152081c07471596368324cce2bffa82dfec579f6fa20f59161" + "sha256:bcf2799a0b63c29eb77a3d799a2f28443ae0f69f8691ff3d753f706be515c3e9" ], - "version": "==1.3.5" + "version": "==1.4.0" }, "docxtpl": { "editable": true, @@ -29,178 +73,347 @@ }, "flake8": { "hashes": [ - "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", - "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d" + "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", + "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426" ], "index": "pypi", - "version": "==4.0.1" + "version": "==7.2.0" }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.6" }, "lxml": { "hashes": [ - "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", - "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", - "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", - "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", - "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", - "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", - "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", - "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", - "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", - "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", - "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", - "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", - "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", - "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", - "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", - "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", - "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", - "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", - "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", - "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", - "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", - "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", - "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", - "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", - "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", - "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", - "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", - "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", - "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", - "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", - "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", - "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", - "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", - "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", - "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", - "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", - "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", - "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", - "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", - "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", - "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", - "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", - "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", - "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", - "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", - "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", - "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", - "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", - "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", - "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", - "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", - "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", - "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", - "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", - "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", - "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", - "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", - "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", - "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", - "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", - "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", - "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", - "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", - "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", - "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", - "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", - "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", - "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", - "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", - "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.9.1" + "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", + "sha256:073eb6dcdf1f587d9b88c8c93528b57eccda40209cf9be549d469b942b41d70b", + "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49", + "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c", + "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", + "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", + "sha256:0e108352e203c7afd0eb91d782582f00a0b16a948d204d4dec8565024fafeea5", + "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", + "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", + "sha256:1320091caa89805df7dcb9e908add28166113dcd062590668514dbd510798c88", + "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", + "sha256:14479c2ad1cb08b62bb941ba8e0e05938524ee3c3114644df905d2331c76cd57", + "sha256:151d6c40bc9db11e960619d2bf2ec5829f0aaffb10b41dcf6ad2ce0f3c0b2325", + "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", + "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", + "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", + "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55", + "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", + "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", + "sha256:226046e386556a45ebc787871d6d2467b32c37ce76c2680f5c608e25823ffc84", + "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", + "sha256:24f6df5f24fc3385f622c0c9d63fe34604893bc1a5bdbb2dbf5870f85f9a404a", + "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740", + "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e", + "sha256:2b31a3a77501d86d8ade128abb01082724c0dfd9524f542f2f07d693c9f1175f", + "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", + "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", + "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6", + "sha256:32697d2ea994e0db19c1df9e40275ffe84973e4232b5c274f47e7c1ec9763cdd", + "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", + "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609", + "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", + "sha256:3e6d5557989cdc3ebb5302bbdc42b439733a841891762ded9514e74f60319ad6", + "sha256:4025bf2884ac4370a3243c5aa8d66d3cb9e15d3ddd0af2d796eccc5f0244390e", + "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", + "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", + "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776", + "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", + "sha256:47fb24cc0f052f0576ea382872b3fc7e1f7e3028e53299ea751839418ade92a6", + "sha256:48b4afaf38bf79109bb060d9016fad014a9a48fb244e11b94f74ae366a64d252", + "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", + "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92", + "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5", + "sha256:4cd915c0fb1bed47b5e6d6edd424ac25856252f09120e3e8ba5154b6b921860e", + "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", + "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", + "sha256:50441c9de951a153c698b9b99992e806b71c1f36d14b154592580ff4a9d0d877", + "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", + "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37", + "sha256:53d9469ab5460402c19553b56c3648746774ecd0681b1b27ea74d5d8a3ef5590", + "sha256:56dbdbab0551532bb26c19c914848d7251d73edb507c3079d6805fa8bba5b706", + "sha256:5a99d86351f9c15e4a901fc56404b485b1462039db59288b203f8c629260a142", + "sha256:5cca36a194a4eb4e2ed6be36923d3cffd03dcdf477515dea687185506583d4c9", + "sha256:5f11a1526ebd0dee85e7b1e39e39a0cc0d9d03fb527f56d8457f6df48a10dc0c", + "sha256:61c7bbf432f09ee44b1ccaa24896d21075e533cd01477966a5ff5a71d88b2f56", + "sha256:639978bccb04c42677db43c79bdaa23785dc7f9b83bfd87570da8207872f1ce5", + "sha256:63e7968ff83da2eb6fdda967483a7a023aa497d85ad8f05c3ad9b1f2e8c84987", + "sha256:664cdc733bc87449fe781dbb1f309090966c11cc0c0cd7b84af956a02a8a4729", + "sha256:67ed8a40665b84d161bae3181aa2763beea3747f748bca5874b4af4d75998f87", + "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", + "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7", + "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf", + "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28", + "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", + "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7", + "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", + "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", + "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872", + "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", + "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4", + "sha256:7be701c24e7f843e6788353c055d806e8bd8466b52907bafe5d13ec6a6dbaecd", + "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", + "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121", + "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7", + "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b", + "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", + "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", + "sha256:9459e6892f59ecea2e2584ee1058f5d8f629446eab52ba2305ae13a32a059530", + "sha256:9776af1aad5a4b4a1317242ee2bea51da54b2a7b7b48674be736d463c999f37d", + "sha256:97dac543661e84a284502e0cf8a67b5c711b0ad5fb661d1bd505c02f8cf716d7", + "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", + "sha256:9b4a3bd174cc9cdaa1afbc4620c049038b441d6ba07629d89a83b408e54c35cd", + "sha256:9c886b481aefdf818ad44846145f6eaf373a20d200b5ce1a5c8e1bc2d8745410", + "sha256:9ceaf423b50ecfc23ca00b7f50b64baba85fb3fb91c53e2c9d00bc86150c7e40", + "sha256:a11a96c3b3f7551c8a8109aa65e8594e551d5a84c76bf950da33d0fb6dfafab7", + "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b", + "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5", + "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", + "sha256:a8c9b7f16b63e65bbba889acb436a1034a82d34fa09752d754f88d708eca80e1", + "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997", + "sha256:ab339536aa798b1e17750733663d272038bf28069761d5be57cb4a9b0137b4f8", + "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", + "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563", + "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", + "sha256:b0989737a3ba6cf2a16efb857fb0dfa20bc5c542737fddb6d893fde48be45433", + "sha256:b108134b9667bcd71236c5a02aad5ddd073e372fb5d48ea74853e009fe38acb6", + "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4", + "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", + "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f", + "sha256:b7c86884ad23d61b025989d99bfdd92a7351de956e01c61307cb87035960bcb1", + "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", + "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", + "sha256:bda3ea44c39eb74e2488297bb39d47186ed01342f0022c8ff407c250ac3f498e", + "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063", + "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", + "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5", + "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571", + "sha256:c70e93fba207106cb16bf852e421c37bbded92acd5964390aad07cb50d60f5cf", + "sha256:ca755eebf0d9e62d6cb013f1261e510317a41bf4650f22963474a663fdfe02aa", + "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", + "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", + "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", + "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", + "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", + "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", + "sha256:dc0af80267edc68adf85f2a5d9be1cdf062f973db6790c1d065e45025fa26140", + "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250", + "sha256:de6f6bb8a7840c7bf216fb83eec4e2f79f7325eca8858167b68708b929ab2172", + "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", + "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", + "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", + "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c", + "sha256:eaf24066ad0b30917186420d51e2e3edf4b0e2ea68d8cd885b14dc8afdcf6556", + "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", + "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", + "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", + "sha256:fa0e294046de09acd6146be0ed6727d1f42ded4ce3ea1e9a19c11b6774eea27c", + "sha256:fb54f7c6bafaa808f27166569b1511fc42701a7713858dddc08afdde9746849e", + "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539" + ], + "markers": "python_version >= '3.6'", + "version": "==5.4.0" }, "markupsafe": { "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" + "markers": "python_version >= '3.9'", + "version": "==3.0.2" }, "mccabe": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "version": "==0.6.1" + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", + "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351" + ], + "markers": "python_version >= '3.9'", + "version": "==4.3.7" }, "pycodestyle": { "hashes": [ - "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", - "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f" + "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", + "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.8.0" + "markers": "python_version >= '3.9'", + "version": "==2.13.0" }, "pyflakes": { "hashes": [ - "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", - "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e" + "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", + "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.0" + "markers": "python_version >= '3.9'", + "version": "==3.3.2" }, "python-docx": { "hashes": [ - "sha256:1105d233a0956dd8dd1e710d20b159e2d72ac3c301041b95f4d4ceb3e0ebebc4" + "sha256:08c20d6058916fb19853fcf080f7f42b6270d89eac9fa5f8c15f691c0017fabe", + "sha256:0cf1f22e95b9002addca7948e16f2cd7acdfd498047f1941ca5d293db7762efd" ], - "version": "==0.8.11" + "markers": "python_version >= '3.7'", + "version": "==1.1.2" + }, + "setuptools": { + "hashes": [ + "sha256:2e308396e1d83de287ada2c2fd6e64286008fe6aca5008e0b6a8cb0e2c86eedd", + "sha256:ea0e7655c05b74819f82e76e11a85b31779fee7c4969e82f72bab0664e8317e4" + ], + "markers": "python_version >= '3.9'", + "version": "==80.1.0" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "version": "==1.17.0" + }, + "tomli": { + "hashes": [ + "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", + "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", + "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", + "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", + "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", + "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", + "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", + "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", + "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", + "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", + "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", + "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", + "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", + "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", + "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", + "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", + "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", + "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", + "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", + "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", + "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", + "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", + "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", + "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", + "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", + "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", + "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", + "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", + "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", + "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", + "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", + "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7" + ], + "markers": "python_version < '3.11'", + "version": "==2.2.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", + "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" + ], + "markers": "python_version < '3.11'", + "version": "==4.13.2" } } } diff --git a/README.rst b/README.rst index 11c1e0d..6b1807f 100644 --- a/README.rst +++ b/README.rst @@ -21,10 +21,6 @@ You save the document as a .docx file (xml format) : it will be your .docx templ Now you can use python-docx-template to generate as many word documents you want from this .docx template and context variables you will associate. -Share ------ - -If you like this project, please rate and share it here : http://rate.re/github/elapouya/python-docx-template Documentation ------------- @@ -34,8 +30,9 @@ Please, `read the doc `_ Other projects -------------- -Have a look at some of my other projects : +If you like python-docx-template, please have a look at some of my other projects : +- `django-listing `_ : A listing/table library on steroid for Djano - `python-textops3 `_ : Chainable text operations - `django-robohash-svg `_ : Create svg robots avatars diff --git a/docs/conf.py b/docs/conf.py index b97390f..9054a75 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,33 +26,33 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', + "sphinx.ext.autodoc", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'python-docx-template' -copyright = u'2015, Eric Lapouyade' +project = "python-docx-template" +copyright = "2015, Eric Lapouyade" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.9' +version = "0.20" # The full version, including alpha/beta/rc tags. -release = '0.9.x' +release = "0.20.x" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -66,7 +66,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -84,7 +84,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -97,7 +97,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -126,7 +126,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -175,7 +175,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-docx-templatedoc' +htmlhelp_basename = "python-docx-templatedoc" # -- Options for LaTeX output --------------------------------------------- @@ -193,8 +193,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'python-docx-template.tex', u'python-docx-template Documentation', - u'Eric Lapouyade', 'manual'), + ( + "index", + "python-docx-template.tex", + "python-docx-template Documentation", + "Eric Lapouyade", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -223,8 +228,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'python-docx-template', u'python-docx-template Documentation', - [u'Eric Lapouyade'], 1) + ( + "index", + "python-docx-template", + "python-docx-template Documentation", + ["Eric Lapouyade"], + 1, + ) ] # If true, show URL addresses after external links. @@ -237,9 +247,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'python-docx-template', u'python-docx-template Documentation', - u'Eric Lapouyade', 'python-docx-template', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "python-docx-template", + "python-docx-template Documentation", + "Eric Lapouyade", + "python-docx-template", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. diff --git a/docs/index.rst b/docs/index.rst index e54c757..46cf69f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -263,6 +263,14 @@ You can add an hyperlink to a text by using a Richtext with this syntax:: Put ``rt`` in your context, then use ``{{r rt}}`` in your template +RichTextParagraph +----------------- + +If you want to change paragraph properties, you can use ``RichTextParagraph()`` or ``RP()`` object. +It must be added to the template by using ``{{p }}``. +Have a look to the example here ``tests/richtextparagraph.py``. + + Inline image ------------ @@ -278,6 +286,8 @@ Please see tests/inline_image.py for an example. Sub-documents ------------- +> Need to install with the subdoc extra: `pip install "docxtpl[subdoc]"` + A template variable can contain a complex subdoc object and be built from scratch using python-docx document methods. To do so, first, get the sub-document object from your template object, then use it by treating it as a python-docx document object. See example in `tests/subdoc.py`. @@ -288,6 +298,16 @@ calling method `new_subdoc()` :: tpl = DocxTemplate('templates/merge_docx_master_tpl.docx') sd = tpl.new_subdoc('templates/merge_docx_subdoc.docx') + context = { + 'mysubdoc': sd, + } + + tpl.render(context) + tpl.save('output/merge_docx.docx') + +In the above example, the content of 'templates/merge_docx_subdoc.docx' will be inserted into the parent document in place of the declared +variable `{{p mysubdoc }}`. + See `tests/merge_docx.py` for full code. .. _Escaping: @@ -388,9 +408,9 @@ In order to get the missing variables after rendering use :: tpl=DocxTemplate('your_template.docx') tpl.render(context_dict) - set_of_variables = tpl.get_undeclared_template_variables() + set_of_variables = tpl.get_undeclared_template_variables(context=context_dict) -**IMPORTANT** : You may use the method before rendering to get a set of keys you need, e.g. to be prompted to a user or written in a file for manual processing. +**IMPORTANT** : If `context` is not passed, you will get a set with all keys you need, e.g. to be prompted to a user or written in a file for manual processing. Multiple rendering ------------------ diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 29a1c08..5559d4f 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -4,11 +4,16 @@ @author: Eric Lapouyade """ -__version__ = '0.16.6' + +__version__ = "0.20.2" # flake8: noqa from .inline_image import InlineImage from .listing import Listing -from .richtext import RichText, R -from .subdoc import Subdoc +from .richtext import RichText, R, RichTextParagraph, RP from .template import DocxTemplate + +try: + from .subdoc import Subdoc +except ImportError: + pass diff --git a/docxtpl/__main__.py b/docxtpl/__main__.py index 17b3b05..59cf049 100644 --- a/docxtpl/__main__.py +++ b/docxtpl/__main__.py @@ -4,32 +4,41 @@ from .template import DocxTemplate, TemplateError -TEMPLATE_ARG = 'template_path' -JSON_ARG = 'json_path' -OUTPUT_ARG = 'output_filename' -OVERWRITE_ARG = 'overwrite' -QUIET_ARG = 'quiet' +TEMPLATE_ARG = "template_path" +JSON_ARG = "json_path" +OUTPUT_ARG = "output_filename" +OVERWRITE_ARG = "overwrite" +QUIET_ARG = "quiet" def make_arg_parser(): parser = argparse.ArgumentParser( - usage='python -m docxtpl [-h] [-o] [-q] {} {} {}'.format(TEMPLATE_ARG, JSON_ARG, OUTPUT_ARG), - description='Make docx file from existing template docx and json data.') - parser.add_argument(TEMPLATE_ARG, - type=str, - help='The path to the template docx file.') - parser.add_argument(JSON_ARG, - type=str, - help='The path to the json file with the data.') - parser.add_argument(OUTPUT_ARG, - type=str, - help='The filename to save the generated docx.') - parser.add_argument('-' + OVERWRITE_ARG[0], '--' + OVERWRITE_ARG, - action='store_true', - help='If output file already exists, overwrites without asking for confirmation') - parser.add_argument('-' + QUIET_ARG[0], '--' + QUIET_ARG, - action='store_true', - help='Do not display unnecessary messages') + usage="python -m docxtpl [-h] [-o] [-q] {} {} {}".format( + TEMPLATE_ARG, JSON_ARG, OUTPUT_ARG + ), + description="Make docx file from existing template docx and json data.", + ) + parser.add_argument( + TEMPLATE_ARG, type=str, help="The path to the template docx file." + ) + parser.add_argument( + JSON_ARG, type=str, help="The path to the json file with the data." + ) + parser.add_argument( + OUTPUT_ARG, type=str, help="The filename to save the generated docx." + ) + parser.add_argument( + "-" + OVERWRITE_ARG[0], + "--" + OVERWRITE_ARG, + action="store_true", + help="If output file already exists, overwrites without asking for confirmation", + ) + parser.add_argument( + "-" + QUIET_ARG[0], + "--" + QUIET_ARG, + action="store_true", + help="Do not display unnecessary messages", + ) return parser @@ -43,18 +52,21 @@ def get_args(parser): if e.code == 0: raise SystemExit else: - raise RuntimeError('Correct usage is:\n{parser.usage}'.format(parser=parser)) + raise RuntimeError( + "Correct usage is:\n{parser.usage}".format(parser=parser) + ) def is_argument_valid(arg_name, arg_value, overwrite): # Basic checks for the arguments if arg_name == TEMPLATE_ARG: - return os.path.isfile(arg_value) and arg_value.endswith('.docx') + return os.path.isfile(arg_value) and arg_value.endswith(".docx") elif arg_name == JSON_ARG: - return os.path.isfile(arg_value) and arg_value.endswith('.json') + return os.path.isfile(arg_value) and arg_value.endswith(".json") elif arg_name == OUTPUT_ARG: - return arg_value.endswith('.docx') and check_exists_ask_overwrite( - arg_value, overwrite) + return arg_value.endswith(".docx") and check_exists_ask_overwrite( + arg_value, overwrite + ) elif arg_name in [OVERWRITE_ARG, QUIET_ARG]: return arg_value in [True, False] @@ -65,13 +77,18 @@ def check_exists_ask_overwrite(arg_value, overwrite): # confirmed returns True, else raises OSError. if os.path.exists(arg_value) and not overwrite: try: - msg = 'File %s already exists, would you like to overwrite the existing file? (y/n)' % arg_value - if input(msg).lower() == 'y': + msg = ( + "File %s already exists, would you like to overwrite the existing file? " + "(y/n)" % arg_value + ) + if input(msg).lower() == "y": return True else: raise OSError except OSError: - raise RuntimeError('File %s already exists, please choose a different name.' % arg_value) + raise RuntimeError( + "File %s already exists, please choose a different name." % arg_value + ) else: return True @@ -87,7 +104,8 @@ def validate_all_args(parsed_args): raise RuntimeError( 'The specified {arg_name} "{arg_value}" is not valid.'.format( arg_name=arg_name, arg_value=arg_value - )) + ) + ) def get_json_data(json_path): @@ -97,17 +115,17 @@ def get_json_data(json_path): return json_data except json.JSONDecodeError as e: print( - 'There was an error on line {e.lineno}, column {e.colno} while trying to parse file {json_path}'.format( - e=e, json_path=json_path - )) - raise RuntimeError('Failed to get json data.') + "There was an error on line {e.lineno}, column {e.colno} while trying " + "to parse file {json_path}".format(e=e, json_path=json_path) + ) + raise RuntimeError("Failed to get json data.") def make_docxtemplate(template_path): try: return DocxTemplate(template_path) except TemplateError: - raise RuntimeError('Could not create docx template.') + raise RuntimeError("Could not create docx template.") def render_docx(doc, json_data): @@ -115,7 +133,7 @@ def render_docx(doc, json_data): doc.render(json_data) return doc except TemplateError: - raise RuntimeError('An error ocurred while trying to render the docx') + raise RuntimeError("An error ocurred while trying to render the docx") def save_file(doc, parsed_args): @@ -123,10 +141,14 @@ def save_file(doc, parsed_args): output_path = parsed_args[OUTPUT_ARG] doc.save(output_path) if not parsed_args[QUIET_ARG]: - print('Document successfully generated and saved at {output_path}'.format(output_path=output_path)) + print( + "Document successfully generated and saved at {output_path}".format( + output_path=output_path + ) + ) except OSError as e: - print('{e.strerror}. Could not save file {e.filename}.'.format(e=e)) - raise RuntimeError('Failed to save file.') + print("{e.strerror}. Could not save file {e.filename}.".format(e=e)) + raise RuntimeError("Failed to save file.") def main(): @@ -142,12 +164,12 @@ def main(): doc = render_docx(doc, json_data) save_file(doc, parsed_args) except RuntimeError as e: - print('Error: '+e.__str__()) + print("Error: " + e.__str__()) return finally: if not parsed_args[QUIET_ARG]: - print('Exiting program!') + print("Exiting program!") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/docxtpl/inline_image.py b/docxtpl/inline_image.py index 43af07b..f860749 100644 --- a/docxtpl/inline_image.py +++ b/docxtpl/inline_image.py @@ -4,6 +4,8 @@ @author: Eric Lapouyade """ +from docx.oxml import OxmlElement, parse_xml +from docx.oxml.ns import qn class InlineImage(object): @@ -11,23 +13,60 @@ class InlineImage(object): This is much faster than using Subdoc class. """ + tpl = None image_descriptor = None width = None height = None + anchor = None - def __init__(self, tpl, image_descriptor, width=None, height=None): + def __init__(self, tpl, image_descriptor, width=None, height=None, anchor=None): self.tpl, self.image_descriptor = tpl, image_descriptor self.width, self.height = width, height + self.anchor = anchor + + def _add_hyperlink(self, run, url, part): + # Create a relationship for the hyperlink + r_id = part.relate_to( + url, + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", + is_external=True, + ) + + # Find the and element + docPr = run.xpath(".//wp:docPr")[0] + cNvPr = run.xpath(".//pic:cNvPr")[0] + + # Create the element + hlinkClick1 = OxmlElement("a:hlinkClick") + hlinkClick1.set(qn("r:id"), r_id) + hlinkClick2 = OxmlElement("a:hlinkClick") + hlinkClick2.set(qn("r:id"), r_id) + + # Insert the element right after the element + docPr.append(hlinkClick1) + cNvPr.append(hlinkClick2) + + return run def _insert_image(self): pic = self.tpl.current_rendering_part.new_pic_inline( self.image_descriptor, self.width, - self.height + self.height, ).xml - return '%s' \ - '' % pic + if self.anchor: + run = parse_xml(pic) + if run.xpath(".//a:blip"): + hyperlink = self._add_hyperlink( + run, self.anchor, self.tpl.current_rendering_part + ) + pic = hyperlink.xml + + return ( + "%s" + '' % pic + ) def __unicode__(self): return self._insert_image() diff --git a/docxtpl/listing.py b/docxtpl/listing.py index 4a7f85c..62cde54 100644 --- a/docxtpl/listing.py +++ b/docxtpl/listing.py @@ -4,7 +4,6 @@ @author: Eric Lapouyade """ -import six try: from html import escape except ImportError: @@ -19,10 +18,11 @@ class Listing(object): use {{ mylisting }} in your template and context={ mylisting:Listing(the_listing_with_newlines) } """ + def __init__(self, text): # If not a string : cast to string (ex: int, dict etc...) - if not isinstance(text, (six.text_type, six.binary_type)): - text = six.text_type(text) + if not isinstance(text, (str, bytes)): + text = str(text) self.xml = escape(text) def __unicode__(self): diff --git a/docxtpl/richtext.py b/docxtpl/richtext.py index 02ab0b9..f0f4738 100644 --- a/docxtpl/richtext.py +++ b/docxtpl/richtext.py @@ -4,7 +4,6 @@ @author: Eric Lapouyade """ -import six try: from html import escape except ImportError: @@ -13,88 +12,158 @@ class RichText(object): - """ class to generate Rich Text when using templates variables + """class to generate Rich Text when using templates variables This is much faster than using Subdoc class, but this only for texts INSIDE an existing paragraph. """ + def __init__(self, text=None, **text_prop): - self.xml = '' + self.xml = "" if text: self.add(text, **text_prop) - def add(self, text, - style=None, - color=None, - highlight=None, - size=None, - subscript=None, - superscript=None, - bold=False, - italic=False, - underline=False, - strike=False, - font=None, - url_id=None): + def add( + self, + text, + style=None, + color=None, + highlight=None, + size=None, + subscript=None, + superscript=None, + bold=False, + italic=False, + underline=False, + strike=False, + font=None, + url_id=None, + rtl=False, + lang=None, + ): # If a RichText is added if isinstance(text, RichText): self.xml += text.xml return + # # If nothing to add : just return + # if text is None or text == "": + # return + # If not a string : cast to string (ex: int, dict etc...) - if not isinstance(text, (six.text_type, six.binary_type)): - text = six.text_type(text) - if not isinstance(text, six.text_type): - text = text.decode('utf-8', errors='ignore') + if not isinstance(text, (str, bytes)): + text = str(text) + if not isinstance(text, str): + text = text.decode("utf-8", errors="ignore") text = escape(text) - prop = u'' + prop = "" if style: - prop += u'' % style + prop += '' % style if color: - if color[0] == '#': + if color[0] == "#": color = color[1:] - prop += u'' % color + prop += '' % color if highlight: - if highlight[0] == '#': + if highlight[0] == "#": highlight = highlight[1:] - prop += u'' % highlight + prop += '' % highlight if size: - prop += u'' % size - prop += u'' % size + prop += '' % size + prop += '' % size if subscript: - prop += u'' + prop += '' if superscript: - prop += u'' + prop += '' if bold: - prop += u'' + prop += "" + if rtl: + prop += "" if italic: - prop += u'' + prop += "" + if rtl: + prop += "" if underline: - if underline not in ['single', 'double', 'thick', 'dotted', 'dash', 'dotDash', 'dotDotDash', 'wave']: - underline = 'single' - prop += u'' % underline + if underline not in [ + "single", + "double", + "thick", + "dotted", + "dash", + "dotDash", + "dotDotDash", + "wave", + ]: + underline = "single" + prop += '' % underline if strike: - prop += u'' + prop += "" if font: - regional_font = u'' - if ':' in font: - region, font = font.split(':', 1) - regional_font = u' w:{region}="{font}"'.format(font=font, region=region) - prop += ( - u'' - .format(font=font, regional_font=regional_font) + regional_font = "" + if ":" in font: + region, font = font.split(":", 1) + regional_font = ' w:{region}="{font}"'.format(font=font, region=region) + prop += ''.format( + font=font, regional_font=regional_font ) - - xml = u'' + if rtl: + prop += '' + if lang: + prop += '' % lang + xml = "" if prop: - xml += u'%s' % prop - xml += u'%s' % text + xml += "%s" % prop + xml += '%s' % text if url_id: - xml = (u'%s' - % (url_id, xml)) + xml = '%s' % ( + url_id, + xml, + ) + self.xml += xml + + def __unicode__(self): + return self.xml + + def __str__(self): + return self.xml + + def __html__(self): + return self.xml + + +class RichTextParagraph(object): + """class to generate Rich Text Paragraphs when using templates variables + + This is much faster than using Subdoc class, + but this only for texts OUTSIDE an existing paragraph. + """ + + def __init__(self, text=None, **text_prop): + self.xml = "" + if text: + self.add(text, **text_prop) + + def add( + self, + text, + parastyle=None, + ): + + # If a RichText is added + if not isinstance(text, RichText): + text = RichText(text) + + prop = "" + if parastyle: + prop += '' % parastyle + + xml = "" + if prop: + xml += "%s" % prop + xml += text.xml + xml += "" self.xml += xml def __unicode__(self): @@ -108,3 +177,4 @@ def __html__(self): R = RichText +RP = RichTextParagraph diff --git a/docxtpl/subdoc.py b/docxtpl/subdoc.py index 84a1777..e8da093 100644 --- a/docxtpl/subdoc.py +++ b/docxtpl/subdoc.py @@ -18,8 +18,8 @@ class SubdocComposer(Composer): def attach_parts(self, doc, remove_property_fields=True): - """ Attach docx parts instead of appending the whole document - thus subdoc insertion can be delegated to jinja2 """ + """Attach docx parts instead of appending the whole document + thus subdoc insertion can be delegated to jinja2""" self.reset_reference_mapping() # Remove custom property fields but keep the values @@ -51,22 +51,23 @@ def attach_parts(self, doc, remove_property_fields=True): def add_diagrams(self, doc, element): # While waiting docxcompose 1.3.3 - dgm_rels = xpath(element, './/dgm:relIds[@r:dm]') + dgm_rels = xpath(element, ".//dgm:relIds[@r:dm]") for dgm_rel in dgm_rels: for item, rt_type in ( - ('dm', RT.DIAGRAM_DATA), - ('lo', RT.DIAGRAM_LAYOUT), - ('qs', RT.DIAGRAM_QUICK_STYLE), - ('cs', RT.DIAGRAM_COLORS) + ("dm", RT.DIAGRAM_DATA), + ("lo", RT.DIAGRAM_LAYOUT), + ("qs", RT.DIAGRAM_QUICK_STYLE), + ("cs", RT.DIAGRAM_COLORS), ): - dm_rid = dgm_rel.get('{%s}%s' % (NS['r'], item)) + dm_rid = dgm_rel.get("{%s}%s" % (NS["r"], item)) dm_part = doc.part.rels[dm_rid].target_part new_rid = self.doc.part.relate_to(dm_part, rt_type) - dgm_rel.set('{%s}%s' % (NS['r'], item), new_rid) + dgm_rel.set("{%s}%s" % (NS["r"], item), new_rid) class Subdoc(object): - """ Class for subdocument to insert into master document """ + """Class for subdocument to insert into master document""" + def __init__(self, tpl, docpath=None): self.tpl = tpl self.docx = tpl.get_docx() @@ -83,8 +84,13 @@ def __getattr__(self, name): def _get_xml(self): if self.subdocx.element.body.sectPr is not None: self.subdocx.element.body.remove(self.subdocx.element.body.sectPr) - xml = re.sub(r']*>', '', etree.tostring( - self.subdocx.element.body, encoding='unicode', pretty_print=False)) + xml = re.sub( + r"]*>", + "", + etree.tostring( + self.subdocx.element.body, encoding="unicode", pretty_print=False + ), + ) return xml def __unicode__(self): diff --git a/docxtpl/template.py b/docxtpl/template.py index 943bf29..f20280a 100644 --- a/docxtpl/template.py +++ b/docxtpl/template.py @@ -4,10 +4,10 @@ @author: Eric Lapouyade """ +from __future__ import annotations from os import PathLike -from typing import Any, Optional, IO, Union, Dict, Set -from .subdoc import Subdoc +from typing import TYPE_CHECKING, Any, Optional, IO, Union, Dict, Set import functools import io from lxml import etree @@ -18,23 +18,30 @@ from docx.opc.constants import RELATIONSHIP_TYPE as REL_TYPE from jinja2 import Environment, Template, meta from jinja2.exceptions import TemplateError + try: from html import escape # noqa: F401 except ImportError: # cgi.escape is deprecated in python 3.7 from cgi import escape # noqa: F401 import re -import six import binascii import os import zipfile +if TYPE_CHECKING: + from .subdoc import Subdoc + class DocxTemplate(object): - """ Class for managing docx files as they were jinja2 templates """ + """Class for managing docx files as they were jinja2 templates""" - HEADER_URI = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" - FOOTER_URI = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + HEADER_URI = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" + ) + FOOTER_URI = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + ) def __init__(self, template_file: Union[IO[bytes], str, PathLike]) -> None: self.template_file = template_file @@ -42,6 +49,7 @@ def __init__(self, template_file: Union[IO[bytes], str, PathLike]) -> None: self.docx = None self.is_rendered = False self.is_saved = False + self.allow_missing_pics = False def init_docx(self, reload: bool = True): if not self.docx or (self.is_rendered and reload): @@ -58,10 +66,10 @@ def render_init(self): def __getattr__(self, name): return getattr(self.docx, name) - def xml_to_string(self, xml, encoding='unicode'): + def xml_to_string(self, xml, encoding="unicode"): # Be careful : pretty_print MUST be set to False, otherwise patch_xml() # won't work properly - return etree.tostring(xml, encoding='unicode', pretty_print=False) + return etree.tostring(xml, encoding="unicode", pretty_print=False) def get_docx(self): self.init_docx() @@ -71,121 +79,180 @@ def get_xml(self): return self.xml_to_string(self.docx._element.body) def write_xml(self, filename): - with open(filename, 'w') as fh: + with open(filename, "w") as fh: fh.write(self.get_xml()) def patch_xml(self, src_xml): - """ Make a lots of cleaning to have a raw xml understandable by jinja2 : + """Make a lots of cleaning to have a raw xml understandable by jinja2 : strip all unnecessary xml tags, manage table cell background color and colspan, - unescape html entities, etc... """ + unescape html entities, etc...""" # replace {{ by {{ ( works with {{ }} {% and %} {# and #}) - src_xml = re.sub(r'(?<={)(<[^>]*>)+(?=[\{%\#])|(?<=[%\}\#])(<[^>]*>)+(?=\})', '', - src_xml, flags=re.DOTALL) + src_xml = re.sub( + r"(?<={)(<[^>]*>)+(?=[\{%\#])|(?<=[%\}\#])(<[^>]*>)+(?=\})", + "", + src_xml, + flags=re.DOTALL, + ) # replace {{jinja2 stuff}} by {{jinja2 stuff}} # same thing with {% ... %} and {# #} # "jinja2 stuff" could a variable, a 'if' etc... anything jinja2 will understand def striptags(m): - return re.sub('.*?(|]*>)', '', - m.group(0), flags=re.DOTALL) - src_xml = re.sub(r'{%(?:(?!%}).)*|{#(?:(?!#}).)*|{{(?:(?!}}).)*', striptags, - src_xml, flags=re.DOTALL) + return re.sub( + ".*?(|]*>)", "", m.group(0), flags=re.DOTALL + ) + + src_xml = re.sub( + r"{%(?:(?!%}).)*|{#(?:(?!#}).)*|{{(?:(?!}}).)*", + striptags, + src_xml, + flags=re.DOTALL, + ) # manage table cell colspan def colspan(m): cell_xml = m.group(1) + m.group(3) - cell_xml = re.sub(r'](?:(?!]).)*.*?', - '', cell_xml, flags=re.DOTALL) - cell_xml = re.sub(r'', '', cell_xml, count=1) - return re.sub(r'(]*>)', r'\1' - % m.group(2), cell_xml) - src_xml = re.sub(r'(](?:(?!]).)*){%\s*colspan\s+([^%]*)\s*%}(.*?)', - colspan, src_xml, flags=re.DOTALL) + cell_xml = re.sub( + r"](?:(?!]).)*.*?", + "", + cell_xml, + flags=re.DOTALL, + ) + cell_xml = re.sub(r"", "", cell_xml, count=1) + return re.sub( + r"(]*>)", + r'\1' % m.group(2), + cell_xml, + ) + + src_xml = re.sub( + r"(](?:(?!]).)*){%\s*colspan\s+([^%]*)\s*%}(.*?)", + colspan, + src_xml, + flags=re.DOTALL, + ) # manage table cell background color def cellbg(m): cell_xml = m.group(1) + m.group(3) - cell_xml = re.sub(r'](?:(?!]).)*.*?', - '', cell_xml, flags=re.DOTALL) - cell_xml = re.sub(r'', '', cell_xml, count=1) - return re.sub(r'(]*>)', - r'\1' - % m.group(2), cell_xml) - src_xml = re.sub(r'(](?:(?!]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?)', - cellbg, src_xml, flags=re.DOTALL) + cell_xml = re.sub( + r"](?:(?!]).)*.*?", + "", + cell_xml, + flags=re.DOTALL, + ) + cell_xml = re.sub(r"", "", cell_xml, count=1) + return re.sub( + r"(]*>)", + r'\1' % m.group(2), + cell_xml, + ) + + src_xml = re.sub( + r"(](?:(?!]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?)", + cellbg, + src_xml, + flags=re.DOTALL, + ) # ensure space preservation - src_xml = re.sub(r'((?:(?!).)*)({{.*?}}|{%.*?%})', - r'\1\2', - src_xml, flags=re.DOTALL) - src_xml = re.sub(r'({{r\s.*?}}|{%r\s.*?%})', - r'\1', - src_xml, flags=re.DOTALL) + src_xml = re.sub( + r"((?:(?!).)*)({{.*?}}|{%.*?%})", + r'\1\2', + src_xml, + flags=re.DOTALL, + ) + src_xml = re.sub( + r"({{r\s.*?}}|{%r\s.*?%})", + r'\1', + src_xml, + flags=re.DOTALL, + ) # {%- will merge with previous paragraph text - src_xml = re.sub(r'(?:(?!).)*?{%-', '{%', src_xml, flags=re.DOTALL) + src_xml = re.sub(r"(?:(?!).)*?{%-", "{%", src_xml, flags=re.DOTALL) # -%} will merge with next paragraph text - src_xml = re.sub(r'-%}(?:(?!]|{%|{{).)*?]*?>', '%}', src_xml, flags=re.DOTALL) + src_xml = re.sub( + r"-%}(?:(?!]|{%|{{).)*?]*?>", "%}", src_xml, flags=re.DOTALL + ) - for y in ['tr', 'tc', 'p', 'r']: + for y in ["tr", "tc", "p", "r"]: # replace into xml code the row/paragraph/run containing # {%y xxx %} or {{y xxx}} template tag # by {% xxx %} or {{ xx }} without any surrounding tags : # This is mandatory to have jinja2 generating correct xml code - pat = r'](?:(?!]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?' % {'y': y} - src_xml = re.sub(pat, r'\1 \2', src_xml, flags=re.DOTALL) + pat = ( + r"](?:(?!]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?" + % {"y": y} + ) + src_xml = re.sub(pat, r"\1 \2", src_xml, flags=re.DOTALL) - for y in ['tr', 'tc', 'p']: + for y in ["tr", "tc", "p"]: # same thing, but for {#y xxx #} (but not where y == 'r', since that # makes less sense to use comments in that context - pat = r'](?:(?!]).)*({#)%(y)s ([^}#]*(?:#})).*?' % {'y': y} - src_xml = re.sub(pat, r'\1 \2', src_xml, flags=re.DOTALL) + pat = ( + r"](?:(?!]).)*({#)%(y)s ([^}#]*(?:#})).*?" + % {"y": y} + ) + src_xml = re.sub(pat, r"\1 \2", src_xml, flags=re.DOTALL) # add vMerge - # use {% vm %} to make this table cell and its copies be vertically merged within a {% for %} + # use {% vm %} to make this table cell and its copies + # be vertically merged within a {% for %} def v_merge_tc(m): def v_merge(m1): return ( - '' + - m1.group(1) + # Everything between ```` and ````. - "{% if loop.first %}" + - m1.group(2) + # Everything before ``{% vm %}``. - m1.group(3) + # Everything after ``{% vm %}``. - "{% endif %}" + - m1.group(4) # ````. + '' + + m1.group(1) # Everything between ```` and ````. + + "{% if loop.first %}" + + m1.group(2) # Everything before ``{% vm %}``. + + m1.group(3) # Everything after ``{% vm %}``. + + "{% endif %}" + + m1.group(4) # ````. ) + return re.sub( - r'(].*?)(.*?)(?:{%\s*vm\s*%})(.*?)()', + r"(].*?)(.*?)(?:{%\s*vm\s*%})(.*?)()", v_merge, - m.group(), # Everything between ```` and ```` with ``{% vm %}`` inside. + m.group(), + # Everything between ```` and ```` with ``{% vm %}`` inside. flags=re.DOTALL, ) - src_xml = re.sub(r'](?:(?!]).)*?{%\s*vm\s*%}.*?]', - v_merge_tc, src_xml, flags=re.DOTALL) + + src_xml = re.sub( + r"](?:(?!]).)*?{%\s*vm\s*%}.*?]", + v_merge_tc, + src_xml, + flags=re.DOTALL, + ) # Use ``{% hm %}`` to make table cell become horizontally merged within # a ``{% for %}``. def h_merge_tc(m): - xml_to_patch = m.group() # Everything between ```` and ```` with ``{% hm %}`` inside. + xml_to_patch = ( + m.group() + ) # Everything between ```` and ```` with ``{% hm %}`` inside. def with_gridspan(m1): return ( - m1.group(1) + # ``w:gridSpan w:val="``. - '{{ ' + m1.group(2) + ' * loop.length }}' + # Content of ``w:val``, multiplied by loop length. - m1.group(3) # Closing quotation mark. + m1.group(1) # ``w:gridSpan w:val="``. + + "{{ " + + m1.group(2) + + " * loop.length }}" # Content of ``w:val``, multiplied by loop length. + + m1.group(3) # Closing quotation mark. ) def without_gridspan(m2): return ( - '' + - m2.group(1) + # Everything between ```` and ````. - m2.group(2) + # Everything before ``{% hm %}``. - m2.group(3) + # Everything after ``{% hm %}``. - m2.group(4) # ````. + '' + + m2.group(1) # Everything between ```` and ````. + + m2.group(2) # Everything before ``{% hm %}``. + + m2.group(3) # Everything after ``{% hm %}``. + + m2.group(4) # ````. ) - if re.search(r'w:gridSpan', xml_to_patch): + if re.search(r"w:gridSpan", xml_to_patch): # Simple case, there's already ``gridSpan``, multiply its value. xml = re.sub( @@ -195,15 +262,15 @@ def without_gridspan(m2): flags=re.DOTALL, ) xml = re.sub( - r'{%\s*hm\s*%}', - '', + r"{%\s*hm\s*%}", + "", xml, # Patched xml. flags=re.DOTALL, ) else: # There're no ``gridSpan``, add one. xml = re.sub( - r'(].*?)(.*?)(?:{%\s*hm\s*%})(.*?)()', + r"(].*?)(.*?)(?:{%\s*hm\s*%})(.*?)()", without_gridspan, xml_to_patch, flags=re.DOTALL, @@ -212,24 +279,31 @@ def without_gridspan(m2): # Discard every other cell generated in loop. return "{% if loop.first %}" + xml + "{% endif %}" - src_xml = re.sub(r'](?:(?!]).)*?{%\s*hm\s*%}.*?]', - h_merge_tc, src_xml, flags=re.DOTALL) + src_xml = re.sub( + r"](?:(?!]).)*?{%\s*hm\s*%}.*?]", + h_merge_tc, + src_xml, + flags=re.DOTALL, + ) def clean_tags(m): - return (m.group(0) - .replace(r"‘", "'") - .replace('<', '<') - .replace('>', '>') - .replace(u'“', u'"') - .replace(u'”', u'"') - .replace(u"‘", u"'") - .replace(u"’", u"'")) - src_xml = re.sub(r'(?<=\{[\{%])(.*?)(?=[\}%]})', clean_tags, src_xml) + return ( + m.group(0) + .replace(r"‘", "'") + .replace("<", "<") + .replace(">", ">") + .replace("“", '"') + .replace("”", '"') + .replace("‘", "'") + .replace("’", "'") + ) + + src_xml = re.sub(r"(?<=\{[\{%])(.*?)(?=[\}%]})", clean_tags, src_xml) return src_xml def render_xml_part(self, src_xml, part, context, jinja_env=None): - src_xml = re.sub(r'])', r'\n])", r"\n]+>', '', x), - src_xml.splitlines()[line_number:(line_number + 7)]) + exc.docx_context = map( + lambda x: re.sub(r"<[^>]+>", "", x), + src_xml.splitlines()[line_number: (line_number + 7)], # fmt: skip + ) + raise exc - dst_xml = re.sub(r'\n])', r'])", r" None: + def render_properties( + self, context: Dict[str, Any], jinja_env: Optional[Environment] = None + ) -> None: # List of string attributes of docx.opc.coreprops.CoreProperties which are strings. # It seems that some attributes cannot be written as strings. Those are commented out. properties = [ - 'author', + "author", # 'category', - 'comments', + "comments", # 'content_status', - 'identifier', + "identifier", # 'keywords', - 'language', + "language", # 'last_modified_by', - 'subject', - 'title', + "subject", + "title", # 'version', ] if jinja_env is None: @@ -277,35 +357,76 @@ def render_properties(self, context: Dict[str, Any], jinja_env: Optional[Environ rendered = template.render(context) setattr(self.docx.core_properties, prop, rendered) + def render_footnotes( + self, context: Dict[str, Any], jinja_env: Optional[Environment] = None + ) -> None: + if jinja_env is None: + jinja_env = Environment() + + for section in self.docx.sections: + for part in section.part.package.parts: + if part.content_type == ( + "application/vnd.openxmlformats-officedocument" + ".wordprocessingml.footnotes+xml" + ): + xml = self.patch_xml( + part.blob.decode("utf-8") + if isinstance(part.blob, bytes) + else part.blob + ) + xml = self.render_xml_part(xml, part, context, jinja_env) + part._blob = xml.encode("utf-8") + def resolve_listing(self, xml): def resolve_text(run_properties, paragraph_properties, m): - xml = m.group(0).replace('\t', '' - '%s' - '%s' % (run_properties, run_properties)) - xml = xml.replace('\a', '' - '%s%s' % (paragraph_properties, run_properties)) - xml = xml.replace('\n', '') - xml = xml.replace('\f', '' - '' - '%s%s' % (paragraph_properties, run_properties)) + xml = m.group(0).replace( + "\t", + "" + "%s" + '%s' % (run_properties, run_properties), + ) + xml = xml.replace( + "\a", + "" + '%s%s' + % (paragraph_properties, run_properties), + ) + xml = xml.replace("\n", '') + xml = xml.replace( + "\f", + "" + '' + '%s%s' + % (paragraph_properties, run_properties), + ) return xml def resolve_run(paragraph_properties, m): - run_properties = re.search(r'.*?', m.group(0)) - run_properties = run_properties.group(0) if run_properties else '' - return re.sub(r']*)?>.*?', - lambda x: resolve_text(run_properties, paragraph_properties, x), m.group(0), - flags=re.DOTALL) + run_properties = re.search(r".*?", m.group(0)) + run_properties = run_properties.group(0) if run_properties else "" + return re.sub( + r"]*)?>.*?", + lambda x: resolve_text(run_properties, paragraph_properties, x), + m.group(0), + flags=re.DOTALL, + ) def resolve_paragraph(m): - paragraph_properties = re.search(r'.*?', m.group(0)) - paragraph_properties = paragraph_properties.group(0) if paragraph_properties else '' - return re.sub(r']*)?>.*?', - lambda x: resolve_run(paragraph_properties, x), - m.group(0), flags=re.DOTALL) + paragraph_properties = re.search(r".*?", m.group(0)) + paragraph_properties = ( + paragraph_properties.group(0) if paragraph_properties else "" + ) + return re.sub( + r"]*)?>.*?", + lambda x: resolve_run(paragraph_properties, x), + m.group(0), + flags=re.DOTALL, + ) - xml = re.sub(r']*)?>.*?', resolve_paragraph, xml, flags=re.DOTALL) + xml = re.sub( + r"]*)?>.*?", resolve_paragraph, xml, flags=re.DOTALL + ) return xml @@ -321,7 +442,7 @@ def map_tree(self, tree): root.replace(body, tree) def get_headers_footers(self, uri): - for relKey, val in self.docx._part._rels.items(): + for relKey, val in self.docx._part.rels.items(): if (val.reltype == uri) and (val.target_part.blob): yield relKey, val.target_part @@ -332,7 +453,7 @@ def get_headers_footers_encoding(self, xml): m = re.match(r'<\?xml[^\?]+\bencoding="([^"]+)"', xml, re.I) if m: return m.group(1) - return 'utf-8' + return "utf-8" def build_headers_footers_xml(self, context, uri, jinja_env=None): for relKey, part in self.get_headers_footers(uri): @@ -343,17 +464,17 @@ def build_headers_footers_xml(self, context, uri, jinja_env=None): yield relKey, xml.encode(encoding) def map_headers_footers_xml(self, relKey, xml): - part = self.docx._part._rels[relKey].target_part + part = self.docx._part.rels[relKey].target_part new_part = XmlPart.load(part.partname, part.content_type, xml, part.package) for rId, rel in part.rels.items(): new_part.load_rel(rel.reltype, rel._target, rel.rId, rel.is_external) - self.docx._part._rels[relKey]._target = new_part + self.docx._part.rels[relKey]._target = new_part def render( self, context: Dict[str, Any], jinja_env: Optional[Environment] = None, - autoescape: bool = False + autoescape: bool = False, ) -> None: # init template working attributes self.render_init() @@ -377,19 +498,19 @@ def render( self.map_tree(tree) # Headers - headers = self.build_headers_footers_xml(context, self.HEADER_URI, - jinja_env) + headers = self.build_headers_footers_xml(context, self.HEADER_URI, jinja_env) for relKey, xml in headers: self.map_headers_footers_xml(relKey, xml) # Footers - footers = self.build_headers_footers_xml(context, self.FOOTER_URI, - jinja_env) + footers = self.build_headers_footers_xml(context, self.FOOTER_URI, jinja_env) for relKey, xml in footers: self.map_headers_footers_xml(relKey, xml) self.render_properties(context, jinja_env) + self.render_footnotes(context, jinja_env) + # set rendered flag self.is_rendered = True @@ -399,15 +520,15 @@ def fix_tables(self, xml): parser = etree.XMLParser(recover=True) tree = etree.fromstring(xml, parser=parser) # get namespace - ns = '{' + tree.nsmap['w'] + '}' + ns = "{" + tree.nsmap["w"] + "}" # walk trough xml and find table - for t in tree.iter(ns+'tbl'): - tblGrid = t.find(ns+'tblGrid') - columns = tblGrid.findall(ns+'gridCol') + for t in tree.iter(ns + "tbl"): + tblGrid = t.find(ns + "tblGrid") + columns = tblGrid.findall(ns + "gridCol") to_add = 0 # walk trough all rows and try to find if there is higher cell count - for r in t.iter(ns+'tr'): - cells = r.findall(ns+'tc') + for r in t.iter(ns + "tr"): + cells = r.findall(ns + "tc") if (len(columns) + to_add) < len(cells): to_add = len(cells) - len(columns) # is necessary to add columns? @@ -417,39 +538,44 @@ def fix_tables(self, xml): width = 0.0 new_average = None for c in columns: - if not c.get(ns+'w') is None: - width += float(c.get(ns+'w')) + if c.get(ns + "w") is not None: + width += float(c.get(ns + "w")) # try to keep proportion of table if width > 0: old_average = width / len(columns) new_average = width / (len(columns) + to_add) # scale the old columns for c in columns: - c.set(ns+'w', str(int(float(c.get(ns+'w')) * - new_average/old_average))) + c.set( + ns + "w", + str( + int(float(c.get(ns + "w")) * new_average / old_average) + ), + ) # add new columns for i in range(to_add): - etree.SubElement(tblGrid, ns+'gridCol', - {ns+'w': str(int(new_average))}) + etree.SubElement( + tblGrid, ns + "gridCol", {ns + "w": str(int(new_average))} + ) # Refetch columns after columns addition. - columns = tblGrid.findall(ns + 'gridCol') + columns = tblGrid.findall(ns + "gridCol") columns_len = len(columns) cells_len_max = 0 def get_cell_len(total, cell): - tc_pr = cell.find(ns + 'tcPr') - grid_span = None if tc_pr is None else tc_pr.find(ns + 'gridSpan') + tc_pr = cell.find(ns + "tcPr") + grid_span = None if tc_pr is None else tc_pr.find(ns + "gridSpan") if grid_span is not None: - return total + int(grid_span.get(ns + 'val')) + return total + int(grid_span.get(ns + "val")) return total + 1 # Calculate max of table cells to compare with `gridCol`. - for r in t.iter(ns + 'tr'): - cells = r.findall(ns + 'tc') + for r in t.iter(ns + "tr"): + cells = r.findall(ns + "tc") cells_len = functools.reduce(get_cell_len, cells, 0) cells_len_max = max(cells_len_max, cells_len) @@ -463,11 +589,11 @@ def get_cell_len(total, cell): removed_width = 0.0 for c in columns[-to_remove:]: - removed_width += float(c.get(ns + 'w')) + removed_width += float(c.get(ns + "w")) tblGrid.remove(c) - columns_left = tblGrid.findall(ns + 'gridCol') + columns_left = tblGrid.findall(ns + "gridCol") # Distribute `removed_width` across all columns that has # left after extras removal. @@ -477,29 +603,31 @@ def get_cell_len(total, cell): extra_space = int(extra_space) for c in columns_left: - c.set(ns+'w', str(int(float(c.get(ns+'w')) + extra_space))) + c.set(ns + "w", str(int(float(c.get(ns + "w")) + extra_space))) return tree def fix_docpr_ids(self, tree): # some Ids may have some collisions : so renumbering all of them : - for elt in tree.xpath('//wp:docPr', namespaces=docx.oxml.ns.nsmap): + for elt in tree.xpath("//wp:docPr", namespaces=docx.oxml.ns.nsmap): self.docx_ids_index += 1 - elt.attrib['id'] = str(self.docx_ids_index) + elt.attrib["id"] = str(self.docx_ids_index) + + def new_subdoc(self, docpath=None) -> Subdoc: + from .subdoc import Subdoc - def new_subdoc(self, docpath=None): self.init_docx() return Subdoc(self, docpath) @staticmethod def get_file_crc(file_obj): - if hasattr(file_obj, 'read'): + if hasattr(file_obj, "read"): buf = file_obj.read() else: - with open(file_obj, 'rb') as fh: + with open(file_obj, "rb") as fh: buf = fh.read() - crc = (binascii.crc32(buf) & 0xFFFFFFFF) + crc = binascii.crc32(buf) & 0xFFFFFFFF return crc def replace_media(self, src_file, dst_file): @@ -522,10 +650,10 @@ def replace_media(self, src_file, dst_file): """ crc = self.get_file_crc(src_file) - if hasattr(dst_file, 'read'): + if hasattr(dst_file, "read"): self.crc_to_new_media[crc] = dst_file.read() else: - with open(dst_file, 'rb') as fh: + with open(dst_file, "rb") as fh: self.crc_to_new_media[crc] = fh.read() def replace_pic(self, embedded_file, dst_file): @@ -543,11 +671,11 @@ def replace_pic(self, embedded_file, dst_file): for replace_embedded and replace_media) """ - if hasattr(dst_file, 'read'): + if hasattr(dst_file, "read"): # NOTE: file extension not checked self.pics_to_replace[embedded_file] = dst_file.read() else: - with open(dst_file, 'rb') as fh: + with open(dst_file, "rb") as fh: self.pics_to_replace[embedded_file] = fh.read() def replace_embedded(self, src_file, dst_file): @@ -563,7 +691,7 @@ def replace_embedded(self, src_file, dst_file): Note2 : it is important to have the source file as it is required to calculate its CRC to find them in the docx """ - with open(dst_file, 'rb') as fh: + with open(dst_file, "rb") as fh: crc = self.get_file_crc(src_file) self.crc_to_new_embedded[crc] = fh.read() @@ -594,7 +722,7 @@ def replace_zipname(self, zipname, dst_file): "word/embeddings/". Note that the file is renamed by MSWord, so you have to guess a little bit... """ - with open(dst_file, 'rb') as fh: + with open(dst_file, "rb") as fh: self.zipname_to_replace[zipname] = fh.read() def reset_replacements(self): @@ -619,11 +747,9 @@ def reset_replacements(self): self.pics_to_replace = {} def post_processing(self, docx_file): - if (self.crc_to_new_media or - self.crc_to_new_embedded or - self.zipname_to_replace): + if self.crc_to_new_media or self.crc_to_new_embedded or self.zipname_to_replace: - if hasattr(docx_file, 'read'): + if hasattr(docx_file, "read"): tmp_file = io.BytesIO() DocxTemplate(docx_file).save(tmp_file) tmp_file.seek(0) @@ -632,27 +758,31 @@ def post_processing(self, docx_file): docx_file.seek(0) else: - tmp_file = '%s_docxtpl_before_replace_medias' % docx_file + tmp_file = "%s_docxtpl_before_replace_medias" % docx_file os.rename(docx_file, tmp_file) with zipfile.ZipFile(tmp_file) as zin: - with zipfile.ZipFile(docx_file, 'w') as zout: + with zipfile.ZipFile(docx_file, "w") as zout: for item in zin.infolist(): buf = zin.read(item.filename) if item.filename in self.zipname_to_replace: zout.writestr(item, self.zipname_to_replace[item.filename]) - elif (item.filename.startswith('word/media/') and - item.CRC in self.crc_to_new_media): + elif ( + item.filename.startswith("word/media/") + and item.CRC in self.crc_to_new_media + ): zout.writestr(item, self.crc_to_new_media[item.CRC]) - elif (item.filename.startswith('word/embeddings/') and - item.CRC in self.crc_to_new_embedded): + elif ( + item.filename.startswith("word/embeddings/") + and item.CRC in self.crc_to_new_embedded + ): zout.writestr(item, self.crc_to_new_embedded[item.CRC]) else: zout.writestr(item, buf) - if not hasattr(tmp_file, 'read'): + if not hasattr(tmp_file, "read"): os.remove(tmp_file) - if hasattr(docx_file, 'read'): + if hasattr(docx_file, "read"): docx_file.seek(0) def pre_processing(self): @@ -670,16 +800,17 @@ def _replace_pics(self): self._replace_docx_part_pics(part, replaced_pics) # Header/Footer - for relid, rel in six.iteritems(part.rels): + for relid, rel in part.rels.items(): if rel.reltype in (REL_TYPE.HEADER, REL_TYPE.FOOTER): self._replace_docx_part_pics(rel.target_part, replaced_pics) - # make sure all template images defined by user were replaced - for img_id, replaced in replaced_pics.items(): - if not replaced: - raise ValueError( - "Picture %s not found in the docx template" % img_id - ) + if not self.allow_missing_pics: + # make sure all template images defined by user were replaced + for img_id, replaced in replaced_pics.items(): + if not replaced: + raise ValueError( + "Picture %s not found in the docx template" % img_id + ) def get_pic_map(self): return self.pic_map @@ -690,16 +821,17 @@ def _replace_docx_part_pics(self, doc_part, replaced_pics): part_map = {} - gds = et.xpath('//a:graphic/a:graphicData', namespaces=docx.oxml.ns.nsmap) + gds = et.xpath("//a:graphic/a:graphicData", namespaces=docx.oxml.ns.nsmap) for gd in gds: rel = None # Either IMAGE, CHART, SMART_ART, ... try: - if gd.attrib['uri'] == docx.oxml.ns.nsmap['pic']: + if gd.attrib["uri"] == docx.oxml.ns.nsmap["pic"]: # Either PICTURE or LINKED_PICTURE image - blip = gd.xpath('pic:pic/pic:blipFill/a:blip', - namespaces=docx.oxml.ns.nsmap)[0] - dest = blip.xpath('@r:embed', namespaces=docx.oxml.ns.nsmap) + blip = gd.xpath( + "pic:pic/pic:blipFill/a:blip", namespaces=docx.oxml.ns.nsmap + )[0] + dest = blip.xpath("@r:embed", namespaces=docx.oxml.ns.nsmap) if len(dest) > 0: rel = dest[0] else: @@ -707,33 +839,39 @@ def _replace_docx_part_pics(self, doc_part, replaced_pics): else: continue - non_visual_properties = 'pic:pic/pic:nvPicPr/pic:cNvPr/' - filename = gd.xpath('%s@name' % non_visual_properties, - namespaces=docx.oxml.ns.nsmap)[0] - titles = gd.xpath('%s@title' % non_visual_properties, - namespaces=docx.oxml.ns.nsmap) + non_visual_properties = "pic:pic/pic:nvPicPr/pic:cNvPr/" + filename = gd.xpath( + "%s@name" % non_visual_properties, namespaces=docx.oxml.ns.nsmap + )[0] + titles = gd.xpath( + "%s@title" % non_visual_properties, namespaces=docx.oxml.ns.nsmap + ) if titles: title = titles[0] else: title = "" - descriptions = gd.xpath('%s@descr' % non_visual_properties, - namespaces=docx.oxml.ns.nsmap) + descriptions = gd.xpath( + "%s@descr" % non_visual_properties, namespaces=docx.oxml.ns.nsmap + ) if descriptions: description = descriptions[0] else: description = "" - part_map[filename] = (doc_part.rels[rel].target_ref, - doc_part.rels[rel].target_part) + part_map[filename] = ( + doc_part.rels[rel].target_ref, + doc_part.rels[rel].target_part, + ) # replace data - for img_id, img_data in six.iteritems(self.pics_to_replace): + for img_id, img_data in self.pics_to_replace.items(): if img_id == filename or img_id == title or img_id == description: part_map[filename][1]._blob = img_data replaced_pics[img_id] = True break - # FIXME: figure out what exceptions are thrown here and catch more specific exceptions + # FIXME: figure out what exceptions are thrown here + # and catch more specific exceptions except Exception: continue @@ -741,8 +879,7 @@ def _replace_docx_part_pics(self, doc_part, replaced_pics): def build_url_id(self, url): self.init_docx() - return self.docx._part.relate_to(url, REL_TYPE.HYPERLINK, - is_external=True) + return self.docx._part.relate_to(url, REL_TYPE.HYPERLINK, is_external=True) def save(self, filename: Union[IO[bytes], str, PathLike], *args, **kwargs) -> None: # case where save() is called without doing rendering @@ -754,19 +891,37 @@ def save(self, filename: Union[IO[bytes], str, PathLike], *args, **kwargs) -> No self.post_processing(filename) self.is_saved = True - def get_undeclared_template_variables(self, jinja_env: Optional[Environment] = None) -> Set[str]: - self.init_docx(reload=False) - xml = self.get_xml() + def get_undeclared_template_variables( + self, + jinja_env: Optional[Environment] = None, + context: Optional[Dict[str, Any]] = None, + ) -> Set[str]: + # Create a temporary document to analyze the template without affecting the current state + temp_doc = Document(self.template_file) + + # Get XML from the temporary document + xml = self.xml_to_string(temp_doc._element.body) xml = self.patch_xml(xml) + + # Add headers and footers for uri in [self.HEADER_URI, self.FOOTER_URI]: - for relKey, part in self.get_headers_footers(uri): - _xml = self.get_part_xml(part) - xml += self.patch_xml(_xml) + for relKey, val in temp_doc._part.rels.items(): + if (val.reltype == uri) and (val.target_part.blob): + _xml = self.xml_to_string(parse_xml(val.target_part.blob)) + xml += self.patch_xml(_xml) + if jinja_env: env = jinja_env else: env = Environment() + parse_content = env.parse(xml) - return meta.find_undeclared_variables(parse_content) + all_variables = meta.find_undeclared_variables(parse_content) + + # If context is provided, return only variables that are not in the context + if context is not None: + provided_variables = set(context.keys()) + return all_variables - provided_variables - undeclared_template_variables = property(get_undeclared_template_variables) + # If no context provided, return all variables (original behavior) + return all_variables diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..6ca6fc4 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1032 @@ +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = true +python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"subdoc\" or extra == \"docs\"" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "extra == \"docs\" and sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + +[[package]] +name = "docxcompose" +version = "1.4.0" +description = "Compose .docx documents" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"subdoc\"" +files = [ + {file = "docxcompose-1.4.0.tar.gz", hash = "sha256:bcf2799a0b63c29eb77a3d799a2f28443ae0f69f8691ff3d753f706be515c3e9"}, +] + +[package.dependencies] +babel = "*" +lxml = "*" +python-docx = ">=0.8.8" +setuptools = "*" +six = "*" + +[package.extras] +test = ["pytest"] +tests = ["pytest"] + +[[package]] +name = "flake8" +version = "7.3.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, + {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.14.0,<2.15.0" +pyflakes = ">=3.4.0,<3.5.0" + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = true +python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +description = "Read metadata from Python packages" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\" and python_version < \"3.10\"" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lxml" +version = "5.4.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c"}, + {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776"}, + {file = "lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7"}, + {file = "lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250"}, + {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9"}, + {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751"}, + {file = "lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4"}, + {file = "lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539"}, + {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4"}, + {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc"}, + {file = "lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f"}, + {file = "lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2"}, + {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0"}, + {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a"}, + {file = "lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82"}, + {file = "lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f"}, + {file = "lxml-5.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7be701c24e7f843e6788353c055d806e8bd8466b52907bafe5d13ec6a6dbaecd"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb54f7c6bafaa808f27166569b1511fc42701a7713858dddc08afdde9746849e"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97dac543661e84a284502e0cf8a67b5c711b0ad5fb661d1bd505c02f8cf716d7"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:c70e93fba207106cb16bf852e421c37bbded92acd5964390aad07cb50d60f5cf"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9c886b481aefdf818ad44846145f6eaf373a20d200b5ce1a5c8e1bc2d8745410"}, + {file = "lxml-5.4.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:fa0e294046de09acd6146be0ed6727d1f42ded4ce3ea1e9a19c11b6774eea27c"}, + {file = "lxml-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:61c7bbf432f09ee44b1ccaa24896d21075e533cd01477966a5ff5a71d88b2f56"}, + {file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"}, + {file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"}, + {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"}, + {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"}, + {file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"}, + {file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"}, + {file = "lxml-5.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eaf24066ad0b30917186420d51e2e3edf4b0e2ea68d8cd885b14dc8afdcf6556"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b31a3a77501d86d8ade128abb01082724c0dfd9524f542f2f07d693c9f1175f"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e108352e203c7afd0eb91d782582f00a0b16a948d204d4dec8565024fafeea5"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11a96c3b3f7551c8a8109aa65e8594e551d5a84c76bf950da33d0fb6dfafab7"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:ca755eebf0d9e62d6cb013f1261e510317a41bf4650f22963474a663fdfe02aa"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:4cd915c0fb1bed47b5e6d6edd424ac25856252f09120e3e8ba5154b6b921860e"}, + {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:226046e386556a45ebc787871d6d2467b32c37ce76c2680f5c608e25823ffc84"}, + {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b108134b9667bcd71236c5a02aad5ddd073e372fb5d48ea74853e009fe38acb6"}, + {file = "lxml-5.4.0-cp38-cp38-win32.whl", hash = "sha256:1320091caa89805df7dcb9e908add28166113dcd062590668514dbd510798c88"}, + {file = "lxml-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:073eb6dcdf1f587d9b88c8c93528b57eccda40209cf9be549d469b942b41d70b"}, + {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bda3ea44c39eb74e2488297bb39d47186ed01342f0022c8ff407c250ac3f498e"}, + {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ceaf423b50ecfc23ca00b7f50b64baba85fb3fb91c53e2c9d00bc86150c7e40"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:664cdc733bc87449fe781dbb1f309090966c11cc0c0cd7b84af956a02a8a4729"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67ed8a40665b84d161bae3181aa2763beea3747f748bca5874b4af4d75998f87"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b4a3bd174cc9cdaa1afbc4620c049038b441d6ba07629d89a83b408e54c35cd"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b0989737a3ba6cf2a16efb857fb0dfa20bc5c542737fddb6d893fde48be45433"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dc0af80267edc68adf85f2a5d9be1cdf062f973db6790c1d065e45025fa26140"}, + {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:639978bccb04c42677db43c79bdaa23785dc7f9b83bfd87570da8207872f1ce5"}, + {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a99d86351f9c15e4a901fc56404b485b1462039db59288b203f8c629260a142"}, + {file = "lxml-5.4.0-cp39-cp39-win32.whl", hash = "sha256:3e6d5557989cdc3ebb5302bbdc42b439733a841891762ded9514e74f60319ad6"}, + {file = "lxml-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:a8c9b7f16b63e65bbba889acb436a1034a82d34fa09752d754f88d708eca80e1"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5f11a1526ebd0dee85e7b1e39e39a0cc0d9d03fb527f56d8457f6df48a10dc0c"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4afaf38bf79109bb060d9016fad014a9a48fb244e11b94f74ae366a64d252"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6f6bb8a7840c7bf216fb83eec4e2f79f7325eca8858167b68708b929ab2172"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5cca36a194a4eb4e2ed6be36923d3cffd03dcdf477515dea687185506583d4c9"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b7c86884ad23d61b025989d99bfdd92a7351de956e01c61307cb87035960bcb1"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:53d9469ab5460402c19553b56c3648746774ecd0681b1b27ea74d5d8a3ef5590"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:56dbdbab0551532bb26c19c914848d7251d73edb507c3079d6805fa8bba5b706"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14479c2ad1cb08b62bb941ba8e0e05938524ee3c3114644df905d2331c76cd57"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32697d2ea994e0db19c1df9e40275ffe84973e4232b5c274f47e7c1ec9763cdd"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:24f6df5f24fc3385f622c0c9d63fe34604893bc1a5bdbb2dbf5870f85f9a404a"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:151d6c40bc9db11e960619d2bf2ec5829f0aaffb10b41dcf6ad2ce0f3c0b2325"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4025bf2884ac4370a3243c5aa8d66d3cb9e15d3ddd0af2d796eccc5f0244390e"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9459e6892f59ecea2e2584ee1058f5d8f629446eab52ba2305ae13a32a059530"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47fb24cc0f052f0576ea382872b3fc7e1f7e3028e53299ea751839418ade92a6"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50441c9de951a153c698b9b99992e806b71c1f36d14b154592580ff4a9d0d877"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ab339536aa798b1e17750733663d272038bf28069761d5be57cb4a9b0137b4f8"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9776af1aad5a4b4a1317242ee2bea51da54b2a7b7b48674be736d463c999f37d"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:63e7968ff83da2eb6fdda967483a7a023aa497d85ad8f05c3ad9b1f2e8c84987"}, + {file = "lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11,<3.1.0)"] + +[[package]] +name = "lxml-stubs" +version = "0.5.1" +description = "Type annotations for the lxml package" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d"}, + {file = "lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272"}, +] + +[package.extras] +test = ["coverage[toml] (>=7.2.5)", "mypy (>=1.2.0)", "pytest (>=7.3.0)", "pytest-mypy-plugins (>=1.10.1)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy" +version = "1.18.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pockets" +version = "0.9.1" +description = "A collection of helpful Python tools!" +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86"}, + {file = "pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3"}, +] + +[package.dependencies] +six = ">=1.5.2" + +[[package]] +name = "pycodestyle" +version = "2.14.0" +description = "Python style guide checker" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, + {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, + {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata ; python_version < \"3.8\""] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-docx" +version = "1.1.0" +description = "Create, read, and update Microsoft Word .docx files." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "python-docx-1.1.0.tar.gz", hash = "sha256:5829b722141cf1ab79aedf0c34d9fe9924b29764584c0f2164eb2b02dcdf17c9"}, + {file = "python_docx-1.1.0-py3-none-any.whl", hash = "sha256:bac9773278098a1ddc43a52d84e22f5909c4a3080a624530b3ecb3771b07c6cd"}, +] + +[package.dependencies] +lxml = ">=3.1.0" +typing-extensions = "*" + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = true +python-versions = "*" +groups = ["main"] +markers = "(extra == \"subdoc\" or extra == \"docs\") and python_version < \"3.9\"" +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"subdoc\"" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf", "pytest-ruff ; sys_platform != \"cygwin\"", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +markers = "extra == \"subdoc\" or extra == \"docs\"" +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +optional = true +python-versions = "!=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +optional = true +python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast ; python_version < \"3.8\""] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = true +python-versions = ">=3.6" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-napoleon" +version = "0.7" +description = "Sphinx \"napoleon\" extension." +optional = true +python-versions = "*" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8"}, + {file = "sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef"}, +] + +[package.dependencies] +pockets = ">=0.3" +six = ">=1.5.2" + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = true +python-versions = ">=3.5" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version >= \"3.9\" and python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] +markers = {dev = "python_version >= \"3.9\""} + +[[package]] +name = "urllib3" +version = "2.0.7" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\"" +files = [ + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = true +python-versions = ">=3.7" +groups = ["main"] +markers = "extra == \"docs\" and python_version < \"3.10\"" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8 ; python_version < \"3.12\"", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] + +[extras] +docs = ["Sphinx", "sphinxcontrib-napoleon"] +subdoc = ["docxcompose"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.7" +content-hash = "a6deaefbe026e97162df4940d1807ba479cbabac06ebb94c413d3d6964f76cfc" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..476fc0e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,70 @@ +[project] +name = "docxtpl" +dynamic = ["version"] +description = "Python docx template engine" +authors = [{name="Eric Lapouyade", email="elapouya@proton.me"}] +readme = "README.rst" +requires-python = ">=3.7" +license = {text="LGPL-2.1-only"} +classifiers=[ + "Intended Audience :: Developers", + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +keywords = ["jinja2"] +dependencies = [ + "python-docx", + "jinja2", + "lxml", +] + +[project.optional-dependencies] +subdoc = ["docxcompose"] +docs = ["Sphinx", "sphinxcontrib-napoleon"] + +[dependency-groups] +dev = [ + "mypy >=1.18.2; python_version >= '3.9'", + "lxml-stubs >=0.5.1; python_version >= '3.9'", + "flake8 >=7.3.0; python_version >= '3.9'" +] + +[project.urls] +homepage = "https://github.com/elapouya/python-docx-template" +repository = "https://github.com/elapouya/python-docx-template.git" +document = "https://docxtpl.readthedocs.org" + +[tool.poetry] +version = "0.0.0" + +[tool.poetry.requires-plugins] +poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] } + +[tool.poetry-dynamic-versioning] +enable = true + +[tool.poetry-dynamic-versioning.from-file] +source = "docxtpl/__init__.py" +pattern = '__version__ = "(.+)"' + +[tool.mypy] +pretty = true +python_version = "3.9" +check_untyped_defs = true +warn_unused_ignores = true +exclude = ["docs", "build", "setup.py"] + +[[tool.mypy.overrides]] +module = ["docxcompose.*"] +ignore_missing_imports = true + +[build-system] +requires = ["poetry-core", "poetry-dynamic-versioning >=1.0.0,<2.0.0"] +build-backend = "poetry_dynamic_versioning.backend" diff --git a/requirements.txt b/requirements.txt index 1588ab2..c29971d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -six python-docx docxcompose jinja2 lxml +sphinx-book-theme diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7c2b287..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[bdist_wheel] -universal = 1 \ No newline at end of file diff --git a/setup.py b/setup.py index e6762c1..b0dfe57 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,9 @@ -from setuptools import setup import os import re import sys +from setuptools import setup + # To register onto Pypi : # python setup.py sdist bdist_wheel upload @@ -10,13 +11,13 @@ def read(*names): values = dict() for name in names: - filename = name + '.rst' + filename = name + ".rst" if os.path.isfile(filename): fd = open(filename) value = fd.read() fd.close() else: - value = '' + value = "" values[name] = value return values @@ -27,13 +28,15 @@ def read(*names): News ==== %(CHANGES)s -""" % read('README', 'CHANGES') +""" % read( + "README", "CHANGES" +) def get_version(pkg): - path = os.path.join(os.path.dirname(__file__), pkg, '__init__.py') + path = os.path.join(os.path.dirname(__file__), pkg, "__init__.py") if sys.version_info >= (3, 0): - fh = open(path, encoding='utf-8') # required to read utf-8 file on windows + fh = open(path, encoding="utf-8") # required to read utf-8 file on windows else: fh = open(path) # encoding parameter does not exist in python 2 with fh: @@ -43,28 +46,32 @@ def get_version(pkg): raise RuntimeError("Unable to find __version__ string in %s." % path) -setup(name='docxtpl', - version=get_version('docxtpl'), - description='Python docx template engine', - long_description=long_description, - classifiers=[ - "Intended Audience :: Developers", - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - ], - keywords='jinja2', - url='https://github.com/elapouya/python-docx-template', - author='Eric Lapouyade', - license='LGPL 2.1', - packages=['docxtpl'], - install_requires=['six', - 'python-docx', - 'docxcompose', - 'jinja2', - 'lxml'], - extras_require={'docs': ['Sphinx', 'sphinxcontrib-napoleon']}, - eager_resources=['docs'], - zip_safe=False) +setup( + name="docxtpl", + version=get_version("docxtpl"), + description="Python docx template engine", + long_description=long_description, + long_description_content_type="text/x-rst", + classifiers=[ + "Intended Audience :: Developers", + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + ], + keywords="jinja2", + url="https://github.com/elapouya/python-docx-template", + author="Eric Lapouyade", + license="LGPL-2.1-only", + license_files=[], + packages=["docxtpl"], + install_requires=["python-docx>=1.1.1", "jinja2", "lxml"], + extras_require={"docs": ["Sphinx", "sphinxcontrib-napoleon"], "subdoc": ["docxcompose"]}, + eager_resources=["docs"], + zip_safe=False, +) diff --git a/tests/cellbg.py b/tests/cellbg.py index 79938fb..e8f4487 100644 --- a/tests/cellbg.py +++ b/tests/cellbg.py @@ -1,42 +1,42 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate, RichText -tpl = DocxTemplate('templates/cellbg_tpl.docx') +tpl = DocxTemplate("templates/cellbg_tpl.docx") context = { - 'alerts': [ + "alerts": [ { - 'date': '2015-03-10', - 'desc': RichText('Very critical alert', color='FF0000', bold=True), - 'type': 'CRITICAL', - 'bg': 'FF0000', + "date": "2015-03-10", + "desc": RichText("Very critical alert", color="FF0000", bold=True), + "type": "CRITICAL", + "bg": "FF0000", }, { - 'date': '2015-03-11', - 'desc': RichText('Just a warning'), - 'type': 'WARNING', - 'bg': 'FFDD00', + "date": "2015-03-11", + "desc": RichText("Just a warning"), + "type": "WARNING", + "bg": "FFDD00", }, { - 'date': '2015-03-12', - 'desc': RichText('Information'), - 'type': 'INFO', - 'bg': '8888FF', + "date": "2015-03-12", + "desc": RichText("Information"), + "type": "INFO", + "bg": "8888FF", }, { - 'date': '2015-03-13', - 'desc': RichText('Debug trace'), - 'type': 'DEBUG', - 'bg': 'FF00FF', + "date": "2015-03-13", + "desc": RichText("Debug trace"), + "type": "DEBUG", + "bg": "FF00FF", }, ], } tpl.render(context) -tpl.save('output/cellbg.docx') +tpl.save("output/cellbg.docx") diff --git a/tests/comments.py b/tests/comments.py index 5a214ca..b0d31e4 100644 --- a/tests/comments.py +++ b/tests/comments.py @@ -1,6 +1,6 @@ from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/comments_tpl.docx') +tpl = DocxTemplate("templates/comments_tpl.docx") tpl.render({}) -tpl.save('output/comments.docx') +tpl.save("output/comments.docx") diff --git a/tests/custom_jinja_filters.py b/tests/custom_jinja_filters.py index 580986b..5d89570 100644 --- a/tests/custom_jinja_filters.py +++ b/tests/custom_jinja_filters.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: sandeeprah, Eric Lapouyade -''' +""" from docxtpl import DocxTemplate import jinja2 @@ -14,7 +14,7 @@ # to create new filters, first create functions that accept the value to filter # as first argument, and filter parameters as next arguments def my_filterA(value, my_string_arg): - return_value = value + ' ' + my_string_arg + return_value = value + " " + my_string_arg return return_value @@ -24,12 +24,12 @@ def my_filterB(value, my_float_arg): # Then, declare them to jinja like this : -jinja_env.filters['my_filterA'] = my_filterA -jinja_env.filters['my_filterB'] = my_filterB +jinja_env.filters["my_filterA"] = my_filterA +jinja_env.filters["my_filterB"] = my_filterB -context = {'base_value_string': ' Hello', 'base_value_float': 1.5} +context = {"base_value_string": " Hello", "base_value_float": 1.5} -tpl = DocxTemplate('templates/custom_jinja_filters_tpl.docx') +tpl = DocxTemplate("templates/custom_jinja_filters_tpl.docx") tpl.render(context, jinja_env) -tpl.save('output/custom_jinja_filters.docx') +tpl.save("output/custom_jinja_filters.docx") diff --git a/tests/doc_properties.py b/tests/doc_properties.py index e60ac19..fe48bf8 100644 --- a/tests/doc_properties.py +++ b/tests/doc_properties.py @@ -1,12 +1,10 @@ from docxtpl import DocxTemplate -doctemplate = r'templates/doc_properties_tpl.docx' +doctemplate = r"templates/doc_properties_tpl.docx" tpl = DocxTemplate(doctemplate) -context = { - 'test': 'HelloWorld' -} +context = {"test": "HelloWorld"} tpl.render(context) tpl.save("output/doc_properties.docx") diff --git a/tests/dynamic_table.py b/tests/dynamic_table.py index f4446d3..2eb6b48 100644 --- a/tests/dynamic_table.py +++ b/tests/dynamic_table.py @@ -1,15 +1,15 @@ from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/dynamic_table_tpl.docx') +tpl = DocxTemplate("templates/dynamic_table_tpl.docx") context = { - 'col_labels': ['fruit', 'vegetable', 'stone', 'thing'], - 'tbl_contents': [ - {'label': 'yellow', 'cols': ['banana', 'capsicum', 'pyrite', 'taxi']}, - {'label': 'red', 'cols': ['apple', 'tomato', 'cinnabar', 'doubledecker']}, - {'label': 'green', 'cols': ['guava', 'cucumber', 'aventurine', 'card']}, + "col_labels": ["fruit", "vegetable", "stone", "thing"], + "tbl_contents": [ + {"label": "yellow", "cols": ["banana", "capsicum", "pyrite", "taxi"]}, + {"label": "red", "cols": ["apple", "tomato", "cinnabar", "doubledecker"]}, + {"label": "green", "cols": ["guava", "cucumber", "aventurine", "card"]}, ], } tpl.render(context) -tpl.save('output/dynamic_table.docx') +tpl.save("output/dynamic_table.docx") diff --git a/tests/embedded.py b/tests/embedded.py index 842798c..a9fff5f 100644 --- a/tests/embedded.py +++ b/tests/embedded.py @@ -1,45 +1,45 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2017-09-09 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate # rendering the "dynamic embedded docx": -embedded_docx_tpl = DocxTemplate('templates/embedded_embedded_docx_tpl.docx') +embedded_docx_tpl = DocxTemplate("templates/embedded_embedded_docx_tpl.docx") context = { - 'name': 'John Doe', + "name": "John Doe", } embedded_docx_tpl.render(context) -embedded_docx_tpl.save('output/embedded_embedded_docx.docx') +embedded_docx_tpl.save("output/embedded_embedded_docx.docx") # rendering the main document : -tpl = DocxTemplate('templates/embedded_main_tpl.docx') +tpl = DocxTemplate("templates/embedded_main_tpl.docx") context = { - 'name': 'John Doe', + "name": "John Doe", } tpl.replace_embedded( - 'templates/embedded_dummy.docx', 'templates/embedded_static_docx.docx' + "templates/embedded_dummy.docx", "templates/embedded_static_docx.docx" ) tpl.replace_embedded( - 'templates/embedded_dummy2.docx', 'output/embedded_embedded_docx.docx' + "templates/embedded_dummy2.docx", "output/embedded_embedded_docx.docx" ) # The zipname is the one you can find when you open docx with WinZip, 7zip (Windows) # or unzip -l (Linux). The zipname starts with "word/embeddings/". # Note that the file is renamed by MSWord, so you have to guess a little bit... tpl.replace_zipname( - 'word/embeddings/Feuille_Microsoft_Office_Excel3.xlsx', 'templates/real_Excel.xlsx' + "word/embeddings/Feuille_Microsoft_Office_Excel3.xlsx", "templates/real_Excel.xlsx" ) tpl.replace_zipname( - 'word/embeddings/Pr_sentation_Microsoft_Office_PowerPoint4.pptx', - 'templates/real_PowerPoint.pptx', + "word/embeddings/Pr_sentation_Microsoft_Office_PowerPoint4.pptx", + "templates/real_PowerPoint.pptx", ) tpl.render(context) -tpl.save('output/embedded.docx') +tpl.save("output/embedded.docx") diff --git a/tests/escape.py b/tests/escape.py index de14345..77375ab 100644 --- a/tests/escape.py +++ b/tests/escape.py @@ -1,19 +1,19 @@ from docxtpl import DocxTemplate, R, Listing -tpl = DocxTemplate('templates/escape_tpl.docx') +tpl = DocxTemplate("templates/escape_tpl.docx") context = { - 'myvar': R( + "myvar": R( '"less than" must be escaped : <, this can be done with RichText() or R()' ), - 'myescvar': 'It can be escaped with a "|e" jinja filter in the template too : < ', - 'nlnp': R('Here is a multiple\nlines\nstring\aand some\aother\aparagraphs', - color='#ff00ff'), - 'mylisting': Listing( - 'the listing\nwith\nsome\nlines\nand special chars : <>& ...' + "myescvar": 'It can be escaped with a "|e" jinja filter in the template too : < ', + "nlnp": R( + "Here is a multiple\nlines\nstring\aand some\aother\aparagraphs", + color="#ff00ff", ), - 'page_break': R('\f'), - 'new_listing': """ + "mylisting": Listing("the listing\nwith\nsome\nlines\nand special chars : <>& ..."), + "page_break": R("\f"), + "new_listing": """ This is a new listing Now, does not require Listing() Object Here is a \t tab\a @@ -21,34 +21,34 @@ Here is a page break : \f That's it """, - 'some_html': ( - 'HTTP/1.1 200 OK\n' - 'Server: Apache-Coyote/1.1\n' - 'Cache-Control: no-store\n' - 'Expires: Thu, 01 Jan 1970 00:00:00 GMT\n' - 'Pragma: no-cache\n' - 'Content-Type: text/html;charset=UTF-8\n' - 'Content-Language: zh-CN\n' - 'Date: Thu, 22 Oct 2020 10:59:40 GMT\n' - 'Content-Length: 9866\n' - '\n' - '\n' - '\n' - ' Struts Problem Report\n' - ' \n' - '\n' - '\n' - '...\n' - '\n' - '' + "some_html": ( + "HTTP/1.1 200 OK\n" + "Server: Apache-Coyote/1.1\n" + "Cache-Control: no-store\n" + "Expires: Thu, 01 Jan 1970 00:00:00 GMT\n" + "Pragma: no-cache\n" + "Content-Type: text/html;charset=UTF-8\n" + "Content-Language: zh-CN\n" + "Date: Thu, 22 Oct 2020 10:59:40 GMT\n" + "Content-Length: 9866\n" + "\n" + "\n" + "\n" + " Struts Problem Report\n" + " \n" + "\n" + "\n" + "...\n" + "\n" + "" ), } tpl.render(context) -tpl.save('output/escape.docx') +tpl.save("output/escape.docx") diff --git a/tests/escape_auto.py b/tests/escape_auto.py index e0def1d..70e8195 100644 --- a/tests/escape_auto.py +++ b/tests/escape_auto.py @@ -5,25 +5,23 @@ import os from unicodedata import name -from six import iteritems, text_type - from docxtpl import DocxTemplate XML_RESERVED = """<"&'>""" -tpl = DocxTemplate('templates/escape_tpl_auto.docx') +tpl = DocxTemplate("templates/escape_tpl_auto.docx") context = { - 'nested_dict': {name(text_type(c)): c for c in XML_RESERVED}, - 'autoescape': 'Escaped "str & ing"!', - 'autoescape_unicode': u'This is an escaped example \u4f60 & \u6211', - 'iteritems': iteritems, + "nested_dict": {name(str(c)): c for c in XML_RESERVED}, + "autoescape": 'Escaped "str & ing"!', + "autoescape_unicode": "This is an escaped example \u4f60 & \u6211", + "iteritems": lambda x: x.items(), } tpl.render(context, autoescape=True) -OUTPUT = 'output' +OUTPUT = "output" if not os.path.exists(OUTPUT): os.makedirs(OUTPUT) -tpl.save(OUTPUT + '/escape_auto.docx') +tpl.save(OUTPUT + "/escape_auto.docx") diff --git a/tests/footnotes.py b/tests/footnotes.py new file mode 100644 index 0000000..b15b93c --- /dev/null +++ b/tests/footnotes.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +Created : 2024-09-23 + +@author: Bart Broere +""" + +from docxtpl import DocxTemplate + +DEST_FILE = "output/footnotes.docx" + +tpl = DocxTemplate("templates/footnotes_tpl.docx") + +context = {"a_jinja_variable": "A Jinja variable!"} + +tpl.render(context) +tpl.save(DEST_FILE) diff --git a/tests/get_undeclared_variables.py b/tests/get_undeclared_variables.py new file mode 100644 index 0000000..1e6a929 --- /dev/null +++ b/tests/get_undeclared_variables.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +Test for get_undeclared_template_variables method + +This test demonstrates the correct behavior of get_undeclared_template_variables: +1. Before rendering - finds all template variables +2. After rendering with incomplete context - finds missing variables +3. After rendering with complete context - returns empty set +""" + +from docxtpl import DocxTemplate + + +def test_before_render(): + """Test that get_undeclared_template_variables finds all variables before rendering""" + print("=== Test 1: Before render ===") + tpl = DocxTemplate("templates/get_undeclared_variables.docx") + undeclared = tpl.get_undeclared_template_variables() + print(f"Variables found: {undeclared}") + + # Should find all variables + expected_vars = { + "name", + "age", + "email", + "is_student", + "has_degree", + "degree_field", + "skills", + "projects", + "company_name", + "page_number", + "generation_date", + "author", + } + + if undeclared == expected_vars: + print("PASS: Found all expected variables before render") + else: + print(f"FAIL: Expected {expected_vars}, got {undeclared}") + + return undeclared == expected_vars + + +def test_after_incomplete_render(): + """Test that get_undeclared_template_variables finds missing variables after incomplete render""" + print("\n=== Test 2: After incomplete render ===") + tpl = DocxTemplate("templates/get_undeclared_variables.docx") + + # Provide only some variables (missing several) + context = { + "name": "John Doe", + "age": 25, + "email": "john@example.com", + "is_student": True, + "skills": ["Python", "Django"], + "company_name": "Test Corp", + "author": "Test Author", + } + + tpl.render(context) + undeclared = tpl.get_undeclared_template_variables(context=context) + print(f"Missing variables: {undeclared}") + + # Should find missing variables + expected_missing = { + "has_degree", + "degree_field", + "projects", + "page_number", + "generation_date", + } + + if undeclared == expected_missing: + print("PASS: Found missing variables after incomplete render") + else: + print(f"FAIL: Expected missing {expected_missing}, got {undeclared}") + + return undeclared == expected_missing + + +def test_after_complete_render(): + """Test that get_undeclared_template_variables returns empty set after complete render""" + print("\n=== Test 3: After complete render ===") + tpl = DocxTemplate("templates/get_undeclared_variables.docx") + + # Provide all variables + context = { + "name": "John Doe", + "age": 25, + "email": "john@example.com", + "is_student": True, + "has_degree": True, + "degree_field": "Computer Science", + "skills": ["Python", "Django", "JavaScript"], + "projects": [ + {"name": "Project A", "year": 2023, "description": "A great project"}, + {"name": "Project B", "year": 2024, "description": "Another great project"}, + ], + "company_name": "Test Corp", + "page_number": 1, + "generation_date": "2024-01-15", + "author": "Test Author", + } + + tpl.render(context) + undeclared = tpl.get_undeclared_template_variables(context=context) + print(f"Undeclared variables: {undeclared}") + + # Should return empty set + if undeclared == set(): + print("PASS: No undeclared variables after complete render") + else: + print(f"FAIL: Expected empty set, got {undeclared}") + + return undeclared == set() + + +def test_with_custom_jinja_env(): + """Test that get_undeclared_template_variables works with custom Jinja environment""" + print("\n=== Test 4: With custom Jinja environment ===") + from jinja2 import Environment + + tpl = DocxTemplate("templates/get_undeclared_variables.docx") + custom_env = Environment() + + undeclared = tpl.get_undeclared_template_variables(jinja_env=custom_env) + print(f"Variables found with custom env: {undeclared}") + + # Should find all variables + expected_vars = { + "name", + "age", + "email", + "is_student", + "has_degree", + "degree_field", + "skills", + "projects", + "company_name", + "page_number", + "generation_date", + "author", + } + + if undeclared == expected_vars: + print("PASS: Custom Jinja environment works correctly") + else: + print(f"FAIL: Expected {expected_vars}, got {undeclared}") + + return undeclared == expected_vars + + +if __name__ == "__main__": + print("Testing get_undeclared_template_variables method...") + print("=" * 50) + + # Run all tests + test1_passed = test_before_render() + test2_passed = test_after_incomplete_render() + test3_passed = test_after_complete_render() + test4_passed = test_with_custom_jinja_env() + + print("\n" + "=" * 50) + print("SUMMARY:") + print(f"Test 1 (Before render): {'PASS' if test1_passed else 'FAIL'}") + print(f"Test 2 (After incomplete render): {'PASS' if test2_passed else 'FAIL'}") + print(f"Test 3 (After complete render): {'PASS' if test3_passed else 'FAIL'}") + print(f"Test 4 (Custom Jinja env): {'PASS' if test4_passed else 'FAIL'}") + + all_passed = test1_passed and test2_passed and test3_passed and test4_passed + + if all_passed: + print("ALL TESTS PASSED!") + else: + print("SOME TESTS FAILED!") + + print("\nNote: This test demonstrates that get_undeclared_template_variables") + print("now correctly analyzes the original template, not the rendered document.") diff --git a/tests/header_footer.py b/tests/header_footer.py index d60cc74..53362b6 100644 --- a/tests/header_footer.py +++ b/tests/header_footer.py @@ -1,25 +1,25 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/header_footer_tpl.docx') +tpl = DocxTemplate("templates/header_footer_tpl.docx") sd = tpl.new_subdoc() p = sd.add_paragraph( - 'This is a sub-document to check it does not break header and footer' + "This is a sub-document to check it does not break header and footer" ) context = { - 'title': 'Header and footer test', - 'company_name': 'The World Wide company', - 'date': '2016-03-17', - 'mysubdoc': sd, + "title": "Header and footer test", + "company_name": "The World Wide company", + "date": "2016-03-17", + "mysubdoc": sd, } tpl.render(context) -tpl.save('output/header_footer.docx') +tpl.save("output/header_footer.docx") diff --git a/tests/header_footer_entities.py b/tests/header_footer_entities.py index 320b5ef..ddaa3d2 100644 --- a/tests/header_footer_entities.py +++ b/tests/header_footer_entities.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/header_footer_entities_tpl.docx') +tpl = DocxTemplate("templates/header_footer_entities_tpl.docx") context = { - 'title': 'Header and footer test', + "title": "Header and footer test", } tpl.render(context) -tpl.save('output/header_footer_entities.docx') +tpl.save("output/header_footer_entities.docx") diff --git a/tests/header_footer_image.py b/tests/header_footer_image.py index 6ccaf55..24ca020 100644 --- a/tests/header_footer_image.py +++ b/tests/header_footer_image.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2017-09-03 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -DEST_FILE = 'output/header_footer_image.docx' +DEST_FILE = "output/header_footer_image.docx" -tpl = DocxTemplate('templates/header_footer_image_tpl.docx') +tpl = DocxTemplate("templates/header_footer_image_tpl.docx") context = { - 'mycompany': 'The World Wide company', + "mycompany": "The World Wide company", } -tpl.replace_media('templates/dummy_pic_for_header.png', 'templates/python.png') +tpl.replace_media("templates/dummy_pic_for_header.png", "templates/python.png") tpl.render(context) tpl.save(DEST_FILE) diff --git a/tests/header_footer_image_file_obj.py b/tests/header_footer_image_file_obj.py index a1bfcc9..0959748 100644 --- a/tests/header_footer_image_file_obj.py +++ b/tests/header_footer_image_file_obj.py @@ -1,29 +1,29 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2019-05-22 @author: Eric Dufresne -''' +""" from docxtpl import DocxTemplate import io -DEST_FILE = 'output/header_footer_image_file_obj.docx' -DEST_FILE2 = 'output/header_footer_image_file_obj2.docx' +DEST_FILE = "output/header_footer_image_file_obj.docx" +DEST_FILE2 = "output/header_footer_image_file_obj2.docx" -tpl = DocxTemplate('templates/header_footer_image_tpl.docx') +tpl = DocxTemplate("templates/header_footer_image_tpl.docx") context = { - 'mycompany': 'The World Wide company', + "mycompany": "The World Wide company", } -dummy_pic = io.BytesIO(open('templates/dummy_pic_for_header.png', 'rb').read()) -new_image = io.BytesIO(open('templates/python.png', 'rb').read()) +dummy_pic = io.BytesIO(open("templates/dummy_pic_for_header.png", "rb").read()) +new_image = io.BytesIO(open("templates/python.png", "rb").read()) tpl.replace_media(dummy_pic, new_image) tpl.render(context) tpl.save(DEST_FILE) -tpl = DocxTemplate('templates/header_footer_image_tpl.docx') +tpl = DocxTemplate("templates/header_footer_image_tpl.docx") dummy_pic.seek(0) new_image.seek(0) tpl.replace_media(dummy_pic, new_image) @@ -32,5 +32,5 @@ file_obj = io.BytesIO() tpl.save(file_obj) file_obj.seek(0) -with open(DEST_FILE2, 'wb') as f: +with open(DEST_FILE2, "wb") as f: f.write(file_obj.read()) diff --git a/tests/header_footer_inline_image.py b/tests/header_footer_inline_image.py index 81fa0eb..26a3122 100644 --- a/tests/header_footer_inline_image.py +++ b/tests/header_footer_inline_image.py @@ -1,24 +1,24 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2021-04-06 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate, InlineImage # for height and width you have to use millimeters (Mm), inches or points(Pt) class : from docx.shared import Mm -tpl = DocxTemplate('templates/header_footer_inline_image_tpl.docx') +tpl = DocxTemplate("templates/header_footer_inline_image_tpl.docx") context = { - 'inline_image': InlineImage(tpl, 'templates/django.png', height=Mm(10)), - 'images': [ - InlineImage(tpl, 'templates/python.png', height=Mm(10)), - InlineImage(tpl, 'templates/python.png', height=Mm(10)), - InlineImage(tpl, 'templates/python.png', height=Mm(10)) - ] + "inline_image": InlineImage(tpl, "templates/django.png", height=Mm(10)), + "images": [ + InlineImage(tpl, "templates/python.png", height=Mm(10)), + InlineImage(tpl, "templates/python.png", height=Mm(10)), + InlineImage(tpl, "templates/python.png", height=Mm(10)), + ], } tpl.render(context) -tpl.save('output/header_footer_inline_image.docx') +tpl.save("output/header_footer_inline_image.docx") diff --git a/tests/header_footer_utf8.py b/tests/header_footer_utf8.py index 9d2c16b..a167074 100644 --- a/tests/header_footer_utf8.py +++ b/tests/header_footer_utf8.py @@ -1,28 +1,28 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2016-07-19 @author: AhnSeongHyun Edited : 2016-07-19 by Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/header_footer_tpl_utf8.docx') +tpl = DocxTemplate("templates/header_footer_tpl_utf8.docx") sd = tpl.new_subdoc() p = sd.add_paragraph( - u'This is a sub-document to check it does not break header and footer with utf-8 ' - u'characters inside the template .docx' + "This is a sub-document to check it does not break header and footer with utf-8 " + "characters inside the template .docx" ) context = { - 'title': u'헤더와 푸터', - 'company_name': u'세계적 회사', - 'date': u'2016-03-17', - 'mysubdoc': sd, + "title": "헤더와 푸터", + "company_name": "세계적 회사", + "date": "2016-03-17", + "mysubdoc": sd, } tpl.render(context) -tpl.save('output/header_footer_utf8.docx') +tpl.save("output/header_footer_utf8.docx") diff --git a/tests/horizontal_merge.py b/tests/horizontal_merge.py index 7c8393c..889eb77 100644 --- a/tests/horizontal_merge.py +++ b/tests/horizontal_merge.py @@ -2,6 +2,6 @@ from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/horizontal_merge_tpl.docx') +tpl = DocxTemplate("templates/horizontal_merge_tpl.docx") tpl.render({}) -tpl.save('output/horizontal_merge.docx') +tpl.save("output/horizontal_merge.docx") diff --git a/tests/inline_image.py b/tests/inline_image.py index 5134d64..48ed670 100644 --- a/tests/inline_image.py +++ b/tests/inline_image.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2017-01-14 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate, InlineImage @@ -11,37 +11,41 @@ from docx.shared import Mm import jinja2 -tpl = DocxTemplate('templates/inline_image_tpl.docx') +tpl = DocxTemplate("templates/inline_image_tpl.docx") context = { - 'myimage': InlineImage(tpl, 'templates/python_logo.png', width=Mm(20)), - 'myimageratio': InlineImage( - tpl, 'templates/python_jpeg.jpg', width=Mm(30), height=Mm(60) + "myimage": InlineImage(tpl, "templates/python_logo.png", width=Mm(20)), + "myimageratio": InlineImage( + tpl, "templates/python_jpeg.jpg", width=Mm(30), height=Mm(60) ), - 'frameworks': [ + "frameworks": [ { - 'image': InlineImage(tpl, 'templates/django.png', height=Mm(10)), - 'desc': 'The web framework for perfectionists with deadlines', + "image": InlineImage(tpl, "templates/django.png", height=Mm(10)), + "desc": "The web framework for perfectionists with deadlines", }, { - 'image': InlineImage(tpl, 'templates/zope.png', height=Mm(10)), - 'desc': 'Zope is a leading Open Source Application Server and Content Management Framework', + "image": InlineImage(tpl, "templates/zope.png", height=Mm(10)), + "desc": "Zope is a leading Open Source Application Server " + "and Content Management Framework", }, { - 'image': InlineImage(tpl, 'templates/pyramid.png', height=Mm(10)), - 'desc': 'Pyramid is a lightweight Python web framework aimed at taking small web apps into big web apps.', + "image": InlineImage(tpl, "templates/pyramid.png", height=Mm(10)), + "desc": "Pyramid is a lightweight Python web framework aimed at taking " + "small web apps into big web apps.", }, { - 'image': InlineImage(tpl, 'templates/bottle.png', height=Mm(10)), - 'desc': 'Bottle is a fast, simple and lightweight WSGI micro web-framework for Python', + "image": InlineImage(tpl, "templates/bottle.png", height=Mm(10)), + "desc": "Bottle is a fast, simple and lightweight WSGI micro web-framework " + "for Python", }, { - 'image': InlineImage(tpl, 'templates/tornado.png', height=Mm(10)), - 'desc': 'Tornado is a Python web framework and asynchronous networking library.', + "image": InlineImage(tpl, "templates/tornado.png", height=Mm(10)), + "desc": "Tornado is a Python web framework and asynchronous networking " + "library.", }, ], } # testing that it works also when autoescape has been forced to True jinja_env = jinja2.Environment(autoescape=True) tpl.render(context, jinja_env) -tpl.save('output/inline_image.docx') +tpl.save("output/inline_image.docx") diff --git a/tests/less_cells_after_loop.py b/tests/less_cells_after_loop.py index 4e0cd5a..ca725d4 100644 --- a/tests/less_cells_after_loop.py +++ b/tests/less_cells_after_loop.py @@ -1,5 +1,5 @@ from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/less_cells_after_loop_tpl.docx') +tpl = DocxTemplate("templates/less_cells_after_loop_tpl.docx") tpl.render({}) -tpl.save('output/less_cells_after_loop.docx') +tpl.save("output/less_cells_after_loop.docx") diff --git a/tests/merge_docx.py b/tests/merge_docx.py index 9c99d11..28bbfd5 100644 --- a/tests/merge_docx.py +++ b/tests/merge_docx.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2021-07-30 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/merge_docx_master_tpl.docx') -sd = tpl.new_subdoc('templates/merge_docx_subdoc.docx') +tpl = DocxTemplate("templates/merge_docx_master_tpl.docx") +sd = tpl.new_subdoc("templates/merge_docx_subdoc.docx") context = { - 'mysubdoc': sd, + "mysubdoc": sd, } tpl.render(context) -tpl.save('output/merge_docx.docx') +tpl.save("output/merge_docx.docx") diff --git a/tests/merge_paragraph.py b/tests/merge_paragraph.py index 6ff5cf0..39cdcef 100644 --- a/tests/merge_paragraph.py +++ b/tests/merge_paragraph.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/merge_paragraph_tpl.docx') +tpl = DocxTemplate("templates/merge_paragraph_tpl.docx") context = { - 'living_in_town': True, + "living_in_town": True, } tpl.render(context) -tpl.save('output/merge_paragraph.docx') +tpl.save("output/merge_paragraph.docx") diff --git a/tests/module_execute.py b/tests/module_execute.py index e2ef28f..86c4ab9 100644 --- a/tests/module_execute.py +++ b/tests/module_execute.py @@ -1,19 +1,25 @@ import os -TEMPLATE_PATH = 'templates/module_execute_tpl.docx' -JSON_PATH = 'templates/module_execute.json' -OUTPUT_FILENAME = 'output/module_execute.docx' -OVERWRITE = '-o' -QUIET = '-q' +TEMPLATE_PATH = "templates/module_execute_tpl.docx" +JSON_PATH = "templates/module_execute.json" +OUTPUT_FILENAME = "output/module_execute.docx" +OVERWRITE = "-o" +QUIET = "-q" if os.path.exists(OUTPUT_FILENAME): os.unlink(OUTPUT_FILENAME) os.chdir(os.path.dirname(__file__)) -cmd = 'python -m docxtpl %s %s %s %s %s' % (TEMPLATE_PATH, JSON_PATH, OUTPUT_FILENAME, OVERWRITE, QUIET) +cmd = "python -m docxtpl %s %s %s %s %s" % ( + TEMPLATE_PATH, + JSON_PATH, + OUTPUT_FILENAME, + OVERWRITE, + QUIET, +) print('Executing "%s" ...' % cmd) os.system(cmd) if os.path.exists(OUTPUT_FILENAME): - print(' --> File %s has been generated.' % OUTPUT_FILENAME) + print(" --> File %s has been generated." % OUTPUT_FILENAME) diff --git a/tests/multi_rendering.py b/tests/multi_rendering.py index 6822a6f..f9a934f 100644 --- a/tests/multi_rendering.py +++ b/tests/multi_rendering.py @@ -1,40 +1,40 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2021-12-20 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/multi_rendering_tpl.docx') +tpl = DocxTemplate("templates/multi_rendering_tpl.docx") documents_data = [ { - 'dest_file': 'multi_render1.docx', - 'context': { - 'title': 'Title ONE', - 'body': 'This is the body for first document' - } + "dest_file": "multi_render1.docx", + "context": { + "title": "Title ONE", + "body": "This is the body for first document", + }, }, { - 'dest_file': 'multi_render2.docx', - 'context': { - 'title': 'Title TWO', - 'body': 'This is the body for second document' - } + "dest_file": "multi_render2.docx", + "context": { + "title": "Title TWO", + "body": "This is the body for second document", + }, }, { - 'dest_file': 'multi_render3.docx', - 'context': { - 'title': 'Title THREE', - 'body': 'This is the body for third document' - } + "dest_file": "multi_render3.docx", + "context": { + "title": "Title THREE", + "body": "This is the body for third document", + }, }, ] for document_data in documents_data: - dest_file = document_data['dest_file'] - context = document_data['context'] + dest_file = document_data["dest_file"] + context = document_data["context"] tpl.render(context) - tpl.save('output/%s' % dest_file) + tpl.save("output/%s" % dest_file) diff --git a/tests/nested_for.py b/tests/nested_for.py index 839becc..fc67eea 100644 --- a/tests/nested_for.py +++ b/tests/nested_for.py @@ -1,45 +1,45 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2016-03-26 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/nested_for_tpl.docx') +tpl = DocxTemplate("templates/nested_for_tpl.docx") context = { - 'dishes': [ - {'name': 'Pizza', 'ingredients': ['bread', 'tomato', 'ham', 'cheese']}, + "dishes": [ + {"name": "Pizza", "ingredients": ["bread", "tomato", "ham", "cheese"]}, { - 'name': 'Hamburger', - 'ingredients': ['bread', 'chopped steak', 'cheese', 'sauce'], + "name": "Hamburger", + "ingredients": ["bread", "chopped steak", "cheese", "sauce"], }, { - 'name': 'Apple pie', - 'ingredients': ['flour', 'apples', 'suggar', 'quince jelly'], + "name": "Apple pie", + "ingredients": ["flour", "apples", "suggar", "quince jelly"], }, ], - 'authors': [ + "authors": [ { - 'name': 'Saint-Exupery', - 'books': [ - {'title': 'Le petit prince'}, - {'title': "L'aviateur"}, - {'title': 'Vol de nuit'}, + "name": "Saint-Exupery", + "books": [ + {"title": "Le petit prince"}, + {"title": "L'aviateur"}, + {"title": "Vol de nuit"}, ], }, { - 'name': 'Barjavel', - 'books': [ - {'title': 'Ravage'}, - {'title': "La nuit des temps"}, - {'title': 'Le grand secret'}, + "name": "Barjavel", + "books": [ + {"title": "Ravage"}, + {"title": "La nuit des temps"}, + {"title": "Le grand secret"}, ], }, ], } tpl.render(context) -tpl.save('output/nested_for.docx') +tpl.save("output/nested_for.docx") diff --git a/tests/order.py b/tests/order.py index a4f1f57..416da89 100644 --- a/tests/order.py +++ b/tests/order.py @@ -1,26 +1,26 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/order_tpl.docx') +tpl = DocxTemplate("templates/order_tpl.docx") context = { - 'customer_name': 'Eric', - 'items': [ - {'desc': 'Python interpreters', 'qty': 2, 'price': 'FREE'}, - {'desc': 'Django projects', 'qty': 5403, 'price': 'FREE'}, - {'desc': 'Guido', 'qty': 1, 'price': '100,000,000.00'}, + "customer_name": "Eric", + "items": [ + {"desc": "Python interpreters", "qty": 2, "price": "FREE"}, + {"desc": "Django projects", "qty": 5403, "price": "FREE"}, + {"desc": "Guido", "qty": 1, "price": "100,000,000.00"}, ], - 'in_europe': True, - 'is_paid': False, - 'company_name': 'The World Wide company', - 'total_price': '100,000,000.00', + "in_europe": True, + "is_paid": False, + "company_name": "The World Wide company", + "total_price": "100,000,000.00", } tpl.render(context) -tpl.save('output/order.docx') +tpl.save("output/order.docx") diff --git a/tests/preserve_spaces.py b/tests/preserve_spaces.py index 8ff9e82..03d9af0 100644 --- a/tests/preserve_spaces.py +++ b/tests/preserve_spaces.py @@ -3,12 +3,12 @@ # With old docxtpl version, "... for spicy ..." was replaced by "... forspicy..." # This test is for checking that is some cases the spaces are not lost anymore -tpl = DocxTemplate('templates/preserve_spaces_tpl.docx') +tpl = DocxTemplate("templates/preserve_spaces_tpl.docx") -tags = ['tag_1', 'tag_2'] -replacement = ['looking', 'too'] +tags = ["tag_1", "tag_2"] +replacement = ["looking", "too"] context = dict(zip(tags, replacement)) tpl.render(context) -tpl.save('output/preserve_spaces.docx') +tpl.save("output/preserve_spaces.docx") diff --git a/tests/replace_picture.py b/tests/replace_picture.py index 45ccd7d..c30f2ce 100644 --- a/tests/replace_picture.py +++ b/tests/replace_picture.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2017-09-03 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate -DEST_FILE = 'output/replace_picture.docx' +DEST_FILE = "output/replace_picture.docx" -tpl = DocxTemplate('templates/replace_picture_tpl.docx') +tpl = DocxTemplate("templates/replace_picture_tpl.docx") context = {} -tpl.replace_pic('python_logo.png', 'templates/python.png') +tpl.replace_pic("python_logo.png", "templates/python.png") tpl.render(context) tpl.save(DEST_FILE) diff --git a/tests/richtext.py b/tests/richtext.py index 7d40ae5..c836ecf 100644 --- a/tests/richtext.py +++ b/tests/richtext.py @@ -1,55 +1,64 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-26 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate, RichText -tpl = DocxTemplate('templates/richtext_tpl.docx') +tpl = DocxTemplate("templates/richtext_tpl.docx") rt = RichText() -rt.add('a rich text', style='myrichtextstyle') -rt.add(' with ') -rt.add('some italic', italic=True) -rt.add(' and ') -rt.add('some violet', color='#ff00ff') -rt.add(' and ') -rt.add('some striked', strike=True) -rt.add(' and ') -rt.add('some Highlighted', highlight='#ffff00') -rt.add(' and ') -rt.add('some small', size=14) -rt.add(' or ') -rt.add('big', size=60) -rt.add(' text.') -rt.add('\nYou can add an hyperlink, here to ') -rt.add('google', url_id=tpl.build_url_id('http://google.com')) -rt.add('\nEt voilà ! ') -rt.add('\n1st line') -rt.add('\n2nd line') -rt.add('\n3rd line') -rt.add('\aA new paragraph : \a') -rt.add('--- A page break here (see next page) ---\f') - -for ul in ['single', 'double', 'thick', 'dotted', 'dash', 'dotDash', 'dotDotDash', 'wave']: - rt.add('\nUnderline : ' + ul + ' \n', underline=ul) -rt.add('\nFonts :\n', underline=True) -rt.add('Arial\n', font='Arial') -rt.add('Courier New\n', font='Courier New') -rt.add('Times New Roman\n', font='Times New Roman') -rt.add('\n\nHere some') -rt.add('superscript', superscript=True) -rt.add(' and some') -rt.add('subscript', subscript=True) - -rt_embedded = RichText('an example of ') +rt.add("a rich text", style="myrichtextstyle") +rt.add(" with ") +rt.add("some italic", italic=True) +rt.add(" and ") +rt.add("some violet", color="#ff00ff") +rt.add(" and ") +rt.add("some striked", strike=True) +rt.add(" and ") +rt.add("some Highlighted", highlight="#ffff00") +rt.add(" and ") +rt.add("some small", size=14) +rt.add(" or ") +rt.add("big", size=60) +rt.add(" text.") +rt.add("\nYou can add an hyperlink, here to ") +rt.add("google", url_id=tpl.build_url_id("http://google.com")) +rt.add("\nEt voilà ! ") +rt.add("\n1st line") +rt.add("\n2nd line") +rt.add("\n3rd line") +rt.add("\aA new paragraph : \a") +rt.add("--- A page break here (see next page) ---\f") + +for ul in [ + "single", + "double", + "thick", + "dotted", + "dash", + "dotDash", + "dotDotDash", + "wave", +]: + rt.add("\nUnderline : " + ul + " \n", underline=ul) +rt.add("\nFonts :\n", underline=True) +rt.add("Arial\n", font="Arial") +rt.add("Courier New\n", font="Courier New") +rt.add("Times New Roman\n", font="Times New Roman") +rt.add("\n\nHere some") +rt.add("superscript", superscript=True) +rt.add(" and some") +rt.add("subscript", subscript=True) + +rt_embedded = RichText("an example of ") rt_embedded.add(rt) context = { - 'example': rt_embedded, + "example": rt_embedded, } tpl.render(context) -tpl.save('output/richtext.docx') +tpl.save("output/richtext.docx") diff --git a/tests/richtext_and_if.py b/tests/richtext_and_if.py index 34ed64d..031eb62 100644 --- a/tests/richtext_and_if.py +++ b/tests/richtext_and_if.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-26 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate, RichText -tpl = DocxTemplate('templates/richtext_and_if_tpl.docx') +tpl = DocxTemplate("templates/richtext_and_if_tpl.docx") -context = {'foobar': RichText('Foobar!', color='ff0000')} +context = {"foobar": RichText("Foobar!", color="ff0000")} tpl.render(context) -tpl.save('output/richtext_and_if.docx') +tpl.save("output/richtext_and_if.docx") diff --git a/tests/richtext_eastAsia.py b/tests/richtext_eastAsia.py index 1dd2a4f..23177bc 100644 --- a/tests/richtext_eastAsia.py +++ b/tests/richtext_eastAsia.py @@ -6,15 +6,16 @@ from docxtpl import DocxTemplate, RichText -tpl = DocxTemplate('templates/richtext_eastAsia_tpl.docx') -rt = RichText('测试TEST', font='eastAsia:Microsoft YaHei') -ch = RichText('测试TEST', font='eastAsia:微软雅黑') -sun = RichText('测试TEST', font='eastAsia:SimSun') + +tpl = DocxTemplate("templates/richtext_eastAsia_tpl.docx") +rt = RichText("测试TEST", font="eastAsia:Microsoft YaHei") +ch = RichText("测试TEST", font="eastAsia:微软雅黑") +sun = RichText("测试TEST", font="eastAsia:SimSun") context = { - 'example': rt, - 'Chinese': ch, - 'simsun': sun, + "example": rt, + "Chinese": ch, + "simsun": sun, } tpl.render(context) -tpl.save('output/richtext_eastAsia.docx') +tpl.save("output/richtext_eastAsia.docx") diff --git a/tests/richtextparagraph.py b/tests/richtextparagraph.py new file mode 100644 index 0000000..5055837 --- /dev/null +++ b/tests/richtextparagraph.py @@ -0,0 +1,63 @@ +""" +Created : 2025-02-28 + +@author: Hannah Imrie +""" + +from docxtpl import DocxTemplate, RichText, RichTextParagraph + +tpl = DocxTemplate("templates/richtext_paragraph_tpl.docx") + +rtp = RichTextParagraph() +rt = RichText() + +rtp.add( + "The rich text paragraph function allows paragraph styles to be added to text", + parastyle="myrichparastyle", +) +rtp.add("Any built in paragraph style can be used", parastyle="IntenseQuote") +rtp.add( + "or you can add your own, unlocking all style options", parastyle="createdStyle" +) +rtp.add( + "To use, just create a style in your template word doc with the formatting you want " + "and call it in the code.", + parastyle="normal", +) + +rtp.add("This allows for the use of") +rtp.add("custom bullet\apoints", parastyle="SquareBullet") +rtp.add("Numbered Bullet Points", parastyle="BasicNumbered") +rtp.add("and Alpha Bullet Points.", parastyle="alphaBracketNumbering") +rtp.add("You can", parastyle="normal") +rtp.add("set the", parastyle="centerAlign") +rtp.add("text alignment", parastyle="rightAlign") +rtp.add( + "as well as the spacing between lines of text. Like this for example, " + "this text has very tight spacing between the lines.\aIt also has no space between " + "paragraphs of the same style.", + parastyle="TightLineSpacing", +) +rtp.add( + "Unlike this one, which has extra large spacing between lines for when you want to " + "space things out a bit or just write a little less.", + parastyle="WideLineSpacing", +) +rtp.add( + "You can also set the background colour of a line.", parastyle="LineShadingGreen" +) + +rt.add("This works with ") +rt.add("Rich ", bold=True) +rt.add("Text ", italic=True) +rt.add("Strings", underline="single") +rt.add(" too.") + +rtp.add(rt, parastyle="SquareBullet") + +context = { + "example": rtp, +} + +tpl.render(context) +tpl.save("output/richtext_paragraph.docx") diff --git a/tests/runtests.py b/tests/runtests.py index c956864..b0a3bf3 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,18 +1,17 @@ import subprocess import glob -import six import os -tests = sorted(glob.glob('[A-Za-z]*.py')) -excludes = ['runtests.py'] +tests = sorted(glob.glob("[A-Za-z]*.py")) +excludes = ["runtests.py"] -output_dir = os.path.join(os.path.dirname(__file__), 'output') +output_dir = os.path.join(os.path.dirname(__file__), "output") if not os.path.exists(output_dir): os.mkdir(output_dir) for test in tests: if test not in excludes: - six.print_('%s ...' % test) - subprocess.call(['python', './%s' % test]) + print("%s ..." % test) + subprocess.call(["python", "./%s" % test]) -six.print_('Done.') +print("Done.") diff --git a/tests/subdoc.py b/tests/subdoc.py index b118e4b..10f5abd 100644 --- a/tests/subdoc.py +++ b/tests/subdoc.py @@ -1,36 +1,36 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2015-03-12 @author: Eric Lapouyade -''' +""" from docxtpl import DocxTemplate from docx.shared import Inches -tpl = DocxTemplate('templates/subdoc_tpl.docx') +tpl = DocxTemplate("templates/subdoc_tpl.docx") sd = tpl.new_subdoc() -p = sd.add_paragraph('This is a sub-document inserted into a bigger one') -p = sd.add_paragraph('It has been ') -p.add_run('dynamically').style = 'dynamic' -p.add_run(' generated with python by using ') -p.add_run('python-docx').italic = True -p.add_run(' library') +p = sd.add_paragraph("This is a sub-document inserted into a bigger one") +p = sd.add_paragraph("It has been ") +p.add_run("dynamically").style = "dynamic" +p.add_run(" generated with python by using ") +p.add_run("python-docx").italic = True +p.add_run(" library") -sd.add_heading('Heading, level 1', level=1) -sd.add_paragraph('This is an Intense quote', style='IntenseQuote') +sd.add_heading("Heading, level 1", level=1) +sd.add_paragraph("This is an Intense quote", style="IntenseQuote") -sd.add_paragraph('A picture :') -sd.add_picture('templates/python_logo.png', width=Inches(1.25)) +sd.add_paragraph("A picture :") +sd.add_picture("templates/python_logo.png", width=Inches(1.25)) -sd.add_paragraph('A Table :') +sd.add_paragraph("A Table :") table = sd.add_table(rows=1, cols=3) hdr_cells = table.rows[0].cells -hdr_cells[0].text = 'Qty' -hdr_cells[1].text = 'Id' -hdr_cells[2].text = 'Desc' -recordset = ((1, 101, 'Spam'), (2, 42, 'Eggs'), (3, 631, 'Spam,spam, eggs, and ham')) +hdr_cells[0].text = "Qty" +hdr_cells[1].text = "Id" +hdr_cells[2].text = "Desc" +recordset = ((1, 101, "Spam"), (2, 42, "Eggs"), (3, 631, "Spam,spam, eggs, and ham")) for item in recordset: row_cells = table.add_row().cells row_cells[0].text = str(item[0]) @@ -38,8 +38,8 @@ row_cells[2].text = item[2] context = { - 'mysubdoc': sd, + "mysubdoc": sd, } tpl.render(context) -tpl.save('output/subdoc.docx') +tpl.save("output/subdoc.docx") diff --git a/tests/template_error.py b/tests/template_error.py index 2938fdc..66c6428 100644 --- a/tests/template_error.py +++ b/tests/template_error.py @@ -1,20 +1,19 @@ from docxtpl import DocxTemplate from jinja2.exceptions import TemplateError -import six -six.print_('=' * 80) -six.print_("Generating template error for testing (so it is safe to ignore) :") -six.print_('.' * 80) +print("=" * 80) +print("Generating template error for testing (so it is safe to ignore) :") +print("." * 80) try: - tpl = DocxTemplate('templates/template_error_tpl.docx') - tpl.render({'test_variable': 'test variable value'}) + tpl = DocxTemplate("templates/template_error_tpl.docx") + tpl.render({"test_variable": "test variable value"}) except TemplateError as the_error: - six.print_(six.text_type(the_error)) - if hasattr(the_error, 'docx_context'): - six.print_("Context:") + print(str(the_error)) + if hasattr(the_error, "docx_context"): + print("Context:") for line in the_error.docx_context: - six.print_(line) -tpl.save('output/template_error.docx') -six.print_('.' * 80) -six.print_(" End of TemplateError Test ") -six.print_('=' * 80) + print(line) +tpl.save("output/template_error.docx") +print("." * 80) +print(" End of TemplateError Test ") +print("=" * 80) diff --git a/tests/templates/footnotes_tpl.docx b/tests/templates/footnotes_tpl.docx new file mode 100644 index 0000000..838c002 Binary files /dev/null and b/tests/templates/footnotes_tpl.docx differ diff --git a/tests/templates/get_undeclared_variables.docx b/tests/templates/get_undeclared_variables.docx new file mode 100644 index 0000000..e112a90 Binary files /dev/null and b/tests/templates/get_undeclared_variables.docx differ diff --git a/tests/templates/richtext_paragraph_tpl.docx b/tests/templates/richtext_paragraph_tpl.docx new file mode 100644 index 0000000..4ae4501 Binary files /dev/null and b/tests/templates/richtext_paragraph_tpl.docx differ diff --git a/tests/vertical_merge.py b/tests/vertical_merge.py index 60b6288..c0dc68f 100644 --- a/tests/vertical_merge.py +++ b/tests/vertical_merge.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- -''' +""" Created : 2017-10-15 @author: Arthaslixin -''' +""" from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/vertical_merge_tpl.docx') +tpl = DocxTemplate("templates/vertical_merge_tpl.docx") context = { - 'items': [ - {'desc': 'Python interpreters', 'qty': 2, 'price': 'FREE'}, - {'desc': 'Django projects', 'qty': 5403, 'price': 'FREE'}, - {'desc': 'Guido', 'qty': 1, 'price': '100,000,000.00'}, + "items": [ + {"desc": "Python interpreters", "qty": 2, "price": "FREE"}, + {"desc": "Django projects", "qty": 5403, "price": "FREE"}, + {"desc": "Guido", "qty": 1, "price": "100,000,000.00"}, ], - 'total_price': '100,000,000.00', - 'category': 'Book', + "total_price": "100,000,000.00", + "category": "Book", } tpl.render(context) -tpl.save('output/vertical_merge.docx') +tpl.save("output/vertical_merge.docx") diff --git a/tests/vertical_merge_nested.py b/tests/vertical_merge_nested.py index 3ae7bcf..bcd912a 100644 --- a/tests/vertical_merge_nested.py +++ b/tests/vertical_merge_nested.py @@ -1,5 +1,5 @@ from docxtpl import DocxTemplate -tpl = DocxTemplate('templates/vertical_merge_nested_tpl.docx') +tpl = DocxTemplate("templates/vertical_merge_nested_tpl.docx") tpl.render({}) -tpl.save('output/vertical_merge_nested.docx') +tpl.save("output/vertical_merge_nested.docx") diff --git a/tests/word2016.py b/tests/word2016.py index eb2f77d..81f2333 100644 --- a/tests/word2016.py +++ b/tests/word2016.py @@ -1,12 +1,12 @@ from docxtpl import DocxTemplate, RichText -tpl = DocxTemplate('templates/word2016_tpl.docx') +tpl = DocxTemplate("templates/word2016_tpl.docx") tpl.render( { - 'test_space': ' ', - 'test_tabs': 5 * '\t', - 'test_space_r': RichText(' '), - 'test_tabs_r': RichText(5 * '\t'), + "test_space": " ", + "test_tabs": 5 * "\t", + "test_space_r": RichText(" "), + "test_tabs_r": RichText(5 * "\t"), } ) -tpl.save('output/word2016.docx') +tpl.save("output/word2016.docx")