response.coffee | |
---|---|
The ResponseParser class parses an Amazon Query API response converting the XML response to a JSON object. The parser makes assumptions about the structure of the XML.
With these assumptions, we convert the XML document into a direct JSON object representation. Because the JSON representation is a direct represntation of the XML, the client can use the Amazon Query API documentation as a reference for query results. | |
Require libxml. | libxml = require "libxmljs" |
Matches a timestamp in ISO 8601 format. | isTimestamp = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).(\d*)Z/
class ResponseParser |
Construct a new reponse parser which is based on a SaxParser. | constructor: () ->
accumulator = []
depth = 0
names = []
@sax = new libxml.SaxParser (sax) => |
We push collections onto the stack at start and reset the accumulator. | sax.onStartElementNS (elem) => |
When the element name is | if /^item|Error$/.test(elem)
if @branches.length != depth
@branches.push([])
names.push(elem) |
Otherwise, when the depth is greater than one, the parent element defines a container, so we create an object if it does not exist. | else if depth != 0
if depth > 1 && @branches.length != depth
@branches.push({})
names.push(elem) |
Reset acculator and increment depth. | accumulator.length = 0
depth++ |
We assign collections and values to their parent collections on end. | sax.onEndElementNS (elem) => |
When | if --depth == 0
@callback null, @base |
When we encouter an element named item or Error, we have completed an array element, so we push it onto the parent array element. | else if /^item|Error$/.test(names[depth - 1])
map = @branches.pop()
@branches[@branches.length - 1].push(map)
names.pop() |
Otherwise, if we created an object or array at this level, we assign it to the parent object using the element name as key. | else if @branches[depth]
map = @branches.pop()
@branches[@branches.length - 1][names.pop()] = map |
When we encounter an element that contains only text, we know that this is either
If the accumulator has not gathered any text, then it is either a null scalar value or an empty list, depending on the name. If the name ends with | else
name = names.pop()
if accumulator.length == 0
value = null
if name == "Errors" || /Set$/.test(name)
value = []
else
value = accumulator.join("")
if /Time$/.test(name)
match = isTimestamp.exec(value)
if match
match = match.slice(1).map (part) -> parseInt(part, 10)
match[1]--
value = new Date(Date.UTC.apply(null, match))
if /size$/i.test(name)
value = parseInt(value, 10)
if name == "return" and /^true|false$/.test(value)
value = value == "true"
@branches[@branches.length - 1][name] = value
accumulator.length = 0 |
We don't gather text if it is only whitspace. | sax.onCharacters (chars) ->
if accumulator.length is 0 and /\S/.test(chars)
accumulator.push(chars) |
Errors are reported to our caller. | sax.onError (msg) =>
@callback new Error(msg), null |
Parses the Amazon Query API response | read: (text, callback) ->
@base = {}
@branches = [ @base ]
@callback = callback || () ->
@sax.parseString(text) |
Export ResponseParser. | module.exports.ResponseParser = ResponseParser
|