diff options
Diffstat (limited to 'js/dav/lib/parser.js')
-rw-r--r-- | js/dav/lib/parser.js | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/js/dav/lib/parser.js b/js/dav/lib/parser.js new file mode 100644 index 00000000..2db089c3 --- /dev/null +++ b/js/dav/lib/parser.js @@ -0,0 +1,160 @@ +import camelize from './camelize'; + +let debug = require('./debug')('dav:parser'); + +let DOMParser; +if (typeof self !== 'undefined' && 'DOMParser' in self) { + // browser main thread + DOMParser = self.DOMParser; +} else { + // nodejs or web worker + DOMParser = require('xmldom').DOMParser; +} + +export function multistatus(string) { + let parser = new DOMParser(); + let doc = parser.parseFromString(string, 'text/xml'); + let result = traverse.multistatus(child(doc, 'multistatus')); + debug(`input:\n${string}\noutput:\n${JSON.stringify(result)}\n`); + return result; +} + +let traverse = { + // { response: [x, y, z] } + multistatus: node => complex(node, { response: true }), + + // { propstat: [x, y, z] } + response: node => complex(node, { propstat: true, href: false }), + + // { prop: x } + propstat: node => complex(node, { prop: false }), + + // { + // resourcetype: x + // supportedCalendarComponentSet: y, + // supportedReportSet: z + // } + prop: node => { + return complex(node, { + resourcetype: false, + supportedCalendarComponentSet: false, + supportedReportSet: false, + currentUserPrincipal: false + }); + }, + + resourcetype: node => { + return childNodes(node).map(childNode => childNode.localName); + }, + + // [x, y, z] + supportedCalendarComponentSet: node => complex(node, { comp: true }, 'comp'), + + // [x, y, z] + supportedReportSet: node => { + return complex(node, { supportedReport: true }, 'supportedReport'); + }, + + comp: node => node.getAttribute('name'), + + // x + supportedReport: node => complex(node, { report: false }, 'report'), + + report: node => { + return childNodes(node).map(childNode => childNode.localName); + }, + + href: node => { + return decodeURIComponent(childNodes(node)[0].nodeValue); + }, + + currentUserPrincipal: node => { + return complex(node, {href: false}, 'href'); + } +}; + +function complex(node, childspec, collapse) { + let result = {}; + for (let key in childspec) { + if (childspec[key]) { + // Create array since we're expecting multiple. + result[key] = []; + } + } + + childNodes(node).forEach( + childNode => traverseChild(node, childNode, childspec, result) + ); + + return maybeCollapse(result, childspec, collapse); +} + +/** + * Parse child childNode of node with childspec and write outcome to result. + */ +function traverseChild(node, childNode, childspec, result) { + if (childNode.nodeType === 3 && /^\s+$/.test(childNode.nodeValue)) { + // Whitespace... nothing to do. + return; + } + + let localName = camelize(childNode.localName, '-'); + if (!(localName in childspec)) { + debug('Unexpected node of type ' + localName + ' encountered while ' + + 'parsing ' + node.localName + ' node!'); + let value = childNode.textContent; + if (localName in result) { + if (!Array.isArray(result[camelCase])) { + // Since we've already encountered this node type and we haven't yet + // made an array for it, make an array now. + result[localName] = [result[localName]]; + } + + result[localName].push(value); + return; + } + + // First time we're encountering this node. + result[localName] = value; + return; + } + + let traversal = traverse[localName](childNode); + if (childspec[localName]) { + // Expect multiple. + result[localName].push(traversal); + } else { + // Expect single. + result[localName] = traversal; + } +} + +function maybeCollapse(result, childspec, collapse) { + if (!collapse) { + return result; + } + + if (!childspec[collapse]) { + return result[collapse]; + } + + // Collapse array. + return result[collapse].reduce((a, b) => a.concat(b), []); +} + +function childNodes(node) { + let result = node.childNodes; + if (!Array.isArray(result)) { + result = Array.prototype.slice.call(result); + } + + return result; +} + +function children(node, localName) { + return childNodes(node).filter(childNode => childNode.localName === localName); +} + +function child(node, localName) { + return children(node, localName)[0]; +} |