text: dojo.provide("dojox.dtl.filter.strings");
  
  dojo.require("dojox.dtl.filter.htmlstrings");
  dojo.require("dojox.string.sprintf");
  dojo.require("dojox.string.tokenize");
  
  dojo.mixin(dojox.dtl.filter.strings, {
  	_urlquote: function(/*String*/ url, /*String?*/ safe){
  		if(!safe){
  			safe = "/";
  		}
  		return dojox.string.tokenize(url, /([^\w-_.])/g, function(token){
  			if(safe.indexOf(token) == -1){
  				if(token == " "){
  					return "+";
  				}else{
  					return "%" + token.charCodeAt(0).toString(16).toUpperCase();
  				}
  			}
  			return token;
  		}).join("");
  	},
  	addslashes: function(value){
  		// summary: Adds slashes - useful for passing strings to JavaScript, for example.
  		return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/'/g, "\\'");
  	},
  	capfirst: function(value){
  		// summary: Capitalizes the first character of the value
  		value = "" + value;
  		return value.charAt(0).toUpperCase() + value.substring(1);
  	},
  	center: function(value, arg){
  		// summary: Centers the value in a field of a given width
  		arg = arg || value.length;
  		value = value + "";
  		var diff = arg - value.length;
  		if(diff % 2){
  			value = value + " ";
  			diff -= 1;
  		}
  		for(var i = 0; i < diff; i += 2){
  			value = " " + value + " ";
  		}
  		return value;
  	},
  	cut: function(value, arg){
  		// summary: Removes all values of arg from the given string
  		arg = arg + "" || "";
  		value = value + "";
  		return value.replace(new RegExp(arg, "g"), "");
  	},
  	_fix_ampersands: /&(?!(\w+|#\d+);)/g,
  	fix_ampersands: function(value){
  		// summary: Replaces ampersands with ``&amp;`` entities
  		return value.replace(dojox.dtl.filter.strings._fix_ampersands, "&amp;");
  	},
  	floatformat: function(value, arg){
  		// summary: Format a number according to arg
  		// description:
  		//		If called without an argument, displays a floating point
  		//		number as 34.2 -- but only if there's a point to be displayed.
  		//		With a positive numeric argument, it displays that many decimal places
  		//		always.
  		//		With a negative numeric argument, it will display that many decimal
  		//		places -- but only if there's places to be displayed.
  		arg = parseInt(arg || -1);
  		value = parseFloat(value);
  		var m = value - value.toFixed(0);
  		if(!m && arg < 0){
  			return value.toFixed();
  		}
  		value = value.toFixed(Math.abs(arg));
  		return (arg < 0) ? parseFloat(value) + "" : value;
  	},
  	iriencode: function(value){
  		return dojox.dtl.filter.strings._urlquote(value, "/#%[]=:;$&()+,!");
  	},
  	linenumbers: function(value){
  		// summary: Displays text with line numbers
  		var df = dojox.dtl.filter;
  		var lines = value.split("\n");
  		var output = [];
  		var width = (lines.length + "").length;
  		for(var i = 0, line; i < lines.length; i++){
  			line = lines[i];
  			output.push(df.strings.ljust(i + 1, width) + ". " + dojox.dtl._base.escape(line));
  		}
  		return output.join("\n");
  	},
  	ljust: function(value, arg){
  		value = value + "";
  		arg = parseInt(arg);
  		while(value.length < arg){
  			value = value + " ";
  		}
  		return value;
  	},
  	lower: function(value){
  		// summary: Converts a string into all lowercase
  		return (value + "").toLowerCase();
  	},
  	make_list: function(value){
  		// summary:
  		//		Returns the value turned into a list. For an integer, it's a list of
  		//		digits. For a string, it's a list of characters.
  		var output = [];
  		if(typeof value == "number"){
  			value = value + "";
  		}
  		if(value.charAt){
  			for(var i = 0; i < value.length; i++){
  				output.push(value.charAt(i));
  			}
  			return output;
  		}
  		if(typeof value == "object"){
  			for(var key in value){
  				output.push(value[key]);
  			}
  			return output;
  		}
  		return [];
  	},
  	rjust: function(value, arg){
  		value = value + "";
  		arg = parseInt(arg);
  		while(value.length < arg){
  			value = " " + value;
  		}
  		return value;
  	},
  	slugify: function(value){
  		// summary: Converts to lowercase, removes
  		//		non-alpha chars and converts spaces to hyphens
  		value = value.replace(/[^\w\s-]/g, "").toLowerCase();
  		return value.replace(/[\-\s]+/g, "-");
  	},
  	_strings: {},
  	stringformat: function(value, arg){
  		// summary:
  		//		Formats the variable according to the argument, a string formatting specifier.
  		//		This specifier uses Python string formating syntax, with the exception that
  		//		the leading "%" is dropped.
  		arg = "" + arg;
  		var strings = dojox.dtl.filter.strings._strings;
  		if(!strings[arg]){
  			strings[arg] = new dojox.string.sprintf.Formatter("%" + arg);
  		}
  		return strings[arg].format(value);
  	},
  	title: function(value){
  		// summary: Converts a string into titlecase
  		var last, title = "";
  		for(var i = 0, current; i < value.length; i++){
  			current = value.charAt(i);
  			if(last == " " || last == "\n" || last == "\t" || !last){
  				title += current.toUpperCase();
  			}else{
  				title += current.toLowerCase();
  			}
  			last = current;
  		}
  		return title;
  	},
  	_truncatewords: /[ \n\r\t]/,
  	truncatewords: function(value, arg){
  		// summary: Truncates a string after a certain number of words
  		// arg: Integer
  		//		Number of words to truncate after
  		arg = parseInt(arg);
  		if(!arg){
  			return value;
  		}
  
  		for(var i = 0, j = value.length, count = 0, current, last; i < value.length; i++){
  			current = value.charAt(i);
  			if(dojox.dtl.filter.strings._truncatewords.test(last)){
  				if(!dojox.dtl.filter.strings._truncatewords.test(current)){
  					++count;
  					if(count == arg){
  						return value.substring(0, j + 1);
  					}
  				}
  			}else if(!dojox.dtl.filter.strings._truncatewords.test(current)){
  				j = i;
  			}
  			last = current;
  		}
  		return value;
  	},
  	_truncate_words: /(&.*?;|<.*?>|(\w[\w\-]*))/g,
  	_truncate_tag: /<(\/)?([^ ]+?)(?: (\/)| .*?)?>/,
  	_truncate_singlets: { br: true, col: true, link: true, base: true, img: true, param: true, area: true, hr: true, input: true },
  	truncatewords_html: function(value, arg){
  		arg = parseInt(arg);
  
  		if(arg <= 0){
  			return "";
  		}
  
  		var strings = dojox.dtl.filter.strings;
  		var words = 0;
  		var open = [];
  
  		var output = dojox.string.tokenize(value, strings._truncate_words, function(all, word){
  			if(word){
  				// It's an actual non-HTML word
  				++words;
  				if(words < arg){
  					return word;
  				}else if(words == arg){
  					return word + " ...";
  				}
  			}
  			// Check for tag
  			var tag = all.match(strings._truncate_tag);
  			if(!tag || words >= arg){
  				// Don't worry about non tags or tags after our truncate point
  				return;
  			}
  			var closing = tag[1];
  			var tagname = tag[2].toLowerCase();
  			var selfclosing = tag[3];
  			if(closing || strings._truncate_singlets[tagname]){
  			}else if(closing){
  				var i = dojo.indexOf(open, tagname);
  				if(i != -1){
  					open = open.slice(i + 1);
  				}
  			}else{
  				open.unshift(tagname);
  			}
  			return all;
  		}).join("");
  
  		output = output.replace(/\s+$/g, "");
  
  		for(var i = 0, tag; tag = open[i]; i++){
  			output += "</" + tag + ">";
  		}
  
  		return output;
  	},
  	upper: function(value){
  		return value.toUpperCase();
  	},
  	urlencode: function(value){
  		return dojox.dtl.filter.strings._urlquote(value);
  	},
  	_urlize: /^((?:[(>]|&lt;)*)(.*?)((?:[.,)>\n]|&gt;)*)$/,
  	_urlize2: /^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$/,
  	urlize: function(value){
  		return dojox.dtl.filter.strings.urlizetrunc(value);
  	},
  	urlizetrunc: function(value, arg){
  		arg = parseInt(arg);
  		return dojox.string.tokenize(value, /(\S+)/g, function(word){
  			var matches = dojox.dtl.filter.strings._urlize.exec(word);
  			if(!matches){
  				return word;
  			}
  			var lead = matches[1];
  			var middle = matches[2];
  			var trail = matches[3];
  
  			var startsWww = middle.indexOf("www.") == 0;
  			var hasAt = middle.indexOf("@") != -1;
  			var hasColon = middle.indexOf(":") != -1;
  			var startsHttp = middle.indexOf("http://") == 0;
  			var startsHttps = middle.indexOf("https://") == 0;
  			var firstAlpha = /[a-zA-Z0-9]/.test(middle.charAt(0));
  			var last4 = middle.substring(middle.length - 4);
  
  			var trimmed = middle;
  			if(arg > 3){
  				trimmed = trimmed.substring(0, arg - 3) + "...";
  			}
  
  			if(startsWww || (!hasAt && !startsHttp && middle.length && firstAlpha && (last4 == ".org" || last4 == ".net" || last4 == ".com"))){
  				return '<a href="http://' + middle + '" rel="nofollow">' + trimmed + '</a>';
  			}else if(startsHttp || startsHttps){
  				return '<a href="' + middle + '" rel="nofollow">' + trimmed + '</a>';
  			}else if(hasAt && !startsWww && !hasColon && dojox.dtl.filter.strings._urlize2.test(middle)){
  				return '<a href="mailto:' + middle + '">' + middle + '</a>';
  			}
  			return word;
  		}).join("");
  	},
  	wordcount: function(value){
  		value = dojo.trim(value);
  		if(!value){ return 0; }
  		return value.split(/\s+/g).length;
  	},
  	wordwrap: function(value, arg){
  		arg = parseInt(arg);
  		// summary: Wraps words at specified line length
  		var output = [];
  		var parts = value.split(/\s+/g);
  		if(parts.length){
  			var word = parts.shift();
  			output.push(word);
  			var pos = word.length - word.lastIndexOf("\n") - 1;
  			for(var i = 0; i < parts.length; i++){
  				word = parts[i];
  				if(word.indexOf("\n") != -1){
  					var lines = word.split(/\n/g);
  				}else{
  					var lines = [word];
  				}
  				pos += lines[0].length + 1;
  				if(arg && pos > arg){
  					output.push("\n");
  					pos = lines[lines.length - 1].length;
  				}else{
  					output.push(" ");
  					if(lines.length > 1){
  						pos = lines[lines.length - 1].length;
  					}
  				}
  				output.push(word);
  			}
  		}
  		return output.join("");
  	}
  });