UNPKG

yhbatis

Version:

Like Mybatis for nodejs By YYH.

393 lines (323 loc) 11.9 kB
var sax; // wrapper for non-node envs (function(xml_digester) { // TODO: CDATA // TODO: Namespace handling // TODO: error handling // Logger: // --------------------------------------------------------------------------- var _logger = { ERROR_LEVEL: 1, WARN_LEVEL: 2, INFO_LEVEL: 3, DEBUG_LEVEL: 4, TRACE_LEVEL: 5, current_level: 3, log: function(level, prefix, message) { if (this.current_level >= level) { console.log(prefix, message); } }, error: function(message) { this.log(this.ERROR_LEVEL, "ERROR: ", message); }, warn: function(message) { this.log(this.WARN_LEVEL, "WARN: ", message); }, info: function(message) { this.log(this.INFO_LEVEL, "INFO: ", message); }, debug: function(message) { this.log(this.DEBUG_LEVEL, "DEBUG: ", message); }, trace: function(message) { this.log(this.TRACE_LEVEL, "TRACE: ", message); }, level: function(new_level) { if (new_level) { this.current_level = new_level; } else { return this.current_level; } }, isEnabled: function(level) { return this.current_level >= level; } }; // Digester: // --------------------------------------------------------------------------- function XmlDigester(opt) { if (!(this instanceof XmlDigester)) return new XmlDigester(opt); var digester = this; if (opt) { digester.handler = opt.handler; } digester.sax = require("sax").parser(false); digester.defaultHandler = new DefaultHandler(); var only_whitespace_pattern = /^\s*$/; digester.sax.onerror = function(e) { _logger.error("error!" + e); _logger.error(e.stack); this.error = this.error + e + "\n"; }; digester.sax.onopentag = function(node) { digester.xpath_stack.push(node.name); var handled = false; if (digester.handler) { digester.handler.forEach(function(elem) { _logger.trace(" --- checking: " + elem.path); // TODO: break if (! handled && match_stack(elem.path, digester.xpath_stack)) { _logger.trace("--- using: " + elem.handler); handled = true; elem.handler.onopentag(node, digester); } }); } if (! handled) { digester.defaultHandler.onopentag(node, digester); } _logger.trace(" "); _logger.trace("<" + digester.xpath_stack.join('><') + '>'); _logger.trace(node); if (_logger.isEnabled(_logger.DEBUG_LEVEL)) { digester._printObjectStack(); } }; digester.sax.ontext = function(t) { if (! only_whitespace_pattern.test(t)) { digester.current_text = t; } else { digester.current_text = ""; } }; digester.sax.onclosetag = function(node_name) { var handled = false; if (digester.handler) { digester.handler.forEach(function(elem) { // TODO: break if (! handled && match_stack(elem.path, digester.xpath_stack)) { handled = true; elem.handler.onclosetag(node_name, digester); } }); } if (! handled) { digester.defaultHandler.onclosetag(node_name, digester); } if (digester.xpath_stack.length == 1) { _logger.trace("</" + digester.xpath_stack[0] + ">"); } else { _logger.trace("<" + digester.xpath_stack.join('></') + ">"); } digester.xpath_stack.pop(); }; } // XmlDigester // helper functions // --------------------------------------------------------------------------- function has_properties(object) { var propertyName; for (propertyName in object) { if (object.hasOwnProperty(propertyName)) { return true; } } return false; } // import util (if possible) var util; try { util = require("util"); } catch (ex) { util = { inspect: function(object) { return object.toString(); } }; } // handler for XML element that should be skipped // --------------------------------------------------------------------------- function SkipElementsHandler() { if (!(this instanceof SkipElementsHandler)) return new SkipElementsHandler(); this.defaultHandler = new DefaultHandler(); } SkipElementsHandler.prototype.onopentag = function(node, digester) { this.defaultHandler.onopentag(node, digester); }; SkipElementsHandler.prototype.onclosetag = function(node_name, digester) { var parent_object = digester.object_stack.pop(); digester.current_object = parent_object; }; // handler for XML element, where the order of elements must be preserved // --------------------------------------------------------------------------- function OrderedElementsHandler(name_property) { if (!(this instanceof OrderedElementsHandler)) return new OrderedElementsHandler(name_property); this.name_property = name_property; this.defaultHandler = new DefaultHandler(); } OrderedElementsHandler.prototype.onopentag = function(node, digester) { var parent_object = digester.current_object; var new_object = node.attributes; if (this.name_property) { new_object[this.name_property] = node.name; } else { Object.defineProperty(new_object, "_name", {value: node.name}); } if (! Array.isArray(parent_object)) { if (has_properties(parent_object)) { _logger.warn(util.inspect(node, false, 1)); console.log("<" + digester.xpath_stack.join('><') + ">"); console.log(new Error().stack); throw "Ordered Element Container must not have attributes: " + util.inspect(parent_object, true, 1); } parent_object = []; } digester.object_stack.push(parent_object); digester.current_object = new_object; }; OrderedElementsHandler.prototype.onclosetag = function(node_name, digester) { var parent_object = digester.object_stack.pop(); if (! Array.isArray(parent_object)) { throw "internal error: object should be Array: (" + node_name + ") " + util.inspect(parent_object, true, 1); } this.defaultHandler.textifyCurrentObject(digester); parent_object.push(digester.current_object); digester.current_object = parent_object; }; // default handler for an XML element // --------------------------------------------------------------------------- function DefaultHandler() { if (!(this instanceof DefaultHandler)) return new DefaultHandler(); } DefaultHandler.prototype.onopentag = function(node, digester) { var new_object = node.attributes; Object.defineProperty(new_object, "_name", {value: node.name}); digester.object_stack.push(digester.current_object); digester.current_object = new_object; }; // If the current_object only has text content (no attributes, no child // elements), than just use the text as the current_object DefaultHandler.prototype.textifyCurrentObject = function(digester) { if (digester.current_text) { if (has_properties(digester.current_object)) { digester.current_object._text = digester.useText(); } else { digester.current_object = digester.useText(); } } }; DefaultHandler.prototype.onclosetag = function(node_name, digester) { var parent_object = digester.object_stack.pop(); // the text of a node has been collected previously // if the current object has no properties (i.e. the XML element had // no children nor attributes) replace the current _object_ with the text // otherwise add the text as "_text" this.textifyCurrentObject(digester); // does the parent object already have a property with the name of the current node? // i.e. there are multiple child elements with the same name if (parent_object[node_name]) { // if there are multiple elements with the same name the value is converted to an array // has this already happend? if (! Array.isArray(parent_object[node_name])) { parent_object[node_name] = [ parent_object[node_name] ]; } parent_object[node_name].push(digester.current_object); //_logger.info("the parent object already has a property with the name: " + node_name); // either: make all properties into an array, but we have already lost the order :-( // or: make only elements of the same name into an array: default? if (_logger.isEnabled(_logger.DEBUG_LEVEL)) { digester._printObjectStack(); } } else { parent_object[node_name] = digester.current_object; } digester.current_object = parent_object; }; XmlDigester.prototype.digest = function(xml, func) { this.xml = xml; this.object_stack = []; this.xpath_stack = []; this.error = ""; this.document = { }; Object.defineProperty(this.document, "_name", {value: "document"}); this.current_object = this.document; this.current_text = ""; try { this.sax.write(xml).close(); } catch (err) { this.error = err; console.log(err.stack); } if (this.error) { if (func) { func(this.error, null); } else { return undefined; } } else { if (func) { func(null, this.document); } else { return this.document; } } }; XmlDigester.prototype._printObjectStack = function() { var length = this.object_stack.length; var i; var indent = "-> "; for (i = 0; i < length; i++) { if (Array.isArray(this.object_stack[i])) { if (this.object_stack[i][0]) { _logger.debug(indent + "[" + util.inspect(this.object_stack[i][0]) + ", ...]"); } else { _logger.debug(indent + "[ ]"); } } else { _logger.debug(indent + this.object_stack[i]._name); } indent = indent + " "; } }; XmlDigester.prototype.useText = function() { var result = this.current_text; this.current_text = ""; return result; }; // StackMatcher: // --------------------------------------------------------------------------- var pattern = /^(.*?)(\/*)([^\/]*)$/; function match_stack(match_expression, stack) { _logger.trace(" "); _logger.trace("-----------------------------------------------------------"); return match_stack_from_pos(match_expression, stack, stack.length - 1); } function match_stack_from_pos(match_expression, stack, pos) { // end of stack reached? if (pos < 0) { // path not completely consumed? if (match_expression) { return false; } else { return true; } } var match = pattern.exec(match_expression); _logger.trace(match); _logger.trace("pos: " + pos); var name = match[3].toString(); var sep = match[2]; var rest = match[1]; if ((name != '*') && (stack[pos] != name)) { _logger.trace("'" + stack[pos] + "' != '" + name + "' -- " + (typeof name)); return false; } if (sep) { sep = sep.toString(); if (sep == "/") { return match_stack_from_pos(rest, stack, pos - 1); } else if (sep == "//") { while (pos >= 0) { _logger.trace("--pos: " + (pos -1)); if (match_stack_from_pos(rest, stack, pos - 1)) { return true; } else { pos--; } } if (pos < 0) { return false; } } } return true; } // StackMatcher end xml_digester.digester = function(xml, options) { return new XmlDigester(xml, options); }; xml_digester.XmlDigester = XmlDigester; xml_digester.DefaultHandler = DefaultHandler; xml_digester.OrderedElementsHandler = OrderedElementsHandler; xml_digester.SkipElementsHandler = SkipElementsHandler; xml_digester._logger = _logger; xml_digester._match_stack = match_stack; })(typeof exports === "undefined" ? sax = {} : exports);