leptris 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec7aed2502125d89ff790ecd754cab242aec563ee58c45ceaf7e86cac4e96f85
4
- data.tar.gz: 6e9a72b02fc76907143756992afa7866415912bbe5fa9282d1800974cfde8b74
3
+ metadata.gz: 949fe73711448bd7857072004c75d6201b06829e9eb632124ae922a2fb7ff3f1
4
+ data.tar.gz: 259bbac7ce3e90389cce4f0e79dc979e7bc8ca844caa7745680ba352c010ba86
5
5
  SHA512:
6
- metadata.gz: 1e2a587372714a54f60e560bd175885f09fa3dd5ec3c0d620715c58e152f36651606e63770000f1a7c5162590299328967942093a96a45f9cac923db2c86d369
7
- data.tar.gz: 60aee00fff31eeba0e89a4b5d4affdf93dff22e5080db1093d17b05e6b029d94608f7934f5ec9c1c4eed3b7bfb3d905adb47f8e51ecc0645f8d52cb3e51da453
6
+ metadata.gz: cdab285d1fe462fcf7bc66681ebfbe1af74748ae99a8f0eebe1e502dfd72313b4429ac5943a18ebebd5706a308b135c33dfd7e8aab4bd313e383adcd91bb682f
7
+ data.tar.gz: 18240f20ed093b9e6434d0f9036ab93545b6af47237aa5ee72f616f3b10836148a3850fec5332fb2e44f416a53d25523d69f8bc4e4e1bb17c1f1181f16515f98
data/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.1] - 2026-08-22
9
+
10
+ ### Added
11
+
12
+ - **Precompiled platform gems** (emf2svg-ruby model): `rake compile`
13
+ vendors libleptris into `lib/`, and `rake gem:native:<platform>`
14
+ builds binary gems for x86_64/aarch64-linux (incl. musl, via Alpine
15
+ containers), x64-mingw32/ucrt + aarch64-mingw-ucrt, and
16
+ x86_64/arm64-darwin. `gem install leptris` resolves the platform
17
+ gem and works with no system libleptris and no env vars — the FFI
18
+ search order prefers the vendored library. The pure-Ruby gem
19
+ remains the fallback for other setups.
20
+ - `Element#prefix` — the element's own namespace prefix (nil-safe).
21
+ - `Element#each_attribute` — Enumerator over the v1.1.0
22
+ attribute-iteration face; `attributes`/`attribute_nodes`/`keys`/
23
+ `values` now use it (O(n) vs the O(n^2) index-re-walk API).
24
+ - Bindings for the v1.1.0 public surface: attribute-iteration face,
25
+ mixed-nodeset API (`xpath_result_node_kind/get_node/node_name/
26
+ node_value` + kind constants), `leptris_serialize_document`,
27
+ `leptris_document_get_dtd`, `leptris_element_prefix`,
28
+ `leptris_xpath_register_function` (Ruby sugar deferred until the
29
+ callback return-string ownership is verified).
30
+ - `NodeSet` fetches via `get_node` (all node kinds) instead of the
31
+ elements-only `result_get`.
32
+
33
+ ### Changed
34
+
35
+ - `release.yml` is standalone again, building the 8-platform matrix
36
+ plus musl containers and publishing all gems via OIDC trusted
37
+ publishing. Requires the rubygems.org trusted publisher to be
38
+ re-pointed at this workflow (filename `release.yml`, no
39
+ reusable-workflow fields).
40
+ - CI builds libleptris via `rake compile` (single version pin in the
41
+ Rakefile) instead of an inline cmake script.
42
+
43
+ ### Fixed
44
+
45
+ - Removes the resurrected `c14n.rb` (its deletion was lost in a
46
+ rebase-merge; nothing referenced it).
47
+
48
+ ### Known issues
49
+
50
+ - Mixed-kind XPath results misreport node types upstream
51
+ (leptris#477: enum-space collision; `node_name`/`node_value`
52
+ crash on text entries). Specs assert only the correct surface;
53
+ `NodeSet` carries a per-index fallback for the under-copying batch
54
+ accessor.
55
+
8
56
  ## [1.1.0] - 2026-08-22
9
57
 
10
58
  Lockstep with libleptris 1.1.0.
data/README.adoc CHANGED
@@ -397,7 +397,7 @@ bundle exec rspec spec/xml/xpath_spec.rb:42 # one example by line
397
397
  bundle exec rubocop # lint
398
398
  ----
399
399
 
400
- CI pins libleptris to a released tag (currently v1.1.0) and builds it
400
+ CI pins libleptris to a released tag (currently v1.1.1) and builds it
401
401
  from source on each runner; see `.github/workflows/build.yml`.
402
402
 
403
403
  == License
data/Rakefile CHANGED
@@ -4,4 +4,95 @@ require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
+
8
+ # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
+ # with .github/workflows/build.yml (which calls `rake compile`) and the
10
+ # CHANGELOG when libleptris releases.
11
+ LIBLEPTRIS_VERSION = "1.1.1"
12
+
13
+ CMAKE_FLAGS = %w[
14
+ -DCMAKE_BUILD_TYPE=Release
15
+ -DLEPTRIS_BUILD_SHARED=ON
16
+ -DLEPTRIS_BUILD_STATIC=OFF
17
+ -DBUILD_TESTING=OFF
18
+ -DLEPTRIS_BUILD_CLI=OFF
19
+ -DLEPTRIS_BUILD_BENCHMARKS=OFF
20
+ -DLEPTRIS_BUILD_MAN_PAGES=OFF
21
+ -DLEPTRIS_ENABLE_UTF8PROC=OFF
22
+ -DLEPTRIS_ENABLE_ICONV=OFF
23
+ ].freeze
24
+
25
+ desc "Build libleptris #{LIBLEPTRIS_VERSION} from its release tarball into lib/"
26
+ task :compile do
27
+ version = ENV.fetch("LIBLEPTRIS_VERSION", LIBLEPTRIS_VERSION)
28
+ build = File.expand_path("tmp/libleptris-#{version}", __dir__)
29
+ rm_rf(build)
30
+ mkdir_p(build)
31
+ url = "https://api.github.com/repos/leptris/leptris/tarball/v#{version}"
32
+ sh "curl -sL #{url} | tar xz -C #{build} --strip-components=1"
33
+ sh "cmake -B #{build}/build -S #{build} #{CMAKE_FLAGS.join(' ')}"
34
+ sh "cmake --build #{build}/build --config Release -j 4"
35
+ # Windows names the shared library leptris.dll (no "lib" prefix);
36
+ # vendoring under the uniform libleptris.* name keeps the FFI
37
+ # search order simple.
38
+ lib = Dir.glob("#{build}/build/**/libleptris.{dylib,so}").first ||
39
+ Dir.glob("#{build}/build/**/leptris.dll").first
40
+ raise "libleptris shared library not found after build" unless lib
41
+ ext = File.extname(lib)
42
+ cp(lib, "lib/libleptris#{ext}")
43
+ puts "Vendored #{File.basename(lib)} as lib/libleptris#{ext}"
44
+ end
45
+
46
+ task spec: :compile unless ENV.key?("LEPTRIS_LIB_PATH")
47
+
7
48
  task default: :spec
49
+
50
+ require "rubygems/package_task"
51
+
52
+ desc "Build the pure-Ruby gem"
53
+ task "gem:native:any" do
54
+ sh "rake platform:any gem"
55
+ end
56
+
57
+ desc "Define the gem task to build the pure-Ruby gem"
58
+ task "platform:any" do
59
+ spec = Gem::Specification::load("leptris.gemspec").dup
60
+ task = Gem::PackageTask.new(spec)
61
+ task.define
62
+ end
63
+
64
+ platforms = [
65
+ "x64-mingw32",
66
+ "x64-mingw-ucrt",
67
+ "aarch64-mingw-ucrt",
68
+ "x86_64-linux",
69
+ "x86_64-linux-musl",
70
+ "aarch64-linux",
71
+ "aarch64-linux-musl",
72
+ "x86_64-darwin",
73
+ "arm64-darwin",
74
+ ]
75
+
76
+ platforms.each do |platform|
77
+ desc "Build pre-compiled gem for the #{platform} platform"
78
+ task "gem:native:#{platform}" do
79
+ sh "rake compile platform:#{platform} gem"
80
+ end
81
+
82
+ desc "Define the gem task to build on the #{platform} platform (binary gem)"
83
+ task "platform:#{platform}" do
84
+ spec = Gem::Specification::load("leptris.gemspec").dup
85
+ spec.platform = Gem::Platform.new(platform)
86
+ spec.files += Dir.glob("lib/libleptris.{dll,so,dylib}")
87
+ task = Gem::PackageTask.new(spec)
88
+ task.define
89
+ end
90
+ end
91
+
92
+ require "rake/clean"
93
+
94
+ CLOBBER.include("pkg")
95
+ CLEAN.include("tmp",
96
+ "lib/libleptris.dll",
97
+ "lib/libleptris.dylib",
98
+ "lib/libleptris.so")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.1.0"
4
+ VERSION = "1.1.2"
5
5
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Lightweight attribute wrapper. libleptris v0.4.4 doesn't expose per-attribute
4
- # pointers (no LeptrisAttribute accessors in the public API), so an Attr is a
5
- # (name, value, parent_element) triple. Mutation goes through the parent
6
- # element via #[]. Once upstream adds attribute-level accessors, this can
7
- # become a pointer-backed wrapper.
3
+ # Lightweight attribute wrapper: a (name, value, parent_element) triple
4
+ # materialized from the attribute-iteration face. Mutation goes through
5
+ # the parent element via #[].
8
6
 
9
7
  class Leptris::XML::Attr
10
8
  attr_reader :name, :value, :element
@@ -48,34 +48,43 @@ class Leptris::XML::Element < Leptris::XML::Node
48
48
  end
49
49
  alias_method :delete, :remove_attribute
50
50
 
51
+ # Iterates the element's attributes via the v1.1.0 linked-list face
52
+ # (one FFI call per attribute; the name_at/value_at indexing API
53
+ # re-walks the list per index, making it O(n^2) per element).
54
+ def each_attribute
55
+ return enum_for(:each_attribute) unless block_given?
56
+ attr = Leptris::XML::FFI.leptris_element_first_attribute(@c_ptr)
57
+ until attr.nil? || attr.null?
58
+ name = Leptris::XML::FFI.leptris_attribute_get_name(attr)
59
+ value = Leptris::XML::FFI.leptris_attribute_get_value(@c_ptr, attr)
60
+ yield Leptris::XML::Attr.new(name, value, self)
61
+ attr = Leptris::XML::FFI.leptris_attribute_next(attr)
62
+ end
63
+ self
64
+ end
65
+
51
66
  def keys
52
- count = Leptris::XML::FFI.leptris_element_attribute_count(@c_ptr)
53
- count.times.map { |i| Leptris::XML::FFI.leptris_element_attribute_name_at(@c_ptr, i) }
67
+ each_attribute.to_a.map(&:name)
54
68
  end
55
69
 
56
70
  def values
57
- count = Leptris::XML::FFI.leptris_element_attribute_count(@c_ptr)
58
- count.times.map { |i| Leptris::XML::FFI.leptris_element_attribute_value_at(@c_ptr, i) }
71
+ each_attribute.to_a.map(&:value)
59
72
  end
60
73
 
61
74
  def attributes
62
- count = Leptris::XML::FFI.leptris_element_attribute_count(@c_ptr)
63
75
  result = {}
64
- count.times do |i|
65
- name = Leptris::XML::FFI.leptris_element_attribute_name_at(@c_ptr, i)
66
- value = Leptris::XML::FFI.leptris_element_attribute_value_at(@c_ptr, i)
67
- result[name] = Leptris::XML::Attr.new(name, value, self)
68
- end
76
+ each_attribute { |attr| result[attr.name] = attr }
69
77
  result
70
78
  end
71
79
 
72
80
  def attribute_nodes
73
- count = Leptris::XML::FFI.leptris_element_attribute_count(@c_ptr)
74
- count.times.map do |i|
75
- name = Leptris::XML::FFI.leptris_element_attribute_name_at(@c_ptr, i)
76
- value = Leptris::XML::FFI.leptris_element_attribute_value_at(@c_ptr, i)
77
- Leptris::XML::Attr.new(name, value, self)
78
- end
81
+ each_attribute.to_a
82
+ end
83
+
84
+ # The element's own namespace prefix (e.g. "foo" for <foo:child/>),
85
+ # or nil when the element has none.
86
+ def prefix
87
+ Leptris::XML::FFI.leptris_element_prefix(@c_ptr)
79
88
  end
80
89
 
81
90
  def prepend_child(node)
@@ -11,6 +11,7 @@ module Leptris
11
11
  ENV["LEPTRIS_LIB_PATH"],
12
12
  File.expand_path("../../libleptris.dylib", __dir__),
13
13
  File.expand_path("../../libleptris.so", __dir__),
14
+ File.expand_path("../../libleptris.dll", __dir__),
14
15
  "/usr/local/lib/libleptris.dylib",
15
16
  "/usr/local/lib/libleptris.so",
16
17
  "leptris",
@@ -186,6 +187,16 @@ module Leptris
186
187
  [:leptris_element, :string, :int], :int
187
188
  attach_function :leptris_element_has_attribute,
188
189
  [:leptris_element, :string], :int
190
+ attach_function :leptris_element_first_attribute,
191
+ [:leptris_element], :leptris_attribute
192
+ attach_function :leptris_attribute_next,
193
+ [:leptris_attribute], :leptris_attribute
194
+ attach_function :leptris_attribute_get_name,
195
+ [:leptris_attribute], :string
196
+ attach_function :leptris_attribute_get_value,
197
+ [:leptris_element, :leptris_attribute], :string
198
+ attach_function :leptris_element_prefix,
199
+ [:leptris_element], :string
189
200
  attach_function :leptris_element_attribute_count,
190
201
  [:leptris_element], :size_t
191
202
  attach_function :leptris_element_attribute_name_at,
@@ -310,6 +321,22 @@ module Leptris
310
321
  [:leptris_xpath_result], :size_t
311
322
  attach_function :leptris_xpath_result_get,
312
323
  [:leptris_xpath_result, :size_t], :leptris_element
324
+ # Mixed nodesets (v1.1.0): unlike result_get (elements only),
325
+ # get_node returns the node whatever its kind; node_kind reports
326
+ # which (element / synthetic attribute / text / other).
327
+ attach_function :leptris_xpath_result_node_kind,
328
+ [:leptris_xpath_result, :size_t], :int
329
+ attach_function :leptris_xpath_result_get_node,
330
+ [:leptris_xpath_result, :size_t], :leptris_node_ref
331
+ attach_function :leptris_xpath_result_node_name,
332
+ [:leptris_xpath_result, :size_t], :string
333
+ attach_function :leptris_xpath_result_node_value,
334
+ [:leptris_xpath_result, :size_t], :string
335
+ # Document-scoped custom XPath functions. The callback receives
336
+ # (const char* const* args, int argc, void* user_data) and returns
337
+ # a heap string the library frees.
338
+ attach_function :leptris_xpath_register_function,
339
+ [:leptris_document, :string, :pointer, :pointer], :leptris_status
313
340
  attach_function :leptris_xpath_result_get_nodes,
314
341
  [:leptris_xpath_result, :pointer, :size_t], :size_t
315
342
  attach_function :leptris_xpath_result_boolean,
@@ -347,6 +374,12 @@ module Leptris
347
374
 
348
375
  attach_function :leptris_document_serialize,
349
376
  [:leptris_document, :pointer], :pointer
377
+ # Legacy simple serializer, declared retroactively in v1.1.0.
378
+ # Prefer leptris_document_serialize; bound for API completeness.
379
+ attach_function :leptris_serialize_document,
380
+ [:leptris_document], :pointer
381
+ attach_function :leptris_document_get_dtd,
382
+ [:leptris_document], :pointer
350
383
  attach_function :leptris_element_serialize,
351
384
  [:leptris_element, :pointer], :pointer
352
385
  attach_function :leptris_document_save_file,
@@ -398,6 +431,11 @@ module Leptris
398
431
  XPATH_NUMBER = 2
399
432
  XPATH_STRING = 3
400
433
 
434
+ XPATH_NODE_ELEMENT = 0
435
+ XPATH_NODE_ATTRIBUTE = 1
436
+ XPATH_NODE_TEXT = 2
437
+ XPATH_NODE_OTHER = 3
438
+
401
439
  NODE_ELEMENT = 0
402
440
  NODE_TEXT = 1
403
441
  NODE_COMMENT = 2
@@ -52,7 +52,7 @@ class Leptris::XML::NodeSet
52
52
  def [](idx)
53
53
  if @result_ptr
54
54
  return nil if idx < 0 || idx >= length
55
- ptr = Leptris::XML::FFI.leptris_xpath_result_get(@result_ptr, idx)
55
+ ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(@result_ptr, idx)
56
56
  return nil if ptr.null?
57
57
  Leptris::XML::Node.wrap(ptr, @document)
58
58
  else
@@ -77,6 +77,13 @@ class Leptris::XML::NodeSet
77
77
  next if ptr.null?
78
78
  yield Leptris::XML::Node.wrap(ptr, @document)
79
79
  end
80
+ # The batch accessor under-copies mixed-kind results (upstream
81
+ # leptris#477); fall back to per-index fetch for the remainder.
82
+ (copied...n).each do |i|
83
+ ptr = Leptris::XML::FFI.leptris_xpath_result_get_node(@result_ptr, i)
84
+ next if ptr.null?
85
+ yield Leptris::XML::Node.wrap(ptr, @document)
86
+ end
80
87
  ensure
81
88
  buf.free
82
89
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-22 00:00:00.000000000 Z
11
+ date: 2026-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -112,7 +112,6 @@ files:
112
112
  - lib/leptris/version.rb
113
113
  - lib/leptris/xml.rb
114
114
  - lib/leptris/xml/attr.rb
115
- - lib/leptris/xml/c14n.rb
116
115
  - lib/leptris/xml/c_string_array.rb
117
116
  - lib/leptris/xml/cdata.rb
118
117
  - lib/leptris/xml/comment.rb
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Leptris
4
- module XML
5
- # @api private
6
- #
7
- # Builds a NULL-terminated `const char**` from a Ruby array of strings,
8
- # suitable for passing to leptris_c14n_canonicalize_ex's
9
- # `inclusive_ns_prefixes` argument. Returns [pointer, anchor] where
10
- # anchor is the underlying MemoryPointer that the caller must keep
11
- # alive (e.g. via local variable) for the duration of the FFI call.
12
- def self.c14n_build_ns_pointer(inclusive_namespaces)
13
- return [nil, nil] if inclusive_namespaces.nil? || inclusive_namespaces.empty?
14
- strs = Array(inclusive_namespaces).map(&:to_s)
15
- ptr_size = ::FFI.type_size(:pointer)
16
- buffer = ::FFI::MemoryPointer.new(:pointer, strs.size + 1)
17
- anchors = strs.map { |s| ::FFI::MemoryPointer.from_string(s) }
18
- anchors.each_with_index { |p, i| buffer.put_pointer(i * ptr_size, p) }
19
- buffer.put_pointer(strs.size * ptr_size, nil)
20
- [buffer, anchors]
21
- end
22
- end
23
- end