diff --git a/.github/workflows/rake.yml b/.github/workflows/rake.yml index 4ce1372..8fd1e7f 100644 --- a/.github/workflows/rake.yml +++ b/.github/workflows/rake.yml @@ -20,11 +20,12 @@ jobs: before-setup-ruby: | set -e mkdir -p /tmp/tb && cd /tmp/tb - curl -sL https://api.github.com/repos/lutaml/taurus/tarball/v0.12.0 | tar xz --strip-components=1 + curl -sL https://api.github.com/repos/lutaml/taurus/tarball/main | tar xz --strip-components=1 cmake -B build -S . \ -DCMAKE_BUILD_TYPE=Release \ -DTAURUS_BUILD_SHARED=ON \ -DTAURUS_BUILD_STATIC=OFF \ + -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=ON \ -DBUILD_TESTING=OFF \ -DTAURUS_BUILD_CLI=OFF \ -DTAURUS_BUILD_BENCHMARKS=OFF \ @@ -32,13 +33,15 @@ jobs: -DTAURUS_ENABLE_UTF8PROC=OFF \ -DTAURUS_ENABLE_ICONV=OFF cmake --build build -j 4 - LIB=$(find build/src -type f -o -type l 2>/dev/null | grep -E 'libtaurus\.(dylib|so|dll)$' | head -1) + LIB=$(find build -type f -o -type l 2>/dev/null | grep -iE '(lib)?taurus\.(dylib|so|dll)$' | head -1) if [ -z "$LIB" ]; then echo "ERROR: libtaurus shared library not found after build" - find build/src -name 'libtaurus*' + find build -name '*taurus*' -type f exit 1 fi - echo "TAURUS_LIB_PATH=$(pwd)/$LIB" >> "$GITHUB_ENV" - echo "Installed libtaurus at: $(pwd)/$LIB" + # Convert to native path for Ruby FFI (Git-bash /tmp/ → C:\... on Windows) + LIBPATH=$(cygpath -w "$(pwd)/$LIB" 2>/dev/null || echo "$(pwd)/$LIB") + echo "TAURUS_LIB_PATH=$LIBPATH" >> "$GITHUB_ENV" + echo "Installed libtaurus at: $LIBPATH" secrets: pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }} diff --git a/lib/taurus/xml/ffi.rb b/lib/taurus/xml/ffi.rb index 990f3e9..56fbe82 100644 --- a/lib/taurus/xml/ffi.rb +++ b/lib/taurus/xml/ffi.rb @@ -124,6 +124,8 @@ class SerializeOptions < ::FFI::Struct [:taurus_node_ref], :int attach_function :taurus_node_compare, [:taurus_node_ref, :taurus_node_ref], :int + attach_function :taurus_node_traverse, + [:taurus_node_ref, :int, :pointer, :pointer], :int attach_function :taurus_text_node_get_content, [:taurus_node_ref], :string @@ -410,6 +412,9 @@ class SerializeOptions < ::FFI::Struct C14N_MODE_CANONICAL = 0 C14N_MODE_EXCLUSIVE = 1 + + TRAVERSE_PRE_ORDER = 0 + TRAVERSE_POST_ORDER = 1 end end end diff --git a/lib/taurus/xml/node.rb b/lib/taurus/xml/node.rb index e2adfe4..43e102e 100644 --- a/lib/taurus/xml/node.rb +++ b/lib/taurus/xml/node.rb @@ -162,7 +162,12 @@ def unlink # callback (libtaurus #273); the per-node FFI cost is the floor. def traverse return enum_for(:traverse) unless block_given? - walk_post_order(@c_ptr, @document) { |n| yield n } + callback = ::FFI::Function.new(:int, [:pointer, :pointer], blocking: true) do |node_ptr, _| + yield Taurus::XML::Node.wrap(node_ptr, @document) + 0 + end + Taurus::XML::FFI.taurus_node_traverse( + @c_ptr, Taurus::XML::FFI::TRAVERSE_POST_ORDER, callback, nil) end def path @@ -212,17 +217,5 @@ def as_element_or_self # calls and wrapping nodes directly. Saves one Array + one NodeSet # allocation per parent node. # - # Still pays ~2 FFI calls per visited node (first_child + next_sibling). - # Beating Nokogiri on this benchmark needs C-side traverse with a - # callback (libtaurus #273); the per-node FFI cost is the floor. - def walk_post_order(ptr, doc, &block) - child_ptr = Taurus::XML::FFI.taurus_node_first_child(ptr) - until child_ptr.nil? || child_ptr.null? - walk_post_order(child_ptr, doc, &block) - child_ptr = Taurus::XML::FFI.taurus_node_next_sibling(child_ptr) - end - yield Taurus::XML::Node.wrap(ptr, doc) - end - include Taurus::XML::Searchable end