Class: Moxml::Context
- Inherits:
-
Object
- Object
- Moxml::Context
- Defined in:
- lib/moxml/context.rb
Constant Summary collapse
- WEAK_WRAPPERS =
Opal's default runtime excludes ObjectSpace entirely; the weak registry needs WeakMap, so the strong Hash (with its wholesale-clear valve) covers that platform.
!defined?(ObjectSpace::WeakMap).nil?
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Instance Method Summary collapse
- #build(&block) ⇒ Object
- #bump_namespace_scope_generation ⇒ Object
- #create_document(native_doc = nil) ⇒ Object
-
#default_element_serialize_options ⇒ Object
The argless element.to_xml form resolves every option statically (no declaration for non-document nodes, context defaults for the rest) — this prebuilt frozen hash makes that path allocation-free.
-
#default_serialize_options ⇒ Object
Frozen serialization defaults, memoized per context — Node#to_xml merged these from live config reads on every call (bulk element serialization pays a hash build + 4 chain derefs per element).
- #entity_registry ⇒ Object
-
#initialize(adapter = nil) ⇒ Context
constructor
A new instance of Context.
- #iterparse(xml, mode: :top_level, &block) ⇒ Object
- #iterparse_file(path, mode: :top_level, &block) ⇒ Object
-
#materialize(xml, options = {}, &block) ⇒ Object
Parse then flatten in one call — see Moxml::Materializer (issue #132).
-
#materialize_fields(xml, options = {}, &block) ⇒ Object
Parse then stream the zero-allocation field form (issue #143) — see Moxml::Materializer.
- #namespace_scope_generation ⇒ Object
- #parse(xml, options = {}) ⇒ Object
-
#parse_fragment(xml) ⇒ Object
Streaming incremental parse (leptris): yields each completed element while parsing — :top_level yields the root's children, :full_document every element in completion (post-order) order.
-
#parse_html(html, options = {}) ⇒ Object
Tolerant HTML4/5 parsing into the standard DOM: implied end tags, void elements, case-insensitive lowercased names, the HTML named-entity table, synthesized html/head/body.
- #register_wrapper(native, wrapper) ⇒ Object
-
#sax_parse(xml, handler = nil) {|block| ... } ⇒ void
Parse XML using SAX (event-driven) parsing.
-
#unregister_wrapper(native) ⇒ Object
WeakMap has no delete — a nil value tombstones the entry (reads as a miss) and dies with the native like any other.
- #wrapper_for(native) ⇒ Object
Constructor Details
#initialize(adapter = nil) ⇒ Context
Returns a new instance of Context.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/moxml/context.rb', line 11 def initialize(adapter = nil) @config = Config.new(adapter) # Bumped on any namespace-scope mutation; Element scope caches # compare against it so every wrapper sees changes without # needing wrapper identity. @namespace_scope_generation = 0 # Native → wrapper identity map: repeated traversals hand back # the same wrapper instead of allocating a fresh one per # access. Keyed by object identity; re-keyed by # Node#refresh_native! when an adapter swaps a native. # WeakMap where available: entries die with their native — # parse-and-drop workloads release wrappers, natives, and (via # the binding's finalizer) the C subtrees, instead of pinning # up to the old 65,536 wholesale-clear valve which also # destroyed identity for live wrappers when it fired. @wrappers = WEAK_WRAPPERS ? ObjectSpace::WeakMap.new : {}.compare_by_identity # Static per context (the registry flavor never changes). @wrappers_strong = !WEAK_WRAPPERS # Resolved on first registration — the adapter-resolution # chain ran per mint and the decision never changes for a # context's adapter (re-configuring an adapter mid-context is # unsupported: minted wrappers already carry the old one). @register_wrappers = nil end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
9 10 11 |
# File 'lib/moxml/context.rb', line 9 def config @config end |
Instance Method Details
#build(&block) ⇒ Object
193 194 195 |
# File 'lib/moxml/context.rb', line 193 def build(&block) Builder.new(self).build(&block) end |
#bump_namespace_scope_generation ⇒ Object
71 72 73 |
# File 'lib/moxml/context.rb', line 71 def bump_namespace_scope_generation @namespace_scope_generation += 1 end |
#create_document(native_doc = nil) ⇒ Object
79 80 81 |
# File 'lib/moxml/context.rb', line 79 def create_document(native_doc = nil) Wrappers::Document.new(config.adapter.create_document(native_doc), self) end |
#default_element_serialize_options ⇒ Object
The argless element.to_xml form resolves every option statically (no declaration for non-document nodes, context defaults for the rest) — this prebuilt frozen hash makes that path allocation-free.
212 213 214 215 |
# File 'lib/moxml/context.rb', line 212 def @default_element_serialize_options ||= .merge(no_declaration: true).freeze end |
#default_serialize_options ⇒ Object
Frozen serialization defaults, memoized per context — Node#to_xml merged these from live config reads on every call (bulk element serialization pays a hash build + 4 chain derefs per element).
200 201 202 203 204 205 206 207 |
# File 'lib/moxml/context.rb', line 200 def @default_serialize_options ||= { encoding: config.default_encoding, indent: config.default_indent, line_ending: config.default_line_ending, expand_empty: true, }.freeze end |
#entity_registry ⇒ Object
75 76 77 |
# File 'lib/moxml/context.rb', line 75 def entity_registry @entity_registry ||= build_entity_registry end |
#iterparse(xml, mode: :top_level, &block) ⇒ Object
135 136 137 |
# File 'lib/moxml/context.rb', line 135 def iterparse(xml, mode: :top_level, &block) config.adapter.iterparse(xml, mode, self, &block) end |
#iterparse_file(path, mode: :top_level, &block) ⇒ Object
139 140 141 |
# File 'lib/moxml/context.rb', line 139 def iterparse_file(path, mode: :top_level, &block) config.adapter.iterparse_file(path, mode, self, &block) end |
#materialize(xml, options = {}, &block) ⇒ Object
Parse then flatten in one call — see Moxml::Materializer (issue #132). Yields records; returns an Enumerator when no block is given.
146 147 148 |
# File 'lib/moxml/context.rb', line 146 def materialize(xml, = {}, &block) parse(xml, ).materialize(&block) end |
#materialize_fields(xml, options = {}, &block) ⇒ Object
Parse then stream the zero-allocation field form (issue #143) — see Moxml::Materializer.
152 153 154 155 156 |
# File 'lib/moxml/context.rb', line 152 def materialize_fields(xml, = {}, &block) raise ArgumentError, "materialize_fields requires a block" unless block parse(xml, ).materialize_fields(&block) end |
#namespace_scope_generation ⇒ Object
67 68 69 |
# File 'lib/moxml/context.rb', line 67 def namespace_scope_generation @namespace_scope_generation end |
#parse(xml, options = {}) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/moxml/context.rb', line 83 def parse(xml, = {}) xml_string = if xml.is_a?(String) xml else xml.read.tap do xml.rewind if xml.is_a?(IO) || xml.is_a?(StringIO) end end # Allocation-free declaration sniff: String#strip would copy the # whole buffer just to test its prefix. has_declaration = xml_string.match?(/\A[ \t\r\n\f\v\0]*<\?xml/) # Parse with adapter, passing self (context) so adapter can use our config = .merge() doc = config.adapter.parse(xml_string, , self) # Set declaration flag on Document wrapper (proper OOP) doc.has_xml_declaration = has_declaration if doc.is_a?(Document) doc end |
#parse_fragment(xml) ⇒ Object
Streaming incremental parse (leptris): yields each completed element while parsing — :top_level yields the root's children, :full_document every element in completion (post-order) order. Memory stays bounded by the largest subtree, not the document (iterparse_file streams the file C-side). Yielded elements are parentless and valid only inside the block. Parse an XML fragment: returns its top-level nodes as an Array of wrappers, uniformly across adapters (issue #188 — fragment: true was Nokogiri-only). Engines without a fragment node type parse inside a synthetic root; the fragment's nodes are the result either way.
125 126 127 128 129 130 131 132 133 |
# File 'lib/moxml/context.rb', line 125 def parse_fragment(xml) adapter = config.adapter adapter.parse_fragment(xml, self).map do |node| # Adapters that tie a parent_node lifetime chain (#245) # return wrapped nodes — pass them through untouched; # re-wrapping would race the weak wrapper map. node.is_a?(Moxml::Node) ? node : Moxml::Node.wrap(node, self) end end |
#parse_html(html, options = {}) ⇒ Object
Tolerant HTML4/5 parsing into the standard DOM: implied end tags, void elements, case-insensitive lowercased names, the HTML named-entity table, synthesized html/head/body. Supported by adapters with an engine HTML mode (leptris >= 1.9.80, nokogiri); others raise Moxml::AdapterError.
110 111 112 |
# File 'lib/moxml/context.rb', line 110 def parse_html(html, = {}) config.adapter.parse_html(html, , self) end |
#register_wrapper(native, wrapper) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/moxml/context.rb', line 40 def register_wrapper(native, wrapper) # Adapter opt-in, resolved once (see initialize): adapters # whose natives are recreated per access (libxml mints fresh # Ruby objects for the same C node) opt out so the map does # not accumulate dead entries; the default is opt-in. if @register_wrappers.nil? @register_wrappers = @config&.adapter&.wrappers_recyclable? != false end return unless @register_wrappers # The strong fallback (Opal) needs the wholesale-clear valve; # the WeakMap registry is self-cleaning. @wrappers.clear if @wrappers_strong && @wrappers.size >= 65_536 @wrappers[native] = wrapper end |
#sax_parse(xml, handler = nil) {|block| ... } ⇒ void
This method returns an undefined value.
Parse XML using SAX (event-driven) parsing
SAX parsing is memory-efficient and suitable for large XML files. Provide either a handler object or a block with DSL.
179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/moxml/context.rb', line 179 def sax_parse(xml, handler = nil, &block) # Create block handler if block given handler ||= SAX::BlockHandler.new(&block) if block # Validate handler raise ArgumentError, "Handler or block required" unless handler unless handler.is_a?(SAX::Handler) raise ArgumentError, "Handler must inherit from Moxml::SAX::Handler" end # Delegate to adapter config.adapter.sax_parse(xml, handler) end |
#unregister_wrapper(native) ⇒ Object
WeakMap has no delete — a nil value tombstones the entry (reads as a miss) and dies with the native like any other.
59 60 61 62 63 64 65 |
# File 'lib/moxml/context.rb', line 59 def unregister_wrapper(native) if WEAK_WRAPPERS @wrappers[native] = nil else @wrappers.delete(native) end end |
#wrapper_for(native) ⇒ Object
36 37 38 |
# File 'lib/moxml/context.rb', line 36 def wrapper_for(native) @wrappers[native] end |