rxsd 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ # RXSD Ruby Definition builder
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ module RXSD
7
+
8
+ # Implements the RXSD::ClassBuilder interface to build string Ruby Class Definitions from xsd
9
+ class RubyDefinitionBuilder < ClassBuilder
10
+
11
+ # implementation of RXSD::ClassBuilder::build
12
+ def build
13
+ return "class #{@klass.to_s}\nend" if Parser.is_builtin? @klass
14
+
15
+ # need the class name to build class
16
+ return nil if @klass_name.nil?
17
+
18
+ Logger.debug "building definition for #{@klass}/#{@klass_name} from xsd"
19
+
20
+ # defined class w/ base
21
+ superclass = "Object"
22
+ unless @base_builder.nil?
23
+ if ! @base_builder.klass_name.nil?
24
+ superclass = @base_builder.klass_name
25
+ elsif ! @base_builder.klass.nil?
26
+ superclass = @base_builder.klass.to_s
27
+ end
28
+ end
29
+ res = "class " + @klass_name + " < " + superclass + "\n"
30
+
31
+ # define accessors for attributes
32
+ @attribute_builders.each { |atb|
33
+ unless atb.nil?
34
+ att_name = nil
35
+ if !atb.attribute_name.nil?
36
+ att_name = atb.attribute_name.underscore
37
+ elsif !atb.klass_name.nil?
38
+ att_name = atb.klass_name.underscore
39
+ else
40
+ att_name = atb.klass.to_s.underscore
41
+ end
42
+
43
+ res += "attr_accessor :#{att_name}\n"
44
+ end
45
+ }
46
+ res += "end"
47
+
48
+ Logger.debug "definition #{res} built, returning"
49
+ return res
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,59 @@
1
+ # RXSD Ruby Object builder
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ module RXSD
7
+
8
+ # Implements the RXSD::ObjectBuilder interface to build Ruby Objects from a xsd-conforming xml doc
9
+ class RubyObjectBuilder < ObjectBuilder
10
+
11
+ # implementation of RXSD::ObjectBuilder::build
12
+ def build(schema)
13
+ # return object if already built
14
+ return @obj unless @obj.nil?
15
+
16
+ Logger.debug "instantiating class #{@tag_name} from xsd"
17
+
18
+ # find class builder corresponding to tag_name to instantiate
19
+ tags = schema.tags
20
+ klass = tags[@tag_name].klass
21
+
22
+ # instantiate the target class
23
+ if @content.nil? # not a text based obj, construct normally
24
+ @obj = klass.new
25
+ elsif klass == Array # special case when instantiating arrays, need to specify item type
26
+ @obj = klass.from_s @content, tags[@tag_name].associated_builder.klass
27
+ else
28
+ @obj = klass.from_s @content
29
+ end
30
+
31
+ # go through each attribute, find corresponding class builder,
32
+ # instantiate, and assign to object
33
+ @attributes.each { |atn, atv|
34
+ if tags.has_key? @tag_name + ":" + atn # FIXME how do we want to handle attributes that are not in the schema (eg the else here)
35
+ aklass = tags[@tag_name + ":" + atn].klass
36
+ if aklass == Array # special case when instantiating arrays, need to specify item type
37
+ val = aklass.from_s atv, tags[@tag_name + ":" + atn].associated_builder.klass
38
+ else
39
+ val = aklass.from_s atv
40
+ end
41
+ @obj.send("#{atn.underscore}=".intern, val)
42
+ end
43
+ }
44
+
45
+ # instantiate each child using builder and assign to object
46
+ @children.each { |child|
47
+ cob = RubyObjectBuilder.new(:builder => child)
48
+ cobj = cob.build(schema)
49
+ @obj.send("#{cob.tag_name.underscore}=".intern, cobj)
50
+ }
51
+
52
+ Logger.debug "object type #{@tag_name} instantiated, returning"
53
+
54
+ return @obj
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,82 @@
1
+ # RXSD builtin types
2
+ #
3
+ # Here we add some functionality to some basic
4
+ # Ruby types and define some of our own.
5
+ #
6
+ # Each type must be able to be instantiated with
7
+ # no arguments as well as from a string string parameter
8
+ # (exception is made in the case of Array)
9
+ #
10
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
11
+ # See COPYING for the License of this software
12
+
13
+ require 'date'
14
+
15
+ # Array, String, Time can be instantiated as is
16
+
17
+ class Array
18
+ # arrays take addition parameter when instantiating from
19
+ # string, the item type which to instantiate array elements w/
20
+ def self.from_s(str, item_type)
21
+ arr = []
22
+ str.split.each { |i|
23
+ arr.push item_type.from_s(i)
24
+ }
25
+ return arr
26
+ end
27
+ end
28
+
29
+ class String
30
+ # convert string to boolean
31
+ def to_b
32
+ return true if self == true || self =~ /^true$/i
33
+ return false if self == false || self.nil? || self =~ /^false$/i
34
+ raise ArgumentError, "invalid value for Boolean: \"#{self}\""
35
+ end
36
+
37
+ def self.from_s(str)
38
+ str
39
+ end
40
+ end
41
+
42
+ class Time
43
+ def self.from_s(str)
44
+ return Time.parse(str)
45
+ end
46
+ end
47
+
48
+ # ruby doesn't define Char class, so we dispatch to string
49
+ class Char < String
50
+ end
51
+
52
+ # Since we can't create new instances of Integer, Float,
53
+ # etc subclasses, we use the delegate module
54
+ # http://codeidol.com/other/rubyckbk/Numbers/Simulating-a-Subclass-of-Fixnum/
55
+ require 'delegate'
56
+
57
+ class XSDInteger < DelegateClass(::Integer)
58
+ def self.from_s(str)
59
+ str.to_i
60
+ end
61
+ end
62
+
63
+ class XSDFloat < DelegateClass(::Float)
64
+ def self.from_s(str)
65
+ str.to_f
66
+ end
67
+ end
68
+
69
+ # ruby doesn't define Boolean class, so we define one ourselves
70
+ class Boolean
71
+ def self.from_s(str)
72
+ str.to_b
73
+ end
74
+
75
+ def initialize(val=false)
76
+ @val = val
77
+ end
78
+
79
+ def nil?
80
+ return !@val
81
+ end
82
+ end
@@ -0,0 +1,69 @@
1
+ # Things that don't fit elsewhere
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ # we make use of the activesupport inflector
7
+ require 'active_support'
8
+
9
+ # logger support
10
+ require 'logger'
11
+
12
+ class Logger
13
+ def format_message(severity, timestamp, progname, msg)
14
+ "#{severity} #{timestamp} (#{$$}) #{msg}\n"
15
+ end
16
+ end
17
+
18
+ module RXSD
19
+ # Logger helper class
20
+ class Logger
21
+ private
22
+ LOG_LEVEL = ::Logger::FATAL # FATAL ERROR WARN INFO DEBUG
23
+
24
+ def self._instantiate_logger
25
+ unless defined? @@logger
26
+ @@logger = ::Logger.new(STDOUT)
27
+ @@logger.level = LOG_LEVEL
28
+ end
29
+ end
30
+
31
+ public
32
+ def self.method_missing(method_id, *args)
33
+ _instantiate_logger
34
+ @@logger.send(method_id, args)
35
+ end
36
+ def self.logger
37
+ _instantiate_logger
38
+ @@logger
39
+ end
40
+ end
41
+ end
42
+
43
+ class Module
44
+ # add virtual method support
45
+ def virtual(*methods)
46
+ methods.each do |m|
47
+ define_method(m) {
48
+ raise VirtualMethodCalledError, m
49
+ }
50
+ end
51
+ end
52
+
53
+ # add helper method to define a class method on any class
54
+ def class_method(method_name, &block)
55
+ (class << self; self; end).instance_eval do
56
+ define_method method_name, block
57
+ end
58
+ end
59
+ end
60
+
61
+ # read entire file into string
62
+ def File.read_all(path)
63
+ File.open(path, 'rb') {|file| return file.read }
64
+ end
65
+
66
+ # write contents of file from string
67
+ def File.write(path, str)
68
+ File.open(path, 'wb') {|file| file.write str }
69
+ end
@@ -0,0 +1,25 @@
1
+ # RXSD exceptions
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ require 'uri' # use uri to parse sources
7
+
8
+ # add virtual method support
9
+ class VirtualMethodCalledError < RuntimeError
10
+ attr :name
11
+ def initialize(name)
12
+ super("Virtual function '#{name}' called")
13
+ @name = name
14
+ end
15
+ end
16
+
17
+ module RXSD
18
+ module Exceptions
19
+
20
+ # thrown when specified resource uri is invalid
21
+ class InvalidResourceUri
22
+ end
23
+
24
+ end # module Exceptions
25
+ end # module RXSD
@@ -0,0 +1,77 @@
1
+
2
+ # libxml adapter
3
+ #
4
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
5
+ # See COPYING for the License of this software
6
+
7
+ require 'rubygems'
8
+ require 'libxml' # based on libxml
9
+
10
+ module RXSD
11
+ module XML
12
+
13
+ # class prototype needed :-(
14
+ class Node
15
+ end
16
+
17
+ # some additions to libxml xml node interface
18
+ class LibXMLNode < Node
19
+
20
+ # implementation of RXSD::XML::Node::xml_root(xml)
21
+ def self.xml_root(xml)
22
+ LibXMLNode.new :node => LibXML::XML::Document.string(xml).root
23
+ end
24
+
25
+ # create libxml node adapter w/ specified args, which may include
26
+ # * :node LibXML::Node to use to satify requests
27
+ def initialize(args = {})
28
+ @node = args[:node]
29
+ end
30
+
31
+
32
+ # implementation of RXSD::XML::Node.name
33
+ def name
34
+ @node.name
35
+ end
36
+
37
+ # implementation of RXSD::XML::Node.attrs
38
+ def attrs
39
+ @node.attributes.to_h
40
+ end
41
+
42
+ # implementation of RXSD::XML::Node.parent?
43
+ def parent?
44
+ @node.parent? && @node.parent.class != LibXML::XML::Document
45
+ end
46
+
47
+ # implementation of RXSD::XML::Node.parent
48
+ def parent
49
+ parent? ? LibXMLNode.new(:node => @node.parent) : nil
50
+ end
51
+
52
+ # implementation of RXSD::XML::Node.children
53
+ def children
54
+ @node.children.collect { |n|
55
+ LibXMLNode.new :node => n
56
+ }
57
+ end
58
+
59
+ # implementation of RXSD::XML::Node.text?
60
+ def text?
61
+ @node.text?
62
+ end
63
+
64
+ # implementation of RXSD::XML::Node.content
65
+ def content
66
+ @node.content
67
+ end
68
+
69
+ # implementation of RXSD::XML::Node.namespaces
70
+ def namespaces
71
+ @node.namespaces
72
+ end
73
+
74
+ end
75
+
76
+ end # module XML
77
+ end # module RXSD
@@ -0,0 +1,33 @@
1
+ # RXSD resource loader
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ require 'uri' # use uri to parse sources
7
+ require 'net/http' # get http:// based resources
8
+
9
+ module RXSD
10
+
11
+ # loads resources from uris
12
+ class Loader
13
+
14
+ # loads and return text resource from specified source uri
15
+ def self.load(source_uri)
16
+ Logger.info "loading resource from uri #{source_uri}"
17
+ data = nil
18
+ uri = URI.parse(source_uri)
19
+ if uri.scheme == "file"
20
+ data = File.read_all uri.path
21
+ elsif uri.scheme == "http"
22
+ data = Net::HTTP.get_response(uri.host, uri.path).body
23
+ # elsif FIXME support other uri types
24
+ end
25
+
26
+ return data
27
+
28
+ rescue URI::InvalidURIError
29
+ raise Exceptions::InvalidResourceUri
30
+ end
31
+
32
+ end # class loader
33
+ end # module RXSD
@@ -0,0 +1,135 @@
1
+ # xml / xsd parsers
2
+ #
3
+ # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
+ # See COPYING for the License of this software
5
+
6
+ module RXSD
7
+
8
+ # provides class methods to parse xsd and xml data
9
+ class Parser
10
+ private
11
+ def initialize
12
+ end
13
+
14
+ public
15
+
16
+ # parse xsd specified by uri or in raw data form into RXSD::XSD::Schema instance
17
+ # args should be a hash w/ optional keys:
18
+ # * :uri location which to load resource from
19
+ # * :raw raw data which to parse
20
+ def self.parse_xsd(args)
21
+ data = Loader.load(args[:uri]) unless args[:uri].nil?
22
+ data = args[:raw] unless args[:raw].nil?
23
+ Logger.debug "parsing xsd"
24
+
25
+ # FIXME validate against xsd's own xsd
26
+ root_xml_node = XML::Node.factory :backend => :libxml, :xml => data
27
+ schema = XSD::Schema.from_xml root_xml_node
28
+
29
+ Logger.debug "parsed xsd, resolving relationships"
30
+ Resolver.resolve_nodes schema
31
+
32
+ Logger.debug "xsd parsing complete"
33
+ return schema
34
+ end
35
+
36
+ # parse xml specified by uri or in raw data form into RXSD::XSD::SchemaInstance instance
37
+ def self.parse_xml(args)
38
+ data = Loader.load(args[:uri]) unless args[:uri].nil?
39
+ data = args[:raw] unless args[:raw].nil?
40
+ Logger.debug "parsing xml"
41
+
42
+ root_xml_node = XML::Node.factory :backend => :libxml, :xml => data
43
+ schema_instance = SchemaInstance.new :builders => SchemaInstance.builders_from_xml(root_xml_node)
44
+
45
+ Logger.debug "xml parsing complete"
46
+ return schema_instance
47
+ end
48
+
49
+ # return true is specified class is builtin, else false
50
+ def self.is_builtin?(builtin_class)
51
+ [Array, String, Boolean, Char, Time, XSDFloat, XSDInteger].include? builtin_class
52
+ end
53
+
54
+ # return ruby class corresponding to builting type
55
+ def self.parse_builtin_type(builtin_type_name)
56
+ res = nil
57
+
58
+ case builtin_type_name
59
+ when "xs:string":
60
+ res = String
61
+ when "xs:boolean":
62
+ res = Boolean
63
+ when "xs:decimal":
64
+ res = XSDFloat
65
+ when "xs:float":
66
+ res = XSDFloat
67
+ when "xs:double":
68
+ res = XSDFloat
69
+ when "xs:duration":
70
+ when "xs:dateTime":
71
+ res = Time
72
+ when "xs:date":
73
+ res = Time
74
+ when "xs:gYearMonth":
75
+ res = Time
76
+ when "xs:gYear":
77
+ res = Time
78
+ when "xs:gMonthDay":
79
+ res = Time
80
+ when "xs:gDay":
81
+ res = Time
82
+ when "xs:gMonth":
83
+ res = Time
84
+ when "xs:hexBinary":
85
+ when "xs:base64Binary":
86
+ when "xs:anyURI":
87
+ when "xs:QName":
88
+ when "xs:NOTATION":
89
+ when "xs:normalizedString"
90
+ when "xs:token"
91
+ res = String # FIXME should be a string derived class, eliminating whitespace
92
+ when "xs:language"
93
+ when "xs:NMTOKEN"
94
+ when "xs:NMTOKENS"
95
+ when "xs:Name"
96
+ when "xs:NCName"
97
+ when "xs:ID"
98
+ when "xs:IDREF"
99
+ when "xs:IDREFS"
100
+ when "xs:ENTITY"
101
+ when "xs:ENTITIES"
102
+ when "xs:integer"
103
+ res = XSDInteger
104
+ when "xs:nonPositiveInteger"
105
+ res = XSDInteger
106
+ when "xs:negativeInteger"
107
+ res = XSDInteger
108
+ when "xs:long"
109
+ res = XSDInteger
110
+ when "xs:int"
111
+ res = XSDInteger
112
+ when "xs:short"
113
+ res = XSDInteger
114
+ when "xs:byte"
115
+ res = Char
116
+ when "xs:nonNegativeInteger"
117
+ res = XSDInteger
118
+ when "xs:unsignedLong"
119
+ res = XSDInteger
120
+ when "xs:unsignedInt"
121
+ res = XSDInteger
122
+ when "xs:unsignedShort"
123
+ res = XSDInteger
124
+ when "xs:unsignedByte"
125
+ res = Char
126
+ when "xs:positiveInteger"
127
+ res = XSDInteger
128
+ end
129
+
130
+ return res
131
+ end
132
+
133
+ end
134
+
135
+ end