eeml 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +0 -0
- data/LICENSE +25 -0
- data/Manifest +30 -0
- data/README +8 -0
- data/Rakefile +25 -0
- data/eeml.gemspec +46 -0
- data/example.rb +33 -0
- data/lib/blank.rb +50 -0
- data/lib/eeml/constants.rb +17 -0
- data/lib/eeml/csv_parser.rb +16 -0
- data/lib/eeml/environment.rb +113 -0
- data/lib/eeml/exceptions.rb +59 -0
- data/lib/eeml/feed_output.rb +169 -0
- data/lib/eeml/feed_retriever.rb +103 -0
- data/lib/eeml/json_environment_parser.rb +11 -0
- data/lib/eeml/libxml_eeml_output_v005.rb +120 -0
- data/lib/eeml/libxml_eeml_parser_v005.rb +179 -0
- data/lib/eeml/output_registry.rb +13 -0
- data/lib/eeml/parser_registry.rb +14 -0
- data/lib/eeml.rb +35 -0
- data/schemas/eeml/005.xsd +134 -0
- data/test/data/doc_1.json +2 -0
- data/test/data/doc_1.xml +32 -0
- data/test/data/minimal.xml +9 -0
- data/test/data/out_empty.xml +4 -0
- data/test/libxml_test_helper.rb +39 -0
- data/test/test_environment.rb +370 -0
- data/test/test_helper.rb +56 -0
- data/test/test_libxml_eeml_parser_v005.rb +9 -0
- data/test/test_libxml_test_helper.rb +85 -0
- data.tar.gz.sig +1 -0
- metadata +177 -0
- metadata.gz.sig +3 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
module Eeml
|
4
|
+
|
5
|
+
class FeedRetriever
|
6
|
+
|
7
|
+
DEFAULT_TIMEOUT = 5
|
8
|
+
PREMIUM_ENVIRONMENT_AGE = raise 3
|
9
|
+
|
10
|
+
attr_accessor :retrieval_errors #will hold an array of string error messages populated during feed retrieval. currently populated only in fetch_remote_data
|
11
|
+
attr_accessor :logger
|
12
|
+
attr_accessor :feed_url, :retrieved_at
|
13
|
+
attr_accessor :feed_changed, :feed_retrieved
|
14
|
+
attr_accessor :feed_content, :mime_type
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
end
|
18
|
+
|
19
|
+
# only attempt to retrieve data if request is after our minimum time delta
|
20
|
+
#currently makes use of get_response() and fetch() to do actual retrieval.
|
21
|
+
def fetch_remote_data
|
22
|
+
@retrieval_errors = []
|
23
|
+
logger.debug("*** Attempting to fetch remote data")
|
24
|
+
|
25
|
+
# only attempt to retrieve data if this environment has a feed_url
|
26
|
+
if !self.feed_url.blank? && (self.retrieved_at.nil? || (Time.now.utc - self.retrieved_at.utc) > PREMIUM_ENVIRONMENT_AGE)
|
27
|
+
logger.debug("*** Our refresh delta has passed so retrieve remote data")
|
28
|
+
|
29
|
+
self.feed_changed = false
|
30
|
+
self.feed_retrieved = false
|
31
|
+
|
32
|
+
response = get_response
|
33
|
+
|
34
|
+
case response
|
35
|
+
when Net::HTTPSuccess
|
36
|
+
logger.debug("*** 200 ok... checking mime type #{response.content_type}.")
|
37
|
+
if MIME_TYPES.include?(response.content_type)
|
38
|
+
logger.debug("*** We have a valid mime type")
|
39
|
+
|
40
|
+
self.feed_content = response.body.to_s.strip
|
41
|
+
self.mime_type = response.content_type
|
42
|
+
|
43
|
+
self.retrieved_at = Time.now.utc.to_s(:db)
|
44
|
+
else
|
45
|
+
logger.debug("*** wrong mime-type.")
|
46
|
+
white_list = ['text/html', 'text/javascript', 'application/javascript'] # acceptably WRONG header values.
|
47
|
+
filtered_advice = ( white_list.member?(response.content_type) ? " Got '#{response.content_type}'." : "" )
|
48
|
+
@retrieval_errors << "Wrong mime-type. Need application/xml, text/csv, or variants." + filtered_advice
|
49
|
+
end
|
50
|
+
else
|
51
|
+
self.feed_retrieved = false
|
52
|
+
self.feed_changed = false
|
53
|
+
logger.debug("*** Unable to fetch remote data")
|
54
|
+
end
|
55
|
+
else
|
56
|
+
logger.debug("*** No feed url present or refresh delta not yet expired - don't do nothin")
|
57
|
+
# TODO remove after development
|
58
|
+
end
|
59
|
+
rescue URI::InvalidURIError, Timeout::Error, SystemCallError => e
|
60
|
+
self.feed_retrieved = false
|
61
|
+
self.feed_changed = false
|
62
|
+
@retrieval_errors << "Url bad or unavailable."
|
63
|
+
logger.error("*** Error retrieving feed from remote source: #{e}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# private
|
67
|
+
|
68
|
+
# separating this method out makes mocking our response fairly simple
|
69
|
+
# basically we just create a dummy response object, and override this
|
70
|
+
# method with a stubbed version that returns our dummy response
|
71
|
+
def get_response
|
72
|
+
return fetch(feed_url)
|
73
|
+
end
|
74
|
+
|
75
|
+
# TODO: update test specs so this passes properly
|
76
|
+
# this fetch method recursively follows redirections up to a maximum depth of 10 redirections
|
77
|
+
def fetch(uri_str, limit = 10)
|
78
|
+
uri = create_uri(uri_str)
|
79
|
+
|
80
|
+
logger.debug("*** Fetching content from :#{uri}")
|
81
|
+
http_client = Net::HTTP.new(uri.host, uri.port)
|
82
|
+
|
83
|
+
# the default timeout appears to be 60 seconds. this is very long
|
84
|
+
# override it to be 5 seconds
|
85
|
+
http_client.open_timeout = DEFAULT_TIMEOUT
|
86
|
+
http_client.read_timeout = DEFAULT_TIMEOUT
|
87
|
+
response = http_client.request_get(uri.request_uri)
|
88
|
+
logger.debug("*** Got response: #{response}")
|
89
|
+
case response
|
90
|
+
when Net::HTTPSuccess then response
|
91
|
+
when Net::HTTPRedirection then fetch(response['location'], limit - 1)
|
92
|
+
else response
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_uri(uri_str)
|
97
|
+
logger.debug("*** Creating uri from: #{uri_str}")
|
98
|
+
uri = URI.parse(uri_str)
|
99
|
+
raise URI::InvalidURIError unless uri.is_a?(URI::HTTP) or uri.is_a?(URI::HTTPS)
|
100
|
+
return uri
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Eeml
|
2
|
+
class LibXMLEemlOutputV005 # :nodoc:
|
3
|
+
include LibXML
|
4
|
+
include Constants
|
5
|
+
|
6
|
+
# main method. creates an EEML 5 document for the given environment.
|
7
|
+
def to_eeml(environment)
|
8
|
+
doc = XML::Document.new
|
9
|
+
eeml = doc.root = XML::Node.new('eeml')
|
10
|
+
XML::Namespace.new(eeml, nil, EEML_HREF)
|
11
|
+
XML::Namespace.new(eeml, 'xsi', XSI_NAMESPACE)
|
12
|
+
eeml['version'] = EEML_VERSION
|
13
|
+
eeml['xsi:schemaLocation'] = EEML_SCHEMA_LOCATION
|
14
|
+
eeml << xml_node_for_environment(environment)
|
15
|
+
|
16
|
+
return doc.to_s(:encoding => XML::Encoding::UTF_8)
|
17
|
+
end
|
18
|
+
|
19
|
+
def xml_node_for_environment(environment)
|
20
|
+
environment_node = XML::Node.new('environment')
|
21
|
+
|
22
|
+
#TODO: write all these strings out safely for xml
|
23
|
+
|
24
|
+
environment_node['updated'] = environment.updated.strftime(XML_TIME_FORMAT_STRING) unless environment.updated.nil?
|
25
|
+
environment_node['id'] = environment.identifier.to_s unless environment.identifier.blank?
|
26
|
+
environment_node['creator'] = environment.creator.to_s unless environment.creator.blank?
|
27
|
+
|
28
|
+
unless environment.title.blank?
|
29
|
+
environment_node << title_node = XML::Node.new('title')
|
30
|
+
title_node << environment.title
|
31
|
+
end
|
32
|
+
|
33
|
+
unless environment.feed_url.blank?
|
34
|
+
environment_node << feed_node = XML::Node.new('feed')
|
35
|
+
feed_node << environment.feed_url
|
36
|
+
end
|
37
|
+
|
38
|
+
unless environment.status.blank?
|
39
|
+
environment_node << status_node = XML::Node.new('status')
|
40
|
+
status_node << environment.status
|
41
|
+
end
|
42
|
+
|
43
|
+
unless environment.description.blank?
|
44
|
+
environment_node << description_node = XML::Node.new('description')
|
45
|
+
description_node << environment.description
|
46
|
+
end
|
47
|
+
|
48
|
+
unless environment.icon.blank?
|
49
|
+
environment_node << icon_node = XML::Node.new('icon')
|
50
|
+
icon_node << environment.icon
|
51
|
+
end
|
52
|
+
|
53
|
+
unless environment.website.blank?
|
54
|
+
environment_node << website_node = XML::Node.new('website')
|
55
|
+
website_node << environment.website
|
56
|
+
end
|
57
|
+
|
58
|
+
unless environment.email.blank?
|
59
|
+
environment_node << email_node = XML::Node.new('email')
|
60
|
+
email_node << environment.email
|
61
|
+
end
|
62
|
+
|
63
|
+
unless environment.location.nil?
|
64
|
+
environment_node << location_node = XML::Node.new('location')
|
65
|
+
location_node['domain'] = environment.location.domain
|
66
|
+
location_node['exposure'] = environment.location.exposure unless environment.location.exposure.blank?
|
67
|
+
location_node['disposition'] = environment.location.disposition unless environment.location.disposition.blank?
|
68
|
+
|
69
|
+
unless environment.location.name.blank?
|
70
|
+
location_node << location_name_node = XML::Node.new('name')
|
71
|
+
location_name_node << environment.location.name
|
72
|
+
end
|
73
|
+
|
74
|
+
location_node << lat_node = XML::Node.new('lat')
|
75
|
+
lat_node << environment.location.latitude
|
76
|
+
|
77
|
+
location_node << lng_node = XML::Node.new('lon')
|
78
|
+
lng_node << environment.location.longitude
|
79
|
+
|
80
|
+
unless environment.location.elevation.blank?
|
81
|
+
location_node << elevation_node = XML::Node.new('ele')
|
82
|
+
elevation_node << environment.location.elevation
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
environment.datastreams.each do |datastream|
|
87
|
+
environment_node << datastream_to_xml_node(datastream)
|
88
|
+
end
|
89
|
+
|
90
|
+
return environment_node
|
91
|
+
end
|
92
|
+
|
93
|
+
def datastream_to_xml_node(datastream)
|
94
|
+
datastream_node = XML::Node.new('data')
|
95
|
+
datastream_node['id'] = datastream.identifier.to_s
|
96
|
+
|
97
|
+
datastream.tags.each do |tag|
|
98
|
+
tag_node = XML::Node.new('tag')
|
99
|
+
tag_node << tag
|
100
|
+
datastream_node << tag_node
|
101
|
+
end
|
102
|
+
|
103
|
+
datastream_node << value_node = XML::Node.new('value')
|
104
|
+
|
105
|
+
value_node['minValue'] = datastream.min_value.to_s unless datastream.min_value.to_s.empty?
|
106
|
+
value_node['maxValue'] = datastream.max_value.to_s unless datastream.max_value.to_s.empty?
|
107
|
+
|
108
|
+
value_node << datastream.value.to_s
|
109
|
+
|
110
|
+
unless datastream.unit_value.to_s.empty? && datastream.unit_type.to_s.empty? && datastream.unit_symbol.to_s.empty?
|
111
|
+
datastream_node << unit_node = XML::Node.new('unit')
|
112
|
+
unit_node['type'] = datastream.unit_type.to_s unless datastream.unit_type.to_s.empty?
|
113
|
+
unit_node['symbol'] = datastream.unit_symbol.to_s unless datastream.unit_symbol.to_s.empty?
|
114
|
+
unit_node << datastream.unit_value.to_s unless datastream.unit_value.to_s.empty?
|
115
|
+
end
|
116
|
+
|
117
|
+
return datastream_node
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "parsedate.rb"
|
2
|
+
module Eeml
|
3
|
+
#a parser for xml eeml v005, implemented with LibXML
|
4
|
+
class LibXMLEemlParserV005 # :nodoc:
|
5
|
+
include LibXML
|
6
|
+
|
7
|
+
#main method
|
8
|
+
|
9
|
+
#take an xml string, and create an Environment from it
|
10
|
+
def make_environment_from_xml(xml_str)
|
11
|
+
doc = parse_xml(xml_str)
|
12
|
+
return extract_environment(doc)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def parse_xml(xml_str)
|
18
|
+
errors = []
|
19
|
+
#http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Error.html
|
20
|
+
#TODO: is the error handler set up per thread? (XML::Error.set_handler)
|
21
|
+
XML::Error.set_handler { |error| errors << error }
|
22
|
+
|
23
|
+
#TODO: performance - is this expensive?
|
24
|
+
#TODO: are these configurations per-thread? If they're global (e.g. class variables) then we shouldn't be setting them here.
|
25
|
+
XML.default_line_numbers=true
|
26
|
+
|
27
|
+
parser = XML::Parser.string(xml_str)
|
28
|
+
begin
|
29
|
+
doc = parser.parse
|
30
|
+
rescue XML::Error => e
|
31
|
+
#note: errors var available here, too.
|
32
|
+
raise BadXML, "Malformed xml: #{e.class}: #{e}", e.backtrace
|
33
|
+
end
|
34
|
+
#validation?
|
35
|
+
# seems we have to recreate our XML::Schema object on each invocation
|
36
|
+
# else libxml segfaults very quickly
|
37
|
+
#doc.validate_schema(XML::Schema.from_string(IO.read(LOCAL_EEML_SCHEMA_LOCATION)))
|
38
|
+
return doc
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def extract_environment(doc)
|
43
|
+
env = Environment.new
|
44
|
+
doc.root.namespaces.default_prefix = 'x'
|
45
|
+
|
46
|
+
env_node = find_first_node_or_fail(doc, 'x:environment', 'environment')
|
47
|
+
env.identifier = env_node['id']
|
48
|
+
env.updated = Time.mktime(*ParseDate.parsedate(env_node['updated'])) if !env_node['updated'].nil?
|
49
|
+
|
50
|
+
|
51
|
+
env.creator = env_node['creator']
|
52
|
+
|
53
|
+
env.title = optional_content(env_node, 'x:title', 'title')
|
54
|
+
env.feed_url = optional_content(env_node, 'x:feed', 'feed')
|
55
|
+
env.description = optional_content(env_node, 'x:description', 'description')
|
56
|
+
env.website = optional_content(env_node, 'x:website', 'website')
|
57
|
+
env.status = optional_content(env_node, 'x:status', 'status')
|
58
|
+
env.email = optional_content(env_node, 'x:email', 'email')
|
59
|
+
env.icon = optional_content(env_node, 'x:icon', 'icon')
|
60
|
+
|
61
|
+
#find_first_node_or_fail(env_node, 'x:location', 'location')
|
62
|
+
loc_node = env_node.find_first('x:location')
|
63
|
+
env.location = extractLocation(loc_node) if loc_node
|
64
|
+
|
65
|
+
datastream_nodes = env_node.find('x:data')
|
66
|
+
# raise NoDataStreams.new, "no datastreams found" if datastream_nodes.empty?
|
67
|
+
env.datastreams = extractDataStreams(datastream_nodes) unless datastream_nodes.empty?
|
68
|
+
|
69
|
+
return env
|
70
|
+
end
|
71
|
+
|
72
|
+
def extractLocation(node)
|
73
|
+
#<location domain="physical" exposure="outdoor" disposition="mobile">
|
74
|
+
# <lat>50.1</lat>
|
75
|
+
# <lon>48.7</lon>
|
76
|
+
# <ele>1.34</ele>
|
77
|
+
#</location>
|
78
|
+
raise "given nil node" if node.nil?
|
79
|
+
loc = Location.new
|
80
|
+
loc.domain = node['domain']
|
81
|
+
loc.disposition = node['disposition']
|
82
|
+
loc.exposure = node['exposure']
|
83
|
+
loc.name = optional_content(node, 'x:name', 'name')
|
84
|
+
loc.latitude = optional_content(node, 'x:lat', 'lat')
|
85
|
+
loc.longitude = optional_content(node, 'x:lon', 'lon')
|
86
|
+
loc.elevation = optional_content(node, 'x:ele', 'ele')
|
87
|
+
return loc
|
88
|
+
end
|
89
|
+
|
90
|
+
#return an array (TODO: or a hash?) of DataStream objects from the given list of data nodes
|
91
|
+
def extractDataStreams(nodes)
|
92
|
+
#<data id="blah1">...</data><data id="blah2">...</data>
|
93
|
+
dataStreams = []
|
94
|
+
nodes.each do |node|
|
95
|
+
dataStreams << extractDataStream(node)
|
96
|
+
end
|
97
|
+
return dataStreams
|
98
|
+
end
|
99
|
+
|
100
|
+
#builds and returns a detailed exception of the given class, for problems concerning the given node (or its missing children)
|
101
|
+
#details include node's name and line number (zero if not available)
|
102
|
+
def exception_for_node(node, exception_class, message)
|
103
|
+
ex = exception_class.new(message)
|
104
|
+
ex.line_num = node.line_num
|
105
|
+
ex.node_name = node_name_or_root(node)
|
106
|
+
return ex
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def extractDataStream(node)
|
111
|
+
#<data id="0">
|
112
|
+
#<tag>some_tag</tag>
|
113
|
+
#<tag>another_tag</tag>
|
114
|
+
#<value minValue="0.0" maxValue="1022.0">0</value>
|
115
|
+
#<unit symbol="C" type="basicSI">Celsius</unit>
|
116
|
+
#</data>
|
117
|
+
data = DataStream.new
|
118
|
+
raise MissingAttribute.new('id', node.name) if node['id'].nil?
|
119
|
+
data.identifier = node['id']
|
120
|
+
node.find('x:tag').each do |tag_node|
|
121
|
+
data.tags << tag_node.content
|
122
|
+
end
|
123
|
+
|
124
|
+
value_nodes = node.find('x:value')
|
125
|
+
raise exception_for_node(node, DataMissingValue, "Data node is missing value node.") if value_nodes.empty?
|
126
|
+
raise exception_for_node(node, DataHasMultipleValues, "Data node has multiple 'value' nodes.") if value_nodes.size > 1
|
127
|
+
|
128
|
+
value_node = value_nodes.first
|
129
|
+
data.min_value = value_node['minValue']
|
130
|
+
data.max_value = value_node['maxValue']
|
131
|
+
data.value = value_node.content
|
132
|
+
|
133
|
+
unit_nodes = node.find('x:unit')
|
134
|
+
raise exception_for_node(node, DataHasMultipleUnits, "Data node has multiple 'unit' nodes.") if unit_nodes.size > 1
|
135
|
+
|
136
|
+
unit_node = unit_nodes.first
|
137
|
+
unless unit_node.nil?
|
138
|
+
data.unit_symbol = unit_node['symbol']
|
139
|
+
data.unit_type = unit_node['type']
|
140
|
+
data.unit_value = unit_node.content
|
141
|
+
end
|
142
|
+
|
143
|
+
return data
|
144
|
+
end
|
145
|
+
|
146
|
+
#Helpers ------------------------------------------------------------------
|
147
|
+
#Consider mixing these in to the libxml parser for more readable code
|
148
|
+
|
149
|
+
#raises MissingNode if the node isn't there
|
150
|
+
def mandatory_content(base_node, xpath, description)
|
151
|
+
node = base_node.find_first(xpath)
|
152
|
+
raise(MissingNode.new(node_name_or_root(base_node), description, xpath)) if node.nil?
|
153
|
+
return node.content
|
154
|
+
end
|
155
|
+
|
156
|
+
#returns the node's content, or the given default if the node isn't there (default itself defaults to nil)
|
157
|
+
#description isn't used, but keeps our signature same as mandatory_content(), up to that point.
|
158
|
+
def optional_content(base_node, xpath, description, default = nil)
|
159
|
+
node = base_node.find_first(xpath)
|
160
|
+
return node.nil? ? default : node.content
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
#get the name of the given node if it is a node, or 'root' if it is a doc.
|
165
|
+
#for use only for error messages
|
166
|
+
def node_name_or_root(node)
|
167
|
+
node.respond_to?(:name) ? node.name : 'root'
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_first_node_or_fail(base_node, xpath, description)
|
171
|
+
node = base_node.find_first(xpath)
|
172
|
+
raise(MissingNode.new(node_name_or_root(base_node), description, xpath)) if node.nil?
|
173
|
+
return node
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Eeml
|
2
|
+
class OutputRegistry
|
3
|
+
#look at the given xml, build and return a new v005 or 006 parser, accordingly
|
4
|
+
def self.get_xml_output_for(version = EEML_VERSION)
|
5
|
+
LibXMLEemlOutputV005.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.get_json_output_for(version = EEML_VERSION)
|
9
|
+
JsonEnvironmentParser.new
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Eeml
|
2
|
+
class ParserRegistry
|
3
|
+
#look at the given xml, build and return a new v005 or 006 parser, accordingly
|
4
|
+
def self.get_xml_parser_for(xml_str)
|
5
|
+
LibXMLEemlParserV005.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.get_json_parser_for(json_str)
|
9
|
+
JsonEnvironmentParser.new
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
data/lib/eeml.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#TODO: what's going on here and why?
|
2
|
+
#add this dir to the search path (?)
|
3
|
+
$:.unshift(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'logger' #std lib
|
6
|
+
require 'blank'
|
7
|
+
require 'libxml'
|
8
|
+
require 'json'
|
9
|
+
require 'eeml/constants'
|
10
|
+
require 'eeml/exceptions'
|
11
|
+
require 'eeml/feed_output'
|
12
|
+
require 'eeml/environment'
|
13
|
+
require 'eeml/libxml_eeml_parser_v005'
|
14
|
+
require 'eeml/json_environment_parser'
|
15
|
+
require 'eeml/parser_registry'
|
16
|
+
require 'eeml/output_registry'
|
17
|
+
require 'eeml/libxml_eeml_output_v005'
|
18
|
+
|
19
|
+
|
20
|
+
module Eeml
|
21
|
+
|
22
|
+
# enable logger before including everything else, in case we ever want to log initialization
|
23
|
+
##TODO: this config should be environment-specific (e.g. test/production). Tests can stub.
|
24
|
+
Environment.logger = Logger.new(STDERR)
|
25
|
+
|
26
|
+
# library version number
|
27
|
+
VERSION = '0.0.1'
|
28
|
+
#TODO: put in some configuration file, not here
|
29
|
+
LOCAL_EEML_SCHEMA_LOCATION = 'schemas/eeml/005.xsd'
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
end #module
|
34
|
+
|
35
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<xsd:schema xmlns="http://www.eeml.org/xsd/005" elementFormDefault="qualified" targetNamespace="http://www.eeml.org/xsd/005" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
3
|
+
<xsd:element name="eeml">
|
4
|
+
<xsd:complexType>
|
5
|
+
<xsd:choice>
|
6
|
+
<xsd:element maxOccurs="unbounded" name="environment">
|
7
|
+
<xsd:complexType>
|
8
|
+
<xsd:sequence>
|
9
|
+
<xsd:element minOccurs="0" name="title" type="xsd:string" />
|
10
|
+
<xsd:element minOccurs="0" name="feed" type="xsd:anyURI" />
|
11
|
+
<xsd:element minOccurs="0" name="status">
|
12
|
+
<xsd:simpleType>
|
13
|
+
<xsd:restriction base="xsd:string">
|
14
|
+
<xsd:enumeration value="frozen" />
|
15
|
+
<xsd:enumeration value="live" />
|
16
|
+
</xsd:restriction>
|
17
|
+
</xsd:simpleType>
|
18
|
+
</xsd:element>
|
19
|
+
<xsd:element minOccurs="0" name="description" type="xsd:string" />
|
20
|
+
<xsd:element minOccurs="0" name="icon" type="xsd:anyURI" />
|
21
|
+
<xsd:element minOccurs="0" name="website" type="xsd:anyURI" />
|
22
|
+
<xsd:element minOccurs="0" name="email" type="xsd:string" />
|
23
|
+
<xsd:element minOccurs="0" name="location">
|
24
|
+
<xsd:complexType>
|
25
|
+
<xsd:sequence>
|
26
|
+
<xsd:element minOccurs="0" name="name" type="xsd:string" />
|
27
|
+
<xsd:element name="lat">
|
28
|
+
<xsd:simpleType>
|
29
|
+
<xsd:restriction base="xsd:double">
|
30
|
+
<xsd:minInclusive value="-90" />
|
31
|
+
<xsd:maxInclusive value="90" />
|
32
|
+
</xsd:restriction>
|
33
|
+
</xsd:simpleType>
|
34
|
+
</xsd:element>
|
35
|
+
<xsd:element name="lon">
|
36
|
+
<xsd:simpleType>
|
37
|
+
<xsd:restriction base="xsd:double">
|
38
|
+
<xsd:minInclusive value="-180" />
|
39
|
+
<xsd:maxInclusive value="180" />
|
40
|
+
</xsd:restriction>
|
41
|
+
</xsd:simpleType>
|
42
|
+
</xsd:element>
|
43
|
+
<xsd:element minOccurs="0" maxOccurs="1" name="ele" type="xsd:double" />
|
44
|
+
</xsd:sequence>
|
45
|
+
<xsd:attribute name="exposure" use="optional">
|
46
|
+
<xsd:simpleType>
|
47
|
+
<xsd:restriction base="xsd:string">
|
48
|
+
<xsd:enumeration value="indoor" />
|
49
|
+
<xsd:enumeration value="outdoor" />
|
50
|
+
</xsd:restriction>
|
51
|
+
</xsd:simpleType>
|
52
|
+
</xsd:attribute>
|
53
|
+
<xsd:attribute name="domain" use="required">
|
54
|
+
<xsd:simpleType>
|
55
|
+
<xsd:restriction base="xsd:string">
|
56
|
+
<xsd:enumeration value="physical" />
|
57
|
+
<xsd:enumeration value="virtual" />
|
58
|
+
</xsd:restriction>
|
59
|
+
</xsd:simpleType>
|
60
|
+
</xsd:attribute>
|
61
|
+
<xsd:attribute name="disposition" use="optional">
|
62
|
+
<xsd:simpleType>
|
63
|
+
<xsd:restriction base="xsd:string">
|
64
|
+
<xsd:enumeration value="fixed" />
|
65
|
+
<xsd:enumeration value="mobile" />
|
66
|
+
</xsd:restriction>
|
67
|
+
</xsd:simpleType>
|
68
|
+
</xsd:attribute>
|
69
|
+
</xsd:complexType>
|
70
|
+
</xsd:element>
|
71
|
+
<xsd:element minOccurs="1" maxOccurs="unbounded" name="data">
|
72
|
+
<xsd:complexType>
|
73
|
+
<xsd:sequence>
|
74
|
+
<xsd:element minOccurs="0" maxOccurs="unbounded" name="tag" type="xsd:string" />
|
75
|
+
<xsd:element name="value">
|
76
|
+
<xsd:complexType>
|
77
|
+
<xsd:simpleContent>
|
78
|
+
<xsd:extension base="xsd:string">
|
79
|
+
<xsd:attribute name="minValue" type="xsd:string" use="optional" />
|
80
|
+
<xsd:attribute name="maxValue" type="xsd:string" use="optional" />
|
81
|
+
</xsd:extension>
|
82
|
+
</xsd:simpleContent>
|
83
|
+
</xsd:complexType>
|
84
|
+
</xsd:element>
|
85
|
+
<xsd:element minOccurs="0" maxOccurs="1" name="unit">
|
86
|
+
<xsd:complexType>
|
87
|
+
<xsd:simpleContent>
|
88
|
+
<xsd:extension base="xsd:string">
|
89
|
+
<xsd:attribute name="symbol" type="xsd:string" use="optional">
|
90
|
+
</xsd:attribute>
|
91
|
+
<xsd:attribute name="type" use="optional">
|
92
|
+
<xsd:simpleType>
|
93
|
+
<xsd:restriction base="xsd:string">
|
94
|
+
<xsd:enumeration value="basicSI" />
|
95
|
+
<xsd:enumeration value="derivedSI" />
|
96
|
+
<xsd:enumeration value="conversionBasedUnits" />
|
97
|
+
<xsd:enumeration value="derivedUnits" />
|
98
|
+
<xsd:enumeration value="contextDependentUnits" />
|
99
|
+
</xsd:restriction>
|
100
|
+
</xsd:simpleType>
|
101
|
+
</xsd:attribute>
|
102
|
+
</xsd:extension>
|
103
|
+
</xsd:simpleContent>
|
104
|
+
</xsd:complexType>
|
105
|
+
</xsd:element>
|
106
|
+
</xsd:sequence>
|
107
|
+
<xsd:attribute name="id" type="xsd:nonNegativeInteger" use="required" />
|
108
|
+
</xsd:complexType>
|
109
|
+
</xsd:element>
|
110
|
+
</xsd:sequence>
|
111
|
+
<xsd:attribute name="updated" use="optional">
|
112
|
+
<xsd:simpleType>
|
113
|
+
<xsd:restriction base="xsd:dateTime" />
|
114
|
+
</xsd:simpleType>
|
115
|
+
</xsd:attribute>
|
116
|
+
<xsd:attribute fixed="http://www.haque.co.uk" name="creator" use="optional">
|
117
|
+
<xsd:simpleType>
|
118
|
+
<xsd:restriction base="xsd:string" />
|
119
|
+
</xsd:simpleType>
|
120
|
+
</xsd:attribute>
|
121
|
+
<xsd:attribute name="id" type="xsd:nonNegativeInteger" use="optional" />
|
122
|
+
</xsd:complexType>
|
123
|
+
</xsd:element>
|
124
|
+
</xsd:choice>
|
125
|
+
<xsd:attribute name="version" use="optional">
|
126
|
+
<xsd:simpleType>
|
127
|
+
<xsd:restriction base="xsd:positiveInteger">
|
128
|
+
<xsd:enumeration value="5" />
|
129
|
+
</xsd:restriction>
|
130
|
+
</xsd:simpleType>
|
131
|
+
</xsd:attribute>
|
132
|
+
</xsd:complexType>
|
133
|
+
</xsd:element>
|
134
|
+
</xsd:schema>
|
data/test/data/doc_1.xml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<eeml xmlns="http://www.eeml.org/xsd/005" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="5" xsi:schemaLocation="http://www.eeml.org/xsd/005 http://www.eeml.org/xsd/005/005.xsd">
|
3
|
+
<environment updated="2009-02-11T10:56:56Z" id="1247" creator="http://example.com/creator/">
|
4
|
+
<title>title here</title>
|
5
|
+
<feed>http://example.com/api/1247.xml</feed>
|
6
|
+
<status>frozen</status>
|
7
|
+
<description>description here</description>
|
8
|
+
<icon>http://example.com/some/icon.gif</icon>
|
9
|
+
<website>http://example.com/studio/</website>
|
10
|
+
<email>someone@example.com</email>
|
11
|
+
<location domain="physical" exposure="outdoor" disposition="mobile">
|
12
|
+
<name>Up on the roof (somewhere)</name>
|
13
|
+
<lat>50.1</lat>
|
14
|
+
<lon>48.7</lon>
|
15
|
+
<ele>1.34</ele>
|
16
|
+
</location>
|
17
|
+
<data id="0">
|
18
|
+
<tag>tagD0</tag>
|
19
|
+
<value minValue="-9999.0" maxValue="1022.0">0</value>
|
20
|
+
<unit type="basicSI" symbol="C">Celsius</unit>
|
21
|
+
</data>
|
22
|
+
<data id="1">
|
23
|
+
<value minValue="0.0" maxValue="1023.0">33</value>
|
24
|
+
</data>
|
25
|
+
<data id="2">
|
26
|
+
<tag>tagD2a</tag>
|
27
|
+
<tag>tagD2b</tag>
|
28
|
+
<tag>tagD2c</tag>
|
29
|
+
<value minValue="23.4" maxValue="1021.0">42.1</value>
|
30
|
+
</data>
|
31
|
+
</environment>
|
32
|
+
</eeml>
|