Skip to content

Commit

Permalink
first code and specs to support attributes
Browse files Browse the repository at this point in the history
example Hashes contain attributes prefixed with _ (underscore).
the @-sign would be more natural for this, but it doesn't work
with the new ruby 1.9 hash syntax.

/cc savonrb/wasabi#27
  • Loading branch information
rubiii committed Jun 4, 2013
1 parent 9483f14 commit 24376f2
Show file tree
Hide file tree
Showing 10 changed files with 1,214 additions and 20 deletions.
11 changes: 11 additions & 0 deletions lib/savon/attribute.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Savon
class Attribute

attr_accessor :name, :base_type, :use

def optional?
use == 'optional'
end

end
end
28 changes: 18 additions & 10 deletions lib/savon/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ class Savon
class Element

def initialize
@children = []
@recursive = false
@singular = true
@children = []
@attributes = {}
@recursive = false
@singular = true
end

attr_accessor :parent, :name, :form, :namespace, :singular, :recursive,
:base_type, :children, :complex_type_id, :recursive_type
:base_type, :children, :complex_type_id, :recursive_type,
:attributes

alias_method :singular?, :singular

Expand All @@ -26,18 +28,24 @@ def complex_type?

def to_a(memo = [], stack = [])
new_stack = stack + [name]
attributes = { namespace: namespace, form: form, singular: singular? }
data = { namespace: namespace, form: form, singular: singular? }

unless attributes.empty?
data[:attributes] = attributes.each_with_object({}) do |attribute, memo|
memo[attribute.name] = { optional: attribute.optional? }
end
end

if recursive?
attributes[:recursive_type] = recursive_type
memo << [new_stack, attributes]
data[:recursive_type] = recursive_type
memo << [new_stack, data]

elsif simple_type?
attributes[:type] = base_type
memo << [new_stack, attributes]
data[:type] = base_type
memo << [new_stack, data]

elsif complex_type?
memo << [new_stack, attributes]
memo << [new_stack, data]

children.each do |child|
child.to_a(memo, new_stack)
Expand Down
11 changes: 11 additions & 0 deletions lib/savon/example_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ def build(elements)

when element.complex_type?
value = build(element.children)

unless element.attributes.empty?
value.merge! collect_attributes(element)
end

value = [value] unless element.singular?
memo[name] = value

Expand All @@ -34,5 +39,11 @@ def build(elements)
memo
end

def collect_attributes(element)
element.attributes.each_with_object({}) { |attribute, memo|
memo["_#{attribute.name}".to_sym] = attribute.base_type
}
end

end
end
19 changes: 18 additions & 1 deletion lib/savon/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
class Savon
class Message

ATTRIBUTE_PREFIX = '_'

def initialize(envelope, parts)
@logger = Logging.logger[self]

Expand Down Expand Up @@ -73,7 +75,9 @@ def build_complex_type_element(element, xml, tag, value)
raise ArgumentError, "Expected a Hash for the #{tag.last.inspect} complex type"
end

xml.tag! *tag do |xml|
attributes, value = extract_attributes(value)

xml.tag! *tag, attributes do |xml|
build_elements(element.children, value, xml)
end
else
Expand Down Expand Up @@ -101,5 +105,18 @@ def extract_value(name, symbol_name, message)
end
end

def extract_attributes(hash)
attributes = {}

hash.dup.each do |k, v|
next unless k.to_s[0, 1] == ATTRIBUTE_PREFIX

attributes[k.to_s[1..-1]] = v
hash.delete(k)
end

[attributes, hash]
end

end
end
47 changes: 47 additions & 0 deletions lib/savon/wsdl/message_builder.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
require 'savon/element'
require 'savon/attribute'

class Savon
class WSDL
class MessageBuilder

def initialize(wsdl)
@logger = Logging.logger[self]
@wsdl = wsdl
end

Expand Down Expand Up @@ -53,16 +55,54 @@ def build_element(part)

def handle_type(element, type)
case type

when XS::ComplexType
element.complex_type_id = type.id
element.children = child_elements(element, type)
element.attributes = element_attributes(type)

when XS::SimpleType
element.base_type = type.base

when String
element.base_type = type

end
end

def handle_simple_type(attribute, type)
case type
when XS::SimpleType then attribute.base_type = type.base
when String then attribute.base_type = type
end
end

def element_attributes(type)
type.attributes.map { |attribute|
attr = Attribute.new

if attribute.ref
local, namespace = expand_qname(attribute.ref, attribute.namespaces)
schema = find_schema(namespace)

if schema
attribute = schema.attributes[local]
else
@logger.debug("Unable to find schema for attribute@ref #{attribute.ref.inspect}")
next
end
end

type = find_type_for_attribute(attribute)
handle_simple_type(attr, type)

attr.name = attribute.name
attr.use = attribute.use

attr
}.compact
end

def child_elements(parent, type)
type.elements.map { |child_element|
el = Element.new
Expand Down Expand Up @@ -120,6 +160,8 @@ def find_type_for_element(element)
end
end

alias_method :find_type_for_attribute, :find_type_for_element

def find_type(qname, namespaces)
local, namespace = expand_qname(qname, namespaces)

Expand Down Expand Up @@ -154,6 +196,11 @@ def find_element(qname, namespaces)
@wsdl.schemas.element(namespace, local)
end

def find_attribute(qname, namespaces)
local, namespace = expand_qname(qname, namespaces)
@wsdl.schemas.attribute(namespace, local)
end

def find_schema(namespace)
@wsdl.schemas.find_by_namespace(namespace)
end
Expand Down
30 changes: 21 additions & 9 deletions lib/savon/xs/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ def initialize(schema, wsdl)
@target_namespace = @schema['targetNamespace']
@element_form_default = @schema['elementFormDefault'] || 'unqualified'

@elements = {}
@complex_types = {}
@simple_types = {}
@imports = {}
@attributes = {}
@attribute_groups = {}
@elements = {}
@complex_types = {}
@simple_types = {}
@imports = {}

parse
end

attr_accessor :target_namespace, :element_form_default, :imports,
:elements, :complex_types, :simple_types
:attributes, :attribute_groups, :elements, :complex_types, :simple_types

private

Expand All @@ -32,14 +34,24 @@ def parse

@schema.element_children.each do |node|
case node.name
when 'element' then @elements[node['name']] = XS::Element.new(node, @wsdl, schema)
when 'complexType' then @complex_types[node['name']] = XS::ComplexType.new(node, @wsdl, schema)
when 'simpleType' then @simple_types[node['name']] = XS::SimpleType.new(node, @wsdl, schema)
when 'import' then @imports[node['namespace']] = node['schemaLocation']
when 'attribute' then store_element(@attributes, node, schema)
when 'attributeGroup' then store_element(@attribute_groups, node, schema)
when 'element' then store_element(@elements, node, schema)
when 'complexType' then store_element(@complex_types, node, schema)
when 'simpleType' then store_element(@simple_types, node, schema)
when 'import' then store_import(node)
end
end
end

def store_element(collection, node, schema)
collection[node['name']] = XS.build(node, @wsdl, schema)
end

def store_import(node)
@imports[node['namespace']] = node['schemaLocation']
end

end
end
end
8 changes: 8 additions & 0 deletions lib/savon/xs/schema_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ def each(&block)
@schemas.each(&block)
end

def attribute(namespace, name)
find_by_namespace(namespace).attributes[name]
end

def attribute_group(namespace, name)
find_by_namespace(namespace).attribute_groups[name]
end

def element(namespace, name)
find_by_namespace(namespace).elements[name]
end
Expand Down
Loading

0 comments on commit 24376f2

Please sign in to comment.