diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e48034e..14b4627 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,9 +5,6 @@ on: tags: [v*] workflow_dispatch: -env: - MACOSX_DEPLOYMENT_TARGET: '12.0' - jobs: build_wheels: runs-on: ${{matrix.os}} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 890008a..4dbe60e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,16 +7,13 @@ on: pull_request: workflow_dispatch: -env: - MACOSX_DEPLOYMENT_TARGET: '12.0' - jobs: test: runs-on: ${{matrix.os}} strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, windows-latest, macos-14] python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d9939..505e61e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Changed +- Bump pugixml from 1.15 to 1.16 ([#200]) + - Add `pugixml.pugi.XMLNode.ensure_attribute(name: str)` + - Add `pugixml.pugi.XMLNode.ensure_child(name: str)` - Bump pybind11 from 2.13.6 to 3.0.4 ([#141], [#159], [#198]) ### Added @@ -136,3 +139,4 @@ Initial release. [#154]: https://github.com/miute/pugixml-python/pull/154 [#159]: https://github.com/miute/pugixml-python/pull/159 [#198]: https://github.com/miute/pugixml-python/pull/198 +[#200]: https://github.com/miute/pugixml-python/pull/200 diff --git a/CMakeLists.txt b/CMakeLists.txt index d84789e..f546c85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,13 @@ cmake_policy(SET CMP0074 NEW) project(pugi LANGUAGES CXX) +if(APPLE) + set(CMAKE_OSX_DEPLOYMENT_TARGET "14.5" CACHE STRING "Minimum macOS version" FORCE) + set(PUGIXML_CHARCONV_FLOAT OFF) # PUGIXML_CHARCONV_FLOAT requires Xcode 26 or later +else() + set(PUGIXML_CHARCONV_FLOAT ON) +endif() + set(PYBIND11_FINDPYTHON ON) set(DESTDIR pugixml CACHE STRING "The directory where the binaries will be written. (e.g., path/to/pugixml-python/src/pugixml)") message(STATUS "DESTDIR: ${DESTDIR}") diff --git a/src/main.cpp b/src/main.cpp index 01fc771..62ab44f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1048,6 +1048,39 @@ PYBIND11_MODULE(MODULE_NAME, m) { bool: :obj:`True` if node is empty, :obj:`False` otherwise. )doc"); + node.def("ensure_attribute", py::overload_cast(&xml_node::ensure_attribute), + py::arg("name").none(false), + R"doc( + Return the attribute with the specified name. + + If the attribute with the specified name does not exist, it will be created. + + Args: + name (str): The name of the attribute. + + Returns: + XMLAttribute: The attribute with the specified name, or empty attribute if error occurs. + + See Also: + :meth:`.attribute` + )doc"); + + node.def("ensure_child", py::overload_cast(&xml_node::ensure_child), py::arg("name").none(false), + R"doc( + Return the child node with the specified name. + + If the child node with the specified name does not exist, it will be created as an element. + + Args: + name (str): The name of the child node. + + Returns: + XMLNode: The child node with the specified name, or empty node if error occurs. + + See Also: + :meth:`.child` + )doc"); + node.def("type", &xml_node::type, R"doc( Return the node type. @@ -1176,6 +1209,9 @@ PYBIND11_MODULE(MODULE_NAME, m) { Returns: XMLNode: The first node found, or empty node if not exists. + + See Also: + :meth:`.ensure_child` )doc"); node.def("attribute", py::overload_cast(&xml_node::attribute, py::const_), @@ -1190,6 +1226,8 @@ PYBIND11_MODULE(MODULE_NAME, m) { "attribute after the one found, or with an empty attribute if not found.\n\n" "Returns:\n" " XMLAttribute: The first attribute found, or empty attribute if not exists.\n\n" + "See Also:\n" + " :meth:`.ensure_attribute`\n\n" "Examples:\n" " >>> from pugixml import pugi\n" " >>> doc = pugi.XMLDocument()\n" diff --git a/src/third_party/pugixml b/src/third_party/pugixml index ee86beb..c8033ce 160000 --- a/src/third_party/pugixml +++ b/src/third_party/pugixml @@ -1 +1 @@ -Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38 +Subproject commit c8033ce9d039e7f9d134877c363397b3cfe20816 diff --git a/tests/test_attribute.py b/tests/test_attribute.py index 02453fa..762a749 100644 --- a/tests/test_attribute.py +++ b/tests/test_attribute.py @@ -240,6 +240,35 @@ def test_empty() -> None: assert not node.attribute("attr2").empty() +def test_ensure_attribute() -> None: + doc = pugi.XMLDocument() + doc.load_string("") + node = doc.child("node") + + a1 = node.ensure_attribute("a1") + assert isinstance(a1, pugi.XMLAttribute) + assert not a1.empty() + assert a1 == node.attribute("a1") + assert a1.value() == "v1" + + a2 = node.ensure_attribute("a2") + assert isinstance(a2, pugi.XMLAttribute) + assert not a2.empty() + assert a2 != a1 + assert a2.set_value("v2") + + a3 = node.child("child").ensure_attribute("a3") + assert isinstance(a3, pugi.XMLAttribute) + assert not a3.empty() + assert a3 != a2 + assert a3 != a1 + assert a3.set_value("v3") + + writer = pugi.StringWriter() + doc.print(writer, flags=pugi.FORMAT_RAW) + assert writer.getvalue() == '' + + def test_hash_value() -> None: doc = pugi.XMLDocument() doc.load_string("") diff --git a/tests/test_node.py b/tests/test_node.py index de008f5..d25cfb3 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -539,6 +539,29 @@ def test_empty() -> None: assert not doc.child("node").empty() +def test_ensure_child() -> None: + doc = pugi.XMLDocument() + doc.load_string("foo") + node = doc.child("node") + + child = node.ensure_child("child") + assert isinstance(child, pugi.XMLNode) + assert not child.empty() + assert child == node.child("child") + + n1 = node.ensure_child("n1") + assert isinstance(n1, pugi.XMLNode) + assert not n1.empty() + + n2 = doc.ensure_child("n2") + assert isinstance(n2, pugi.XMLNode) + assert not n2.empty() + + writer = pugi.StringWriter() + doc.print(writer, flags=pugi.FORMAT_RAW) + assert writer.getvalue() == "foo" + + def test_file_writer() -> None: doc = pugi.XMLDocument() doc.load_string("\U0001f308")