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
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

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.



768
769
770
771
772
773
# File 'lib/leptris/xml/ffi.rb', line 768

def self.check_status(status)
  unless status == LEPTRIS_OK
    raise Leptris::XML::Error, status_message(status)
  end
  status
end

.fetch_children(c_ptr) ⇒ Object



828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
# File 'lib/leptris/xml/ffi.rb', line 828

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_result_nodes(result_ptr, count) ⇒ Object



858
859
860
861
862
863
864
865
866
867
# File 'lib/leptris/xml/ffi.rb', line 858

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].



871
872
873
874
875
876
# File 'lib/leptris/xml/ffi.rb', line 871

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.



758
759
760
761
762
# File 'lib/leptris/xml/ffi.rb', line 758

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_ints(count) ⇒ Object



902
903
904
905
906
907
908
909
910
# File 'lib/leptris/xml/ffi.rb', line 902

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



892
893
894
895
896
897
898
899
900
# File 'lib/leptris/xml/ffi.rb', line 892

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.



808
809
810
811
812
813
814
815
816
817
818
# File 'lib/leptris/xml/ffi.rb', line 808

def self.serialize_into_string(ffi_function, c_ptr, options)
  need = ffi_function.call(c_ptr, nil, 0, nil, options)
  return "" if need.zero?
  buffer = ::FFI::MemoryPointer.new(:char, need)
  begin
    ffi_function.call(c_ptr, buffer, need, nil, options)
    buffer.read_string
  ensure
    buffer.free
  end
end

.status_message(status) ⇒ Object



775
776
777
778
779
# File 'lib/leptris/xml/ffi.rb', line 775

def self.status_message(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.



786
787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'lib/leptris/xml/ffi.rb', line 786

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