Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove lxml #1452

Merged
merged 11 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
testLinux:
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
runs-on: ubuntu-latest
steps:
- name: Python Setup
Expand Down
1 change: 0 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ The following libraries are dependencies of novelWriter:

* [Qt5](https://www.qt.io) by Qt Company
* [PyQt5](https://www.riverbankcomputing.com/software/pyqt) by Riverbank Computing
* [lxml](https://lxml.de) by Martijn Faassen
* [Enchant](https://abiword.github.io/enchant) by Dom Lachowicz
* [PyEnchant](https://pyenchant.github.io/pyenchant) by Dimitri Merejkowsky

Expand Down
5 changes: 1 addition & 4 deletions docs/source/int_source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,10 @@ format of the main project file. Everything else is handled with standard Python
The following Python packages are needed to run novelWriter:

* ``PyQt5`` – needed for connecting with the Qt5 libraries.
* ``lxml`` – needed for full XML support.
* ``PyEnchant`` – needed for spell checking (optional).

PyQt/Qt should be at least 5.10, but ideally 5.13 or higher for all features to work. For instance,
searching using regular expressions with full Unicode support requires 5.13. There is no known
minimum version requirement for package ``lxml``, but the code was originally written with 4.2,
which is therefore set as the minimum. It may work on lower versions. You have to test it.
searching using regular expressions with full Unicode support requires 5.13.

If you want spell checking, you must install the ``PyEnchant`` package. The spell check library
must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine.
Expand Down
17 changes: 6 additions & 11 deletions novelwriter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,14 @@ def main(sysArgs=None):
elif inOpt == "--testmode":
testMode = True

# Set Logging
cHandle = logging.StreamHandler()
cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{"))

# Setup Logging
pkgLogger = logging.getLogger(__package__)
pkgLogger.addHandler(cHandle)
pkgLogger.setLevel(logLevel)
if len(pkgLogger.handlers) == 0:
# Make sure we only create one logger (mostly an issue with tests)
cHandle = logging.StreamHandler()
cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{"))
pkgLogger.addHandler(cHandle)

logger.info("Starting novelWriter %s (%s) %s", __version__, __hexversion__, __date__)

Expand All @@ -184,12 +185,6 @@ def main(sysArgs=None):
)
errorCode |= 0x10

try:
import lxml # noqa: F401
except ImportError:
errorData.append("Python module 'lxml' is missing")
errorCode |= 0x20

if errorData:
errApp = QApplication([])
errDlg = QErrorMessage()
Expand Down
1 change: 0 additions & 1 deletion novelwriter/assets/text/credits_en.htm
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ <h3>Libraries</h3>
<p>
<a href="https://www.qt.io">Qt5</a> by Qt Company<br>
<a href="https://www.riverbankcomputing.com/software/pyqt">PyQt5</a> by Riverbank Computing<br>
<a href="https://lxml.de">lxml</a> by Martijn Faassen<br>
<a href="https://abiword.github.io/enchant">Enchant</a> by Dom Lachowicz<br>
<a href="https://pyenchant.github.io/pyenchant">PyEnchant</a> by Dimitri Merejkowsky
</p>
Expand Down
42 changes: 42 additions & 0 deletions novelwriter/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from __future__ import annotations

import json
import uuid
import hashlib
import logging
import xml.etree.ElementTree as ET

from pathlib import Path
from datetime import datetime
Expand Down Expand Up @@ -435,6 +438,45 @@ def jsonEncode(data, n=0, nmax=0):
return "".join(buffer)


def xmlIndent(tree: ET.Element | ET.ElementTree):
"""A modified version of the XML indent function in the standard
library. It behaves more closely to how the one from lxml does.
"""
if isinstance(tree, ET.ElementTree):
tree = tree.getroot()

indentations = ["\n"]

def indentChildren(elem, level):
chLevel = level + 1
try:
chIndent = indentations[chLevel]
except IndexError:
chIndent = indentations[level] + " "
indentations.append(chIndent)

if elem.text is None:
elem.text = chIndent

last = None
for child in elem:
if len(child):
indentChildren(child, chLevel)
if child.tail is None:
child.tail = chIndent
last = child

# Dedent the last child
if last is not None:
last.tail = indentations[level]

if len(tree):
indentChildren(tree, 0)
tree.tail = "\n"

return


# =============================================================================================== #
# File and File System Functions
# =============================================================================================== #
Expand Down
1 change: 0 additions & 1 deletion novelwriter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ def __init__(self):

# Check Python Version
self.verPyString = sys.version.split()[0]
self.verPyHexVal = sys.hexversion

# Check OS Type
self.osType = sys.platform
Expand Down
38 changes: 19 additions & 19 deletions novelwriter/core/projectxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@
"""

import logging
import xml.etree.ElementTree as ET

from enum import Enum
from lxml import etree
from time import time
from pathlib import Path

from novelwriter import __version__, __hexversion__
from novelwriter.common import (
checkBool, checkInt, checkString, checkStringNone, formatTimeStamp,
hexToInt, simplified, yesNo
hexToInt, simplified, xmlIndent, yesNo
)
from novelwriter.constants import nwFiles

Expand Down Expand Up @@ -163,7 +163,7 @@ def read(self, projData, projContent):
logger.debug("Reading project XML")

try:
xml = etree.parse(str(self._path))
xml = ET.parse(str(self._path))
self._state = XMLReadState.NO_ERROR

except Exception as exc:
Expand All @@ -174,7 +174,7 @@ def read(self, projData, projContent):
backFile = self._path.with_suffix(".bak")
if backFile.is_file():
try:
xml = etree.parse(str(backFile))
xml = ET.parse(str(backFile))
self._state = XMLReadState.PARSED_BACKUP
logger.info("Backup project file parsed")
except Exception as exc:
Expand Down Expand Up @@ -500,7 +500,7 @@ def write(self, projData, projContent, saveTime, editTime):
tStart = time()
logger.debug("Writing project XML")

xRoot = etree.Element("novelWriterXML", attrib={
xRoot = ET.Element("novelWriterXML", attrib={
"appVersion": str(__version__),
"hexVersion": str(__hexversion__),
"fileVersion": FILE_VERSION,
Expand All @@ -515,13 +515,13 @@ def write(self, projData, projContent, saveTime, editTime):
"editTime": str(editTime),
}

xProject = etree.SubElement(xRoot, "project", attrib=projAttr)
xProject = ET.SubElement(xRoot, "project", attrib=projAttr)
self._packSingleValue(xProject, "name", projData.name)
self._packSingleValue(xProject, "title", projData.title)
self._packSingleValue(xProject, "author", projData.author)

# Save Project Settings
xSettings = etree.SubElement(xRoot, "settings")
xSettings = ET.SubElement(xRoot, "settings")
self._packSingleValue(xSettings, "doBackup", yesNo(projData.doBackup))
self._packSingleValue(xSettings, "language", projData.language)
self._packSingleValue(xSettings, "spellChecking", projData.spellLang, attrib={
Expand All @@ -532,11 +532,11 @@ def write(self, projData, projContent, saveTime, editTime):
self._packDictKeyValue(xSettings, "titleFormat", projData.titleFormat)

# Save Status/Importance
xStatus = etree.SubElement(xSettings, "status")
xStatus = ET.SubElement(xSettings, "status")
for label, attrib in projData.itemStatus.pack():
self._packSingleValue(xStatus, "entry", label, attrib=attrib)

xImport = etree.SubElement(xSettings, "importance")
xImport = ET.SubElement(xSettings, "importance")
for label, attrib in projData.itemImport.pack():
self._packSingleValue(xImport, "entry", label, attrib=attrib)

Expand All @@ -547,21 +547,21 @@ def write(self, projData, projContent, saveTime, editTime):
"notesWords": str(projData.currCounts[1]),
}

xContent = etree.SubElement(xRoot, "content", attrib=contAttr)
xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
for item in projContent:
xItem = etree.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
etree.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = etree.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xItem = ET.SubElement(xContent, "item", attrib=item.get("itemAttr", {}))
ET.SubElement(xItem, "meta", attrib=item.get("metaAttr", {}))
xName = ET.SubElement(xItem, "name", attrib=item.get("nameAttr", {}))
xName.text = item["name"]

# Write the XML tree to file
saveFile = self._path / nwFiles.PROJ_FILE
tempFile = saveFile.with_suffix(".tmp")
backFile = saveFile.with_suffix(".bak")
try:
tempFile.write_bytes(etree.tostring(
xRoot, pretty_print=True, encoding="utf-8", xml_declaration=True
))
xml = ET.ElementTree(xRoot)
xmlIndent(xml)
xml.write(tempFile, encoding="utf-8", xml_declaration=True)
except Exception as exc:
self._error = exc
return False
Expand All @@ -587,17 +587,17 @@ def write(self, projData, projContent, saveTime, editTime):
def _packSingleValue(self, xParent, name, value, attrib=None):
"""Pack a single value into an XML element.
"""
xItem = etree.SubElement(xParent, name, attrib=attrib)
xItem = ET.SubElement(xParent, name, attrib=attrib or {})
xItem.text = str(value) or ""
return

def _packDictKeyValue(self, xParent, name, data):
"""Pack the entries of a dictionary into an XML element.
"""
xItem = etree.SubElement(xParent, name)
xItem = ET.SubElement(xParent, name)
for key, value in data.items():
if len(key) > 0:
xEntry = etree.SubElement(xItem, "entry", attrib={"key": key})
xEntry = ET.SubElement(xItem, "entry", attrib={"key": key})
xEntry.text = str(value) or ""
return

Expand Down
Loading