eeml 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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>
|