eeml 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,11 @@
1
+ module Eeml
2
+ #a parser for json environments
3
+ class JsonEnvironmentParser
4
+ def make_environment_from_json(json_str)
5
+ env = Environment.new
6
+ return env
7
+ end
8
+ end
9
+ end
10
+
11
+
@@ -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>
@@ -0,0 +1,2 @@
1
+ foo
2
+
@@ -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>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <eeml xmlns="http://www.eeml.org/xsd/005">
3
+ <environment>
4
+ <title>Minimal</title>
5
+ <data id="0">
6
+ <value>36.2</value>
7
+ </data>
8
+ </environment>
9
+ </eeml>
@@ -0,0 +1,4 @@
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/>
4
+ </eeml>