Jelajahi Sumber

ESM: Ported HTML, Unicode, Quoted Printable and Endian operations

n1474335 7 tahun lalu
induk
melakukan
5362508a99

+ 79 - 0
src/core/operations/EscapeUnicodeCharacters.mjs

@@ -0,0 +1,79 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * Escape Unicode Characters operation
+ */
+class EscapeUnicodeCharacters extends Operation {
+
+    /**
+     * EscapeUnicodeCharacters constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Escape Unicode Characters";
+        this.module = "Default";
+        this.description = "Converts characters to their unicode-escaped notations.<br><br>Supports the prefixes:<ul><li><code>\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>σου</code> becomes <code>\\u03C3\\u03BF\\u03C5</code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Prefix",
+                "type": "option",
+                "value": ["\\u", "%u", "U+"]
+            },
+            {
+                "name": "Encode all chars",
+                "type": "boolean",
+                "value": false
+            },
+            {
+                "name": "Padding",
+                "type": "number",
+                "value": 4
+            },
+            {
+                "name": "Uppercase hex",
+                "type": "boolean",
+                "value": true
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const regexWhitelist = /[ -~]/i,
+            [prefix, encodeAll, padding, uppercaseHex] = args;
+
+        let output = "",
+            character = "";
+
+        for (let i = 0; i < input.length; i++) {
+            character = input[i];
+            if (!encodeAll && regexWhitelist.test(character)) {
+                // It’s a printable ASCII character so don’t escape it.
+                output += character;
+                continue;
+            }
+
+            let cp = character.codePointAt(0).toString(16);
+            if (uppercaseHex) cp = cp.toUpperCase();
+            output += prefix + cp.padStart(padding, "0");
+        }
+
+        return output;
+    }
+
+}
+
+export default EscapeUnicodeCharacters;

+ 335 - 0
src/core/operations/FromHTMLEntity.mjs

@@ -0,0 +1,335 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * From HTML Entity operation
+ */
+class FromHTMLEntity extends Operation {
+
+    /**
+     * FromHTMLEntity constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "From HTML Entity";
+        this.module = "Default";
+        this.description = "Converts HTML entities back to characters<br><br>e.g. <code>&amp;<span>amp;</span></code> becomes <code>&amp;</code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const regex = /&(#?x?[a-zA-Z0-9]{1,8});/g;
+        let output = "",
+            m,
+            i = 0;
+
+        while ((m = regex.exec(input))) {
+            // Add up to match
+            for (; i < m.index;)
+                output += input[i++];
+
+            // Add match
+            const bite = entityToByte[m[1]];
+            if (bite) {
+                output += Utils.chr(bite);
+            } else if (!bite && m[1][0] === "#" && m[1].length > 1 && /^#\d{1,6}$/.test(m[1])) {
+                // Numeric entity (e.g. &#10;)
+                const num = m[1].slice(1, m[1].length);
+                output += Utils.chr(parseInt(num, 10));
+            } else if (!bite && m[1][0] === "#" && m[1].length > 3 && /^#x[\dA-F]{2,8}$/i.test(m[1])) {
+                // Hex entity (e.g. &#x3A;)
+                const hex = m[1].slice(2, m[1].length);
+                output += Utils.chr(parseInt(hex, 16));
+            } else {
+                // Not a valid entity, print as normal
+                for (; i < regex.lastIndex;)
+                    output += input[i++];
+            }
+
+            i = regex.lastIndex;
+        }
+        // Add all after final match
+        for (; i < input.length;)
+            output += input[i++];
+
+        return output;
+    }
+
+}
+
+
+/**
+ * Lookup table to translate HTML entity codes to their byte values.
+ */
+const entityToByte = {
+    "quot": 34,
+    "amp": 38,
+    "apos": 39,
+    "lt": 60,
+    "gt": 62,
+    "nbsp": 160,
+    "iexcl": 161,
+    "cent": 162,
+    "pound": 163,
+    "curren": 164,
+    "yen": 165,
+    "brvbar": 166,
+    "sect": 167,
+    "uml": 168,
+    "copy": 169,
+    "ordf": 170,
+    "laquo": 171,
+    "not": 172,
+    "shy": 173,
+    "reg": 174,
+    "macr": 175,
+    "deg": 176,
+    "plusmn": 177,
+    "sup2": 178,
+    "sup3": 179,
+    "acute": 180,
+    "micro": 181,
+    "para": 182,
+    "middot": 183,
+    "cedil": 184,
+    "sup1": 185,
+    "ordm": 186,
+    "raquo": 187,
+    "frac14": 188,
+    "frac12": 189,
+    "frac34": 190,
+    "iquest": 191,
+    "Agrave": 192,
+    "Aacute": 193,
+    "Acirc": 194,
+    "Atilde": 195,
+    "Auml": 196,
+    "Aring": 197,
+    "AElig": 198,
+    "Ccedil": 199,
+    "Egrave": 200,
+    "Eacute": 201,
+    "Ecirc": 202,
+    "Euml": 203,
+    "Igrave": 204,
+    "Iacute": 205,
+    "Icirc": 206,
+    "Iuml": 207,
+    "ETH": 208,
+    "Ntilde": 209,
+    "Ograve": 210,
+    "Oacute": 211,
+    "Ocirc": 212,
+    "Otilde": 213,
+    "Ouml": 214,
+    "times": 215,
+    "Oslash": 216,
+    "Ugrave": 217,
+    "Uacute": 218,
+    "Ucirc": 219,
+    "Uuml": 220,
+    "Yacute": 221,
+    "THORN": 222,
+    "szlig": 223,
+    "agrave": 224,
+    "aacute": 225,
+    "acirc": 226,
+    "atilde": 227,
+    "auml": 228,
+    "aring": 229,
+    "aelig": 230,
+    "ccedil": 231,
+    "egrave": 232,
+    "eacute": 233,
+    "ecirc": 234,
+    "euml": 235,
+    "igrave": 236,
+    "iacute": 237,
+    "icirc": 238,
+    "iuml": 239,
+    "eth": 240,
+    "ntilde": 241,
+    "ograve": 242,
+    "oacute": 243,
+    "ocirc": 244,
+    "otilde": 245,
+    "ouml": 246,
+    "divide": 247,
+    "oslash": 248,
+    "ugrave": 249,
+    "uacute": 250,
+    "ucirc": 251,
+    "uuml": 252,
+    "yacute": 253,
+    "thorn": 254,
+    "yuml": 255,
+    "OElig": 338,
+    "oelig": 339,
+    "Scaron": 352,
+    "scaron": 353,
+    "Yuml": 376,
+    "fnof": 402,
+    "circ": 710,
+    "tilde": 732,
+    "Alpha": 913,
+    "Beta": 914,
+    "Gamma": 915,
+    "Delta": 916,
+    "Epsilon": 917,
+    "Zeta": 918,
+    "Eta": 919,
+    "Theta": 920,
+    "Iota": 921,
+    "Kappa": 922,
+    "Lambda": 923,
+    "Mu": 924,
+    "Nu": 925,
+    "Xi": 926,
+    "Omicron": 927,
+    "Pi": 928,
+    "Rho": 929,
+    "Sigma": 931,
+    "Tau": 932,
+    "Upsilon": 933,
+    "Phi": 934,
+    "Chi": 935,
+    "Psi": 936,
+    "Omega": 937,
+    "alpha": 945,
+    "beta": 946,
+    "gamma": 947,
+    "delta": 948,
+    "epsilon": 949,
+    "zeta": 950,
+    "eta": 951,
+    "theta": 952,
+    "iota": 953,
+    "kappa": 954,
+    "lambda": 955,
+    "mu": 956,
+    "nu": 957,
+    "xi": 958,
+    "omicron": 959,
+    "pi": 960,
+    "rho": 961,
+    "sigmaf": 962,
+    "sigma": 963,
+    "tau": 964,
+    "upsilon": 965,
+    "phi": 966,
+    "chi": 967,
+    "psi": 968,
+    "omega": 969,
+    "thetasym": 977,
+    "upsih": 978,
+    "piv": 982,
+    "ensp": 8194,
+    "emsp": 8195,
+    "thinsp": 8201,
+    "zwnj": 8204,
+    "zwj": 8205,
+    "lrm": 8206,
+    "rlm": 8207,
+    "ndash": 8211,
+    "mdash": 8212,
+    "lsquo": 8216,
+    "rsquo": 8217,
+    "sbquo": 8218,
+    "ldquo": 8220,
+    "rdquo": 8221,
+    "bdquo": 8222,
+    "dagger": 8224,
+    "Dagger": 8225,
+    "bull": 8226,
+    "hellip": 8230,
+    "permil": 8240,
+    "prime": 8242,
+    "Prime": 8243,
+    "lsaquo": 8249,
+    "rsaquo": 8250,
+    "oline": 8254,
+    "frasl": 8260,
+    "euro": 8364,
+    "image": 8465,
+    "weierp": 8472,
+    "real": 8476,
+    "trade": 8482,
+    "alefsym": 8501,
+    "larr": 8592,
+    "uarr": 8593,
+    "rarr": 8594,
+    "darr": 8595,
+    "harr": 8596,
+    "crarr": 8629,
+    "lArr": 8656,
+    "uArr": 8657,
+    "rArr": 8658,
+    "dArr": 8659,
+    "hArr": 8660,
+    "forall": 8704,
+    "part": 8706,
+    "exist": 8707,
+    "empty": 8709,
+    "nabla": 8711,
+    "isin": 8712,
+    "notin": 8713,
+    "ni": 8715,
+    "prod": 8719,
+    "sum": 8721,
+    "minus": 8722,
+    "lowast": 8727,
+    "radic": 8730,
+    "prop": 8733,
+    "infin": 8734,
+    "ang": 8736,
+    "and": 8743,
+    "or": 8744,
+    "cap": 8745,
+    "cup": 8746,
+    "int": 8747,
+    "there4": 8756,
+    "sim": 8764,
+    "cong": 8773,
+    "asymp": 8776,
+    "ne": 8800,
+    "equiv": 8801,
+    "le": 8804,
+    "ge": 8805,
+    "sub": 8834,
+    "sup": 8835,
+    "nsub": 8836,
+    "sube": 8838,
+    "supe": 8839,
+    "oplus": 8853,
+    "otimes": 8855,
+    "perp": 8869,
+    "sdot": 8901,
+    "vellip": 8942,
+    "lceil": 8968,
+    "rceil": 8969,
+    "lfloor": 8970,
+    "rfloor": 8971,
+    "lang": 9001,
+    "rang": 9002,
+    "loz": 9674,
+    "spades": 9824,
+    "clubs": 9827,
+    "hearts": 9829,
+    "diams": 9830,
+};
+
+export default FromHTMLEntity;

+ 61 - 0
src/core/operations/FromQuotedPrintable.mjs

@@ -0,0 +1,61 @@
+/**
+ * Some parts taken from mimelib (http://github.com/andris9/mimelib)
+ * @author Andris Reinman
+ * @license MIT
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * From Quoted Printable operation
+ */
+class FromQuotedPrintable extends Operation {
+
+    /**
+     * FromQuotedPrintable constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "From Quoted Printable";
+        this.module = "Default";
+        this.description = "Converts QP-encoded text back to standard text.";
+        this.inputType = "string";
+        this.outputType = "byteArray";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {byteArray}
+     */
+    run(input, args) {
+        const str = input.replace(/=(?:\r?\n|$)/g, "");
+
+        const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length,
+            bufferLength = str.length - encodedBytesCount * 2,
+            buffer = new Array(bufferLength);
+        let chr, hex,
+            bufferPos = 0;
+
+        for (let i = 0, len = str.length; i < len; i++) {
+            chr = str.charAt(i);
+            if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) {
+                buffer[bufferPos++] = parseInt(hex, 16);
+                i += 2;
+                continue;
+            }
+            buffer[bufferPos++] = chr.charCodeAt(0);
+        }
+
+        return buffer;
+    }
+
+}
+
+export default FromQuotedPrintable;

+ 195 - 0
src/core/operations/ParseColourCode.mjs

@@ -0,0 +1,195 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * Parse colour code operation
+ */
+class ParseColourCode extends Operation {
+
+    /**
+     * ParseColourCode constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Parse colour code";
+        this.module = "Default";
+        this.description = "Converts a colour code in a standard format to other standard formats and displays the colour itself.<br><br><strong>Example inputs</strong><ul><li><code>#d9edf7</code></li><li><code>rgba(217,237,247,1)</code></li><li><code>hsla(200,65%,91%,1)</code></li><li><code>cmyk(0.12, 0.04, 0.00, 0.03)</code></li></ul>";
+        this.inputType = "string";
+        this.outputType = "html";
+        this.args = [];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {html}
+     */
+    run(input, args) {
+        let m = null,
+            r = 0, g = 0, b = 0, a = 1;
+
+        // Read in the input
+        if ((m = input.match(/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i))) {
+            // Hex - #d9edf7
+            r = parseInt(m[1], 16);
+            g = parseInt(m[2], 16);
+            b = parseInt(m[3], 16);
+        } else if ((m = input.match(/rgba?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)(?:,\s?(\d(?:\.\d+)?))?\)/i))) {
+            // RGB or RGBA - rgb(217,237,247) or rgba(217,237,247,1)
+            r = parseFloat(m[1]);
+            g = parseFloat(m[2]);
+            b = parseFloat(m[3]);
+            a = m[4] ? parseFloat(m[4]) : 1;
+        } else if ((m = input.match(/hsla?\((\d{1,3}(?:\.\d+)?),\s?(\d{1,3}(?:\.\d+)?)%,\s?(\d{1,3}(?:\.\d+)?)%(?:,\s?(\d(?:\.\d+)?))?\)/i))) {
+            // HSL or HSLA - hsl(200, 65%, 91%) or hsla(200, 65%, 91%, 1)
+            const h_ = parseFloat(m[1]) / 360,
+                s_ = parseFloat(m[2]) / 100,
+                l_ = parseFloat(m[3]) / 100,
+                rgb_ = ParseColourCode._hslToRgb(h_, s_, l_);
+
+            r = rgb_[0];
+            g = rgb_[1];
+            b = rgb_[2];
+            a = m[4] ? parseFloat(m[4]) : 1;
+        } else if ((m = input.match(/cmyk\((\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?),\s?(\d(?:\.\d+)?)\)/i))) {
+            // CMYK - cmyk(0.12, 0.04, 0.00, 0.03)
+            const c_ = parseFloat(m[1]),
+                m_ = parseFloat(m[2]),
+                y_ = parseFloat(m[3]),
+                k_ = parseFloat(m[4]);
+
+            r = Math.round(255 * (1 - c_) * (1 - k_));
+            g = Math.round(255 * (1 - m_) * (1 - k_));
+            b = Math.round(255 * (1 - y_) * (1 - k_));
+        }
+
+        const hsl_ = ParseColourCode._rgbToHsl(r, g, b),
+            h = Math.round(hsl_[0] * 360),
+            s = Math.round(hsl_[1] * 100),
+            l = Math.round(hsl_[2] * 100);
+        let k = 1 - Math.max(r/255, g/255, b/255),
+            c = (1 - r/255 - k) / (1 - k),
+            y = (1 - b/255 - k) / (1 - k);
+
+        m = (1 - g/255 - k) / (1 - k);
+
+        c = isNaN(c) ? "0" : c.toFixed(2);
+        m = isNaN(m) ? "0" : m.toFixed(2);
+        y = isNaN(y) ? "0" : y.toFixed(2);
+        k = k.toFixed(2);
+
+        const hex = "#" +
+                Math.round(r).toString(16).padStart(2, "0") +
+                Math.round(g).toString(16).padStart(2, "0") +
+                Math.round(b).toString(16).padStart(2, "0"),
+            rgb  = "rgb(" + r + ", " + g + ", " + b + ")",
+            rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")",
+            hsl  = "hsl(" + h + ", " + s + "%, " + l + "%)",
+            hsla = "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")",
+            cmyk = "cmyk(" + c + ", " + m + ", " + y + ", " + k + ")";
+
+        // Generate output
+        return `<div id="colorpicker" style="display: inline-block"></div>
+Hex:  ${hex}
+RGB:  ${rgb}
+RGBA: ${rgba}
+HSL:  ${hsl}
+HSLA: ${hsla}
+CMYK: ${cmyk}
+<script>
+    $('#colorpicker').colorpicker({
+        format: 'rgba',
+        color: '${rgba}',
+        container: true,
+        inline: true,
+    }).on('changeColor', function(e) {
+        var color = e.color.toRGB();
+        document.getElementById('input-text').value = 'rgba(' +
+            color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')';
+        window.app.autoBake();
+    });
+</script>`;
+    }
+
+    /**
+     * Converts an HSL color value to RGB. Conversion formula
+     * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace.
+     * Assumes h, s, and l are contained in the set [0, 1] and
+     * returns r, g, and b in the set [0, 255].
+     *
+     * @author Mohsen (http://stackoverflow.com/a/9493060)
+     *
+     * @param {number} h - The hue
+     * @param {number} s - The saturation
+     * @param {number} l - The lightness
+     * @return {Array} The RGB representation
+     */
+    static _hslToRgb(h, s, l) {
+        let r, g, b;
+
+        if (s === 0){
+            r = g = b = l; // achromatic
+        } else {
+            const hue2rgb = function hue2rgb(p, q, t) {
+                if (t < 0) t += 1;
+                if (t > 1) t -= 1;
+                if (t < 1/6) return p + (q - p) * 6 * t;
+                if (t < 1/2) return q;
+                if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+                return p;
+            };
+
+            const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+            const p = 2 * l - q;
+            r = hue2rgb(p, q, h + 1/3);
+            g = hue2rgb(p, q, h);
+            b = hue2rgb(p, q, h - 1/3);
+        }
+
+        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+    }
+
+    /**
+     * Converts an RGB color value to HSL. Conversion formula
+     * adapted from http://en.wikipedia.org/wiki/HSL_colorSpace.
+     * Assumes r, g, and b are contained in the set [0, 255] and
+     * returns h, s, and l in the set [0, 1].
+     *
+     * @author Mohsen (http://stackoverflow.com/a/9493060)
+     *
+     * @param {number} r - The red color value
+     * @param {number} g - The green color value
+     * @param {number} b - The blue color value
+     * @return {Array} The HSL representation
+     */
+    static _rgbToHsl(r, g, b) {
+        r /= 255; g /= 255; b /= 255;
+        const max = Math.max(r, g, b),
+            min = Math.min(r, g, b),
+            l = (max + min) / 2;
+        let h, s;
+
+        if (max === min) {
+            h = s = 0; // achromatic
+        } else {
+            const d = max - min;
+            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+            switch (max) {
+                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+                case g: h = (b - r) / d + 2; break;
+                case b: h = (r - g) / d + 4; break;
+            }
+            h /= 6;
+        }
+
+        return [h, s, l];
+    }
+}
+
+export default ParseColourCode;

+ 65 - 0
src/core/operations/StripHTMLTags.mjs

@@ -0,0 +1,65 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * Strip HTML tags operation
+ */
+class StripHTMLTags extends Operation {
+
+    /**
+     * StripHTMLTags constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Strip HTML tags";
+        this.module = "Default";
+        this.description = "Removes all HTML tags from the input.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Remove indentation",
+                "type": "boolean",
+                "value": true
+            },
+            {
+                "name": "Remove excess line breaks",
+                "type": "boolean",
+                "value": true
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const [removeIndentation, removeLineBreaks] = args;
+
+        input = Utils.stripHtmlTags(input);
+
+        if (removeIndentation) {
+            input = input.replace(/\n[ \f\t]+/g, "\n");
+        }
+
+        if (removeLineBreaks) {
+            input = input
+                .replace(/^\s*\n/, "") // first line
+                .replace(/(\n\s*){2,}/g, "\n"); // all others
+        }
+
+        return input;
+    }
+
+}
+
+export default StripHTMLTags;

+ 137 - 0
src/core/operations/SwapEndianness.mjs

@@ -0,0 +1,137 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import {toHex, fromHex} from "../lib/Hex";
+import OperationError from "../errors/OperationError";
+
+/**
+ * Swap endianness operation
+ */
+class SwapEndianness extends Operation {
+
+    /**
+     * SwapEndianness constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Swap endianness";
+        this.module = "Default";
+        this.description = "Switches the data from big-endian to little-endian or vice-versa. Data can be read in as hexadecimal or raw bytes. It will be returned in the same format as it is entered.";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Data format",
+                "type": "option",
+                "value": ["Hex", "Raw"]
+            },
+            {
+                "name": "Word length (bytes)",
+                "type": "number",
+                "value": 4
+            },
+            {
+                "name": "Pad incomplete words",
+                "type": "boolean",
+                "value": true
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const [dataFormat, wordLength, padIncompleteWords] = args,
+            result = [],
+            words = [];
+        let i = 0,
+            j = 0,
+            data = [];
+
+        if (wordLength <= 0) {
+            throw new OperationError("Word length must be greater than 0");
+        }
+
+        // Convert input to raw data based on specified data format
+        switch (dataFormat) {
+            case "Hex":
+                data = fromHex(input);
+                break;
+            case "Raw":
+                data = Utils.strToByteArray(input);
+                break;
+            default:
+                data = input;
+        }
+
+        // Split up into words
+        for (i = 0; i < data.length; i += wordLength) {
+            const word = data.slice(i, i + wordLength);
+
+            // Pad word if too short
+            if (padIncompleteWords && word.length < wordLength){
+                for (j = word.length; j < wordLength; j++) {
+                    word.push(0);
+                }
+            }
+
+            words.push(word);
+        }
+
+        // Swap endianness and flatten
+        for (i = 0; i < words.length; i++) {
+            j = words[i].length;
+            while (j--) {
+                result.push(words[i][j]);
+            }
+        }
+
+        // Convert data back to specified data format
+        switch (dataFormat) {
+            case "Hex":
+                return toHex(result);
+            case "Raw":
+                return Utils.byteArrayToUtf8(result);
+            default:
+                return result;
+        }
+    }
+
+    /**
+     * Highlight Swap endianness
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlight(pos, args) {
+        return pos;
+    }
+
+    /**
+     * Highlight Swap endianness in reverse
+     *
+     * @param {Object[]} pos
+     * @param {number} pos[].start
+     * @param {number} pos[].end
+     * @param {Object[]} args
+     * @returns {Object[]} pos
+     */
+    highlightReverse(pos, args) {
+        return pos;
+    }
+
+}
+
+export default SwapEndianness;

+ 345 - 0
src/core/operations/ToHTMLEntity.mjs

@@ -0,0 +1,345 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * To HTML Entity operation
+ */
+class ToHTMLEntity extends Operation {
+
+    /**
+     * ToHTMLEntity constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To HTML Entity";
+        this.module = "Default";
+        this.description = "Converts characters to HTML entities<br><br>e.g. <code>&amp;</code> becomes <code>&amp;<span>amp;</span></code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Convert all characters",
+                "type": "boolean",
+                "value": false
+            },
+            {
+                "name": "Convert to",
+                "type": "option",
+                "value": ["Named entities where possible", "Numeric entities", "Hex entities"]
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const convertAll = args[0],
+            numeric = args[1] === "Numeric entities",
+            hexa = args[1] === "Hex entities";
+
+        const charcodes = Utils.strToCharcode(input);
+        let output = "";
+
+        for (let i = 0; i < charcodes.length; i++) {
+            if (convertAll && numeric) {
+                output += "&#" + charcodes[i] + ";";
+            } else if (convertAll && hexa) {
+                output += "&#x" + Utils.hex(charcodes[i]) + ";";
+            } else if (convertAll) {
+                output += byteToEntity[charcodes[i]] || "&#" + charcodes[i] + ";";
+            } else if (numeric) {
+                if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) {
+                    output += "&#" + charcodes[i] + ";";
+                } else {
+                    output += Utils.chr(charcodes[i]);
+                }
+            } else if (hexa) {
+                if (charcodes[i] > 255 || byteToEntity.hasOwnProperty(charcodes[i])) {
+                    output += "&#x" + Utils.hex(charcodes[i]) + ";";
+                } else {
+                    output += Utils.chr(charcodes[i]);
+                }
+            } else {
+                output += byteToEntity[charcodes[i]] || (
+                    charcodes[i] > 255 ?
+                        "&#" + charcodes[i] + ";" :
+                        Utils.chr(charcodes[i])
+                );
+            }
+        }
+        return output;
+    }
+
+}
+
+/**
+ * Lookup table to translate byte values to their HTML entity codes.
+ */
+const byteToEntity = {
+    34: "&quot;",
+    38: "&amp;",
+    39: "&apos;",
+    60: "&lt;",
+    62: "&gt;",
+    160: "&nbsp;",
+    161: "&iexcl;",
+    162: "&cent;",
+    163: "&pound;",
+    164: "&curren;",
+    165: "&yen;",
+    166: "&brvbar;",
+    167: "&sect;",
+    168: "&uml;",
+    169: "&copy;",
+    170: "&ordf;",
+    171: "&laquo;",
+    172: "&not;",
+    173: "&shy;",
+    174: "&reg;",
+    175: "&macr;",
+    176: "&deg;",
+    177: "&plusmn;",
+    178: "&sup2;",
+    179: "&sup3;",
+    180: "&acute;",
+    181: "&micro;",
+    182: "&para;",
+    183: "&middot;",
+    184: "&cedil;",
+    185: "&sup1;",
+    186: "&ordm;",
+    187: "&raquo;",
+    188: "&frac14;",
+    189: "&frac12;",
+    190: "&frac34;",
+    191: "&iquest;",
+    192: "&Agrave;",
+    193: "&Aacute;",
+    194: "&Acirc;",
+    195: "&Atilde;",
+    196: "&Auml;",
+    197: "&Aring;",
+    198: "&AElig;",
+    199: "&Ccedil;",
+    200: "&Egrave;",
+    201: "&Eacute;",
+    202: "&Ecirc;",
+    203: "&Euml;",
+    204: "&Igrave;",
+    205: "&Iacute;",
+    206: "&Icirc;",
+    207: "&Iuml;",
+    208: "&ETH;",
+    209: "&Ntilde;",
+    210: "&Ograve;",
+    211: "&Oacute;",
+    212: "&Ocirc;",
+    213: "&Otilde;",
+    214: "&Ouml;",
+    215: "&times;",
+    216: "&Oslash;",
+    217: "&Ugrave;",
+    218: "&Uacute;",
+    219: "&Ucirc;",
+    220: "&Uuml;",
+    221: "&Yacute;",
+    222: "&THORN;",
+    223: "&szlig;",
+    224: "&agrave;",
+    225: "&aacute;",
+    226: "&acirc;",
+    227: "&atilde;",
+    228: "&auml;",
+    229: "&aring;",
+    230: "&aelig;",
+    231: "&ccedil;",
+    232: "&egrave;",
+    233: "&eacute;",
+    234: "&ecirc;",
+    235: "&euml;",
+    236: "&igrave;",
+    237: "&iacute;",
+    238: "&icirc;",
+    239: "&iuml;",
+    240: "&eth;",
+    241: "&ntilde;",
+    242: "&ograve;",
+    243: "&oacute;",
+    244: "&ocirc;",
+    245: "&otilde;",
+    246: "&ouml;",
+    247: "&divide;",
+    248: "&oslash;",
+    249: "&ugrave;",
+    250: "&uacute;",
+    251: "&ucirc;",
+    252: "&uuml;",
+    253: "&yacute;",
+    254: "&thorn;",
+    255: "&yuml;",
+    338: "&OElig;",
+    339: "&oelig;",
+    352: "&Scaron;",
+    353: "&scaron;",
+    376: "&Yuml;",
+    402: "&fnof;",
+    710: "&circ;",
+    732: "&tilde;",
+    913: "&Alpha;",
+    914: "&Beta;",
+    915: "&Gamma;",
+    916: "&Delta;",
+    917: "&Epsilon;",
+    918: "&Zeta;",
+    919: "&Eta;",
+    920: "&Theta;",
+    921: "&Iota;",
+    922: "&Kappa;",
+    923: "&Lambda;",
+    924: "&Mu;",
+    925: "&Nu;",
+    926: "&Xi;",
+    927: "&Omicron;",
+    928: "&Pi;",
+    929: "&Rho;",
+    931: "&Sigma;",
+    932: "&Tau;",
+    933: "&Upsilon;",
+    934: "&Phi;",
+    935: "&Chi;",
+    936: "&Psi;",
+    937: "&Omega;",
+    945: "&alpha;",
+    946: "&beta;",
+    947: "&gamma;",
+    948: "&delta;",
+    949: "&epsilon;",
+    950: "&zeta;",
+    951: "&eta;",
+    952: "&theta;",
+    953: "&iota;",
+    954: "&kappa;",
+    955: "&lambda;",
+    956: "&mu;",
+    957: "&nu;",
+    958: "&xi;",
+    959: "&omicron;",
+    960: "&pi;",
+    961: "&rho;",
+    962: "&sigmaf;",
+    963: "&sigma;",
+    964: "&tau;",
+    965: "&upsilon;",
+    966: "&phi;",
+    967: "&chi;",
+    968: "&psi;",
+    969: "&omega;",
+    977: "&thetasym;",
+    978: "&upsih;",
+    982: "&piv;",
+    8194: "&ensp;",
+    8195: "&emsp;",
+    8201: "&thinsp;",
+    8204: "&zwnj;",
+    8205: "&zwj;",
+    8206: "&lrm;",
+    8207: "&rlm;",
+    8211: "&ndash;",
+    8212: "&mdash;",
+    8216: "&lsquo;",
+    8217: "&rsquo;",
+    8218: "&sbquo;",
+    8220: "&ldquo;",
+    8221: "&rdquo;",
+    8222: "&bdquo;",
+    8224: "&dagger;",
+    8225: "&Dagger;",
+    8226: "&bull;",
+    8230: "&hellip;",
+    8240: "&permil;",
+    8242: "&prime;",
+    8243: "&Prime;",
+    8249: "&lsaquo;",
+    8250: "&rsaquo;",
+    8254: "&oline;",
+    8260: "&frasl;",
+    8364: "&euro;",
+    8465: "&image;",
+    8472: "&weierp;",
+    8476: "&real;",
+    8482: "&trade;",
+    8501: "&alefsym;",
+    8592: "&larr;",
+    8593: "&uarr;",
+    8594: "&rarr;",
+    8595: "&darr;",
+    8596: "&harr;",
+    8629: "&crarr;",
+    8656: "&lArr;",
+    8657: "&uArr;",
+    8658: "&rArr;",
+    8659: "&dArr;",
+    8660: "&hArr;",
+    8704: "&forall;",
+    8706: "&part;",
+    8707: "&exist;",
+    8709: "&empty;",
+    8711: "&nabla;",
+    8712: "&isin;",
+    8713: "&notin;",
+    8715: "&ni;",
+    8719: "&prod;",
+    8721: "&sum;",
+    8722: "&minus;",
+    8727: "&lowast;",
+    8730: "&radic;",
+    8733: "&prop;",
+    8734: "&infin;",
+    8736: "&ang;",
+    8743: "&and;",
+    8744: "&or;",
+    8745: "&cap;",
+    8746: "&cup;",
+    8747: "&int;",
+    8756: "&there4;",
+    8764: "&sim;",
+    8773: "&cong;",
+    8776: "&asymp;",
+    8800: "&ne;",
+    8801: "&equiv;",
+    8804: "&le;",
+    8805: "&ge;",
+    8834: "&sub;",
+    8835: "&sup;",
+    8836: "&nsub;",
+    8838: "&sube;",
+    8839: "&supe;",
+    8853: "&oplus;",
+    8855: "&otimes;",
+    8869: "&perp;",
+    8901: "&sdot;",
+    8942: "&vellip;",
+    8968: "&lceil;",
+    8969: "&rceil;",
+    8970: "&lfloor;",
+    8971: "&rfloor;",
+    9001: "&lang;",
+    9002: "&rang;",
+    9674: "&loz;",
+    9824: "&spades;",
+    9827: "&clubs;",
+    9829: "&hearts;",
+    9830: "&diams;",
+};
+
+export default ToHTMLEntity;

+ 244 - 0
src/core/operations/ToQuotedPrintable.mjs

@@ -0,0 +1,244 @@
+/**
+ * Some parts taken from mimelib (http://github.com/andris9/mimelib)
+ * @author Andris Reinman
+ * @license MIT
+ *
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+
+/**
+ * To Quoted Printable operation
+ */
+class ToQuotedPrintable extends Operation {
+
+    /**
+     * ToQuotedPrintable constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "To Quoted Printable";
+        this.module = "Default";
+        this.description = "Quoted-Printable, or QP encoding, is an encoding using printable ASCII characters (alphanumeric and the equals sign '=') to transmit 8-bit data over a 7-bit data path or, generally, over a medium which is not 8-bit clean. It is defined as a MIME content transfer encoding for use in e-mail.<br><br>QP works by using the equals sign '=' as an escape character. It also limits line length to 76, as some software has limits on line length.";
+        this.inputType = "byteArray";
+        this.outputType = "string";
+        this.args = [];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        let mimeEncodedStr = this.mimeEncode(input);
+
+        // fix line breaks
+        mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() {
+            return "\r\n";
+        }).replace(/[\t ]+$/gm, function(spaces) {
+            return spaces.replace(/ /g, "=20").replace(/\t/g, "=09");
+        });
+
+        return this._addSoftLinebreaks(mimeEncodedStr, "qp");
+    }
+
+
+    /** @license
+    ========================================================================
+      mimelib: http://github.com/andris9/mimelib
+      Copyright (c) 2011-2012 Andris Reinman
+
+      Permission is hereby granted, free of charge, to any person obtaining a copy
+      of this software and associated documentation files (the "Software"), to deal
+      in the Software without restriction, including without limitation the rights
+      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+      copies of the Software, and to permit persons to whom the Software is
+      furnished to do so, subject to the following conditions:
+
+      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+      SOFTWARE.
+    */
+
+    /**
+     * Encodes mime data.
+     *
+     * @param {byteArray} buffer
+     * @returns {string}
+     */
+    mimeEncode(buffer) {
+        const ranges = [
+            [0x09],
+            [0x0A],
+            [0x0D],
+            [0x20],
+            [0x21],
+            [0x23, 0x3C],
+            [0x3E],
+            [0x40, 0x5E],
+            [0x60, 0x7E]
+        ];
+        let result = "";
+
+        for (let i = 0, len = buffer.length; i < len; i++) {
+            if (this._checkRanges(buffer[i], ranges)) {
+                result += String.fromCharCode(buffer[i]);
+                continue;
+            }
+            result += "=" + (buffer[i] < 0x10 ? "0" : "") + buffer[i].toString(16).toUpperCase();
+        }
+
+        return result;
+    }
+
+    /**
+     * Checks if a given number falls within a given set of ranges.
+     *
+     * @private
+     * @param {number} nr
+     * @param {byteArray[]} ranges
+     * @returns {bolean}
+     */
+    _checkRanges(nr, ranges) {
+        for (let i = ranges.length - 1; i >= 0; i--) {
+            if (!ranges[i].length)
+                continue;
+            if (ranges[i].length === 1 && nr === ranges[i][0])
+                return true;
+            if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1])
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Adds soft line breaks to a string.
+     * Lines can't be longer that 76 + <CR><LF> = 78 bytes
+     * http://tools.ietf.org/html/rfc2045#section-6.7
+     *
+     * @private
+     * @param {string} str
+     * @param {string} encoding
+     * @returns {string}
+     */
+    _addSoftLinebreaks(str, encoding) {
+        const lineLengthMax = 76;
+
+        encoding = (encoding || "base64").toString().toLowerCase().trim();
+
+        if (encoding === "qp") {
+            return this._addQPSoftLinebreaks(str, lineLengthMax);
+        } else {
+            return this._addBase64SoftLinebreaks(str, lineLengthMax);
+        }
+    }
+
+    /**
+     * Adds soft line breaks to a base64 string.
+     *
+     * @private
+     * @param {string} base64EncodedStr
+     * @param {number} lineLengthMax
+     * @returns {string}
+     */
+    _addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) {
+        base64EncodedStr = (base64EncodedStr || "").toString().trim();
+        return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim();
+    }
+
+    /**
+     * Adds soft line breaks to a quoted printable string.
+     *
+     * @private
+     * @param {string} mimeEncodedStr
+     * @param {number} lineLengthMax
+     * @returns {string}
+     */
+    _addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) {
+        const len = mimeEncodedStr.length,
+            lineMargin = Math.floor(lineLengthMax / 3);
+        let pos = 0,
+            match, code, line,
+            result = "";
+
+        // insert soft linebreaks where needed
+        while (pos < len) {
+            line = mimeEncodedStr.substr(pos, lineLengthMax);
+            if ((match = line.match(/\r\n/))) {
+                line = line.substr(0, match.index + match[0].length);
+                result += line;
+                pos += line.length;
+                continue;
+            }
+
+            if (line.substr(-1) === "\n") {
+                // nothing to change here
+                result += line;
+                pos += line.length;
+                continue;
+            } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
+                // truncate to nearest line break
+                line = line.substr(0, line.length - (match[0].length - 1));
+                result += line;
+                pos += line.length;
+                continue;
+            } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
+                // truncate to nearest space
+                line = line.substr(0, line.length - (match[0].length - 1));
+            } else if (line.substr(-1) === "\r") {
+                line = line.substr(0, line.length - 1);
+            } else {
+                if (line.match(/=[\da-f]{0,2}$/i)) {
+
+                    // push incomplete encoding sequences to the next line
+                    if ((match = line.match(/=[\da-f]{0,1}$/i))) {
+                        line = line.substr(0, line.length - match[0].length);
+                    }
+
+                    // ensure that utf-8 sequences are not split
+                    while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/=[\da-f]{2}$/ig))) {
+                        code = parseInt(match[0].substr(1, 2), 16);
+                        if (code < 128) {
+                            break;
+                        }
+
+                        line = line.substr(0, line.length - 3);
+
+                        if (code >= 0xC0) {
+                            break;
+                        }
+                    }
+
+                }
+            }
+
+            if (pos + line.length < len && line.substr(-1) !== "\n") {
+                if (line.length === 76 && line.match(/=[\da-f]{2}$/i)) {
+                    line = line.substr(0, line.length - 3);
+                } else if (line.length === 76) {
+                    line = line.substr(0, line.length - 1);
+                }
+                pos += line.length;
+                line += "=\r\n";
+            } else {
+                pos += line.length;
+            }
+
+            result += line;
+        }
+
+        return result;
+    }
+
+}
+
+export default ToQuotedPrintable;

+ 75 - 0
src/core/operations/UnescapeUnicodeCharacters.mjs

@@ -0,0 +1,75 @@
+/**
+ * @author n1474335 [n1474335@gmail.com]
+ * @copyright Crown Copyright 2016
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+
+/**
+ * Unescape Unicode Characters operation
+ */
+class UnescapeUnicodeCharacters extends Operation {
+
+    /**
+     * UnescapeUnicodeCharacters constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "Unescape Unicode Characters";
+        this.module = "Default";
+        this.description = "Converts unicode-escaped character notation back into raw characters.<br><br>Supports the prefixes:<ul><li><code>\\u</code></li><li><code>%u</code></li><li><code>U+</code></li></ul>e.g. <code>\\u03c3\\u03bf\\u03c5</code> becomes <code>σου</code>";
+        this.inputType = "string";
+        this.outputType = "string";
+        this.args = [
+            {
+                "name": "Prefix",
+                "type": "option",
+                "value": ["\\u", "%u", "U+"]
+            }
+        ];
+    }
+
+    /**
+     * @param {string} input
+     * @param {Object[]} args
+     * @returns {string}
+     */
+    run(input, args) {
+        const prefix = prefixToRegex[args[0]],
+            regex = new RegExp(prefix+"([a-f\\d]{4})", "ig");
+        let output = "",
+            m,
+            i = 0;
+
+        while ((m = regex.exec(input))) {
+            // Add up to match
+            output += input.slice(i, m.index);
+            i = m.index;
+
+            // Add match
+            output += Utils.chr(parseInt(m[1], 16));
+
+            i = regex.lastIndex;
+        }
+
+        // Add all after final match
+        output += input.slice(i, input.length);
+
+        return output;
+    }
+
+}
+
+/**
+ * Lookup table to add prefixes to unicode delimiters so that they can be used in a regex.
+ */
+const prefixToRegex = {
+    "\\u": "\\\\u",
+    "%u": "%u",
+    "U+": "U\\+"
+};
+
+export default UnescapeUnicodeCharacters;