<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h3>basic usage</h3>
<pre>var parser = new Drycleaner(someString);
var str = parser.escape().str;</pre>
<textarea id="basic-edit"></textarea> <br />
result: <pre id="basic-disp"></pre>
<h3>with exceptions</h3>
<pre>var parser = new Drycleaner(someString);
var str = parser.escape({except: ["&"]}).str;</pre>
<textarea id="except-edit"></textarea> <br />
result: <pre id="except-disp"></pre>
<h3>cache initial argument</h3>
<pre>var parser = new Drycleaner("<b>initial string</b>");
var html = parser.escape({result: "res"})
var cache = html.str;
var processed = html.res;</pre>
<textarea id="result-edit"><b>initial string</b></textarea> <br />
cache: <pre id="result-disp-init"></pre>
result: <pre id="result-disp"></pre>
<h3>enable white space parsing</h3>
<pre>var parser = new Drycleaner(someString);
var str = parser.escape({nbsp: true}).str;</pre>
<textarea id="space-edit"></textarea> <br />
result: <pre id="space-disp"></pre>
<h3>wrap result in html tag</h3>
<pre>var parser = new Drycleaner("<b>hello world</b>");
var str = parser.escape()
.wrap({ el: "pre", result: "pre" })
.escape({ workOn: "pre", result: "post" })
.post
.outerHTML;</pre>
stringified result: <span id="wrap-disp"></span> <br />
rendered result: <span id="wrap-ren"></span>
<script src="drycleaner.js"></script>
<script src="script.js"></script>
</body>
</html>
// Code goes here
(function(obj) {
function basic() {
var parser = new Drycleaner(this.value);
var str = parser.escape().str;
obj.basic.disp.textContent = str;
}
function except() {
var parser = new Drycleaner(this.value);
var str = parser.escape({except: ["&"]}).str;
obj.except.disp.textContent = str;
}
function result() {
var parser = new Drycleaner(this.value);
var html = parser.escape({to: "res"});
if (!obj.result.initState) {
obj.result.initState = true;
obj.result.init.textContent = html.str;
}
obj.result.disp.textContent = html.res;
}
function space() {
var parser = new Drycleaner(this.value);
var str = parser.escape({nbsp: true}).str;
obj.space.disp.textContent = str;
}
function wrap() {
var parser = new Drycleaner("<b>hello world</b>");
var str = parser.escape()
.wrap({ el: "pre", to: "pre" })
.escape({ from: "pre", to: "post" })
obj.wrap.ren.appendChild(str.pre)
obj.wrap.disp.textContent = str.pre.outerHTML
}
obj.basic.edit.addEventListener("keyup", basic, false);
obj.except.edit.addEventListener("keyup", except, false);
obj.result.edit.addEventListener("keyup", result, false);
obj.space.edit.addEventListener("keyup", space, false);
wrap();
})({
basic: {
edit: document.getElementById("basic-edit"),
disp: document.getElementById("basic-disp")
},
except: {
edit: document.getElementById("except-edit"),
disp: document.getElementById("except-disp")
},
result: {
edit: document.getElementById("result-edit"),
init: document.getElementById("result-disp-init"),
disp: document.getElementById("result-disp")
},
space: {
edit: document.getElementById("space-edit"),
disp: document.getElementById("space-disp")
},
wrap: {
disp: document.getElementById("wrap-disp"),
ren: document.getElementById("wrap-ren")
}
});
/* Styles go here */
pre {
margin:0;
padding:0;
background-color:#E6E6E6;
width:100%;
}
textarea {
margin-top:10px;
width:99%;
}
.edit {
background-color:#D8D8D8;
height:50%;
width:100%;
}
#wrap-disp {
font-family: "courier";
font-size: 11pt;
}
demo for drycleaner, a tiny library for manipulating html entity strings
github.com/daniellizik/drycleaner
/**
* List of defaults. It's easier to pass a "ghost" version
* if a user-specified options object is not passed.
*
* @nbsp {boolean}: if true, all nbsp will be replaced as default is false
* @except {array}: array of characters/strings that will not be transformed
* @from {string}: name of object property that will be worked on
* @to {string}: name of object property that is to be assigned to product of conversion
* @wrap {object}: wraps initializing string in html element, takes el and style properties
* @wrap.el {string}: name of html element you want to wrap string in
* @wrap.style {object}: inline style of the html element
*
**/
var Drycleaner = (function() {
"use strict";
var matcher = {
space: /^\s*$/g,
dom: /\[object HTML\w+Element\]/,
str: "[object String]",
obj: "[object Object]",
arr: "[object Array]",
rx: "[object RegExp]"
};
var HTML_ENTITIES = [
{ str: "&", char: "&", charRx: /&(?!.*\;)/g },
{ str: """, char: '"', charRx: /\"/g },
{ str: "<", char: "<", charRx: /\</g },
{ str: ">", char: ">", charRx: /\>/g },
{ str: "'", char: "'", charRx: /\'/g },
{ str: " ", char: " ", charRx: /\s{1}/g }
];
var err = {
empty: "Cannot pass an empty string.",
notString: "Can only pass a string into drycleaner.",
undeclared: "This property has not been declared.",
notArray: "The except property must be an array.",
rx: "Invalid regular expression.",
notDefined: "Trying to pass undefined variable.",
shouldBeBoolean: "The parameter must be a boolean.",
unspecifiedElement: "Must specifiy an html element in the el property.",
wrongProps: "This option property is not supported.",
reserved: "You cannot set 'from' or 'to' to the name of a publicly available method."
};
var ghostDefaults = {
global: {
from: "str",
to: false
},
convert: {
nbsp: false,
except: [],
},
wrap: {
el: "div",
style: false,
}
};
var conf = [
{
public: "escape",
fn: configurator({ defaults: "convert", from: "charRx", to: "str", exceptId: "char", fn: convertPrimer })
},
{
public: "unescape",
fn: configurator({ defaults: "convert", from: "str", to: "char", exceptId: "str", fn: convertPrimer })
},
{
public: "wrap",
fn: configurator({ defaults: "wrap", fn: wrap })
}
];
function init(input) {
//cannot be "" or only whitespace
if (matcher.space.test(input)) throw new Error(errMsg.empty);
return new Drycleaner(input);
}
function Drycleaner(str) {
this.str = matcher.dom.test(getType(str)) === true ? str.outerHTML : str;
}
//first take config options
function configurator(config) {
//this is the returned function that will be publicly available
return function optionHandler(userOptions) {
//userOptions must an object if something is passed
if (userOptions && getType(userOptions) !== matcher.obj) throw new Error(errMsg.notObject);
//test for invalid options; send user options, global defaults and method-specific options send from configurator arguments
if (getOptionKeys(userOptions, ghostDefaults.global, ghostDefaults[config.defaults]) === false) throw new Error(errMsg.wrongProps);
//merge ghost defaults and user options
var options = !userOptions ? merge({}, ghostDefaults.global, ghostDefaults[config.defaults]) : merge(userOptions, ghostDefaults.global, ghostDefaults[config.defaults]);
//check if user set to or from as one of the public methods
preventOverwrite(options);
//set the result property
var to = options.to === false ? "str" : options.to;
this[to] = config.fn.call(this, config, options);
return this;
}
}
function convertPrimer(config, options) {
//except option must be array (if not specified, it comes from ghost defaults, which by default is an array)
if (getType(options.except) !== matcher.arr) throw new Error(errMsg.notArray);
var tmpStr = setFrom.call(this, options);
return loopCharacters.call(this, tmpStr, config, options);
}
function setFrom(options) {
if (options.from && !this[options.from]) throw new Error(errMsg.undeclared);
if (options.from === "str") return this.str;
if (options.from && this[options.from]) {
if (matcher.dom.test(getType(this[options.from]))) return this[options.from].outerHTML;
else return this[options.from];
}
}
function loopCharacters(tmpStr, config, options) {
var rxPre;
for (var i = 0; i < HTML_ENTITIES.length; i++) {
var obj = HTML_ENTITIES[i];
//matches with exception array
var index = options.except.indexOf(obj[config.exceptId]);
//if the current html entity is being excepted via user options replace the same thing (nothing is being converted)
var rxPre = index === -1 ? makeRegex(obj[config.from]) : obj[config.to];
var rxPost = (options.nbsp === true && obj.char === " ") ? rxPre : obj[config.to];
var rxFinal = (options.nbsp === false && obj.char === " ") ? obj[config.to] : rxPre;
tmpStr = convert(tmpStr, rxFinal, obj[config.to]);
}
return tmpStr;
}
function convert(str, regex, replacer) {
if (regex === replacer) return str;
var tmp = str.replace(regex, replacer);
return tmp;
}
function wrap(config, options) {
if (options.el === false) return this.str;
if (options && !options.el) throw new Error(errMsg.unspecifiedElement);
var wrap = document.createElement(options.el);
if (options.style !== false) {
for (var p in options.style) wrap.style[p] = options.style[p];
}
//using outerHTML or innerHTML will escape html characters again
wrap.textContent = this[options.from];
return wrap;
}
function makeRegex(input) {
if (getType(input) === matcher.rx) return input;
if (getType(input) === matcher.str) return new RegExp(input, "g");
}
function getOptionKeys(options, globals, defaults) {
//if no options are supplied that means they couldn't have supplied any wrong options
if (!options) return true;
var userKeys = getKeys(options);
var defaultKeys = getKeys(defaults).concat(getKeys(globals));
for (var i = 0; i < userKeys.length; i++) {
if (defaultKeys.indexOf(userKeys[i]) === -1) return false;
}
return true;
}
function getKeys(obj) {
var keys = [];
for (var p in obj) {
if (keys.indexOf(p) === -1) keys.push(p);
}
return keys;
}
function preventOverwrite(obj) {
var publicMethods = conf.map(function(o){ return o.public; });
if (publicMethods.indexOf(obj.to) > -1 || publicMethods.indexOf(obj.from) > -1) throw new Error(err.reserved);
}
function merge() {
var args = Array.prototype.slice.call(arguments);
var base = args.slice(0, 1)[0];
var objs = args.slice(1);
for (var i = 0; i < objs.length; i++) {
for (var p in objs[i]) {
if (!base[p]) {
base[p] = objs[i][p];
}
}
}
return base;
}
function getType(str) {
return Object.prototype.toString.call(str);
}
//set public methods
conf.forEach(function(obj){ Drycleaner.prototype[obj.public] = obj.fn; });
return init;
})();