Module: Leptris::XML::FFI
- Extended by:
- FFI::Library
- Defined in:
- lib/leptris/xml/ffi.rb
Defined Under Namespace
Classes: ParseOptionsStruct, PullEventStruct, SAXHandler, SaxEventRecord, SerializeOptions
Constant Summary collapse
- SAX_EVENT_START_DOCUMENT =
0- SAX_EVENT_END_DOCUMENT =
1- SAX_EVENT_START_ELEMENT =
2- SAX_EVENT_END_ELEMENT =
3- SAX_EVENT_CHARACTERS =
4- SAX_EVENT_COMMENT =
5- SAX_EVENT_CDATA =
6- SAX_EVENT_PI =
7- SAX_EVENT_START_PREFIX =
8- SAX_EVENT_END_PREFIX =
9- SAX_EVENT_ERROR =
10- ITERPARSE_TOP_LEVEL =
0- ITERPARSE_FULL_DOCUMENT =
1- LEPTRIS_OK =
0- LEPTRIS_ERROR_MEMORY =
-1
- LEPTRIS_ERROR_PARSE =
-2
- LEPTRIS_ERROR_XPATH =
-3
- LEPTRIS_ERROR_NULL_ARG =
-4
- LEPTRIS_ERROR_INVALID_ARG =
-5
- LEPTRIS_ERROR_NOT_FOUND =
-6
- LEPTRIS_ERROR_IO =
-7
- LEPTRIS_ERROR_NOT_IMPLEMENTED =
-8
- XPATH_NODESET =
0- XPATH_BOOLEAN =
1- XPATH_NUMBER =
2- XPATH_STRING =
3- XPATH_NODE_ELEMENT =
0- XPATH_NODE_ATTRIBUTE =
1- XPATH_NODE_TEXT =
2- XPATH_NODE_OTHER =
3- NODE_ELEMENT =
0- NODE_TEXT =
1- NODE_COMMENT =
2- NODE_CDATA =
3- NODE_PI =
4- NODE_DOCTYPE =
5- NODE_ATTRIBUTE =
6- C14N_1_0 =
0- C14N_1_1 =
1- C14N_MODE_CANONICAL =
0- C14N_MODE_EXCLUSIVE =
1- TRAVERSE_PRE_ORDER =
0- TRAVERSE_POST_ORDER =
1- PULL_START_ELEMENT =
0- PULL_END_ELEMENT =
1- PULL_TEXT =
2- PULL_COMMENT =
3- PULL_CDATA =
4- PULL_PI =
5- PULL_END_DOCUMENT =
6- PULL_ERROR =
7- LEPTRIS_PARSE_DEFAULT =
0- LEPTRIS_PARSE_DROP_WS_TEXT =
1- LEPTRIS_PARSE_DTDATTR =
libleptris >= 1.9.8 (#606): apply DTD ATTLIST default (and #FIXED) attribute values at parse time — libxml2 XML_PARSE_DTDATTR opt-in parity.
2- XPATH_KIND_HINT =
XPath result-set batch (leptris_xpath_result_get_nodes_ex): the call fills out_kinds in the 4-value XPATH_NODE space (ELEMENT/ATTRIBUTE/TEXT/OTHER). ELEMENT is the only value that maps unambiguously — XPath's data model reports CDATA as TEXT, so a TEXT hint cannot distinguish Text from CDATA (different content getters) — everything else falls back to get_type in the wrapper. Returns [pointers, element_hints].
{ XPATH_NODE_ELEMENT => NODE_ELEMENT }.freeze
Class Method Summary collapse
-
.check_status(status) ⇒ Object
Single status seam: turns a C status code into a Ruby error, appending the library's last-error detail when present.
- .fetch_children(c_ptr) ⇒ Object
-
.fetch_element_children(el_ptr) ⇒ Object
Element-only child batch (the mirror of fetch_children over leptris_element_children): element receivers skip wrapping — and paying for — the text/comment children they will filter out anyway; every wrap carries the ELEMENT hint so the per-child get_type dispatch disappears too.
- .fetch_result_nodes(result_ptr, count) ⇒ Object
-
.parse_fragment_with_status(xml, document_ptr) ⇒ Object
Fragment parse with the status out-param read: returns [document-or-fragment pointer, status].
-
.read_owned_string(ptr) ⇒ Object
Reads an libleptris-owned char* result and frees it as one unit, so a call site can neither leak nor double-free.
-
.scratch_events(count) ⇒ Object
Pull-event staging block for leptris_pull_next_batch (the #589 bulk transport): LeptrisPullEvent records, stride from the struct layout that mirrors the ABI.
- .scratch_ints(count) ⇒ Object
- .scratch_pointers(count) ⇒ Object
-
.serialize_into_string(ffi_function, c_ptr, options) ⇒ Object
Caller-buffer serialization cycle: size query (buf=NULL), allocate exactly, fill, read.
- .status_message(status) ⇒ Object
-
.with_ns_set(hash) ⇒ Object
Namespace-binding lifecycle, written once: flatten the prefix/URI hash into the alternating CStringArray wire format, build the caller-owned set, yield it, free it under all outcomes.
Class Method Details
.check_status(status) ⇒ Object
Single status seam: turns a C status code into a Ruby error, appending the library's last-error detail when present. The detail is a library-global string, so it can only be treated as best-effort context.
788 789 790 791 792 793 |
# File 'lib/leptris/xml/ffi.rb', line 788 def self.check_status(status) unless status == LEPTRIS_OK raise Leptris::XML::Error, (status) end status end |
.fetch_children(c_ptr) ⇒ Object
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 |
# File 'lib/leptris/xml/ffi.rb', line 848 def self.fetch_children(c_ptr) scratch = (Thread.current[:leptris_scratch] ||= {}) buffer = scratch[:pointers] if buffer.nil? buffer = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL) end cap = buffer.size / PTR_BYTES copied = leptris_node_children(c_ptr, buffer, cap) if copied == cap total = leptris_node_children(c_ptr, nil, 0) if total > cap buffer.free buffer = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, total) copied = leptris_node_children(c_ptr, buffer, total) end end buffer.get_array_of_pointer(0, copied) end |
.fetch_element_children(el_ptr) ⇒ Object
Element-only child batch (the mirror of fetch_children over leptris_element_children): element receivers skip wrapping — and paying for — the text/comment children they will filter out anyway; every wrap carries the ELEMENT hint so the per-child get_type dispatch disappears too.
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 |
# File 'lib/leptris/xml/ffi.rb', line 874 def self.fetch_element_children(el_ptr) scratch = (Thread.current[:leptris_scratch] ||= {}) buffer = scratch[:pointers] if buffer.nil? buffer = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, FETCH_INITIAL) end cap = buffer.size / PTR_BYTES copied = leptris_element_children(el_ptr, buffer, cap) if copied == cap # leptris_element_children has NO count-only mode (NULL # returns 0, unlike leptris_node_children) — size the # regrow from the dedicated counter. total = leptris_element_child_count(el_ptr) if total > cap buffer.free buffer = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, total) copied = leptris_element_children(el_ptr, buffer, total) end end buffer.get_array_of_pointer(0, copied) end |
.fetch_result_nodes(result_ptr, count) ⇒ Object
907 908 909 910 911 912 913 914 915 916 |
# File 'lib/leptris/xml/ffi.rb', line 907 def self.fetch_result_nodes(result_ptr, count) return [[], nil] if count.zero? buffer = scratch_pointers(count) kinds = scratch_ints(count) copied = leptris_xpath_result_get_nodes_ex( result_ptr, buffer, kinds, count) hints = kinds.get_array_of_int(0, copied) .map { |k| XPATH_KIND_HINT[k] } [buffer.get_array_of_pointer(0, copied), hints] end |
.parse_fragment_with_status(xml, document_ptr) ⇒ Object
Fragment parse with the status out-param read: returns [document-or-fragment pointer, status].
920 921 922 923 924 925 |
# File 'lib/leptris/xml/ffi.rb', line 920 def self.parse_fragment_with_status(xml, document_ptr) status = ::FFI::MemoryPointer.new(:int) raw = leptris_parse_fragment( xml, xml.bytesize, document_ptr, status) [raw, status.read_int] end |
.read_owned_string(ptr) ⇒ Object
Reads an libleptris-owned char* result and frees it as one unit, so a call site can neither leak nor double-free.
778 779 780 781 782 |
# File 'lib/leptris/xml/ffi.rb', line 778 def self.read_owned_string(ptr) return "" if ptr.nil? || ptr.null? ptr.read_string.tap { leptris_free_string(ptr) } .force_encoding(Encoding::UTF_8) end |
.scratch_events(count) ⇒ Object
Pull-event staging block for leptris_pull_next_batch (the #589 bulk transport): LeptrisPullEvent records, stride from the struct layout that mirrors the ABI.
964 965 966 967 968 969 970 971 972 973 |
# File 'lib/leptris/xml/ffi.rb', line 964 def self.scratch_events(count) need = count * PullEventStruct.size scratch = (Thread.current[:leptris_scratch] ||= {}) ptr = scratch[:pull_events] if ptr.nil? || ptr.size < need ptr&.free ptr = scratch[:pull_events] = ::FFI::MemoryPointer.new(need) end ptr end |
.scratch_ints(count) ⇒ Object
951 952 953 954 955 956 957 958 959 |
# File 'lib/leptris/xml/ffi.rb', line 951 def self.scratch_ints(count) scratch = (Thread.current[:leptris_scratch] ||= {}) ptr = scratch[:ints] if ptr.nil? || ptr.size / INT_BYTES < count ptr&.free ptr = scratch[:ints] = ::FFI::MemoryPointer.new(:int, count) end ptr end |
.scratch_pointers(count) ⇒ Object
941 942 943 944 945 946 947 948 949 |
# File 'lib/leptris/xml/ffi.rb', line 941 def self.scratch_pointers(count) scratch = (Thread.current[:leptris_scratch] ||= {}) ptr = scratch[:pointers] if ptr.nil? || ptr.size / PTR_BYTES < count ptr&.free ptr = scratch[:pointers] = ::FFI::MemoryPointer.new(:pointer, count) end ptr end |
.serialize_into_string(ffi_function, c_ptr, options) ⇒ Object
Caller-buffer serialization cycle: size query (buf=NULL),
allocate exactly, fill, read. Since libleptris 1.9.0 the
pair reuses one serialization through the per-document
cache. ffi_function is leptris_document_serialize_into or
leptris_element_serialize_into. The buffer holds the
serialization + NUL and XML output is NUL-free, so the
bounded read is exact.
828 829 830 831 832 833 834 835 836 837 838 |
# File 'lib/leptris/xml/ffi.rb', line 828 def self.serialize_into_string(ffi_function, c_ptr, ) need = ffi_function.call(c_ptr, nil, 0, nil, ) return "" if need.zero? buffer = ::FFI::MemoryPointer.new(:char, need) begin ffi_function.call(c_ptr, buffer, need, nil, ) buffer.read_string ensure buffer.free end end |
.status_message(status) ⇒ Object
795 796 797 798 799 |
# File 'lib/leptris/xml/ffi.rb', line 795 def self.(status) base = leptris_status_string(status).to_s detail = leptris_last_error.to_s detail.empty? ? base : "#{base} (#{detail})" end |
.with_ns_set(hash) ⇒ Object
Namespace-binding lifecycle, written once: flatten the prefix/URI hash into the alternating CStringArray wire format, build the caller-owned set, yield it, free it under all outcomes. Every eval variant uses this; none of them knows how a set is born or dies.
806 807 808 809 810 811 812 813 814 815 816 817 818 819 |
# File 'lib/leptris/xml/ffi.rb', line 806 def self.with_ns_set(hash) flat = hash.flat_map { |prefix, uri| [prefix.to_s, uri.to_s] } buffer, _anchors = Leptris::XML::CStringArray.to_c(flat) set = leptris_xpath_ns_set_new_from_pairs(buffer, flat.length / 2) if set.null? raise Leptris::XML::Error, "leptris_xpath_ns_set_new_from_pairs failed" end begin yield set ensure leptris_xpath_ns_set_free(set) end end |