Skip to content

Commit

Permalink
prepared the new public interface for version 2
Browse files Browse the repository at this point in the history
changed the Nori module to a class, removed any global configuration,
introduced simple options, removed the Nori::Parser module.
  • Loading branch information
rubiii committed Dec 11, 2012
1 parent 56b588f commit 0735544
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 244 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,23 @@
# 2.0 (UPCOMING)

Please make sure to read the updated README for how to use the new version.

* Refactoring: Changed the `Nori` module to a class. This might cause problems if you
included the `Nori` module somewhere in your application. This use case was removed
for overall simplicity.

* Refactoring: Changed the interface to remove any global state. The global configuration
is gone and split up between initializing a new `Nori` instance and passing the xml to
the new `#parse` method.

``` ruby
parser = Nori.new(strip_namespaces: true)
parser.parse(xml, parser: :nokogiri)
```

* Refactoring: Removed the `Nori::Parser` module methods. After refactoring the rest,
there was only a single method left for this module and that was moved to `Nori`.

## 1.1.3 (2012-07-12)

* Fix: Merged [pull request 21](https://github.com/savonrb/nori/pull/21) to fix an
Expand Down
72 changes: 34 additions & 38 deletions lib/nori.rb
@@ -1,59 +1,55 @@
require "nori/version"
require "nori/core_ext"
require "nori/parser"
require "nori/xml_utility_node"

module Nori
extend self
class Nori

# Translates the given +xml+ to a Hash. Accepts an optional +parser+ to use.
def parse(xml, parser = nil)
return {} if xml.blank?
Parser.parse xml, parser, self
end
PARSERS = { :rexml => "REXML", :nokogiri => "Nokogiri" }

# Sets the +parser+ to use.
def parser=(parser)
Parser.use = parser
end
def initialize(globals = {})
defaults = {
:strip_namespaces => false,
:convert_tags_to => nil
}

# Yields +self+ for configuration.
def configure
yield self
validate_options! defaults.keys, globals.keys
@globals = defaults.merge(globals)
end

# Sets whether to use advanced typecasting.
attr_writer :advanced_typecasting
def parse(xml, locals = {})
validate_xml! xml

# Returns whether to use advanced typecasting.
# Defaults to +true+.
def advanced_typecasting?
@advanced_typecasting != false
end
defaults = {
:advanced_typecasting => true,
:parser => :rexml
}

# Sets whether to strip namespaces.
attr_writer :strip_namespaces
validate_options! defaults.keys, locals.keys
options = @globals.merge defaults.merge(locals)

# Returns whether to strip namespaces.
# Defaults to +false+.
def strip_namespaces?
@strip_namespaces
parser = load_parser options[:parser]
parser.parse(xml, options)
end

# Expects a +block+ which receives a tag to convert.
# Accepts +nil+ for a reset to the default behavior of not converting tags.
def convert_tags_to(reset = nil, &block)
@convert_tag = reset || block
private

def load_parser(parser)
require "nori/parser/#{parser}"
Parser.const_get PARSERS[parser]
end

# Transforms a given +tag+ using the specified conversion formula.
def convert_tag(tag)
@convert_tag.call(tag)
def validate_xml!(xml)
return if xml.kind_of? String
raise ArgumentError, "Expected a String to parse, got: #{xml.inspect}"
end

# Returns whether to convert tags.
def convert_tags?
@convert_tag
def validate_options!(available_options, options)
spurious_options = options - available_options

unless spurious_options.empty?
raise ArgumentError, "Spurious options: #{spurious_options.inspect}\n" \
"Available options are: #{available_options.inspect}"
end
end

end
2 changes: 1 addition & 1 deletion lib/nori/core_ext/hash.rb
@@ -1,6 +1,6 @@
require "uri"

module Nori
class Nori
module CoreExt
module Hash

Expand Down
2 changes: 1 addition & 1 deletion lib/nori/core_ext/object.rb
@@ -1,4 +1,4 @@
module Nori
class Nori
module CoreExt
module Object

Expand Down
2 changes: 1 addition & 1 deletion lib/nori/core_ext/string.rb
@@ -1,4 +1,4 @@
module Nori
class Nori
module CoreExt
module String

Expand Down
51 changes: 0 additions & 51 deletions lib/nori/parser.rb

This file was deleted.

10 changes: 5 additions & 5 deletions lib/nori/parser/nokogiri.rb
@@ -1,6 +1,6 @@
require "nokogiri"

module Nori
class Nori
module Parser

# = Nori::Parser::Nokogiri
Expand All @@ -9,14 +9,14 @@ module Parser
module Nokogiri

class Document < ::Nokogiri::XML::SAX::Document
attr_accessor :nori
attr_accessor :options

def stack
@stack ||= []
end

def start_element(name, attrs = [])
stack.push Nori::XMLUtilityNode.new(nori, name, Hash[*attrs.flatten])
stack.push Nori::XMLUtilityNode.new(options, name, Hash[*attrs.flatten])
end

def end_element(name)
Expand All @@ -34,11 +34,11 @@ def characters(string)

end

def self.parse(xml, nori)
def self.parse(xml, options)
return {} if xml.strip.empty?

document = Document.new
document.nori = nori
document.options = options
parser = ::Nokogiri::XML::SAX::Parser.new document
parser.parse xml
document.stack.length > 0 ? document.stack.pop.to_hash : {}
Expand Down
6 changes: 3 additions & 3 deletions lib/nori/parser/rexml.rb
@@ -1,14 +1,14 @@
require "rexml/parsers/baseparser"

module Nori
class Nori
module Parser

# = Nori::Parser::REXML
#
# REXML pull parser.
module REXML

def self.parse(xml, nori)
def self.parse(xml, options)
stack = []
parser = ::REXML::Parsers::BaseParser.new(xml)

Expand All @@ -20,7 +20,7 @@ def self.parse(xml, nori)
when :end_doctype, :start_doctype
# do nothing
when :start_element
stack.push Nori::XMLUtilityNode.new(nori, event[1], event[2])
stack.push Nori::XMLUtilityNode.new(options, event[1], event[2])
when :end_element
if stack.size > 1
temp = stack.pop
Expand Down
2 changes: 1 addition & 1 deletion lib/nori/string_io_file.rb
@@ -1,4 +1,4 @@
module Nori
class Nori
class StringIOFile < StringIO

attr_accessor :original_filename, :content_type
Expand Down
2 changes: 1 addition & 1 deletion lib/nori/string_with_attributes.rb
@@ -1,4 +1,4 @@
module Nori
class Nori
class StringWithAttributes < String

attr_accessor :attributes
Expand Down
2 changes: 1 addition & 1 deletion lib/nori/version.rb
@@ -1,4 +1,4 @@
module Nori
class Nori

VERSION = "1.1.3"

Expand Down
17 changes: 9 additions & 8 deletions lib/nori/xml_utility_node.rb
Expand Up @@ -7,7 +7,7 @@
require "nori/string_with_attributes"
require "nori/string_io_file"

module Nori
class Nori

# This is a slighly modified version of the XMLUtilityNode from
# http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
Expand Down Expand Up @@ -84,16 +84,16 @@ def self.available_typecasts=(obj)

self.available_typecasts = self.typecasts.keys

def initialize(nori, name, normalized_attributes = {})
def initialize(options, name, normalized_attributes = {})
# unnormalize attribute values
attributes = Hash[* normalized_attributes.map do |key, value|
[ key, unnormalize_xml_entities(value) ]
end.flatten]

@nori = nori
@name = name.tr("-", "_")
@name = @name.split(":").last if @nori.strip_namespaces?
@name = @nori.convert_tag(@name) if @nori.convert_tags?
@options = options
@name = name.tr("-", "_")
@name = @name.split(":").last if @options[:strip_namespaces]
@name = @options[:convert_tags_to].call(@name) if @options[:convert_tags_to].respond_to? :call

# leave the type alone if we don't know what it is
@type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
Expand All @@ -120,7 +120,8 @@ def prefixed_attributes
end

def prefixed_attribute_name(attribute)
@nori.convert_tags? ? @nori.convert_tag(attribute) : attribute
return attribute unless @options[:convert_tags_to].respond_to? :call
@options[:convert_tags_to].call(attribute)
end

def add_node(node)
Expand All @@ -138,7 +139,7 @@ def to_hash

if @text
t = typecast_value unnormalize_xml_entities(inner_html)
t = advanced_typecasting(t) if t.is_a?(String) && @nori.advanced_typecasting?
t = advanced_typecasting(t) if t.is_a?(String) && @options[:advanced_typecasting]

if t.is_a?(String)
t = StringWithAttributes.new(t)
Expand Down

0 comments on commit 0735544

Please sign in to comment.