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 1976dc734..ba6ac6cc1 100644 Binary files a/tests/template-out.odt and b/tests/template-out.odt differ 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