Skip to content
Open
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
7 changes: 7 additions & 0 deletions Mergin/project_settings_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
QgsMapLayer,
QgsVectorLayer,
QgsFieldProxyModel,
Qgis,
)
from qgis.gui import (
QgsOptionsWidgetFactory,
Expand Down Expand Up @@ -51,6 +52,7 @@
excluded_layers_list,
get_fields_for_checkbox,
)
from .qgis_properties_version_4 import read_mergin_properties, is_qgis_version_4

ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "ui", "ui_project_config.ui")
ProjectConfigUiWidget, _ = uic.loadUiType(ui_file)
Expand Down Expand Up @@ -82,6 +84,11 @@ def __init__(self, parent=None):
QgsOptionsPageWidget.__init__(self, parent)
self.setupUi(self)

if Qgis.versionInt() < 40000 and is_qgis_version_4(QgsProject.instance().fileName()):
props = read_mergin_properties(QgsProject.instance().fileName())
for key, value in props.items():
QgsProject.instance().writeEntry("Mergin", key, value)

self.cmb_photo_quality.addItem("Original", 0)
self.cmb_photo_quality.addItem("High (approx. 2-4 Mb)", 1)
self.cmb_photo_quality.addItem("Medium (approx. 1-2 Mb)", 2)
Expand Down
78 changes: 78 additions & 0 deletions Mergin/qgis_properties_version_4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import xml.etree.ElementTree as ET # nosec B405
from xml.etree.ElementTree import Element # nosec B405
from typing import Dict
import zipfile


def _read_xml_tree_from_project(project_path: str) -> Element:
if project_path.endswith(".qgs"):
with open(project_path, "r", encoding="utf-8") as f:
return ET.parse(f).getroot() # nosec B314
elif project_path.endswith(".qgz"):
return _read_xml_tree_from_qgz(project_path)
else:
raise ValueError(f"Unsupported project file format: {project_path}")


def _read_xml_tree_from_qgz(qgz_path: str) -> Element:
qgs_filename = next(
(name for name in zipfile.ZipFile(qgz_path).namelist() if name.endswith(".qgs")),
None,
)

if qgs_filename is None:
raise ValueError(f"No .qgs file found inside {qgz_path}")

with zipfile.ZipFile(qgz_path, "r") as input_zip_file:
entries = {name: input_zip_file.read(name) for name in input_zip_file.namelist()}

return ET.fromstring(entries[qgs_filename]) # nosec B314


def is_qgis_version_4(project_file: str) -> bool:
root = _read_xml_tree_from_project(project_file)

version = root.attrib.get("version", "")
if not version.startswith("4."):
return False

return True


def _parse_properties(element, prefix="") -> Dict:
"""Recursively parse nested <properties> elements into a flat dict with path keys."""
result = {}

for child in element:
if child.tag != "properties":
continue

name = child.attrib.get("name", "")
key = f"{prefix}/{name}" if prefix else name
prop_type = child.attrib.get("type")

if prop_type is not None:
if prop_type == "QStringList":
result[key] = [v.text for v in child.findall("value")]
else:
result[key] = child.text
else:
result.update(_parse_properties(child, prefix=key))

return result


def read_mergin_properties(project_file: str) -> Dict:
root = _read_xml_tree_from_project(project_file)

version = root.attrib.get("version", "")
if not version.startswith("4."):
return {}

mergin_elem = root.find(".//properties[@name='Mergin']")
if mergin_elem is None:
return {}

props = _parse_properties(mergin_elem)

return props
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,9 @@ def layer_field_filter(test_data_path: Path) -> QgsVectorLayer:
layer = QgsVectorLayer(str(test_data_path / "data_field_filter.gpkg"), "field filter layer", "ogr")
assert layer.isValid()
return layer


@pytest.fixture
def qgis_project_4_path(test_data_path: Path) -> Path:
"""Fixture for a QGIS project file created with QGIS 4.x."""
return test_data_path / "test_variables_4.0.qgz"
Binary file added tests/data/test_variables_4.0.qgz
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/test_qgis_properties_version_4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-

# GPLv3 license
# Copyright Lutra Consulting Limited

from pathlib import Path

from Mergin.qgis_properties_version_4 import is_qgis_version_4, read_mergin_properties


def test_is_qgis_version_4(qgis_project_4_path: Path):
assert is_qgis_version_4(str(qgis_project_4_path))


def test_read_mergin_properties(qgis_project_4_path: Path):

mergin_properties = read_mergin_properties(str(qgis_project_4_path))

assert isinstance(mergin_properties, dict)
assert len(mergin_properties) > 0
Loading