From 28fdbd691dc58cce4c36ec074f00e6de0ae8ed1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Wed, 21 Jun 2023 17:00:49 +0200 Subject: [PATCH] general: revert switch to lxml, go back to ElementTree (#78824) --- pylint.rc | 1 - tests/admin_pages/test_card.py | 2 +- tests/admin_pages/test_datasource.py | 2 +- tests/admin_pages/test_form.py | 2 +- tests/admin_pages/test_workflow.py | 2 +- tests/admin_pages/test_wscall.py | 2 +- tests/api/test_custom_view.py | 2 +- tests/api/test_export_import.py | 2 +- tests/api/test_formdata.py | 2 +- tests/backoffice_pages/test_export.py | 2 +- tests/form_pages/test_all.py | 2 +- tests/form_pages/test_formdata.py | 4 +-- tests/template-out.odt | Bin 11166 -> 32910 bytes tests/test_block.py | 6 ++-- tests/test_blockdef_import.py | 5 +-- tests/test_carddef.py | 5 +-- tests/test_comment_template.py | 5 +-- tests/test_datasource.py | 5 +-- tests/test_formdef_import.py | 17 +++++----- tests/test_mail_templates.py | 5 +-- tests/test_publisher.py | 2 +- tests/test_snapshots.py | 2 +- tests/test_workflow_import.py | 9 +++-- wcs/admin/forms.py | 2 +- wcs/admin/settings.py | 16 ++++----- wcs/admin/workflows.py | 2 +- wcs/api_export_import.py | 6 ++-- wcs/backoffice/i18n.py | 6 ++-- wcs/blocks.py | 2 +- wcs/categories.py | 2 +- wcs/custom_views.py | 2 +- wcs/data_sources.py | 2 +- wcs/fields.py | 2 +- wcs/formdef.py | 2 +- wcs/qommon/misc.py | 21 ++++++++++-- wcs/qommon/ods.py | 6 ++-- wcs/qommon/xml_storage.py | 9 +++-- wcs/roles.py | 2 +- wcs/snapshots.py | 47 +++++++++++++++++++++++--- wcs/wf/attachment.py | 2 +- wcs/wf/backoffice_fields.py | 2 +- wcs/wf/comment.py | 2 +- wcs/wf/create_formdata.py | 2 +- wcs/wf/dispatch.py | 2 +- wcs/wf/export_to_model.py | 15 +++++++- wcs/wf/form.py | 2 +- wcs/wf/profile.py | 2 +- wcs/wf/wscall.py | 2 +- wcs/workflows.py | 2 +- wcs/wscalls.py | 2 +- 50 files changed, 161 insertions(+), 89 deletions(-) diff --git a/pylint.rc b/pylint.rc index ecbbca5ca..ceab635f5 100644 --- a/pylint.rc +++ b/pylint.rc @@ -1,7 +1,6 @@ [MASTER] persistent=yes ignore=vendor,Bouncers,ezt.py -extension-pkg-allow-list=lxml.etree [MESSAGES CONTROL] disable= diff --git a/tests/admin_pages/test_card.py b/tests/admin_pages/test_card.py index 1ab57a5bb..75b4a89b2 100644 --- a/tests/admin_pages/test_card.py +++ b/tests/admin_pages/test_card.py @@ -1,8 +1,8 @@ import os import re +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from webtest import Upload from wcs import fields diff --git a/tests/admin_pages/test_datasource.py b/tests/admin_pages/test_datasource.py index c4aa94ad4..4a0963697 100644 --- a/tests/admin_pages/test_datasource.py +++ b/tests/admin_pages/test_datasource.py @@ -2,11 +2,11 @@ import io import json import os import re +import xml.etree.ElementTree as ET from unittest import mock import pytest import responses -from lxml import etree as ET from webtest import Upload from wcs import fields diff --git a/tests/admin_pages/test_form.py b/tests/admin_pages/test_form.py index 354e0259f..759e8600d 100644 --- a/tests/admin_pages/test_form.py +++ b/tests/admin_pages/test_form.py @@ -3,11 +3,11 @@ import os import re import time import uuid +import xml.etree.ElementTree as ET import pytest import responses from django.utils.timezone import now -from lxml import etree as ET from pyquery import PyQuery from webtest import Upload diff --git a/tests/admin_pages/test_workflow.py b/tests/admin_pages/test_workflow.py index b8ad31122..ffce9902c 100644 --- a/tests/admin_pages/test_workflow.py +++ b/tests/admin_pages/test_workflow.py @@ -2,10 +2,10 @@ import io import os import re import uuid +import xml.etree.ElementTree as ET import pytest import responses -from lxml import etree as ET from quixote.http_request import Upload as QuixoteUpload from webtest import Radio, Upload diff --git a/tests/admin_pages/test_wscall.py b/tests/admin_pages/test_wscall.py index 7d2274251..321109821 100644 --- a/tests/admin_pages/test_wscall.py +++ b/tests/admin_pages/test_wscall.py @@ -1,7 +1,7 @@ import io +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from webtest import Upload from wcs.qommon.http_request import HTTPRequest diff --git a/tests/api/test_custom_view.py b/tests/api/test_custom_view.py index 68b4c9d8e..9fd2c06d1 100644 --- a/tests/api/test_custom_view.py +++ b/tests/api/test_custom_view.py @@ -1,9 +1,9 @@ import io import os +import xml.etree.ElementTree as ET import zipfile import pytest -from lxml import etree as ET from wcs import fields from wcs.carddef import CardDef diff --git a/tests/api/test_export_import.py b/tests/api/test_export_import.py index 78e21e327..d71bcc9cd 100644 --- a/tests/api/test_export_import.py +++ b/tests/api/test_export_import.py @@ -2,9 +2,9 @@ import io import json import os import tarfile +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from wcs.applications import Application, ApplicationElement from wcs.blocks import BlockDef diff --git a/tests/api/test_formdata.py b/tests/api/test_formdata.py index 67b2264fc..a7e60036a 100644 --- a/tests/api/test_formdata.py +++ b/tests/api/test_formdata.py @@ -5,11 +5,11 @@ import json import os import re import time +import xml.etree.ElementTree as ET import zipfile import pytest from django.utils.encoding import force_bytes -from lxml import etree as ET from quixote import get_publisher from webtest import Upload diff --git a/tests/backoffice_pages/test_export.py b/tests/backoffice_pages/test_export.py index 09d460260..7b8852c48 100644 --- a/tests/backoffice_pages/test_export.py +++ b/tests/backoffice_pages/test_export.py @@ -4,10 +4,10 @@ import json import os import time import urllib.parse +import xml.etree.ElementTree as ET import zipfile import pytest -from lxml import etree as ET from wcs import fields from wcs.blocks import BlockDef diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py index 17d47c929..f6e668554 100644 --- a/tests/form_pages/test_all.py +++ b/tests/form_pages/test_all.py @@ -6,13 +6,13 @@ import os import re import time import urllib.parse +import xml.etree.ElementTree as ET import zipfile from unittest import mock import pytest import responses from django.utils.encoding import force_bytes, force_str -from lxml import etree as ET from webtest import Hidden, Upload from wcs import fields diff --git a/tests/form_pages/test_formdata.py b/tests/form_pages/test_formdata.py index 413cdbf6a..0cd781aa4 100644 --- a/tests/form_pages/test_formdata.py +++ b/tests/form_pages/test_formdata.py @@ -3,13 +3,13 @@ import io import json import os import urllib.parse +import xml.etree.ElementTree as ET import zipfile from unittest import mock import pytest import responses from django.utils.encoding import force_bytes -from lxml import etree as ET from quixote.http_request import Upload as QuixoteUpload from webtest import Hidden, Upload @@ -59,7 +59,7 @@ def assert_equal_zip(stream1, stream2): t1, t2 = ET.tostring(ET.XML(z1.read(name))), ET.tostring(ET.XML(z2.read(name))) try: # >= python 3.8: tostring preserves attribute order; use canonicalize to sort them - t1, t2 = ET.canonicalize(t1.decode("utf-8")), ET.canonicalize(t2.decode("utf-8")) + t1, t2 = ET.canonicalize(t1), ET.canonicalize(t2) except AttributeError: pass else: diff --git a/tests/template-out.odt b/tests/template-out.odt index 1976dc734df578862b33b7408480169bb2801a56..ba6ac6cc1470edf5bff0bbbc9c98a2ce272d3a4e 100644 GIT binary patch literal 32910 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 QwTIz92beYYjYd!Z2lAfox&QzG literal 11166 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 diff --git a/tests/test_block.py b/tests/test_block.py index 57e99f1f0..6dc9fa8f5 100644 --- a/tests/test_block.py +++ b/tests/test_block.py @@ -1,8 +1,10 @@ +import xml.etree.ElementTree as ET + import pytest -from lxml import etree as ET from wcs.blocks import BlockDef from wcs.categories import BlockCategory +from wcs.qommon.misc import indent_xml as indent from .utilities import clean_temporary_pub, create_temporary_pub @@ -18,7 +20,7 @@ def teardown_module(module): def export_to_indented_xml(block, include_id=False): block_xml = block.export_to_xml(include_id=include_id) - ET.indent(block_xml) + indent(block_xml) return block_xml diff --git a/tests/test_blockdef_import.py b/tests/test_blockdef_import.py index 1a68f7bc2..dd31950a1 100644 --- a/tests/test_blockdef_import.py +++ b/tests/test_blockdef_import.py @@ -1,11 +1,12 @@ import io +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from wcs import fields from wcs.blocks import BlockDef, BlockdefImportError from wcs.carddef import CardDef +from wcs.qommon.misc import indent_xml as indent from .utilities import clean_temporary_pub, create_temporary_pub @@ -21,7 +22,7 @@ def teardown_module(module): def export_to_indented_xml(blockdef, include_id=False): blockdef_xml = ET.fromstring(ET.tostring(blockdef.export_to_xml(include_id=include_id))) - ET.indent(blockdef_xml) + indent(blockdef_xml) return blockdef_xml diff --git a/tests/test_carddef.py b/tests/test_carddef.py index a8b72e80f..9824b8943 100644 --- a/tests/test_carddef.py +++ b/tests/test_carddef.py @@ -1,8 +1,8 @@ import io import json +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from wcs.blocks import BlockDef from wcs.carddef import CardDef @@ -11,6 +11,7 @@ from wcs.data_sources import NamedDataSource from wcs.fields import BlockField, ComputedField, ItemField, ItemsField, StringField from wcs.formdef import FormDef from wcs.qommon.http_request import HTTPRequest +from wcs.qommon.misc import indent_xml as indent from wcs.qommon.template import Template from .utilities import clean_temporary_pub, create_temporary_pub @@ -32,7 +33,7 @@ def teardown_module(module): def export_to_indented_xml(carddef, include_id=False): carddef_xml = ET.fromstring(ET.tostring(carddef.export_to_xml(include_id=include_id))) - ET.indent(carddef_xml) + indent(carddef_xml) return carddef_xml diff --git a/tests/test_comment_template.py b/tests/test_comment_template.py index 1602a38d6..67ce9d976 100644 --- a/tests/test_comment_template.py +++ b/tests/test_comment_template.py @@ -1,9 +1,9 @@ import io import os import re +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from quixote import cleanup from webtest import Upload @@ -13,6 +13,7 @@ from wcs.fields import FileField from wcs.formdef import FormDef from wcs.qommon.http_request import HTTPRequest from wcs.qommon.ident.password_accounts import PasswordAccount +from wcs.qommon.misc import indent_xml as indent from wcs.qommon.upload_storage import PicklableUpload from wcs.workflows import Workflow @@ -473,7 +474,7 @@ def test_comment_templates_duplicate(pub, superuser, comment_template): def export_to_indented_xml(comment_template, include_id=False): comment_template_xml = comment_template.export_to_xml(include_id=include_id) - ET.indent(comment_template_xml) + indent(comment_template_xml) return comment_template_xml diff --git a/tests/test_datasource.py b/tests/test_datasource.py index 6075262f8..08ad829e9 100644 --- a/tests/test_datasource.py +++ b/tests/test_datasource.py @@ -2,10 +2,10 @@ import codecs import json import os import urllib.parse +import xml.etree.ElementTree as ET import pytest import responses -from lxml import etree as ET from wcs import data_sources, fields from wcs.categories import DataSourceCategory @@ -13,6 +13,7 @@ from wcs.data_sources import NamedDataSource, register_data_source_function from wcs.formdef import FormDef from wcs.qommon.form import Form, get_request from wcs.qommon.http_request import HTTPRequest +from wcs.qommon.misc import indent_xml as indent from wcs.workflows import WorkflowStatusItem from .test_widgets import MockHtmlForm, mock_form_submission @@ -1590,7 +1591,7 @@ def test_data_source_in_template(pub): def export_to_indented_xml(data_source, include_id=False): data_source_xml = data_source.export_to_xml(include_id=include_id) - ET.indent(data_source_xml) + indent(data_source_xml) return data_source_xml diff --git a/tests/test_formdef_import.py b/tests/test_formdef_import.py index 0a0fcf86c..21bd1c653 100644 --- a/tests/test_formdef_import.py +++ b/tests/test_formdef_import.py @@ -1,13 +1,14 @@ import io import time +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from wcs.blocks import BlockDef from wcs.carddef import CardDef from wcs.categories import Category from wcs.formdef import FormDef, FormdefImportError, fields +from wcs.qommon.misc import indent_xml as indent from wcs.workflows import Workflow from .utilities import clean_temporary_pub, create_temporary_pub @@ -24,7 +25,7 @@ def teardown_module(module): def export_to_indented_xml(formdef, include_id=False): formdef_xml = ET.fromstring(ET.tostring(formdef.export_to_xml(include_id=include_id))) - ET.indent(formdef_xml) + indent(formdef_xml) return formdef_xml @@ -93,9 +94,9 @@ def test_empty_display_locations_tag(pub): f1 = formdef_xml.findall('fields/field')[0] f2 = formdef_xml.findall('fields/field')[1] f3 = formdef_xml.findall('fields/field')[2] - assert '' in str(ET.tostring(f1)) - assert '' in str(ET.tostring(f2)) - assert '' in str(ET.tostring(f3)) + assert '' in str(ET.tostring(f1)) + assert '' in str(ET.tostring(f2)) + assert '' in str(ET.tostring(f3)) formdef2 = assert_xml_import_export_works(formdef) assert formdef2.fields[0].display_locations == [] @@ -105,9 +106,9 @@ def test_empty_display_locations_tag(pub): f1 = formdef2_xml.findall('fields/field')[0] f2 = formdef2_xml.findall('fields/field')[1] f3 = formdef2_xml.findall('fields/field')[2] - assert '' in str(ET.tostring(f1)) - assert '' in str(ET.tostring(f2)) - assert '' in str(ET.tostring(f3)) + assert '' in str(ET.tostring(f1)) + assert '' in str(ET.tostring(f2)) + assert '' in str(ET.tostring(f3)) def test_boolean_attributes(pub): diff --git a/tests/test_mail_templates.py b/tests/test_mail_templates.py index 04553d047..e1e91d281 100644 --- a/tests/test_mail_templates.py +++ b/tests/test_mail_templates.py @@ -1,9 +1,9 @@ import io import os import re +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from quixote import cleanup from webtest import Upload @@ -13,6 +13,7 @@ from wcs.formdef import FormDef from wcs.mail_templates import MailTemplate from wcs.qommon.http_request import HTTPRequest from wcs.qommon.ident.password_accounts import PasswordAccount +from wcs.qommon.misc import indent_xml as indent from wcs.qommon.upload_storage import PicklableUpload from wcs.workflows import Workflow @@ -502,7 +503,7 @@ def test_mail_templates_duplicate(pub, superuser, mail_template): def export_to_indented_xml(mail_template, include_id=False): mail_template_xml = mail_template.export_to_xml(include_id=include_id) - ET.indent(mail_template_xml) + indent(mail_template_xml) return mail_template_xml diff --git a/tests/test_publisher.py b/tests/test_publisher.py index e17411136..3f9a89a78 100644 --- a/tests/test_publisher.py +++ b/tests/test_publisher.py @@ -8,6 +8,7 @@ import re import shutil import sys import time +import xml.etree.ElementTree as ET import zipfile from unittest import mock @@ -16,7 +17,6 @@ from django.core.management import call_command from django.http import Http404 from django.test import override_settings from django.utils.timezone import localtime -from lxml import etree as ET from quixote import cleanup, get_publisher from quixote.http_request import Upload diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index 54accfe20..8225151d9 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -1,9 +1,9 @@ import io import os import shutil +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from quixote.http_request import Upload from wcs.blocks import BlockDef diff --git a/tests/test_workflow_import.py b/tests/test_workflow_import.py index 880b5eab7..fe4cac481 100644 --- a/tests/test_workflow_import.py +++ b/tests/test_workflow_import.py @@ -1,8 +1,8 @@ import io import re +import xml.etree.ElementTree as ET import pytest -from lxml import etree as ET from quixote.http_request import Upload from wcs.blocks import BlockDef @@ -12,6 +12,7 @@ from wcs.fields import BlockField, BoolField, EmailField, FileField, ItemField, from wcs.formdef import FormDef from wcs.mail_templates import MailTemplate from wcs.qommon.form import UploadedFile +from wcs.qommon.misc import indent_xml as indent from wcs.wf.create_formdata import Mapping from wcs.wf.form import WorkflowFormFieldsFormDef from wcs.workflows import ( @@ -36,7 +37,7 @@ def teardown_module(module): def export_to_indented_xml(workflow, include_id=False): workflow_xml = workflow.export_to_xml(include_id=include_id) - ET.indent(workflow_xml) + indent(workflow_xml) return workflow_xml @@ -167,11 +168,9 @@ def test_status_actions_named_existing_role(pub): commentable.by = [2] wf2 = assert_import_export_works(wf) - wf_xml = wf.export_to_xml() - ET.indent(wf_xml) assert re.findall( 'Test Role named existing role', - ET.tostring(wf_xml).decode(), + ET.tostring(indent(wf.export_to_xml())).decode(), ) assert wf2.possible_status[0].items[0].by == ['2'] diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index f9fc7f53c..a345a6c8d 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -16,9 +16,9 @@ import difflib import io +import xml.etree.ElementTree as ET from collections import defaultdict -from lxml import etree as ET from quixote import get_publisher, get_request, get_response, get_session, redirect from quixote.directory import AccessControlled, Directory from quixote.html import TemplateIO, htmlescape, htmltext diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py index 99fafc8b2..603477f55 100644 --- a/wcs/admin/settings.py +++ b/wcs/admin/settings.py @@ -24,10 +24,10 @@ try: import lasso except ImportError: lasso = None +import xml.etree.ElementTree as ET import zipfile from django.utils.encoding import force_bytes -from lxml import etree as ET from quixote import get_publisher, get_request, get_response, get_session, redirect from quixote.directory import AccessControlled, Directory from quixote.html import TemplateIO, htmltext @@ -38,7 +38,7 @@ from wcs.carddef import CardDef from wcs.data_sources import NamedDataSource from wcs.fields import MapOptionsMixin from wcs.formdef import FormDef, FormdefImportError -from wcs.qommon import _, errors, get_cfg, ident, template +from wcs.qommon import _, errors, get_cfg, ident, misc, template from wcs.qommon.admin.cfg import cfg_submit from wcs.qommon.admin.emails import EmailsDirectory from wcs.qommon.admin.texts import TextsDirectory @@ -821,7 +821,7 @@ class SettingsDirectory(AccessControlled, Directory): if ds.external == 'agenda': continue node = ds.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('datasources', str(ds.id)), ET.tostring(node), @@ -830,7 +830,7 @@ class SettingsDirectory(AccessControlled, Directory): if 'formdefs' in self.dirs: for formdef in FormDef.select(): node = formdef.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('formdefs_xml', str(formdef.id)), b'\n' + ET.tostring(node), @@ -839,7 +839,7 @@ class SettingsDirectory(AccessControlled, Directory): if 'carddefs' in self.dirs: for formdef in CardDef.select(): node = formdef.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('carddefs_xml', str(formdef.id)), b'\n' + ET.tostring(node), @@ -848,7 +848,7 @@ class SettingsDirectory(AccessControlled, Directory): if 'workflows' in self.dirs: for workflow in Workflow.select(): node = workflow.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('workflows_xml', str(workflow.id)), b'\n' + ET.tostring(node), @@ -857,7 +857,7 @@ class SettingsDirectory(AccessControlled, Directory): if 'blockdefs' in self.dirs: for blockdef in BlockDef.select(): node = blockdef.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('blockdefs_xml', str(blockdef.id)), b'\n' + ET.tostring(node), @@ -866,7 +866,7 @@ class SettingsDirectory(AccessControlled, Directory): if 'roles' in self.dirs: for role in get_publisher().role_class.select(): node = role.export_to_xml(include_id=True) - ET.indent(node) + misc.indent_xml(node) z.writestr( os.path.join('roles_xml', str(role.id)), b'\n' + ET.tostring(node), diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py index 2c661dd14..c28daa53d 100644 --- a/wcs/admin/workflows.py +++ b/wcs/admin/workflows.py @@ -19,10 +19,10 @@ import itertools import json import textwrap import time +import xml.etree.ElementTree as ET from subprocess import PIPE, Popen from django.utils.encoding import force_bytes -from lxml import etree as ET from quixote import get_publisher, get_request, get_response, get_session, redirect from quixote.directory import Directory from quixote.html import TemplateIO, htmltext diff --git a/wcs/api_export_import.py b/wcs/api_export_import.py index 5fa5f16c0..3e54623c5 100644 --- a/wcs/api_export_import.py +++ b/wcs/api_export_import.py @@ -17,11 +17,11 @@ import io import json import tarfile +import xml.etree.ElementTree as ET from django.http import Http404, HttpResponse, HttpResponseForbidden, JsonResponse from django.shortcuts import redirect from django.urls import reverse -from lxml import etree as ET from wcs.api_utils import is_url_signed from wcs.applications import Application, ApplicationElement @@ -46,7 +46,7 @@ from wcs.wscalls import NamedWsCall from .qommon import _ from .qommon.afterjobs import AfterJob -from .qommon.misc import xml_node_text +from .qommon.misc import indent_xml, xml_node_text klasses = { 'blocks': BlockDef, @@ -208,7 +208,7 @@ def object_export(request, objects, slug): content, content_type = obj.export_for_application() else: etree = obj.export_to_xml(include_id=True) - ET.indent(etree) + indent_xml(etree) content = ET.tostring(etree) content_type = 'text/xml' return HttpResponse(content, content_type=content_type) diff --git a/wcs/backoffice/i18n.py b/wcs/backoffice/i18n.py index 6c1e586ae..1d626dba7 100644 --- a/wcs/backoffice/i18n.py +++ b/wcs/backoffice/i18n.py @@ -15,9 +15,9 @@ # along with this program; if not, see . import io +import xml.etree.ElementTree as ET import zipfile -from lxml import etree as ET from quixote import get_publisher, get_request, get_response, redirect from quixote.directory import Directory from quixote.html import TemplateIO, htmltext @@ -345,8 +345,8 @@ class ExportAfterJob(AfterJob): self.file_name = 'catalog.ods' self.content_type = 'application/vnd.oasis.opendocument.spreadsheet' elif self.file_format == 'xliff': - ET.indent(root) - output.write(ET.tostring(root, encoding='utf-8')) + misc.indent_xml(root) + output.write(ET.tostring(root, 'utf-8')) self.file_name = 'catalog.xliff' self.content_type = 'text/xml' diff --git a/wcs/blocks.py b/wcs/blocks.py index 3573e211b..5a6f19e3b 100644 --- a/wcs/blocks.py +++ b/wcs/blocks.py @@ -18,9 +18,9 @@ import collections import itertools import types import uuid +import xml.etree.ElementTree as ET from contextlib import contextmanager -from lxml import etree as ET from quixote import get_publisher, get_request, get_response from quixote.html import htmltag, htmltext diff --git a/wcs/categories.py b/wcs/categories.py index e7f29c1f8..276c2e9a4 100644 --- a/wcs/categories.py +++ b/wcs/categories.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_publisher from quixote.html import htmltext diff --git a/wcs/custom_views.py b/wcs/custom_views.py index 6337cc62f..7a9d91d5a 100644 --- a/wcs/custom_views.py +++ b/wcs/custom_views.py @@ -15,9 +15,9 @@ # along with this program; if not, see . import urllib.parse +import xml.etree.ElementTree as ET from django.utils.encoding import force_str -from lxml import etree as ET from quixote import get_publisher from wcs.backoffice.data_management import CardPage diff --git a/wcs/data_sources.py b/wcs/data_sources.py index 18318df6e..2cd867a1a 100644 --- a/wcs/data_sources.py +++ b/wcs/data_sources.py @@ -19,11 +19,11 @@ import collections.abc import hashlib import json import urllib.parse +import xml.etree.ElementTree as ET from django.core.cache import cache from django.template import TemplateSyntaxError, VariableDoesNotExist from django.utils.encoding import force_bytes, force_str -from lxml import etree as ET from quixote import get_publisher, get_request, get_session from quixote.errors import RequestError from quixote.html import TemplateIO diff --git a/wcs/fields.py b/wcs/fields.py index ee3ab611d..7c080fc36 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -24,11 +24,11 @@ import random import re import sys import time +import xml.etree.ElementTree as ET from django.utils.encoding import force_bytes, force_str, smart_str from django.utils.formats import date_format as django_date_format from django.utils.html import strip_tags, urlize -from lxml import etree as ET from quixote import get_publisher, get_request, get_session from quixote.html import TemplateIO, htmlescape, htmltag, htmltext diff --git a/wcs/formdef.py b/wcs/formdef.py index 6aff150f4..20972a237 100644 --- a/wcs/formdef.py +++ b/wcs/formdef.py @@ -28,10 +28,10 @@ import sys import time import types import uuid +import xml.etree.ElementTree as ET from operator import itemgetter from django.utils.encoding import force_bytes, force_str -from lxml import etree as ET from quixote import get_publisher, get_session from quixote.html import htmltext from quixote.http_request import Upload diff --git a/wcs/qommon/misc.py b/wcs/qommon/misc.py index 46dacee42..c1a0a1787 100644 --- a/wcs/qommon/misc.py +++ b/wcs/qommon/misc.py @@ -29,6 +29,7 @@ import subprocess import time import unicodedata import urllib.parse +import xml.etree.ElementTree as ET from contextlib import contextmanager import phonenumbers @@ -44,7 +45,6 @@ from django.utils.html import strip_tags from django.utils.safestring import mark_safe from django.utils.text import Truncator from django.utils.timezone import is_aware, make_naive -from lxml import etree as ET from PIL import Image from quixote import get_publisher, get_request, get_response, redirect from quixote.errors import RequestError @@ -558,6 +558,23 @@ def get_foreground_colour(background_colour): return fg_colour +def indent_xml(elem, level=0): + # in-place prettyprint formatter + # http://effbot.org/zone/element-lib.htm#prettyprint + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + for elem in elem: + indent_xml(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + return elem + + def xml_node_text(node): if node is None or node.text is None: return None @@ -1167,7 +1184,7 @@ def xml_response(obj, filename, content_type='text/xml'): etree = obj.export_to_xml(include_id=True) if hasattr(obj, 'get_admin_url'): etree.attrib['url'] = obj.get_admin_url() - ET.indent(etree) + indent_xml(etree) response = get_response() response.set_content_type(content_type) response.set_header('content-disposition', 'attachment; filename=%s' % filename) diff --git a/wcs/qommon/ods.py b/wcs/qommon/ods.py index 4b3ce2d05..50e223e76 100644 --- a/wcs/qommon/ods.py +++ b/wcs/qommon/ods.py @@ -15,10 +15,10 @@ # along with this program; if not, see . import re +import xml.etree.ElementTree as ET import zipfile from django.utils.encoding import force_str -from lxml import etree as ET from .evalutils import make_date, make_datetime from .misc import date_format, datetime_format, strftime @@ -149,10 +149,10 @@ class Workbook: return root def get_styles(self): - return ET.tostring(self.get_styles_node()) + return ET.tostring(self.get_styles_node(), 'utf-8') def get_content(self): - return ET.tostring(self.get_content_node()) + return ET.tostring(self.get_content_node(), 'utf-8') def save(self, output): with zipfile.ZipFile(output, 'w') as z: diff --git a/wcs/qommon/xml_storage.py b/wcs/qommon/xml_storage.py index b16d09a03..4ba44a43f 100644 --- a/wcs/qommon/xml_storage.py +++ b/wcs/qommon/xml_storage.py @@ -15,12 +15,12 @@ # along with this program; if not, see . import datetime +import xml.etree.ElementTree as ET from django.utils.encoding import force_str -from lxml import etree as ET from quixote import get_publisher -from .misc import xml_node_text +from .misc import indent_xml, xml_node_text from .storage import Equal, Or, StorableObject @@ -74,7 +74,7 @@ class XmlStorableObject(StorableObject): def export_to_xml_string(self, include_id=False): x = self.export_to_xml(include_id=include_id) - ET.indent(x) + indent_xml(x) return ET.tostring(x) def export_roles_to_xml(self, element, attribute_name, include_id=False, **kwargs): @@ -88,8 +88,7 @@ class XmlStorableObject(StorableObject): @classmethod def import_from_xml(cls, fd, charset=None, include_id=False): try: - parser = ET.XMLParser(encoding='utf-8', huge_tree=True) - tree = ET.parse(fd, parser=parser) + tree = ET.parse(fd) except Exception: raise ValueError() return cls.import_from_xml_tree(tree, charset=charset, include_id=include_id) diff --git a/wcs/roles.py b/wcs/roles.py index ea5c4e5bc..3c5c9b610 100644 --- a/wcs/roles.py +++ b/wcs/roles.py @@ -16,9 +16,9 @@ import json import urllib.parse +import xml.etree.ElementTree as ET from django.utils.encoding import force_str -from lxml import etree as ET from quixote import get_publisher from quixote.html import htmltext diff --git a/wcs/snapshots.py b/wcs/snapshots.py index 208e3d09b..3aa187884 100644 --- a/wcs/snapshots.py +++ b/wcs/snapshots.py @@ -16,9 +16,9 @@ import difflib import re +import xml.etree.ElementTree as ET from django.utils.timezone import now -from lxml import etree as ET from quixote import get_publisher, get_response, get_session from wcs.qommon import _, misc @@ -29,6 +29,43 @@ class UnknownUser: return str(_('unknown user')) +def indent(tree, space=" ", level=0): + # backport from Lib/xml/etree/ElementTree.py python 3.9 + if isinstance(tree, ET.ElementTree): + tree = tree.getroot() + if level < 0: + raise ValueError(f"Initial indentation level must be >= 0, got {level}") + if len(tree) == 0: + return + + # Reduce the memory consumption by reusing indentation strings. + indentations = ["\n" + level * space] + + def _indent_children(elem, level): + # Start a new indentation level for the first child. + child_level = level + 1 + try: + child_indentation = indentations[child_level] + except IndexError: + child_indentation = indentations[level] + space + indentations.append(child_indentation) + + if not elem.text or not elem.text.strip(): + elem.text = child_indentation + + for child in elem: + if len(child): + _indent_children(child, child_level) + if not child.tail or not child.tail.strip(): + child.tail = child_indentation + + # Dedent after the last child by overwriting the previous indentation. + if not child.tail.strip(): + child.tail = indentations[level] + + _indent_children(tree, 0) + + _no_eol = "\\ No newline at end of file" _hdr_pat = re.compile(r"^@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@$") @@ -121,8 +158,8 @@ class Snapshot: # get patch beetween latest serialization and current instance # indent xml to minimize patch latest_tree = ET.fromstring(latest_complete.serialization) - ET.indent(tree) - ET.indent(latest_tree) + indent(tree) + indent(latest_tree) patch = make_patch(ET.tostring(latest_tree).decode('utf-8'), ET.tostring(tree).decode('utf-8')) # should we store a snapshot ? store_snapshot = True @@ -180,7 +217,7 @@ class Snapshot: return self.serialization tree = ET.fromstring(self.serialization) - ET.indent(tree) + indent(tree) return ET.tostring(tree).decode('utf-8') # get latest version with serialization @@ -188,7 +225,7 @@ class Snapshot: self.object_type, self.object_id, complete=True, max_timestamp=self.timestamp ) latest_tree = ET.fromstring(latest_complete.serialization) - ET.indent(latest_tree) + indent(latest_tree) serialization = apply_patch(ET.tostring(latest_tree).decode('utf-8'), self.patch or '') return serialization diff --git a/wcs/wf/attachment.py b/wcs/wf/attachment.py index ed380fdb3..a2eea9caa 100644 --- a/wcs/wf/attachment.py +++ b/wcs/wf/attachment.py @@ -16,8 +16,8 @@ import os import urllib.parse +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_request, redirect from wcs.forms.common import FileDirectory, FormStatusPage diff --git a/wcs/wf/backoffice_fields.py b/wcs/wf/backoffice_fields.py index e446a7a1d..d9219cd06 100644 --- a/wcs/wf/backoffice_fields.py +++ b/wcs/wf/backoffice_fields.py @@ -15,8 +15,8 @@ # along with this program; if not, see . import copy +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_publisher from quixote.html import htmltext diff --git a/wcs/wf/comment.py b/wcs/wf/comment.py index 215645995..b4cbfe9b8 100644 --- a/wcs/wf/comment.py +++ b/wcs/wf/comment.py @@ -15,9 +15,9 @@ # along with this program; if not, see . import html +import xml.etree.ElementTree as ET from django.utils.html import strip_tags -from lxml import etree as ET from quixote.html import htmltext from wcs.qommon import _ diff --git a/wcs/wf/create_formdata.py b/wcs/wf/create_formdata.py index 5dd25e808..22b3169f5 100644 --- a/wcs/wf/create_formdata.py +++ b/wcs/wf/create_formdata.py @@ -16,9 +16,9 @@ import collections import time +import xml.etree.ElementTree as ET from django.utils.functional import cached_property -from lxml import etree as ET from quixote import get_publisher, get_request, get_session from quixote.html import TemplateIO, htmltext diff --git a/wcs/wf/dispatch.py b/wcs/wf/dispatch.py index 4a6cab624..cf48626b5 100644 --- a/wcs/wf/dispatch.py +++ b/wcs/wf/dispatch.py @@ -15,8 +15,8 @@ # along with this program; if not, see . import collections +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_publisher from quixote.html import htmltext diff --git a/wcs/wf/export_to_model.py b/wcs/wf/export_to_model.py index 79d19bbab..2a635f0bb 100644 --- a/wcs/wf/export_to_model.py +++ b/wcs/wf/export_to_model.py @@ -24,10 +24,10 @@ import subprocess import tempfile import time import zipfile +from xml.etree import ElementTree as ET from django.template.defaultfilters import filesizeformat from django.utils.encoding import force_bytes, force_str -from lxml import etree as ET from quixote import get_publisher, get_request, get_response from quixote.directory import Directory from quixote.errors import PublishError @@ -144,6 +144,19 @@ def transform_opendocument(instream, outstream, process): root = ET.fromstring(content) process(root, new_images) content = ET.tostring(root) + if ( + root.find(f'{{{OO_OFFICE_NS}}}body/{{{OO_OFFICE_NS}}}spreadsheet') + and b'xmlns:of=' not in content + ): + # force xmlns:of namespace inclusion in spreadsheet files, as it may be + # required for proper handling of table:formula attributes. + # (there is no easy way to have ElementTree include namespace declarations + # if there are no elements of that namespace) + content = content.replace( + b':document-content ', + b':document-content xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" ', + 1, + ) zout.writestr(filename, content) for filename in zin.namelist(): diff --git a/wcs/wf/form.py b/wcs/wf/form.py index b99e9eaf7..8a3ffcf3a 100644 --- a/wcs/wf/form.py +++ b/wcs/wf/form.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_publisher, get_session from quixote.html import TemplateIO, htmltext diff --git a/wcs/wf/profile.py b/wcs/wf/profile.py index e2088391c..a7a2baffd 100644 --- a/wcs/wf/profile.py +++ b/wcs/wf/profile.py @@ -18,8 +18,8 @@ import datetime import json import time import urllib.parse +import xml.etree.ElementTree as ET -from lxml import etree as ET from quixote import get_publisher, get_request, get_response from wcs.api_utils import MissingSecret, get_secret_and_orig, sign_url diff --git a/wcs/wf/wscall.py b/wcs/wf/wscall.py index e87d3e30e..585c8f956 100644 --- a/wcs/wf/wscall.py +++ b/wcs/wf/wscall.py @@ -21,9 +21,9 @@ import io import mimetypes import sys import traceback +import xml.etree.ElementTree as ET from django.utils.encoding import force_str -from lxml import etree as ET from quixote import get_publisher, get_request from quixote.html import TemplateIO, htmltext diff --git a/wcs/workflows.py b/wcs/workflows.py index cd097ede0..55bc0fcd5 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -24,12 +24,12 @@ import os import random import time import uuid +import xml.etree.ElementTree as ET from contextlib import contextmanager from importlib import import_module from django.utils.encoding import force_str from django.utils.timezone import is_aware, localtime, make_aware, now -from lxml import etree as ET from quixote import get_publisher, get_request, get_response from quixote.html import TemplateIO, htmltext diff --git a/wcs/wscalls.py b/wcs/wscalls.py index 1dec63adb..4c127a494 100644 --- a/wcs/wscalls.py +++ b/wcs/wscalls.py @@ -17,10 +17,10 @@ import collections import json import urllib.parse +import xml.etree.ElementTree as ET from django.conf import settings from django.utils.encoding import force_str -from lxml import etree as ET from quixote import get_publisher, get_request from wcs.api_utils import MissingSecret, get_secret_and_orig, sign_url