00001
00002 (function() {
00003 "use strict";
00004 var bom, builder, escapeCDATA, events, isEmpty, processName, processors, requiresCDATA, sax, setImmediate, wrapCDATA,
00005 extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
00006 hasProp = {}.hasOwnProperty,
00007 bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
00008
00009 sax = require('sax');
00010
00011 events = require('events');
00012
00013 builder = require('xmlbuilder');
00014
00015 bom = require('./bom');
00016
00017 processors = require('./processors');
00018
00019 setImmediate = require('timers').setImmediate;
00020
00021 isEmpty = function(thing) {
00022 return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
00023 };
00024
00025 processName = function(processors, processedName) {
00026 var i, len, process;
00027 for (i = 0, len = processors.length; i < len; i++) {
00028 process = processors[i];
00029 processedName = process(processedName);
00030 }
00031 return processedName;
00032 };
00033
00034 requiresCDATA = function(entry) {
00035 return entry.indexOf('&') >= 0 || entry.indexOf('>') >= 0 || entry.indexOf('<') >= 0;
00036 };
00037
00038 wrapCDATA = function(entry) {
00039 return "<![CDATA[" + (escapeCDATA(entry)) + "]]>";
00040 };
00041
00042 escapeCDATA = function(entry) {
00043 return entry.replace(']]>', ']]]]><![CDATA[>');
00044 };
00045
00046 exports.processors = processors;
00047
00048 exports.defaults = {
00049 "0.1": {
00050 explicitCharkey: false,
00051 trim: true,
00052 normalize: true,
00053 normalizeTags: false,
00054 attrkey: "@",
00055 charkey: "#",
00056 explicitArray: false,
00057 ignoreAttrs: false,
00058 mergeAttrs: false,
00059 explicitRoot: false,
00060 validator: null,
00061 xmlns: false,
00062 explicitChildren: false,
00063 childkey: '@@',
00064 charsAsChildren: false,
00065 includeWhiteChars: false,
00066 async: false,
00067 strict: true,
00068 attrNameProcessors: null,
00069 attrValueProcessors: null,
00070 tagNameProcessors: null,
00071 valueProcessors: null,
00072 emptyTag: ''
00073 },
00074 "0.2": {
00075 explicitCharkey: false,
00076 trim: false,
00077 normalize: false,
00078 normalizeTags: false,
00079 attrkey: "$",
00080 charkey: "_",
00081 explicitArray: true,
00082 ignoreAttrs: false,
00083 mergeAttrs: false,
00084 explicitRoot: true,
00085 validator: null,
00086 xmlns: false,
00087 explicitChildren: false,
00088 preserveChildrenOrder: false,
00089 childkey: '$$',
00090 charsAsChildren: false,
00091 includeWhiteChars: false,
00092 async: false,
00093 strict: true,
00094 attrNameProcessors: null,
00095 attrValueProcessors: null,
00096 tagNameProcessors: null,
00097 valueProcessors: null,
00098 rootName: 'root',
00099 xmldec: {
00100 'version': '1.0',
00101 'encoding': 'UTF-8',
00102 'standalone': true
00103 },
00104 doctype: null,
00105 renderOpts: {
00106 'pretty': true,
00107 'indent': ' ',
00108 'newline': '\n'
00109 },
00110 headless: false,
00111 chunkSize: 10000,
00112 emptyTag: '',
00113 cdata: false
00114 }
00115 };
00116
00117 exports.ValidationError = (function(superClass) {
00118 extend(ValidationError, superClass);
00119
00120 function ValidationError(message) {
00121 this.message = message;
00122 }
00123
00124 return ValidationError;
00125
00126 })(Error);
00127
00128 exports.Builder = (function() {
00129 function Builder(opts) {
00130 var key, ref, value;
00131 this.options = {};
00132 ref = exports.defaults["0.2"];
00133 for (key in ref) {
00134 if (!hasProp.call(ref, key)) continue;
00135 value = ref[key];
00136 this.options[key] = value;
00137 }
00138 for (key in opts) {
00139 if (!hasProp.call(opts, key)) continue;
00140 value = opts[key];
00141 this.options[key] = value;
00142 }
00143 }
00144
00145 Builder.prototype.buildObject = function(rootObj) {
00146 var attrkey, charkey, render, rootElement, rootName;
00147 attrkey = this.options.attrkey;
00148 charkey = this.options.charkey;
00149 if ((Object.keys(rootObj).length === 1) && (this.options.rootName === exports.defaults['0.2'].rootName)) {
00150 rootName = Object.keys(rootObj)[0];
00151 rootObj = rootObj[rootName];
00152 } else {
00153 rootName = this.options.rootName;
00154 }
00155 render = (function(_this) {
00156 return function(element, obj) {
00157 var attr, child, entry, index, key, value;
00158 if (typeof obj !== 'object') {
00159 if (_this.options.cdata && requiresCDATA(obj)) {
00160 element.raw(wrapCDATA(obj));
00161 } else {
00162 element.txt(obj);
00163 }
00164 } else {
00165 for (key in obj) {
00166 if (!hasProp.call(obj, key)) continue;
00167 child = obj[key];
00168 if (key === attrkey) {
00169 if (typeof child === "object") {
00170 for (attr in child) {
00171 value = child[attr];
00172 element = element.att(attr, value);
00173 }
00174 }
00175 } else if (key === charkey) {
00176 if (_this.options.cdata && requiresCDATA(child)) {
00177 element = element.raw(wrapCDATA(child));
00178 } else {
00179 element = element.txt(child);
00180 }
00181 } else if (Array.isArray(child)) {
00182 for (index in child) {
00183 if (!hasProp.call(child, index)) continue;
00184 entry = child[index];
00185 if (typeof entry === 'string') {
00186 if (_this.options.cdata && requiresCDATA(entry)) {
00187 element = element.ele(key).raw(wrapCDATA(entry)).up();
00188 } else {
00189 element = element.ele(key, entry).up();
00190 }
00191 } else {
00192 element = render(element.ele(key), entry).up();
00193 }
00194 }
00195 } else if (typeof child === "object") {
00196 element = render(element.ele(key), child).up();
00197 } else {
00198 if (typeof child === 'string' && _this.options.cdata && requiresCDATA(child)) {
00199 element = element.ele(key).raw(wrapCDATA(child)).up();
00200 } else {
00201 if (child == null) {
00202 child = '';
00203 }
00204 element = element.ele(key, child.toString()).up();
00205 }
00206 }
00207 }
00208 }
00209 return element;
00210 };
00211 })(this);
00212 rootElement = builder.create(rootName, this.options.xmldec, this.options.doctype, {
00213 headless: this.options.headless,
00214 allowSurrogateChars: this.options.allowSurrogateChars
00215 });
00216 return render(rootElement, rootObj).end(this.options.renderOpts);
00217 };
00218
00219 return Builder;
00220
00221 })();
00222
00223 exports.Parser = (function(superClass) {
00224 extend(Parser, superClass);
00225
00226 function Parser(opts) {
00227 this.parseString = bind(this.parseString, this);
00228 this.reset = bind(this.reset, this);
00229 this.assignOrPush = bind(this.assignOrPush, this);
00230 this.processAsync = bind(this.processAsync, this);
00231 var key, ref, value;
00232 if (!(this instanceof exports.Parser)) {
00233 return new exports.Parser(opts);
00234 }
00235 this.options = {};
00236 ref = exports.defaults["0.2"];
00237 for (key in ref) {
00238 if (!hasProp.call(ref, key)) continue;
00239 value = ref[key];
00240 this.options[key] = value;
00241 }
00242 for (key in opts) {
00243 if (!hasProp.call(opts, key)) continue;
00244 value = opts[key];
00245 this.options[key] = value;
00246 }
00247 if (this.options.xmlns) {
00248 this.options.xmlnskey = this.options.attrkey + "ns";
00249 }
00250 if (this.options.normalizeTags) {
00251 if (!this.options.tagNameProcessors) {
00252 this.options.tagNameProcessors = [];
00253 }
00254 this.options.tagNameProcessors.unshift(processors.normalize);
00255 }
00256 this.reset();
00257 }
00258
00259 Parser.prototype.processAsync = function() {
00260 var chunk, err, error1;
00261 try {
00262 if (this.remaining.length <= this.options.chunkSize) {
00263 chunk = this.remaining;
00264 this.remaining = '';
00265 this.saxParser = this.saxParser.write(chunk);
00266 return this.saxParser.close();
00267 } else {
00268 chunk = this.remaining.substr(0, this.options.chunkSize);
00269 this.remaining = this.remaining.substr(this.options.chunkSize, this.remaining.length);
00270 this.saxParser = this.saxParser.write(chunk);
00271 return setImmediate(this.processAsync);
00272 }
00273 } catch (error1) {
00274 err = error1;
00275 if (!this.saxParser.errThrown) {
00276 this.saxParser.errThrown = true;
00277 return this.emit(err);
00278 }
00279 }
00280 };
00281
00282 Parser.prototype.assignOrPush = function(obj, key, newValue) {
00283 if (!(key in obj)) {
00284 if (!this.options.explicitArray) {
00285 return obj[key] = newValue;
00286 } else {
00287 return obj[key] = [newValue];
00288 }
00289 } else {
00290 if (!(obj[key] instanceof Array)) {
00291 obj[key] = [obj[key]];
00292 }
00293 return obj[key].push(newValue);
00294 }
00295 };
00296
00297 Parser.prototype.reset = function() {
00298 var attrkey, charkey, ontext, stack;
00299 this.removeAllListeners();
00300 this.saxParser = sax.parser(this.options.strict, {
00301 trim: false,
00302 normalize: false,
00303 xmlns: this.options.xmlns
00304 });
00305 this.saxParser.errThrown = false;
00306 this.saxParser.onerror = (function(_this) {
00307 return function(error) {
00308 _this.saxParser.resume();
00309 if (!_this.saxParser.errThrown) {
00310 _this.saxParser.errThrown = true;
00311 return _this.emit("error", error);
00312 }
00313 };
00314 })(this);
00315 this.saxParser.onend = (function(_this) {
00316 return function() {
00317 if (!_this.saxParser.ended) {
00318 _this.saxParser.ended = true;
00319 return _this.emit("end", _this.resultObject);
00320 }
00321 };
00322 })(this);
00323 this.saxParser.ended = false;
00324 this.EXPLICIT_CHARKEY = this.options.explicitCharkey;
00325 this.resultObject = null;
00326 stack = [];
00327 attrkey = this.options.attrkey;
00328 charkey = this.options.charkey;
00329 this.saxParser.onopentag = (function(_this) {
00330 return function(node) {
00331 var key, newValue, obj, processedKey, ref;
00332 obj = {};
00333 obj[charkey] = "";
00334 if (!_this.options.ignoreAttrs) {
00335 ref = node.attributes;
00336 for (key in ref) {
00337 if (!hasProp.call(ref, key)) continue;
00338 if (!(attrkey in obj) && !_this.options.mergeAttrs) {
00339 obj[attrkey] = {};
00340 }
00341 newValue = _this.options.attrValueProcessors ? processName(_this.options.attrValueProcessors, node.attributes[key]) : node.attributes[key];
00342 processedKey = _this.options.attrNameProcessors ? processName(_this.options.attrNameProcessors, key) : key;
00343 if (_this.options.mergeAttrs) {
00344 _this.assignOrPush(obj, processedKey, newValue);
00345 } else {
00346 obj[attrkey][processedKey] = newValue;
00347 }
00348 }
00349 }
00350 obj["#name"] = _this.options.tagNameProcessors ? processName(_this.options.tagNameProcessors, node.name) : node.name;
00351 if (_this.options.xmlns) {
00352 obj[_this.options.xmlnskey] = {
00353 uri: node.uri,
00354 local: node.local
00355 };
00356 }
00357 return stack.push(obj);
00358 };
00359 })(this);
00360 this.saxParser.onclosetag = (function(_this) {
00361 return function() {
00362 var cdata, emptyStr, err, error1, key, node, nodeName, obj, objClone, old, s, xpath;
00363 obj = stack.pop();
00364 nodeName = obj["#name"];
00365 if (!_this.options.explicitChildren || !_this.options.preserveChildrenOrder) {
00366 delete obj["#name"];
00367 }
00368 if (obj.cdata === true) {
00369 cdata = obj.cdata;
00370 delete obj.cdata;
00371 }
00372 s = stack[stack.length - 1];
00373 if (obj[charkey].match(/^\s*$/) && !cdata) {
00374 emptyStr = obj[charkey];
00375 delete obj[charkey];
00376 } else {
00377 if (_this.options.trim) {
00378 obj[charkey] = obj[charkey].trim();
00379 }
00380 if (_this.options.normalize) {
00381 obj[charkey] = obj[charkey].replace(/\s{2,}/g, " ").trim();
00382 }
00383 obj[charkey] = _this.options.valueProcessors ? processName(_this.options.valueProcessors, obj[charkey]) : obj[charkey];
00384 if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
00385 obj = obj[charkey];
00386 }
00387 }
00388 if (isEmpty(obj)) {
00389 obj = _this.options.emptyTag !== '' ? _this.options.emptyTag : emptyStr;
00390 }
00391 if (_this.options.validator != null) {
00392 xpath = "/" + ((function() {
00393 var i, len, results;
00394 results = [];
00395 for (i = 0, len = stack.length; i < len; i++) {
00396 node = stack[i];
00397 results.push(node["#name"]);
00398 }
00399 return results;
00400 })()).concat(nodeName).join("/");
00401 try {
00402 obj = _this.options.validator(xpath, s && s[nodeName], obj);
00403 } catch (error1) {
00404 err = error1;
00405 _this.emit("error", err);
00406 }
00407 }
00408 if (_this.options.explicitChildren && !_this.options.mergeAttrs && typeof obj === 'object') {
00409 if (!_this.options.preserveChildrenOrder) {
00410 node = {};
00411 if (_this.options.attrkey in obj) {
00412 node[_this.options.attrkey] = obj[_this.options.attrkey];
00413 delete obj[_this.options.attrkey];
00414 }
00415 if (!_this.options.charsAsChildren && _this.options.charkey in obj) {
00416 node[_this.options.charkey] = obj[_this.options.charkey];
00417 delete obj[_this.options.charkey];
00418 }
00419 if (Object.getOwnPropertyNames(obj).length > 0) {
00420 node[_this.options.childkey] = obj;
00421 }
00422 obj = node;
00423 } else if (s) {
00424 s[_this.options.childkey] = s[_this.options.childkey] || [];
00425 objClone = {};
00426 for (key in obj) {
00427 if (!hasProp.call(obj, key)) continue;
00428 objClone[key] = obj[key];
00429 }
00430 s[_this.options.childkey].push(objClone);
00431 delete obj["#name"];
00432 if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) {
00433 obj = obj[charkey];
00434 }
00435 }
00436 }
00437 if (stack.length > 0) {
00438 return _this.assignOrPush(s, nodeName, obj);
00439 } else {
00440 if (_this.options.explicitRoot) {
00441 old = obj;
00442 obj = {};
00443 obj[nodeName] = old;
00444 }
00445 _this.resultObject = obj;
00446 _this.saxParser.ended = true;
00447 return _this.emit("end", _this.resultObject);
00448 }
00449 };
00450 })(this);
00451 ontext = (function(_this) {
00452 return function(text) {
00453 var charChild, s;
00454 s = stack[stack.length - 1];
00455 if (s) {
00456 s[charkey] += text;
00457 if (_this.options.explicitChildren && _this.options.preserveChildrenOrder && _this.options.charsAsChildren && (_this.options.includeWhiteChars || text.replace(/\\n/g, '').trim() !== '')) {
00458 s[_this.options.childkey] = s[_this.options.childkey] || [];
00459 charChild = {
00460 '#name': '__text__'
00461 };
00462 charChild[charkey] = text;
00463 if (_this.options.normalize) {
00464 charChild[charkey] = charChild[charkey].replace(/\s{2,}/g, " ").trim();
00465 }
00466 s[_this.options.childkey].push(charChild);
00467 }
00468 return s;
00469 }
00470 };
00471 })(this);
00472 this.saxParser.ontext = ontext;
00473 return this.saxParser.oncdata = (function(_this) {
00474 return function(text) {
00475 var s;
00476 s = ontext(text);
00477 if (s) {
00478 return s.cdata = true;
00479 }
00480 };
00481 })(this);
00482 };
00483
00484 Parser.prototype.parseString = function(str, cb) {
00485 var err, error1;
00486 if ((cb != null) && typeof cb === "function") {
00487 this.on("end", function(result) {
00488 this.reset();
00489 return cb(null, result);
00490 });
00491 this.on("error", function(err) {
00492 this.reset();
00493 return cb(err);
00494 });
00495 }
00496 try {
00497 str = str.toString();
00498 if (str.trim() === '') {
00499 this.emit("end", null);
00500 return true;
00501 }
00502 str = bom.stripBOM(str);
00503 if (this.options.async) {
00504 this.remaining = str;
00505 setImmediate(this.processAsync);
00506 return this.saxParser;
00507 }
00508 return this.saxParser.write(str).close();
00509 } catch (error1) {
00510 err = error1;
00511 if (!(this.saxParser.errThrown || this.saxParser.ended)) {
00512 this.emit('error', err);
00513 return this.saxParser.errThrown = true;
00514 } else if (this.saxParser.ended) {
00515 throw err;
00516 }
00517 }
00518 };
00519
00520 return Parser;
00521
00522 })(events.EventEmitter);
00523
00524 exports.parseString = function(str, a, b) {
00525 var cb, options, parser;
00526 if (b != null) {
00527 if (typeof b === 'function') {
00528 cb = b;
00529 }
00530 if (typeof a === 'object') {
00531 options = a;
00532 }
00533 } else {
00534 if (typeof a === 'function') {
00535 cb = a;
00536 }
00537 options = {};
00538 }
00539 parser = new exports.Parser(options);
00540 return parser.parseString(str, cb);
00541 };
00542
00543 }).call(this);