summaryrefslogtreecommitdiffstats
path: root/js/vendor/dav/lib/parser.js
blob: 2db089c3963fe1145803daa44cf3c760912142eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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];
}