Class: Taurus::XML::Document

Inherits:
Object
  • Object
show all
Includes:
Searchable
Defined in:
lib/taurus/xml/document.rb

Defined Under Namespace

Classes: Freed

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Searchable

#at, #at_css, #at_xpath, #css, #search, #xpath

Constructor Details

#initialize(c_ptr = nil, freed = Freed.new(:alive)) ⇒ Document

Returns a new instance of Document.



17
18
19
20
21
22
23
24
25
# File 'lib/taurus/xml/document.rb', line 17

def initialize(c_ptr = nil, freed = Freed.new(:alive))
  @c_ptr = c_ptr
  @freed = freed
  # Per-document weak-ref cache for Node wrappers, keyed on c_ptr
  # address. Eliminates re-allocation when the same node is accessed
  # repeatedly (e.g. via children, siblings, multiple xpath calls).
  # Dies with the Document — no stale entries pointing at freed memory.
  @wrapper_cache = ObjectSpace::WeakMap.new
end

Instance Attribute Details

#c_ptrObject (readonly)

Returns the value of attribute c_ptr.



6
7
8
# File 'lib/taurus/xml/document.rb', line 6

def c_ptr
  @c_ptr
end

#wrapper_cacheObject (readonly)

Returns the value of attribute wrapper_cache.



6
7
8
# File 'lib/taurus/xml/document.rb', line 6

def wrapper_cache
  @wrapper_cache
end

Class Method Details

.parse(xml_or_io) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/taurus/xml/document.rb', line 27

def self.parse(xml_or_io)
  xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
  if xml.empty?
    raise Taurus::XML::ParseError, "empty input"
  end
  status_ptr = ::FFI::MemoryPointer.new(:int)
  raw = Taurus::XML::FFI.taurus_parse_string(xml, xml.bytesize, status_ptr)
  if raw.null?
    status = status_ptr.read_int
    raise Taurus::XML::ParseError,
      "taurus_parse_string failed (status=#{status})"
  end
  wrap(raw)
end

.parse_file(path) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/taurus/xml/document.rb', line 42

def self.parse_file(path)
  status_ptr = ::FFI::MemoryPointer.new(:int)
  raw = Taurus::XML::FFI.taurus_parse_file(path, status_ptr)
  if raw.null?
    status = status_ptr.read_int
    raise Taurus::XML::ParseError,
      "taurus_parse_file failed (status=#{status})"
  end
  wrap(raw)
end

.wrap(raw_address) ⇒ Object

Convert a raw TaurusDocument pointer into a Ruby Document with safe GC lifetime management. The finalizer captures the raw address integer (not the Document or Pointer object — those would prevent GC) and shares a one-shot flag with the instance so explicit #free and the GC finalizer can never both call taurus_document_free on the same address.



59
60
61
62
63
64
65
66
# File 'lib/taurus/xml/document.rb', line 59

def self.wrap(raw_address)
  addr = raw_address.is_a?(::FFI::Pointer) ? raw_address.address : raw_address
  ptr = ::FFI::Pointer.new(addr)
  freed = Freed.new(:alive)
  doc = new(ptr, freed)
  ObjectSpace.define_finalizer(doc, finalizer(addr, freed))
  doc
end

Instance Method Details

#canonicalize(version = Taurus::XML::FFI::C14N_1_0, inclusive_namespaces = nil, with_comments: false, exclusive: false, mode: nil) ⇒ Object Also known as: c14n



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/taurus/xml/document.rb', line 155

def canonicalize(version = Taurus::XML::FFI::C14N_1_0,
                 inclusive_namespaces = nil,
                 with_comments: false,
                 exclusive: false,
                 mode: nil)
  raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
  return "" if @c_ptr.nil?
  resolved_mode = mode || (exclusive ? Taurus::XML::FFI::C14N_MODE_EXCLUSIVE
                                     : Taurus::XML::FFI::C14N_MODE_CANONICAL)
  ns_ptr, _anchor = Taurus::XML.c14n_build_ns_pointer(inclusive_namespaces)
  flags = with_comments ? 1 : 0
  str_ptr = Taurus::XML::FFI.taurus_c14n_canonicalize_ex(
    @c_ptr, version, resolved_mode, ns_ptr, flags)
  return "" if str_ptr.null?
  str_ptr.read_string.tap { Taurus::XML::FFI.taurus_free_string(str_ptr) }
end

#create_cdata(content) ⇒ Object

Raises:



103
104
105
106
107
# File 'lib/taurus/xml/document.rb', line 103

def create_cdata(content)
  ptr = Taurus::XML::FFI.taurus_cdata_node_create(@c_ptr, content.to_s)
  raise Taurus::XML::Error, "taurus_cdata_node_create failed" if ptr.null?
  Taurus::XML::CDATA.new(ptr, self)
end

#create_comment(content) ⇒ Object

Raises:



97
98
99
100
101
# File 'lib/taurus/xml/document.rb', line 97

def create_comment(content)
  ptr = Taurus::XML::FFI.taurus_comment_node_create(@c_ptr, content.to_s)
  raise Taurus::XML::Error, "taurus_comment_node_create failed" if ptr.null?
  Taurus::XML::Comment.new(ptr, self)
end

#create_element(name) ⇒ Object

Raises:



85
86
87
88
89
# File 'lib/taurus/xml/document.rb', line 85

def create_element(name)
  ptr = Taurus::XML::FFI.taurus_element_create(@c_ptr, name)
  raise Taurus::XML::Error, "taurus_element_create failed" if ptr.null?
  Taurus::XML::Element.new(ptr, self)
end

#create_processing_instruction(target, data = "") ⇒ Object

Raises:



109
110
111
112
113
# File 'lib/taurus/xml/document.rb', line 109

def create_processing_instruction(target, data = "")
  ptr = Taurus::XML::FFI.taurus_pi_node_create(@c_ptr, target.to_s, data.to_s)
  raise Taurus::XML::Error, "taurus_pi_node_create failed" if ptr.null?
  Taurus::XML::ProcessingInstruction.new(ptr, self)
end

#create_text_node(content) ⇒ Object

Raises:



91
92
93
94
95
# File 'lib/taurus/xml/document.rb', line 91

def create_text_node(content)
  ptr = Taurus::XML::FFI.taurus_text_node_create(@c_ptr, content.to_s)
  raise Taurus::XML::Error, "taurus_text_node_create failed" if ptr.null?
  Taurus::XML::Text.new(ptr, self)
end

#doctypeObject Also known as: internal_subset



126
127
128
129
130
# File 'lib/taurus/xml/document.rb', line 126

def doctype
  ptr = Taurus::XML::FFI.taurus_document_internal_subset(@c_ptr)
  return nil if ptr.null?
  Taurus::XML::DocType.new(ptr, self)
end

#documentObject



181
# File 'lib/taurus/xml/document.rb', line 181

def document; self; end

#dupObject Also known as: clone

Raises:



119
120
121
122
123
# File 'lib/taurus/xml/document.rb', line 119

def dup
  raw = Taurus::XML::FFI.taurus_document_copy(@c_ptr)
  raise Taurus::XML::Error, "taurus_document_copy failed" if raw.null?
  self.class.wrap(raw)
end

#encodingObject



182
183
184
185
# File 'lib/taurus/xml/document.rb', line 182

def encoding
  return nil if @c_ptr.nil?
  Taurus::XML::FFI.taurus_document_encoding(@c_ptr)
end

#fragment(markup) ⇒ Object



115
116
117
# File 'lib/taurus/xml/document.rb', line 115

def fragment(markup)
  Taurus::XML::DocumentFragment.parse(markup, self)
end

#freeObject



173
174
175
176
177
178
# File 'lib/taurus/xml/document.rb', line 173

def free
  return if @freed.state == :freed
  @freed.state = :freed
  Taurus::XML::FFI.taurus_document_free(@c_ptr) unless @c_ptr.nil?
  @c_ptr = nil
end

#nameObject



180
# File 'lib/taurus/xml/document.rb', line 180

def name; "document"; end

#rootObject



77
78
79
80
81
82
83
# File 'lib/taurus/xml/document.rb', line 77

def root
  raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
  return nil if @c_ptr.nil?
  ptr = Taurus::XML::FFI.taurus_document_root(@c_ptr)
  return nil if ptr.null?
  Taurus::XML::Element.new(ptr, self)
end

#save(path, **opts) ⇒ Object

Raises:



144
145
146
147
148
149
150
151
152
153
# File 'lib/taurus/xml/document.rb', line 144

def save(path, **opts)
  opts_struct, enc_ptr = build_serialize_options(
    indent: opts.fetch(:indent, 0),
    no_decl: opts.fetch(:no_decl, false),
    encoding: opts[:encoding])
  status = Taurus::XML::FFI.taurus_document_save_file(@c_ptr, path, opts_struct.pointer)
  raise Taurus::XML::Error,
    Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
  self
end

#to_xml(indent: 0, no_decl: false, encoding: nil) ⇒ Object Also known as: to_s, serialize



133
134
135
136
137
138
139
140
# File 'lib/taurus/xml/document.rb', line 133

def to_xml(indent: 0, no_decl: false, encoding: nil)
  raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
  return "" if @c_ptr.nil?
  opts, enc_ptr = build_serialize_options(indent: indent, no_decl: no_decl, encoding: encoding)
  str_ptr = Taurus::XML::FFI.taurus_document_serialize(@c_ptr, opts.pointer)
  return "" if str_ptr.null?
  str_ptr.read_string.tap { |s| Taurus::XML::FFI.taurus_free_string(str_ptr) }
end