This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module XML
|
||||
module SAX
|
||||
# :markup: markdown
|
||||
#
|
||||
# The SAX::Document class is used for registering types of events you are interested in
|
||||
# handling. All of the methods on this class are available as possible events while parsing an
|
||||
# \XML document. To register for any particular event, subclass this class and implement the
|
||||
# methods you are interested in knowing about.
|
||||
#
|
||||
# To only be notified about start and end element events, write a class like this:
|
||||
#
|
||||
# class MyHandler < Nokogiri::XML::SAX::Document
|
||||
# def start_element name, attrs = []
|
||||
# puts "#{name} started!"
|
||||
# end
|
||||
#
|
||||
# def end_element name
|
||||
# puts "#{name} ended"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# You can use this event handler for any SAX-style parser included with Nokogiri.
|
||||
#
|
||||
# See also:
|
||||
#
|
||||
# - Nokogiri::XML::SAX
|
||||
# - Nokogiri::HTML4::SAX
|
||||
#
|
||||
# ### Entity Handling
|
||||
#
|
||||
# ⚠ Entity handling is complicated in a SAX parser! Please read this section carefully if
|
||||
# you're not getting the behavior you expect.
|
||||
#
|
||||
# Entities will be reported to the user via callbacks to #characters, to #reference, or
|
||||
# possibly to both. The behavior is determined by a combination of _entity type_ and the value
|
||||
# of ParserContext#replace_entities. (Recall that the default value of
|
||||
# ParserContext#replace_entities is `false`.)
|
||||
#
|
||||
# ⚠ <b>It is UNSAFE to set ParserContext#replace_entities to `true`</b> when parsing untrusted
|
||||
# documents.
|
||||
#
|
||||
# 💡 For more information on entity types, see [Wikipedia's page on
|
||||
# DTDs](https://en.wikipedia.org/wiki/Document_type_definition#Entity_declarations).
|
||||
#
|
||||
# | Entity type | #characters | #reference |
|
||||
# |--------------------------------------|------------------------------------|-------------------------------------|
|
||||
# | Char ref (e.g., <tt>’</tt>) | always | never |
|
||||
# | Predefined (e.g., <tt>&</tt>) | always | never |
|
||||
# | Undeclared † | never | <tt>#replace_entities == false</tt> |
|
||||
# | Internal | always | <tt>#replace_entities == false</tt> |
|
||||
# | External † | <tt>#replace_entities == true</tt> | <tt>#replace_entities == false</tt> |
|
||||
#
|
||||
#
|
||||
#
|
||||
# † In the case where the replacement text for the entity is unknown (e.g., an undeclared entity
|
||||
# or an external entity that could not be resolved because of network issues), then the
|
||||
# replacement text will not be reported. If ParserContext#replace_entities is `true`, this
|
||||
# means the #characters callback will not be invoked. If ParserContext#replace_entities is
|
||||
# `false`, then the #reference callback will be invoked, but with `nil` for the `content`
|
||||
# argument.
|
||||
#
|
||||
class Document
|
||||
###
|
||||
# Called when an \XML declaration is parsed.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +version+ (String) the version attribute
|
||||
# - +encoding+ (String, nil) the encoding of the document if present, else +nil+
|
||||
# - +standalone+ ("yes", "no", nil) the standalone attribute if present, else +nil+
|
||||
def xmldecl(version, encoding, standalone)
|
||||
end
|
||||
|
||||
###
|
||||
# Called when document starts parsing.
|
||||
def start_document
|
||||
end
|
||||
|
||||
###
|
||||
# Called when document ends parsing.
|
||||
def end_document
|
||||
end
|
||||
|
||||
###
|
||||
# Called at the beginning of an element.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +name+ (String) the name of the element
|
||||
# - +attrs+ (Array<Array<String>>) an assoc list of namespace declarations and attributes, e.g.:
|
||||
# [ ["xmlns:foo", "http://sample.net"], ["size", "large"] ]
|
||||
#
|
||||
# 💡If you're dealing with XML and need to handle namespaces, use the
|
||||
# #start_element_namespace method instead.
|
||||
#
|
||||
# Note that the element namespace and any attribute namespaces are not provided, and so any
|
||||
# namespaced elements or attributes will be returned as strings including the prefix:
|
||||
#
|
||||
# parser.parse(<<~XML)
|
||||
# <root xmlns:foo='http://foo.example.com/' xmlns='http://example.com/'>
|
||||
# <foo:bar foo:quux="xxx">hello world</foo:bar>
|
||||
# </root>
|
||||
# XML
|
||||
#
|
||||
# assert_pattern do
|
||||
# parser.document.start_elements => [
|
||||
# ["root", [["xmlns:foo", "http://foo.example.com/"], ["xmlns", "http://example.com/"]]],
|
||||
# ["foo:bar", [["foo:quux", "xxx"]]],
|
||||
# ]
|
||||
# end
|
||||
#
|
||||
def start_element(name, attrs = [])
|
||||
end
|
||||
|
||||
###
|
||||
# Called at the end of an element.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +name+ (String) the name of the element being closed
|
||||
#
|
||||
def end_element(name)
|
||||
end
|
||||
|
||||
###
|
||||
# Called at the beginning of an element.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +name+ (String) is the name of the element
|
||||
# - +attrs+ (Array<Attribute>) is an array of structs with the following properties:
|
||||
# - +localname+ (String) the local name of the attribute
|
||||
# - +value+ (String) the value of the attribute
|
||||
# - +prefix+ (String, nil) the namespace prefix of the attribute
|
||||
# - +uri+ (String, nil) the namespace URI of the attribute
|
||||
# - +prefix+ (String, nil) is the namespace prefix for the element
|
||||
# - +uri+ (String, nil) is the associated URI for the element's namespace
|
||||
# - +ns+ (Array<Array<String, String>>) is an assoc list of namespace declarations on the element
|
||||
#
|
||||
# 💡If you're dealing with HTML or don't care about namespaces, try #start_element instead.
|
||||
#
|
||||
# [Example]
|
||||
# it "start_elements_namespace is called with namespaced attributes" do
|
||||
# parser.parse(<<~XML)
|
||||
# <root xmlns:foo='http://foo.example.com/'>
|
||||
# <foo:a foo:bar='hello' />
|
||||
# </root>
|
||||
# XML
|
||||
#
|
||||
# assert_pattern do
|
||||
# parser.document.start_elements_namespace => [
|
||||
# [
|
||||
# "root",
|
||||
# [],
|
||||
# nil, nil,
|
||||
# [["foo", "http://foo.example.com/"]], # namespace declarations
|
||||
# ], [
|
||||
# "a",
|
||||
# [Nokogiri::XML::SAX::Parser::Attribute(localname: "bar", prefix: "foo", uri: "http://foo.example.com/", value: "hello")], # prefixed attribute
|
||||
# "foo", "http://foo.example.com/", # prefix and uri for the "a" element
|
||||
# [],
|
||||
# ]
|
||||
# ]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = []) # rubocop:disable Metrics/ParameterLists
|
||||
# Deal with SAX v1 interface
|
||||
name = [prefix, name].compact.join(":")
|
||||
attributes = ns.map do |ns_prefix, ns_uri|
|
||||
[["xmlns", ns_prefix].compact.join(":"), ns_uri]
|
||||
end + attrs.map do |attr|
|
||||
[[attr.prefix, attr.localname].compact.join(":"), attr.value]
|
||||
end
|
||||
start_element(name, attributes)
|
||||
end
|
||||
|
||||
###
|
||||
# Called at the end of an element.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +name+ (String) is the name of the element
|
||||
# - +prefix+ (String, nil) is the namespace prefix for the element
|
||||
# - +uri+ (String, nil) is the associated URI for the element's namespace
|
||||
#
|
||||
def end_element_namespace(name, prefix = nil, uri = nil)
|
||||
# Deal with SAX v1 interface
|
||||
end_element([prefix, name].compact.join(":"))
|
||||
end
|
||||
|
||||
###
|
||||
# Called when character data is parsed, and for parsed entities when
|
||||
# ParserContext#replace_entities is +true+.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +string+ contains the character data or entity replacement text
|
||||
#
|
||||
# ⚠ Please see Document@Entity+Handling for important information about how entities are handled.
|
||||
#
|
||||
# ⚠ This method might be called multiple times for a contiguous string of characters.
|
||||
#
|
||||
def characters(string)
|
||||
end
|
||||
|
||||
###
|
||||
# Called when a parsed entity is referenced and not replaced.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +name+ (String) is the name of the entity
|
||||
# - +content+ (String, nil) is the replacement text for the entity, if known
|
||||
#
|
||||
# ⚠ Please see Document@Entity+Handling for important information about how entities are handled.
|
||||
#
|
||||
# ⚠ An internal entity may result in a call to both #characters and #reference.
|
||||
#
|
||||
# Since v1.17.0
|
||||
#
|
||||
def reference(name, content)
|
||||
end
|
||||
|
||||
###
|
||||
# Called when comments are encountered
|
||||
# [Parameters]
|
||||
# - +string+ contains the comment data
|
||||
def comment(string)
|
||||
end
|
||||
|
||||
###
|
||||
# Called on document warnings
|
||||
# [Parameters]
|
||||
# - +string+ contains the warning
|
||||
def warning(string)
|
||||
end
|
||||
|
||||
###
|
||||
# Called on document errors
|
||||
# [Parameters]
|
||||
# - +string+ contains the error
|
||||
def error(string)
|
||||
end
|
||||
|
||||
###
|
||||
# Called when cdata blocks are found
|
||||
# [Parameters]
|
||||
# - +string+ contains the cdata content
|
||||
def cdata_block(string)
|
||||
end
|
||||
|
||||
###
|
||||
# Called when processing instructions are found
|
||||
# [Parameters]
|
||||
# - +name+ is the target of the instruction
|
||||
# - +content+ is the value of the instruction
|
||||
def processing_instruction(name, content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module XML
|
||||
module SAX
|
||||
###
|
||||
# This parser is a SAX style parser that reads its input as it deems necessary. The parser
|
||||
# takes a Nokogiri::XML::SAX::Document, an optional encoding, then given an XML input, sends
|
||||
# messages to the Nokogiri::XML::SAX::Document.
|
||||
#
|
||||
# Here is an example of using this parser:
|
||||
#
|
||||
# # Create a subclass of Nokogiri::XML::SAX::Document and implement
|
||||
# # the events we care about:
|
||||
# class MyHandler < Nokogiri::XML::SAX::Document
|
||||
# def start_element name, attrs = []
|
||||
# puts "starting: #{name}"
|
||||
# end
|
||||
#
|
||||
# def end_element name
|
||||
# puts "ending: #{name}"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# parser = Nokogiri::XML::SAX::Parser.new(MyHandler.new)
|
||||
#
|
||||
# # Hand an IO object to the parser, which will read the XML from the IO.
|
||||
# File.open(path_to_xml) do |f|
|
||||
# parser.parse(f)
|
||||
# end
|
||||
#
|
||||
# For more information about \SAX parsers, see Nokogiri::XML::SAX.
|
||||
#
|
||||
# Also see Nokogiri::XML::SAX::Document for the available events.
|
||||
#
|
||||
# For \HTML documents, use the subclass Nokogiri::HTML4::SAX::Parser.
|
||||
#
|
||||
class Parser
|
||||
# to dynamically resolve ParserContext in inherited methods
|
||||
include Nokogiri::ClassResolver
|
||||
|
||||
# Structure used for marshalling attributes for some callbacks in XML::SAX::Document.
|
||||
class Attribute < Struct.new(:localname, :prefix, :uri, :value)
|
||||
end
|
||||
|
||||
ENCODINGS = { # :nodoc:
|
||||
"NONE" => 0, # No char encoding detected
|
||||
"UTF-8" => 1, # UTF-8
|
||||
"UTF16LE" => 2, # UTF-16 little endian
|
||||
"UTF16BE" => 3, # UTF-16 big endian
|
||||
"UCS4LE" => 4, # UCS-4 little endian
|
||||
"UCS4BE" => 5, # UCS-4 big endian
|
||||
"EBCDIC" => 6, # EBCDIC uh!
|
||||
"UCS4-2143" => 7, # UCS-4 unusual ordering
|
||||
"UCS4-3412" => 8, # UCS-4 unusual ordering
|
||||
"UCS2" => 9, # UCS-2
|
||||
"ISO-8859-1" => 10, # ISO-8859-1 ISO Latin 1
|
||||
"ISO-8859-2" => 11, # ISO-8859-2 ISO Latin 2
|
||||
"ISO-8859-3" => 12, # ISO-8859-3
|
||||
"ISO-8859-4" => 13, # ISO-8859-4
|
||||
"ISO-8859-5" => 14, # ISO-8859-5
|
||||
"ISO-8859-6" => 15, # ISO-8859-6
|
||||
"ISO-8859-7" => 16, # ISO-8859-7
|
||||
"ISO-8859-8" => 17, # ISO-8859-8
|
||||
"ISO-8859-9" => 18, # ISO-8859-9
|
||||
"ISO-2022-JP" => 19, # ISO-2022-JP
|
||||
"SHIFT-JIS" => 20, # Shift_JIS
|
||||
"EUC-JP" => 21, # EUC-JP
|
||||
"ASCII" => 22, # pure ASCII
|
||||
}
|
||||
REVERSE_ENCODINGS = ENCODINGS.invert # :nodoc:
|
||||
deprecate_constant :ENCODINGS
|
||||
|
||||
# The Nokogiri::XML::SAX::Document where events will be sent.
|
||||
attr_accessor :document
|
||||
|
||||
# The encoding beings used for this document.
|
||||
attr_accessor :encoding
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# new ⇒ SAX::Parser
|
||||
# new(handler) ⇒ SAX::Parser
|
||||
# new(handler, encoding) ⇒ SAX::Parser
|
||||
#
|
||||
# Create a new Parser.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +handler+ (optional Nokogiri::XML::SAX::Document) The document that will receive
|
||||
# events. Will create a new Nokogiri::XML::SAX::Document if not given, which is accessible
|
||||
# through the #document attribute.
|
||||
# - +encoding+ (optional Encoding, String, nil) An Encoding or encoding name to use when
|
||||
# parsing the input. (default +nil+ for auto-detection)
|
||||
#
|
||||
def initialize(doc = Nokogiri::XML::SAX::Document.new, encoding = nil)
|
||||
@encoding = encoding
|
||||
@document = doc
|
||||
@warned = false
|
||||
|
||||
initialize_native unless Nokogiri.jruby?
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# parse(input) { |parser_context| ... }
|
||||
#
|
||||
# Parse the input, sending events to the SAX::Document at #document.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +input+ (String, IO) The input to parse.
|
||||
#
|
||||
# If +input+ quacks like a readable IO object, this method forwards to Parser.parse_io,
|
||||
# otherwise it forwards to Parser.parse_memory.
|
||||
#
|
||||
# [Yields]
|
||||
# If a block is given, the underlying ParserContext object will be yielded. This can be used
|
||||
# to set options on the parser context before parsing begins.
|
||||
#
|
||||
def parse(input, &block)
|
||||
if input.respond_to?(:read) && input.respond_to?(:close)
|
||||
parse_io(input, &block)
|
||||
else
|
||||
parse_memory(input, &block)
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# parse_io(io) { |parser_context| ... }
|
||||
# parse_io(io, encoding) { |parser_context| ... }
|
||||
#
|
||||
# Parse an input stream.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +io+ (IO) The readable IO object from which to read input
|
||||
# - +encoding+ (optional Encoding, String, nil) An Encoding or encoding name to use when
|
||||
# parsing the input, or +nil+ for auto-detection. (default #encoding)
|
||||
#
|
||||
# [Yields]
|
||||
# If a block is given, the underlying ParserContext object will be yielded. This can be used
|
||||
# to set options on the parser context before parsing begins.
|
||||
#
|
||||
def parse_io(io, encoding = @encoding)
|
||||
ctx = related_class("ParserContext").io(io, encoding)
|
||||
yield ctx if block_given?
|
||||
ctx.parse_with(self)
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# parse_memory(input) { |parser_context| ... }
|
||||
# parse_memory(input, encoding) { |parser_context| ... }
|
||||
#
|
||||
# Parse an input string.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +input+ (String) The input string to be parsed.
|
||||
# - +encoding+ (optional Encoding, String, nil) An Encoding or encoding name to use when
|
||||
# parsing the input, or +nil+ for auto-detection. (default #encoding)
|
||||
#
|
||||
# [Yields]
|
||||
# If a block is given, the underlying ParserContext object will be yielded. This can be used
|
||||
# to set options on the parser context before parsing begins.
|
||||
#
|
||||
def parse_memory(input, encoding = @encoding)
|
||||
ctx = related_class("ParserContext").memory(input, encoding)
|
||||
yield ctx if block_given?
|
||||
ctx.parse_with(self)
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# parse_file(filename) { |parser_context| ... }
|
||||
# parse_file(filename, encoding) { |parser_context| ... }
|
||||
#
|
||||
# Parse a file.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +filename+ (String) The path to the file to be parsed.
|
||||
# - +encoding+ (optional Encoding, String, nil) An Encoding or encoding name to use when
|
||||
# parsing the input, or +nil+ for auto-detection. (default #encoding)
|
||||
#
|
||||
# [Yields]
|
||||
# If a block is given, the underlying ParserContext object will be yielded. This can be used
|
||||
# to set options on the parser context before parsing begins.
|
||||
#
|
||||
def parse_file(filename, encoding = @encoding)
|
||||
raise ArgumentError, "no filename provided" unless filename
|
||||
raise Errno::ENOENT unless File.exist?(filename)
|
||||
raise Errno::EISDIR if File.directory?(filename)
|
||||
|
||||
ctx = related_class("ParserContext").file(filename, encoding)
|
||||
yield ctx if block_given?
|
||||
ctx.parse_with(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,129 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module XML
|
||||
module SAX
|
||||
###
|
||||
# Context object to invoke the XML SAX parser on the SAX::Document handler.
|
||||
#
|
||||
# 💡 This class is usually not instantiated by the user. Use Nokogiri::XML::SAX::Parser
|
||||
# instead.
|
||||
class ParserContext
|
||||
class << self
|
||||
###
|
||||
# :call-seq:
|
||||
# new(input)
|
||||
# new(input, encoding)
|
||||
#
|
||||
# Create a parser context for an IO or a String. This is a shorthand method for
|
||||
# ParserContext.io and ParserContext.memory.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +input+ (IO, String) A String or a readable IO object
|
||||
# - +encoding+ (optional) (Encoding) The +Encoding+ to use, or the name of an
|
||||
# encoding to use (default +nil+, encoding will be autodetected)
|
||||
#
|
||||
# If +input+ quacks like a readable IO object, this method forwards to ParserContext.io,
|
||||
# otherwise it forwards to ParserContext.memory.
|
||||
#
|
||||
# [Returns] Nokogiri::XML::SAX::ParserContext
|
||||
#
|
||||
def new(input, encoding = nil)
|
||||
if [:read, :close].all? { |x| input.respond_to?(x) }
|
||||
io(input, encoding)
|
||||
else
|
||||
memory(input, encoding)
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# io(input)
|
||||
# io(input, encoding)
|
||||
#
|
||||
# Create a parser context for an +input+ IO which will assume +encoding+
|
||||
#
|
||||
# [Parameters]
|
||||
# - +io+ (IO) The readable IO object from which to read input
|
||||
# - +encoding+ (optional) (Encoding) The +Encoding+ to use, or the name of an
|
||||
# encoding to use (default +nil+, encoding will be autodetected)
|
||||
#
|
||||
# [Returns] Nokogiri::XML::SAX::ParserContext
|
||||
#
|
||||
# 💡 Calling this method directly is discouraged. Use Nokogiri::XML::SAX::Parser parse
|
||||
# methods which are more convenient for most use cases.
|
||||
#
|
||||
def io(input, encoding = nil)
|
||||
native_io(input, resolve_encoding(encoding))
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# memory(input)
|
||||
# memory(input, encoding)
|
||||
#
|
||||
# Create a parser context for the +input+ String.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +input+ (String) The input string to be parsed.
|
||||
# - +encoding+ (optional) (Encoding, String) The +Encoding+ to use, or the name of an encoding to
|
||||
# use (default +nil+, encoding will be autodetected)
|
||||
#
|
||||
# [Returns] Nokogiri::XML::SAX::ParserContext
|
||||
#
|
||||
# 💡 Calling this method directly is discouraged. Use Nokogiri::XML::SAX::Parser parse methods
|
||||
# which are more convenient for most use cases.
|
||||
#
|
||||
def memory(input, encoding = nil)
|
||||
native_memory(input, resolve_encoding(encoding))
|
||||
end
|
||||
|
||||
###
|
||||
# :call-seq:
|
||||
# file(path)
|
||||
# file(path, encoding)
|
||||
#
|
||||
# Create a parser context for the file at +path+.
|
||||
#
|
||||
# [Parameters]
|
||||
# - +path+ (String) The path to the input file
|
||||
# - +encoding+ (optional) (Encoding, String) The +Encoding+ to use, or the name of an encoding to
|
||||
# use (default +nil+, encoding will be autodetected)
|
||||
#
|
||||
# [Returns] Nokogiri::XML::SAX::ParserContext
|
||||
#
|
||||
# 💡 Calling this method directly is discouraged. Use Nokogiri::XML::SAX::Parser.parse_file which
|
||||
# is more convenient for most use cases.
|
||||
def file(input, encoding = nil)
|
||||
native_file(input, resolve_encoding(encoding))
|
||||
end
|
||||
|
||||
private def resolve_encoding(encoding)
|
||||
case encoding
|
||||
when Encoding
|
||||
encoding
|
||||
|
||||
when nil
|
||||
nil # totally fine, parser will guess encoding
|
||||
|
||||
when Integer
|
||||
warn("Passing an integer to Nokogiri::XML::SAX::ParserContext.io is deprecated. Use an Encoding object instead. This will become an error in a future release.", uplevel: 2, category: :deprecated)
|
||||
|
||||
return nil if encoding == Parser::ENCODINGS["NONE"]
|
||||
|
||||
encoding = Parser::REVERSE_ENCODINGS[encoding]
|
||||
raise ArgumentError, "Invalid libxml2 encoding id #{encoding}" if encoding.nil?
|
||||
Encoding.find(encoding)
|
||||
|
||||
when String
|
||||
Encoding.find(encoding)
|
||||
|
||||
else
|
||||
raise ArgumentError, "Cannot resolve #{encoding.inspect} to an Encoding"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Nokogiri
|
||||
module XML
|
||||
module SAX
|
||||
###
|
||||
# PushParser can parse a document that is fed to it manually. It
|
||||
# must be given a SAX::Document object which will be called with
|
||||
# SAX events as the document is being parsed.
|
||||
#
|
||||
# Calling PushParser#<< writes XML to the parser, calling any SAX
|
||||
# callbacks it can.
|
||||
#
|
||||
# PushParser#finish tells the parser that the document is finished
|
||||
# and calls the end_document SAX method.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# parser = PushParser.new(Class.new(XML::SAX::Document) {
|
||||
# def start_document
|
||||
# puts "start document called"
|
||||
# end
|
||||
# }.new)
|
||||
# parser << "<div>hello<"
|
||||
# parser << "/div>"
|
||||
# parser.finish
|
||||
class PushParser
|
||||
# The Nokogiri::XML::SAX::Document on which the PushParser will be
|
||||
# operating
|
||||
attr_accessor :document
|
||||
|
||||
###
|
||||
# Create a new PushParser with +doc+ as the SAX Document, providing
|
||||
# an optional +file_name+ and +encoding+
|
||||
def initialize(doc = XML::SAX::Document.new, file_name = nil, encoding = "UTF-8")
|
||||
@document = doc
|
||||
@encoding = encoding
|
||||
@sax_parser = XML::SAX::Parser.new(doc)
|
||||
|
||||
## Create our push parser context
|
||||
initialize_native(@sax_parser, file_name)
|
||||
end
|
||||
|
||||
###
|
||||
# Write a +chunk+ of XML to the PushParser. Any callback methods
|
||||
# that can be called will be called immediately.
|
||||
def write(chunk, last_chunk = false)
|
||||
native_write(chunk, last_chunk)
|
||||
end
|
||||
alias_method :<<, :write
|
||||
|
||||
###
|
||||
# Finish the parsing. This method is only necessary for
|
||||
# Nokogiri::XML::SAX::Document#end_document to be called.
|
||||
#
|
||||
# ⚠ Note that empty documents are treated as an error when using the libxml2-based
|
||||
# implementation (CRuby), but are fine when using the Xerces-based implementation (JRuby).
|
||||
def finish
|
||||
write("", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user