/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileOverview A collection of various string utilities. */ // TODO: uncomment with import works with * import("funhtml.{TABLE,TR,TH,TD,OL,LI}"); import("jsutils.{object,eachProperty}"); //import("funhtml.*"); jimport("java.util.Random"); jimport("java.lang.System.currentTimeMillis"); /** * Removes leading and trailing whitespace from a string. * @param {string} str * @return {string} The trimmed string. */ function trim(str) { return str.replace(/^\s+|\s+$/g, ""); } //---------------------------------------------------------------- // String prototype enhancements. // TODO: should we move this to a new library "enhancedstring"? //---------------------------------------------------------------- startsWith = function(s, prefix) { return (s.indexOf(prefix) == 0); }; endsWith = function(s, suffix) { return (s.substr(s.length - suffix.length) == suffix); }; contains = function(s, x) { return (s.indexOf(x) != -1); }; makeTitle = function(s) { if (! s) return; return s.split(" ").map(function(x) { return x[0].toUpperCase() + x.substr(1) }).join(" "); } repeat = function(s, n) { var out = []; while (n-- > 0) { out.push(s); } return out.join(''); } /* * Helper function that converts a raw string to an HTML string, with * character entities replaced by appropriate HTML codes, and newlines * rentered as BRs. * *

A more general version of this function is toHTML(), which can operate * on not just strings, but any object. * * @param {string} str the raw string * @return {string} HTML-formatted string */ function _stringToHTML(str) { return String(net.appjet.oui.Util.stringToHTML(str)); } // used to convert an object to HTML when the object does not have a // toHTML method. // function _coerceObjectToHTML(obj) { var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0}); eachProperty(obj, function(name, value) { t.push(TR(TH(String(name)), TD(String(value)))); }); return toHTML(t); } // Converts an array to an HTML list by listing its properties and // recursively converting the values to HTML by calling toHTML() on // each of them. function _objectToOL(obj) { var l = OL(); eachProperty(obj, function(name, value) { l.push(LI({value: name}, value)); }); return l; } function _sameProperties(obj1, obj2) { if (typeof(obj1) != 'object' || typeof(obj2) != 'object') return typeof(obj1) == typeof(obj2); var mismatch = 0; eachProperty(obj1, function(name) { if (! obj2.hasOwnProperty(name)) { mismatch++; }}); eachProperty(obj2, function(name) { if (! obj1.hasOwnProperty(name)) { mismatch++; }}); return mismatch < 2; } // // for pretty-printing arrays. needs a lot of work. // function _arrayToHTML(a) { if (a.length === 0) { return ""; } if (typeof(a[0]) != 'object') { return toHTML(_objectToOL(a)); } else if (! _sameProperties(a[0], a[1])) { return toHTML(_objectToOL(a)); } else { return _likeObjectsToHTML(function (f) { a.forEach(function(value, i) { f({index: i}, value, {}); });}, null); } } /** @ignore */ // a foreaching function that takes three arguments: properties to put first, // properties to put in the middle, and properties to put at the end. // and a table header (with large colspan) function _likeObjectsToHTML(forEachFunction, tophead) { objs = []; prepnames = new StringSet(); objpnames = new StringSet(); postpnames = new StringSet(); rows = []; var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0}); var head = TR(); if (tophead) t.push(tophead); t.push(head); var butWaitTheresMore = false; var howManyMore = 0; forEachFunction(function(pre, o, post) { if (objs.length >= 10) { butWaitTheresMore = true; howManyMore++; return; } objs.push({pre: pre, o: o, post: post}); var tr = TR(); rows.push(tr); t.push(tr); eachProperty(pre, function(name) { prepnames.add(name); }); eachProperty(o, function(name) { objpnames.add(name); }); eachProperty(post, function(name) { postpnames.add(name); }); }); var numpnames = 0; var appendTDsForPropName = function (where) { return function(name) { numpnames++; head.push(TH(name)); for (var j = 0; j < objs.length; ++j) { if (! (objs[j][where] === undefined) && ! (objs[j][where][name] === undefined)) rows[j].push(TD(String(objs[j][where][name]))); else rows[j].push(TD()); } }; }; prepnames.forEach(appendTDsForPropName("pre")); objpnames.forEach(appendTDsForPropName("o")); postpnames.forEach(appendTDsForPropName("post")); if (butWaitTheresMore) { t.push(TR(TD({colspan: numpnames}, "..."+howManyMore+ " additional element"+(howManyMore == 1 ? "" : "s")+" omitted..."))); } return toHTML(t); } /** * Returns a string with any number of variables substituted in, as * popularized by C's function of the same name. Some common substitutions: * *

* *

Each time one of these "slot" appears in your format string, the next argument is displayed * according to the type of slot you specified. * *

AppJet supports * Java's specification of printf, which has a ton of features, including selecting * arguments out of order, formatting dates and times, and specifying how many characters * wide each slot should be. * * @example var x = 5; response.write(sprintf("an integer: %d", x)); response.write(sprintf("Two strings: [%s] and [%s].", "string one", "string two")); * * @param {string} formatString * @param {*} arg1 * @param {*} arg2 * @param {*} arg3 ... */ function sprintf(formatString, arg1, arg2, etc) { if (typeof(formatString) != 'string') { throw new Error('printf takes a string as the first argument.'); } var argList = java.lang.reflect.Array.newInstance(java.lang.Object, arguments.length-1); for (var i = 1; i < arguments.length; i++) { if (arguments[i] instanceof Date) argList[i-1] = arguments[i].getTime(); else argList[i-1] = arguments[i]; } return String(net.appjet.ajstdlib.printf.printf(formatString, argList)); }; /** * Replaces keys of data found in string with their corresponding values. * *

(Inspired by http://javascript.crockford.com/remedial.html) * * @example var data = {name: "Aaron", age: 25, today: new Date()}; print(supplant(data, """ {name}'s age is {age} years, as of {today}. """)); * @param {object} data dictionary of values * @param {string} str * @return {string} str with keys of data replaced by their values */ function supplant(data, str) { var s = str; var o = data; function rep(a, b) { var r = o[b]; if (typeof(r) != 'undefined') { return r; } else { return a; } } return s.replace(/{([^{}]*)}/g, rep); }; //---------------------------------------------------------------- // raw printing //---------------------------------------------------------------- var _raw_prototype; /** * Used for printing un-escaped HTML, such as your own HTML tags. * *

Normally, printing a string will cause it to be translated * so that it appears the same on the screen as it did in your code. * If you're writing your own HTML, you don't want it to be processed * this way. Wrapping a string in html(...) by-passes normal printing behavior, * so that print(html(" -- html goes here ---")) will write the HTML * directly to the page. * *

If you want to mix your own HTML code with HTML code generated from a * tag object, you can get the HTML for the tag by calling its toHTML(...) method. * *

Multiple arguments to html(...) will be concatenated into one string. * * @example print(html(""" <br /> <br /> <div><p>Here is some text inside a P inside a DIV.</p> </div> <br /> """)); * * @param {string} text the raw text * @return {object} an object which, when printed, prints the raw html text */ function html(text) { if (!_raw_prototype) { _raw_prototype = object(Object.prototype); _raw_prototype.toString = function() { return this._text; }; _raw_prototype.toHTML = function() { return this._text; }; } var rawObj = object(_raw_prototype); rawObj._text = Array.prototype.map.call(arguments, String).join(''); return rawObj; } /** * This function is used by print(...) to convert a string or object * into nice-looking printable HTML. It may be useful in conjunction * with html(...) if you wish to work directly with HTML. * *

You can control how toHTML(...) (and therefore print(...)) behave on an object * by giving that object a .toHTML() function. * * @param {*} x any javascript variable * @return {string} html-formatted string */ function toHTML(x) { if (typeof(x) == 'undefined') { return 'undefined'; } if (x === null) { return 'null'; } if (typeof x == "string" || (x instanceof java.lang.String)) { return _stringToHTML(x); } if (typeof(x.toHTML) == "function") { return x.toHTML(); } if (typeof(x) == "xml") { return _stringToHTML(x.toSource()); } if (x instanceof Array) { return _arrayToHTML(x); } if (x instanceof Date) { var pieces = x.toString().split(" "); return pieces.slice(0, 5).join(' ') + ' ' + pieces[6]; } if (typeof(x) == "object") { return _coerceObjectToHTML(x); } // TODO: add more types to auto-printing, such as functions, // numbers, what else? return _stringToHTML(""+x); } /** * Generates a random string of specified length using upper-case letters, lower-case letters, and numbers. */ var _jrand = new Random(currentTimeMillis()); function randomString(nchars) { var result = ''; // 48-58 or 65-91 or 97-123 (inclusive-exclusive) // 0-10 or 0-26 or 0-26 // 0-62 for (var i = 0; i < nchars; i++) { var x = _jrand.nextInt(62); var code; if (x < 10) { code = x + 48; } if (x >= 10 && x < 36) { code = x - 10 + 65/*a*/; } if (x >= 36) { code = x - 36 + 97/*A*/; } result += String.fromCharCode(code); } return result; } function md5(x) { return net.appjet.ajstdlib.md5.md5(x); } function randomHash(len) { var x = md5(""+_jrand.nextDouble()*1e12+_jrand.nextDouble()*1e12); if (len) { return String(x).substr(0,len); } else { return x; } } function gzip(x) { return net.appjet.oui.Util.gzip(x) } function isNumeric(x) { return !!(/^\d+$/.test(x)); }