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.
@@ -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>