diff -Nru libxmlb-0.1.15/contrib/ci/build-debian.sh libxmlb-0.3.6/contrib/ci/build-debian.sh --- libxmlb-0.1.15/contrib/ci/build-debian.sh 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/build-debian.sh 2021-12-06 20:36:37.000000000 +0000 @@ -2,7 +2,7 @@ set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build -rm * -rf +rm -rf * meson .. \ -Dgtkdoc=true \ -Dtests=true $@ diff -Nru libxmlb-0.1.15/contrib/ci/build-fedora.sh libxmlb-0.3.6/contrib/ci/build-fedora.sh --- libxmlb-0.1.15/contrib/ci/build-fedora.sh 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/build-fedora.sh 2021-12-06 20:36:37.000000000 +0000 @@ -2,7 +2,7 @@ set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build -rm * -rf +rm -rf * meson .. \ -Db_coverage=true \ -Dgtkdoc=true \ diff -Nru libxmlb-0.1.15/contrib/ci/build-fedora-w64.sh libxmlb-0.3.6/contrib/ci/build-fedora-w64.sh --- libxmlb-0.1.15/contrib/ci/build-fedora-w64.sh 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/build-fedora-w64.sh 2021-12-06 20:36:37.000000000 +0000 @@ -2,7 +2,7 @@ set -e export LC_ALL=C.UTF-8 mkdir -p build && cd build -rm * -rf +rm -rf * meson .. \ --cross-file=../contrib/mingw64.cross \ -Dintrospection=false \ diff -Nru libxmlb-0.1.15/contrib/ci/Dockerfile-debian libxmlb-0.3.6/contrib/ci/Dockerfile-debian --- libxmlb-0.1.15/contrib/ci/Dockerfile-debian 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/Dockerfile-debian 2021-12-06 20:36:37.000000000 +0000 @@ -14,6 +14,7 @@ python3-setuptools \ python3-wheel \ shared-mime-info \ + liblzma-dev \ uuid-dev \ pkg-config diff -Nru libxmlb-0.1.15/contrib/ci/Dockerfile-fedora libxmlb-0.3.6/contrib/ci/Dockerfile-fedora --- libxmlb-0.1.15/contrib/ci/Dockerfile-fedora 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/Dockerfile-fedora 2021-12-06 20:36:37.000000000 +0000 @@ -4,6 +4,7 @@ RUN dnf -y install \ diffutils \ gcovr \ + xz-devel \ git-core \ glib2-devel \ gobject-introspection-devel \ diff -Nru libxmlb-0.1.15/contrib/ci/Dockerfile-fedora-w64 libxmlb-0.3.6/contrib/ci/Dockerfile-fedora-w64 --- libxmlb-0.1.15/contrib/ci/Dockerfile-fedora-w64 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/ci/Dockerfile-fedora-w64 2021-12-06 20:36:37.000000000 +0000 @@ -6,6 +6,7 @@ gcc \ git-core \ meson \ + mingw64-xz-libs \ mingw64-gcc \ mingw64-glib2 \ mingw64-pkg-config \ diff -Nru libxmlb-0.1.15/contrib/libxmlb.spec.in libxmlb-0.3.6/contrib/libxmlb.spec.in --- libxmlb-0.1.15/contrib/libxmlb.spec.in 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/contrib/libxmlb.spec.in 2021-12-06 20:36:37.000000000 +0000 @@ -15,19 +15,16 @@ BuildRequires: libstemmer-devel BuildRequires: meson BuildRequires: gobject-introspection-devel +BuildRequires: xz-devel %if 0%{?rhel} == 7 BuildRequires: python36-setuptools %else -BuildRequires: python-setuptools +BuildRequires: python3-setuptools %endif # needed for the self tests BuildRequires: shared-mime-info -%if 0%{?rhel} < 8 -BuildRequires: python34 -%endif - Requires: glib2%{?_isa} >= %{glib2_version} Requires: shared-mime-info @@ -76,24 +73,25 @@ %license LICENSE %{_libexecdir}/xb-tool %dir %{_libdir}/girepository-1.0 -%{_libdir}/girepository-1.0/*.typelib -%{_libdir}/libxmlb.so.1* +%{_libdir}/girepository-1.0/Xmlb-2.0.typelib +%{_libdir}/libxmlb.so.2* %files devel %dir %{_datadir}/gir-1.0 -%{_datadir}/gir-1.0/*.gir +%{_datadir}/gir-1.0/Xmlb-2.0.gir %dir %{_datadir}/gtk-doc %dir %{_datadir}/gtk-doc/html %{_datadir}/gtk-doc/html/libxmlb -%{_includedir}/libxmlb-1 +%{_includedir}/libxmlb-2 %{_libdir}/libxmlb.so %{_libdir}/pkgconfig/xmlb.pc %files tests +%dir %{_libexecdir}/installed-tests/libxmlb %{_libexecdir}/installed-tests/libxmlb/xb-self-test -%{_datadir}/installed-tests/libxmlb/libxmlb.test -%{_datadir}/installed-tests/libxmlb/test.xml.gz.gz.gz +%{_libexecdir}/installed-tests/libxmlb/test.xml.gz.gz.gz %dir %{_datadir}/installed-tests/libxmlb +%{_datadir}/installed-tests/libxmlb/libxmlb.test %changelog * #LONGDATE# Richard Hughes #VERSION#-0.#BUILD##ALPHATAG# diff -Nru libxmlb-0.1.15/data/meson.build libxmlb-0.3.6/data/meson.build --- libxmlb-0.1.15/data/meson.build 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/data/meson.build 2021-12-06 20:36:37.000000000 +0000 @@ -8,6 +8,6 @@ ) install_data(['test.xml.gz.gz.gz'], - install_dir: installed_test_datadir, + install_dir: installed_test_bindir, ) endif Binary files /tmp/tmpmlbi27om/aFGzx9yrCZ/libxmlb-0.1.15/data/test.xml.xz and /tmp/tmpmlbi27om/rnJ6EBHSBM/libxmlb-0.3.6/data/test.xml.xz differ diff -Nru libxmlb-0.1.15/debian/changelog libxmlb-0.3.6/debian/changelog --- libxmlb-0.1.15/debian/changelog 2021-01-25 16:05:07.000000000 +0000 +++ libxmlb-0.3.6/debian/changelog 2022-08-02 07:04:04.000000000 +0000 @@ -1,16 +1,70 @@ -libxmlb (0.1.15-2ubuntu1~20.04.1) focal; urgency=medium +libxmlb (0.3.6-2build1~20.04.1) focal; urgency=medium - * No-change backport from hirsute to focal + * Backport to focal (no changes). + * Upgrade to support EM120/160 firmware update (LP: #1980334) - -- Iain Lane Mon, 25 Jan 2021 16:05:07 +0000 + -- Yuan-Chen Cheng Tue, 02 Aug 2022 07:04:04 +0000 -libxmlb (0.1.15-2ubuntu1) hirsute; urgency=medium +libxmlb (0.3.6-2build1) jammy; urgency=high - * d/p/xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch: - Cherry-pick upstream patch to fix a deadlock. Forwarded to Debian at - https://salsa.debian.org/efi-team/libxmlb/-/merge_requests (LP: #1890313) + * No change rebuild for ppc64el baseline bump. - -- Iain Lane Mon, 25 Jan 2021 16:01:47 +0000 + -- Julian Andres Klode Thu, 24 Mar 2022 17:15:30 +0100 + +libxmlb (0.3.6-2) unstable; urgency=medium + + * Add dependency on liblzma-dev to dev package + - This fulfills the private pkgconf requirement on liblzma + + -- Matthias Klumpp Thu, 16 Dec 2021 22:35:28 +0100 + +libxmlb (0.3.6-1) unstable; urgency=medium + + * New upstream bugfix release: 0.3.6 + + -- Matthias Klumpp Wed, 08 Dec 2021 14:42:14 +0100 + +libxmlb (0.3.5-1) unstable; urgency=medium + + * New upstream release: 0.3.5 + * Bump standards version (no changes needed) + * Add build dependency on liblzma + * Update symbols file + + -- Matthias Klumpp Thu, 02 Dec 2021 22:38:52 +0100 + +libxmlb (0.3.2-1) unstable; urgency=medium + + [ Matthias Klumpp ] + * Upload to unstable + * New upstream release: 0.3.2 + * d/copyright: Use secure URL + * Update symbols file + + [ Will Thompson ] + * debian/control: fix typo + + -- Matthias Klumpp Sat, 21 Aug 2021 19:05:36 +0200 + +libxmlb (0.2.1-1) experimental; urgency=medium + + * New upstream release: 0.2.1 + * Target experimental, as we are in transition freeze and the + previous 0.2.0 release was never uploaded and would trigger + a transition. + * Add gbp.conf + * Drop all patches: Applied upstream + + -- Matthias Klumpp Fri, 22 Jan 2021 18:06:10 +0100 + +libxmlb (0.2.0-1) unstable; urgency=medium + + * New upstream version. + * drop existing patches, now upstream + * Backport new patches that fix ABI mistake in 0.2.0 + * Update packages for soname bump that broke ABI + + -- Mario Limonciello Tue, 18 Aug 2020 14:31:01 -0500 libxmlb (0.1.15-2) unstable; urgency=medium diff -Nru libxmlb-0.1.15/debian/control libxmlb-0.3.6/debian/control --- libxmlb-0.1.15/debian/control 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/control 2022-08-02 07:02:28.000000000 +0000 @@ -1,29 +1,31 @@ Source: libxmlb Section: libs Priority: optional -Maintainer: Debian EFI team +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian EFI team Uploaders: Steve McIntyre <93sam@debian.org>, Matthias Klumpp , Mario Limonciello -Build-Depends: debhelper-compat (=12), - gir1.2-glib-2.0, - gobject-introspection, - gtk-doc-tools, - libglib2.0-dev, - libglib2.0-doc, - libgirepository1.0-dev, - meson (>= 0.52.0), - shared-mime-info, -Standards-Version: 4.5.0 +Build-Depends: debhelper-compat (= 12), + gir1.2-glib-2.0, + gobject-introspection, + gtk-doc-tools, + libgirepository1.0-dev, + libglib2.0-dev, + libglib2.0-doc, + liblzma-dev, + meson (>= 0.52.0), + shared-mime-info +Standards-Version: 4.6.0 Homepage: https://github.com/hughsie/libxmlb Vcs-Git: https://salsa.debian.org/efi-team/libxmlb.git Vcs-Browser: https://salsa.debian.org/efi-team/libxmlb -Package: gir1.2-xmlb-1.0 +Package: gir1.2-xmlb-2.0 Architecture: any Multi-Arch: same -Depends: ${misc:Depends}, - ${gir:Depends} +Depends: ${gir:Depends}, + ${misc:Depends} Section: introspection Description: GObject introspection data for libxmlb This package provides the introspection data for libxmlb. @@ -36,9 +38,12 @@ Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} -Depends: ${misc:Depends}, ${shlibs:Depends}, gir1.2-xmlb-1.0 (= ${binary:Version}) +Depends: gir1.2-xmlb-2.0 (= ${binary:Version}), + liblzma-dev, + ${misc:Depends}, + ${shlibs:Depends} Description: development files for libxmlb - This package provides development libraryes for libxmlb. + This package provides development libraries for libxmlb. . The libxmlb library takes XML source, and converts it to a structured binary representation with a deduplicated string @@ -48,12 +53,12 @@ return some strings without actually parsing the entire document. This is all done using (almost) zero allocations and no actual copying of the binary data. -Package: libxmlb1 -Section: libs +Package: libxmlb2 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} -Depends: ${misc:Depends}, ${shlibs:Depends} +Depends: ${misc:Depends}, + ${shlibs:Depends} Description: Binary XML library The libxmlb library takes XML source, and converts it to a structured binary representation with a deduplicated string @@ -65,11 +70,11 @@ Package: libxmlb-tests Architecture: any -Depends: libxmlb1 (= ${binary:Version}), +Depends: libxmlb2 (= ${binary:Version}), shared-mime-info, ${misc:Depends}, ${shlibs:Depends} -Description: Installed tests for libxmlb1 +Description: Installed tests for libxmlb2 The libxmlb library takes XML source, and converts it to a structured binary representation with a deduplicated string table -- where the strings have the NULs included. diff -Nru libxmlb-0.1.15/debian/copyright libxmlb-0.3.6/debian/copyright --- libxmlb-0.1.15/debian/copyright 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/copyright 2021-12-08 12:41:00.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: libxmlb Source: diff -Nru libxmlb-0.1.15/debian/gbp.conf libxmlb-0.3.6/debian/gbp.conf --- libxmlb-0.1.15/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/debian/gbp.conf 2021-12-08 12:41:00.000000000 +0000 @@ -0,0 +1,14 @@ +[DEFAULT] +pristine-tar = True +debian-branch = debian/master +upstream-branch = upstream/latest + +[buildpackage] +sign-tags = True + +[dch] +multimaint-merge = True + +[import-orig] +postimport = dch -v%(version)s New upstream release; git add debian/changelog; debcommit +upstream-vcs-tag = %(version)s diff -Nru libxmlb-0.1.15/debian/gir1.2-xmlb-1.0.install libxmlb-0.3.6/debian/gir1.2-xmlb-1.0.install --- libxmlb-0.1.15/debian/gir1.2-xmlb-1.0.install 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/gir1.2-xmlb-1.0.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/*/girepository-1.0 diff -Nru libxmlb-0.1.15/debian/gir1.2-xmlb-2.0.install libxmlb-0.3.6/debian/gir1.2-xmlb-2.0.install --- libxmlb-0.1.15/debian/gir1.2-xmlb-2.0.install 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/debian/gir1.2-xmlb-2.0.install 2021-12-08 12:41:00.000000000 +0000 @@ -0,0 +1 @@ +usr/lib/*/girepository-1.0 diff -Nru libxmlb-0.1.15/debian/libxmlb1.install libxmlb-0.3.6/debian/libxmlb1.install --- libxmlb-0.1.15/debian/libxmlb1.install 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/libxmlb1.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/lib/*/*.so.* diff -Nru libxmlb-0.1.15/debian/libxmlb1.symbols libxmlb-0.3.6/debian/libxmlb1.symbols --- libxmlb-0.1.15/debian/libxmlb1.symbols 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/libxmlb1.symbols 1970-01-01 00:00:00.000000000 +0000 @@ -1,177 +0,0 @@ -libxmlb.so.1 libxmlb1 #MINVER# -* Build-Depends-Package: libxmlb-dev - LIBXMLB_0.1.0@LIBXMLB_0.1.0 0.1.0 - LIBXMLB_0.1.11@LIBXMLB_0.1.11 0.1.11 - LIBXMLB_0.1.12@LIBXMLB_0.1.12 0.1.12 - LIBXMLB_0.1.13@LIBXMLB_0.1.13 0.1.13 - LIBXMLB_0.1.15@LIBXMLB_0.1.15 0.1.15 - LIBXMLB_0.1.1@LIBXMLB_0.1.1 0.1.1 - LIBXMLB_0.1.2@LIBXMLB_0.1.2 0.1.2 - LIBXMLB_0.1.3@LIBXMLB_0.1.3 0.1.3 - LIBXMLB_0.1.4@LIBXMLB_0.1.4 0.1.4 - LIBXMLB_0.1.5@LIBXMLB_0.1.5 0.1.5 - LIBXMLB_0.1.6@LIBXMLB_0.1.6 0.1.6 - LIBXMLB_0.1.7@LIBXMLB_0.1.7 0.1.7 - xb_builder_add_fixup@LIBXMLB_0.1.3 0.1.3 - xb_builder_add_locale@LIBXMLB_0.1.0 0.1.0 - xb_builder_append_guid@LIBXMLB_0.1.0 0.1.0 - xb_builder_compile@LIBXMLB_0.1.0 0.1.0 - xb_builder_ensure@LIBXMLB_0.1.0 0.1.0 - xb_builder_fixup_get_max_depth@LIBXMLB_0.1.3 0.1.3 - xb_builder_fixup_get_type@LIBXMLB_0.1.3 0.1.3 - xb_builder_fixup_new@LIBXMLB_0.1.3 0.1.3 - xb_builder_fixup_set_max_depth@LIBXMLB_0.1.3 0.1.3 - xb_builder_get_type@LIBXMLB_0.1.0 0.1.0 - xb_builder_import_node@LIBXMLB_0.1.0 0.1.0 - xb_builder_import_source@LIBXMLB_0.1.0 0.1.0 - xb_builder_new@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_add_child@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_add_flag@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_depth@LIBXMLB_0.1.1 0.1.1 - xb_builder_node_export@LIBXMLB_0.1.5 0.1.5 - xb_builder_node_get_attr@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_get_attr_as_uint@LIBXMLB_0.1.3 0.1.3 - xb_builder_node_get_child@LIBXMLB_0.1.1 0.1.1 - xb_builder_node_get_children@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_get_element@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_get_first_child@LIBXMLB_0.1.12 0.1.12 - xb_builder_node_get_last_child@LIBXMLB_0.1.12 0.1.12 - xb_builder_node_get_parent@LIBXMLB_0.1.1 0.1.1 - xb_builder_node_get_tail@LIBXMLB_0.1.12 0.1.12 - xb_builder_node_get_text@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_get_text_as_uint@LIBXMLB_0.1.3 0.1.3 - xb_builder_node_get_type@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_has_flag@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_insert@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_insert_text@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_new@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_remove_attr@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_remove_child@LIBXMLB_0.1.1 0.1.1 - xb_builder_node_set_attr@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_set_element@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_set_tail@LIBXMLB_0.1.12 0.1.12 - xb_builder_node_set_text@LIBXMLB_0.1.0 0.1.0 - xb_builder_node_sort_children@LIBXMLB_0.1.3 0.1.3 - xb_builder_node_traverse@LIBXMLB_0.1.1 0.1.1 - xb_builder_node_unlink@LIBXMLB_0.1.1 0.1.1 - xb_builder_set_profile_flags@LIBXMLB_0.1.1 0.1.1 - xb_builder_source_add_adapter@LIBXMLB_0.1.7 0.1.7 - xb_builder_source_add_converter@LIBXMLB_0.1.1 0.1.1 - xb_builder_source_add_fixup@LIBXMLB_0.1.3 0.1.3 - xb_builder_source_add_node_func@LIBXMLB_0.1.0 0.1.0 - xb_builder_source_add_simple_adapter@LIBXMLB_0.1.15 0.1.15 - xb_builder_source_ctx_get_bytes@LIBXMLB_0.1.7 0.1.7 - xb_builder_source_ctx_get_filename@LIBXMLB_0.1.7 0.1.7 - xb_builder_source_ctx_get_stream@LIBXMLB_0.1.7 0.1.7 - xb_builder_source_ctx_get_type@LIBXMLB_0.1.7 0.1.7 - xb_builder_source_get_type@LIBXMLB_0.1.0 0.1.0 - xb_builder_source_load_bytes@LIBXMLB_0.1.2 0.1.3 - xb_builder_source_load_file@LIBXMLB_0.1.1 0.1.1 - xb_builder_source_load_xml@LIBXMLB_0.1.1 0.1.1 - xb_builder_source_new@LIBXMLB_0.1.1 0.1.1 - xb_builder_source_set_info@LIBXMLB_0.1.0 0.1.0 - xb_builder_source_set_prefix@LIBXMLB_0.1.0 0.1.10 - xb_machine_add_method@LIBXMLB_0.1.1 0.1.1 - xb_machine_add_opcode_fixup@LIBXMLB_0.1.1 0.1.1 - xb_machine_add_operator@LIBXMLB_0.1.1 0.1.1 - xb_machine_add_text_handler@LIBXMLB_0.1.1 0.1.1 - xb_machine_get_stack_size@LIBXMLB_0.1.3 0.1.3 - xb_machine_get_type@LIBXMLB_0.1.1 0.1.1 - xb_machine_new@LIBXMLB_0.1.1 0.1.1 - xb_machine_opcode_func_new@LIBXMLB_0.1.1 0.1.1 - xb_machine_opcode_to_string@LIBXMLB_0.1.1 0.1.1 - xb_machine_opcodes_to_string@LIBXMLB_0.1.1 0.1.1 - xb_machine_parse@LIBXMLB_0.1.1 0.1.1 - xb_machine_parse_full@LIBXMLB_0.1.4 0.1.4 - xb_machine_run@LIBXMLB_0.1.1 0.1.1 - xb_machine_set_debug_flags@LIBXMLB_0.1.1 0.1.1 - xb_machine_set_stack_size@LIBXMLB_0.1.3 0.1.3 - xb_machine_stack_pop@LIBXMLB_0.1.1 0.1.1 - xb_machine_stack_push@LIBXMLB_0.1.1 0.1.1 - xb_machine_stack_push_integer@LIBXMLB_0.1.1 0.1.1 - xb_machine_stack_push_steal@LIBXMLB_0.1.4 0.1.4 - xb_machine_stack_push_text@LIBXMLB_0.1.1 0.1.1 - xb_machine_stack_push_text_static@LIBXMLB_0.1.1 0.1.1 - xb_machine_stack_push_text_steal@LIBXMLB_0.1.1 0.1.1 - xb_node_export@LIBXMLB_0.1.0 0.1.0 - xb_node_get_attr@LIBXMLB_0.1.0 0.1.0 - xb_node_get_attr_as_uint@LIBXMLB_0.1.0 0.1.0 - xb_node_get_child@LIBXMLB_0.1.0 0.1.0 - xb_node_get_children@LIBXMLB_0.1.0 0.1.0 - xb_node_get_data@LIBXMLB_0.1.0 0.1.0 - xb_node_get_depth@LIBXMLB_0.1.0 0.1.0 - xb_node_get_element@LIBXMLB_0.1.0 0.1.0 - xb_node_get_next@LIBXMLB_0.1.0 0.1.0 - xb_node_get_parent@LIBXMLB_0.1.0 0.1.0 - xb_node_get_root@LIBXMLB_0.1.0 0.1.0 - xb_node_get_tail@LIBXMLB_0.1.12 0.1.12 - xb_node_get_text@LIBXMLB_0.1.0 0.1.0 - xb_node_get_text_as_uint@LIBXMLB_0.1.0 0.1.0 - xb_node_get_type@LIBXMLB_0.1.0 0.1.0 - xb_node_query@LIBXMLB_0.1.0 0.1.0 - xb_node_query_attr@LIBXMLB_0.1.0 0.1.0 - xb_node_query_attr_as_uint@LIBXMLB_0.1.0 0.1.0 - xb_node_query_export@LIBXMLB_0.1.0 0.1.0 - xb_node_query_first@LIBXMLB_0.1.0 0.1.0 - xb_node_query_first_full@LIBXMLB_0.1.11 0.1.11 - xb_node_query_full@LIBXMLB_0.1.4 0.1.4 - xb_node_query_text@LIBXMLB_0.1.0 0.1.0 - xb_node_query_text_as_uint@LIBXMLB_0.1.0 0.1.0 - xb_node_set_data@LIBXMLB_0.1.0 0.1.0 - xb_node_transmogrify@LIBXMLB_0.1.12 0.1.12 - xb_opcode_cmp_str@LIBXMLB_0.1.1 0.1.1 - xb_opcode_cmp_val@LIBXMLB_0.1.1 0.1.1 - xb_opcode_func_new@LIBXMLB_0.1.1 0.1.1 - xb_opcode_get_kind@LIBXMLB_0.1.1 0.1.1 - xb_opcode_get_str@LIBXMLB_0.1.1 0.1.1 - xb_opcode_get_type@LIBXMLB_0.1.1 0.1.1 - xb_opcode_get_val@LIBXMLB_0.1.1 0.1.1 - xb_opcode_integer_new@LIBXMLB_0.1.1 0.1.1 - xb_opcode_kind_from_string@LIBXMLB_0.1.1 0.1.1 - xb_opcode_kind_to_string@LIBXMLB_0.1.1 0.1.1 - xb_opcode_ref@LIBXMLB_0.1.1 0.1.1 - xb_opcode_text_new@LIBXMLB_0.1.1 0.1.1 - xb_opcode_text_new_static@LIBXMLB_0.1.1 0.1.1 - xb_opcode_text_new_steal@LIBXMLB_0.1.1 0.1.1 - xb_opcode_to_string@LIBXMLB_0.1.4 0.1.4 - xb_opcode_unref@LIBXMLB_0.1.1 0.1.1 - xb_query_bind_str@LIBXMLB_0.1.4 0.1.4 - xb_query_bind_val@LIBXMLB_0.1.4 0.1.4 - xb_query_get_flags@LIBXMLB_0.1.15 0.1.15 - xb_query_get_limit@LIBXMLB_0.1.4 0.1.4 - xb_query_get_type@LIBXMLB_0.1.4 0.1.4 - xb_query_get_xpath@LIBXMLB_0.1.4 0.1.4 - xb_query_new@LIBXMLB_0.1.4 0.1.4 - xb_query_new_full@LIBXMLB_0.1.6 0.1.6 - xb_query_set_flags@LIBXMLB_0.1.15 0.1.15 - xb_query_set_limit@LIBXMLB_0.1.4 0.1.4 - xb_silo_export@LIBXMLB_0.1.0 0.1.0 - xb_silo_export_file@LIBXMLB_0.1.2 0.1.2 - xb_silo_get_bytes@LIBXMLB_0.1.0 0.1.0 - xb_silo_get_guid@LIBXMLB_0.1.0 0.1.0 - xb_silo_get_profile_string@LIBXMLB_0.1.1 0.1.1 - xb_silo_get_root@LIBXMLB_0.1.0 0.1.0 - xb_silo_get_size@LIBXMLB_0.1.0 0.1.0 - xb_silo_get_type@LIBXMLB_0.1.0 0.1.0 - xb_silo_invalidate@LIBXMLB_0.1.1 0.1.1 - xb_silo_is_valid@LIBXMLB_0.1.0 0.1.0 - xb_silo_load_from_bytes@LIBXMLB_0.1.0 0.1.0 - xb_silo_load_from_file@LIBXMLB_0.1.0 0.1.0 - xb_silo_new@LIBXMLB_0.1.0 0.1.0 - xb_silo_new_from_xml@LIBXMLB_0.1.0 0.1.0 - xb_silo_query@LIBXMLB_0.1.0 0.1.0 - xb_silo_query_build_index@LIBXMLB_0.1.4 0.1.4 - xb_silo_query_first@LIBXMLB_0.1.0 0.1.0 - xb_silo_query_first_full@LIBXMLB_0.1.13 0.1.13 - xb_silo_query_full@LIBXMLB_0.1.13 0.1.13 - xb_silo_save_to_file@LIBXMLB_0.1.0 0.1.0 - xb_silo_set_profile_flags@LIBXMLB_0.1.1 0.1.1 - xb_silo_to_string@LIBXMLB_0.1.0 0.1.0 - xb_silo_watch_file@LIBXMLB_0.1.0 0.1.0 - xb_stack_get_type@LIBXMLB_0.1.3 0.1.4 - xb_stack_pop@LIBXMLB_0.1.3 0.1.4 - xb_stack_push@LIBXMLB_0.1.3 0.1.4 - xb_stack_push_steal@LIBXMLB_0.1.3 0.1.3 - xb_stack_to_string@LIBXMLB_0.1.4 0.1.4 - xb_string_append_union@LIBXMLB_0.1.2 0.1.2 - xb_string_escape@LIBXMLB_0.1.2 0.1.2 diff -Nru libxmlb-0.1.15/debian/libxmlb2.install libxmlb-0.3.6/debian/libxmlb2.install --- libxmlb-0.1.15/debian/libxmlb2.install 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/debian/libxmlb2.install 2021-12-08 12:41:00.000000000 +0000 @@ -0,0 +1 @@ +usr/lib/*/*.so.* diff -Nru libxmlb-0.1.15/debian/libxmlb2.symbols libxmlb-0.3.6/debian/libxmlb2.symbols --- libxmlb-0.1.15/debian/libxmlb2.symbols 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/debian/libxmlb2.symbols 2021-12-08 12:41:00.000000000 +0000 @@ -0,0 +1,208 @@ +libxmlb.so.2 libxmlb2 #MINVER# + LIBXMLB_0.1.0@LIBXMLB_0.1.0 0.1.0 + LIBXMLB_0.1.11@LIBXMLB_0.1.11 0.1.11 + LIBXMLB_0.1.12@LIBXMLB_0.1.12 0.1.12 + LIBXMLB_0.1.13@LIBXMLB_0.1.13 0.1.13 + LIBXMLB_0.1.15@LIBXMLB_0.1.15 0.1.15 + LIBXMLB_0.1.1@LIBXMLB_0.1.1 0.1.1 + LIBXMLB_0.1.2@LIBXMLB_0.1.2 0.1.2 + LIBXMLB_0.1.3@LIBXMLB_0.1.3 0.1.3 + LIBXMLB_0.1.4@LIBXMLB_0.1.4 0.1.4 + LIBXMLB_0.1.5@LIBXMLB_0.1.5 0.1.5 + LIBXMLB_0.1.6@LIBXMLB_0.1.6 0.1.6 + LIBXMLB_0.1.7@LIBXMLB_0.1.7 0.1.7 + LIBXMLB_0.2.0@LIBXMLB_0.2.0 0.2.0 + LIBXMLB_0.3.0@LIBXMLB_0.3.0 0.3.2 + LIBXMLB_0.3.1@LIBXMLB_0.3.1 0.3.2 + LIBXMLB_0.3.4@LIBXMLB_0.3.4 0.3.4 + xb_builder_add_fixup@LIBXMLB_0.1.3 0.1.3 + xb_builder_add_locale@LIBXMLB_0.1.0 0.1.0 + xb_builder_append_guid@LIBXMLB_0.1.0 0.1.0 + xb_builder_compile@LIBXMLB_0.1.0 0.1.0 + xb_builder_ensure@LIBXMLB_0.1.0 0.1.0 + xb_builder_fixup_get_max_depth@LIBXMLB_0.1.3 0.1.3 + xb_builder_fixup_get_type@LIBXMLB_0.1.3 0.1.3 + xb_builder_fixup_new@LIBXMLB_0.1.3 0.1.3 + xb_builder_fixup_set_max_depth@LIBXMLB_0.1.3 0.1.3 + xb_builder_get_type@LIBXMLB_0.1.0 0.1.0 + xb_builder_import_node@LIBXMLB_0.1.0 0.1.0 + xb_builder_import_source@LIBXMLB_0.1.0 0.1.0 + xb_builder_new@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_add_child@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_add_flag@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_add_token@LIBXMLB_0.3.1 0.3.2 + xb_builder_node_depth@LIBXMLB_0.1.1 0.1.1 + xb_builder_node_export@LIBXMLB_0.1.5 0.1.5 + xb_builder_node_get_attr@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_get_attr_as_uint@LIBXMLB_0.1.3 0.1.3 + xb_builder_node_get_child@LIBXMLB_0.1.1 0.1.1 + xb_builder_node_get_children@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_get_element@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_get_first_child@LIBXMLB_0.1.12 0.1.12 + xb_builder_node_get_last_child@LIBXMLB_0.1.12 0.1.12 + xb_builder_node_get_parent@LIBXMLB_0.1.1 0.1.1 + xb_builder_node_get_tail@LIBXMLB_0.1.12 0.1.12 + xb_builder_node_get_text@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_get_text_as_uint@LIBXMLB_0.1.3 0.1.3 + xb_builder_node_get_tokens@LIBXMLB_0.3.1 0.3.2 + xb_builder_node_get_type@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_has_flag@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_insert@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_insert_text@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_new@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_remove_attr@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_remove_child@LIBXMLB_0.1.1 0.1.1 + xb_builder_node_set_attr@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_set_element@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_set_tail@LIBXMLB_0.1.12 0.1.12 + xb_builder_node_set_text@LIBXMLB_0.1.0 0.1.0 + xb_builder_node_sort_children@LIBXMLB_0.1.3 0.1.3 + xb_builder_node_tokenize_text@LIBXMLB_0.3.1 0.3.2 + xb_builder_node_traverse@LIBXMLB_0.1.1 0.1.1 + xb_builder_node_unlink@LIBXMLB_0.1.1 0.1.1 + xb_builder_set_profile_flags@LIBXMLB_0.1.1 0.1.1 + xb_builder_source_add_adapter@LIBXMLB_0.1.7 0.1.7 + xb_builder_source_add_fixup@LIBXMLB_0.1.3 0.1.3 + xb_builder_source_add_simple_adapter@LIBXMLB_0.1.15 0.1.15 + xb_builder_source_ctx_get_bytes@LIBXMLB_0.1.7 0.1.7 + xb_builder_source_ctx_get_filename@LIBXMLB_0.1.7 0.1.7 + xb_builder_source_ctx_get_stream@LIBXMLB_0.1.7 0.1.7 + xb_builder_source_ctx_get_type@LIBXMLB_0.1.7 0.1.7 + xb_builder_source_get_type@LIBXMLB_0.1.0 0.1.0 + xb_builder_source_load_bytes@LIBXMLB_0.1.2 0.1.2 + xb_builder_source_load_file@LIBXMLB_0.1.1 0.1.1 + xb_builder_source_load_xml@LIBXMLB_0.1.1 0.1.1 + xb_builder_source_new@LIBXMLB_0.1.1 0.1.1 + xb_builder_source_set_info@LIBXMLB_0.1.0 0.1.0 + xb_builder_source_set_prefix@LIBXMLB_0.1.0 0.1.10 + xb_machine_add_method@LIBXMLB_0.1.1 0.1.1 + xb_machine_add_opcode_fixup@LIBXMLB_0.1.1 0.1.1 + xb_machine_add_operator@LIBXMLB_0.1.1 0.1.1 + xb_machine_add_text_handler@LIBXMLB_0.1.1 0.1.1 + xb_machine_get_stack_size@LIBXMLB_0.1.3 0.1.3 + xb_machine_get_type@LIBXMLB_0.1.1 0.1.1 + xb_machine_new@LIBXMLB_0.1.1 0.1.1 + xb_machine_opcode_func_init@LIBXMLB_0.2.0 0.2.0 + xb_machine_parse@LIBXMLB_0.1.1 0.1.1 + xb_machine_parse_full@LIBXMLB_0.1.4 0.1.4 + xb_machine_run@LIBXMLB_0.1.1 0.1.1 + xb_machine_run_with_bindings@LIBXMLB_0.3.0 0.3.2 + xb_machine_set_debug_flags@LIBXMLB_0.1.1 0.1.1 + xb_machine_set_stack_size@LIBXMLB_0.1.3 0.1.3 + xb_machine_stack_pop@LIBXMLB_0.2.0 0.2.0 + xb_machine_stack_push@LIBXMLB_0.2.0 0.2.0 + xb_machine_stack_push_integer@LIBXMLB_0.2.0 0.2.0 + xb_machine_stack_push_text@LIBXMLB_0.2.0 0.2.0 + xb_machine_stack_push_text_static@LIBXMLB_0.2.0 0.2.0 + xb_machine_stack_push_text_steal@LIBXMLB_0.2.0 0.2.0 + xb_node_attr_iter_init@LIBXMLB_0.3.4 0.3.4 + xb_node_attr_iter_next@LIBXMLB_0.3.4 0.3.4 + xb_node_child_iter_init@LIBXMLB_0.3.4 0.3.4 + xb_node_child_iter_loop@LIBXMLB_0.3.4 0.3.4 + xb_node_child_iter_next@LIBXMLB_0.3.4 0.3.4 + xb_node_export@LIBXMLB_0.1.0 0.1.0 + xb_node_get_attr@LIBXMLB_0.1.0 0.1.0 + xb_node_get_attr_as_uint@LIBXMLB_0.1.0 0.1.0 + xb_node_get_child@LIBXMLB_0.1.0 0.1.0 + xb_node_get_children@LIBXMLB_0.1.0 0.1.0 + xb_node_get_data@LIBXMLB_0.1.0 0.1.0 + xb_node_get_depth@LIBXMLB_0.1.0 0.1.0 + xb_node_get_element@LIBXMLB_0.1.0 0.1.0 + xb_node_get_next@LIBXMLB_0.1.0 0.1.0 + xb_node_get_parent@LIBXMLB_0.1.0 0.1.0 + xb_node_get_root@LIBXMLB_0.1.0 0.1.0 + xb_node_get_silo@LIBXMLB_0.2.0 0.2.0 + xb_node_get_tail@LIBXMLB_0.1.12 0.1.12 + xb_node_get_text@LIBXMLB_0.1.0 0.1.0 + xb_node_get_text_as_uint@LIBXMLB_0.1.0 0.1.0 + xb_node_get_type@LIBXMLB_0.1.0 0.1.0 + xb_node_query@LIBXMLB_0.1.0 0.1.0 + xb_node_query_attr@LIBXMLB_0.1.0 0.1.0 + xb_node_query_attr_as_uint@LIBXMLB_0.1.0 0.1.0 + xb_node_query_export@LIBXMLB_0.1.0 0.1.0 + xb_node_query_first@LIBXMLB_0.1.0 0.1.0 + xb_node_query_first_full@LIBXMLB_0.1.11 0.1.11 + xb_node_query_first_with_context@LIBXMLB_0.3.0 0.3.2 + xb_node_query_full@LIBXMLB_0.1.4 0.1.4 + xb_node_query_text@LIBXMLB_0.1.0 0.1.0 + xb_node_query_text_as_uint@LIBXMLB_0.1.0 0.1.0 + xb_node_query_with_context@LIBXMLB_0.3.0 0.3.2 + xb_node_set_data@LIBXMLB_0.1.0 0.1.0 + xb_node_transmogrify@LIBXMLB_0.1.12 0.1.12 + xb_opcode_cmp_str@LIBXMLB_0.1.1 0.3.4 + xb_opcode_cmp_val@LIBXMLB_0.1.1 0.3.4 + xb_opcode_func_init@LIBXMLB_0.2.0 0.3.4 + xb_opcode_get_kind@LIBXMLB_0.1.1 0.3.4 + xb_opcode_get_str@LIBXMLB_0.1.1 0.3.4 + xb_opcode_get_val@LIBXMLB_0.1.1 0.3.4 + xb_opcode_integer_init@LIBXMLB_0.2.0 0.3.4 + xb_opcode_kind_from_string@LIBXMLB_0.1.1 0.1.1 + xb_opcode_kind_to_string@LIBXMLB_0.1.1 0.1.1 + xb_opcode_text_init@LIBXMLB_0.2.0 0.3.4 + xb_opcode_text_init_static@LIBXMLB_0.2.0 0.3.4 + xb_opcode_text_init_steal@LIBXMLB_0.2.0 0.3.4 + xb_opcode_to_string@LIBXMLB_0.1.4 0.3.4 + xb_query_bind_str@LIBXMLB_0.1.4 0.1.4 + xb_query_bind_val@LIBXMLB_0.1.4 0.1.4 + xb_query_context_clear@LIBXMLB_0.3.0 0.3.2 + xb_query_context_copy@LIBXMLB_0.3.0 0.3.2 + xb_query_context_free@LIBXMLB_0.3.0 0.3.2 + xb_query_context_get_bindings@LIBXMLB_0.3.0 0.3.2 + xb_query_context_get_flags@LIBXMLB_0.3.0 0.3.2 + xb_query_context_get_limit@LIBXMLB_0.3.0 0.3.2 + xb_query_context_get_type@LIBXMLB_0.3.0 0.3.2 + xb_query_context_init@LIBXMLB_0.3.0 0.3.2 + xb_query_context_set_flags@LIBXMLB_0.3.0 0.3.2 + xb_query_context_set_limit@LIBXMLB_0.3.0 0.3.2 + xb_query_get_flags@LIBXMLB_0.1.15 0.1.15 + xb_query_get_limit@LIBXMLB_0.1.4 0.1.4 + xb_query_get_type@LIBXMLB_0.1.4 0.1.4 + xb_query_get_xpath@LIBXMLB_0.1.4 0.1.4 + xb_query_new@LIBXMLB_0.1.4 0.1.4 + xb_query_new_full@LIBXMLB_0.1.6 0.1.6 + xb_query_set_flags@LIBXMLB_0.1.15 0.1.15 + xb_query_set_limit@LIBXMLB_0.1.4 0.1.4 + xb_silo_export@LIBXMLB_0.1.0 0.1.0 + xb_silo_export_file@LIBXMLB_0.1.2 0.1.2 + xb_silo_get_bytes@LIBXMLB_0.1.0 0.1.0 + xb_silo_get_enable_node_cache@LIBXMLB_0.2.0 0.2.0 + xb_silo_get_guid@LIBXMLB_0.1.0 0.1.0 + xb_silo_get_profile_string@LIBXMLB_0.1.1 0.1.1 + xb_silo_get_root@LIBXMLB_0.1.0 0.1.0 + xb_silo_get_size@LIBXMLB_0.1.0 0.1.0 + xb_silo_get_type@LIBXMLB_0.1.0 0.1.0 + xb_silo_invalidate@LIBXMLB_0.1.1 0.1.1 + xb_silo_is_valid@LIBXMLB_0.1.0 0.1.0 + xb_silo_load_from_bytes@LIBXMLB_0.1.0 0.1.0 + xb_silo_load_from_file@LIBXMLB_0.1.0 0.1.0 + xb_silo_lookup_query@LIBXMLB_0.3.0 0.3.2 + xb_silo_new@LIBXMLB_0.1.0 0.1.0 + xb_silo_new_from_xml@LIBXMLB_0.1.0 0.1.0 + xb_silo_query@LIBXMLB_0.1.0 0.1.0 + xb_silo_query_build_index@LIBXMLB_0.1.4 0.1.4 + xb_silo_query_first@LIBXMLB_0.1.0 0.1.0 + xb_silo_query_first_full@LIBXMLB_0.1.13 0.1.13 + xb_silo_query_first_with_context@LIBXMLB_0.3.0 0.3.2 + xb_silo_query_full@LIBXMLB_0.1.13 0.1.13 + xb_silo_query_with_context@LIBXMLB_0.3.0 0.3.2 + xb_silo_save_to_file@LIBXMLB_0.1.0 0.1.0 + xb_silo_set_enable_node_cache@LIBXMLB_0.2.0 0.2.0 + xb_silo_set_profile_flags@LIBXMLB_0.1.1 0.1.1 + xb_silo_to_string@LIBXMLB_0.1.0 0.1.0 + xb_silo_watch_file@LIBXMLB_0.1.0 0.1.0 + xb_stack_get_type@LIBXMLB_0.1.4 0.1.4 + xb_stack_pop@LIBXMLB_0.2.0 0.2.0 + xb_stack_push@LIBXMLB_0.2.0 0.2.0 + xb_stack_to_string@LIBXMLB_0.1.4 0.1.4 + xb_string_append_union@LIBXMLB_0.1.2 0.1.2 + xb_string_escape@LIBXMLB_0.1.2 0.1.2 + xb_value_bindings_bind_str@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_bind_val@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_clear@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_copy@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_copy_binding@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_free@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_get_type@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_init@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_is_bound@LIBXMLB_0.3.0 0.3.2 + xb_value_bindings_lookup_opcode@LIBXMLB_0.3.0 0.3.2 diff -Nru libxmlb-0.1.15/debian/libxmlb-dev.install libxmlb-0.3.6/debian/libxmlb-dev.install --- libxmlb-0.1.15/debian/libxmlb-dev.install 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/libxmlb-dev.install 2021-12-08 12:41:00.000000000 +0000 @@ -1,6 +1,6 @@ usr/include/ usr/lib/*/*.so usr/lib/*/pkgconfig +usr/lib/*/xb-tool usr/share/gir-1.0 usr/share/gtk-doc -usr/lib/*/xb-tool diff -Nru libxmlb-0.1.15/debian/patches/series libxmlb-0.3.6/debian/patches/series --- libxmlb-0.1.15/debian/patches/series 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -tests-Make-the-build-reproducible.patch -xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch diff -Nru libxmlb-0.1.15/debian/patches/tests-Make-the-build-reproducible.patch libxmlb-0.3.6/debian/patches/tests-Make-the-build-reproducible.patch --- libxmlb-0.1.15/debian/patches/tests-Make-the-build-reproducible.patch 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/patches/tests-Make-the-build-reproducible.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,77 +0,0 @@ -From: Laurent Bigonville -Date: Sat, 21 Mar 2020 13:32:58 +0100 -Subject: tests: Make the build reproducible - ---- - meson.build | 2 +- - src/meson.build | 5 +++-- - src/xb-self-test.c | 8 +++++--- - 3 files changed, 9 insertions(+), 6 deletions(-) - -diff --git a/meson.build b/meson.build -index 38486c9..475cee4 100644 ---- a/meson.build -+++ b/meson.build -@@ -1,7 +1,7 @@ - project('libxmlb', 'c', - version : '0.1.15', - license : 'LGPL-2.1+', -- meson_version : '>=0.47.0', -+ meson_version : '>=0.52.0', - default_options : ['warning_level=2', 'c_std=c99'], - ) - -diff --git a/src/meson.build b/src/meson.build -index 936cc1d..ffbd97d 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -184,6 +184,8 @@ if get_option('introspection') - endif - - if get_option('tests') -+ testdir = environment({'TESTDIR' : join_paths(meson.current_source_dir(), '..', 'data')}) -+ - e = executable( - 'xb-self-test', - sources : [ -@@ -209,7 +211,6 @@ if get_option('tests') - configinc, - ], - c_args : [ -- '-DTESTDIR="@0@/../data"'.format(meson.current_source_dir()), - '-DINSTALLEDTESTDIR="' + installed_test_datadir + '"', - ], - dependencies : [ -@@ -219,5 +220,5 @@ if get_option('tests') - install : true, - install_dir : installed_test_bindir - ) -- test('xb-self-test', e) -+ test('xb-self-test', e, env : testdir) - endif -diff --git a/src/xb-self-test.c b/src/xb-self-test.c -index f1b4f2a..85cf062 100644 ---- a/src/xb-self-test.c -+++ b/src/xb-self-test.c -@@ -449,6 +449,7 @@ static void - xb_builder_chained_adapters_func (void) - { - gboolean ret; -+ const gchar *testdir; - g_autofree gchar *xml = NULL; - g_autofree gchar *path = NULL; - g_autofree gchar *tmp_xmlb = g_build_filename (g_get_tmp_dir (), "temp.xmlb", NULL); -@@ -460,9 +461,10 @@ xb_builder_chained_adapters_func (void) - g_autoptr(XbSilo) silo = NULL; - - /* import a source file */ -- path = g_build_filename (TESTDIR, "test.xml.gz.gz.gz", NULL); -- if (!g_file_test (path, G_FILE_TEST_EXISTS)) { -- g_free (path); -+ testdir = g_getenv ("TESTDIR"); -+ if (testdir != NULL) { -+ path = g_build_filename (testdir, "test.xml.gz.gz.gz", NULL); -+ } else { - path = g_build_filename (INSTALLEDTESTDIR, "test.xml.gz.gz.gz", NULL); - } - file_src = g_file_new_for_path (path); diff -Nru libxmlb-0.1.15/debian/patches/xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch libxmlb-0.3.6/debian/patches/xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch --- libxmlb-0.1.15/debian/patches/xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/patches/xb-silo-Cancel-the-GFileMonitor-before-unreffing-it.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -From: Iain Lane -Date: Mon, 25 Jan 2021 12:58:30 +0000 -Subject: xb-silo: Cancel the GFileMonitor before unreffing it - -GFileMonitors currently need to be explicitly cancelled when they are -unreffed, otherwise they can deadlock due to the GLib-internal worker -thread also holding a strong reference to the monitor. - -For full details, see: https://gitlab.gnome.org/GNOME/glib/-/issues/1941 - -Closes #59 - -Origin: upstream, commit:a137f1a84f6e50e19a70cfa9247b29725f22432a -Applied-Upstream: 0.2.2 -Bug-Upstream: https://github.com/hughsie/libxmlb/issues/59 ---- - src/xb-silo.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/xb-silo.c b/src/xb-silo.c -index e967f35..fa8a72e 100644 ---- a/src/xb-silo.c -+++ b/src/xb-silo.c -@@ -1347,6 +1347,7 @@ xb_silo_machine_fixup_attr_text_cb (XbMachine *self, - static void - xb_silo_file_monitor_item_free (XbSiloFileMonitorItem *item) - { -+ g_file_monitor_cancel (item->file_monitor); - g_signal_handler_disconnect (item->file_monitor, item->file_monitor_id); - g_object_unref (item->file_monitor); - g_slice_free (XbSiloFileMonitorItem, item); diff -Nru libxmlb-0.1.15/debian/tests/control libxmlb-0.3.6/debian/tests/control --- libxmlb-0.1.15/debian/tests/control 2021-01-25 16:01:47.000000000 +0000 +++ libxmlb-0.3.6/debian/tests/control 2021-12-08 12:41:00.000000000 +0000 @@ -1,2 +1,3 @@ Tests: installed-tests -Depends: gnome-desktop-testing, libxmlb-tests +Depends: gnome-desktop-testing, + libxmlb-tests diff -Nru libxmlb-0.1.15/docs/libxmlb-docs.xml libxmlb-0.3.6/docs/libxmlb-docs.xml --- libxmlb-0.1.15/docs/libxmlb-docs.xml 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/docs/libxmlb-docs.xml 2021-12-06 20:36:37.000000000 +0000 @@ -35,11 +35,13 @@ + + diff -Nru libxmlb-0.1.15/docs/libxmlb.types libxmlb-0.3.6/docs/libxmlb.types --- libxmlb-0.1.15/docs/libxmlb.types 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/docs/libxmlb.types 2021-12-06 20:36:37.000000000 +0000 @@ -1,7 +1,6 @@ xb_silo_get_type xb_machine_get_type xb_node_get_type -xb_opcode_get_type xb_stack_get_type xb_builder_get_type xb_builder_fixup_get_type diff -Nru libxmlb-0.1.15/.editorconfig libxmlb-0.3.6/.editorconfig --- libxmlb-0.1.15/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/.editorconfig 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,6 @@ +# See https://editorconfig.org/ +root = true + +[*] +indent_style = tab +indent_size = 8 diff -Nru libxmlb-0.1.15/.github/workflows/main.yml libxmlb-0.3.6/.github/workflows/main.yml --- libxmlb-0.1.15/.github/workflows/main.yml 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/.github/workflows/main.yml 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,38 @@ +name: Build and Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-linux: + runs-on: ubuntu-latest + strategy: + matrix: + distro: + - fedora + - debian + fail-fast: false + steps: + - uses: actions/checkout@v2 + - run: docker build -t libxmlb-${{ matrix.distro }} -f contrib/ci/Dockerfile-${{ matrix.distro }} . + - run: docker run -t -v `pwd`:/build libxmlb-${{ matrix.distro }} ./contrib/ci/build-${{ matrix.distro }}.sh + + build-freebsd: + runs-on: macos-10.15 + + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Build + id: test + uses: vmactions/freebsd-vm@v0.1.5 + with: + usesh: true + mem: 8192 + prepare: | + pkg install -y git python3 glib meson pkgconf gobject-introspection gtk-doc lzma + sync: rsync + run: ./contrib/ci/build-debian.sh diff -Nru libxmlb-0.1.15/meson.build libxmlb-0.3.6/meson.build --- libxmlb-0.1.15/meson.build 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/meson.build 2021-12-06 20:36:37.000000000 +0000 @@ -1,5 +1,5 @@ project('libxmlb', 'c', - version : '0.1.15', + version : '0.3.6', license : 'LGPL-2.1+', meson_version : '>=0.47.0', default_options : ['warning_level=2', 'c_std=c99'], @@ -18,7 +18,7 @@ conf.set_quoted('PACKAGE_VERSION', libxmlb_version) # libtool versioning - this applies to libxmlb -lt_current = '1' +lt_current = '2' lt_revision = '0' lt_age = '0' lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) @@ -27,7 +27,7 @@ # get suported warning flags warning_flags = [ - '-Waggregate-return', + '-Wno-aggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', @@ -116,12 +116,20 @@ gio = dependency('gio-2.0', version : '>= 2.45.8') giounix = dependency('gio-unix-2.0', version : '>= 2.45.8', required: false) +lzma = dependency('liblzma') if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif +# Limit our use of GLib API to our minimum version requirement, and what’s +# available in Debian Stable. Use of more modern API has to be optional and +# protected by GLIB_CHECK_VERSION. +add_project_arguments('-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_46', language: 'c') +add_project_arguments('-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_58', language: 'c') + libxmlb_deps = [ gio, + lzma, ] # support stemming of search tokens @@ -143,7 +151,7 @@ ) python = import('python') -python3 = python.find_installation('python3') +python3 = python.find_installation() subdir('data') subdir('src') diff -Nru libxmlb-0.1.15/meson_options.txt libxmlb-0.3.6/meson_options.txt --- libxmlb-0.1.15/meson_options.txt 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/meson_options.txt 2021-12-06 20:36:37.000000000 +0000 @@ -2,3 +2,4 @@ option('introspection', type : 'boolean', value : true, description : 'generate GObject Introspection data') option('tests', type : 'boolean', value : true, description : 'enable tests') option('stemmer', type : 'boolean', value : false, description : 'enable stemmer support') +option('cli', type : 'boolean', value : true, description : 'build and install the xb-tool CLI') diff -Nru libxmlb-0.1.15/NEWS libxmlb-0.3.6/NEWS --- libxmlb-0.1.15/NEWS 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/NEWS 2021-12-06 20:36:37.000000000 +0000 @@ -1,6 +1,126 @@ +Version 0.3.6 +~~~~~~~~~~~~~ +Released: 2021-12-06 + +Bugfixes: + - Ensure _IS_TOKENIZED is set if tokens are added manually (Richard Hughes) + - Ensure we never add too many tokens to the silo (Richard Hughes) + +Version 0.3.5 +~~~~~~~~~~~~~ +Released: 2021-12-01 + +Bugfixes: + - Put tail after the node when exporting XbBuilderNode (Matthias Klumpp) + - Remove the G_ALIGNOF checks to fix compile with old GLib versions (Richard Hughes) + +Version 0.3.4 +~~~~~~~~~~~~~ +Released: 2021-11-29 + +New Features: + - Add a flag to require the XbBuilderSource to have no siblings (Richard Hughes) + - Add iterator for XbNode attributes and children (Matthias Klumpp) + - Allow removing XbBuilderNode text (Richard Hughes) + - Allow stripping builder node inner text (Richard Hughes) + - Include records in the symbol export list (Richard Hughes) + - Use -Dcli=false to reduce the install size (Richard Hughes) + +Version 0.3.3 +~~~~~~~~~~~~~ +Released: 2021-10-06 + +New Features: + - Add support for LZMA decompression (Richard Hughes) + +Bugfixes: + - Add locking for file monitors (Philip Withnall) + - Clarify GMainContext usage and signal emission (Philip Withnall) + - Modernise property declaration (Philip Withnall) + +Version 0.3.2 +~~~~~~~~~~~~~ +Released: 2021-05-24 + +Bugfixes: + - Respect XB_BUILDER_NODE_FLAG_IGNORE when exporting (Richard Hughes) + - Use the chosen chunk size when reading from a stream (Richard Hughes) + - Use the correct pkgconfig export package name in the GIR file (Richard Hughes) + +Version 0.3.1 +~~~~~~~~~~~~~ +Released: 2021-05-06 + +New Features: + - Use stack-allocated XbStack instances for running a machine (Philip Withnall) + +Bugfixes: + - Always run xb-tool queries with the optimizer (Richard Hughes) + - Do not use g_file_replace_contents for win32 (Richard Hughes) + - Ensure tokens handling works when XbOpcode is not zero-inited (Philip Withnall) + +Version 0.3.0 +~~~~~~~~~~~~~ +Released: 2021-03-12 + +New Features: + - Add a new object to contain query context data (Philip Withnall) + - Allow collapsing empty XML tags if no children or text (Aleksander Morgado) + - Allow marking elements as tokenized from xb-tool (Richard Hughes) + - Allow optimized searching when comparing tokens (Richard Hughes) + - Support mmap()ing the source file to get bytes (Philip Withnall) + - Support query caching with xb_silo_lookup_query() (Philip Withnall) + - Support UTF-8 for upper-case() and lower-case() (Richard Hughes) + - Use g_str_match_string() for non-ASCII search queries (Richard Hughes) + - Write search tokens into the built silo (Richard Hughes) + +Bugfixes: + - Cancel the GFileMonitor before unreffing it (Iain Lane) + - Do not allocate a 128Mb buffer for each xb_builder_source_ctx_get_bytes() (Richard Hughes) + - Do not error when creating a query if the element doesn’t exist (Philip Withnall) + - Fix a parse failure for a double comment (Richard Hughes) + - Fix various errors or missing annotations in docs (Philip Withnall) + - Make handling of single-result queries more robust (Philip Withnall) + +Version 0.2.1 +~~~~~~~~~~~~~ +Released: 2020-09-07 + +Bugfixes: + - Do not assume g_content_type_guess() always returns valid results (Richard Hughes) + - Make the build reproducible (Richard Hughes) + - Revert "Do not show a critical warning for invalid XML" (Richard Hughes) + - Update the header location to reflect the new API (Richard Hughes) + +Version 0.2.0 +~~~~~~~~~~~~~ +Released: 2020-08-18 + +Important: + - This release breaks API and ABI and bumps the version of libxmlb.so and so + packages that depend on this library (e.g. fwupd or gnome-software) will need + to be rebuilt at the same time. + +New Features: + - Add the missing TEXT:INTE XPath support (Richard Hughes) + - Add variant of xb_silo_query_with_root() avoiding XbNode creation (Philip Withnall) + - Add XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY flag (Philip Withnall) + - Allow specifying the node cache behaviour for the query (Richard Hughes) + +Bugfixes: + - Avoid recursion when setting flags if possible (Philip Withnall) + - Avoid using weak pointers when building the silo (Philip Withnall) + - Change the default value for the node cache (Richard Hughes) + - Do not allocate opcodes individually (Philip Withnall) + - Do not show a critical warning for invalid XML (Richard Hughes) + - Do not unconditionally create GTimer objects (Philip Withnall) + - Do not use the node cache when building indexes (Richard Hughes) + - Lazy load more arrays to reduce RSS usage (Philip Withnall) + - Report silo versions when versions mismatch (Robert Ancell) + Version 0.1.15 ~~~~~~~~~~~~~~ -Released: 2020-xx-xx +Released: 2020-03-04 New Features: - Add xb_builder_source_add_simple_adapter (Daniel Campello) diff -Nru libxmlb-0.1.15/README.md libxmlb-0.3.6/README.md --- libxmlb-0.1.15/README.md 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/README.md 2021-12-06 20:36:37.000000000 +0000 @@ -22,7 +22,7 @@ be larger than the original XML file. This isn't important; the fast query speed and the ability to mmap strings without copies more than makes up for the larger on-disk size. If you want to compress your XML, this library probably isn't for -you -- just use gzip -- its gives you an almost a perfect compression ratio for +you -- just use gzip -- it gives you an almost a perfect compression ratio for data like this. For example: diff -Nru libxmlb-0.1.15/RELEASE libxmlb-0.3.6/RELEASE --- libxmlb-0.1.15/RELEASE 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/RELEASE 2021-12-06 20:36:37.000000000 +0000 @@ -2,11 +2,11 @@ 1. Write NEWS entries for libxmlb in the same format as usual. -git shortlog 0.1.14.. | grep -i -v trivial | grep -v Merge > NEWS.new +git shortlog 0.3.5.. | grep -i -v trivial | grep -v Merge > NEWS.new -Version 0.1.15 -~~~~~~~~~~~~~~ -Released: 2020-xx-xx +Version 0.3.6 +~~~~~~~~~~~~~ +Released: 2021-xx-xx New Features: Bugfixes: @@ -15,7 +15,7 @@ Commit changes to git: # MAKE SURE THESE ARE CORRECT -export release_ver="0.1.15" +export release_ver="0.3.6" git commit -a -m "Release libxmlb ${release_ver}" git tag -s -f -m "Release libxmlb ${release_ver}" "${release_ver}" diff -Nru libxmlb-0.1.15/src/generate-version-script.py libxmlb-0.3.6/src/generate-version-script.py --- libxmlb-0.1.15/src/generate-version-script.py 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/generate-version-script.py 2021-12-06 20:36:37.000000000 +0000 @@ -6,15 +6,17 @@ # SPDX-License-Identifier: LGPL-2.1+ import sys +import argparse import xml.etree.ElementTree as ET from pkg_resources import parse_version -XMLNS = '{http://www.gtk.org/introspection/core/1.0}' -XMLNS_C = '{http://www.gtk.org/introspection/c/1.0}' +XMLNS = "{http://www.gtk.org/introspection/core/1.0}" +XMLNS_C = "{http://www.gtk.org/introspection/c/1.0}" + def usage(return_code): - """ print usage and exit with the supplied return code """ + """print usage and exit with the supplied return code""" if return_code == 0: out = sys.stdout else: @@ -22,19 +24,25 @@ out.write("usage: %s \n" % sys.argv[0]) sys.exit(return_code) + class LdVersionScript: - """ Rasterize some text """ + """Rasterize some text""" def __init__(self, library_name): self.library_name = library_name self.releases = {} + self.overrides = {} def _add_node(self, node): - identifier = node.attrib[XMLNS_C + 'identifier'] - if 'version' not in node.attrib: - print('No version for', identifier) + identifier = node.attrib[XMLNS_C + "identifier"] + introspectable = int(node.get("introspectable", 1)) + version = node.get("version", None) + if introspectable and not version: + print("No version for", identifier) sys.exit(1) - version = node.attrib['version'] + if not version: + return None + version = node.attrib["version"] if version not in self.releases: self.releases[version] = [] release = self.releases[version] @@ -45,42 +53,48 @@ def _add_cls(self, cls): # add all class functions - for node in cls.findall(XMLNS + 'function'): + for node in cls.findall(XMLNS + "function"): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None - if '{http://www.gtk.org/introspection/glib/1.0}get-type' not in cls.attrib: - return - type_name = cls.attrib['{http://www.gtk.org/introspection/glib/1.0}get-type'] # add all class methods - for node in cls.findall(XMLNS + 'method'): + for node in cls.findall(XMLNS + "method"): version_tmp = self._add_node(node) if version_tmp: - if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): + if not version_lowest or parse_version(version_tmp) < parse_version( + version_lowest + ): version_lowest = version_tmp # add the constructor - for node in cls.findall(XMLNS + 'constructor'): + for node in cls.findall(XMLNS + "constructor"): version_tmp = self._add_node(node) if version_tmp: - if not version_lowest or parse_version(version_tmp) < parse_version(version_lowest): + if not version_lowest or parse_version(version_tmp) < parse_version( + version_lowest + ): version_lowest = version_tmp + if "{http://www.gtk.org/introspection/glib/1.0}get-type" not in cls.attrib: + return + type_name = cls.attrib["{http://www.gtk.org/introspection/glib/1.0}get-type"] + # finally add the get_type symbol - if version_lowest: - self.releases[version_lowest].append(type_name) + version = self.overrides.get(type_name, version_lowest) + if version: + self.releases[version].append(type_name) def import_gir(self, filename): tree = ET.parse(filename) root = tree.getroot() - for ns in root.findall(XMLNS + 'namespace'): - for node in ns.findall(XMLNS + 'function'): + for ns in root.findall(XMLNS + "namespace"): + for node in ns.findall(XMLNS + "function"): self._add_node(node) - for cls in ns.findall(XMLNS + 'record'): + for cls in ns.findall(XMLNS + "record"): self._add_cls(cls) - for cls in ns.findall(XMLNS + 'class'): + for cls in ns.findall(XMLNS + "class"): self._add_cls(cls) def render(self): @@ -91,28 +105,36 @@ versions.append(version) # output the version data to a file - verout = '# generated automatically, do not edit!\n' + verout = "# generated automatically, do not edit!\n" oldversion = None for version in sorted(versions, key=parse_version): symbols = sorted(self.releases[version]) - verout += '\n%s_%s {\n' % (self.library_name, version) - verout += ' global:\n' + verout += "\n%s_%s {\n" % (self.library_name, version) + verout += " global:\n" for symbol in symbols: - verout += ' %s;\n' % symbol - verout += ' local: *;\n' + verout += " %s;\n" % symbol + verout += " local: *;\n" if oldversion: - verout += '} %s_%s;\n' % (self.library_name, oldversion) + verout += "} %s_%s;\n" % (self.library_name, oldversion) else: - verout += '};\n' + verout += "};\n" oldversion = version return verout -if __name__ == '__main__': - if {'-?', '--help', '--usage'}.intersection(set(sys.argv)): - usage(0) - if len(sys.argv) != 4: + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + parser.add_argument( + "-r", "--override", action="append", nargs=2, metavar=("symbol", "version") + ) + args, argv = parser.parse_known_args() + if len(argv) != 3: usage(1) - ld = LdVersionScript(library_name=sys.argv[1]) - ld.import_gir(sys.argv[2]) - open(sys.argv[3], 'w').write(ld.render()) + ld = LdVersionScript(library_name=argv[0]) + if args.override: + for override_symbol, override_version in args.override: + ld.overrides[override_symbol] = override_version + ld.import_gir(argv[1]) + open(argv[2], "w").write(ld.render()) diff -Nru libxmlb-0.1.15/src/libxmlb.map libxmlb-0.3.6/src/libxmlb.map --- libxmlb-0.1.15/src/libxmlb.map 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/libxmlb.map 2021-12-06 20:36:37.000000000 +0000 @@ -25,7 +25,6 @@ xb_builder_node_set_attr; xb_builder_node_set_element; xb_builder_node_set_text; - xb_builder_source_add_node_func; xb_builder_source_get_type; xb_builder_source_set_info; xb_builder_source_set_prefix; @@ -79,7 +78,6 @@ xb_builder_node_traverse; xb_builder_node_unlink; xb_builder_set_profile_flags; - xb_builder_source_add_converter; xb_builder_source_load_file; xb_builder_source_load_xml; xb_builder_source_new; @@ -89,33 +87,16 @@ xb_machine_add_text_handler; xb_machine_get_type; xb_machine_new; - xb_machine_opcode_func_new; - xb_machine_opcode_to_string; - xb_machine_opcodes_to_string; xb_machine_parse; xb_machine_run; xb_machine_set_debug_flags; - xb_machine_stack_pop; - xb_machine_stack_push; - xb_machine_stack_push_integer; - xb_machine_stack_push_text; - xb_machine_stack_push_text_static; - xb_machine_stack_push_text_steal; xb_opcode_cmp_str; xb_opcode_cmp_val; - xb_opcode_func_new; xb_opcode_get_kind; xb_opcode_get_str; - xb_opcode_get_type; xb_opcode_get_val; - xb_opcode_integer_new; xb_opcode_kind_from_string; xb_opcode_kind_to_string; - xb_opcode_ref; - xb_opcode_text_new; - xb_opcode_text_new_static; - xb_opcode_text_new_steal; - xb_opcode_unref; xb_silo_get_profile_string; xb_silo_invalidate; xb_silo_set_profile_flags; @@ -144,17 +125,12 @@ xb_builder_source_add_fixup; xb_machine_get_stack_size; xb_machine_set_stack_size; - xb_stack_get_type; - xb_stack_pop; - xb_stack_push; - xb_stack_push_steal; local: *; } LIBXMLB_0.1.2; LIBXMLB_0.1.4 { global: xb_machine_parse_full; - xb_machine_stack_push_steal; xb_node_query_full; xb_opcode_to_string; xb_query_bind_str; @@ -165,6 +141,7 @@ xb_query_new; xb_query_set_limit; xb_silo_query_build_index; + xb_stack_get_type; xb_stack_to_string; local: *; } LIBXMLB_0.1.3; @@ -222,3 +199,74 @@ xb_query_set_flags; local: *; } LIBXMLB_0.1.13; + +LIBXMLB_0.2.0 { + global: + xb_machine_opcode_func_init; + xb_machine_stack_pop; + xb_machine_stack_push; + xb_machine_stack_push_integer; + xb_machine_stack_push_text; + xb_machine_stack_push_text_static; + xb_machine_stack_push_text_steal; + xb_node_get_silo; + xb_opcode_func_init; + xb_opcode_integer_init; + xb_opcode_text_init; + xb_opcode_text_init_static; + xb_opcode_text_init_steal; + xb_silo_get_enable_node_cache; + xb_silo_set_enable_node_cache; + xb_stack_pop; + xb_stack_push; + local: *; +} LIBXMLB_0.1.15; + +LIBXMLB_0.3.0 { + global: + xb_machine_run_with_bindings; + xb_node_query_first_with_context; + xb_node_query_with_context; + xb_query_context_clear; + xb_query_context_copy; + xb_query_context_free; + xb_query_context_get_bindings; + xb_query_context_get_flags; + xb_query_context_get_limit; + xb_query_context_get_type; + xb_query_context_init; + xb_query_context_set_flags; + xb_query_context_set_limit; + xb_silo_lookup_query; + xb_silo_query_first_with_context; + xb_silo_query_with_context; + xb_value_bindings_bind_str; + xb_value_bindings_bind_val; + xb_value_bindings_clear; + xb_value_bindings_copy; + xb_value_bindings_copy_binding; + xb_value_bindings_free; + xb_value_bindings_get_type; + xb_value_bindings_init; + xb_value_bindings_is_bound; + xb_value_bindings_lookup_opcode; + local: *; +} LIBXMLB_0.2.0; + +LIBXMLB_0.3.1 { + global: + xb_builder_node_add_token; + xb_builder_node_get_tokens; + xb_builder_node_tokenize_text; + local: *; +} LIBXMLB_0.3.0; + +LIBXMLB_0.3.4 { + global: + xb_node_attr_iter_init; + xb_node_attr_iter_next; + xb_node_child_iter_init; + xb_node_child_iter_loop; + xb_node_child_iter_next; + local: *; +} LIBXMLB_0.3.1; diff -Nru libxmlb-0.1.15/src/meson.build libxmlb-0.3.6/src/meson.build --- libxmlb-0.1.15/src/meson.build 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/meson.build 2021-12-06 20:36:37.000000000 +0000 @@ -6,7 +6,7 @@ install_headers( 'xmlb.h', - subdir : 'libxmlb-1', + subdir : 'libxmlb-2', ) xb_headers = files( @@ -18,18 +18,21 @@ 'xb-machine.h', 'xb-node.h', 'xb-node-query.h', + 'xb-node-silo.h', 'xb-opcode.h', 'xb-query.h', + 'xb-query-context.h', 'xb-silo-export.h', 'xb-silo.h', 'xb-silo-query.h', 'xb-stack.h', 'xb-string.h', + 'xb-value-bindings.h', ) + [libxmlb_version_h] install_headers( xb_headers, - subdir : 'libxmlb-1/libxmlb', + subdir : 'libxmlb-2/libxmlb', ) subdir('libxmlb') @@ -44,16 +47,21 @@ 'xb-builder-node.c', 'xb-builder-source.c', 'xb-builder-source-ctx.c', + 'xb-common.c', + 'xb-lzma-decompressor.c', 'xb-machine.c', 'xb-opcode.c', 'xb-node.c', 'xb-node-query.c', 'xb-query.c', + 'xb-query-context.c', 'xb-silo.c', 'xb-silo-export.c', + 'xb-silo-node.c', 'xb-silo-query.c', 'xb-stack.c', 'xb-string.c', + 'xb-value-bindings.c', ], soversion : lt_current, version : lt_version, @@ -72,6 +80,7 @@ dependencies : libxmlb_deps ) +if get_option('cli') xb_tool = executable( 'xb-tool', sources : [ @@ -89,12 +98,12 @@ install : true, install_dir : libexecdir ) +endif pkgg = import('pkgconfig') -pkgg.generate( - libraries : libxmlb, +pkgg.generate(libxmlb, requires : [ 'gio-2.0' ], - subdirs : 'libxmlb-1', + subdirs : 'libxmlb-2', version : meson.project_version(), name : 'libxmlb', filebase : 'xmlb', @@ -114,16 +123,20 @@ 'xb-builder-source.h', 'xb-builder-source-ctx.c', 'xb-builder-source-ctx.h', + 'xb-common.c', 'xb-machine.c', 'xb-machine.h', 'xb-node.c', 'xb-node.h', 'xb-node-query.c', 'xb-node-query.h', + 'xb-node-silo.h', 'xb-opcode.c', 'xb-opcode.h', 'xb-query.c', 'xb-query.h', + 'xb-query-context.c', + 'xb-query-context.h', 'xb-silo.c', 'xb-silo.h', 'xb-silo-export.c', @@ -134,12 +147,14 @@ 'xb-stack.h', 'xb-string.c', 'xb-string.h', + 'xb-value-bindings.c', + 'xb-value-bindings.h', ], - nsversion : '1.0', + nsversion : '2.0', namespace : 'Xmlb', symbol_prefix : 'xb', identifier_prefix : 'Xb', - export_packages : 'libxmlb', + export_packages : 'xmlb', header : 'xmlb.h', dependencies : [ gio, @@ -184,6 +199,9 @@ endif if get_option('tests') + testdatadirs = environment() + testdatadirs.set('G_TEST_SRCDIR', join_paths(meson.source_root(), 'data')) + testdatadirs.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'xb-self-test', sources : [ @@ -193,25 +211,26 @@ 'xb-builder-node.c', 'xb-builder-source.c', 'xb-builder-source-ctx.c', + 'xb-common.c', + 'xb-lzma-decompressor.c', 'xb-machine.c', 'xb-node.c', 'xb-node-query.c', 'xb-opcode.c', 'xb-self-test.c', 'xb-query.c', + 'xb-query-context.c', 'xb-silo.c', 'xb-silo-export.c', + 'xb-silo-node.c', 'xb-silo-query.c', 'xb-stack.c', 'xb-string.c', + 'xb-value-bindings.c', ], include_directories : [ configinc, ], - c_args : [ - '-DTESTDIR="@0@/../data"'.format(meson.current_source_dir()), - '-DINSTALLEDTESTDIR="' + installed_test_datadir + '"', - ], dependencies : [ gio, libxmlb_dep, @@ -219,5 +238,5 @@ install : true, install_dir : installed_test_bindir ) - test('xb-self-test', e) + test('xb-self-test', e, env : testdatadirs) endif diff -Nru libxmlb-0.1.15/src/xb-builder.c libxmlb-0.3.6/src/xb-builder.c --- libxmlb-0.1.15/src/xb-builder.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder.c 2021-12-06 20:36:37.000000000 +0000 @@ -13,13 +13,13 @@ #include "xb-silo-private.h" #include "xb-string-private.h" +#include "xb-opcode-private.h" #include "xb-builder.h" #include "xb-builder-fixup-private.h" #include "xb-builder-source-private.h" #include "xb-builder-node-private.h" typedef struct { - GObject parent_instance; GPtrArray *sources; /* of XbBuilderSource */ GPtrArray *nodes; /* of XbBuilderNode */ GPtrArray *fixups; /* of XbBuilderFixup */ @@ -171,14 +171,9 @@ xb_builder_node_set_tail (bc, text, text_len); return; } - if (!xb_builder_node_has_flag (bn, XB_BUILDER_NODE_FLAG_HAS_TAIL)) { - xb_builder_node_set_tail (bn, text, text_len); - return; - } - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "Mismatched XML; cannot store %s", text); + + /* always set a tail, even if already set */ + xb_builder_node_set_tail (bn, text, text_len); } /** @@ -221,7 +216,7 @@ g_autoptr(GPtrArray) children_copy = NULL; g_autoptr(GInputStream) istream = NULL; g_autoptr(GMarkupParseContext) ctx = NULL; - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GTimer) timer = xb_silo_start_profile (helper->silo); g_autoptr(XbBuilderNode) root_tmp = xb_builder_node_new (NULL); const GMarkupParser parser = { xb_builder_compile_start_element_cb, @@ -265,6 +260,17 @@ if (!xb_builder_source_fixup (source, root_tmp, error)) return FALSE; + /* a single root with no siblings was required */ + if (helper->compile_flags & XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT) { + if (xb_builder_node_get_children (root_tmp)->len > 1) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "A root node without siblings was required"); + return FALSE; + } + } + /* this is something we can query with later */ info = xb_builder_source_get_info (source); if (info != NULL) { @@ -321,7 +327,7 @@ if (xb_builder_node_has_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; attrs = xb_builder_node_get_attrs (bn); - for (guint i = 0; i < attrs->len; i++) { + for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *attr = g_ptr_array_index (attrs, i); attr->name_idx = xb_builder_compile_add_to_strtab (helper, attr->name); } @@ -340,7 +346,7 @@ if (xb_builder_node_has_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE)) return FALSE; attrs = xb_builder_node_get_attrs (bn); - for (guint i = 0; i < attrs->len; i++) { + for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *attr = g_ptr_array_index (attrs, i); attr->value_idx = xb_builder_compile_add_to_strtab (helper, attr->value); } @@ -370,6 +376,28 @@ } static gboolean +xb_builder_strtab_tokens_cb (XbBuilderNode *bn, gpointer user_data) +{ + XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *) user_data; + GPtrArray *tokens = xb_builder_node_get_tokens (bn); + + /* root node */ + if (xb_builder_node_get_element (bn) == NULL) + return FALSE; + if (xb_builder_node_has_flag (bn, XB_BUILDER_NODE_FLAG_IGNORE)) + return FALSE; + if (tokens == NULL) + return FALSE; + for (guint i = 0; i < MIN(tokens->len, XB_OPCODE_TOKEN_MAX); i++) { + const gchar *tmp = g_ptr_array_index (tokens, i); + if (tmp == NULL) + continue; + xb_builder_node_add_token_idx (bn, xb_builder_compile_add_to_strtab (helper, tmp)); + } + return FALSE; +} + +static gboolean xb_builder_xml_lang_prio_cb (XbBuilderNode *bn, gpointer user_data) { GPtrArray *nodes_to_destroy = (GPtrArray *) user_data; @@ -441,8 +469,8 @@ xb_builder_nodetab_write_sentinel (XbBuilderNodetabHelper *helper) { XbSiloNode sn = { - .is_node = FALSE, - .nr_attrs = 0, + .flags = XB_SILO_NODE_FLAG_NONE, + .attr_count = 0, }; // g_debug ("SENT @%u", (guint) helper->buf->len); XB_SILO_APPENDBUF (helper->buf, &sn, xb_silo_node_get_size (&sn)); @@ -452,16 +480,22 @@ xb_builder_nodetab_write_node (XbBuilderNodetabHelper *helper, XbBuilderNode *bn) { GPtrArray *attrs = xb_builder_node_get_attrs (bn); + GArray *token_idxs = xb_builder_node_get_token_idxs (bn); XbSiloNode sn = { - .is_node = TRUE, - .nr_attrs = attrs->len, + .flags = XB_SILO_NODE_FLAG_IS_ELEMENT, + .attr_count = (attrs != NULL) ? attrs->len : 0, .element_name = xb_builder_node_get_element_idx (bn), .next = 0x0, .parent = 0x0, .text = xb_builder_node_get_text_idx (bn), .tail = xb_builder_node_get_tail_idx (bn), + .token_count = 0, }; + /* add tokens */ + if (token_idxs != NULL) + sn.flags |= XB_SILO_NODE_FLAG_IS_TOKENIZED; + /* if the node had no children and the text is just whitespace then * remove it even in literal mode */ if (xb_builder_node_has_flag (bn, XB_BUILDER_NODE_FLAG_LITERAL_TEXT)) { @@ -476,18 +510,28 @@ // g_debug ("NODE @%u (%s)", (guint) helper->buf->len, xb_builder_node_get_element (bn)); + /* there is no point adding more tokens than we can match */ + if (token_idxs != NULL) + sn.token_count = MIN (token_idxs->len, XB_OPCODE_TOKEN_MAX); + /* add to the buf */ XB_SILO_APPENDBUF (helper->buf, &sn, sizeof(XbSiloNode)); /* add to the buf */ - for (guint i = 0; i < attrs->len; i++) { + for (guint i = 0; attrs != NULL && i < attrs->len; i++) { XbBuilderNodeAttr *ba = g_ptr_array_index (attrs, i); - XbSiloAttr attr = { + XbSiloNodeAttr attr = { .attr_name = ba->name_idx, .attr_value = ba->value_idx, }; XB_SILO_APPENDBUF (helper->buf, &attr, sizeof(attr)); } + + /* add tokens */ + for (guint i = 0; i < sn.token_count; i++) { + guint32 idx = g_array_index (token_idxs, guint32, i); + XB_SILO_APPENDBUF (helper->buf, &idx, sizeof(idx)); + } } static void @@ -648,11 +692,18 @@ { XbBuilderPrivate *priv = GET_PRIVATE (self); GFile *file = xb_builder_source_get_file (source); + g_autoptr(GFile) watched_file = NULL; if (file == NULL) return TRUE; - if ((xb_builder_source_get_flags (source) & XB_BUILDER_SOURCE_FLAG_WATCH_FILE) == 0) + if ((xb_builder_source_get_flags (source) & (XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY)) == 0) return TRUE; - if (!xb_silo_watch_file (priv->silo, file, cancellable, error)) + + if (xb_builder_source_get_flags (source) & XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY) + watched_file = g_file_get_parent (file); + else + watched_file = g_object_ref (file); + + if (!xb_silo_watch_file (priv->silo, watched_file, cancellable, error)) return FALSE; return TRUE; } @@ -701,7 +752,7 @@ .buf = NULL, }; g_autoptr(GPtrArray) nodes_to_destroy = g_ptr_array_new (); - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GTimer) timer = xb_silo_start_profile (priv->silo); g_autoptr(XbBuilderCompileHelper) helper = NULL; g_return_val_if_fail (XB_IS_BUILDER (self), NULL); @@ -748,7 +799,8 @@ root = g_object_ref (helper->root); } - g_debug ("compiling %s…", source_guid); + if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) + g_debug ("compiling %s…", source_guid); if (!xb_builder_compile_source (helper, source, root, cancellable, &error_local)) { if (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID) { @@ -799,7 +851,7 @@ buf = g_string_sized_new (nodetabsz); xb_silo_add_profile (priv->silo, timer, "get size nodetab"); - /* add element names, attr name, attr value, then text to the strtab */ + /* add everything to the strtab */ xb_builder_node_traverse (helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_element_names_cb, helper); hdr.strtab_ntags = g_hash_table_size (helper->strtab_hash); @@ -813,6 +865,9 @@ xb_builder_node_traverse (helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xb_builder_strtab_text_cb, helper); xb_silo_add_profile (priv->silo, timer, "adding strtab text"); + xb_builder_node_traverse (helper->root, G_PRE_ORDER, G_TRAVERSE_ALL, -1, + xb_builder_strtab_tokens_cb, helper); + xb_silo_add_profile (priv->silo, timer, "adding strtab tokens"); /* add the initial header */ hdr.strtab = nodetabsz; @@ -861,6 +916,9 @@ * If @silo is being used by a query (e.g. in another thread) then all node * data is immediately invalid. * + * The returned #XbSilo will use the thread-default main context at the time of + * calling this function for its future signal emissions. + * * Returns: (transfer full): a #XbSilo, or %NULL for error * * Since: 0.1.0 @@ -914,6 +972,7 @@ if (g_strcmp0 (xb_silo_get_guid (silo_tmp), guid) == 0 || (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_GUID) > 0) { g_autoptr(GBytes) blob = xb_silo_get_bytes (silo_tmp); + g_debug ("loading silo with file contents"); if (!xb_silo_load_from_bytes (priv->silo, blob, load_flags, error)) @@ -1054,6 +1113,10 @@ * * Creates a new builder. * + * The #XbSilo returned by the methods of this #XbBuilder will use the + * thread-default main context at the time of calling this function for its + * future signal emissions. + * * Returns: a new #XbBuilder * * Since: 0.1.0 diff -Nru libxmlb-0.1.15/src/xb-builder-fixup.c libxmlb-0.3.6/src/xb-builder-fixup.c --- libxmlb-0.1.15/src/xb-builder-fixup.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-fixup.c 2021-12-06 20:36:37.000000000 +0000 @@ -13,7 +13,6 @@ #include "xb-builder-fixup-private.h" typedef struct { - GObject parent_instance; gchar *id; XbBuilderFixupFunc func; gpointer user_data; @@ -90,16 +89,16 @@ gchar * xb_builder_fixup_get_guid (XbBuilderFixup *self) { - GString *str = g_string_new ("func-id="); + g_autoptr(GString) str = g_string_new ("func-id="); XbBuilderFixupPrivate *priv = GET_PRIVATE (self); - g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), 0); + g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), NULL); /* build GUID using ID and max-depth, if set */ g_string_append (str, priv->id); if (priv->max_depth != -1) g_string_append_printf (str, "@%i", priv->max_depth); - return g_string_free (str, FALSE); + return g_string_free (g_steal_pointer (&str), FALSE); } /** diff -Nru libxmlb-0.1.15/src/xb-builder.h libxmlb-0.3.6/src/xb-builder.h --- libxmlb-0.1.15/src/xb-builder.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder.h 2021-12-06 20:36:37.000000000 +0000 @@ -39,6 +39,7 @@ * @XB_BUILDER_COMPILE_FLAG_SINGLE_LANG: Only store a single language * @XB_BUILDER_COMPILE_FLAG_WATCH_BLOB: Watch the XMLB file for changes * @XB_BUILDER_COMPILE_FLAG_IGNORE_GUID: Ignore the cache GUID value + * @XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT: Require at most one root node * * The flags for converting to XML. **/ @@ -49,6 +50,7 @@ XB_BUILDER_COMPILE_FLAG_SINGLE_LANG = 1 << 3, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_WATCH_BLOB = 1 << 4, /* Since: 0.1.0 */ XB_BUILDER_COMPILE_FLAG_IGNORE_GUID = 1 << 5, /* Since: 0.1.7 */ + XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT = 1 << 6, /* Since: 0.3.4 */ /*< private >*/ XB_BUILDER_COMPILE_FLAG_LAST } XbBuilderCompileFlags; diff -Nru libxmlb-0.1.15/src/xb-builder-node.c libxmlb-0.3.6/src/xb-builder-node.c --- libxmlb-0.1.15/src/xb-builder-node.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-node.c 2021-12-06 20:36:37.000000000 +0000 @@ -12,11 +12,11 @@ #include #include "xb-builder-node-private.h" +#include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-string-private.h" typedef struct { - GObject parent_instance; guint32 offset; gint priority; XbBuilderNodeFlags flags; @@ -27,13 +27,26 @@ gchar *tail; guint32 tail_idx; XbBuilderNode *parent; /* noref */ - GPtrArray *children; /* of XbBuilderNode */ - GPtrArray *attrs; /* of XbBuilderNodeAttr */ + + /* Around 87% of all XML nodes have zero children, so this array is only + * allocated if it’s non-empty. %NULL means an empty array. */ + GPtrArray *children; /* (element-type XbBuilderNode) (nullable) */ + + /* Around 80% of all XML nodes have zero attributes, so this array is only + * allocated if it’s non-empty. %NULL means an empty array. */ + GPtrArray *attrs; /* (element-type XbBuilderNodeAttr) (nullable) */ + + /* Most nodes will have no tokens */ + GPtrArray *tokens; /* (element-type utf8) (nullable) */ + GArray *token_idxs; /* (element-type guint32) (nullable) */ + } XbBuilderNodePrivate; G_DEFINE_TYPE_WITH_PRIVATE (XbBuilderNode, xb_builder_node, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_builder_node_get_instance_private (o)) +static void xb_builder_node_attr_free (XbBuilderNodeAttr *attr); + /** * xb_builder_node_has_flag: * @self: a #XbBuilderNode @@ -67,8 +80,16 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_BUILDER_NODE (self)); + + if ((priv->flags & flag) != 0) + return; + + /* do in-place */ + if ((flag & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL) + g_strstrip (priv->text); + priv->flags |= flag; - for (guint i = 0; i < priv->children->len; i++) { + for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) { XbBuilderNode *c = g_ptr_array_index (priv->children, i); xb_builder_node_add_flag (c, flag); } @@ -127,6 +148,10 @@ XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL); g_return_val_if_fail (name != NULL, NULL); + + if (priv->attrs == NULL) + return NULL; + for (guint i = 0; i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i); if (g_strcmp0 (a->name, name) == 0) @@ -215,6 +240,7 @@ } /* private */ +/* Returns NULL if the array is empty */ GPtrArray * xb_builder_node_get_attrs (XbBuilderNode *self) { @@ -231,6 +257,10 @@ g_auto(GStrv) split = NULL; gsize text_len_safe; + /* sanity check */ + if (text == NULL) + return NULL; + /* we know this has been pre-fixed */ text_len_safe = text_len >= 0 ? (gsize) text_len : strlen (text); if (xb_builder_node_has_flag (self, XB_BUILDER_NODE_FLAG_LITERAL_TEXT)) @@ -282,9 +312,72 @@ } /** + * xb_builder_node_tokenize_text: + * @self: a #XbBuilderNode + * + * Tokenize text added with xb_builder_node_set_text(). + * + * When searching, libxmlb often has to tokenize strings before they can be + * compared. This is done in the "fast path" and makes searching for non-ASCII + * text much slower. + * + * Adding the tokens to the deduplicated string table allows much faster + * searching at the expense of a ~5% size increase of the silo. + * + * This function adds all valid UTF-8 and ASCII search words generated from + * the value of xb_builder_node_set_text(). + * + * The transliteration locale (e.g. `en_GB`) is read from the `xml:lang` + * node attribute if set. + * + * Since: 0.3.1 + **/ +void +xb_builder_node_tokenize_text (XbBuilderNode *self) +{ + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + const gchar *xml_lang = xb_builder_node_get_attr (self, "xml:lang"); + guint ascii_tokens_sz; + guint tokens_sz; + g_autofree gchar **ascii_tokens = NULL; + g_autofree gchar **tokens = NULL; + + g_return_if_fail (XB_IS_BUILDER_NODE (self)); + + if (priv->text == NULL) + return; + tokens = g_str_tokenize_and_fold (priv->text, xml_lang, &ascii_tokens); + + /* preallocate the right array size (and more for invalid tokens) */ + tokens_sz = g_strv_length (tokens); + ascii_tokens_sz = g_strv_length (ascii_tokens); + if (priv->tokens == NULL) + priv->tokens = g_ptr_array_new_full (tokens_sz + ascii_tokens_sz, g_free); + + /* add all valid UTF-8 and ASCII tokens */ + for (guint i = 0; i < tokens_sz; i++) { + if (!xb_string_token_valid (tokens[i])) { + g_free (g_steal_pointer (&tokens[i])); + continue; + } + g_ptr_array_add (priv->tokens, g_steal_pointer (&tokens[i])); + } + for (guint i = 0; i < ascii_tokens_sz; i++) { + if (!xb_string_token_valid (ascii_tokens[i])) { + g_free (g_steal_pointer (&ascii_tokens[i])); + continue; + } + g_ptr_array_add (priv->tokens, g_steal_pointer (&ascii_tokens[i])); + } + + /* add this so we can set XbSiloNodeFlag.TOKENIZE_TEXT */ + xb_builder_node_add_flag (self, XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT); +} + +/** * xb_builder_node_set_text: * @self: a #XbBuilderNode - * @text: a string + * @text: (allow-none): a string * @text_len: length of @text, or -1 if @text is NUL terminated * * Sets the text on the builder node. @@ -297,18 +390,25 @@ XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_BUILDER_NODE (self)); - g_return_if_fail (text != NULL); /* old data */ g_free (priv->text); priv->text = xb_builder_node_parse_literal_text (self, text, text_len); priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TEXT; + + /* strip before tokenization */ + if ((priv->flags & XB_BUILDER_NODE_FLAG_STRIP_TEXT) > 0 && priv->text != NULL) + g_strstrip (priv->text); + + /* tokenize */ + if (priv->flags & XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT) + xb_builder_node_tokenize_text (self); } /** * xb_builder_node_set_tail: * @self: a #XbBuilderNode - * @tail: a string + * @tail: (allow-none): a string * @tail_len: length of @tail, or -1 if @tail is NUL terminated * * Sets the tail on the builder node. @@ -321,7 +421,6 @@ XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_BUILDER_NODE (self)); - g_return_if_fail (tail != NULL); /* old data */ g_free (priv->tail); @@ -348,6 +447,9 @@ g_return_if_fail (XB_IS_BUILDER_NODE (self)); g_return_if_fail (name != NULL); + if (priv->attrs == NULL) + priv->attrs = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_builder_node_attr_free); + /* check for existing name */ for (guint i = 0; i < priv->attrs->len; i++) { a = g_ptr_array_index (priv->attrs, i); @@ -384,6 +486,9 @@ g_return_if_fail (XB_IS_BUILDER_NODE (self)); g_return_if_fail (name != NULL); + if (priv->attrs == NULL) + return; + for (guint i = 0; i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i); if (g_strcmp0 (a->name, name) == 0) { @@ -433,7 +538,9 @@ /* no refcount */ priv_child->parent = self; - g_object_add_weak_pointer (G_OBJECT (self), (gpointer *) &priv_child->parent); + + if (priv->children == NULL) + priv->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); g_ptr_array_add (priv->children, g_object_ref (child)); } @@ -454,10 +561,10 @@ XbBuilderNodePrivate *priv_child = GET_PRIVATE (child); /* no refcount */ - g_object_remove_weak_pointer (G_OBJECT (self), (gpointer *) &priv_child->parent); priv_child->parent = NULL; - g_ptr_array_remove (priv->children, child); + if (priv->children != NULL) + g_ptr_array_remove (priv->children, child); } /** @@ -518,6 +625,12 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL); + + /* For backwards compatibility reasons we have to return a non-%NULL + * array here. */ + if (priv->children == NULL) + priv->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + return priv->children; } @@ -536,7 +649,7 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL); - if (priv->children->len == 0) + if (priv->children == NULL || priv->children->len == 0) return NULL; return g_ptr_array_index (priv->children, 0); } @@ -556,7 +669,7 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL); - if (priv->children->len == 0) + if (priv->children == NULL || priv->children->len == 0) return NULL; return g_ptr_array_index (priv->children, priv->children->len - 1); } @@ -581,6 +694,9 @@ g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL); g_return_val_if_fail (element != NULL, NULL); + if (priv->children == NULL) + return NULL; + for (guint i = 0; i < priv->children->len; i++) { XbBuilderNode *child = g_ptr_array_index (priv->children, i); if (g_strcmp0 (xb_builder_node_get_element (child), element) != 0) @@ -605,16 +721,17 @@ XbBuilderNode *bn, gint depth) { - GPtrArray *children = xb_builder_node_get_children (bn); + XbBuilderNodePrivate *priv = GET_PRIVATE (bn); + GPtrArray *children = priv->children; /* only leaves */ if (helper->flags == G_TRAVERSE_LEAVES && - children->len > 0) + children != NULL && children->len > 0) return; /* only non-leaves */ if (helper->flags == G_TRAVERSE_NON_LEAVES && - children->len == 0) + (children == NULL || children->len == 0)) return; /* recurse */ @@ -622,7 +739,7 @@ if (helper->func (bn, helper->user_data)) return; } - if (helper->max_depth < 0 || depth < helper->max_depth) { + if ((helper->max_depth < 0 || depth < helper->max_depth) && children != NULL) { for (guint i = 0; i < children->len; i++) { XbBuilderNode *bc = g_ptr_array_index (children, i); xb_builder_node_traverse_cb (helper, bc, depth + 1); @@ -708,6 +825,10 @@ }; g_return_if_fail (XB_IS_BUILDER_NODE (self)); g_return_if_fail (func != NULL); + + if (priv->children == NULL) + return; + g_ptr_array_sort_with_data (priv->children, xb_builder_node_sort_children_cb, &helper); @@ -809,7 +930,9 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); guint32 sz = sizeof(XbSiloNode); - return sz + priv->attrs->len * sizeof(XbSiloAttr); + gsize attr_len = (priv->attrs != NULL) ? priv->attrs->len : 0; + gsize token_len = (priv->tokens != NULL) ? MIN(priv->tokens->len, XB_OPCODE_TOKEN_MAX) : 0; + return sz + attr_len * sizeof(XbSiloNodeAttr) + token_len * sizeof(guint32); } static void @@ -827,8 +950,26 @@ priv->element_idx = XB_SILO_UNSET; priv->text_idx = XB_SILO_UNSET; priv->tail_idx = XB_SILO_UNSET; - priv->attrs = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_builder_node_attr_free); - priv->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + priv->attrs = NULL; /* only allocated when an attribute is added */ + priv->children = NULL; /* only allocated when a child is added */ +} + +static void +xb_builder_node_dispose (GObject *obj) +{ + XbBuilderNode *self = XB_BUILDER_NODE (obj); + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + + /* clear all the child nodes’ parent pointers */ + if (priv->children != NULL) { + for (guint i = 0; i < priv->children->len; i++) { + XbBuilderNode *child = g_ptr_array_index (priv->children, i); + XbBuilderNodePrivate *priv_child = GET_PRIVATE (child); + priv_child->parent = NULL; + } + } + + G_OBJECT_CLASS (xb_builder_node_parent_class)->dispose (obj); } static void @@ -839,8 +980,10 @@ g_free (priv->element); g_free (priv->text); g_free (priv->tail); - g_ptr_array_unref (priv->attrs); - g_ptr_array_unref (priv->children); + g_clear_pointer (&priv->attrs, g_ptr_array_unref); + g_clear_pointer (&priv->children, g_ptr_array_unref); + g_clear_pointer (&priv->tokens, g_ptr_array_unref); + g_clear_pointer (&priv->token_idxs, g_array_unref); G_OBJECT_CLASS (xb_builder_node_parent_class)->finalize (obj); } @@ -848,6 +991,8 @@ xb_builder_node_class_init (XbBuilderNodeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = xb_builder_node_dispose; object_class->finalize = xb_builder_node_finalize; } @@ -966,6 +1111,10 @@ { XbBuilderNodePrivate *priv = GET_PRIVATE (self); + /* do not output */ + if (xb_builder_node_has_flag (self, XB_BUILDER_NODE_FLAG_IGNORE)) + return TRUE; + /* add start of opening tag */ if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) { for (guint i = 0; i < helper->level; i++) @@ -974,7 +1123,7 @@ g_string_append_printf (helper->xml, "<%s", priv->element); /* add any attributes */ - for (guint i = 0; i < priv->attrs->len; i++) { + for (guint i = 0; priv->attrs != NULL && i < priv->attrs->len; i++) { XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i); g_autofree gchar *key = xb_string_xml_escape (a->name); g_autofree gchar *val = xb_string_xml_escape (a->value); @@ -993,7 +1142,7 @@ } /* recurse deeper */ - for (guint i = 0; i < priv->children->len; i++) { + for (guint i = 0; priv->children != NULL && i < priv->children->len; i++) { XbBuilderNode *child = g_ptr_array_index (priv->children, i); helper->level++; if (!xb_builder_node_export_helper (child, helper, error)) @@ -1001,12 +1150,6 @@ helper->level--; } - /* add any tail if it exists */ - if (priv->tail != NULL) { - g_autofree gchar *tail = xb_string_xml_escape (priv->tail); - g_string_append (helper->xml, tail); - } - /* add closing tag */ if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && priv->text == NULL) { @@ -1014,6 +1157,13 @@ g_string_append (helper->xml, " "); } g_string_append_printf (helper->xml, "", priv->element); + + /* add any tail if it exists */ + if (priv->tail != NULL) { + g_autofree gchar *tail = xb_string_xml_escape (priv->tail); + g_string_append (helper->xml, tail); + } + if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) g_string_append (helper->xml, "\n"); return TRUE; @@ -1048,3 +1198,66 @@ return NULL; return g_string_free (g_steal_pointer (&xml), FALSE); } + +/** + * xb_builder_node_add_token: + * @self: a #XbBuilderNode + * @token: a new token + * + * Adds a token to the builder node. + * + * Since: 0.3.1 + **/ +void +xb_builder_node_add_token (XbBuilderNode *self, const gchar *token) +{ + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (token != NULL); + + if (priv->tokens == NULL) + priv->tokens = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (priv->tokens, g_strdup (token)); +} + +/** + * xb_builder_node_get_tokens: + * @self: a #XbBuilderNode + * + * Gets the tokens of the builder node. + * + * Returns: (transfer none) (element-type utf8) (nullable): tokens + * + * Since: 0.3.1 + **/ +GPtrArray * +xb_builder_node_get_tokens (XbBuilderNode *self) +{ + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + g_return_val_if_fail (self != NULL, NULL); + return priv->tokens; +} + +/* private */ +void +xb_builder_node_add_token_idx (XbBuilderNode *self, guint32 tail_idx) +{ + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (tail_idx != XB_SILO_UNSET); + + if (priv->token_idxs == NULL) + priv->token_idxs = g_array_new (FALSE, FALSE, sizeof(guint32)); + g_array_append_val (priv->token_idxs, tail_idx); +} + +/* Returns: (transfer none) (element-type guint32) (nullable): token indexes */ +GArray * +xb_builder_node_get_token_idxs (XbBuilderNode *self) +{ + XbBuilderNodePrivate *priv = GET_PRIVATE (self); + g_return_val_if_fail (self != NULL, NULL); + return priv->token_idxs; +} diff -Nru libxmlb-0.1.15/src/xb-builder-node.h libxmlb-0.3.6/src/xb-builder-node.h --- libxmlb-0.1.15/src/xb-builder-node.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-node.h 2021-12-06 20:36:37.000000000 +0000 @@ -34,6 +34,8 @@ * @XB_BUILDER_NODE_FLAG_LITERAL_TEXT: Assume the node CDATA is already valid * @XB_BUILDER_NODE_FLAG_HAS_TEXT: If the node has leading text * @XB_BUILDER_NODE_FLAG_HAS_TAIL: If the node has trailing text + * @XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT: Tokenize and fold text to ASCII (Since: 0.3.1) + * @XB_BUILDER_NODE_FLAG_STRIP_TEXT: Strip leading and trailing spaces from text (Since: 0.3.4) * * The flags used when building a node. **/ @@ -43,6 +45,8 @@ XB_BUILDER_NODE_FLAG_LITERAL_TEXT = 1 << 1, /* Since: 0.1.0 */ XB_BUILDER_NODE_FLAG_HAS_TEXT = 1 << 2, /* Since: 0.1.12 */ XB_BUILDER_NODE_FLAG_HAS_TAIL = 1 << 3, /* Since: 0.1.12 */ + XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT = 1 << 4, /* Since: 0.3.1 */ + XB_BUILDER_NODE_FLAG_STRIP_TEXT = 1 << 5, /* Since: 0.3.4 */ /*< private >*/ XB_BUILDER_NODE_FLAG_LAST } XbBuilderNodeFlags; @@ -74,6 +78,7 @@ void xb_builder_node_set_text (XbBuilderNode *self, const gchar *text, gssize text_len); +void xb_builder_node_tokenize_text (XbBuilderNode *self); const gchar *xb_builder_node_get_tail (XbBuilderNode *self); void xb_builder_node_set_tail (XbBuilderNode *self, const gchar *tail, @@ -112,5 +117,8 @@ gchar *xb_builder_node_export (XbBuilderNode *self, XbNodeExportFlags flags, GError **error); +GPtrArray *xb_builder_node_get_tokens (XbBuilderNode *self); +void xb_builder_node_add_token (XbBuilderNode *self, + const gchar *token); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-builder-node-private.h libxmlb-0.3.6/src/xb-builder-node-private.h --- libxmlb-0.1.15/src/xb-builder-node-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-node-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -37,5 +37,8 @@ guint32 xb_builder_node_get_tail_idx (XbBuilderNode *self); void xb_builder_node_set_tail_idx (XbBuilderNode *self, guint32 tail_idx); +void xb_builder_node_add_token_idx (XbBuilderNode *self, + guint32 tail_idx); +GArray *xb_builder_node_get_token_idxs (XbBuilderNode *self); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-builder-source.c libxmlb-0.3.6/src/xb-builder-source.c --- libxmlb-0.1.15/src/xb-builder-source.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-source.c 2021-12-06 20:36:37.000000000 +0000 @@ -14,9 +14,9 @@ #include "xb-builder-fixup-private.h" #include "xb-builder-source-ctx-private.h" #include "xb-builder-source-private.h" +#include "xb-lzma-decompressor.h" typedef struct { - GObject parent_instance; GInputStream *istream; GFile *file; GPtrArray *fixups; /* of XbBuilderFixup */ @@ -266,56 +266,6 @@ g_ptr_array_add (priv->fixups, g_object_ref (fixup)); } -/** - * xb_builder_source_add_node_func: - * @self: a #XbBuilderSource - * @id: a text ID value, e.g. `AppStreamUpgrade` - * @func: a callback - * @user_data: user pointer to pass to @func, or %NULL - * @user_data_free: a function which gets called to free @user_data, or %NULL - * - * Adds a function that will get run on every #XbBuilderNode compile creates. - * - * Since: 0.1.0 - **/ -void -xb_builder_source_add_node_func (XbBuilderSource *self, - const gchar *id, - XbBuilderSourceNodeFunc func, - gpointer user_data, - GDestroyNotify user_data_free) -{ - g_autoptr(XbBuilderFixup) fixup = NULL; - /* close enough... */ - fixup = xb_builder_fixup_new (id, (XbBuilderFixupFunc) func, - user_data, user_data_free); - xb_builder_source_add_fixup (self, fixup); -} - -/** - * xb_builder_source_add_converter: - * @self: a #XbBuilderSource - * @content_types: mimetypes, e.g. `application/x-desktop,application/gzip` - * @func: a callback - * @user_data: user pointer to pass to @func, or %NULL - * @user_data_free: a function which gets called to free @user_data, or %NULL - * - * This function is now deprecated, and does nothing. - * - * See also: xb_builder_source_add_adapter() - * - * Since: 0.1.1 - **/ -void -xb_builder_source_add_converter (XbBuilderSource *self, - const gchar *content_types, - XbBuilderSourceConverterFunc func, - gpointer user_data, - GDestroyNotify user_data_free) -{ - g_warning ("%s() does nothing", G_STRFUNC); -} - static void xb_builder_source_init_adapter (XbBuilderSource *self, const gchar *content_types, @@ -421,7 +371,7 @@ xb_builder_source_get_guid (XbBuilderSource *self) { XbBuilderSourcePrivate *priv = GET_PRIVATE (self); - GString *str = g_string_new (priv->guid); + g_autoptr(GString) str = g_string_new (priv->guid); g_return_val_if_fail (XB_IS_BUILDER_SOURCE (self), NULL); @@ -441,7 +391,7 @@ /* append prefix */ if (priv->prefix != NULL) g_string_append_printf (str, ":prefix=%s", priv->prefix); - return g_string_free (str, FALSE); + return g_string_free (g_steal_pointer (&str), FALSE); } const gchar * @@ -476,6 +426,7 @@ { XbBuilderSourcePrivate *priv = GET_PRIVATE (self); g_autofree gchar *basename = NULL; + GFile *file; g_return_val_if_fail (XB_IS_BUILDER_SOURCE (self), NULL); @@ -490,11 +441,13 @@ /* run the content type handlers until we get application/xml */ basename = g_file_get_basename (priv->file); + file = priv->file; + do { XbBuilderSourceAdapter *item; g_autofree gchar *content_type = NULL; g_autoptr(GInputStream) istream_tmp = NULL; - g_autoptr(XbBuilderSourceCtx) ctx = xb_builder_source_ctx_new (priv->istream); + g_autoptr(XbBuilderSourceCtx) ctx = xb_builder_source_ctx_new (file, priv->istream); /* get the content type of the stream */ xb_builder_source_ctx_set_filename (ctx, basename); @@ -523,6 +476,12 @@ xb_builder_source_remove_last_extension (basename); g_set_object (&priv->istream, istream_tmp); + /* the #GFile is only useful for the outermost input stream, + * for example it points to the .tar.gz file, while inner input + * streams are the .xml output of decompressing the .gz in + * memory and can’t be represented as a #GFile */ + file = NULL; + if (item->is_simple) break; } while (TRUE); @@ -558,6 +517,18 @@ return g_converter_input_stream_new (istream, conv); } +static GInputStream * +xb_builder_source_load_lzma_cb (XbBuilderSource *self, + XbBuilderSourceCtx *ctx, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + GInputStream *istream = xb_builder_source_ctx_get_stream (ctx); + g_autoptr(GConverter) conv = G_CONVERTER (xb_lzma_decompressor_new ()); + return g_converter_input_stream_new (istream, conv); +} + static void xb_builder_source_adapter_free (XbBuilderSourceAdapter *item) { @@ -603,6 +574,8 @@ priv->adapters = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_builder_source_adapter_free); xb_builder_source_add_adapter (self, "application/gzip,application/x-gzip", xb_builder_source_load_gzip_cb, NULL, NULL); + xb_builder_source_add_adapter (self, "application/x-xz", + xb_builder_source_load_lzma_cb, NULL, NULL); } /** diff -Nru libxmlb-0.1.15/src/xb-builder-source-ctx.c libxmlb-0.3.6/src/xb-builder-source-ctx.c --- libxmlb-0.1.15/src/xb-builder-source-ctx.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-source-ctx.c 2021-12-06 20:36:37.000000000 +0000 @@ -11,11 +11,12 @@ #include #include "xb-builder-source-ctx-private.h" +#include "xb-common-private.h" typedef struct { - GObject parent_instance; + GFile *file; GInputStream *istream; - gchar *filename; + gchar *basename; gchar *content_type; } XbBuilderSourceCtxPrivate; @@ -40,6 +41,43 @@ return priv->istream; } +static GBytes * +_g_input_stream_read_bytes_in_chunks (GInputStream *stream, + gsize count, + gsize chunk_sz, + GCancellable *cancellable, + GError **error) +{ + g_autofree guint8 *tmp = NULL; + g_autoptr(GByteArray) buf = g_byte_array_new (); + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL); + g_return_val_if_fail (count > 0, NULL); + g_return_val_if_fail (chunk_sz > 0, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* read from stream in chunks */ + tmp = g_malloc (chunk_sz); + while (TRUE) { + gssize sz; + sz = g_input_stream_read (stream, tmp, chunk_sz, NULL, error); + if (sz == 0) + break; + if (sz < 0) + return NULL; + g_byte_array_append (buf, tmp, sz); + if (buf->len > count) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "cannot read from fd: 0x%x > 0x%x", + buf->len, (guint) count); + return NULL; + } + } + return g_byte_array_free_to_bytes (g_steal_pointer (&buf)); +} + /** * xb_builder_source_ctx_get_bytes: * @self: a #XbBuilderSourceCtx @@ -48,7 +86,11 @@ * * Returns the data currently being processed. * - * Returns: (transfer none): a #GInputStream + * If the #XbBuilderSourceCtx is backed by a file, the returned #GBytes may be + * memory-mapped, and the backing file must not be modified until the #GBytes is + * destroyed. + * + * Returns: (transfer full): a #GBytes * * Since: 0.1.7 **/ @@ -61,9 +103,23 @@ g_return_val_if_fail (XB_IS_BUILDER_SOURCE_CTX (self), NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return g_input_stream_read_bytes (priv->istream, - 128 * 1024 * 1024, /* 128Mb */ - cancellable, error); + + /* Try mmap()ing the file first, as that avoids buffer allocation. + * Note that this imposes the restriction that the backing file must not + * be modified during the lifetime of the returned #GBytes. */ + if (priv->file != NULL) { + g_autoptr(GMappedFile) mapped_file = NULL; + g_autofree gchar *filename = g_file_get_path (priv->file); + + mapped_file = g_mapped_file_new (filename, FALSE, NULL); + if (mapped_file != NULL) + return g_mapped_file_get_bytes (mapped_file); + } + + return _g_input_stream_read_bytes_in_chunks (priv->istream, + 128 * 1024 * 1024, /* 128Mb */ + 32 * 1024, /* 32Kb */ + cancellable, error); } /** @@ -72,7 +128,7 @@ * * Returns the basename of the file currently being processed. * - * Returns: a filename, or %NULL if unset + * Returns: (transfer none) (nullable): a basename, or %NULL if unset * * Since: 0.1.7 **/ @@ -81,17 +137,19 @@ { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_BUILDER_SOURCE_CTX (self), NULL); - return priv->filename; + return priv->basename; } /** * xb_builder_source_ctx_get_content_type: * @self: a #XbBuilderSourceCtx + * @cancellable: a #GCancellable, or %NULL + * @error: the #GError, or %NULL * * Returns the content type of the input stream currently being * processed. * - * Returns: (transfer full): a content type (e.g. `application/x-desktop`), or %NULL + * Returns: (transfer full): a content type (e.g. `application/x-desktop`), or %NULL on error * * Since: 0.1.7 **/ @@ -101,50 +159,33 @@ GError **error) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE (self); - g_autofree gchar *content_type = NULL; + gsize bufsz = 0; + guchar buf[4096] = { 0x00 }; g_return_val_if_fail (XB_IS_BUILDER_SOURCE_CTX (self), NULL); if (G_IS_SEEKABLE (priv->istream)) { - gsize bufsz = 0; - guchar buf[4096] = { 0x00 }; if (!g_input_stream_read_all (priv->istream, buf, sizeof(buf), &bufsz, cancellable, error)) return NULL; if (!g_seekable_seek (G_SEEKABLE (priv->istream), 0, G_SEEK_SET, cancellable, error)) return NULL; - if (bufsz > 0) - content_type = g_content_type_guess (priv->filename, buf, bufsz, NULL); } - - /* either unseekable, or empty */ - if (content_type == NULL) - content_type = g_content_type_guess (priv->filename, NULL, 0, NULL); - -#ifdef _WIN32 - /* map Windows "mime-type" to a content type */ - if (g_strcmp0 (content_type, ".gz") == 0) - return g_strdup ("application/gzip"); - if (g_strcmp0 (content_type, ".txt") == 0 || - g_strcmp0 (content_type, ".xml") == 0) - return g_strdup ("application/xml"); - if (g_strcmp0 (content_type, ".desktop") == 0) - return g_strdup ("application/x-desktop"); -#endif - - return g_steal_pointer (&content_type); + if (bufsz > 0) + return xb_content_type_guess (priv->basename, buf, bufsz); + return xb_content_type_guess (priv->basename, NULL, 0); } /* private */ void -xb_builder_source_ctx_set_filename (XbBuilderSourceCtx *self, const gchar *filename) +xb_builder_source_ctx_set_filename (XbBuilderSourceCtx *self, const gchar *basename) { XbBuilderSourceCtxPrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_BUILDER_SOURCE_CTX (self)); - g_return_if_fail (filename != NULL); - g_free (priv->filename); - priv->filename = g_strdup (filename); + g_return_if_fail (basename != NULL); + g_free (priv->basename); + priv->basename = g_strdup (basename); } static void @@ -157,8 +198,9 @@ { XbBuilderSourceCtx *self = XB_BUILDER_SOURCE_CTX (obj); XbBuilderSourceCtxPrivate *priv = GET_PRIVATE (self); - g_free (priv->filename); + g_free (priv->basename); g_object_unref (priv->istream); + g_clear_object (&priv->file); G_OBJECT_CLASS (xb_builder_source_ctx_parent_class)->finalize (obj); } @@ -171,7 +213,9 @@ /** * xb_builder_source_ctx_new: - * @element: An element name, e.g. "component" + * @file: (transfer none) (nullable): Path to the file which contains the source, + * or %NULL if the source is not directly loadable from disk + * @istream: (transfer none): Input stream to load the source from * * Creates a new builder source_ctx. * @@ -180,10 +224,15 @@ * Since: 0.1.7 **/ XbBuilderSourceCtx * -xb_builder_source_ctx_new (GInputStream *istream) +xb_builder_source_ctx_new (GFile *file, GInputStream *istream) { XbBuilderSourceCtx *self = g_object_new (XB_TYPE_BUILDER_SOURCE_CTX, NULL); XbBuilderSourceCtxPrivate *priv = GET_PRIVATE (self); + + g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); + g_return_val_if_fail (G_IS_INPUT_STREAM (istream), NULL); + + priv->file = (file != NULL) ? g_object_ref (file) : NULL; priv->istream = g_object_ref (istream); return self; } diff -Nru libxmlb-0.1.15/src/xb-builder-source-ctx-private.h libxmlb-0.3.6/src/xb-builder-source-ctx-private.h --- libxmlb-0.1.15/src/xb-builder-source-ctx-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-source-ctx-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -12,7 +12,8 @@ G_BEGIN_DECLS -XbBuilderSourceCtx *xb_builder_source_ctx_new (GInputStream *istream); +XbBuilderSourceCtx *xb_builder_source_ctx_new (GFile *file, + GInputStream *istream); void xb_builder_source_ctx_set_filename (XbBuilderSourceCtx *self, const gchar *filename); gchar *xb_builder_source_ctx_get_content_type (XbBuilderSourceCtx *self, diff -Nru libxmlb-0.1.15/src/xb-builder-source.h libxmlb-0.3.6/src/xb-builder-source.h --- libxmlb-0.1.15/src/xb-builder-source.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-builder-source.h 2021-12-06 20:36:37.000000000 +0000 @@ -35,6 +35,9 @@ * @XB_BUILDER_SOURCE_FLAG_NONE: No extra flags to use * @XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT: Do not attempt to repair XML whitespace * @XB_BUILDER_SOURCE_FLAG_WATCH_FILE: Watch the source file for changes + * @XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY: Watch the directory containing the source file for changes + * (for example, if watching all the sources in a directory — this allows the + * file monitors to be shared) * * The flags for converting to XML. **/ @@ -42,6 +45,7 @@ XB_BUILDER_SOURCE_FLAG_NONE = 0, /* Since: 0.1.0 */ XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT = 1 << 0, /* Since: 0.1.0 */ XB_BUILDER_SOURCE_FLAG_WATCH_FILE = 1 << 1, /* Since: 0.1.0 */ + XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY = 1 << 2, /* Since: 0.2.0 */ /*< private >*/ XB_BUILDER_SOURCE_FLAG_LAST } XbBuilderSourceFlags; @@ -50,11 +54,6 @@ XbBuilderNode *bn, gpointer user_data, GError **error); -typedef GInputStream *(*XbBuilderSourceConverterFunc) (XbBuilderSource *self, - GFile *file, - gpointer user_data, - GCancellable *cancellable, - GError **error); typedef GInputStream *(*XbBuilderSourceAdapterFunc) (XbBuilderSource *self, XbBuilderSourceCtx *ctx, gpointer user_data, @@ -79,20 +78,8 @@ XbBuilderNode *info); void xb_builder_source_set_prefix (XbBuilderSource *self, const gchar *prefix); -void xb_builder_source_add_node_func (XbBuilderSource *self, - const gchar *id, - XbBuilderSourceNodeFunc func, - gpointer user_data, - GDestroyNotify user_data_free) -G_DEPRECATED_FOR(xb_builder_source_add_fixup); void xb_builder_source_add_fixup (XbBuilderSource *self, XbBuilderFixup *fixup); -void xb_builder_source_add_converter (XbBuilderSource *self, - const gchar *content_types, - XbBuilderSourceConverterFunc func, - gpointer user_data, - GDestroyNotify user_data_free) -G_DEPRECATED_FOR(xb_builder_source_add_adapter); void xb_builder_source_add_adapter (XbBuilderSource *self, const gchar *content_types, XbBuilderSourceAdapterFunc func, diff -Nru libxmlb-0.1.15/src/xb-common.c libxmlb-0.3.6/src/xb-common.c --- libxmlb-0.1.15/src/xb-common.c 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-common.c 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "XbCommon" + +#include "config.h" + +#include +#include + +#include "xb-common-private.h" + +static const gchar * +xb_content_type_guess_from_fn (const gchar *filename) +{ + gchar *ext; /* no ownership */ + + g_return_val_if_fail (filename != NULL, NULL); + + /* get file extension with dot */ + ext = g_strrstr (filename, "."); + if (ext == NULL) + return NULL; + + /* map Windows "mime-type" to a content type */ + if (g_strcmp0 (ext, ".gz") == 0) + return "application/gzip"; + if (g_strcmp0 (ext, ".xz") == 0) + return "application/x-xz"; + if (g_strcmp0 (ext, ".txt") == 0 || + g_strcmp0 (ext, ".xml") == 0) + return "application/xml"; + if (g_strcmp0 (ext, ".desktop") == 0) + return "application/x-desktop"; + return NULL; +} + +static gboolean +xb_content_type_match (const guchar *buf, gsize bufsz, gsize offset, + const gchar *magic, gsize magic_size) +{ + /* document too small */ + if (offset + magic_size > bufsz) + return FALSE; + return memcmp (buf + offset, magic, magic_size) == 0; +} + +/** + * xb_content_type_guess: (skip) + * @filename: (nullable): filename + * @buf: (nullable): file data buffer + * @bufsz: size of file data buffer + * + * Guesses the content type based on example data. Either @filename or @buf may + * be %NULL, in which case the guess will be based solely on the other argument. + * + * Returns: a string indicating a guessed content type + **/ +gchar * +xb_content_type_guess (const gchar *filename, const guchar *buf, gsize bufsz) +{ + g_autofree gchar *content_type = NULL; + + /* check for bad results, e.g. from Chrome OS */ + content_type = g_content_type_guess (filename, buf, bufsz, NULL); + if (g_strstr_len (content_type, -1, "/") == NULL || + g_strcmp0 (content_type, "application/octet-stream") == 0 || + g_strcmp0 (content_type, "text/plain") == 0) { + + /* magic */ + if (bufsz > 0) { + if (xb_content_type_match (buf, bufsz, 0x0, "\x1f\x8b", 2)) + return g_strdup ("application/gzip"); + if (xb_content_type_match (buf, bufsz, 0x0, "\xfd\x37\x7a\x58\x5a\x00", 6)) + return g_strdup ("application/x-xz"); + if (xb_content_type_match (buf, bufsz, 0x0, " + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +gchar *xb_content_type_guess (const gchar *filename, + const guchar *buf, + gsize bufsz); +gboolean xb_file_set_contents (GFile *file, + const guint8 *buf, + gsize bufsz, + GCancellable *cancellable, + GError **error); diff -Nru libxmlb-0.1.15/src/xb-lzma-decompressor.c libxmlb-0.3.6/src/xb-lzma-decompressor.c --- libxmlb-0.1.15/src/xb-lzma-decompressor.c 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-lzma-decompressor.c 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 Shaun McCance + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "xb-lzma-decompressor.h" + +#include +#include + +#include + +#include + +static void xb_lzma_decompressor_iface_init (GConverterIface *iface); + +struct _XbLzmaDecompressor +{ + GObject parent_instance; + lzma_stream lzmastream; +}; + +G_DEFINE_TYPE_WITH_CODE (XbLzmaDecompressor, xb_lzma_decompressor, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER, + xb_lzma_decompressor_iface_init)) + +static void +xb_lzma_decompressor_finalize (GObject *object) +{ + XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR (object); + lzma_end (&self->lzmastream); + G_OBJECT_CLASS (xb_lzma_decompressor_parent_class)->finalize (object); +} + +static void +xb_lzma_decompressor_init (XbLzmaDecompressor *self) +{ +} + +static void +xb_lzma_decompressor_constructed (GObject *object) +{ + XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR (object); + lzma_stream tmp = LZMA_STREAM_INIT; + lzma_ret res; + + self->lzmastream = tmp; + res = lzma_auto_decoder (&self->lzmastream, SIZE_MAX, 0); + if (res == LZMA_MEM_ERROR) + g_error ("XbLzmaDecompressor: Not enough memory for lzma use"); + if (res == LZMA_OPTIONS_ERROR) + g_error ("XbLzmaDecompressor: Unsupported flags"); + if (res != LZMA_OK) + g_error ("XbLzmaDecompressor: Unexpected lzma error"); +} + +static void +xb_lzma_decompressor_class_init (XbLzmaDecompressorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = xb_lzma_decompressor_finalize; + object_class->constructed = xb_lzma_decompressor_constructed; +} + +XbLzmaDecompressor * +xb_lzma_decompressor_new (void) +{ + return g_object_new (XB_TYPE_LZMA_DECOMPRESSOR, NULL); +} + +static void +xb_lzma_decompressor_reset (GConverter *converter) +{ + XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR (converter); + lzma_ret res; + + /* lzma doesn't have a reset function. Ending and reiniting + * might do the trick. But this is untested. If reset matters + * to you, test this. + */ + lzma_end (&self->lzmastream); + res = lzma_code (&self->lzmastream, LZMA_RUN); + if (res == LZMA_MEM_ERROR ) + g_error ("XbLzmaDecompressor: Not enough memory for lzma use"); + if (res != LZMA_OK) + g_error ("XbLzmaDecompressor: Unexpected lzma error"); +} + +static GConverterResult +xb_lzma_decompressor_convert (GConverter *converter, + const void *inbuf, gsize inbuf_size, + void *outbuf, gsize outbuf_size, + GConverterFlags flags, + gsize *bytes_read, + gsize *bytes_written, + GError **error) +{ + XbLzmaDecompressor *self = XB_LZMA_DECOMPRESSOR (converter); + lzma_ret res; + + self->lzmastream.next_in = (void *)inbuf; + self->lzmastream.avail_in = inbuf_size; + self->lzmastream.next_out = outbuf; + self->lzmastream.avail_out = outbuf_size; + + res = lzma_code (&self->lzmastream, LZMA_RUN); + if (res == LZMA_DATA_ERROR) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Invalid compressed data"); + return G_CONVERTER_ERROR; + } + if (res == LZMA_MEM_ERROR) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Not enough memory"); + return G_CONVERTER_ERROR; + } + if (res == LZMA_OK || res == LZMA_STREAM_END) { + *bytes_read = inbuf_size - self->lzmastream.avail_in; + *bytes_written = outbuf_size - self->lzmastream.avail_out; + if (res == LZMA_STREAM_END) + return G_CONVERTER_FINISHED; + return G_CONVERTER_CONVERTED; + } + + g_assert_not_reached (); +} + +static void +xb_lzma_decompressor_iface_init (GConverterIface *iface) +{ + iface->convert = xb_lzma_decompressor_convert; + iface->reset = xb_lzma_decompressor_reset; +} diff -Nru libxmlb-0.1.15/src/xb-lzma-decompressor.h libxmlb-0.3.6/src/xb-lzma-decompressor.h --- libxmlb-0.1.15/src/xb-lzma-decompressor.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-lzma-decompressor.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2009 Shaun McCance + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define XB_TYPE_LZMA_DECOMPRESSOR (xb_lzma_decompressor_get_type ()) +G_DECLARE_FINAL_TYPE (XbLzmaDecompressor, xb_lzma_decompressor, XB, LZMA_DECOMPRESSOR, GObject) + +XbLzmaDecompressor *xb_lzma_decompressor_new (void); + +G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-machine.c libxmlb-0.3.6/src/xb-machine.c --- libxmlb-0.1.15/src/xb-machine.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-machine.c 2021-12-06 20:36:37.000000000 +0000 @@ -15,19 +15,19 @@ #include #endif -#include "xb-machine.h" +#include "xb-machine-private.h" #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-stack-private.h" #include "xb-string-private.h" typedef struct { - GObject parent_instance; XbMachineDebugFlags debug_flags; GPtrArray *methods; /* of XbMachineMethodItem */ GPtrArray *operators; /* of XbMachineOperator */ GPtrArray *text_handlers; /* of XbMachineTextHandlerItem */ GHashTable *opcode_fixup; /* of str[XbMachineOpcodeFixupItem] */ + GHashTable *opcode_tokens; /* of utf8 */ guint stack_size; } XbMachinePrivate; @@ -122,6 +122,10 @@ * Adds a new function to the virtual machine. Registered functions can then be * used as methods. * + * The @method_cb must not modify the stack it’s passed unless it’s going to + * succeed. In particular, if a method call is not optimisable, it must not + * modify the stack it’s passed. + * * You need to add a custom function using xb_machine_add_method() before using * methods that may reference it, for example xb_machine_add_opcode_fixup(). * @@ -220,27 +224,29 @@ } /** - * xb_machine_opcode_func_new: + * xb_machine_opcode_func_init: * @self: a #XbMachine + * @opcode: (out caller-allocates): a stack allocated #XbOpcode to initialise * @func_name: function name, e.g. `eq` * - * Creates a new opcode for a registered function. Some standard opcodes are - * registered by default, for instance `eq` or `ge`. Other opcodes have to be - * added using xb_machine_add_method(). - * - * Returns: a new #XbOpcode, or %NULL - * - * Since: 0.1.1 + * Initialises a stack allocated #XbOpcode for a registered function. + * Some standard functions are registered by default, for instance `eq` or `ge`. + * Other functions have to be added using xb_machine_add_method(). + * + * Returns: %TRUE if the function was found and the opcode initialised, %FALSE + * otherwise + * Since: 0.2.0 **/ -XbOpcode * -xb_machine_opcode_func_new (XbMachine *self, const gchar *func_name) +gboolean +xb_machine_opcode_func_init (XbMachine *self, XbOpcode *opcode, const gchar *func_name) { XbMachineMethodItem *item = xb_machine_find_func (self, func_name); if (item == NULL) - return NULL; - return xb_opcode_new (XB_OPCODE_KIND_FUNCTION, - g_strdup (func_name), - item->idx, g_free); + return FALSE; + xb_opcode_init (opcode, XB_OPCODE_KIND_FUNCTION, + g_strdup (func_name), + item->idx, g_free); + return TRUE; } static gboolean @@ -249,24 +255,18 @@ const gchar *func_name, GError **error) { - XbMachinePrivate *priv = GET_PRIVATE (self); - g_autoptr(XbOpcode) opcode = NULL; + XbOpcode *opcode; + + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; /* match opcode, which should always exist */ - opcode = xb_machine_opcode_func_new (self, func_name); - if (opcode == NULL) { + if (!xb_machine_opcode_func_init (self, opcode, func_name)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "built-in function not found: %s", func_name); - return FALSE; - } - if (!xb_stack_push_steal (opcodes, g_steal_pointer (&opcode))) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "stack size %u exhausted", - priv->stack_size); + xb_stack_pop (opcodes, NULL, NULL); return FALSE; } @@ -352,7 +352,10 @@ /* NULL is perfectly valid */ if (text == NULL) { - xb_stack_push_steal (opcodes, xb_opcode_text_new_static (str)); + XbOpcode *opcode; + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; + xb_opcode_text_init_static (opcode, str); return TRUE; } @@ -376,8 +379,11 @@ /* quoted text */ if (text_len >= 2) { if (str[0] == '\'' && str[text_len - 1] == '\'') { - gchar *tmp = g_strndup (str + 1, text_len - 2); - xb_stack_push_steal (opcodes, xb_opcode_text_new_steal (tmp)); + g_autofree gchar *tmp = g_strndup (str + 1, text_len - 2); + XbOpcode *opcode; + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; + xb_opcode_text_init_steal (opcode, g_steal_pointer (&tmp)); return TRUE; } } @@ -386,22 +392,33 @@ if (text_len >= 3) { if (str[0] == '$' && str[1] == '\'' && str[text_len - 1] == '\'') { gchar *tmp = g_strndup (str + 2, text_len - 3); - XbOpcode *op = xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT, - tmp, XB_SILO_UNSET, g_free); - xb_stack_push_steal (opcodes, op); + XbOpcode *opcode; + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; + xb_opcode_init (opcode, + XB_OPCODE_KIND_INDEXED_TEXT, + g_steal_pointer (&tmp), + XB_SILO_UNSET, + g_free); return TRUE; } } /* bind variables */ if (g_strcmp0 (str, "?") == 0) { - xb_stack_push_steal (opcodes, xb_opcode_bind_new ()); + XbOpcode *opcode; + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; + xb_opcode_bind_init (opcode); return TRUE; } /* check for plain integer */ if (g_ascii_string_to_unsigned (str, 10, 0, G_MAXUINT32, &val, NULL)) { - xb_stack_push_steal (opcodes, xb_opcode_integer_new (val)); + XbOpcode *opcode; + if (!xb_stack_push (opcodes, &opcode, error)) + return FALSE; + xb_opcode_integer_init (opcode, val); return TRUE; } @@ -535,30 +552,34 @@ return g_string_free (str, FALSE); } +/* @results *must* have enough space + * @op is transfer full into this function */ static gboolean xb_machine_opcodes_optimize_fn (XbMachine *self, - XbOpcode *op, - guint *idx, - GPtrArray *src, - GPtrArray *dst, + XbStack *opcodes, + XbOpcode op, + XbStack *results, GError **error) { XbMachineMethodItem *item; XbMachinePrivate *priv = GET_PRIVATE (self); g_autofree gchar *stack_str = NULL; g_autoptr(GError) error_local = NULL; - g_autoptr(XbOpcode) op_result = NULL; - g_autoptr(XbStack) stack = NULL; + g_auto(XbOpcode) op_result = XB_OPCODE_INIT (); + g_auto(XbOpcode) op_owned = op; /* a function! lets check the arg length */ - if (xb_opcode_get_kind (op) != XB_OPCODE_KIND_FUNCTION) { - g_ptr_array_add (dst, xb_opcode_ref (op)); + if (xb_opcode_get_kind (&op) != XB_OPCODE_KIND_FUNCTION) { + XbOpcode *op_out; + if (!xb_stack_push (results, &op_out, error)) + return FALSE; + *op_out = xb_opcode_steal (&op_owned); return TRUE; } /* get function, check if we have enough arguments */ - item = g_ptr_array_index (priv->methods, xb_opcode_get_val (op)); - if (item->n_opcodes >= *idx) { + item = g_ptr_array_index (priv->methods, xb_opcode_get_val (&op)); + if (item->n_opcodes > xb_stack_get_size (opcodes)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, @@ -566,52 +587,39 @@ return FALSE; } - /* make a copy of the stack with the arguments */ - stack = xb_stack_new (item->n_opcodes); - for (guint i = item->n_opcodes; i > 0; i--) { - XbOpcode *op_tmp = g_ptr_array_index (src, *idx - (i + 1)); - xb_stack_push (stack, op_tmp); - } + /* run the method. it's only supposed to pop its arguments off the stack + * if it can complete successfully */ + stack_str = xb_stack_to_string (opcodes); + if (!item->method_cb (self, opcodes, NULL, item->user_data, NULL, &error_local)) { + XbOpcode *op_out; - /* run the method */ - stack_str = xb_stack_to_string (stack); - if (!item->method_cb (self, stack, NULL, item->user_data, NULL, &error_local)) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { - g_debug ("ignoring opimized call to %s(%s): %s", + g_debug ("ignoring optimized call to %s(%s): %s", item->name, stack_str, error_local->message); } - g_ptr_array_add (dst, xb_opcode_ref (op)); + if (!xb_stack_push (results, &op_out, error)) + return FALSE; + *op_out = xb_opcode_steal (&op_owned); return TRUE; } - /* the method ran, add the result and discard the arguments */ - op_result = xb_stack_pop (stack); - if (op_result == NULL) { - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "internal error; no retval on stack"); + /* the method ran, add the result. the arguments have already been popped */ + if (!xb_machine_stack_pop (self, opcodes, &op_result, error)) return FALSE; - } - if (xb_opcode_get_kind (op_result) != XB_OPCODE_KIND_BOOLEAN) { - if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) - g_debug ("method ran, adding result"); - *idx -= item->n_opcodes; - g_ptr_array_add (dst, g_steal_pointer (&op_result)); - return TRUE; - } + if (xb_opcode_get_kind (&op_result) != XB_OPCODE_KIND_BOOLEAN || + xb_opcode_get_val (&op_result)) { + XbOpcode *op_out; + + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { + g_autofree gchar *tmp = xb_opcode_to_string (&op_result); + g_debug ("method ran, adding result %s", tmp); + } + if (!xb_stack_push (results, &op_out, error)) + return FALSE; + *op_out = xb_opcode_steal (&op_result); - /* nothing was added to the stack, so check if the predicate will - * always evaluate to TRUE */ - if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { - g_autofree gchar *tmp = xb_opcode_to_string (op_result); - g_debug ("method ran, result %s", tmp); - } - if (xb_opcode_get_val (op_result) == TRUE) { - *idx -= item->n_opcodes; - g_ptr_array_add (dst, g_steal_pointer (&op_result)); return TRUE; } @@ -628,32 +636,36 @@ xb_machine_opcodes_optimize (XbMachine *self, XbStack *opcodes, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); - g_autoptr(GPtrArray) dst = NULL; - g_autoptr(GPtrArray) src = NULL; + g_autoptr(XbStack) results = xb_stack_new_inline (xb_stack_get_size (opcodes)); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); /* debug */ - if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string (opcodes); g_debug ("before optimizing: %s", str); } /* process the stack in reverse order */ - src = xb_stack_steal_all (opcodes); - dst = xb_stack_steal_all (opcodes); - for (guint i = src->len; i > 0; i--) { - XbOpcode *op = g_ptr_array_index (src, i - 1); - if (!xb_machine_opcodes_optimize_fn (self, op, &i, src, dst, error)) + while (xb_machine_stack_pop (self, opcodes, &op, NULL)) { + /* this takes ownership of @op */ + if (!xb_machine_opcodes_optimize_fn (self, + opcodes, + xb_opcode_steal (&op), + results, + error)) return FALSE; } - /* copy back the result into the opcodes stack */ - for (guint i = dst->len; i > 0; i--) { - XbOpcode *op = g_ptr_array_index (dst, i - 1); - xb_stack_push (opcodes, op); + /* copy back the result into the opcodes stack (and reverse it) */ + while (xb_stack_pop (results, &op, NULL)) { + XbOpcode *op_out; + if (!xb_stack_push (opcodes, &op_out, error)) + return FALSE; + *op_out = xb_opcode_steal (&op); } /* debug */ - if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string (opcodes); g_debug ("after optimizing: %s", str); } @@ -787,6 +799,8 @@ /* do any fixups */ opcodes_sig = xb_machine_get_opcodes_sig (self, opcodes); + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) + g_debug ("opcodes_sig=%s", opcodes_sig); item = g_hash_table_lookup (priv->opcode_fixup, opcodes_sig); if (item != NULL) { if (!item->fixup_cb (self, opcodes, item->user_data, error)) @@ -797,6 +811,11 @@ if (flags & XB_MACHINE_PARSE_FLAG_OPTIMIZE) { for (guint i = 0; i < 10; i++) { guint oldsz = xb_stack_get_size (opcodes); + + /* Is the stack optimal already? */ + if (oldsz == 1) + break; + if (!xb_machine_opcodes_optimize (self, opcodes, error)) return NULL; if (oldsz == xb_stack_get_size (opcodes)) @@ -880,66 +899,67 @@ } /** - * xb_machine_opcode_to_string: - * @self: a #XbMachine - * @opcode: a #XbOpcode - * - * Returns a string representing the specific opcode. - * - * Returns: text - * - * Since: 0.1.1 - **/ -gchar * -xb_machine_opcode_to_string (XbMachine *self, XbOpcode *opcode) -{ - return xb_opcode_to_string (opcode); -} - -/** - * xb_machine_opcodes_to_string: + * xb_machine_run: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes + * @result: (out): return status after running @opcodes + * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions + * @error: a #GError, or %NULL + * + * Runs a set of opcodes on the virtual machine. * - * Returns a string representing a set of opcodes. + * It is safe to call this function from a different thread to the one that + * created the #XbMachine. * - * Returns: text + * Returns: a new #XbOpcode, or %NULL * * Since: 0.1.1 + * Deprecated: 0.3.0: Use xb_machine_run_with_bindings() instead. **/ -gchar * -xb_machine_opcodes_to_string (XbMachine *self, XbStack *opcodes) +gboolean +xb_machine_run (XbMachine *self, + XbStack *opcodes, + gboolean *result, + gpointer exec_data, + GError **error) { - return xb_stack_to_string (opcodes); + return xb_machine_run_with_bindings (self, opcodes, NULL, result, exec_data, error); } /** - * xb_machine_run: + * xb_machine_run_with_bindings: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes + * @bindings: (nullable) (transfer none): values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any bound values * @result: (out): return status after running @opcodes - * @exec_data: per-run user data that is passed to all the XbMachineMethodFunc functions + * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions * @error: a #GError, or %NULL * - * Runs a set of opcodes on the virtual machine. + * Runs a set of opcodes on the virtual machine, using the bound values given in + * @bindings to substitute for bound opcodes. * * It is safe to call this function from a different thread to the one that * created the #XbMachine. * * Returns: a new #XbOpcode, or %NULL * - * Since: 0.1.1 + * Since: 0.3.0 **/ gboolean -xb_machine_run (XbMachine *self, - XbStack *opcodes, - gboolean *result, - gpointer exec_data, - GError **error) +xb_machine_run_with_bindings (XbMachine *self, + XbStack *opcodes, + XbValueBindings *bindings, + gboolean *result, + gpointer exec_data, + GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); - g_autoptr(XbOpcode) opcode_success = NULL; + g_auto(XbOpcode) opcode_success = XB_OPCODE_INIT (); g_autoptr(XbStack) stack = NULL; + guint opcodes_stack_size = xb_stack_get_size (opcodes); + guint bound_opcode_idx = 0; g_return_val_if_fail (XB_IS_MACHINE (self), FALSE); g_return_val_if_fail (opcodes != NULL, FALSE); @@ -947,11 +967,43 @@ g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* process each opcode */ - stack = xb_stack_new (priv->stack_size); - for (guint i = 0; i < xb_stack_get_size (opcodes); i++) { + stack = xb_stack_new_inline (priv->stack_size); + for (guint i = 0; i < opcodes_stack_size; i++) { XbOpcode *opcode = xb_stack_peek (opcodes, i); XbOpcodeKind kind = xb_opcode_get_kind (opcode); + /* replace post-0.3.0-style bound opcodes with their bound values */ + if (bindings != NULL && + (kind == XB_OPCODE_KIND_BOUND_TEXT || + kind == XB_OPCODE_KIND_BOUND_INTEGER)) { + XbOpcode *machine_opcode; + if (!xb_machine_stack_push (self, + stack, + &machine_opcode, + error)) + return FALSE; + if (!xb_value_bindings_lookup_opcode (bindings, bound_opcode_idx++, machine_opcode)) { + g_autofree gchar *tmp1 = xb_stack_to_string (stack); + g_autofree gchar *tmp2 = xb_stack_to_string (opcodes); + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "opcode was not bound at runtime, stack:%s, opcodes:%s", + tmp1, tmp2); + return FALSE; + } + continue; + } else if (kind == XB_OPCODE_KIND_BOUND_UNSET) { + g_autofree gchar *tmp1 = xb_stack_to_string (stack); + g_autofree gchar *tmp2 = xb_stack_to_string (opcodes); + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "opcode was not bound at runtime, stack:%s, opcodes:%s", + tmp1, tmp2); + return FALSE; + } + /* process the stack */ if (kind == XB_OPCODE_KIND_FUNCTION) { if (!xb_machine_run_func (self, @@ -963,29 +1015,27 @@ continue; } - /* add to stack */ + /* add to stack; this uses a const copy of the input opcode, + * so ownership of anything allocated on the heap remains with + * the caller */ if (kind == XB_OPCODE_KIND_TEXT || kind == XB_OPCODE_KIND_BOOLEAN || kind == XB_OPCODE_KIND_INTEGER || kind == XB_OPCODE_KIND_INDEXED_TEXT || - kind == XB_OPCODE_KIND_BOUND_TEXT || - kind == XB_OPCODE_KIND_BOUND_INTEGER) { - xb_machine_stack_push (self, stack, opcode); + (bindings == NULL && + (kind == XB_OPCODE_KIND_BOUND_TEXT || + kind == XB_OPCODE_KIND_BOUND_INTEGER))) { + XbOpcode *machine_opcode; + if (!xb_machine_stack_push (self, + stack, + &machine_opcode, + error)) + return FALSE; + *machine_opcode = *opcode; + machine_opcode->destroy_func = NULL; continue; } - /* unbound */ - if (kind == XB_OPCODE_KIND_BOUND_UNSET) { - g_autofree gchar *tmp1 = xb_stack_to_string (stack); - g_autofree gchar *tmp2 = xb_stack_to_string (opcodes); - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "opcode was not bound at runtime, stack:%s, opcodes:%s", - tmp1, tmp2); - return FALSE; - } - /* invalid */ g_set_error (error, G_IO_ERROR, @@ -1005,8 +1055,9 @@ xb_stack_get_size (stack), tmp); return FALSE; } - opcode_success = xb_stack_pop (stack); - if (xb_opcode_get_kind (opcode_success) != XB_OPCODE_KIND_BOOLEAN) { + if (!xb_stack_pop (stack, &opcode_success, error)) + return FALSE; + if (xb_opcode_get_kind (&opcode_success) != XB_OPCODE_KIND_BOOLEAN) { g_autofree gchar *tmp = xb_stack_to_string (stack); g_set_error (error, G_IO_ERROR, @@ -1015,7 +1066,7 @@ return FALSE; } if (result != NULL) - *result = xb_opcode_get_val (opcode_success); + *result = xb_opcode_get_val (&opcode_success); /* success */ return TRUE; @@ -1025,70 +1076,97 @@ * xb_machine_stack_pop: * @self: a #XbMachine * @stack: a #XbStack + * @opcode_out: (out caller-allocates) (optional): return location for the popped #XbOpcode + * @error: a #GError, or %NULL * * Pops an opcode from the stack. * - * Returns: (transfer full): a new #XbOpcode, or %NULL + * Returns: %TRUE if popping succeeded, %FALSE if the stack was empty already * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_machine_stack_pop (XbMachine *self, XbStack *stack) +gboolean +xb_machine_stack_pop (XbMachine *self, XbStack *stack, XbOpcode *opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + gboolean retval; + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { - XbOpcode *opcode = xb_stack_peek (stack, xb_stack_get_size (stack) - 1); - g_autofree gchar *str = xb_opcode_to_string (opcode); - g_debug ("popping: %s", str); - xb_machine_debug_show_stack (self, stack); + XbOpcode *opcode_peek = xb_stack_peek (stack, xb_stack_get_size (stack) - 1); + if (opcode_peek != NULL) { + g_autofree gchar *str = xb_opcode_to_string (opcode_peek); + g_debug ("popping: %s", str); + } else { + g_debug ("not popping: stack empty"); + } } - return xb_stack_pop (stack); + + retval = xb_stack_pop (stack, opcode_out, error); + + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) + xb_machine_debug_show_stack (self, stack); + + return retval; } /** - * xb_machine_stack_push: - * @self: a #XbMachine - * @stack: a #XbStack - * @opcode: a #XbOpcode - * - * Adds an opcode to the stack. - * - * Since: 0.1.1 + * xb_machine_stack_pop_two: (skip): **/ -void -xb_machine_stack_push (XbMachine *self, XbStack *stack, XbOpcode *opcode) +gboolean +xb_machine_stack_pop_two (XbMachine *self, XbStack *stack, + XbOpcode *opcode1_out, XbOpcode *opcode2_out, + GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + gboolean retval; + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { - g_autofree gchar *str = xb_opcode_to_string (opcode); - g_debug ("pushing: %s", str); + XbOpcode *opcode_peek1 = xb_stack_peek (stack, xb_stack_get_size (stack) - 1); + XbOpcode *opcode_peek2 = xb_stack_peek (stack, xb_stack_get_size (stack) - 2); + if (opcode_peek1 != NULL && opcode_peek2 != NULL) { + g_autofree gchar *str1 = xb_opcode_to_string (opcode_peek1); + g_autofree gchar *str2 = xb_opcode_to_string (opcode_peek2); + g_debug ("popping1: %s", str1); + g_debug ("popping2: %s", str2); + } else { + g_debug ("not popping: stack empty"); + } } - xb_stack_push (stack, opcode); + + retval = xb_stack_pop_two (stack, opcode1_out, opcode2_out, error); + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack (self, stack); + + return retval; } /** - * xb_machine_stack_push_steal: + * xb_machine_stack_push: * @self: a #XbMachine * @stack: a #XbStack - * @opcode: a #XbOpcode + * @opcode_out: (out) (nullable): return location for the new #XbOpcode + * @error: return location for a #GError, or %NULL * - * Adds an stolen opcode to the stack. + * Pushes a new empty opcode onto the end of the stack. A pointer to the opcode + * is returned in @opcode_out so that the caller can initialise it. * - * Since: 0.1.4 + * If the stack reaches its maximum size, %G_IO_ERROR_NO_SPACE will be returned. + * + * Returns: %TRUE if a new empty opcode was returned, or %FALSE if the stack has + * reached its maximum size + * Since: 0.2.0 **/ -void -xb_machine_stack_push_steal (XbMachine *self, XbStack *stack, XbOpcode *opcode) +gboolean +xb_machine_stack_push (XbMachine *self, XbStack *stack, XbOpcode **opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { - g_autofree gchar *str = xb_opcode_to_string (opcode); - g_debug ("pushing: %s", str); + g_debug ("pushing generic opcode"); } - xb_stack_push_steal (stack, opcode); - if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) - xb_machine_debug_show_stack (self, stack); + + return xb_stack_push (stack, opcode_out, error); } /** @@ -1096,20 +1174,31 @@ * @self: a #XbMachine * @stack: a #XbStack * @str: text literal + * @error: return location for a #GError, or %NULL * * Adds a text literal to the stack, copying @str. * - * Since: 0.1.1 + * Errors are as for xb_machine_stack_push(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.2.0 **/ -void -xb_machine_stack_push_text (XbMachine *self, XbStack *stack, const gchar *str) +gboolean +xb_machine_stack_push_text (XbMachine *self, XbStack *stack, const gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + XbOpcode *opcode; + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) g_debug ("pushing: %s", str); - xb_stack_push_steal (stack, xb_opcode_text_new (str)); + + if (!xb_stack_push (stack, &opcode, error)) + return FALSE; + xb_opcode_text_init (opcode, str); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack (self, stack); + + return TRUE; } /** @@ -1117,41 +1206,64 @@ * @self: a #XbMachine * @stack: a #XbStack * @str: text literal + * @error: return location for a #GError, or %NULL * * Adds static text literal to the stack. * - * Since: 0.1.1 + * Errors are as for xb_machine_stack_push(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.2.0 **/ -void -xb_machine_stack_push_text_static (XbMachine *self, XbStack *stack, const gchar *str) +gboolean +xb_machine_stack_push_text_static (XbMachine *self, XbStack *stack, const gchar *str, GError **error) { + XbOpcode *opcode; + XbMachinePrivate *priv = GET_PRIVATE (self); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) g_debug ("pushing: %s", str); - xb_stack_push_steal (stack, xb_opcode_text_new_static (str)); + + if (!xb_stack_push (stack, &opcode, error)) + return FALSE; + xb_opcode_text_init_static (opcode, str); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack (self, stack); + + return TRUE; } /** * xb_machine_stack_push_text_steal: * @self: a #XbMachine * @stack: a #XbStack - * @str: text literal + * @str: (transfer full): text literal + * @error: return location for a #GError, or %NULL * * Adds a stolen text literal to the stack. * - * Since: 0.1.1 + * Errors are as for xb_machine_stack_push(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.2.0 **/ -void -xb_machine_stack_push_text_steal (XbMachine *self, XbStack *stack, gchar *str) +gboolean +xb_machine_stack_push_text_steal (XbMachine *self, XbStack *stack, gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + XbOpcode *opcode; + g_autofree gchar *str_stolen = g_steal_pointer (&str); + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) - g_debug ("pushing: %s", str); - xb_stack_push_steal (stack, xb_opcode_text_new_steal (str)); + g_debug ("pushing: %s", str_stolen); + + if (!xb_stack_push (stack, &opcode, error)) + return FALSE; + xb_opcode_text_init_steal (opcode, g_steal_pointer (&str_stolen)); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack (self, stack); + + return TRUE; } /** @@ -1159,20 +1271,31 @@ * @self: a #XbMachine * @stack: a #XbStack * @val: integer literal + * @error: return location for a #GError, or %NULL * * Adds an integer literal to the stack. * - * Since: 0.1.1 + * Errors are as for xb_machine_stack_push(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 0.2.0 **/ -void -xb_machine_stack_push_integer (XbMachine *self, XbStack *stack, guint32 val) +gboolean +xb_machine_stack_push_integer (XbMachine *self, XbStack *stack, guint32 val, GError **error) { XbMachinePrivate *priv = GET_PRIVATE (self); + XbOpcode *opcode; + if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) g_debug ("pushing: %u", val); - xb_stack_push_steal (stack, xb_opcode_integer_new (val)); + + if (!xb_stack_push (stack, &opcode, error)) + return FALSE; + xb_opcode_integer_init (opcode, val); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack (self, stack); + + return TRUE; } /** @@ -1214,6 +1337,103 @@ return priv->stack_size; } +static const gchar * +xb_machine_intern_token (XbMachine *self, const gchar *str) +{ + XbMachinePrivate *priv = GET_PRIVATE (self); + const gchar *tmp; + gchar *newstr; + + /* existing value */ + tmp = g_hash_table_lookup (priv->opcode_tokens, str); + if (tmp != NULL) + return tmp; + + /* add as both key and value */ + newstr = g_strdup (str); + g_hash_table_add (priv->opcode_tokens, newstr); + return newstr; +} + +/* private */ +void +xb_machine_opcode_tokenize (XbMachine *self, XbOpcode *op) +{ + const gchar *str; + g_auto(GStrv) tokens = NULL; + g_auto(GStrv) ascii_tokens = NULL; + + /* use the fast token path even if there are no valid tokens */ + xb_opcode_add_flag (op, XB_OPCODE_FLAG_TOKENIZED); + + str = xb_opcode_get_str (op); + tokens = g_str_tokenize_and_fold (str, NULL, &ascii_tokens); + for (guint i = 0; tokens[i] != NULL; i++) { + if (!xb_string_token_valid (tokens[i])) + continue; + xb_opcode_append_token (op, xb_machine_intern_token (self, tokens[i])); + } + for (guint i = 0; ascii_tokens[i] != NULL; i++) { + if (!xb_string_token_valid (ascii_tokens[i])) + continue; + xb_opcode_append_token (op, xb_machine_intern_token (self, ascii_tokens[i])); + } +} + +typedef gboolean (*OpcodeCheckFunc) (XbOpcode *op); + +static gboolean +xb_opcode_cmp_val_or_str (XbOpcode *op) +{ + return xb_opcode_cmp_str (op) || xb_opcode_cmp_val (op); +} + +static gboolean +xb_machine_check_one_arg (XbStack *stack, OpcodeCheckFunc f, GError **error) +{ + XbOpcode *head; + + head = xb_stack_peek_tail (stack); + if (head == NULL || !f (head)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s type not supported", + (head != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head)) : "(null)"); + return FALSE; + } + + return TRUE; +} + +static gboolean +xb_machine_check_two_args (XbStack *stack, + OpcodeCheckFunc f1, + OpcodeCheckFunc f2, + GError **error) +{ + XbOpcode *head1 = NULL; + XbOpcode *head2 = NULL; + guint stack_size = xb_stack_get_size (stack); + + if (stack_size >= 2) { + head1 = xb_stack_peek (stack, stack_size - 1); + head2 = xb_stack_peek (stack, stack_size - 2); + } + if (head1 == NULL || head2 == NULL || + !f1 (head1) || !f2 (head2)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s:%s types not supported", + (head1 != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head1)) : "(null)", + (head2 != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head2)) : "(null)"); + return FALSE; + } + + return TRUE; +} + static gboolean xb_machine_func_and_cb (XbMachine *self, XbStack *stack, @@ -1222,23 +1442,18 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); - /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op1) && xb_opcode_get_val (op2)); - return TRUE; - } + if (!xb_machine_check_two_args (stack, xb_opcode_cmp_val, xb_opcode_cmp_val, error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* INTE:INTE */ + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op1) && xb_opcode_get_val (&op2), + error); } static gboolean @@ -1249,23 +1464,18 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); - /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op1) || xb_opcode_get_val (op2)); - return TRUE; - } + if (!xb_machine_check_two_args (stack, xb_opcode_cmp_val, xb_opcode_cmp_val, error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* INTE:INTE */ + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op1) || xb_opcode_get_val (&op2), + error); } static gboolean @@ -1276,61 +1486,59 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op1) == xb_opcode_get_val (op2)); - return TRUE; - } + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) + return xb_stack_push_bool (stack, xb_opcode_get_val (&op1) == xb_opcode_get_val (&op2), error); /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op1), - xb_opcode_get_str (op2)) == 0); - return TRUE; - } + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) + return xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (&op1), + xb_opcode_get_str (&op2)) == 0, error); /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; - } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val == xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val == xb_opcode_get_val (&op1), error); } - /* INTE:TEXT */ - if (xb_opcode_cmp_val (op2) && xb_opcode_cmp_str (op1)) { + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op1) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; - } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op1), + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val == xb_opcode_get_val (op2)); - return TRUE; + return xb_stack_push_bool (stack, val == xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1342,45 +1550,67 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op1) != xb_opcode_get_val (op2)); - return TRUE; + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) { + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op1) != xb_opcode_get_val (&op2), + error); } /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op1), - xb_opcode_get_str (op2)) != 0); - return TRUE; + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) { + return xb_stack_push_bool (stack, + g_strcmp0 (xb_opcode_get_str (&op1), + xb_opcode_get_str (&op2)) != 0, + error); } /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + return xb_stack_push_bool (stack, + val != xb_opcode_get_val (&op1), + error); + } + + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { + guint64 val = 0; + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val != xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val != xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1392,45 +1622,65 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op2) < xb_opcode_get_val (op1)); - return TRUE; + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) { + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op2) < xb_opcode_get_val (&op1), + error); } /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op2), - xb_opcode_get_str (op1)) < 0); - return TRUE; + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) { + return xb_stack_push_bool (stack, + g_strcmp0 (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)) < 0, + error); } /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + return xb_stack_push_bool (stack, val < xb_opcode_get_val (&op1), error); + } + + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { + guint64 val = 0; + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val < xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val < xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1442,45 +1692,65 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op2) > xb_opcode_get_val (op1)); - return TRUE; + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) { + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op2) > xb_opcode_get_val (&op1), + error); } /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op2), - xb_opcode_get_str (op1)) > 0); - return TRUE; + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) { + return xb_stack_push_bool (stack, + g_strcmp0 (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)) > 0, + error); } /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + return xb_stack_push_bool (stack, val > xb_opcode_get_val (&op1), error); + } + + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { + guint64 val = 0; + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val > xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val > xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1492,45 +1762,66 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op2) <= xb_opcode_get_val (op1)); - return TRUE; + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) { + return xb_stack_push_bool (stack, + xb_opcode_get_val (&op2) <= xb_opcode_get_val (&op1), + error); } /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op2), - xb_opcode_get_str (op1)) <= 0); + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) { + return xb_stack_push_bool (stack, + g_strcmp0 (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)) <= 0, + error); return TRUE; } /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + return xb_stack_push_bool (stack, val <= xb_opcode_get_val (&op1), error); + } + + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { + guint64 val = 0; + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val <= xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val <= xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1542,22 +1833,17 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); - /* TEXT */ - if (xb_opcode_cmp_str (op)) { - xb_machine_stack_push_text_steal (self, stack, - g_ascii_strdown (xb_opcode_get_str (op), -1)); - return TRUE; - } + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_str, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s type not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op))); - return FALSE; + /* TEXT */ + return xb_machine_stack_push_text_steal (self, stack, + g_utf8_strdown (xb_opcode_get_str (&op), -1), + error); } static gboolean @@ -1568,22 +1854,17 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); - /* TEXT */ - if (xb_opcode_cmp_str (op)) { - xb_machine_stack_push_text_steal (self, stack, - g_ascii_strup (xb_opcode_get_str (op), -1)); - return TRUE; - } + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_str, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s type not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op))); - return FALSE; + /* TEXT */ + return xb_machine_stack_push_text_steal (self, stack, + g_utf8_strup (xb_opcode_get_str (&op), -1), + error); } static gboolean @@ -1594,26 +1875,27 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); + + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_val_or_str, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; /* TEXT */ - if (xb_opcode_cmp_str (op1)) { - xb_stack_push_bool (stack, xb_opcode_get_str (op1) == NULL); - return TRUE; - } + if (xb_opcode_cmp_str (&op)) + return xb_stack_push_bool (stack, xb_opcode_get_str (&op) == NULL, error); /* INTE */ - if (xb_opcode_cmp_val (op1)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op1) == 0); - return TRUE; - } + if (xb_opcode_cmp_val (&op)) + return xb_stack_push_bool (stack, xb_opcode_get_val (&op) == 0, error); - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s type not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1))); + "cannot invert %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op))); return FALSE; } @@ -1625,45 +1907,61 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_val_or_str, + xb_opcode_cmp_val_or_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (op2), - xb_opcode_get_str (op1)) >= 0); - return TRUE; + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_str (&op2)) { + return xb_stack_push_bool (stack, g_strcmp0 (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)) >= 0, error); } /* INTE:INTE */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_val (op2)) { - xb_stack_push_bool (stack, xb_opcode_get_val (op2) >= xb_opcode_get_val (op1)); - return TRUE; + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_val (&op2)) { + return xb_stack_push_bool (stack, xb_opcode_get_val (&op2) >= xb_opcode_get_val (&op1), error); } /* INTE:TEXT */ - if (xb_opcode_cmp_val (op1) && xb_opcode_cmp_str (op2)) { + if (xb_opcode_cmp_val (&op1) && xb_opcode_cmp_str (&op2)) { guint64 val = 0; - if (xb_opcode_get_str (op2) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; + if (xb_opcode_get_str (&op2) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op2), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op2), + return xb_stack_push_bool (stack, val >= xb_opcode_get_val (&op1), error); + } + + /* TEXT:INTE */ + if (xb_opcode_cmp_str (&op1) && xb_opcode_cmp_val (&op2)) { + guint64 val = 0; + if (xb_opcode_get_str (&op1) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } - xb_stack_push_bool (stack, val >= xb_opcode_get_val (op1)); - return TRUE; + return xb_stack_push_bool (stack, val >= xb_opcode_get_val (&op2), error); } - /* fail */ + /* should have been checked above */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); + "cannot compare %s and %s", + xb_opcode_kind_to_string (xb_opcode_get_kind (&op1)), + xb_opcode_kind_to_string (xb_opcode_get_kind (&op2))); return FALSE; } @@ -1675,24 +1973,22 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); - /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, xb_string_contains (xb_opcode_get_str (op2), - xb_opcode_get_str (op1))); - return TRUE; - } + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_str, + xb_opcode_cmp_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* TEXT:TEXT */ + return xb_stack_push_bool (stack, + xb_string_contains (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)), + error); } static gboolean @@ -1703,24 +1999,22 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); - /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_str_has_prefix (xb_opcode_get_str (op2), - xb_opcode_get_str (op1))); - return TRUE; - } + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_str, + xb_opcode_cmp_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* TEXT:TEXT */ + return xb_stack_push_bool (stack, + g_str_has_prefix (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)), + error); } static gboolean @@ -1731,24 +2025,22 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); - /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, g_str_has_suffix (xb_opcode_get_str (op2), - xb_opcode_get_str (op1))); - return TRUE; - } + if (!xb_machine_check_two_args (stack, + xb_opcode_cmp_str, + xb_opcode_cmp_str, + error)) + return FALSE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* TEXT:TEXT */ + return xb_stack_push_bool (stack, + g_str_has_suffix (xb_opcode_get_str (&op2), + xb_opcode_get_str (&op1)), + error); } static gboolean @@ -1759,31 +2051,23 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); + guint64 val = 0; + g_auto(XbOpcode) op = XB_OPCODE_INIT (); + + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_str, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; /* TEXT */ - if (xb_opcode_cmp_str (op1)) { - guint64 val = 0; - if (xb_opcode_get_str (op1) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; - } - if (!g_ascii_string_to_unsigned (xb_opcode_get_str (op1), - 10, 0, G_MAXUINT32, - &val, error)) { - return FALSE; - } - xb_machine_stack_push_integer (self, stack, val); - return TRUE; + if (xb_opcode_get_str (&op) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + if (!g_ascii_string_to_unsigned (xb_opcode_get_str (&op), + 10, 0, G_MAXUINT32, + &val, error)) { + return FALSE; } - - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1))); - return FALSE; + return xb_machine_stack_push_integer (self, stack, val, error); } static gboolean @@ -1794,25 +2078,20 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); - /* TEXT */ - if (xb_opcode_cmp_str (op1)) { - if (xb_opcode_get_str (op1) == NULL) { - xb_stack_push_bool (stack, FALSE); - return TRUE; - } - xb_machine_stack_push_integer (self, stack, strlen (xb_opcode_get_str (op1))); - return TRUE; - } + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_str, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1))); - return FALSE; + /* TEXT */ + if (xb_opcode_get_str (&op) == NULL) + return xb_stack_push_bool (stack, FALSE, error); + return xb_machine_stack_push_integer (self, + stack, + strlen (xb_opcode_get_str (&op)), + error); } static gboolean @@ -1823,23 +2102,17 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); + gchar *tmp; + g_auto(XbOpcode) op = XB_OPCODE_INIT (); - /* INTE */ - if (xb_opcode_cmp_val (op1)) { - gchar *tmp = g_strdup_printf ("%" G_GUINT32_FORMAT, - xb_opcode_get_val (op1)); - xb_machine_stack_push_text_steal (self, stack, tmp); - return TRUE; - } + if (!xb_machine_check_one_arg (stack, xb_opcode_cmp_val, error)) + return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1))); - return FALSE; + /* INTE */ + tmp = g_strdup_printf ("%" G_GUINT32_FORMAT, xb_opcode_get_val (&op)); + return xb_machine_stack_push_text_steal (self, stack, tmp, error); } static void @@ -1885,6 +2158,7 @@ priv->text_handlers = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_machine_text_handler_free); priv->opcode_fixup = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) xb_machine_opcode_fixup_free); + priv->opcode_tokens = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); /* built-in functions */ xb_machine_add_method (self, "and", 2, xb_machine_func_and_cb, NULL, NULL); @@ -1928,6 +2202,7 @@ g_ptr_array_unref (priv->operators); g_ptr_array_unref (priv->text_handlers); g_hash_table_unref (priv->opcode_fixup); + g_hash_table_unref (priv->opcode_tokens); G_OBJECT_CLASS (xb_machine_parent_class)->finalize (obj); } diff -Nru libxmlb-0.1.15/src/xb-machine.h libxmlb-0.3.6/src/xb-machine.h --- libxmlb-0.1.15/src/xb-machine.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-machine.h 2021-12-06 20:36:37.000000000 +0000 @@ -10,6 +10,7 @@ #include "xb-opcode.h" #include "xb-stack.h" +#include "xb-value-bindings.h" G_BEGIN_DECLS @@ -90,11 +91,19 @@ gssize text_len, XbMachineParseFlags flags, GError **error); + +G_DEPRECATED_FOR(xb_machine_run_with_bindings) gboolean xb_machine_run (XbMachine *self, XbStack *opcodes, gboolean *result, gpointer exec_data, GError **error); +gboolean xb_machine_run_with_bindings (XbMachine *self, + XbStack *opcodes, + XbValueBindings *bindings, + gboolean *result, + gpointer exec_data, + GError **error); void xb_machine_add_opcode_fixup (XbMachine *self, const gchar *opcodes_sig, @@ -115,35 +124,34 @@ const gchar *str, const gchar *name); -XbOpcode *xb_machine_opcode_func_new (XbMachine *self, +gboolean xb_machine_opcode_func_init (XbMachine *self, + XbOpcode *opcode, const gchar *func_name); -gchar *xb_machine_opcode_to_string (XbMachine *self, - XbOpcode *opcode) -G_DEPRECATED_FOR(xb_opcode_to_string); -gchar *xb_machine_opcodes_to_string (XbMachine *self, - XbStack *opcodes) -G_DEPRECATED_FOR(xb_stack_to_string); -XbOpcode *xb_machine_stack_pop (XbMachine *self, - XbStack *stack); -void xb_machine_stack_push (XbMachine *self, +gboolean xb_machine_stack_pop (XbMachine *self, XbStack *stack, - XbOpcode *opcode); -void xb_machine_stack_push_steal (XbMachine *self, + XbOpcode *opcode_out, + GError **error); +gboolean xb_machine_stack_push (XbMachine *self, XbStack *stack, - XbOpcode *opcode); -void xb_machine_stack_push_text (XbMachine *self, + XbOpcode **opcode_out, + GError **error); +gboolean xb_machine_stack_push_text (XbMachine *self, XbStack *stack, - const gchar *str); -void xb_machine_stack_push_text_static (XbMachine *self, + const gchar *str, + GError **error); +gboolean xb_machine_stack_push_text_static (XbMachine *self, XbStack *stack, - const gchar *str); -void xb_machine_stack_push_text_steal (XbMachine *self, + const gchar *str, + GError **error); +gboolean xb_machine_stack_push_text_steal (XbMachine *self, XbStack *stack, - gchar *str); -void xb_machine_stack_push_integer (XbMachine *self, + gchar *str, + GError **error); +gboolean xb_machine_stack_push_integer (XbMachine *self, XbStack *stack, - guint32 val); + guint32 val, + GError **error); void xb_machine_set_stack_size (XbMachine *self, guint stack_size); guint xb_machine_get_stack_size (XbMachine *self); diff -Nru libxmlb-0.1.15/src/xb-machine-private.h libxmlb-0.3.6/src/xb-machine-private.h --- libxmlb-0.1.15/src/xb-machine-private.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-machine-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "xb-machine.h" + +G_BEGIN_DECLS + +gboolean xb_machine_stack_pop_two (XbMachine *self, + XbStack *stack, + XbOpcode *opcode1_out, + XbOpcode *opcode2_out, + GError **error); +void xb_machine_opcode_tokenize (XbMachine *self, + XbOpcode *op); + +G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-node.c libxmlb-0.3.6/src/xb-node.c --- libxmlb-0.1.15/src/xb-node.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node.c 2021-12-06 20:36:37.000000000 +0000 @@ -12,10 +12,10 @@ #include #include "xb-node-private.h" +#include "xb-node-silo.h" #include "xb-silo-export-private.h" typedef struct { - GObject parent_instance; XbSilo *silo; XbSiloNode *sn; } XbNodePrivate; @@ -24,12 +24,65 @@ #define GET_PRIVATE(o) (xb_node_get_instance_private (o)) /** + * XbNodeAttrIter: + * + * A #XbNodeAttrIter structure represents an iterator that can be used + * to iterate over the attributes of a #XbNode. #XbNodeAttrIter + * structures are typically allocated on the stack and then initialized + * with xb_node_attr_iter_init(). + * + * The iteration order of a #XbNodeAttrIter is not defined. + * + * Since: 0.3.4 + */ + +typedef struct +{ + XbNode *node; + guint8 position; + gpointer dummy3; + gpointer dummy4; + gpointer dummy5; + gpointer dummy6; +} RealAttrIter; + +G_STATIC_ASSERT (sizeof (XbNodeAttrIter) == sizeof (RealAttrIter)); + +/** + * XbNodeChildIter: + * + * A #XbNodeChildIter structure represents an iterator that can be used + * to iterate over the children of a #XbNode. #XbNodeChildIter + * structures are typically allocated on the stack and then initialized + * with xb_node_child_iter_init(). + * + * Since: 0.3.4 + */ + +typedef struct +{ + XbNode *node; + XbSiloNode *position; + gboolean first_iter; + gpointer dummy4; + gpointer dummy5; + gpointer dummy6; +} RealChildIter; + +G_STATIC_ASSERT (sizeof (XbNodeChildIter) == sizeof (RealChildIter)); + +/** * xb_node_get_data: * @self: a #XbNode * @key: a string key, e.g. `fwupd::RemoteId` * * Gets any data that has been set on the node using xb_node_set_data(). * + * This will only work across queries to the associated silo if the silo has + * its #XbSilo:enable-node-cache property set to %TRUE. Otherwise a new #XbNode + * may be constructed for future queries which return the same element as a + * result. + * * Returns: (transfer none): a #GBytes, or %NULL if not found * * Since: 0.1.0 @@ -37,8 +90,10 @@ GBytes * xb_node_get_data (XbNode *self, const gchar *key) { + XbNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (key != NULL, NULL); + g_return_val_if_fail (priv->silo, NULL); return g_object_get_data (G_OBJECT (self), key); } @@ -50,14 +105,21 @@ * * Sets some data on the node which can be retrieved using xb_node_get_data(). * + * This will only work across queries to the associated silo if the silo has + * its #XbSilo:enable-node-cache property set to %TRUE. Otherwise a new #XbNode + * may be constructed for future queries which return the same element as a + * result. + * * Since: 0.1.0 **/ void xb_node_set_data (XbNode *self, const gchar *key, GBytes *data) { + XbNodePrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_NODE (self)); g_return_if_fail (key != NULL); g_return_if_fail (data != NULL); + g_return_if_fail (priv->silo); g_object_set_data_full (G_OBJECT (self), key, g_bytes_ref (data), (GDestroyNotify) g_bytes_unref); @@ -81,14 +143,14 @@ } /** - * xb_node_get_silo: (skip) + * xb_node_get_silo: * @self: a #XbNode * * Gets the #XbSilo for the node. * * Returns: (transfer none): a #XbSilo * - * Since: 0.1.0 + * Since: 0.2.0 **/ XbSilo * xb_node_get_silo (XbNode *self) @@ -115,10 +177,10 @@ g_return_val_if_fail (XB_IS_NODE (self), NULL); - sn = xb_silo_get_sroot (priv->silo); + sn = xb_silo_get_root_node (priv->silo); if (sn == NULL) return NULL; - return xb_silo_node_create (priv->silo, sn); + return xb_silo_create_node (priv->silo, sn, FALSE); } /** @@ -139,10 +201,10 @@ g_return_val_if_fail (XB_IS_NODE (self), NULL); - sn = xb_silo_node_get_parent (priv->silo, priv->sn); + sn = xb_silo_get_parent_node (priv->silo, priv->sn); if (sn == NULL) return NULL; - return xb_silo_node_create (priv->silo, sn); + return xb_silo_create_node (priv->silo, sn, FALSE); } /** @@ -163,10 +225,10 @@ g_return_val_if_fail (XB_IS_NODE (self), NULL); - sn = xb_silo_node_get_next (priv->silo, priv->sn); + sn = xb_silo_get_next_node (priv->silo, priv->sn); if (sn == NULL) return NULL; - return xb_silo_node_create (priv->silo, sn); + return xb_silo_create_node (priv->silo, sn, FALSE); } /** @@ -187,10 +249,10 @@ g_return_val_if_fail (XB_IS_NODE (self), NULL); - sn = xb_silo_node_get_child (priv->silo, priv->sn); + sn = xb_silo_get_child_node (priv->silo, priv->sn); if (sn == NULL) return NULL; - return xb_silo_node_create (priv->silo, sn); + return xb_silo_create_node (priv->silo, sn, FALSE); } /** @@ -219,6 +281,134 @@ } /** + * xb_node_child_iter_init: + * @iter: an uninitialized #XbNodeChildIter + * @self: a #XbNode + * + * Initializes a child iterator for the node's children and associates + * it with @self. + * The #XbNodeChildIter structure is typically allocated on the stack + * and does not need to be freed explicitly. + * + * Since: 0.3.4 + */ +void +xb_node_child_iter_init (XbNodeChildIter *iter, XbNode *self) +{ + XbNodePrivate *priv = GET_PRIVATE (self); + RealChildIter *ri = (RealChildIter *) iter; + + g_return_if_fail (iter != NULL); + g_return_if_fail (XB_IS_NODE (self)); + + ri->node = self; + ri->position = xb_silo_get_child_node (priv->silo, priv->sn); + ri->first_iter = TRUE; +} + +/** + * xb_node_child_iter_next: + * @iter: an initialized #XbNodeAttrIter + * @child: (out) (optional) (not nullable): Destination of the returned child + * + * Returns the current child and advances the iterator. + * The retrieved #XbNode child needs to be dereferenced with g_object_unref(). + * Example: + * |[ + * XbNodeChildIter iter; + * g_autoptr(XbNode) child = NULL; + * + * xb_node_child_iter_init (&iter, node); + * while (xb_node_child_iter_next (&iter, &child)) { + * // do something with the node child + * g_clear_pointer (&child, g_object_unref); + * } + * ]| + * + * Returns: %FALSE if the last child has been reached. + * + * Since: 0.3.4 + */ +gboolean +xb_node_child_iter_next (XbNodeChildIter *iter, XbNode **child) +{ + XbNodePrivate *priv; + RealChildIter *ri = (RealChildIter *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (child != NULL, FALSE); + priv = GET_PRIVATE (ri->node); + + /* check if the iteration was finished */ + if (ri->position == NULL) { + *child = NULL; + return FALSE; + } + + *child = xb_silo_create_node (priv->silo, ri->position, FALSE); + ri->position = xb_silo_get_next_node (priv->silo, ri->position); + + return TRUE; +} + +/** + * xb_node_child_iter_loop: (skip) + * @iter: an initialized #XbNodeAttrIter + * @child: (out) (optional) (nullable): Destination of the returned child + * + * Returns the current child and advances the iterator. + * On the first call to this function, the @child pointer is assumed to point + * at uninitialised memory. + * On any later calls, it is assumed that the same pointers + * will be given and that they will point to the memory as set by the + * previous call to this function. This allows the previous values to + * be freed, as appropriate. + * + * Example: + * |[ + * XbNodeChildIter iter; + * XbNode *child; + * + * xb_node_child_iter_init (&iter, node); + * while (xb_node_child_iter_loop (&iter, &child)) { + * // do something with the node child + * // no need to free 'child' unless breaking out of this loop + * } + * ]| + * + * Returns: %FALSE if the last child has been reached. + * + * Since: 0.3.4 + */ +gboolean +xb_node_child_iter_loop (XbNodeChildIter *iter, XbNode **child) +{ + XbNodePrivate *priv; + RealChildIter *ri = (RealChildIter *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (child != NULL, FALSE); + priv = GET_PRIVATE (ri->node); + + /* unref child from previous iterations, if there were any */ + if (ri->first_iter) + ri->first_iter = FALSE; + else + g_object_unref (*child); + + /* check if the iteration was finished */ + if (ri->position == NULL) { + *child = NULL; + return FALSE; + } + + *child = xb_silo_create_node (priv->silo, ri->position, FALSE); + ri->position = xb_silo_get_next_node (priv->silo, ri->position); + + return TRUE; +} + +/** * xb_node_get_text: * @self: a #XbNode * @@ -233,7 +423,7 @@ { XbNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_NODE (self), NULL); - return xb_silo_node_get_text (priv->silo, priv->sn); + return xb_silo_get_node_text (priv->silo, priv->sn); } /** @@ -254,7 +444,7 @@ g_return_val_if_fail (XB_IS_NODE (self), G_MAXUINT64); - tmp = xb_silo_node_get_text (priv->silo, priv->sn);; + tmp = xb_silo_get_node_text (priv->silo, priv->sn);; if (tmp == NULL) return G_MAXUINT64; if (g_str_has_prefix (tmp, "0x")) @@ -277,7 +467,7 @@ { XbNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_NODE (self), NULL); - return xb_silo_node_get_tail (priv->silo, priv->sn); + return xb_silo_get_node_tail (priv->silo, priv->sn); } /** @@ -295,7 +485,7 @@ { XbNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_NODE (self), NULL); - return xb_silo_node_get_element (priv->silo, priv->sn); + return xb_silo_get_node_element (priv->silo, priv->sn); } /** @@ -313,12 +503,12 @@ xb_node_get_attr (XbNode *self, const gchar *name) { XbNodePrivate *priv = GET_PRIVATE (self); - XbSiloAttr *a; + XbSiloNodeAttr *a; g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (name != NULL, NULL); - a = xb_silo_node_get_attr_by_str (priv->silo, priv->sn, name); + a = xb_silo_get_node_attr_by_str (priv->silo, priv->sn, name); if (a == NULL) return NULL; return xb_silo_from_strtab (priv->silo, a->attr_value); @@ -352,6 +542,82 @@ } /** + * xb_node_attr_iter_init: + * @iter: an uninitialized #XbNodeAttrIter + * @self: a #XbNode + * + * Initializes a name/value pair iterator for the node attributes + * and associates it with @self. + * The #XbNodeAttrIter structure is typically allocated on the stack + * and does not need to be freed explicitly. + * + * Since: 0.3.4 + */ +void +xb_node_attr_iter_init (XbNodeAttrIter *iter, XbNode *self) +{ + XbNodePrivate *priv = GET_PRIVATE (self); + RealAttrIter *ri = (RealAttrIter *) iter; + + g_return_if_fail (iter != NULL); + g_return_if_fail (XB_IS_NODE (self)); + + ri->node = self; + ri->position = xb_silo_node_get_attr_count (priv->sn); +} + +/** + * xb_node_attr_iter_next: + * @iter: an initialized #XbNodeAttrIter + * @name: (out) (optional) (not nullable): Destination of the returned attribute name + * @value: (out) (optional) (not nullable): Destination of the returned attribute value + * + * Returns the current attribute name and value and advances the iterator. + * Example: + * |[ + * XbNodeAttrIter iter; + * const gchar *attr_name, *attr_value; + * + * xb_node_attr_iter_init (&iter, node); + * while (xb_node_attr_iter_next (&iter, &attr_name, &attr_value)) { + * // use attr_name and attr_value; no need to free them + * } + * ]| + * + * Returns: %TRUE if there are more attributes. + * + * Since: 0.3.4 + */ +gboolean +xb_node_attr_iter_next (XbNodeAttrIter *iter, const gchar **name, const gchar **value) +{ + XbSiloNodeAttr *a; + XbNodePrivate *priv; + RealAttrIter *ri = (RealAttrIter *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + priv = GET_PRIVATE (ri->node); + + /* check if the iteration was finished */ + if (ri->position == 0) { + if (name != NULL) + *name = NULL; + if (value != NULL) + *value = NULL; + return FALSE; + } + + ri->position--; + a = xb_silo_node_get_attr (priv->sn, ri->position); + if (name != NULL) + *name = xb_silo_from_strtab (priv->silo, a->attr_name); + if (value != NULL) + *value = xb_silo_from_strtab (priv->silo, a->attr_value); + + return TRUE; +} + +/** * xb_node_get_depth: * @self: a #XbNode * @@ -366,7 +632,7 @@ { XbNodePrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_NODE (self), 0); - return xb_silo_node_get_depth (priv->silo, priv->sn); + return xb_silo_get_node_depth (priv->silo, priv->sn); } /** @@ -387,7 +653,7 @@ GString *xml; g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - xml = xb_silo_export_with_root (xb_node_get_silo (self), self, flags, error); + xml = xb_silo_export_with_root (xb_node_get_silo (self), xb_node_get_sn (self), flags, error); if (xml == NULL) return NULL; return g_string_free (xml, FALSE); diff -Nru libxmlb-0.1.15/src/xb-node.h libxmlb-0.3.6/src/xb-node.h --- libxmlb-0.1.15/src/xb-node.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node.h 2021-12-06 20:36:37.000000000 +0000 @@ -33,6 +33,7 @@ * @XB_NODE_EXPORT_FLAG_FORMAT_INDENT: Indent the XML by child depth * @XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS: Include the siblings when converting * @XB_NODE_EXPORT_FLAG_ONLY_CHILDREN: Only export the children of the node + * @XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY: If node has no children, collapse open and close tags * * The flags for converting to XML. **/ @@ -43,10 +44,33 @@ XB_NODE_EXPORT_FLAG_FORMAT_INDENT = 1 << 2, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS = 1 << 3, /* Since: 0.1.0 */ XB_NODE_EXPORT_FLAG_ONLY_CHILDREN = 1 << 4, /* Since: 0.1.0 */ + XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY = 1 << 5, /* Since: 0.2.2 */ /*< private >*/ XB_NODE_EXPORT_FLAG_LAST } XbNodeExportFlags; +typedef struct +{ + /*< private >*/ + gpointer dummy1; + guint8 dummy2; + gpointer dummy3; + gpointer dummy4; + gpointer dummy5; + gpointer dummy6; +} XbNodeAttrIter; + +typedef struct +{ + /*< private >*/ + gpointer dummy1; + gpointer dummy2; + gboolean dummy3; + gpointer dummy4; + gpointer dummy5; + gpointer dummy6; +} XbNodeChildIter; + typedef gboolean (*XbNodeTransmogrifyFunc) (XbNode *self, gpointer user_data); gboolean xb_node_transmogrify (XbNode *self, @@ -78,5 +102,17 @@ const gchar *name); guint xb_node_get_depth (XbNode *self); +void xb_node_attr_iter_init (XbNodeAttrIter *iter, + XbNode *self); +gboolean xb_node_attr_iter_next (XbNodeAttrIter *iter, + const gchar **name, + const gchar **value); + +void xb_node_child_iter_init (XbNodeChildIter *iter, + XbNode *self); +gboolean xb_node_child_iter_next (XbNodeChildIter *iter, + XbNode **child); +gboolean xb_node_child_iter_loop (XbNodeChildIter *iter, + XbNode **child); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-node-private.h libxmlb-0.3.6/src/xb-node-private.h --- libxmlb-0.1.15/src/xb-node-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -16,6 +16,5 @@ XbNode *xb_node_new (XbSilo *silo, XbSiloNode *sn); XbSiloNode *xb_node_get_sn (XbNode *self); -XbSilo *xb_node_get_silo (XbNode *self); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-node-query.c libxmlb-0.3.6/src/xb-node-query.c --- libxmlb-0.1.15/src/xb-node-query.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node-query.c 2021-12-06 20:36:37.000000000 +0000 @@ -8,10 +8,12 @@ #include "config.h" +#include #include -#include "xb-node-private.h" #include "xb-node-query.h" +#include "xb-node-silo.h" +#include "xb-silo-export-private.h" #include "xb-silo-query-private.h" /** @@ -47,7 +49,8 @@ * @query: an #XbQuery * @error: the #GError, or %NULL * - * Searches the silo using an prepared query. + * Searches the silo using a prepared query. To search using a query with + * bound values, use xb_node_query_with_context(). * * It is safe to call this function from a different thread to the one that * created the #XbSilo. @@ -64,7 +67,37 @@ g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (XB_IS_QUERY (query), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return xb_silo_query_with_root_full (xb_node_get_silo (self), self, query, error); + return xb_silo_query_with_root_full (xb_node_get_silo (self), self, query, NULL, FALSE, error); +} + +/** + * xb_node_query_with_context: + * @self: a #XbNode + * @query: an #XbQuery + * @context: (nullable) (transfer none): context including values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any context + * @error: the #GError, or %NULL + * + * Searches the silo using a prepared query, substituting values from the + * bindings in @context for bound opcodes as needed. + * + * It is safe to call this function from a different thread to the one that + * created the #XbSilo. + * + * Please note: Only a subset of XPath is supported. + * + * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound + * + * Since: 0.3.0 + **/ +GPtrArray * +xb_node_query_with_context (XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) +{ + g_return_val_if_fail (XB_IS_NODE (self), NULL); + g_return_val_if_fail (XB_IS_QUERY (query), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + return xb_silo_query_with_root_full (xb_node_get_silo (self), self, query, context, FALSE, error); } /** @@ -73,7 +106,9 @@ * @query: an #XbQuery * @error: the #GError, or %NULL * - * Searches the silo using an prepared query, returning up to one result. + * Searches the silo using a prepared query, returning up to one result. To + * search using a query with bound values, use + * xb_node_query_first_with_context(). * * It is safe to call this function from a different thread to the one that * created the #XbSilo. @@ -87,6 +122,32 @@ XbNode * xb_node_query_first_full (XbNode *self, XbQuery *query, GError **error) { + return xb_node_query_first_with_context (self, query, NULL, error); +} + +/** + * xb_node_query_first_with_context: + * @self: a #XbNode + * @query: an #XbQuery + * @context: (nullable) (transfer none): context including values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any context + * @error: the #GError, or %NULL + * + * Searches the silo using a prepared query, returning up to one result. + * + * It is safe to call this function from a different thread to the one that + * created the #XbSilo. + * + * Please note: Only a subset of XPath is supported. + * + * Returns: (transfer full): a #XbNode, or %NULL if unfound + * + * Since: 0.3.0 + **/ +XbNode * +xb_node_query_first_with_context (XbNode *self, XbQuery *query, XbQueryContext *context, GError **error) +{ g_autoptr(GPtrArray) results = NULL; g_return_val_if_fail (XB_IS_NODE (self), NULL); @@ -94,7 +155,8 @@ g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* nodes don't have to include themselves as part of the query */ - results = xb_silo_query_with_root_full (xb_node_get_silo (self), self, query, error); + results = xb_silo_query_with_root_full (xb_node_get_silo (self), self, query, context, TRUE, error); + if (results == NULL) return NULL; return g_object_ref (g_ptr_array_index (results, 0)); @@ -151,16 +213,21 @@ xb_node_query_text (XbNode *self, const gchar *xpath, GError **error) { const gchar *tmp; - g_autoptr(XbNode) n = NULL; + XbSilo *silo; + g_autoptr(GPtrArray) results = NULL; + XbSiloNode *sn; g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (xpath != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - n = xb_node_query_first (self, xpath, error); - if (n == NULL) + silo = xb_node_get_silo (self); + results = xb_silo_query_sn_with_root (silo, self, xpath, 1, error); + if (results == NULL) return NULL; - tmp = xb_node_get_text (n); + sn = g_ptr_array_index (results, 0); + + tmp = xb_silo_get_node_text (silo, sn); if (tmp == NULL) { g_set_error_literal (error, G_IO_ERROR, @@ -192,25 +259,30 @@ const gchar * xb_node_query_attr (XbNode *self, const gchar *xpath, const gchar *name, GError **error) { - const gchar *tmp; - g_autoptr(XbNode) n = NULL; + XbSiloNodeAttr *a; + XbSilo *silo; + g_autoptr(GPtrArray) results = NULL; + XbSiloNode *sn; g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (xpath != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - n = xb_node_query_first (self, xpath, error); - if (n == NULL) + silo = xb_node_get_silo (self); + results = xb_silo_query_sn_with_root (silo, self, xpath, 1, error); + if (results == NULL) return NULL; - tmp = xb_node_get_attr (n, name); - if (tmp == NULL) { + sn = g_ptr_array_index (results, 0); + + a = xb_silo_get_node_attr_by_str (silo, sn, name); + if (a == NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no text data"); return NULL; } - return tmp; + return xb_silo_from_strtab (silo, a->attr_value); } /** @@ -234,16 +306,25 @@ gchar * xb_node_query_export (XbNode *self, const gchar *xpath, GError **error) { - g_autoptr(XbNode) n = NULL; + GString *xml; + XbSilo *silo; + g_autoptr(GPtrArray) results = NULL; + XbSiloNode *sn; g_return_val_if_fail (XB_IS_NODE (self), NULL); g_return_val_if_fail (xpath != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - n = xb_node_query_first (self, xpath, error); - if (n == NULL) + silo = xb_node_get_silo (self); + results = xb_silo_query_sn_with_root (silo, self, xpath, 1, error); + if (results == NULL) + return NULL; + sn = g_ptr_array_index (results, 0); + + xml = xb_silo_export_with_root (silo, sn, XB_NODE_EXPORT_FLAG_NONE, error); + if (xml == NULL) return NULL; - return xb_node_export (n, XB_NODE_EXPORT_FLAG_NONE, error); + return g_string_free (xml, FALSE); } /** @@ -259,23 +340,26 @@ * * Please note: Only a subset of XPath is supported. * - * Returns: a guint64, or %G_MAXUINT64 if unfound + * Returns: a #guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_query_text_as_uint (XbNode *self, const gchar *xpath, GError **error) { - g_autoptr(XbNode) n = NULL; + const gchar *tmp; g_return_val_if_fail (XB_IS_NODE (self), G_MAXUINT64); g_return_val_if_fail (xpath != NULL, G_MAXUINT64); g_return_val_if_fail (error == NULL || *error == NULL, G_MAXUINT64); - n = xb_node_query_first (self, xpath, error); - if (n == NULL) + tmp = xb_node_query_text (self, xpath, error); + if (tmp == NULL) return G_MAXUINT64; - return xb_node_get_text_as_uint (n); + + if (g_str_has_prefix (tmp, "0x")) + return g_ascii_strtoull (tmp + 2, NULL, 16); + return g_ascii_strtoull (tmp, NULL, 10); } /** @@ -292,21 +376,24 @@ * * Please note: Only a subset of XPath is supported. * - * Returns: a guint64, or %G_MAXUINT64 if unfound + * Returns: a #guint64, or %G_MAXUINT64 if unfound * * Since: 0.1.0 **/ guint64 xb_node_query_attr_as_uint (XbNode *self, const gchar *xpath, const gchar *name, GError **error) { - g_autoptr(XbNode) n = NULL; + const gchar *tmp; g_return_val_if_fail (XB_IS_NODE (self), G_MAXUINT64); g_return_val_if_fail (xpath != NULL, G_MAXUINT64); g_return_val_if_fail (error == NULL || *error == NULL, G_MAXUINT64); - n = xb_node_query_first (self, xpath, error); - if (n == NULL) + tmp = xb_node_query_attr (self, xpath, name, error); + if (tmp == NULL) return G_MAXUINT64; - return xb_node_get_attr_as_uint (n, name); + + if (g_str_has_prefix (tmp, "0x")) + return g_ascii_strtoull (tmp + 2, NULL, 16); + return g_ascii_strtoull (tmp, NULL, 10); } diff -Nru libxmlb-0.1.15/src/xb-node-query.h libxmlb-0.3.6/src/xb-node-query.h --- libxmlb-0.1.15/src/xb-node-query.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node-query.h 2021-12-06 20:36:37.000000000 +0000 @@ -9,6 +9,7 @@ #include #include "xb-query.h" +#include "xb-query-context.h" #include "xb-node.h" G_BEGIN_DECLS @@ -17,15 +18,27 @@ const gchar *xpath, guint limit, GError **error); + GPtrArray *xb_node_query_full (XbNode *self, XbQuery *query, GError **error); +GPtrArray *xb_node_query_with_context (XbNode *self, + XbQuery *query, + XbQueryContext *context, + GError **error); + XbNode *xb_node_query_first (XbNode *self, const gchar *xpath, GError **error); + XbNode *xb_node_query_first_full (XbNode *self, XbQuery *query, GError **error); +XbNode *xb_node_query_first_with_context(XbNode *self, + XbQuery *query, + XbQueryContext *context, + GError **error); + const gchar *xb_node_query_text (XbNode *self, const gchar *xpath, GError **error); diff -Nru libxmlb-0.1.15/src/xb-node-silo.h libxmlb-0.3.6/src/xb-node-silo.h --- libxmlb-0.1.15/src/xb-node-silo.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-node-silo.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "xb-silo.h" +#include "xb-node.h" + +G_BEGIN_DECLS + +XbSilo *xb_node_get_silo (XbNode *self); + +G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-opcode.c libxmlb-0.3.6/src/xb-opcode.c --- libxmlb-0.1.15/src/xb-opcode.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-opcode.c 2021-12-06 20:36:37.000000000 +0000 @@ -12,14 +12,6 @@ #include "xb-opcode-private.h" -struct _XbOpcode { - gint ref; - XbOpcodeKind kind; - guint32 val; - gpointer ptr; - GDestroyNotify destroy_func; -}; - /** * xb_opcode_kind_to_string: * @kind: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_FUNCTION @@ -33,10 +25,7 @@ const gchar * xb_opcode_kind_to_string (XbOpcodeKind kind) { - if (kind == XB_OPCODE_KIND_FUNCTION) - return "FUNC"; - if (kind == XB_OPCODE_KIND_TEXT) - return "TEXT"; + /* special cases */ if (kind == XB_OPCODE_KIND_INTEGER) return "INTE"; if (kind == XB_OPCODE_KIND_BOUND_UNSET) @@ -49,6 +38,12 @@ return "TEXI"; if (kind == XB_OPCODE_KIND_BOOLEAN) return "BOOL"; + + /* bitwise fallbacks */ + if (kind & XB_OPCODE_FLAG_FUNCTION) + return "FUNC"; + if (kind & XB_OPCODE_FLAG_TEXT) + return "TEXT"; return NULL; } @@ -104,23 +99,10 @@ return self->ptr; } -/** - * xb_opcode_to_string: - * @self: a #XbOpcode - * - * Returns a string representing the specific opcode. - * - * Returns: text - * - * Since: 0.1.4 - **/ -gchar * -xb_opcode_to_string (XbOpcode *self) +static gchar * +xb_opcode_to_string_internal (XbOpcode *self) { - if (self->kind == XB_OPCODE_KIND_FUNCTION) - return g_strdup_printf ("%s()", xb_opcode_get_str_for_display (self)); - if (self->kind == XB_OPCODE_KIND_TEXT) - return g_strdup_printf ("'%s'", xb_opcode_get_str_for_display (self)); + /* special cases */ if (self->kind == XB_OPCODE_KIND_INDEXED_TEXT) return g_strdup_printf ("$'%s'", xb_opcode_get_str_for_display (self)); if (self->kind == XB_OPCODE_KIND_INTEGER) @@ -133,11 +115,41 @@ return g_strdup_printf ("?%u", xb_opcode_get_val (self)); if (self->kind == XB_OPCODE_KIND_BOOLEAN) return g_strdup (xb_opcode_get_val (self) ? "True" : "False"); - g_critical ("no to_string for kind %u", self->kind); + + /* bitwise fallbacks */ + if (self->kind & XB_OPCODE_FLAG_FUNCTION) + return g_strdup_printf ("%s()", xb_opcode_get_str_for_display (self)); + if (self->kind & XB_OPCODE_FLAG_TEXT) + return g_strdup_printf ("'%s'", xb_opcode_get_str_for_display (self)); + g_critical ("no to_string for kind 0x%x", self->kind); return NULL; } /** + * xb_opcode_to_string: + * @self: a #XbOpcode + * + * Returns a string representing the specific opcode. + * + * Returns: text + * + * Since: 0.1.4 + **/ +gchar * +xb_opcode_to_string (XbOpcode *self) +{ + g_autofree gchar *tmp = xb_opcode_to_string_internal (self); + if (self->kind & XB_OPCODE_FLAG_TOKENIZED) { + g_autofree gchar *tokens = NULL; + /* Ensure the array is NULL-terminated */ + self->tokens[self->tokens_len] = NULL; + tokens = g_strjoinv (",", (gchar **) self->tokens); + return g_strdup_printf ("%s[%s]", tmp, tokens); + } + return g_steal_pointer (&tmp); +} + +/** * xb_opcode_get_kind: * @self: a #XbOpcode * @@ -150,7 +162,39 @@ XbOpcodeKind xb_opcode_get_kind (XbOpcode *self) { - return self->kind; + return self->kind & ~XB_OPCODE_FLAG_TOKENIZED; +} + +/** + * xb_opcode_has_flag: + * @self: a #XbOpcode + * @flag: a #XbOpcodeFlags, e.g. #XB_OPCODE_FLAG_TOKENIZED + * + * Finds out if an opcode has a flag set. + * + * Returns: %TRUE if the flag is set + * + * Since: 0.3.1 + **/ +gboolean +xb_opcode_has_flag (XbOpcode *self, XbOpcodeFlags flag) +{ + return (self->kind & flag) > 0; +} + +/** + * xb_opcode_add_flag: + * @self: a #XbOpcode + * @flag: a #XbOpcodeFlags, e.g. #XB_OPCODE_FLAG_TOKENIZED + * + * Adds a flag to the opcode. + * + * Since: 0.3.1 + **/ +void +xb_opcode_add_flag (XbOpcode *self, XbOpcodeFlags flag) +{ + self->kind |= flag; } /** @@ -184,16 +228,14 @@ inline gboolean xb_opcode_cmp_str (XbOpcode *self) { - return self->kind == XB_OPCODE_KIND_TEXT || - self->kind == XB_OPCODE_KIND_BOUND_TEXT || - self->kind == XB_OPCODE_KIND_INDEXED_TEXT; + return xb_opcode_has_flag (self, XB_OPCODE_FLAG_TEXT); } /* private */ gboolean -xb_opcode_is_bound (XbOpcode *self) +xb_opcode_is_binding (XbOpcode *self) { - return (self->kind & XB_OPCODE_FLAG_BOUND) > 0; + return xb_opcode_has_flag (self, XB_OPCODE_FLAG_BOUND); } /** @@ -230,173 +272,144 @@ } /** - * xb_opcode_unref: + * xb_opcode_get_tokens: * @self: a #XbOpcode * - * Decrements the reference count of the opcode, freeing the object when the - * refcount drops to zero. + * Gets the tokenized string stored on the opcode. * - * Since: 0.1.1 + * Returns: a #GStrv, which is always %NULL terminated + * + * Since: 0.3.1 **/ -void -xb_opcode_unref (XbOpcode *self) +const gchar ** +xb_opcode_get_tokens (XbOpcode *self) { - g_assert (self->ref > 0); - if (--self->ref > 0) - return; - if (self->destroy_func) - self->destroy_func (self->ptr); - g_slice_free (XbOpcode, self); + return self->tokens; } /** - * xb_opcode_ref: + * xb_opcode_clear: * @self: a #XbOpcode * - * Increments the refcount of the opcode. - * - * Returns: (transfer none): the original @self #XbOpcode instance + * Clears any allocated data inside the opcode, but does not free the #XbOpcode + * itself. This is suitable for calling on stack-allocated #XbOpcodes. * - * Since: 0.1.1 - **/ -XbOpcode * -xb_opcode_ref (XbOpcode *self) + * Since: 0.2.0 + */ +void +xb_opcode_clear (XbOpcode *self) { - self->ref++; - return self; + if (self->destroy_func) + self->destroy_func (self->ptr); + self->destroy_func = NULL; } /** - * xb_opcode_text_new: + * xb_opcode_text_init: + * @opcode: a stack allocated #XbOpcode to initialise * @str: a string * - * Creates a new text literal opcode. The @str argument is copied internally - * and is not tied to the lifecycle of the #XbOpcode. + * Initialises a stack allocated #XbOpcode to contain a text literal. + * The @str argument is copied internally and is not tied to the lifecycle of + * the #XbOpcode. * - * Returns: (transfer full): a #XbOpcode - * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_text_new (const gchar *str) +void +xb_opcode_text_init (XbOpcode *opcode, const gchar *str) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_TEXT; - self->ptr = g_strdup (str); - self->destroy_func = g_free; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_TEXT, g_strdup (str), 0, g_free); } /** - * xb_opcode_new: + * xb_opcode_init: + * @opcode: allocated opcode to fill * @kind: a #XbOpcodeKind, e.g. %XB_OPCODE_KIND_INTEGER * @str: a string * @val: a integer value * @destroy_func: (nullable): a #GDestroyNotify, e.g. g_free() * - * Creates a new opcode. - * - * Returns: (transfer full): a #XbOpcode + * Initialises a stack allocated #XbOpcode. * - * Since: 0.1.4 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_new (XbOpcodeKind kind, - const gchar *str, - guint32 val, - GDestroyNotify destroy_func) -{ - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = kind; - self->ptr = (gpointer) str; - self->val = val; - self->destroy_func = destroy_func; - return self; +void +xb_opcode_init (XbOpcode *opcode, + XbOpcodeKind kind, + const gchar *str, + guint32 val, + GDestroyNotify destroy_func) +{ + opcode->kind = kind; + opcode->ptr = (gpointer) str; + opcode->val = val; + opcode->tokens_len = 0; + opcode->destroy_func = destroy_func; } /** - * xb_opcode_text_new_static: + * xb_opcode_text_init_static: + * @opcode: a stack allocated #XbOpcode to initialise * @str: a string * - * Creates a new text literal opcode, where @str is either static text or will - * outlive the #XbOpcode lifecycle. + * Initialises a stack allocated #XbOpcode to contain a text literal, where + * @str is either static text or will outlive the #XbOpcode lifecycle. * - * Returns: (transfer full): a #XbOpcode - * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_text_new_static (const gchar *str) +void +xb_opcode_text_init_static (XbOpcode *opcode, const gchar *str) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_TEXT; - self->ptr = (gpointer) str; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_TEXT, str, 0, NULL); } /** - * xb_opcode_text_new_steal: + * xb_opcode_text_init_steal: + * @opcode: a stack allocated #XbOpcode to initialise * @str: a string * - * Creates a new text literal opcode, stealing the @str. Once the opcode is - * finalized g_free() will be called on @str. - * - * Returns: (transfer full): a #XbOpcode + * Initialises a stack allocated #XbOpcode to contain a text literal, stealing + * the @str. Once the opcode is finalized g_free() will be called on @str. * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_text_new_steal (gchar *str) +void +xb_opcode_text_init_steal (XbOpcode *opcode, gchar *str) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_TEXT; - self->ptr = (gpointer) str; - self->destroy_func = g_free; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_TEXT, g_steal_pointer (&str), 0, g_free); } /** - * xb_opcode_func_new: + * xb_opcode_func_init: + * @opcode: a stack allocated #XbOpcode to initialise * @func: a function index * - * Creates an opcode for a specific function. Custom functions can be registered - * using xb_machine_add_func() and retrieved using xb_machine_opcode_func_new(). - * - * Returns: (transfer full): a #XbOpcode + * Initialises a stack allocated #XbOpcode to contain a specific function. + * Custom functions can be registered using xb_machine_add_func() and retrieved + * using xb_machine_opcode_func_new(). * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_func_new (guint32 func) +void +xb_opcode_func_init (XbOpcode *opcode, guint32 func) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_FUNCTION; - self->val = func; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_FUNCTION, NULL, func, NULL); } /** - * xb_opcode_bind_new: - * - * Creates an opcode for a bind variable. A value needs to be assigned to this - * opcode at runtime using xb_query_bind_str(). + * xb_opcode_bind_init: + * @opcode: a stack allocated #XbOpcode to initialise * - * Returns: (transfer full): a #XbOpcode + * Initialises a stack allocated #XbOpcode to contain a bind variable. A value + * needs to be assigned to this opcode at runtime using + * xb_value_bindings_bind_str() or xb_value_bindings_bind_val(). * - * Since: 0.1.4 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_bind_new (void) +void +xb_opcode_bind_init (XbOpcode *opcode) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_BOUND_INTEGER; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_BOUND_INTEGER, NULL, 0, NULL); } /* private */ @@ -432,6 +445,19 @@ } /* private */ +gboolean +xb_opcode_append_token (XbOpcode *self, const gchar *val) +{ + g_return_val_if_fail (val != NULL, FALSE); + g_return_val_if_fail (val[0] != '\0', FALSE); + if (self->tokens_len >= XB_OPCODE_TOKEN_MAX) + return FALSE; + self->tokens[self->tokens_len++] = val; + self->kind |= XB_OPCODE_FLAG_TOKENIZED; + return TRUE; +} + +/* private */ void xb_opcode_set_kind (XbOpcode *self, XbOpcodeKind kind) { @@ -439,44 +465,23 @@ } /** - * xb_opcode_integer_new: + * xb_opcode_integer_init: + * @opcode: a stack allocated #XbOpcode to initialise * @val: a integer value * - * Creates an opcode with an literal integer. - * - * Returns: (transfer full): a #XbOpcode + * Initialises a stack allocated #XbOpcode to contain an integer literal. * - * Since: 0.1.1 + * Since: 0.2.0 **/ -XbOpcode * -xb_opcode_integer_new (guint32 val) +void +xb_opcode_integer_init (XbOpcode *opcode, guint32 val) { - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_INTEGER; - self->val = val; - return self; + xb_opcode_init (opcode, XB_OPCODE_KIND_INTEGER, NULL, val, NULL); } /* private */ -XbOpcode * -xb_opcode_bool_new (gboolean val) -{ - XbOpcode *self = g_slice_new0 (XbOpcode); - self->ref = 1; - self->kind = XB_OPCODE_KIND_BOOLEAN; - self->val = val; - return self; -} - -GType -xb_opcode_get_type (void) +void +xb_opcode_bool_init (XbOpcode *opcode, gboolean val) { - static GType type = 0; - if (G_UNLIKELY (!type)) { - type = g_boxed_type_register_static ("XbOpcode", - (GBoxedCopyFunc) xb_opcode_ref, - (GBoxedFreeFunc) xb_opcode_unref); - } - return type; + xb_opcode_init (opcode, XB_OPCODE_KIND_BOOLEAN, NULL, !!val, NULL); } diff -Nru libxmlb-0.1.15/src/xb-opcode.h libxmlb-0.3.6/src/xb-opcode.h --- libxmlb-0.1.15/src/xb-opcode.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-opcode.h 2021-12-06 20:36:37.000000000 +0000 @@ -17,13 +17,14 @@ * @XB_OPCODE_FLAG_TEXT: Text value set * @XB_OPCODE_FLAG_FUNCTION: An operator * @XB_OPCODE_FLAG_BOUND: A bound value, assigned later + * @XB_OPCODE_FLAG_TOKENIZED: Tokenized text * * The opcode flags. The values have been carefully chosen so that a simple * bitmask can be done to know how to compare for equality. * * function─┐ ┌─string * bound──┐ │ │ ┌──integer - * │ │ │ │ + * token┐ │ │ │ │ * X X X X X X X * 8 4 2 1 **/ @@ -34,6 +35,7 @@ XB_OPCODE_FLAG_FUNCTION = 1 << 2, /* Since: 0.1.4 */ XB_OPCODE_FLAG_BOUND = 1 << 3, /* Since: 0.1.4 */ XB_OPCODE_FLAG_BOOLEAN = 1 << 4, /* Since: 0.1.11 */ + XB_OPCODE_FLAG_TOKENIZED = 1 << 5, /* Since: 0.3.1 */ /*< private >*/ XB_OPCODE_FLAG_LAST } XbOpcodeFlags; @@ -67,24 +69,23 @@ gboolean xb_opcode_cmp_val (XbOpcode *self); gboolean xb_opcode_cmp_str (XbOpcode *self); -GType xb_opcode_get_type (void); gchar *xb_opcode_to_string (XbOpcode *self); const gchar *xb_opcode_kind_to_string (XbOpcodeKind kind); XbOpcodeKind xb_opcode_kind_from_string (const gchar *str); -void xb_opcode_unref (XbOpcode *self); -XbOpcode *xb_opcode_ref (XbOpcode *self); - XbOpcodeKind xb_opcode_get_kind (XbOpcode *self); const gchar *xb_opcode_get_str (XbOpcode *self); guint32 xb_opcode_get_val (XbOpcode *self); -XbOpcode *xb_opcode_func_new (guint32 func); -XbOpcode *xb_opcode_integer_new (guint32 val); -XbOpcode *xb_opcode_text_new (const gchar *str); -XbOpcode *xb_opcode_text_new_static (const gchar *str); -XbOpcode *xb_opcode_text_new_steal (gchar *str); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbOpcode, xb_opcode_unref) +void xb_opcode_func_init (XbOpcode *opcode, + guint32 func); +void xb_opcode_integer_init (XbOpcode *opcode, + guint32 val); +void xb_opcode_text_init (XbOpcode *opcode, + const gchar *str); +void xb_opcode_text_init_static (XbOpcode *opcode, + const gchar *str); +void xb_opcode_text_init_steal (XbOpcode *opcode, + gchar *str); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-opcode-private.h libxmlb-0.3.6/src/xb-opcode-private.h --- libxmlb-0.1.15/src/xb-opcode-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-opcode-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -6,26 +6,75 @@ #pragma once +#include + #include "xb-opcode.h" G_BEGIN_DECLS -XbOpcode *xb_opcode_new (XbOpcodeKind kind, +/* maximum number of tokens supported for each element -- this is a compromise + * between making the _XbOpcode struct too large and search results */ +#define XB_OPCODE_TOKEN_MAX 32 + +struct _XbOpcode { + XbOpcodeKind kind; + guint32 val; + gpointer ptr; + guint8 tokens_len; + const gchar *tokens[XB_OPCODE_TOKEN_MAX+1]; + GDestroyNotify destroy_func; +}; + +#define XB_OPCODE_INIT() { 0, 0, NULL, 0, {NULL}, NULL } + +/** + * xb_opcode_steal: + * @op_ptr: (transfer full): pointer to an #XbOpcode to steal + * + * Steal the stack-allocated #XbOpcode pointed to by @op_ptr, returning its + * value and clearing its previous storage location using `memset()`. + * + * Returns: the value of @op_ptr + * Since: 0.2.0 + */ +static inline XbOpcode +xb_opcode_steal (XbOpcode *op_ptr) +{ + XbOpcode op = *op_ptr; + memset (op_ptr, 0, sizeof (XbOpcode)); + return op; +} + +void xb_opcode_init (XbOpcode *opcode, + XbOpcodeKind kind, const gchar *str, guint32 val, GDestroyNotify destroy_func); -XbOpcode *xb_opcode_bind_new (void); -gboolean xb_opcode_is_bound (XbOpcode *self); +void xb_opcode_clear (XbOpcode *opcode); +void xb_opcode_bind_init (XbOpcode *opcode); +gboolean xb_opcode_is_binding (XbOpcode *self); +G_DEPRECATED_FOR(xb_value_bindings_bind_str) void xb_opcode_bind_str (XbOpcode *self, gchar *str, GDestroyNotify destroy_func); +G_DEPRECATED_FOR(xb_value_bindings_bind_val) void xb_opcode_bind_val (XbOpcode *self, guint32 val); void xb_opcode_set_kind (XbOpcode *self, XbOpcodeKind kind); void xb_opcode_set_val (XbOpcode *self, guint32 val); +gboolean xb_opcode_append_token (XbOpcode *self, + const gchar *val); +const gchar **xb_opcode_get_tokens (XbOpcode *self); gchar *xb_opcode_get_sig (XbOpcode *self); -XbOpcode *xb_opcode_bool_new (gboolean val); +void xb_opcode_bool_init (XbOpcode *opcode, + gboolean val); +gboolean xb_opcode_has_flag (XbOpcode *self, + XbOpcodeFlags flag); +void xb_opcode_add_flag (XbOpcode *self, + XbOpcodeFlags flag); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (XbOpcode, xb_opcode_clear) G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-query.c libxmlb-0.3.6/src/xb-query.c --- libxmlb-0.1.15/src/xb-query.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-query.c 2021-12-06 20:36:37.000000000 +0000 @@ -17,9 +17,7 @@ #include "xb-stack-private.h" typedef struct { - GObject parent_instance; GPtrArray *sections; /* of XbQuerySection */ - XbSilo *silo; XbQueryFlags flags; gchar *xpath; guint limit; @@ -28,6 +26,10 @@ G_DEFINE_TYPE_WITH_PRIVATE (XbQuery, xb_query, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_query_get_instance_private (o)) +typedef struct { + XbSilo *silo; +} XbQueryParseContext; + /** * xb_query_get_sections: * @self: a #XbQuery @@ -119,6 +121,8 @@ * * Returns: integer, default 0 * + * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_get_limit() + * instead. * Since: 0.1.4 **/ guint @@ -136,6 +140,8 @@ * * Sets the results limit on this query, where 0 is 'all'. * + * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_set_limit() + * instead. * Since: 0.1.4 **/ void @@ -154,6 +160,8 @@ * * Returns: #XbQueryFlags, default %XB_QUERY_FLAG_NONE * + * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_get_flags() + * instead. * Since: 0.1.15 **/ XbQueryFlags @@ -171,6 +179,8 @@ * * Sets the flags to use for this query. * + * Deprecated: 0.3.0: This is not thread-safe. Use xb_query_context_set_flags() + * instead. * Since: 0.1.15 **/ void @@ -195,7 +205,7 @@ XbStack *stack = g_ptr_array_index (section->predicates, j); for (guint k = 0; k < xb_stack_get_size (stack); k++) { XbOpcode *op = xb_stack_peek (stack, k); - if (xb_opcode_is_bound (op)) { + if (xb_opcode_is_binding (op)) { if (idx == idx_cnt++) return op; } @@ -217,10 +227,14 @@ * Returns: %TRUE if the @idx existed * * Since: 0.1.4 + * Deprecated: 0.3.0: Use #XbValueBindings and xb_value_bindings_bind_str() + * instead. That keeps the value bindings separate from the #XbQuery, + * allowing queries to be re-used over time and between threads. **/ gboolean xb_query_bind_str (XbQuery *self, guint idx, const gchar *str, GError **error) { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbOpcode *op; g_return_val_if_fail (XB_IS_QUERY (self), FALSE); @@ -237,6 +251,7 @@ } xb_opcode_bind_str (op, g_strdup (str), g_free); return TRUE; +G_GNUC_END_IGNORE_DEPRECATIONS } /** @@ -251,10 +266,14 @@ * Returns: %TRUE if the @idx existed * * Since: 0.1.4 + * Deprecated: 0.3.0: Use #XbValueBindings and xb_value_bindings_bind_val() + * instead. That keeps the value bindings separate from the #XbQuery, + * allowing queries to be re-used over time and between threads. **/ gboolean xb_query_bind_val (XbQuery *self, guint idx, guint32 val, GError **error) { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbOpcode *op; g_return_val_if_fail (XB_IS_QUERY (self), FALSE); @@ -271,6 +290,7 @@ } xb_opcode_bind_val (op, val); return TRUE; +G_GNUC_END_IGNORE_DEPRECATIONS } static void @@ -285,12 +305,11 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbQuerySection, xb_query_section_free) static gboolean -xb_query_repair_opcode_texi (XbQuery *self, XbOpcode *op, GError **error) +xb_query_repair_opcode_texi (XbQuery *self, XbQueryParseContext *context, XbOpcode *op, GError **error) { - XbQueryPrivate *priv = GET_PRIVATE (self); if (xb_opcode_get_val (op) == XB_SILO_UNSET) { const gchar *tmp = xb_opcode_get_str (op); - guint32 val = xb_silo_strtab_index_lookup (priv->silo, tmp); + guint32 val = xb_silo_strtab_index_lookup (context->silo, tmp); if (val == XB_SILO_UNSET) { g_set_error (error, G_IO_ERROR, @@ -304,8 +323,10 @@ return TRUE; } +/* Returns an error if the XPath is invalid. */ static gboolean xb_query_parse_predicate (XbQuery *self, + XbQueryParseContext *context, XbQuerySection *section, const gchar *text, gssize text_len, @@ -320,7 +341,7 @@ machine_flags |= XB_MACHINE_PARSE_FLAG_OPTIMIZE; /* parse */ - opcodes = xb_machine_parse_full (xb_silo_get_machine (priv->silo), + opcodes = xb_machine_parse_full (xb_silo_get_machine (context->silo), text, text_len, machine_flags, error); @@ -333,7 +354,7 @@ XbOpcode *op = xb_stack_peek (opcodes, i); if (xb_opcode_get_kind (op) != XB_OPCODE_KIND_INDEXED_TEXT) continue; - if (!xb_query_repair_opcode_texi (self, op, error)) + if (!xb_query_repair_opcode_texi (self, context, op, error)) return FALSE; } } else { @@ -351,10 +372,10 @@ return TRUE; } +/* Returns an error if the XPath is invalid. */ static XbQuerySection * -xb_query_parse_section (XbQuery *self, const gchar *xpath, GError **error) +xb_query_parse_section (XbQuery *self, XbQueryParseContext *context, const gchar *xpath, GError **error) { - XbQueryPrivate *priv = GET_PRIVATE (self); g_autoptr(XbQuerySection) section = g_slice_new0 (XbQuerySection); guint start = 0; @@ -375,6 +396,7 @@ } if (start > 0 && xpath[i] == ']') { if (!xb_query_parse_predicate (self, + context, section, xpath + start + 1, i - start - 1, @@ -403,20 +425,18 @@ section->kind = XB_SILO_QUERY_KIND_WILDCARD; return g_steal_pointer (§ion); } - section->element_idx = xb_silo_get_strtab_idx (priv->silo, section->element); - if (section->element_idx == XB_SILO_UNSET) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_ARGUMENT, - "element name %s is unknown in silo", - section->element); - return NULL; - } + + /* This may result in @element_idx being set to %XB_SILO_UNSET if the + * given element (`section->element`) is not in the silo at all. Ignore + * that for now, and return no matches when the query is actually run. */ + section->element_idx = xb_silo_get_strtab_idx (context->silo, section->element); + return g_steal_pointer (§ion); } +/* Returns an error if the XPath is invalid. */ static gboolean -xb_query_parse (XbQuery *self, const gchar *xpath, GError **error) +xb_query_parse (XbQuery *self, XbQueryParseContext *context, const gchar *xpath, GError **error) { XbQueryPrivate *priv = GET_PRIVATE (self); XbQuerySection *section; @@ -445,7 +465,7 @@ "xpath section empty"); return FALSE; } - section = xb_query_parse_section (self, acc->str, error); + section = xb_query_parse_section (self, context, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add (priv->sections, section); @@ -456,7 +476,7 @@ } /* add any remaining section */ - section = xb_query_parse_section (self, acc->str, error); + section = xb_query_parse_section (self, context, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add (priv->sections, section); @@ -473,6 +493,9 @@ * Creates a query to be used by @silo. It may be quicker to create a query * manually and re-use it multiple times. * + * The query will point to strings inside @silo, so the lifetime of @silo must + * exceed the lifetime of the returned query. + * * Returns: (transfer full): a #XbQuery * * Since: 0.1.6 @@ -482,18 +505,21 @@ { g_autoptr(XbQuery) self = g_object_new (XB_TYPE_QUERY, NULL); XbQueryPrivate *priv = GET_PRIVATE (self); + XbQueryParseContext parse_context = { + .silo = silo, + }; g_return_val_if_fail (XB_IS_SILO (silo), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - /* create */ - priv->silo = g_object_ref (silo); + /* create; don’t take a reference on @silo otherwise we get refcount + * loops with cached queries from xb_silo_lookup_query() */ priv->xpath = g_strdup (xpath); priv->flags = flags; priv->sections = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_query_section_free); /* add each section */ - if (!xb_query_parse (self, xpath, error)) + if (!xb_query_parse (self, &parse_context, xpath, error)) return NULL; /* nothing here! */ @@ -542,7 +568,6 @@ { XbQuery *self = XB_QUERY (obj); XbQueryPrivate *priv = GET_PRIVATE (self); - g_object_unref (priv->silo); g_ptr_array_unref (priv->sections); g_free (priv->xpath); G_OBJECT_CLASS (xb_query_parent_class)->finalize (obj); diff -Nru libxmlb-0.1.15/src/xb-query-context.c libxmlb-0.3.6/src/xb-query-context.c --- libxmlb-0.1.15/src/xb-query-context.c 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-query-context.c 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,224 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2020 Endless OS Foundation LLC + * + * Author: Philip Withnall + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "XbQueryContext" + +#include "config.h" + +#include + +#include "xb-query.h" +#include "xb-query-context.h" +#include "xb-value-bindings.h" + + +/* Why are #XbQueryContext and #XbValueBindings not the same object? + * #XbQueryContext is associated with a query, but the #XbValueBindings is + * associated with a query *and* the #XbMachine which runs for it. Once an + * #XbQuery is turned into an #XbMachine to be evaluated, the #XbQueryContext is + * ignored and only the #XbValueBindings are taken forward to be used, copied + * and subsetted for various parts of the #XbMachine. */ +typedef struct { + guint limit; + XbQueryFlags flags; + XbValueBindings bindings; + gpointer dummy[5]; +} RealQueryContext; + +G_STATIC_ASSERT (sizeof (XbQueryContext) == sizeof (RealQueryContext)); + +G_DEFINE_BOXED_TYPE (XbQueryContext, xb_query_context, + xb_query_context_copy, xb_query_context_free) + +/** + * xb_query_context_init: + * @self: an uninitialised #XbQueryContext to initialise + * + * Initialise a stack-allocated #XbQueryContext struct so it can be used. + * + * Stack-allocated #XbQueryContext instances should be freed once finished + * with, using xb_query_context_clear() (or `g_auto(XbQueryContext)`, which is + * equivalent). + * + * Since: 0.3.0 + */ +void +xb_query_context_init (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + _self->limit = 0; + _self->flags = XB_QUERY_FLAG_NONE; + xb_value_bindings_init (&_self->bindings); +} + +/** + * xb_query_context_clear: + * @self: an #XbQueryContext + * + * Clear an #XbQueryContext, freeing any allocated memory it points to. + * + * After this function has been called, the contents of the #XbQueryContext are + * undefined, and it’s only safe to call xb_query_context_init() on it. + * + * Since: 0.3.0 + */ +void +xb_query_context_clear (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + xb_value_bindings_clear (&_self->bindings); +} + +/** + * xb_query_context_copy: + * @self: an #XbQueryContext + * + * Copy @self into a new heap-allocated #XbQueryContext instance. + * + * Returns: (transfer full): a copy of @self + * Since: 0.3.0 + */ +XbQueryContext * +xb_query_context_copy (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + g_autoptr(XbQueryContext) copy = g_new0 (XbQueryContext, 1); + RealQueryContext *_copy = (RealQueryContext *) copy; + gsize i = 0; + + xb_query_context_init (copy); + + _copy->limit = _self->limit; + _copy->flags = _self->flags; + + while (xb_value_bindings_copy_binding (&_self->bindings, i, &_copy->bindings, i)) + i++; + + return g_steal_pointer (©); +} + +/** + * xb_query_context_free: + * @self: a heap-allocated #XbQueryContext + * + * Free a heap-allocated #XbQueryContext instance. This should be used on + * #XbQueryContext instances created with xb_query_context_copy(). + * + * For stack-allocated instances, xb_query_context_clear() should be used + * instead. + * + * Since: 0.3.0 + */ +void +xb_query_context_free (XbQueryContext *self) +{ + g_return_if_fail (self != NULL); + + xb_query_context_clear (self); + g_free (self); +} + +/** + * xb_query_context_get_bindings: + * @self: an #XbQueryContext + * + * Get the #XbValueBindings for this query context. + * + * Returns: (transfer none) (not nullable): bindings + * Since: 0.3.0 + */ +XbValueBindings * +xb_query_context_get_bindings (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + g_return_val_if_fail (self != NULL, NULL); + + return &_self->bindings; +} + +/** + * xb_query_context_get_limit: + * @self: an #XbQueryContext + * + * Get the limit on the number of query results. See + * xb_query_context_set_limit(). + * + * Returns: limit on results, or `0` if unlimited + * Since: 0.3.0 + */ +guint +xb_query_context_get_limit (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + g_return_val_if_fail (self != NULL, 0); + + return _self->limit; +} + +/** + * xb_query_context_set_limit: + * @self: an #XbQueryContext + * @limit: number of query results to return, or `0` for unlimited + * + * Set the limit on the number of results to return from the query. + * + * Since: 0.3.0 + */ +void +xb_query_context_set_limit (XbQueryContext *self, guint limit) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + g_return_if_fail (self != NULL); + + _self->limit = limit; +} + +/** + * xb_query_context_get_flags: + * @self: an #XbQueryContext + * + * Get the flags set on the context. See xb_query_context_set_flags(). + * + * Returns: query flags + * Since: 0.3.0 + */ +XbQueryFlags +xb_query_context_get_flags (XbQueryContext *self) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + g_return_val_if_fail (self != NULL, XB_QUERY_FLAG_NONE); + + return _self->flags; +} + +/** + * xb_query_context_set_flags: + * @self: an #XbQueryContext + * @flags: query flags, or %XB_QUERY_FLAG_NONE for none + * + * Set flags which affect the behaviour of the query. + * + * Since: 0.3.0 + */ +void +xb_query_context_set_flags (XbQueryContext *self, XbQueryFlags flags) +{ + RealQueryContext *_self = (RealQueryContext *) self; + + g_return_if_fail (self != NULL); + + _self->flags = flags; +} diff -Nru libxmlb-0.1.15/src/xb-query-context.h libxmlb-0.3.6/src/xb-query-context.h --- libxmlb-0.1.15/src/xb-query-context.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-query-context.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,75 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2020 Endless OS Foundation LLC + * + * Author: Philip Withnall + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "xb-query.h" +#include "xb-value-bindings.h" + +G_BEGIN_DECLS + +/** + * XbQueryContext: + * + * An opaque struct which contains context for executing a query in, such as the + * number of results to return, or values to bind to query placeholders. + * + * Since: 0.3.0 + */ +typedef struct { + /*< private >*/ + gint dummy0; + guint dummy1; + XbValueBindings dummy2; + gpointer dummy3[5]; +} XbQueryContext; + +GType xb_query_context_get_type (void); + +/** + * XB_QUERY_CONTEXT_INIT: + * + * Static initialiser for #XbQueryContext so it can be used on the stack. + * + * Use it in association with g_auto(), to ensure the bindings are freed once + * finished with: + * |[ + * g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT (); + * + * xb_query_context_set_limit (&context, 0); + * ]| + * + * Since: 0.3.0 + */ +#define XB_QUERY_CONTEXT_INIT() { 0, 0, XB_VALUE_BINDINGS_INIT (), { NULL, NULL, NULL, NULL, NULL } } + +void xb_query_context_init (XbQueryContext *self); +void xb_query_context_clear (XbQueryContext *self); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (XbQueryContext, xb_query_context_clear) + +XbQueryContext *xb_query_context_copy (XbQueryContext *self); +void xb_query_context_free (XbQueryContext *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XbQueryContext, xb_query_context_free) + +XbValueBindings *xb_query_context_get_bindings (XbQueryContext *self); + +guint xb_query_context_get_limit (XbQueryContext *self); +void xb_query_context_set_limit (XbQueryContext *self, + guint limit); + +XbQueryFlags xb_query_context_get_flags (XbQueryContext *self); +void xb_query_context_set_flags (XbQueryContext *self, + XbQueryFlags flags); + +G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-query.h libxmlb-0.3.6/src/xb-query.h --- libxmlb-0.1.15/src/xb-query.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-query.h 2021-12-06 20:36:37.000000000 +0000 @@ -8,8 +8,6 @@ #include -#include "xb-silo.h" - G_BEGIN_DECLS #define XB_TYPE_QUERY (xb_query_get_type ()) @@ -33,18 +31,22 @@ * @XB_QUERY_FLAG_OPTIMIZE: Optimize the query when possible * @XB_QUERY_FLAG_USE_INDEXES: Use the indexed parameters * @XB_QUERY_FLAG_REVERSE: Reverse the results order + * @XB_QUERY_FLAG_FORCE_NODE_CACHE: Always cache the #XbNode objects * - * The flags used fo query. + * The flags used for queries. **/ typedef enum { XB_QUERY_FLAG_NONE = 0, /* Since: 0.1.6 */ XB_QUERY_FLAG_OPTIMIZE = 1 << 0, /* Since: 0.1.6 */ XB_QUERY_FLAG_USE_INDEXES = 1 << 1, /* Since: 0.1.6 */ XB_QUERY_FLAG_REVERSE = 1 << 2, /* Since: 0.1.15 */ + XB_QUERY_FLAG_FORCE_NODE_CACHE = 1 << 3, /* Since: 0.2.0 */ /*< private >*/ XB_QUERY_FLAG_LAST } XbQueryFlags; +#include "xb-silo.h" + XbQuery *xb_query_new (XbSilo *silo, const gchar *xpath, GError **error); @@ -53,16 +55,25 @@ XbQueryFlags flags, GError **error); const gchar *xb_query_get_xpath (XbQuery *self); + +G_DEPRECATED_FOR(xb_query_context_get_limit) guint xb_query_get_limit (XbQuery *self); +G_DEPRECATED_FOR(xb_query_context_set_limit) void xb_query_set_limit (XbQuery *self, guint limit); + +G_DEPRECATED_FOR(xb_query_context_get_flags) XbQueryFlags xb_query_get_flags (XbQuery *self); +G_DEPRECATED_FOR(xb_query_context_set_flags) void xb_query_set_flags (XbQuery *self, XbQueryFlags flags); + +G_DEPRECATED_FOR(xb_value_bindings_bind_str) gboolean xb_query_bind_str (XbQuery *self, guint idx, const gchar *str, GError **error); +G_DEPRECATED_FOR(xb_value_bindings_bind_val) gboolean xb_query_bind_val (XbQuery *self, guint idx, guint32 val, diff -Nru libxmlb-0.1.15/src/xb-self-test.c libxmlb-0.3.6/src/xb-self-test.c --- libxmlb-0.1.15/src/xb-self-test.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-self-test.c 2021-12-06 20:36:37.000000000 +0000 @@ -7,12 +7,14 @@ #include "config.h" #include +#include #include "xb-builder.h" #include "xb-builder-node.h" #include "xb-machine.h" #include "xb-node-query.h" #include "xb-opcode.h" +#include "xb-opcode-private.h" #include "xb-silo-export.h" #include "xb-silo-private.h" #include "xb-silo-query-private.h" @@ -76,31 +78,38 @@ static void xb_stack_func (void) { - XbOpcode *op; - g_autoptr(XbOpcode) op1 = xb_opcode_func_new (0); - g_autoptr(XbOpcode) op2 = xb_opcode_integer_new (1); - g_autoptr(XbOpcode) op3 = xb_opcode_text_new ("dave"); + XbOpcode *op1, *op2, *op3, *op4; + g_auto(XbOpcode) op1_popped = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2_popped = XB_OPCODE_INIT (); + g_auto(XbOpcode) op3_popped = XB_OPCODE_INIT (); g_autoptr(XbStack) stack = xb_stack_new (3); /* push three opcodes */ - g_assert_true (xb_stack_push (stack, op3)); - g_assert_true (xb_stack_push (stack, op2)); - g_assert_true (xb_stack_push (stack, op1)); - g_assert_false (xb_stack_push (stack, op3)); + g_assert_true (xb_stack_push (stack, &op3, NULL)); + xb_opcode_text_init (op3, "dave"); + g_assert_true (xb_stack_push (stack, &op2, NULL)); + xb_opcode_integer_init (op2, 1); + g_assert_true (xb_stack_push (stack, &op1, NULL)); + xb_opcode_func_init (op1, 0); + g_assert_false (xb_stack_push (stack, &op4, NULL)); + g_assert_null (op4); /* pop the same opcodes */ - op = xb_stack_pop (stack); - g_assert (op == op1); - xb_opcode_unref (op); - op = xb_stack_pop (stack); - g_assert (op == op2); - xb_opcode_unref (op); - op = xb_stack_pop (stack); - g_assert (op == op3); - xb_opcode_unref (op); + g_assert_true (xb_stack_pop (stack, &op1_popped, NULL)); + g_assert_cmpint (xb_opcode_get_kind (&op1_popped), ==, XB_OPCODE_KIND_FUNCTION); + + g_assert_true (xb_stack_pop (stack, &op2_popped, NULL)); + g_assert_cmpint (xb_opcode_get_kind (&op2_popped), ==, XB_OPCODE_KIND_INTEGER); + g_assert_cmpuint (xb_opcode_get_val (&op2_popped), ==, 1); + + g_assert_true (xb_stack_pop (stack, &op3_popped, NULL)); + g_assert_cmpint (xb_opcode_get_kind (&op3_popped), ==, XB_OPCODE_KIND_TEXT); + g_assert_cmpstr (xb_opcode_get_str (&op3_popped), ==, "dave"); /* re-add one opcode */ - g_assert_true (xb_stack_push (stack, op3)); + g_assert_true (xb_stack_push (stack, &op4, NULL)); + xb_opcode_text_init (op4, "dave again"); + g_assert_nonnull (op4); /* finish, cleaning up the stack properly... */ } @@ -108,22 +117,23 @@ static void xb_stack_peek_func (void) { - g_autoptr(XbOpcode) op1 = xb_opcode_func_new (0); - g_autoptr(XbOpcode) op2 = xb_opcode_integer_new (1); - g_autoptr(XbOpcode) op3 = xb_opcode_text_new ("dave"); + XbOpcode *op1, *op2, *op3; g_autoptr(XbStack) stack = xb_stack_new (3); /* push three opcodes */ - g_assert_true (xb_stack_push (stack, op1)); - g_assert_true (xb_stack_push (stack, op2)); - g_assert_true (xb_stack_push (stack, op3)); - - /* pop the same opcodes */ - g_assert (xb_stack_peek_head (stack) == op1); - g_assert (xb_stack_peek_tail (stack) == op3); - g_assert (xb_stack_peek (stack, 0) == op1); - g_assert (xb_stack_peek (stack, 1) == op2); - g_assert (xb_stack_peek (stack, 2) == op3); + g_assert_true (xb_stack_push (stack, &op1, NULL)); + xb_opcode_func_init (op1, 0); + g_assert_true (xb_stack_push (stack, &op2, NULL)); + xb_opcode_integer_init (op2, 1); + g_assert_true (xb_stack_push (stack, &op3, NULL)); + xb_opcode_text_init (op3, "dave"); + + /* peek the same opcodes */ + g_assert_true (xb_stack_peek_head (stack) == op1); + g_assert_true (xb_stack_peek_tail (stack) == op3); + g_assert_true (xb_stack_peek (stack, 0) == op1); + g_assert_true (xb_stack_peek (stack, 1) == op2); + g_assert_true (xb_stack_peek (stack, 2) == op3); } static void @@ -146,19 +156,40 @@ g_assert_false (xb_string_search ("gimp", "")); g_assert_false (xb_string_search ("gimp", "imp")); g_assert_false (xb_string_search ("the gimp editor", "imp")); + g_assert_true (xb_string_token_valid ("the")); + g_assert_false (xb_string_token_valid (NULL)); + g_assert_false (xb_string_token_valid ("")); + g_assert_false (xb_string_token_valid ("a")); + g_assert_false (xb_string_token_valid ("ab")); +} + +static void +xb_common_searchv_func (void) +{ + const gchar *haystack[] = { "these", "words", "ready", NULL }; + const gchar *found[] = { "xxx", "wor", "yyy", NULL }; + const gchar *unfound1[] = { "xxx", "yyy", NULL }; + const gchar *unfound2[] = { "ords", NULL }; + g_assert_true (xb_string_searchv (haystack, found)); + g_assert_false (xb_string_searchv (haystack, unfound1)); + g_assert_false (xb_string_searchv (haystack, unfound2)); } static void xb_opcodes_kind_func (void) { - g_autoptr(XbOpcode) op1 = xb_opcode_func_new (0); - g_autoptr(XbOpcode) op2 = xb_opcode_integer_new (1); - g_autoptr(XbOpcode) op3 = xb_opcode_text_new ("dave"); + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op3 = XB_OPCODE_INIT (); + + xb_opcode_func_init (&op1, 0); + xb_opcode_integer_init (&op2, 1); + xb_opcode_text_init (&op3, "dave"); /* check kind */ - g_assert_cmpint (xb_opcode_get_kind (op1), ==, XB_OPCODE_KIND_FUNCTION); - g_assert_cmpint (xb_opcode_get_kind (op2), ==, XB_OPCODE_KIND_INTEGER); - g_assert_cmpint (xb_opcode_get_kind (op3), ==, XB_OPCODE_KIND_TEXT); + g_assert_cmpint (xb_opcode_get_kind (&op1), ==, XB_OPCODE_KIND_FUNCTION); + g_assert_cmpint (xb_opcode_get_kind (&op2), ==, XB_OPCODE_KIND_INTEGER); + g_assert_cmpint (xb_opcode_get_kind (&op3), ==, XB_OPCODE_KIND_TEXT); /* to and from string */ g_assert_cmpint (xb_opcode_kind_from_string ("TEXT"), ==, XB_OPCODE_KIND_TEXT); @@ -171,14 +202,14 @@ g_assert_cmpstr (xb_opcode_kind_to_string (XB_OPCODE_KIND_UNKNOWN), ==, NULL); /* integer compare */ - g_assert_false (xb_opcode_cmp_val (op1)); - g_assert_true (xb_opcode_cmp_val (op2)); - g_assert_false (xb_opcode_cmp_val (op3)); + g_assert_false (xb_opcode_cmp_val (&op1)); + g_assert_true (xb_opcode_cmp_val (&op2)); + g_assert_false (xb_opcode_cmp_val (&op3)); /* string compare */ - g_assert_false (xb_opcode_cmp_str (op1)); - g_assert_false (xb_opcode_cmp_str (op2)); - g_assert_true (xb_opcode_cmp_str (op3)); + g_assert_false (xb_opcode_cmp_str (&op1)); + g_assert_false (xb_opcode_cmp_str (&op2)); + g_assert_true (xb_opcode_cmp_str (&op3)); } static void @@ -216,7 +247,7 @@ { "last()", "last()" }, { "text()~='beef'", - "text(),'beef',search()" }, + "text(),'beef'[beef],search()" }, { "@type~='dead'", "'type',attr(),'dead',search()" }, { "2", @@ -280,6 +311,7 @@ { "999>=123", "True" }, /* success! */ { "not(0)", "True" }, /* success! */ { "lower-case('Fire')", "'fire'" }, + { "upper-case('Τάχιστη')", "'ΤΆΧΙΣΤΗ'" }, { "upper-case(lower-case('Fire'))", "'FIRE'" }, /* 2nd pass */ /* sentinel */ @@ -337,7 +369,7 @@ " \n" " \n" " gimp.desktop\n" - " GIMP & Friends\n" + " GIMP & Friendẞ\n" " org.gnome.Gimp.desktop\n" " \n" " \n" @@ -373,7 +405,7 @@ /* check size */ bytes = xb_silo_get_bytes (silo); - g_assert_cmpint (g_bytes_get_size (bytes), ==, 605); + g_assert_cmpint (g_bytes_get_size (bytes), ==, 620); } static void @@ -395,7 +427,7 @@ "" "%s", xb_builder_source_ctx_get_filename (ctx)); - return g_memory_input_stream_new_from_data (xml, -1, g_free); + return g_memory_input_stream_new_from_data (g_steal_pointer (&xml), -1, g_free); } static void @@ -446,6 +478,40 @@ } static void +xb_builder_source_lzma_func (void) +{ + gboolean ret; + g_autofree gchar *path = NULL; + g_autofree gchar *tmp_xmlb = g_build_filename (g_get_tmp_dir (), "temp.xmlb", NULL); + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = NULL; + g_autoptr(GFile) file_src = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbBuilderSource) source = xb_builder_source_new (); + g_autoptr(XbSilo) silo = NULL; + + /* import a source file */ + path = g_test_build_filename (G_TEST_DIST, "test.xml.xz", NULL); + file_src = g_file_new_for_path (path); + if (!g_file_query_exists (file_src, NULL)) { + g_test_skip ("does not work in subproject test"); + return; + } + ret = xb_builder_source_load_file (source, file_src, + XB_BUILDER_SOURCE_FLAG_NONE, + NULL, &error); + g_assert_no_error (error); + g_assert_true (ret); + xb_builder_import_source (builder, source); + file = g_file_new_for_path (tmp_xmlb); + silo = xb_builder_ensure (builder, file, + XB_BUILDER_COMPILE_FLAG_NONE, + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (silo); +} + +static void xb_builder_chained_adapters_func (void) { gboolean ret; @@ -460,12 +526,12 @@ g_autoptr(XbSilo) silo = NULL; /* import a source file */ - path = g_build_filename (TESTDIR, "test.xml.gz.gz.gz", NULL); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) { - g_free (path); - path = g_build_filename (INSTALLEDTESTDIR, "test.xml.gz.gz.gz", NULL); - } + path = g_test_build_filename (G_TEST_DIST, "test.xml.gz.gz.gz", NULL); file_src = g_file_new_for_path (path); + if (!g_file_query_exists (file_src, NULL)) { + g_test_skip ("does not work in subproject test"); + return; + } ret = xb_builder_source_load_file (source, file_src, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); @@ -565,7 +631,7 @@ " \n" " \n" " gimp.desktop\n" - " GIMP & Friends\n" + " GIMP & Friendẞ\n" " org.gnome.Gimp.desktop\n" " \n" " \n" @@ -1005,6 +1071,38 @@ } static void +xb_node_export_func (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(XbNode) n = NULL; + g_autoptr(XbSilo) silo = NULL; + g_autofree gchar *xml_default = NULL; + g_autofree gchar *xml_collapsed = NULL; + + /* import from XML */ + silo = xb_silo_new_from_xml ("", &error); + g_assert_no_error (error); + g_assert_nonnull (silo); + + /* get node */ + n = xb_silo_query_first (silo, "component", &error); + g_assert_no_error (error); + g_assert_nonnull (n); + + /* export default */ + xml_default = xb_node_export (n, XB_NODE_EXPORT_FLAG_NONE, &error); + g_assert_no_error (error); + g_assert_nonnull (xml_default); + g_assert_cmpstr (xml_default, ==, ""); + + /* export collapsed */ + xml_collapsed = xb_node_export (n, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); + g_assert_no_error (error); + g_assert_nonnull (xml_collapsed); + g_assert_cmpstr (xml_collapsed, ==, ""); +} + +static void xb_xpath_parent_subnode_func (void) { g_autofree gchar *xml2 = NULL; @@ -1027,6 +1125,7 @@ /* import from XML */ silo = xb_silo_new_from_xml (xml, &error); + xb_silo_set_enable_node_cache (silo, TRUE); g_assert_no_error (error); g_assert_nonnull (silo); @@ -1138,7 +1237,7 @@ /* query with an OR, all sections contains an unknown element */ n = xb_silo_query_first (silo, "components/dave|components/mike", &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (n); } @@ -1166,11 +1265,34 @@ g_assert_null (n); } +static gboolean +xb_builder_fixup_tokenize_cb (XbBuilderFixup *self, + XbBuilderNode *bn, + gpointer user_data, + GError **error) +{ + if (g_strcmp0 (xb_builder_node_get_element (bn), "name") == 0) + xb_builder_node_tokenize_text (bn); + return TRUE; +} + +static gboolean +xb_builder_fixup_strip_inner_cb (XbBuilderFixup *self, + XbBuilderNode *bn, + gpointer user_data, + GError **error) +{ + if (xb_builder_node_get_first_child (bn) == NULL) + xb_builder_node_add_flag (bn, XB_BUILDER_NODE_FLAG_STRIP_TEXT); + return TRUE; +} + static void xb_xpath_func (void) { XbNode *n; XbNode *n2; + gboolean ret; g_autofree gchar *str = NULL; g_autofree gchar *xml_sub1 = NULL; g_autofree gchar *xml_sub2 = NULL; @@ -1178,6 +1300,10 @@ g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(XbNode) n3 = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbBuilderFixup) fixup = NULL; + g_autoptr(XbBuilderFixup) fixup2 = NULL; + g_autoptr(XbBuilderSource) source = xb_builder_source_new (); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" @@ -1185,8 +1311,9 @@ " dead\n" " \n" " \n" - " gimp.desktop\n" + " gimp.desktop \n" " org.gnome.Gimp.desktop\n" + " Mêẞ\n" " \n" " TRUE\n" " \n" @@ -1196,8 +1323,20 @@ " \n" "\n"; + /* tokenize specific fields */ + fixup = xb_builder_fixup_new ("TextTokenize", xb_builder_fixup_tokenize_cb, NULL, NULL); + xb_builder_source_add_fixup (source, fixup); + + /* strip inner nodes without children */ + fixup2 = xb_builder_fixup_new ("TextStripInner", xb_builder_fixup_strip_inner_cb, NULL, NULL); + xb_builder_source_add_fixup (source, fixup2); + /* import from XML */ - silo = xb_silo_new_from_xml (xml, &error); + ret = xb_builder_source_load_xml (source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); + g_assert_no_error (error); + g_assert_true (ret); + xb_builder_import_source (builder, source); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error (error); g_assert_nonnull (silo); @@ -1234,19 +1373,19 @@ /* query that doesn't find anything */ n = xb_silo_query_first (silo, "dave", &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (n); g_clear_error (&error); g_clear_object (&n); n = xb_silo_query_first (silo, "dave/dave", &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (n); g_clear_error (&error); g_clear_object (&n); n = xb_silo_query_first (silo, "components/dave", &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null (n); g_clear_error (&error); g_clear_object (&n); @@ -1339,6 +1478,20 @@ g_assert_cmpstr (xb_node_get_text (n), ==, "gimp.desktop"); g_clear_object (&n); + /* query no normalize */ + n = xb_silo_query_first (silo, "components/component/name", &error); + g_assert_no_error (error); + g_assert_nonnull (n); + g_assert_cmpstr (xb_node_get_text (n), ==, "Mêẞ"); + g_clear_object (&n); + + /* query name not UTF-8 */ + n = xb_silo_query_first (silo, "components/component/name[text()~='mEss']/../id", &error); + g_assert_no_error (error); + g_assert_nonnull (n); + g_assert_cmpstr (xb_node_get_text (n), ==, "gimp.desktop"); + g_clear_object (&n); + /* query with stem */ #ifdef HAVE_LIBSTEMMER xb_machine_set_debug_flags (xb_silo_get_machine (silo), @@ -1503,6 +1656,35 @@ } static void +xb_builder_comments_func (void) +{ + gboolean ret; + g_autoptr(GError) error = NULL; + g_autofree gchar *str = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbSilo) silo = NULL; + const gchar *xml = + "\n" + "\n" + " \n" + " \n" + "\n"; + + /* import XML */ + ret = xb_test_import_xml (builder, xml, &error); + g_assert_no_error (error); + g_assert_true (ret); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (silo); + + /* export */ + str = xb_silo_export (silo, XB_NODE_EXPORT_FLAG_NONE, &error); + g_assert_no_error (error); + g_assert_cmpstr (str, ==, ""); +} + +static void xb_builder_native_lang2_func (void) { gboolean ret; @@ -1672,6 +1854,7 @@ g_autoptr(XbBuilder) builder = xb_builder_new (); g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; + g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT (); g_autoptr(XbNode) component = NULL; g_autoptr(GPtrArray) components = NULL; const gchar *xml = @@ -1704,10 +1887,8 @@ query = xb_query_new (silo, "id[text()=?]/..", &error); g_assert_no_error (error); g_assert_nonnull (query); - ret = xb_query_bind_str (query, 0, "gimp.desktop", &error); - g_assert_no_error (error); - g_assert_true (ret); - components = xb_node_query_full (component, query, &error); + xb_value_bindings_bind_str (xb_query_context_get_bindings (&context), 0, "gimp.desktop", NULL); + components = xb_node_query_with_context (component, query, &context, &error); g_assert_no_error (error); g_assert_nonnull (components); g_assert_cmpint (components->len, ==, 1); @@ -1744,7 +1925,7 @@ query = xb_query_new_full (silo, "names/name", XB_QUERY_FLAG_REVERSE, &error); g_assert_no_error (error); g_assert_nonnull (query); - names = xb_silo_query_full (silo, query, &error); + names = xb_silo_query_with_context (silo, query, NULL, &error); g_assert_no_error (error); g_assert_nonnull (names); g_assert_cmpint (names->len, ==, 3); @@ -1753,6 +1934,43 @@ } static void +xb_xpath_query_force_node_cache_func (void) +{ + gboolean ret; + g_autoptr(GError) error = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbNode) n1 = NULL; + g_autoptr(XbNode) n2 = NULL; + g_autoptr(XbQuery) query = NULL; + g_autoptr(XbSilo) silo = NULL; + const gchar *xml = + "\n" + " foo\n" + "\n"; + + /* import from XML */ + ret = xb_test_import_xml (builder, xml, &error); + g_assert_no_error (error); + g_assert_true (ret); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (silo); + + /* use a cache for this specific result */ + query = xb_query_new_full (silo, "names/name", + XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); + g_assert_no_error (error); + g_assert_nonnull (query); + n1 = xb_silo_query_first_with_context (silo, query, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (n1); + n2 = xb_silo_query_first_with_context (silo, query, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (n2); + g_assert (n1 == n2); +} + +static void xb_xpath_glob_func (void) { g_autofree gchar *xml2 = NULL; @@ -1826,6 +2044,54 @@ } static void +xb_builder_single_root_func (void) +{ + gboolean ret; + g_autoptr(GError) error = NULL; + g_autoptr(XbBuilder) builder = NULL; + g_autoptr(XbSilo) silo = NULL; + + /* import from XML */ + builder = xb_builder_new (); + ret = xb_test_import_xml (builder, "value2value3", &error); + g_assert_no_error (error); + g_assert_true (ret); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT, NULL, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); + g_assert_null (silo); +} + +static void +xb_builder_node_token_max_func (void) +{ + g_autofree gchar *xml = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(XbBuilder) builder = xb_builder_new (); + g_autoptr(XbBuilderNode) components = NULL; + g_autoptr(XbBuilderNode) root = xb_builder_node_new (NULL); + g_autoptr(XbSilo) silo = NULL; + + /* create a simple document */ + components = xb_builder_node_insert (root, "components", NULL); + for (guint i = 0; i < XB_OPCODE_TOKEN_MAX * 2; i++) { + g_autofree gchar *tmp = g_strdup_printf("foobarbaz%04u", i); + xb_builder_node_add_token (components, tmp); + } + + /* import the doc */ + xb_builder_import_node (builder, root); + silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (silo); + + /* check the XML */ + xml = xb_silo_export (silo, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, &error); + g_assert_no_error (error); + g_assert_nonnull (xml); + g_assert_cmpstr ("", ==, xml); +} + +static void xb_builder_node_func (void) { g_autofree gchar *xml = NULL; @@ -1837,6 +2103,9 @@ g_autoptr(XbBuilderNode) component = NULL; g_autoptr(XbBuilderNode) components = NULL; g_autoptr(XbBuilderNode) id = NULL; + g_autoptr(XbBuilderNode) description = xb_builder_node_new ("description"); + g_autoptr(XbBuilderNode) em = xb_builder_node_new ("em"); + g_autoptr(XbBuilderNode) empty = NULL; g_autoptr(XbBuilderNode) root = xb_builder_node_new (NULL); g_autoptr(XbSilo) silo = NULL; @@ -1852,11 +2121,25 @@ g_assert_cmpstr (xb_builder_node_get_attr (component, "type"), ==, "desktop"); g_assert_cmpstr (xb_builder_node_get_attr (component, "dave"), ==, NULL); id = xb_builder_node_new ("id"); + xb_builder_node_add_flag (id, XB_BUILDER_NODE_FLAG_TOKENIZE_TEXT); + xb_builder_node_add_token (id, "foobarbaz"); xb_builder_node_add_child (component, id); xb_builder_node_set_text (id, "gimp.desktop", -1); xb_builder_node_insert_text (component, "icon", "dave", "type", "stock", NULL); g_assert_cmpint (xb_builder_node_depth (id), ==, 3); + xb_builder_node_add_flag (em, XB_BUILDER_NODE_FLAG_LITERAL_TEXT); + xb_builder_node_set_text (em, "world!", -1); + xb_builder_node_set_tail (em, " ", -1); + xb_builder_node_add_child (description, em); + xb_builder_node_set_text (description, "hello ", -1); + xb_builder_node_add_child (component, description); + + /* no text contents */ + empty = xb_builder_node_new ("empty"); + xb_builder_node_set_text(empty, NULL, -1); + xb_builder_node_set_tail(empty, NULL, -1); + /* get specific child */ child_by_element = xb_builder_node_get_child (components, "component", NULL); g_assert_nonnull (child_by_element); @@ -1876,9 +2159,10 @@ "\n" "gimp.desktop\n" "dave\n" + "hello world! \n" + "\n" "\n" "\n", ==, xml_src); - /* import the doc */ xb_builder_import_node (builder, root); silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); @@ -1894,6 +2178,8 @@ "" "gimp.desktop" "dave" + "hello world!" + "" "" "", ==, xml); } @@ -2375,8 +2661,11 @@ g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_setenv ("G_MESSAGES_DEBUG", "all", TRUE); + setlocale (LC_ALL, ""); + /* tests go here */ g_test_add_func ("/libxmlb/common", xb_common_func); + g_test_add_func ("/libxmlb/common{searchv}", xb_common_searchv_func); g_test_add_func ("/libxmlb/common{union}", xb_common_union_func); g_test_add_func ("/libxmlb/opcodes", xb_predicate_func); g_test_add_func ("/libxmlb/opcodes{optimize}", xb_predicate_optimize_func); @@ -2384,7 +2673,9 @@ g_test_add_func ("/libxmlb/stack", xb_stack_func); g_test_add_func ("/libxmlb/stack{peek}", xb_stack_peek_func); g_test_add_func ("/libxmlb/node{data}", xb_node_data_func); + g_test_add_func ("/libxmlb/node{export}", xb_node_export_func); g_test_add_func ("/libxmlb/builder", xb_builder_func); + g_test_add_func ("/libxmlb/builder{comments}", xb_builder_comments_func); g_test_add_func ("/libxmlb/builder{native-lang}", xb_builder_native_lang_func); g_test_add_func ("/libxmlb/builder{native-lang-nested}", xb_builder_native_lang2_func); g_test_add_func ("/libxmlb/builder{native-lang-locale}", xb_builder_native_lang_no_locales_func); @@ -2398,7 +2689,9 @@ g_test_add_func ("/libxmlb/builder{ignore-invalid}", xb_builder_ignore_invalid_func); g_test_add_func ("/libxmlb/builder{custom-mime}", xb_builder_custom_mime_func); g_test_add_func ("/libxmlb/builder{chained-adapters}", xb_builder_chained_adapters_func); + g_test_add_func ("/libxmlb/builder{source-lzma}", xb_builder_source_lzma_func); g_test_add_func ("/libxmlb/builder-node", xb_builder_node_func); + g_test_add_func ("/libxmlb/builder-node{token-max}", xb_builder_node_token_max_func); g_test_add_func ("/libxmlb/builder-node{info}", xb_builder_node_info_func); g_test_add_func ("/libxmlb/builder-node{literal-text}", xb_builder_node_literal_text_func); g_test_add_func ("/libxmlb/builder-node{source-text}", xb_builder_node_source_text_func); @@ -2406,6 +2699,7 @@ g_test_add_func ("/libxmlb/xpath", xb_xpath_func); g_test_add_func ("/libxmlb/xpath-query", xb_xpath_query_func); g_test_add_func ("/libxmlb/xpath-query{reverse}", xb_xpath_query_reverse_func); + g_test_add_func ("/libxmlb/xpath-query{force-node-cache}", xb_xpath_query_force_node_cache_func); g_test_add_func ("/libxmlb/xpath{helpers}", xb_xpath_helpers_func); g_test_add_func ("/libxmlb/xpath{prepared}", xb_xpath_prepared_func); g_test_add_func ("/libxmlb/xpath{incomplete}", xb_xpath_incomplete_func); @@ -2414,6 +2708,7 @@ g_test_add_func ("/libxmlb/xpath-node", xb_xpath_node_func); g_test_add_func ("/libxmlb/xpath-parent-subnode", xb_xpath_parent_subnode_func); g_test_add_func ("/libxmlb/multiple-roots", xb_builder_multiple_roots_func); + g_test_add_func ("/libxmlb/single-root", xb_builder_single_root_func); if (g_test_perf ()) g_test_add_func ("/libxmlb/threading", xb_threading_func); if (g_test_perf ()) diff -Nru libxmlb-0.1.15/src/xb-silo.c libxmlb-0.3.6/src/xb-silo.c --- libxmlb-0.1.15/src/xb-silo.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo.c 2021-12-06 20:36:37.000000000 +0000 @@ -4,6 +4,24 @@ * SPDX-License-Identifier: LGPL-2.1+ */ +/** + * SECTION:xb-silo + * @title: XbSilo + * @include: xmlb.h + * @stability: Stable + * @short_description: A read-only store of parsed XML data + * + * #XbSilo provides read-only access and querying of a previously parsed blob + * of XML data. + * + * All signal emissions from #XbSilo (currently only #GObject::notify emissions) + * will happen in the #GMainContext which is the thread default when the #XbSilo + * is constructed. + * + * This #GMainContext must be iterated for file monitoring using + * xb_silo_watch_file() to function correctly. + */ + #define G_LOG_DOMAIN "XbSilo" #include "config.h" @@ -17,14 +35,15 @@ #endif #include "xb-builder.h" +#include "xb-common-private.h" +#include "xb-machine-private.h" #include "xb-node-private.h" #include "xb-opcode-private.h" -#include "xb-silo-private.h" +#include "xb-silo-node.h" #include "xb-stack-private.h" #include "xb-string-private.h" typedef struct { - GObject parent_instance; GMappedFile *mmap; gchar *guid; gboolean valid; @@ -34,12 +53,17 @@ guint32 strtab; GHashTable *strtab_tags; GHashTable *strindex; - GHashTable *nodes; + gboolean enable_node_cache; + GHashTable *nodes; /* (mutex nodes_mutex) */ GMutex nodes_mutex; - GHashTable *file_monitors; /* of fn:XbSiloFileMonitorItem */ + GHashTable *file_monitors; /* (element-type GFile XbSiloFileMonitorItem) (mutex file_monitors_mutex) */ + GMutex file_monitors_mutex; XbMachine *machine; XbSiloProfileFlags profile_flags; GString *profile_str; + GRWLock query_cache_mutex; + GHashTable *query_cache; + GMainContext *context; /* (owned) */ #ifdef HAVE_LIBSTEMMER struct sb_stemmer *stemmer_ctx; /* lazy loaded */ GMutex stemmer_mutex; @@ -54,12 +78,27 @@ G_DEFINE_TYPE_WITH_PRIVATE (XbSilo, xb_silo, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_silo_get_instance_private (o)) -enum { - PROP_0, - PROP_GUID, +typedef enum { + PROP_GUID = 1, PROP_VALID, - PROP_LAST -}; + PROP_ENABLE_NODE_CACHE, +} XbSiloProperty; + +static GParamSpec *obj_props[PROP_ENABLE_NODE_CACHE + 1] = { NULL, }; + +/* private */ +GTimer * +xb_silo_start_profile (XbSilo *self) +{ + XbSiloPrivate *priv = GET_PRIVATE (self); + + /* nothing to do; g_timer_new() does a syscall to clock_gettime() which + * is best avoided if not needed */ + if (!priv->profile_flags) + return NULL; + + return g_timer_new (); +} /* private */ void @@ -67,12 +106,14 @@ { XbSiloPrivate *priv = GET_PRIVATE (self); va_list args; - g_autoptr(GString) str = g_string_new (NULL); + g_autoptr(GString) str = NULL; /* nothing to do */ if (!priv->profile_flags) return; + str = g_string_new (""); + /* add duration */ if (timer != NULL) { g_string_append_printf (str, "%.2fms", @@ -180,29 +221,6 @@ } /* private */ -XbSiloAttr * -xb_silo_get_attr (XbSilo *self, guint32 off, guint8 idx) -{ - XbSiloPrivate *priv = GET_PRIVATE (self); - off += sizeof(XbSiloNode); - off += sizeof(XbSiloAttr) * idx; - return (XbSiloAttr *) (priv->data + off); -} - -/* private */ -guint8 -xb_silo_node_get_size (XbSiloNode *n) -{ - if (n->is_node) { - guint8 sz = sizeof(XbSiloNode); - sz += n->nr_attrs * sizeof(XbSiloAttr); - return sz; - } - /* sentinel */ - return 1; -} - -/* private */ guint32 xb_silo_get_offset_for_node (XbSilo *self, XbSiloNode *n) { @@ -220,7 +238,7 @@ /* private */ XbSiloNode * -xb_silo_get_sroot (XbSilo *self) +xb_silo_get_root_node (XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE (self); if (priv->blob == NULL) @@ -232,7 +250,7 @@ /* private */ XbSiloNode * -xb_silo_node_get_parent (XbSilo *self, XbSiloNode *n) +xb_silo_get_parent_node (XbSilo *self, XbSiloNode *n) { if (n->parent == 0x0) return NULL; @@ -241,7 +259,7 @@ /* private */ XbSiloNode * -xb_silo_node_get_next (XbSilo *self, XbSiloNode *n) +xb_silo_get_next_node (XbSilo *self, XbSiloNode *n) { if (n->next == 0x0) return NULL; @@ -250,7 +268,7 @@ /* private */ XbSiloNode * -xb_silo_node_get_child (XbSilo *self, XbSiloNode *n) +xb_silo_get_child_node (XbSilo *self, XbSiloNode *n) { XbSiloNode *c; guint32 off = xb_silo_get_offset_for_node (self, n); @@ -258,7 +276,7 @@ /* check for sentinel */ c = xb_silo_get_node (self, off); - if (!c->is_node) + if (!xb_silo_node_has_flag (c, XB_SILO_NODE_FLAG_IS_ELEMENT)) return NULL; return c; } @@ -277,7 +295,7 @@ xb_silo_get_root (XbSilo *self) { g_return_val_if_fail (XB_IS_SILO (self), NULL); - return xb_silo_node_create (self, xb_silo_get_sroot (self)); + return xb_silo_create_node (self, xb_silo_get_root_node (self), FALSE); } /* private */ @@ -320,23 +338,28 @@ g_string_append_printf (str, "strtab_ntags: %" G_GUINT16_FORMAT "\n", hdr->strtab_ntags); while (off < priv->strtab) { XbSiloNode *n = xb_silo_get_node (self, off); - if (n->is_node) { + if (xb_silo_node_has_flag (n, XB_SILO_NODE_FLAG_IS_ELEMENT)) { + guint32 idx; g_string_append_printf (str, "NODE @%" G_GUINT32_FORMAT "\n", off); + g_string_append_printf (str, "size: %" G_GUINT32_FORMAT "\n", xb_silo_node_get_size (n)); + g_string_append_printf (str, "flags: %x\n", xb_silo_node_get_flags (n)); g_string_append_printf (str, "element_name: %s [%03u]\n", xb_silo_from_strtab (self, n->element_name), n->element_name); g_string_append_printf (str, "next: %" G_GUINT32_FORMAT "\n", n->next); g_string_append_printf (str, "parent: %" G_GUINT32_FORMAT "\n", n->parent); - if (n->text != XB_SILO_UNSET) { - g_string_append_printf (str, "text: %s\n", - xb_silo_from_strtab (self, n->text)); + idx = xb_silo_node_get_text_idx (n); + if (idx != XB_SILO_UNSET) { + g_string_append_printf (str, "text: %s [%03u]\n", + xb_silo_from_strtab (self, idx), idx); } - if (n->tail != XB_SILO_UNSET) { - g_string_append_printf (str, "tail: %s\n", - xb_silo_from_strtab (self, n->tail)); + idx = xb_silo_node_get_tail_idx (n); + if (idx != XB_SILO_UNSET) { + g_string_append_printf (str, "tail: %s [%03u]\n", + xb_silo_from_strtab (self, idx), idx); } - for (guint8 i = 0; i < n->nr_attrs; i++) { - XbSiloAttr *a = xb_silo_get_attr (self, off, i); + for (guint8 i = 0; i < xb_silo_node_get_attr_count (n); i++) { + XbSiloNodeAttr *a = xb_silo_node_get_attr (n, i); g_string_append_printf (str, "attr_name: %s [%03u]\n", xb_silo_from_strtab (self, a->attr_name), a->attr_name); @@ -344,6 +367,12 @@ xb_silo_from_strtab (self, a->attr_value), a->attr_value); } + for (guint8 i = 0; i < xb_silo_node_get_token_count (n); i++) { + guint32 idx_tmp = xb_silo_node_get_token_idx (n, i); + g_string_append_printf (str, "token: %s [%03u]\n", + xb_silo_from_strtab (self, idx_tmp), + idx_tmp); + } } else { g_string_append_printf (str, "SENT @%" G_GUINT32_FORMAT "\n", off); } @@ -366,39 +395,41 @@ /* private */ const gchar * -xb_silo_node_get_text (XbSilo *self, XbSiloNode *n) +xb_silo_get_node_text (XbSilo *self, XbSiloNode *n) { - if (n->text == XB_SILO_UNSET) + guint32 idx = xb_silo_node_get_text_idx (n); + if (idx == XB_SILO_UNSET) return NULL; - return xb_silo_from_strtab (self, n->text); + return xb_silo_from_strtab (self, idx); } /* private */ const gchar * -xb_silo_node_get_tail (XbSilo *self, XbSiloNode *n) +xb_silo_get_node_tail (XbSilo *self, XbSiloNode *n) { - if (n->tail == XB_SILO_UNSET) + guint idx = xb_silo_node_get_tail_idx (n); + if (idx == XB_SILO_UNSET) return NULL; - return xb_silo_from_strtab (self, n->tail); + return xb_silo_from_strtab (self, idx); } /* private */ const gchar * -xb_silo_node_get_element (XbSilo *self, XbSiloNode *n) +xb_silo_get_node_element (XbSilo *self, XbSiloNode *n) { return xb_silo_from_strtab (self, n->element_name); } /* private */ -XbSiloAttr * -xb_silo_node_get_attr_by_str (XbSilo *self, XbSiloNode *n, const gchar *name) +XbSiloNodeAttr * +xb_silo_get_node_attr_by_str (XbSilo *self, XbSiloNode *n, const gchar *name) { - guint32 off; + guint8 attr_count; /* calculate offset to first attribute */ - off = xb_silo_get_offset_for_node (self, n); - for (guint8 i = 0; i < n->nr_attrs; i++) { - XbSiloAttr *a = xb_silo_get_attr (self, off, i); + attr_count = xb_silo_node_get_attr_count (n); + for (guint8 i = 0; i < attr_count; i++) { + XbSiloNodeAttr *a = xb_silo_node_get_attr (n, i); if (g_strcmp0 (xb_silo_from_strtab (self, a->attr_name), name) == 0) return a; } @@ -407,15 +438,15 @@ return NULL; } -static XbSiloAttr * +static XbSiloNodeAttr * xb_silo_node_get_attr_by_val (XbSilo *self, XbSiloNode *n, guint32 name) { - guint32 off; + guint8 attr_count; /* calculate offset to first attribute */ - off = xb_silo_get_offset_for_node (self, n); - for (guint8 i = 0; i < n->nr_attrs; i++) { - XbSiloAttr *a = xb_silo_get_attr (self, off, i); + attr_count = xb_silo_node_get_attr_count (n); + for (guint8 i = 0; i < attr_count; i++) { + XbSiloNodeAttr *a = xb_silo_node_get_attr (n, i); if (a->attr_name == name) return a; } @@ -445,7 +476,7 @@ while (off < priv->strtab) { XbSiloNode *n = xb_silo_get_node (self, off); - if (n->is_node) + if (xb_silo_node_has_flag (n, XB_SILO_NODE_FLAG_IS_ELEMENT)) nodes_cnt += 1; off += xb_silo_node_get_size (n); } @@ -483,6 +514,45 @@ return priv->strtab == sizeof(XbSiloHeader); } +typedef struct { + XbSilo *silo; /* (owned) */ + GParamSpec *pspec; /* (owned) */ +} SiloNotifyData; + +static void +silo_notify_data_free (SiloNotifyData *data) +{ + g_clear_object (&data->silo); + g_clear_pointer (&data->pspec, g_param_spec_unref); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SiloNotifyData, silo_notify_data_free) + +static gboolean +silo_notify_cb (gpointer user_data) +{ + g_autoptr(SiloNotifyData) data = g_steal_pointer (&user_data); + + g_object_notify_by_pspec (G_OBJECT (data->silo), data->pspec); + + return G_SOURCE_REMOVE; +} + +/* Like g_object_notify(), but ensure that the signal is emitted in XbSilo.context. */ +static void +silo_notify (XbSilo *self, GParamSpec *pspec) +{ + XbSiloPrivate *priv = GET_PRIVATE (self); + g_autoptr(SiloNotifyData) data = NULL; + + data = g_new0 (SiloNotifyData, 1); + data->silo = g_object_ref (self); + data->pspec = g_param_spec_ref (pspec); + + g_main_context_invoke (priv->context, silo_notify_cb, g_steal_pointer (&data)); +} + /** * xb_silo_invalidate: * @self: a #XbSilo @@ -498,7 +568,7 @@ if (!priv->valid) return; priv->valid = FALSE; - g_object_notify (G_OBJECT (self), "valid"); + silo_notify (self, obj_props[PROP_VALID]); } /* private */ @@ -509,12 +579,12 @@ if (priv->valid) return; priv->valid = TRUE; - g_object_notify (G_OBJECT (self), "valid"); + silo_notify (self, obj_props[PROP_VALID]); } /* private */ guint -xb_silo_node_get_depth (XbSilo *self, XbSiloNode *n) +xb_silo_get_node_depth (XbSilo *self, XbSiloNode *n) { guint depth = 0; while (n->parent != 0) { @@ -594,16 +664,20 @@ XbSiloPrivate *priv = GET_PRIVATE (self); gsize sz = 0; guint32 off = 0; - g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->nodes_mutex); - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GMutexLocker) locker = NULL; + g_autoptr(GTimer) timer = xb_silo_start_profile (self); g_return_val_if_fail (XB_IS_SILO (self), FALSE); g_return_val_if_fail (blob != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - g_return_val_if_fail (locker != NULL, FALSE); /* no longer valid */ - g_hash_table_remove_all (priv->nodes); + if (priv->enable_node_cache) { + locker = g_mutex_locker_new (&priv->nodes_mutex); + if (priv->nodes != NULL) + g_hash_table_remove_all (priv->nodes); + } + g_hash_table_remove_all (priv->strtab_tags); g_clear_pointer (&priv->guid, g_free); @@ -636,10 +710,10 @@ return FALSE; } if (hdr->version != XB_SILO_VERSION) { - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "version incorrect"); + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "version incorrect, got %u, expected %d", hdr->version, XB_SILO_VERSION); return FALSE; } } @@ -716,6 +790,62 @@ XbSiloPrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_SILO (self)); priv->profile_flags = profile_flags; + + /* proxy */ + if (profile_flags & XB_SILO_PROFILE_FLAG_OPTIMIZER) { + xb_machine_set_debug_flags (priv->machine, + XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER); + } +} + +/** + * xb_silo_get_enable_node_cache: + * @self: an #XbSilo + * + * Get #XbSilo:enable-node-cache. + * + * Since: 0.2.0 + */ +gboolean +xb_silo_get_enable_node_cache (XbSilo *self) +{ + XbSiloPrivate *priv = GET_PRIVATE (self); + g_return_val_if_fail (XB_IS_SILO (self), FALSE); + return priv->enable_node_cache; +} + +/** + * xb_silo_set_enable_node_cache: + * @self: an #XbSilo + * @enable_node_cache: %TRUE to enable the node cache, %FALSE otherwise + * + * Set #XbSilo:enable-node-cache. + * + * This is not thread-safe, and can only be called before the #XbSilo is passed + * between threads. + * + * Since: 0.2.0 + */ +void +xb_silo_set_enable_node_cache (XbSilo *self, gboolean enable_node_cache) +{ + XbSiloPrivate *priv = GET_PRIVATE (self); + + g_return_if_fail (XB_IS_SILO (self)); + + if (priv->enable_node_cache == enable_node_cache) + return; + + priv->enable_node_cache = enable_node_cache; + + /* if disabling the cache, destroy any existing data structures; + * if enabling it, create them lazily when the first entry is cached + * (see xb_silo_create_node()) */ + if (!enable_node_cache) { + g_clear_pointer (&priv->nodes, g_hash_table_unref); + } + + silo_notify (self, obj_props[PROP_ENABLE_NODE_CACHE]); } /* private */ @@ -726,6 +856,7 @@ return priv->profile_flags; } +/* This will be invoked in silo->context */ static void xb_silo_watch_file_cb (GFileMonitor *monitor, GFile *file, @@ -742,6 +873,23 @@ xb_silo_invalidate (silo); } +typedef struct { + XbSilo *silo; /* (owned) */ + GFile *file; /* (owned) */ +} WatchFileData; + +static void +watch_file_data_free (WatchFileData *data) +{ + g_clear_object (&data->silo); + g_clear_object (&data->file); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (WatchFileData, watch_file_data_free) + +static gboolean watch_file_cb (gpointer user_data); + /** * xb_silo_watch_file: * @self: a #XbSilo @@ -752,6 +900,10 @@ * Adds a file monitor to the silo. If the file or directory for @file changes * then the silo will be invalidated. * + * The monitor will internally use the #GMainContext which was the thread + * default when the #XbSilo was created, so that #GMainContext must be iterated + * for monitoring to work. + * * Returns: %TRUE for success, otherwise @error is set. * * Since: 0.1.0 @@ -762,25 +914,53 @@ GCancellable *cancellable, GError **error) { - XbSiloFileMonitorItem *item; XbSiloPrivate *priv = GET_PRIVATE (self); - g_autofree gchar *fn = g_file_get_path (file); - g_autoptr(GFileMonitor) file_monitor = NULL; + g_autoptr(WatchFileData) data = NULL; g_return_val_if_fail (XB_IS_SILO (self), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + /* return if cancelled; this is basically the only failure mode of + * g_file_monitor() for local files, and this function shouldn’t really + * be called on non-local files */ + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + data = g_new0 (WatchFileData, 1); + data->silo = g_object_ref (self); + data->file = g_object_ref (file); + + g_main_context_invoke (priv->context, watch_file_cb, g_steal_pointer (&data)); + + return TRUE; +} + +static gboolean +watch_file_cb (gpointer user_data) +{ + g_autoptr(WatchFileData) data = g_steal_pointer (&user_data); + XbSilo *self = data->silo; + GFile *file = data->file; + XbSiloFileMonitorItem *item; + XbSiloPrivate *priv = GET_PRIVATE (self); + g_autoptr(GFileMonitor) file_monitor = NULL; + g_autoptr(GError) error_local = NULL; + g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->file_monitors_mutex); + /* already exists */ - item = g_hash_table_lookup (priv->file_monitors, fn); + item = g_hash_table_lookup (priv->file_monitors, file); if (item != NULL) - return TRUE; + return G_SOURCE_REMOVE; /* try to create */ - file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, - cancellable, error); - if (file_monitor == NULL) - return FALSE; + file_monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, + NULL, &error_local); + if (file_monitor == NULL) { + g_warning ("Error adding file monitor: %s", error_local->message); + return G_SOURCE_REMOVE; + } + g_file_monitor_set_rate_limit (file_monitor, 20); /* add */ @@ -788,8 +968,9 @@ item->file_monitor = g_object_ref (file_monitor); item->file_monitor_id = g_signal_connect (file_monitor, "changed", G_CALLBACK (xb_silo_watch_file_cb), self); - g_hash_table_insert (priv->file_monitors, g_steal_pointer (&fn), item); - return TRUE; + g_hash_table_insert (priv->file_monitors, g_object_ref (file), item); + + return G_SOURCE_REMOVE; } /** @@ -816,16 +997,18 @@ XbSiloPrivate *priv = GET_PRIVATE (self); g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob = NULL; - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GTimer) timer = xb_silo_start_profile (self); + g_autoptr(GMutexLocker) file_monitors_locker = g_mutex_locker_new (&priv->file_monitors_mutex); g_return_val_if_fail (XB_IS_SILO (self), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - /* no longer valid */ + /* no longer valid (@nodes is cleared by xb_silo_load_from_bytes()) */ g_hash_table_remove_all (priv->file_monitors); - g_hash_table_remove_all (priv->nodes); + g_clear_pointer (&file_monitors_locker, g_mutex_locker_free); + g_hash_table_remove_all (priv->strtab_tags); g_clear_pointer (&priv->guid, g_free); if (priv->mmap != NULL) @@ -871,7 +1054,7 @@ { XbSiloPrivate *priv = GET_PRIVATE (self); g_autoptr(GFile) file_parent = NULL; - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GTimer) timer = xb_silo_start_profile (self); g_return_val_if_fail (XB_IS_SILO (self), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); @@ -898,13 +1081,10 @@ } /* save and then rename */ - if (!g_file_replace_contents (file, - (const gchar *) priv->data, - (gsize) priv->datasz, NULL, FALSE, - G_FILE_CREATE_NONE, NULL, - cancellable, error)) { + if (!xb_file_set_contents (file, priv->data, (gsize) priv->datasz, + cancellable, error)) return FALSE; - } + xb_silo_add_profile (self, timer, "save file"); return TRUE; } @@ -935,13 +1115,23 @@ /* private */ XbNode * -xb_silo_node_create (XbSilo *self, XbSiloNode *sn) +xb_silo_create_node (XbSilo *self, XbSiloNode *sn, gboolean force_node_cache) { XbNode *n; XbSiloPrivate *priv = GET_PRIVATE (self); - g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->nodes_mutex); + g_autoptr(GMutexLocker) locker = NULL; - g_return_val_if_fail (locker != NULL, NULL); + /* the cache should only be enabled/disabled before threads are + * spawned, so `priv->enable_node_cache` can be accessed unlocked */ + if (!priv->enable_node_cache && !force_node_cache) + return xb_node_new (self, sn); + + locker = g_mutex_locker_new (&priv->nodes_mutex); + + /* ensure the cache exists */ + if (priv->nodes == NULL) + priv->nodes = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) g_object_unref); /* does already exist */ n = g_hash_table_lookup (priv->nodes, sn); @@ -954,6 +1144,19 @@ return n; } +/* Push two opcodes onto the stack with appropriate rollback on failure. */ +static gboolean +_xb_stack_push_two (XbStack *opcodes, XbOpcode **op1, XbOpcode **op2, GError **error) +{ + if (!xb_stack_push (opcodes, op1, error)) + return FALSE; + if (!xb_stack_push (opcodes, op2, error)) { + xb_stack_pop (opcodes, NULL, NULL); + return FALSE; + } + return TRUE; +} + /* convert [2] to position()=2 */ static gboolean xb_silo_machine_fixup_position_cb (XbMachine *self, @@ -961,8 +1164,15 @@ gpointer user_data, GError **error) { - xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "position")); - xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "eq")); + XbOpcode *op1; + XbOpcode *op2; + + if (!_xb_stack_push_two (opcodes, &op1, &op2, error)) + return FALSE; + + xb_machine_opcode_func_init (self, op1, "position"); + xb_machine_opcode_func_init (self, op2, "eq"); + return TRUE; } @@ -973,8 +1183,56 @@ gpointer user_data, GError **error) { - xb_stack_push_steal (opcodes, xb_opcode_text_new_static (NULL)); - xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "ne")); + XbOpcode *op1; + XbOpcode *op2; + + if (!_xb_stack_push_two (opcodes, &op1, &op2, error)) + return FALSE; + + xb_opcode_text_init_static (op1, NULL); + xb_machine_opcode_func_init (self, op2, "ne"); + + return TRUE; +} + +static gboolean +xb_silo_machine_fixup_attr_search_token_cb (XbMachine *self, + XbStack *opcodes, + gpointer user_data, + GError **error) +{ + XbOpcode op_func; + XbOpcode op_text; + XbOpcode op_search; + XbOpcode *op_tmp; + + /* text() */ + if (!xb_machine_stack_pop (self, opcodes, &op_func, error)) + return FALSE; + + /* TEXT */ + if (!xb_machine_stack_pop (self, opcodes, &op_text, error)) + return FALSE; + xb_machine_opcode_tokenize (self, &op_text); + + /* search() */ + if (!xb_machine_stack_pop (self, opcodes, &op_search, error)) + return FALSE; + + /* text() */ + if (!xb_machine_stack_push (self, opcodes, &op_tmp, error)) + return FALSE; + *op_tmp = op_search; + + /* TEXT */ + if (!xb_machine_stack_push (self, opcodes, &op_tmp, error)) + return FALSE; + *op_tmp = op_text; + + /* search() */ + if (!xb_machine_stack_push (self, opcodes, &op_tmp, error)) + return FALSE; + *op_tmp = op_func; return TRUE; } @@ -987,10 +1245,10 @@ GError **error) { XbOpcode *op2; - XbSiloAttr *a; + XbSiloNodeAttr *a; XbSilo *silo = XB_SILO (user_data); XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data; - g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack); + g_auto(XbOpcode) op = XB_OPCODE_INIT (); /* optimize pass */ if (query_data == NULL) { @@ -999,23 +1257,26 @@ return FALSE; } + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; + /* indexed string */ - if (xb_opcode_get_kind (op) == XB_OPCODE_KIND_INDEXED_TEXT) { - guint32 val = xb_opcode_get_val (op); + if (xb_opcode_get_kind (&op) == XB_OPCODE_KIND_INDEXED_TEXT) { + guint32 val = xb_opcode_get_val (&op); a = xb_silo_node_get_attr_by_val (silo, query_data->sn, val); } else { - const gchar *str = xb_opcode_get_str (op); - a = xb_silo_node_get_attr_by_str (silo, query_data->sn, str); + const gchar *str = xb_opcode_get_str (&op); + a = xb_silo_get_node_attr_by_str (silo, query_data->sn, str); } if (a == NULL) { - xb_machine_stack_push_text_static (self, stack, NULL); - return TRUE; + return xb_machine_stack_push_text_static (self, stack, NULL, error); } - op2 = xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT, - xb_silo_from_strtab (silo, a->attr_value), - a->attr_value, - NULL); - xb_machine_stack_push_steal (self, stack, op2); + if (!xb_machine_stack_push (self, stack, &op2, error)) + return FALSE; + xb_opcode_init (op2, XB_OPCODE_KIND_INDEXED_TEXT, + xb_silo_from_strtab (silo, a->attr_value), + a->attr_value, + NULL); return TRUE; } @@ -1028,22 +1289,26 @@ GError **error) { XbSilo *silo = XB_SILO (user_data); - g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack); - - /* TEXT */ - if (xb_opcode_cmp_str (op)) { - const gchar *str = xb_opcode_get_str (op); - xb_machine_stack_push_text_steal (self, stack, xb_silo_stem (silo, str)); - return TRUE; + XbOpcode *head; + const gchar *str; + g_auto(XbOpcode) op = XB_OPCODE_INIT (); + + head = xb_stack_peek_head (stack); + if (head == NULL || !xb_opcode_cmp_str (head)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s type not supported", + (head != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head)) : "(null)"); + return FALSE; } - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s type not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op))); - return FALSE; + if (!xb_machine_stack_pop (self, stack, &op, error)) + return FALSE; + + /* TEXT */ + str = xb_opcode_get_str (&op); + return xb_machine_stack_push_text_steal (self, stack, xb_silo_stem (silo, str), error); } static gboolean @@ -1056,6 +1321,8 @@ { XbSilo *silo = XB_SILO (user_data); XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data; + XbOpcode *op; + guint8 token_count; /* optimize pass */ if (query_data == NULL) { @@ -1063,11 +1330,25 @@ "cannot optimize: no silo to query"); return FALSE; } - xb_machine_stack_push_steal (self, stack, - xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT, - xb_silo_node_get_text (silo, query_data->sn), - query_data->sn->text, - NULL)); + + if (!xb_machine_stack_push (self, stack, &op, error)) + return FALSE; + xb_opcode_init (op, XB_OPCODE_KIND_INDEXED_TEXT, + xb_silo_get_node_text (silo, query_data->sn), + xb_silo_node_get_text_idx (query_data->sn), + NULL); + + /* use the fast token path even if there are no valid tokens */ + if (xb_silo_node_has_flag (query_data->sn, XB_SILO_NODE_FLAG_IS_TOKENIZED)) + xb_opcode_add_flag (op, XB_OPCODE_FLAG_TOKENIZED); + + /* add tokens */ + token_count = xb_silo_node_get_token_count (query_data->sn); + for (guint i = 0; i < token_count; i++) { + guint32 stridx = xb_silo_node_get_token_idx (query_data->sn, i); + xb_opcode_append_token (op, xb_silo_from_strtab (silo, stridx)); + } + return TRUE; } @@ -1081,6 +1362,7 @@ { XbSilo *silo = XB_SILO (user_data); XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data; + XbOpcode *op; /* optimize pass */ if (query_data == NULL) { @@ -1088,11 +1370,13 @@ "cannot optimize: no silo to query"); return FALSE; } - xb_machine_stack_push_steal (self, stack, - xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT, - xb_silo_node_get_tail (silo, query_data->sn), - query_data->sn->tail, - NULL)); + + if (!xb_machine_stack_push (self, stack, &op, error)) + return FALSE; + xb_opcode_init (op, XB_OPCODE_KIND_INDEXED_TEXT, + xb_silo_get_node_tail (silo, query_data->sn), + xb_silo_node_get_tail_idx (query_data->sn), + NULL); return TRUE; } @@ -1112,8 +1396,7 @@ "cannot optimize: no silo to query"); return FALSE; } - xb_stack_push_bool (stack, query_data->position == 1); - return TRUE; + return xb_stack_push_bool (stack, query_data->position == 1, error); } static gboolean @@ -1132,8 +1415,7 @@ "cannot optimize: no silo to query"); return FALSE; } - xb_stack_push_bool (stack, query_data->sn->next == 0); - return TRUE; + return xb_stack_push_bool (stack, query_data->sn->next == 0, error); } static gboolean @@ -1152,8 +1434,7 @@ "cannot optimize: no silo to query"); return FALSE; } - xb_machine_stack_push_integer (self, stack, query_data->position); - return TRUE; + return xb_machine_stack_push_integer (self, stack, query_data->position, error); } static gboolean @@ -1164,24 +1445,55 @@ gpointer exec_data, GError **error) { - g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack); - g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack); + XbSilo *silo = XB_SILO (user_data); + XbSiloPrivate *priv = GET_PRIVATE (silo); + const gchar *text; + const gchar *search; + XbOpcode *head1 = NULL; + XbOpcode *head2 = NULL; + g_auto(XbOpcode) op1 = XB_OPCODE_INIT (); + g_auto(XbOpcode) op2 = XB_OPCODE_INIT (); + + if (xb_stack_get_size (stack) >= 2) { + head1 = xb_stack_peek (stack, xb_stack_get_size (stack) - 1); + head2 = xb_stack_peek (stack, xb_stack_get_size (stack) - 2); + } + if (head1 == NULL || !xb_opcode_cmp_str (head1) || + head2 == NULL || !xb_opcode_cmp_str (head2)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "%s:%s types not supported", + (head1 != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head1)) : "(null)", + (head2 != NULL) ? xb_opcode_kind_to_string (xb_opcode_get_kind (head2)) : "(null)"); + return FALSE; + } - /* TEXT:TEXT */ - if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) { - xb_stack_push_bool (stack, xb_string_search (xb_opcode_get_str (op2), - xb_opcode_get_str (op1))); - return TRUE; + if (!xb_machine_stack_pop_two (self, stack, &op1, &op2, error)) + return FALSE; + + /* TOKN:TOKN */ + if (xb_opcode_has_flag (&op1, XB_OPCODE_FLAG_TOKENIZED) && + xb_opcode_has_flag (&op2, XB_OPCODE_FLAG_TOKENIZED)) { + return xb_stack_push_bool (stack, xb_string_searchv (xb_opcode_get_tokens (&op2), + xb_opcode_get_tokens (&op1)), error); + } + + /* this is going to be slow, but correct */ + text = xb_opcode_get_str (&op2); + search = xb_opcode_get_str (&op1); + if (text == NULL || search == NULL || text[0] == '\0' || search[0] == '\0') + return xb_stack_push_bool (stack, FALSE, error); + if (!g_str_is_ascii (text) || !g_str_is_ascii (search)) { + if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) { + g_debug ("tokenization for [%s:%s] may be slow!", + text, search); + } + return xb_stack_push_bool (stack, g_str_match_string (search, text, TRUE), error); } - /* fail */ - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "%s:%s types not supported", - xb_opcode_kind_to_string (xb_opcode_get_kind (op1)), - xb_opcode_kind_to_string (xb_opcode_get_kind (op2))); - return FALSE; + /* TEXT:TEXT */ + return xb_stack_push_bool (stack, xb_string_search (text, search), error); } static gboolean @@ -1194,17 +1506,23 @@ { /* @foo -> attr(foo) */ if (g_str_has_prefix (text, "@")) { - XbOpcode *opcode; - opcode = xb_machine_opcode_func_new (self, "attr"); - if (opcode == NULL) { + XbOpcode *op1; + XbOpcode *op2; + + if (!_xb_stack_push_two (opcodes, &op1, &op2, error)) + return FALSE; + + xb_opcode_text_init (op1, text + 1); + if (!xb_machine_opcode_func_init (self, op2, "attr")) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no attr opcode"); + xb_stack_pop (opcodes, NULL, NULL); + xb_stack_pop (opcodes, NULL, NULL); return FALSE; } - xb_stack_push_steal (opcodes, xb_opcode_text_new (text + 1)); - xb_stack_push_steal (opcodes, opcode); + *handled = TRUE; return TRUE; } @@ -1216,6 +1534,7 @@ static void xb_silo_file_monitor_item_free (XbSiloFileMonitorItem *item) { + g_file_monitor_cancel (item->file_monitor); g_signal_handler_disconnect (item->file_monitor, item->file_monitor_id); g_object_unref (item->file_monitor); g_slice_free (XbSiloFileMonitorItem, item); @@ -1226,13 +1545,16 @@ { XbSilo *self = XB_SILO (obj); XbSiloPrivate *priv = GET_PRIVATE (self); - switch (prop_id) { + switch ((XbSiloProperty) prop_id) { case PROP_GUID: g_value_set_string (value, priv->guid); break; case PROP_VALID: g_value_set_boolean (value, priv->valid); break; + case PROP_ENABLE_NODE_CACHE: + g_value_set_boolean (value, priv->enable_node_cache); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; @@ -1244,10 +1566,18 @@ { XbSilo *self = XB_SILO (obj); XbSiloPrivate *priv = GET_PRIVATE (self); - switch (prop_id) { + switch ((XbSiloProperty) prop_id) { case PROP_GUID: g_free (priv->guid); priv->guid = g_value_dup_string (value); + silo_notify (self, obj_props[PROP_GUID]); + break; + case PROP_VALID: + /* Read only */ + g_assert_not_reached (); + break; + case PROP_ENABLE_NODE_CACHE: + xb_silo_set_enable_node_cache (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); @@ -1259,16 +1589,22 @@ xb_silo_init (XbSilo *self) { XbSiloPrivate *priv = GET_PRIVATE (self); - priv->file_monitors = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, (GDestroyNotify) xb_silo_file_monitor_item_free); - priv->nodes = g_hash_table_new_full (g_direct_hash, g_direct_equal, - NULL, (GDestroyNotify) g_object_unref); + + priv->file_monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + g_object_unref, (GDestroyNotify) xb_silo_file_monitor_item_free); + g_mutex_init (&priv->file_monitors_mutex); + priv->strtab_tags = g_hash_table_new (g_str_hash, g_str_equal); priv->strindex = g_hash_table_new (g_str_hash, g_str_equal); priv->profile_str = g_string_new (NULL); + priv->query_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + g_rw_lock_init (&priv->query_cache_mutex); + priv->nodes = NULL; /* initialised when first used */ g_mutex_init (&priv->nodes_mutex); + priv->context = g_main_context_ref_thread_default (); + #ifdef HAVE_LIBSTEMMER g_mutex_init (&priv->stemmer_mutex); #endif @@ -1295,6 +1631,8 @@ xb_silo_machine_fixup_position_cb, self, NULL); xb_machine_add_opcode_fixup (priv->machine, "TEXT,FUNC:attr", xb_silo_machine_fixup_attr_exists_cb, self, NULL); + xb_machine_add_opcode_fixup (priv->machine, "FUNC:text,TEXT,FUNC:search", + xb_silo_machine_fixup_attr_search_token_cb, self, NULL); xb_machine_add_text_handler (priv->machine, xb_silo_machine_fixup_attr_text_cb, self, NULL); } @@ -1305,6 +1643,7 @@ XbSilo *self = XB_SILO (obj); XbSiloPrivate *priv = GET_PRIVATE (self); + g_clear_pointer (&priv->nodes, g_hash_table_unref); g_mutex_clear (&priv->nodes_mutex); #ifdef HAVE_LIBSTEMMER @@ -1313,12 +1652,16 @@ g_mutex_clear (&priv->stemmer_mutex); #endif + g_clear_pointer (&priv->context, g_main_context_unref); + g_free (priv->guid); g_string_free (priv->profile_str, TRUE); + g_hash_table_unref (priv->query_cache); + g_rw_lock_clear (&priv->query_cache_mutex); g_object_unref (priv->machine); g_hash_table_unref (priv->strindex); g_hash_table_unref (priv->file_monitors); - g_hash_table_unref (priv->nodes); + g_mutex_clear (&priv->file_monitors_mutex); g_hash_table_unref (priv->strtab_tags); if (priv->mmap != NULL) g_mapped_file_unref (priv->mmap); @@ -1330,7 +1673,6 @@ static void xb_silo_class_init (XbSiloClass *klass) { - GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = xb_silo_finalize; object_class->get_property = xb_silo_get_property; @@ -1339,19 +1681,50 @@ /** * XbSilo:guid: */ - pspec = g_param_spec_string ("guid", NULL, NULL, NULL, + obj_props[PROP_GUID] = + g_param_spec_string ("guid", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | - G_PARAM_STATIC_NAME); - g_object_class_install_property (object_class, PROP_GUID, pspec); + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); /** - * XbSilo:allow-cancel: + * XbSilo:valid: */ - pspec = g_param_spec_boolean ("valid", NULL, NULL, TRUE, + obj_props[PROP_VALID] = + g_param_spec_boolean ("valid", NULL, NULL, TRUE, G_PARAM_READABLE | - G_PARAM_STATIC_NAME); - g_object_class_install_property (object_class, PROP_VALID, pspec); + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * XbSilo:enable-node-cache: + * + * Whether to cache all #XbNode instances ever constructed in a single + * cache in the #XbSilo, so that the same #XbNode instance is always + * returned in query results for a given XPath. This is a form of + * memoisation, and allows xb_node_get_data() and xb_node_set_data() to + * be used. + * + * This is enabled by default to preserve compatibility with older + * versions of libxmlb, but most clients will want to disable it. It + * adds a large memory overhead (no #XbNode is ever finalised) but + * achieves moderately low hit rates for typical XML parsing workloads + * where most nodes are accessed only once or twice as they are + * processed and then processing moves on to other nodes. + * + * This property can only be changed before the #XbSilo is passed + * between threads. Changing it is not thread-safe. + * + * Since: 0.2.0 + */ + obj_props[PROP_ENABLE_NODE_CACHE] = + g_param_spec_boolean ("enable-node-cache", NULL, NULL, TRUE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); } /** @@ -1368,3 +1741,67 @@ { return g_object_new (XB_TYPE_SILO, NULL); } + +/** + * xb_silo_lookup_query: + * @self: an #XbSilo + * @xpath: an XPath query string + * + * Create an #XbQuery from the given @xpath XPath string, or return it from the + * query cache in the #XbSilo. + * + * @xpath must be valid: it is a programmer error if creating the query fails + * (i.e. if xb_query_new() returns an error). + * + * This function is thread-safe. + * + * Returns: (transfer full): an #XbQuery representing @xpath + * Since: 0.3.0 + */ +XbQuery * +xb_silo_lookup_query (XbSilo *self, const gchar *xpath) +{ + XbSiloPrivate *priv = GET_PRIVATE (self); + XbQuery *result; + + g_rw_lock_reader_lock (&priv->query_cache_mutex); + result = g_hash_table_lookup (priv->query_cache, xpath); + g_rw_lock_reader_unlock (&priv->query_cache_mutex); + + if (result != NULL) { + g_object_ref (result); + g_debug ("Found cached query ‘%s’ (%p) in silo %p", xpath, result, self); + } else { + g_autoptr(XbQuery) query = NULL; + + /* check again with an exclusive lock */ + g_rw_lock_writer_lock (&priv->query_cache_mutex); + result = g_hash_table_lookup (priv->query_cache, xpath); + if (result != NULL) { + g_object_ref (result); + g_debug ("Found cached query ‘%s’ (%p) in silo %p", xpath, result, self); + } else { + g_autoptr(GError) error_local = NULL; + + query = xb_query_new (self, xpath, &error_local); + if (query == NULL) { + /* This should not happen: the caller should + * have written a valid query. */ + g_error ("Invalid XPath query ‘%s’: %s", + xpath, error_local->message); + g_rw_lock_writer_unlock (&priv->query_cache_mutex); + g_assert_not_reached (); + return NULL; + } + + result = g_object_ref (query); + + g_hash_table_insert (priv->query_cache, g_strdup (xpath), g_steal_pointer (&query)); + g_debug ("Caching query ‘%s’ (%p) in silo %p; query cache now has %u entries", + xpath, query, self, g_hash_table_size (priv->query_cache)); + } + g_rw_lock_writer_unlock (&priv->query_cache_mutex); + } + + return result; +} diff -Nru libxmlb-0.1.15/src/xb-silo-export.c libxmlb-0.3.6/src/xb-silo-export.c --- libxmlb-0.1.15/src/xb-silo-export.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-export.c 2021-12-06 20:36:37.000000000 +0000 @@ -12,7 +12,7 @@ #include "xb-node-private.h" #include "xb-silo-export-private.h" -#include "xb-silo-private.h" +#include "xb-silo-node.h" #include "xb-string-private.h" typedef struct { @@ -38,58 +38,66 @@ xb_silo_from_strtab (self, sn->element_name)); /* add any attributes */ - for (guint8 i = 0; i < sn->nr_attrs; i++) { - XbSiloAttr *a = xb_silo_get_attr (self, helper->off, i); + for (guint8 i = 0; i < xb_silo_node_get_attr_count (sn); i++) { + XbSiloNodeAttr *a = xb_silo_node_get_attr (sn, i); g_autofree gchar *key = xb_string_xml_escape (xb_silo_from_strtab (self, a->attr_name)); g_autofree gchar *val = xb_string_xml_escape (xb_silo_from_strtab (self, a->attr_value)); g_string_append_printf (helper->xml, " %s=\"%s\"", key, val); } - /* finish the opening tag and add any text if it exists */ - if (sn->text != XB_SILO_UNSET) { - g_autofree gchar *text = xb_string_xml_escape (xb_silo_from_strtab (self, sn->text)); - g_string_append (helper->xml, ">"); - g_string_append (helper->xml, text); + /* collapse open/close tags together if no text or children */ + if (helper->flags & XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY && + xb_silo_node_get_text_idx (sn) == XB_SILO_UNSET && + xb_silo_get_child_node (self, sn) == NULL) { + g_string_append (helper->xml, " />"); } else { - g_string_append (helper->xml, ">"); - if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) - g_string_append (helper->xml, "\n"); - } - helper->off += xb_silo_node_get_size (sn); - - /* recurse deeper */ - while (xb_silo_get_node(self, helper->off)->is_node) { - XbSiloNode *child = xb_silo_get_node (self, helper->off); - helper->level++; - if (!xb_silo_export_node (self, helper, child, error)) + /* finish the opening tag and add any text if it exists */ + if (xb_silo_node_get_text_idx (sn) != XB_SILO_UNSET) { + g_autofree gchar *text = xb_string_xml_escape (xb_silo_get_node_text (self, sn)); + g_string_append (helper->xml, ">"); + g_string_append (helper->xml, text); + } else { + g_string_append (helper->xml, ">"); + if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE) + g_string_append (helper->xml, "\n"); + } + helper->off += xb_silo_node_get_size (sn); + + /* recurse deeper */ + while (xb_silo_node_has_flag (xb_silo_get_node (self, helper->off), + XB_SILO_NODE_FLAG_IS_ELEMENT)) { + XbSiloNode *child = xb_silo_get_node (self, helper->off); + helper->level++; + if (!xb_silo_export_node (self, helper, child, error)) + return FALSE; + helper->level--; + } + + /* check for the single byte sentinel */ + sn2 = xb_silo_get_node (self, helper->off); + if (xb_silo_node_has_flag (sn2, XB_SILO_NODE_FLAG_IS_ELEMENT)) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "no seninel at %" G_GUINT32_FORMAT, + helper->off); return FALSE; - helper->level--; - } + } + helper->off += xb_silo_node_get_size (sn2); - /* check for the single byte sentinel */ - sn2 = xb_silo_get_node (self, helper->off); - if (sn2->is_node) { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_INVALID_DATA, - "no seninel at %" G_GUINT32_FORMAT, - helper->off); - return FALSE; - } - helper->off += xb_silo_node_get_size (sn2); - - /* add closing tag */ - if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && - sn->text == XB_SILO_UNSET) { - for (guint i = 0; i < helper->level; i++) - g_string_append (helper->xml, " "); + /* add closing tag */ + if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 && + xb_silo_node_get_text_idx (sn) == XB_SILO_UNSET) { + for (guint i = 0; i < helper->level; i++) + g_string_append (helper->xml, " "); + } + g_string_append_printf (helper->xml, "", + xb_silo_from_strtab (self, sn->element_name)); } - g_string_append_printf (helper->xml, "", - xb_silo_from_strtab (self, sn->element_name)); /* add any optional tail */ - if (sn->tail != XB_SILO_UNSET) { - g_autofree gchar *tail = xb_string_xml_escape (xb_silo_from_strtab (self, sn->tail)); + if (xb_silo_node_get_tail_idx (sn) != XB_SILO_UNSET) { + g_autofree gchar *tail = xb_string_xml_escape (xb_silo_get_node_tail (self, sn)); g_string_append (helper->xml, tail); } @@ -101,7 +109,7 @@ /* private */ GString * -xb_silo_export_with_root (XbSilo *self, XbNode *root, XbNodeExportFlags flags, GError **error) +xb_silo_export_with_root (XbSilo *self, XbSiloNode *sroot, XbNodeExportFlags flags, GError **error) { XbSiloNode *sn; XbSiloExportHelper helper = { @@ -117,12 +125,12 @@ flags |= XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS; /* optional subtree export */ - if (root != NULL) { - sn = xb_node_get_sn (root); + if (sroot != NULL) { + sn = sroot; if (sn != NULL && flags & XB_NODE_EXPORT_FLAG_ONLY_CHILDREN) - sn = xb_silo_node_get_child (self, sn); + sn = xb_silo_get_child_node (self, sn); } else { - sn = xb_silo_get_sroot (self); + sn = xb_silo_get_root_node (self); } /* no root */ @@ -145,7 +153,7 @@ } if ((flags & XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS) == 0) break; - sn = xb_silo_node_get_next (self, sn); + sn = xb_silo_get_next_node (self, sn); } while (sn != NULL); /* success */ diff -Nru libxmlb-0.1.15/src/xb-silo-export-private.h libxmlb-0.3.6/src/xb-silo-export-private.h --- libxmlb-0.1.15/src/xb-silo-export-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-export-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -7,11 +7,12 @@ #pragma once #include "xb-silo-export.h" +#include "xb-silo-private.h" G_BEGIN_DECLS GString *xb_silo_export_with_root (XbSilo *self, - XbNode *root, + XbSiloNode *sroot, XbNodeExportFlags flags, GError **error); diff -Nru libxmlb-0.1.15/src/xb-silo.h libxmlb-0.3.6/src/xb-silo.h --- libxmlb-0.1.15/src/xb-silo.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo.h 2021-12-06 20:36:37.000000000 +0000 @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include "xb-node.h" @@ -49,6 +51,7 @@ * @XB_SILO_PROFILE_FLAG_DEBUG: Output profiling as debug * @XB_SILO_PROFILE_FLAG_APPEND: Save profiling in an appended string * @XB_SILO_PROFILE_FLAG_XPATH: Save XPATH queries + * @XB_SILO_PROFILE_FLAG_OPTIMIZER: Output the machine optimizer as debug * * The flags used when profiling a silo. **/ @@ -57,6 +60,7 @@ XB_SILO_PROFILE_FLAG_DEBUG = 1 << 0, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_APPEND = 1 << 1, /* Since: 0.1.1 */ XB_SILO_PROFILE_FLAG_XPATH = 1 << 2, /* Since: 0.1.1 */ + XB_SILO_PROFILE_FLAG_OPTIMIZER = 1 << 3, /* Since: 0.3.1 */ /*< private >*/ XB_SILO_PROFILE_FLAG_LAST } XbSiloProfileFlags; @@ -93,4 +97,13 @@ XbSiloProfileFlags profile_flags); const gchar *xb_silo_get_profile_string (XbSilo *self); +gboolean xb_silo_get_enable_node_cache (XbSilo *self); +void xb_silo_set_enable_node_cache (XbSilo *self, + gboolean enable_node_cache); + +#include "xb-query.h" + +XbQuery *xb_silo_lookup_query (XbSilo *self, + const gchar *xpath); + G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-silo-node.c libxmlb-0.3.6/src/xb-silo-node.c --- libxmlb-0.1.15/src/xb-silo-node.c 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-node.c 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "XbSilo" + +#include + +#include "xb-silo-node.h" + +/* private */ +guint32 +xb_silo_node_get_size (XbSiloNode *self) +{ + if (xb_silo_node_has_flag (self, XB_SILO_NODE_FLAG_IS_ELEMENT)) { + guint8 sz = sizeof(XbSiloNode); + sz += self->attr_count * sizeof(XbSiloNodeAttr); + sz += self->token_count * sizeof(guint32); + return sz; + } + + /* sentinel */ + return sizeof(guint8); +} + +/* private */ +guint8 +xb_silo_node_get_flags (XbSiloNode *self) +{ + return self->flags; +} + +/* private */ +gboolean +xb_silo_node_has_flag (XbSiloNode *self, XbSiloNodeFlag flag) +{ + return (self->flags & flag) > 0; +} + +/* private */ +guint32 +xb_silo_node_get_text_idx (XbSiloNode *self) +{ + return self->text; +} + +/* private */ +guint32 +xb_silo_node_get_tail_idx (XbSiloNode *self) +{ + return self->tail; +} + +/* private */ +guint8 +xb_silo_node_get_attr_count (XbSiloNode *self) +{ + return self->attr_count; +} + +/* private */ +XbSiloNodeAttr * +xb_silo_node_get_attr (XbSiloNode *self, guint8 idx) +{ + guint32 off = sizeof(XbSiloNode); + off += sizeof(XbSiloNodeAttr) * idx; + return (XbSiloNodeAttr *) (((guint8 *) self) + off); +} + +/* private */ +guint8 +xb_silo_node_get_token_count (XbSiloNode *self) +{ + return self->token_count; +} + +/* private */ +guint32 +xb_silo_node_get_token_idx (XbSiloNode *self, guint idx) +{ + guint32 off = 0; + guint32 stridx; + + /* not valid */ + if (!xb_silo_node_has_flag (self, XB_SILO_NODE_FLAG_IS_ELEMENT)) + return XB_SILO_UNSET; + if (!xb_silo_node_has_flag (self, XB_SILO_NODE_FLAG_IS_TOKENIZED)) + return XB_SILO_UNSET; + + /* calculate offset to token */ + off += sizeof(XbSiloNode); + off += self->attr_count * sizeof(XbSiloNodeAttr); + off += idx * sizeof(guint32); + memcpy (&stridx, (guint8 *) self + off, sizeof(stridx)); + return stridx; +} diff -Nru libxmlb-0.1.15/src/xb-silo-node.h libxmlb-0.3.6/src/xb-silo-node.h --- libxmlb-0.1.15/src/xb-silo-node.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-node.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#define XB_SILO_UNSET 0xffffffff + +typedef enum { + XB_SILO_NODE_FLAG_NONE = 0, + XB_SILO_NODE_FLAG_IS_ELEMENT = 1 << 0, + XB_SILO_NODE_FLAG_IS_TOKENIZED = 1 << 1, +} XbSiloNodeFlag; + +typedef struct __attribute__ ((packed)) { + guint8 flags:2; + guint8 attr_count:6; + guint8 token_count; /* ONLY when is_node */ + guint32 element_name; /* ONLY when is_node: from strtab */ + guint32 parent; /* ONLY when is_node: from 0 */ + guint32 next; /* ONLY when is_node: from 0 */ + guint32 text; /* ONLY when is_node: from strtab */ + guint32 tail; /* ONLY when is_node: from strtab */ + /* + guint32 attrs[attr_count]; + guint32 tokens[token_count]; + */ +} XbSiloNode; + +typedef struct __attribute__ ((packed)) { + guint32 attr_name; /* from strtab */ + guint32 attr_value; /* from strtab */ +} XbSiloNodeAttr; + +guint32 xb_silo_node_get_size (XbSiloNode *self); +guint32 xb_silo_node_get_text_idx (XbSiloNode *self); +guint32 xb_silo_node_get_tail_idx (XbSiloNode *self); +guint8 xb_silo_node_get_token_count (XbSiloNode *self); +guint32 xb_silo_node_get_token_idx (XbSiloNode *self, + guint idx); +guint8 xb_silo_node_get_flags (XbSiloNode *self); +gboolean xb_silo_node_has_flag (XbSiloNode *self, + XbSiloNodeFlag flags); +guint8 xb_silo_node_get_attr_count (XbSiloNode *self); +XbSiloNodeAttr *xb_silo_node_get_attr (XbSiloNode *self, + guint8 idx); diff -Nru libxmlb-0.1.15/src/xb-silo-private.h libxmlb-0.3.6/src/xb-silo-private.h --- libxmlb-0.1.15/src/xb-silo-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -9,6 +9,8 @@ #include "xb-machine.h" #include "xb-node.h" #include "xb-silo.h" +#include "xb-silo-node.h" +#include "xb-query.h" #include "xb-string-private.h" @@ -25,23 +27,7 @@ } XbSiloHeader; #define XB_SILO_MAGIC_BYTES 0x624c4d58 -#define XB_SILO_VERSION 0x00000007 -#define XB_SILO_UNSET 0xffffffff - -typedef struct __attribute__ ((packed)) { - guint8 is_node:1; - guint8 nr_attrs:7; - guint32 element_name; /* ONLY when is_node: from strtab */ - guint32 parent; /* ONLY when is_node: from 0 */ - guint32 next; /* ONLY when is_node: from 0 */ - guint32 text; /* ONLY when is_node: from strtab */ - guint32 tail; /* ONLY when is_node: from strtab */ -} XbSiloNode; - -typedef struct __attribute__ ((packed)) { - guint32 attr_name; /* from strtab */ - guint32 attr_value; /* from strtab */ -} XbSiloAttr; +#define XB_SILO_VERSION 0x00000008 typedef struct { /*< private >*/ @@ -57,36 +43,34 @@ const gchar *str); XbSiloNode *xb_silo_get_node (XbSilo *self, guint32 off); -XbSiloAttr *xb_silo_get_attr (XbSilo *self, - guint32 off, - guint8 idx); XbMachine *xb_silo_get_machine (XbSilo *self); guint32 xb_silo_get_strtab (XbSilo *self); guint32 xb_silo_get_strtab_idx (XbSilo *self, const gchar *element); guint32 xb_silo_get_offset_for_node (XbSilo *self, XbSiloNode *n); -guint8 xb_silo_node_get_size (XbSiloNode *n); -XbSiloNode *xb_silo_get_sroot (XbSilo *self); -XbSiloNode *xb_silo_node_get_parent (XbSilo *self, +XbSiloNode *xb_silo_get_root_node (XbSilo *self); +XbSiloNode *xb_silo_get_parent_node (XbSilo *self, XbSiloNode *n); -XbSiloNode *xb_silo_node_get_next (XbSilo *self, +XbSiloNode *xb_silo_get_next_node (XbSilo *self, XbSiloNode *n); -XbSiloNode *xb_silo_node_get_child (XbSilo *self, +XbSiloNode *xb_silo_get_child_node (XbSilo *self, XbSiloNode *n); -const gchar *xb_silo_node_get_element (XbSilo *self, +const gchar *xb_silo_get_node_element (XbSilo *self, XbSiloNode *n); -const gchar *xb_silo_node_get_text (XbSilo *self, +const gchar *xb_silo_get_node_text (XbSilo *self, XbSiloNode *n); -const gchar *xb_silo_node_get_tail (XbSilo *self, +const gchar *xb_silo_get_node_tail (XbSilo *self, XbSiloNode *n); -XbSiloAttr *xb_silo_node_get_attr_by_str (XbSilo *self, +XbSiloNodeAttr *xb_silo_get_node_attr_by_str (XbSilo *self, XbSiloNode *n, const gchar *name); -guint xb_silo_node_get_depth (XbSilo *self, +guint xb_silo_get_node_depth (XbSilo *self, XbSiloNode *n); -XbNode *xb_silo_node_create (XbSilo *self, - XbSiloNode *sn); +XbNode *xb_silo_create_node (XbSilo *self, + XbSiloNode *sn, + gboolean force_node_cache); +GTimer *xb_silo_start_profile (XbSilo *self); void xb_silo_add_profile (XbSilo *self, GTimer *timer, const gchar *fmt, diff -Nru libxmlb-0.1.15/src/xb-silo-query.c libxmlb-0.3.6/src/xb-silo-query.c --- libxmlb-0.1.15/src/xb-silo-query.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-query.c 2021-12-06 20:36:37.000000000 +0000 @@ -13,7 +13,8 @@ #include "xb-node-private.h" #include "xb-opcode.h" -#include "xb-silo-private.h" +#include "xb-opcode-private.h" +#include "xb-silo-node.h" #include "xb-silo-query-private.h" #include "xb-stack-private.h" #include "xb-query-private.h" @@ -24,6 +25,9 @@ XbSiloNode *sn, XbQuerySection *section, XbSiloQueryData *query_data, + XbValueBindings *bindings, + guint bindings_offset, + guint *bindings_offset_end_out, gboolean *result, GError **error) { @@ -41,20 +45,71 @@ if (section->predicates != NULL) { for (guint i = 0; i < section->predicates->len; i++) { XbStack *opcodes = g_ptr_array_index (section->predicates, i); - if (!xb_machine_run (machine, opcodes, result, query_data, error)) + g_auto(XbValueBindings) predicate_bindings = XB_VALUE_BINDINGS_INIT (); + guint predicate_bindings_idx = 0; + XbValueBindings *predicate_bindings_ptr = NULL; + + if (bindings != NULL) + predicate_bindings_ptr = &predicate_bindings; + + /* set up the bindings for this predicate */ + for (guint k = 0; bindings != NULL && k < xb_stack_get_size (opcodes); k++) { + XbOpcode *op = xb_stack_peek (opcodes, k); + if (xb_opcode_is_binding (op)) { + /* ignore errors as they’ll be caught by xb_machine_run() */ + xb_value_bindings_copy_binding (bindings, + bindings_offset + predicate_bindings_idx, + &predicate_bindings, + predicate_bindings_idx); + predicate_bindings_idx++; + } + } + + /* run the predicate; pass NULL for the bindings iff + * (bindings == NULL), as that means we’ve been called + * with pre-0.3.0-style pre-bound values */ + if (!xb_machine_run_with_bindings (machine, opcodes, + predicate_bindings_ptr, + result, + query_data, + error)) return FALSE; + + bindings_offset += predicate_bindings_idx; } } + if (bindings_offset_end_out != NULL) + *bindings_offset_end_out = bindings_offset; + /* success */ return TRUE; } +/** + * XbSiloQueryHelperFlags: + * @XB_SILO_QUERY_HELPER_NONE: No flags set. + * @XB_SILO_QUERY_HELPER_USE_SN: Return #XbSiloNodes as results, rather than + * wrapping them in #XbNode. This assumes that they’ll be wrapped later. + * @XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE: Always cache the #XbNode objects + * + * Flags for #XbSiloQueryHelper. + * + * Since: 0.2.0 + */ +typedef enum { + XB_SILO_QUERY_HELPER_NONE = 0, + XB_SILO_QUERY_HELPER_USE_SN = 1 << 0, + XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE = 1 << 1, +} XbSiloQueryHelperFlags; + typedef struct { GPtrArray *sections; /* of XbQuerySection */ - GPtrArray *results; /* of XbNode */ + GPtrArray *results; /* of XbNode or XbSiloNode (see @flags) */ + XbValueBindings *bindings; GHashTable *results_hash; /* of sn:1 */ guint limit; + XbSiloQueryHelperFlags flags; XbSiloQueryData *query_data; } XbSiloQueryHelper; @@ -63,7 +118,13 @@ { if (g_hash_table_lookup (helper->results_hash, sn) != NULL) return FALSE; - g_ptr_array_add (helper->results, xb_silo_node_create (self, sn)); + if (helper->flags & XB_SILO_QUERY_HELPER_USE_SN) { + g_ptr_array_add (helper->results, sn); + } else { + gboolean force_node_cache = (helper->flags & XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE) > 0; + g_ptr_array_add (helper->results, + xb_silo_create_node (self, sn, force_node_cache)); + } g_hash_table_add (helper->results_hash, sn); return helper->results->len == helper->limit; } @@ -75,6 +136,7 @@ xb_silo_query_section_root (XbSilo *self, XbSiloNode *sn, guint i, + guint bindings_offset, XbSiloQueryHelper *helper, GError **error) { @@ -92,13 +154,13 @@ "cannot obtain parent for root"); return FALSE; } - parent = xb_silo_node_get_parent (self, sn); + parent = xb_silo_get_parent_node (self, sn); if (parent == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no parent set for %s", - xb_silo_node_get_element (self, sn)); + xb_silo_get_node_element (self, sn)); return FALSE; } if (i == helper->sections->len - 1) { @@ -107,12 +169,12 @@ } // g_debug ("PARENT @%u", // xb_silo_get_offset_for_node (self, parent)); - return xb_silo_query_section_root (self, parent, i + 1, helper, error); + return xb_silo_query_section_root (self, parent, i + 1, bindings_offset, helper, error); } /* no node means root */ if (sn == NULL) { - sn = xb_silo_get_sroot (self); + sn = xb_silo_get_root_node (self); if (sn == NULL) { g_set_error_literal (error, G_IO_ERROR, @@ -121,7 +183,7 @@ return FALSE; } } else { - sn = xb_silo_node_get_child (self, sn); + sn = xb_silo_get_child_node (self, sn); if (sn == NULL) return TRUE; } @@ -132,9 +194,12 @@ /* continue matching children ".." */ do { gboolean result = TRUE; + guint bindings_offset_end; query_data->sn = sn; if (!xb_silo_query_node_matches (self, machine, sn, section, - query_data, &result, error)) + query_data, helper->bindings, + bindings_offset, &bindings_offset_end, + &result, error)) return FALSE; if (result) { if (i == helper->sections->len - 1) { @@ -144,9 +209,10 @@ break; } else { // g_debug ("MATCH %s at @%u, deeper", -// xb_silo_node_get_element (self, sn), +// xb_silo_get_node_element (self, sn), // xb_silo_get_offset_for_node (self, sn)); - if (!xb_silo_query_section_root (self, sn, i + 1, helper, error)) + if (!xb_silo_query_section_root (self, sn, i + 1, + bindings_offset_end, helper, error)) return FALSE; if (helper->results->len > 0 && helper->results->len == helper->limit) @@ -166,48 +232,41 @@ GPtrArray *results, GHashTable *results_hash, XbQuery *query, + XbQueryContext *context, + gboolean first_result_only, XbSiloQueryData *query_data, + XbSiloQueryHelperFlags flags, GError **error) { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS XbSiloQueryHelper helper = { .results = results, - .limit = xb_query_get_limit (query), + .bindings = (context != NULL) ? xb_query_context_get_bindings (context) : NULL, + .limit = first_result_only ? 1 : (context != NULL ) ? xb_query_context_get_limit (context) : xb_query_get_limit (query), + .flags = flags, .results_hash = results_hash, .query_data = query_data, }; + XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags (context) : xb_query_get_flags (query); +G_GNUC_END_IGNORE_DEPRECATIONS /* find each section */ helper.sections = xb_query_get_sections (query); - return xb_silo_query_section_root (self, sroot, 0, &helper, error); + if (query_flags & XB_QUERY_FLAG_FORCE_NODE_CACHE) + helper.flags |= XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE; + return xb_silo_query_section_root (self, sroot, 0, 0, &helper, error); } -/** - * xb_silo_query_with_root: (skip) - * @self: a #XbSilo - * @n: (allow-none): a #XbNode - * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` - * @limit: maximum number of results to return, or 0 for "all" - * @error: the #GError, or %NULL - * - * Searches the silo using an XPath query, returning up to @limit results. - * - * It is safe to call this function from a different thread to the one that - * created the #XbSilo. - * - * Please note: Only a subset of XPath is supported. - * - * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound - * - * Since: 0.1.0 - **/ -GPtrArray * -xb_silo_query_with_root (XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) +/* Returns an array with (element-type XbSiloNode) if + * %XB_SILO_QUERY_HELPER_USE_SN is set, and (element-type XbNode) otherwise. */ +static GPtrArray * +silo_query_with_root (XbSilo *self, XbNode *n, const gchar *xpath, guint limit, XbSiloQueryHelperFlags flags, GError **error) { XbSiloNode *sn = NULL; g_auto(GStrv) split = NULL; g_autoptr(GHashTable) results_hash = g_hash_table_new (g_direct_hash, g_direct_equal); - g_autoptr(GPtrArray) results = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GPtrArray) results = NULL; + g_autoptr(GTimer) timer = xb_silo_start_profile (self); XbSiloQueryData query_data = { .sn = NULL, .position = 0, @@ -226,6 +285,11 @@ return NULL; } + if (flags & XB_SILO_QUERY_HELPER_USE_SN) + results = g_ptr_array_new_with_free_func (NULL); + else + results = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + /* subtree query */ if (n != NULL) { sn = xb_node_get_sn (n); @@ -247,11 +311,15 @@ for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; g_autoptr(XbQuery) query = xb_query_new (self, split[i], &error_local); + g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT (); + if (query == NULL) { if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) && (split[i + 1] != NULL || results->len > 0)) { - g_debug ("ignoring for OR statement: %s", - error_local->message); + if (xb_silo_get_profile_flags (self) & XB_SILO_PROFILE_FLAG_DEBUG) { + g_debug ("ignoring for OR statement: %s", + error_local->message); + } continue; } g_propagate_prefixed_error (error, @@ -260,10 +328,12 @@ xpath); return NULL; } - xb_query_set_limit (query, limit); + + xb_query_context_set_limit (&context, limit); if (!xb_silo_query_part (self, sn, results, results_hash, - query, &query_data, + query, &context, FALSE, &query_data, + flags, error)) { return NULL; } @@ -289,6 +359,53 @@ return g_steal_pointer (&results); } +/** + * xb_silo_query_with_root: (skip) + * @self: a #XbSilo + * @n: (allow-none): a #XbNode + * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` + * @limit: maximum number of results to return, or 0 for "all" + * @error: the #GError, or %NULL + * + * Searches the silo using an XPath query, returning up to @limit results. + * + * It is safe to call this function from a different thread to the one that + * created the #XbSilo. + * + * Please note: Only a subset of XPath is supported. + * + * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound + * + * Since: 0.1.0 + **/ +GPtrArray * +xb_silo_query_with_root (XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) +{ + return silo_query_with_root (self, n, xpath, limit, XB_SILO_QUERY_HELPER_NONE, error); +} + +/** + * xb_silo_query_sn_with_root: (skip) + * @self: a #XbSilo + * @n: (allow-none): a #XbNode + * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]` + * @limit: maximum number of results to return, or 0 for "all" + * @error: the #GError, or %NULL + * + * A version of xb_silo_query_with_root() which returns results as #XbSiloNodes, + * rather than as #XbNodes. This is intended to be used internally to save on + * intermediate #XbNode allocations. + * + * Returns: (transfer container) (element-type XbSiloNode): results, or %NULL if unfound + * + * Since: 0.2.0 + **/ +GPtrArray * +xb_silo_query_sn_with_root (XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error) +{ + return silo_query_with_root (self, n, xpath, limit, XB_SILO_QUERY_HELPER_USE_SN, error); +} + static void _g_ptr_array_reverse (GPtrArray *array) { @@ -305,6 +422,11 @@ * @self: a #XbSilo * @n: (allow-none): a #XbNode * @query: an #XbQuery + * @context: (nullable) (transfer none): context including values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any context + * @first_result_only: %TRUE if only the first result is going to be used; this + * overrides the limit set in @context, and may perform other optimisations * @error: the #GError, or %NULL * * Searches the silo using an XPath query, returning up to @limit results. @@ -316,19 +438,22 @@ * * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound * - * Since: 0.1.4 + * Since: 0.3.0 **/ GPtrArray * -xb_silo_query_with_root_full (XbSilo *self, XbNode *n, XbQuery *query, GError **error) +xb_silo_query_with_root_full (XbSilo *self, XbNode *n, XbQuery *query, XbQueryContext *context, gboolean first_result_only, GError **error) { XbSiloNode *sn = NULL; g_autoptr(GHashTable) results_hash = g_hash_table_new (g_direct_hash, g_direct_equal); g_autoptr(GPtrArray) results = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); - g_autoptr(GTimer) timer = g_timer_new (); + g_autoptr(GTimer) timer = xb_silo_start_profile (self); XbSiloQueryData query_data = { .sn = NULL, .position = 0, }; +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags (context) : xb_query_get_flags (query); +G_GNUC_END_IGNORE_DEPRECATIONS /* empty silo */ if (xb_silo_is_empty (self)) { @@ -345,17 +470,22 @@ /* only one query allowed */ if (!xb_silo_query_part (self, sn, results, results_hash, - query, &query_data, error)) + query, context, first_result_only, &query_data, + XB_SILO_QUERY_HELPER_NONE, error)) return NULL; /* profile */ if (xb_silo_get_profile_flags (self) & XB_SILO_PROFILE_FLAG_XPATH) { g_autofree gchar *tmp = xb_query_to_string (query); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + guint limit = first_result_only ? 1 : (context != NULL) ? xb_query_context_get_limit (context) : xb_query_get_limit (query); +G_GNUC_END_IGNORE_DEPRECATIONS + xb_silo_add_profile (self, timer, "query on %s with `%s` limit=%u -> %u results", n != NULL ? xb_node_get_element (n) : "/", tmp, - xb_query_get_limit (query), + limit, results->len); } @@ -371,7 +501,7 @@ } /* reverse order */ - if (xb_query_get_flags (query) & XB_QUERY_FLAG_REVERSE) + if (query_flags & XB_QUERY_FLAG_REVERSE) _g_ptr_array_reverse (results); return g_steal_pointer (&results); @@ -397,10 +527,36 @@ GPtrArray * xb_silo_query_full (XbSilo *self, XbQuery *query, GError **error) { + return xb_silo_query_with_context (self, query, NULL, error); +} + +/** + * xb_silo_query_with_context: + * @self: a #XbSilo + * @query: an #XbQuery + * @context: (nullable) (transfer none): context including values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any context + * @error: the #GError, or %NULL + * + * Searches the silo using an XPath query. + * + * It is safe to call this function from a different thread to the one that + * created the #XbSilo. + * + * Please note: Only a subset of XPath is supported. + * + * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound + * + * Since: 0.3.0 + **/ +GPtrArray * +xb_silo_query_with_context (XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) +{ g_return_val_if_fail (XB_IS_SILO (self), NULL); g_return_val_if_fail (XB_IS_QUERY (query), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); - return xb_silo_query_with_root_full (self, NULL, query, error); + return xb_silo_query_with_root_full (self, NULL, query, context, FALSE, error); } /** @@ -423,8 +579,36 @@ XbNode * xb_silo_query_first_full (XbSilo *self, XbQuery *query, GError **error) { + return xb_silo_query_first_with_context (self, query, NULL, error); +} + +/** + * xb_silo_query_first_with_context: + * @self: a #XbSilo + * @query: an #XbQuery + * @context: (nullable) (transfer none): context including values bound to opcodes of type + * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if + * the query doesn’t need any context + * @error: the #GError, or %NULL + * + * Searches the silo using an XPath query, returning up to one result. + * + * It is safe to call this function from a different thread to the one that + * created the #XbSilo. + * + * Please note: Only a tiny subset of XPath 1.0 is supported. + * + * Returns: (transfer none): a #XbNode, or %NULL if unfound + * + * Since: 0.3.0 + **/ +XbNode * +xb_silo_query_first_with_context (XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error) +{ g_autoptr(GPtrArray) results = NULL; - results = xb_silo_query_full (self, query, error); + + results = xb_silo_query_with_root_full (self, NULL, query, context, TRUE, error); + if (results == NULL) return NULL; return g_object_ref (g_ptr_array_index (results, 0)); @@ -511,7 +695,9 @@ g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* do the query */ - array = xb_silo_query_with_root (self, NULL, xpath, 0, &error_local); + array = silo_query_with_root (self, NULL, xpath, 0, + XB_SILO_QUERY_HELPER_USE_SN, + &error_local); if (array == NULL) { if (g_error_matches (error_local, G_IO_ERROR, @@ -528,17 +714,16 @@ /* add each attribute name AND value */ for (guint i = 0; i < array->len; i++) { - XbNode *n = g_ptr_array_index (array, i); - XbSiloNode *sn = xb_node_get_sn (n); + XbSiloNode *sn = g_ptr_array_index (array, i); if (attr != NULL) { - guint32 off = xb_silo_get_offset_for_node (self, sn); - for (guint8 j = 0; j < sn->nr_attrs; j++) { - XbSiloAttr *a = xb_silo_get_attr (self, off, j); + guint8 attr_count = xb_silo_node_get_attr_count (sn); + for (guint8 j = 0; j < attr_count; j++) { + XbSiloNodeAttr *a = xb_silo_node_get_attr (sn, j); xb_silo_strtab_index_insert (self, a->attr_name); xb_silo_strtab_index_insert (self, a->attr_value); } } else { - xb_silo_strtab_index_insert (self, sn->text); + xb_silo_strtab_index_insert (self, xb_silo_node_get_text_idx (sn)); } } diff -Nru libxmlb-0.1.15/src/xb-silo-query.h libxmlb-0.3.6/src/xb-silo-query.h --- libxmlb-0.1.15/src/xb-silo-query.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-query.h 2021-12-06 20:36:37.000000000 +0000 @@ -10,6 +10,7 @@ #include "xb-node.h" #include "xb-query.h" +#include "xb-query-context.h" #include "xb-silo.h" G_BEGIN_DECLS @@ -18,15 +19,27 @@ const gchar *xpath, guint limit, GError **error); + GPtrArray *xb_silo_query_full (XbSilo *self, XbQuery *query, GError **error); +GPtrArray *xb_silo_query_with_context (XbSilo *self, + XbQuery *query, + XbQueryContext *context, + GError **error); + XbNode *xb_silo_query_first (XbSilo *self, const gchar *xpath, GError **error); + XbNode *xb_silo_query_first_full (XbSilo *self, XbQuery *query, GError **error); +XbNode *xb_silo_query_first_with_context(XbSilo *self, + XbQuery *query, + XbQueryContext *context, + GError **error); + gboolean xb_silo_query_build_index (XbSilo *self, const gchar *xpath, const gchar *attr, diff -Nru libxmlb-0.1.15/src/xb-silo-query-private.h libxmlb-0.3.6/src/xb-silo-query-private.h --- libxmlb-0.1.15/src/xb-silo-query-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-silo-query-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -8,11 +8,17 @@ #include +#include "xb-query-context.h" #include "xb-silo-query.h" #include "xb-query.h" G_BEGIN_DECLS +GPtrArray *xb_silo_query_sn_with_root (XbSilo *self, + XbNode *n, + const gchar *xpath, + guint limit, + GError **error); GPtrArray *xb_silo_query_with_root (XbSilo *self, XbNode *n, const gchar *xpath, @@ -21,6 +27,8 @@ GPtrArray *xb_silo_query_with_root_full (XbSilo *self, XbNode *n, XbQuery *query, + XbQueryContext *context, + gboolean first_result_only, GError **error); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-stack.c libxmlb-0.3.6/src/xb-stack.c --- libxmlb-0.1.15/src/xb-stack.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-stack.c 2021-12-06 20:36:37.000000000 +0000 @@ -13,13 +13,6 @@ #include "xb-opcode-private.h" #include "xb-stack-private.h" -struct _XbStack { - gint ref; - guint pos; - guint max_size; - XbOpcode *opcodes[]; /* allocated as part of XbStack */ -}; - /** * xb_stack_unref: * @self: a #XbStack @@ -36,8 +29,9 @@ if (--self->ref > 0) return; for (guint i = 0; i < self->pos; i++) - xb_opcode_unref (self->opcodes[i]); - g_free (self); + xb_opcode_clear (&self->opcodes[i]); + if (!self->stack_allocated) + g_free (self); } /** @@ -61,33 +55,53 @@ /** * xb_stack_pop: * @self: a #XbStack + * @opcode_out: (out caller-allocates) (optional): return location for the popped #XbOpcode + * @error: a #GError, or %NULL * * Pops an opcode off the stack. * - * Returns: (transfer full): a #XbOpcode + * Returns: %TRUE if popping succeeded, %FALSE if the stack was empty already * - * Since: 0.1.3 + * Since: 0.2.0 **/ -XbOpcode * -xb_stack_pop (XbStack *self) +gboolean +xb_stack_pop (XbStack *self, XbOpcode *opcode_out, GError **error) { - if (self->pos == 0) - return NULL; - return self->opcodes[--self->pos]; + if (self->pos == 0) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "stack is empty"); + return FALSE; + } + self->pos--; + if (opcode_out != NULL) + *opcode_out = self->opcodes[self->pos]; + return TRUE; } -/* private */ -GPtrArray * -xb_stack_steal_all (XbStack *self) -{ - GPtrArray *array; - - /* array takes ownership of the opcodes */ - array = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_opcode_unref); - for (guint i = 0; i < self->pos; i++) - g_ptr_array_add (array, self->opcodes[i]); - self->pos = 0; - return array; +/** + * xb_stack_pop_two: (skip): + **/ +gboolean +xb_stack_pop_two (XbStack *self, + XbOpcode *opcode1_out, + XbOpcode *opcode2_out, + GError **error) +{ + if (self->pos < 2) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "stack is not full enough"); + return FALSE; + } + if (opcode1_out != NULL) + *opcode1_out = self->opcodes[self->pos - 1]; + if (opcode2_out != NULL) + *opcode2_out = self->opcodes[self->pos - 2]; + self->pos -= 2; + return TRUE; } /** @@ -106,14 +120,18 @@ { if (idx >= self->pos) return NULL; - return self->opcodes[idx]; + return &self->opcodes[idx]; } /* private */ gboolean -xb_stack_push_bool (XbStack *self, gboolean val) +xb_stack_push_bool (XbStack *self, gboolean val, GError **error) { - return xb_stack_push_steal (self, xb_opcode_bool_new (val)); + XbOpcode *op; + if (!xb_stack_push (self, &op, error)) + return FALSE; + xb_opcode_bool_init (op, val); + return TRUE; } /* private */ @@ -122,7 +140,7 @@ { if (self->pos == 0) return NULL; - return self->opcodes[0]; + return &self->opcodes[0]; } /* private */ @@ -131,46 +149,40 @@ { if (self->pos == 0) return NULL; - return self->opcodes[self->pos - 1]; + return &self->opcodes[self->pos - 1]; } /** * xb_stack_push: * @self: a #XbStack - * @opcode: a #XbOpcode - * - * Pushes a new opcode onto the end of the stack - * - * Returns: %TRUE if the opcode was stored on the stack + * @opcode_out: (out) (nullable): return location for the new #XbOpcode + * @error: a #GError, or %NULL * - * Since: 0.1.3 + * Pushes a new empty opcode onto the end of the stack. A pointer to the opcode + * is returned in @opcode_out so that the caller can initialise it. This must be + * done before the stack is next used as, for performance reasons, the newly + * pushed opcode is not zero-initialised. + * + * Returns: %TRUE if a new empty opcode was returned, or %FALSE if the stack has + * reached its maximum size + * Since: 0.2.0 **/ gboolean -xb_stack_push (XbStack *self, XbOpcode *opcode) -{ - if (self->pos >= self->max_size) +xb_stack_push (XbStack *self, + XbOpcode **opcode_out, + GError **error) +{ + if (self->pos >= self->max_size) { + *opcode_out = NULL; + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NO_SPACE, + "stack is already at maximum size of %u", + self->max_size); return FALSE; - self->opcodes[self->pos++] = xb_opcode_ref (opcode); - return TRUE; -} + } -/** - * xb_stack_push_steal: - * @self: a #XbStack - * @opcode: a #XbOpcode, which is consumed - * - * Pushes a new opcode onto the end of the stack - * - * Returns: %TRUE if the opcode was stored on the stack - * - * Since: 0.1.3 - **/ -gboolean -xb_stack_push_steal (XbStack *self, XbOpcode *opcode) -{ - if (self->pos >= self->max_size) - return FALSE; - self->opcodes[self->pos++] = opcode; + *opcode_out = &self->opcodes[self->pos++]; return TRUE; } @@ -221,7 +233,7 @@ { GString *str = g_string_new (NULL); for (guint i = 0; i < self->pos; i++) { - g_autofree gchar *tmp = xb_opcode_to_string (self->opcodes[i]); + g_autofree gchar *tmp = xb_opcode_to_string (&self->opcodes[i]); g_string_append_printf (str, "%s,", tmp); } if (str->len > 0) @@ -236,6 +248,8 @@ * Creates a stack for the XbMachine request. Only #XbOpcode's can be pushed and * popped from the stack. * + * Unlike with xb_stack_new_inline(), this stack will be allocated on the heap. + * * Returns: (transfer full): a #XbStack * * Since: 0.1.3 @@ -243,8 +257,10 @@ XbStack * xb_stack_new (guint max_size) { - XbStack *self = g_malloc0 (sizeof(XbStack) + max_size * sizeof(XbOpcode*)); + XbStack *self = g_malloc (sizeof(XbStack) + max_size * sizeof(XbOpcode)); self->ref = 1; + self->stack_allocated = FALSE; + self->pos = 0; self->max_size = max_size; return self; } diff -Nru libxmlb-0.1.15/src/xb-stack.h libxmlb-0.3.6/src/xb-stack.h --- libxmlb-0.1.15/src/xb-stack.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-stack.h 2021-12-06 20:36:37.000000000 +0000 @@ -16,10 +16,11 @@ GType xb_stack_get_type (void); gchar *xb_stack_to_string (XbStack *self); -XbOpcode *xb_stack_pop (XbStack *self); +gboolean xb_stack_pop (XbStack *self, + XbOpcode *opcode_out, + GError **error); gboolean xb_stack_push (XbStack *self, - XbOpcode *opcode); -gboolean xb_stack_push_steal (XbStack *self, - XbOpcode *opcode); + XbOpcode **opcode_out, + GError **error); G_END_DECLS diff -Nru libxmlb-0.1.15/src/xb-stack-private.h libxmlb-0.3.6/src/xb-stack-private.h --- libxmlb-0.1.15/src/xb-stack-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-stack-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -8,22 +8,66 @@ #include +#include "xb-opcode-private.h" #include "xb-stack.h" G_BEGIN_DECLS +/* Members of this struct should not be accessed directly — use the accessor + * functions below instead. */ +struct _XbStack { + /*< private >*/ + gint ref; + gboolean stack_allocated; /* whether this XbStack was allocated with alloca() */ + guint pos; /* index of the next unused entry in .opcodes */ + guint max_size; + XbOpcode opcodes[]; /* allocated as part of XbStack */ +}; + +/** + * xb_stack_new_inline: + * @max_stack_size: maximum size of the stack + * + * Creates a stack for the XbMachine request. Only #XbOpcodes can be pushed and + * popped from the stack. + * + * The stack will be allocated on the current C stack frame, so @max_stack_size + * should be chosen to not overflow the C process’ stack. + * + * Returns: (transfer full): a #XbStack + * + * Since: 0.3.1 + **/ +#define xb_stack_new_inline(max_stack_size) \ +(G_GNUC_EXTENSION ({ \ + /* This function has to be static inline so we can use g_alloca(), which \ + * is needed for performance reasons — about 3 million XbStacks are \ + * allocated while starting gnome-software. */ \ + guint xsni_max_size = (max_stack_size); \ + XbStack *xsni_stack = g_alloca (sizeof(XbStack) + xsni_max_size * sizeof(XbOpcode)); \ + xsni_stack->ref = 1; \ + xsni_stack->stack_allocated = TRUE; \ + xsni_stack->pos = 0; \ + xsni_stack->max_size = xsni_max_size; \ + (XbStack *) xsni_stack; \ +})) + XbStack *xb_stack_new (guint max_size); void xb_stack_unref (XbStack *self); XbStack *xb_stack_ref (XbStack *self); guint xb_stack_get_size (XbStack *self); guint xb_stack_get_max_size (XbStack *self); +gboolean xb_stack_pop_two (XbStack *self, + XbOpcode *opcode1_out, + XbOpcode *opcode2_out, + GError **error); gboolean xb_stack_push_bool (XbStack *self, - gboolean val); + gboolean val, + GError **error); XbOpcode *xb_stack_peek (XbStack *self, guint idx); XbOpcode *xb_stack_peek_head (XbStack *self); XbOpcode *xb_stack_peek_tail (XbStack *self); -GPtrArray *xb_stack_steal_all (XbStack *self); G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbStack, xb_stack_unref) diff -Nru libxmlb-0.1.15/src/xb-string.c libxmlb-0.3.6/src/xb-string.c --- libxmlb-0.1.15/src/xb-string.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-string.c 2021-12-06 20:36:37.000000000 +0000 @@ -143,7 +143,12 @@ * Searches for a fuzzy search match, ignoring search matches that are not at * the start of the token. * + * For example, `foo` and `baz` would match `foobar baz` but `bar` would not + * match the same string. + * * Returns: %TRUE if the string @search is contained in @text. + * + * Since: 0.3.0 **/ gboolean xb_string_search (const gchar *text, const gchar *search) @@ -179,6 +184,62 @@ } /** + * xb_string_searchv: (skip) + * @text: NULL-terminated source strings + * @search: NULL-terminated text tokens to search for + * + * Searches for a fuzzy search match, ignoring search matches that are not at + * the start of the token. + * + * For example `{"foo", "baz"}` would match with `{"wizz", "foobar"}` but + * `{"baz"}` would not match the same set of strings. + * + * Returns: %TRUE if the string @search is contained in @text. + * + * Since: 0.3.0 + **/ +gboolean +xb_string_searchv (const gchar **text, const gchar **search) +{ + if (text == NULL || text[0] == NULL || text[0][0] == '\0') + return FALSE; + if (search == NULL || search[0] == NULL || search[0][0] == '\0') + return FALSE; + for (guint j = 0; text[j] != NULL; j++) { + for (guint i = 0; search[i] != NULL; i++) { + if (g_str_has_prefix (text[j], search[i])) + return TRUE; + } + } + return FALSE; +} + +/** + * xb_string_token_valid: (skip) + * @text: The potential token + * @see_also: xb_builder_node_tokenize_text(), xb_builder_node_get_tokens() + * + * This is typically used to eliminate tokens which are not useful for search + * matching. + * + * For instance, tokens like `in` and `at` are less than three characters in + * size and should be rejected. + * + * Returns: %TRUE if the token should be used for searching. + **/ +gboolean +xb_string_token_valid (const gchar *text) +{ + if (text == NULL) + return FALSE; + if (text[0] == '\0' || + text[1] == '\0' || + text[2] == '\0') + return FALSE; + return TRUE; +} + +/** * xb_string_escape: * @str: string, e.g. `app/org.gnome.ghex/x86_64/stable` * diff -Nru libxmlb-0.1.15/src/xb-string-private.h libxmlb-0.3.6/src/xb-string-private.h --- libxmlb-0.1.15/src/xb-string-private.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-string-private.h 2021-12-06 20:36:37.000000000 +0000 @@ -18,6 +18,9 @@ const gchar *search); gboolean xb_string_search (const gchar *text, const gchar *search); +gboolean xb_string_searchv (const gchar **text, + const gchar **search); +gboolean xb_string_token_valid (const gchar *text); gchar *xb_string_xml_escape (const gchar *str); gboolean xb_string_isspace (const gchar *str, gssize strsz); diff -Nru libxmlb-0.1.15/src/xb-tool.c libxmlb-0.3.6/src/xb-tool.c --- libxmlb-0.1.15/src/xb-tool.c 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xb-tool.c 2021-12-06 20:36:37.000000000 +0000 @@ -12,6 +12,7 @@ #include #endif #include +#include #include "xb-builder.h" #include "xb-silo-export.h" @@ -25,6 +26,7 @@ gboolean force; gboolean wait; gboolean profile; + gchar **tokenize; } XbToolPrivate; static void @@ -36,6 +38,7 @@ g_ptr_array_unref (priv->cmd_array); g_main_loop_unref (priv->loop); g_object_unref (priv->cancellable); + g_strfreev (priv->tokenize); g_free (priv); } @@ -252,7 +255,9 @@ guint limit = 0; g_autoptr(GFile) file = NULL; g_autoptr(GPtrArray) results = NULL; + g_autoptr(XbQuery) query = NULL; g_autoptr(XbSilo) silo = xb_silo_new (); + g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT (); /* check args */ if (g_strv_length (values) < 2) { @@ -269,6 +274,7 @@ file = g_file_new_for_path (values[0]); if (priv->profile) { xb_silo_set_profile_flags (silo, + XB_SILO_PROFILE_FLAG_OPTIMIZER | XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_APPEND); } @@ -278,9 +284,13 @@ /* parse optional limit */ if (g_strv_length (values) == 3) limit = g_ascii_strtoull (values[2], NULL, 10); + xb_query_context_set_limit (&context, limit); /* query */ - results = xb_silo_query (silo, values[1], limit, error); + query = xb_query_new_full (silo, values[1], XB_QUERY_FLAG_OPTIMIZE, error); + if (query == NULL) + return FALSE; + results = xb_silo_query_with_context (silo, query, &context, error); if (results == NULL) return FALSE; for (guint i = 0; i < results->len; i++) { @@ -365,6 +375,22 @@ } static gboolean +xb_tool_builder_tokenize_cb (XbBuilderFixup *self, + XbBuilderNode *bn, + gpointer user_data, + GError **error) +{ + XbToolPrivate *priv = (XbToolPrivate *) user_data; + for (guint i = 0; priv->tokenize != NULL && priv->tokenize[i] != NULL; i++) { + if (g_strcmp0 (xb_builder_node_get_element (bn), priv->tokenize[i]) == 0) { + xb_builder_node_tokenize_text (bn); + break; + } + } + return TRUE; +} + +static gboolean xb_tool_compile (XbToolPrivate *priv, gchar **values, GError **error) { const gchar *const *locales = g_get_language_names (); @@ -390,6 +416,13 @@ for (guint i = 1; values[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path (values[i]); g_autoptr(XbBuilderSource) source = xb_builder_source_new (); + if (priv->tokenize != NULL) { + g_autoptr(XbBuilderFixup) fixup = NULL; + fixup = xb_builder_fixup_new ("TextTokenize", + xb_tool_builder_tokenize_cb, + priv, NULL); + xb_builder_source_add_fixup (source, fixup); + } if (!xb_builder_source_load_file (source, file, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, @@ -404,7 +437,7 @@ silo = xb_builder_ensure (builder, file_dst, XB_BUILDER_COMPILE_FLAG_WATCH_BLOB | XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID | - XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, + XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS, NULL, error); if (silo == NULL) return FALSE; @@ -455,9 +488,13 @@ "Return only when the silo is no longer valid", NULL }, { "profile", 'p', 0, G_OPTION_ARG_NONE, &priv->profile, "Show profiling information", NULL }, + { "tokenize", 't', 0, G_OPTION_ARG_STRING_ARRAY, &priv->tokenize, + "Tokenize elements for faster search, e.g. name,summary", NULL }, { NULL} }; + setlocale (LC_ALL, ""); + /* do not let GIO start a session bus */ g_setenv ("GIO_USE_VFS", "local", 1); diff -Nru libxmlb-0.1.15/src/xb-value-bindings.c libxmlb-0.3.6/src/xb-value-bindings.c --- libxmlb-0.1.15/src/xb-value-bindings.c 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-value-bindings.c 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,309 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2020 Endless OS Foundation LLC + * + * Author: Philip Withnall + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#define G_LOG_DOMAIN "XbValueBindings" + +#include "config.h" + +#include + +#include "xb-opcode-private.h" +#include "xb-value-bindings.h" + +typedef struct { + enum { + KIND_NONE, + KIND_TEXT, + KIND_INTEGER, + } kind; + union { + gchar *text; + guint32 integer; + }; + GDestroyNotify destroy_func; +} BoundValue; + +typedef struct { + /* Currently limited to 4 values since that’s all that any client + * uses. This could be expanded to dynamically allow more in future. */ + BoundValue values[4]; + gpointer dummy[3]; +} RealValueBindings; + +G_STATIC_ASSERT (sizeof (XbValueBindings) == sizeof (RealValueBindings)); + +G_DEFINE_BOXED_TYPE (XbValueBindings, xb_value_bindings, + xb_value_bindings_copy, xb_value_bindings_free) + +/** + * xb_value_bindings_init: + * @self: an uninitialised #XbValueBindings to initialise + * + * Initialise a stack-allocated #XbValueBindings struct so it can be used. + * + * Stack-allocated #XbValueBindings instances should be freed once finished + * with, using xb_value_bindings_clear() (or `g_auto(XbValueBindings)`, which is + * equivalent). + * + * Since: 0.3.0 + */ +void +xb_value_bindings_init (XbValueBindings *self) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + for (gsize i = 0; i < G_N_ELEMENTS (_self->values); i++) + _self->values[i].kind = KIND_NONE; +} + +static void +xb_value_bindings_clear_index (XbValueBindings *self, guint idx) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + g_return_if_fail (idx < G_N_ELEMENTS (_self->values)); + + if (_self->values[idx].kind == KIND_TEXT && _self->values[idx].destroy_func) + _self->values[idx].destroy_func (_self->values[idx].text); + _self->values[idx].kind = KIND_NONE; + _self->values[idx].text = NULL; + _self->values[idx].destroy_func = NULL; +} + +/** + * xb_value_bindings_clear: + * @self: an #XbValueBindings + * + * Clear an #XbValueBindings, freeing any allocated memory it points to. + * + * After this function has been called, the contents of the #XbValueBindings are + * undefined, and it’s only safe to call xb_value_bindings_init() on it. + * + * Since: 0.3.0 + */ +void +xb_value_bindings_clear (XbValueBindings *self) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + for (gsize i = 0; i < G_N_ELEMENTS (_self->values); i++) + xb_value_bindings_clear_index (self, i); +} + +/** + * xb_value_bindings_copy: + * @self: an #XbValueBindings + * + * Copy @self into a new heap-allocated #XbValueBindings instance. + * + * Returns: (transfer full): a copy of @self + * Since: 0.3.0 + */ +XbValueBindings * +xb_value_bindings_copy (XbValueBindings *self) +{ + RealValueBindings *_self = (RealValueBindings *) self; + g_autoptr(XbValueBindings) copy = g_new0 (XbValueBindings, 1); + + xb_value_bindings_init (copy); + + for (gsize i = 0; i < G_N_ELEMENTS (_self->values); i++) { + gboolean copied = xb_value_bindings_copy_binding (self, i, copy, i); + g_assert (copied); + } + + return g_steal_pointer (©); +} + +/** + * xb_value_bindings_free: + * @self: a heap-allocated #XbValueBindings + * + * Free a heap-allocated #XbValueBindings instance. This should be used on + * #XbValueBindings instances created with xb_value_bindings_copy(). + * + * For stack-allocated instances, xb_value_bindings_clear() should be used + * instead. + * + * Since: 0.3.0 + */ +void +xb_value_bindings_free (XbValueBindings *self) +{ + g_return_if_fail (self != NULL); + + xb_value_bindings_clear (self); + g_free (self); +} + +/** + * xb_value_bindings_is_bound: + * @self: an #XbValueBindings + * @idx: 0-based index of the binding to check + * + * Check whether a value has been bound to the given index using (for example) + * xb_value_bindings_bind_str(). + * + * Returns: %TRUE if a value is bound to @idx, %FALSE otherwise + * Since: 0.3.0 + */ +gboolean +xb_value_bindings_is_bound (XbValueBindings *self, guint idx) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + return (idx < G_N_ELEMENTS (_self->values) && _self->values[idx].kind != KIND_NONE); +} + +/** + * xb_value_bindings_bind_str: + * @self: an #XbValueBindings + * @idx: 0-based index to bind to + * @str: (transfer full) (not nullable): a string to bind to @idx + * @destroy_func: (nullable): function to free @str + * + * Bind @str to @idx in the value bindings. + * + * This will overwrite any previous binding at @idx. It will take ownership of + * @str, and an appropriate @destroy_func must be provided to free @str once the + * binding is no longer needed. @destroy_func will be called exactly once at + * some point before the #XbValueBindings is cleared or freed. + * + * Since: 0.3.0 + */ +void +xb_value_bindings_bind_str (XbValueBindings *self, guint idx, const gchar *str, GDestroyNotify destroy_func) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + g_return_if_fail (self != NULL); + g_return_if_fail (str != NULL); + + /* Currently limited to two values, but this restriction could be lifted + * in future. */ + g_return_if_fail (idx < G_N_ELEMENTS (_self->values)); + + xb_value_bindings_clear_index (self, idx); + + _self->values[idx].kind = KIND_TEXT; + _self->values[idx].text = (gchar *) str; + _self->values[idx].destroy_func = destroy_func; +} + +/** + * xb_value_bindings_bind_val: + * @self: an #XbValueBindings + * @idx: 0-based index to bind to + * @val: an integer to bind to @idx + * + * Bind @val to @idx in the value bindings. + * + * This will overwrite any previous binding at @idx. + * + * Since: 0.3.0 + */ +void +xb_value_bindings_bind_val (XbValueBindings *self, guint idx, guint32 val) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + g_return_if_fail (self != NULL); + + /* Currently limited to two values, but this restriction could be lifted + * in future. */ + g_return_if_fail (idx < G_N_ELEMENTS (_self->values)); + + xb_value_bindings_clear_index (self, idx); + + _self->values[idx].kind = KIND_INTEGER; + _self->values[idx].integer = val; + _self->values[idx].destroy_func = NULL; +} + +/** + * xb_value_bindings_lookup_opcode: + * @self: an #XbValueBindings + * @idx: 0-based index to look up the binding from + * @opcode_out: (out caller-allocates) (not nullable): pointer to an #XbOpcode + * to initialise from the binding + * + * Initialises an #XbOpcode with the value bound to @idx, if a value is bound. + * If no value is bound, @opcode_out is not touched and %FALSE is returned. + * + * @opcode_out is initialised to point to the data inside the #XbValueBindings, + * so must have a shorter lifetime than the #XbValueBindings. It will be of kind + * %XB_OPCODE_KIND_BOUND_TEXT or %XB_OPCODE_KIND_BOUND_INTEGER. + * + * Returns: %TRUE if @idx was bound, %FALSE otherwise + * Since: 0.3.0 + */ +gboolean +xb_value_bindings_lookup_opcode (XbValueBindings *self, guint idx, XbOpcode *opcode_out) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + if (!xb_value_bindings_is_bound (self, idx)) + return FALSE; + + switch (_self->values[idx].kind) { + case KIND_TEXT: + xb_opcode_init (opcode_out, XB_OPCODE_KIND_BOUND_TEXT, + _self->values[idx].text, 0, NULL); + break; + case KIND_INTEGER: + xb_opcode_init (opcode_out, XB_OPCODE_KIND_BOUND_INTEGER, NULL, + _self->values[idx].integer, NULL); + break; + case KIND_NONE: + default: + g_assert_not_reached (); + } + + return TRUE; +} + +/** + * xb_value_bindings_copy_binding: + * @self: an #XbValueBindings to copy from + * @idx: 0-based index to look up the binding from in @self + * @dest: an #XbValueBindings to copy to + * @dest_idx: 0-based index to copy the binding to in @dest + * + * Copies the value bound at @idx on @self to @dest_idx on @dest. If no value is + * bound at @idx, @dest is not modified and %FALSE is returned. + * + * @dest must be initialised. If a binding already exists at @dest_idx, it will + * be overwritten. + * + * Returns: %TRUE if @idx was bound, %FALSE otherwise + * Since: 0.3.0 + */ +gboolean +xb_value_bindings_copy_binding (XbValueBindings *self, guint idx, XbValueBindings *dest, guint dest_idx) +{ + RealValueBindings *_self = (RealValueBindings *) self; + + if (!xb_value_bindings_is_bound (self, idx)) + return FALSE; + + switch (_self->values[idx].kind) { + case KIND_TEXT: + xb_value_bindings_bind_str (dest, dest_idx, _self->values[idx].text, NULL); + break; + case KIND_INTEGER: + xb_value_bindings_bind_val (dest, dest_idx, _self->values[idx].integer); + break; + case KIND_NONE: + default: + g_assert_not_reached (); + } + + return TRUE; +} diff -Nru libxmlb-0.1.15/src/xb-value-bindings.h libxmlb-0.3.6/src/xb-value-bindings.h --- libxmlb-0.1.15/src/xb-value-bindings.h 1970-01-01 00:00:00.000000000 +0000 +++ libxmlb-0.3.6/src/xb-value-bindings.h 2021-12-06 20:36:37.000000000 +0000 @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2020 Endless OS Foundation LLC + * + * Author: Philip Withnall + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include + +#include "xb-opcode.h" + +G_BEGIN_DECLS + +/** + * XbValueBindings: + * + * An opaque struct which contains values bound to a query. + * + * Since: 0.3.0 + */ +typedef struct { + /*< private >*/ + gint dummy0; + gpointer dummy1[2]; + gint dummy2; + gpointer dummy3[2]; + gint dummy4; + gpointer dummy5[2]; + gint dummy6; + gpointer dummy7[2]; + gpointer dummy8[3]; +} XbValueBindings; + +GType xb_value_bindings_get_type (void); + +/** + * XB_VALUE_BINDINGS_INIT: + * + * Static initialiser for #XbValueBindings so it can be used on the stack. + * + * Use it in association with g_auto(), to ensure the bindings are freed once + * finished with: + * |[ + * g_auto(XbValueBindings) bindings = XB_VALUE_BINDINGS_INIT (); + * + * xb_value_bindings_bind_str (&bindings, 0, "test", NULL); + * ]| + * + * Since: 0.3.0 + */ +#define XB_VALUE_BINDINGS_INIT() { 0, { NULL, NULL }, 0, { NULL, NULL }, 0, { NULL, NULL }, 0, { NULL, NULL }, { NULL, } } + +void xb_value_bindings_init (XbValueBindings *self); +void xb_value_bindings_clear (XbValueBindings *self); + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (XbValueBindings, xb_value_bindings_clear) + +XbValueBindings *xb_value_bindings_copy (XbValueBindings *self); +void xb_value_bindings_free (XbValueBindings *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (XbValueBindings, xb_value_bindings_free) + +gboolean xb_value_bindings_is_bound (XbValueBindings *self, + guint idx); +void xb_value_bindings_bind_str (XbValueBindings *self, + guint idx, + const gchar *str, + GDestroyNotify destroy_func); +void xb_value_bindings_bind_val (XbValueBindings *self, + guint idx, + guint32 val); + +gboolean xb_value_bindings_lookup_opcode(XbValueBindings *self, + guint idx, + XbOpcode *opcode_out); + +gboolean xb_value_bindings_copy_binding (XbValueBindings *self, + guint idx, + XbValueBindings *dest, + guint dest_idx); + +G_END_DECLS diff -Nru libxmlb-0.1.15/src/xmlb.h libxmlb-0.3.6/src/xmlb.h --- libxmlb-0.1.15/src/xmlb.h 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/src/xmlb.h 2021-12-06 20:36:37.000000000 +0000 @@ -16,12 +16,15 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #undef __LIBXMLB_H_INSIDE__ diff -Nru libxmlb-0.1.15/.travis.yml libxmlb-0.3.6/.travis.yml --- libxmlb-0.1.15/.travis.yml 2020-03-04 15:53:22.000000000 +0000 +++ libxmlb-0.3.6/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -language: c -sudo: required -dist: trusty - -services: - - docker - -env: - - OS=debian - - OS=fedora - - OS=fedora-w64 - -before_script: - - docker build -t libxmlb-${OS} -f contrib/ci/Dockerfile-${OS} . - -script: - - docker run -t -v `pwd`:/build libxmlb-${OS} ./contrib/ci/build-${OS}.sh - -jobs: - include: - - stage: abi - name: "Check for ABI breaks" - env: - - OS=fedora - before_script: - - docker build -t libxmlb-${OS} -f contrib/ci/Dockerfile-${OS} . - script: - - docker run -t -v `pwd`:/build libxmlb-${OS} ./contrib/ci/check-abi $(git describe --abbrev=0 --tags) $(git rev-parse HEAD)