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

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_message(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, 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



795
796
797
798
799
# File 'lib/leptris/xml/ffi.rb', line 795

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.



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