From dcecb90b62d95a3994bc9cc0ccb6a4bcab91d09f Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Wed, 2 Dec 2015 16:03:23 -0700 Subject: [PATCH] Search function overhaul: now search by case sensitive and regardless of diacritics! Changed markdown parser to allow Github-Flavored Markdown. --- css/lexiconga.css | 2 +- css/styles.css | 4 +- index.php | 46 +- js/defiant-js/defiant-latest.js | 687 +++++++++++++++++ js/dictionaryBuilder.js | 107 ++- js/marked.js | 1285 +++++++++++++++++++++++++++++++ js/removeDiacritics.js | 138 ++++ js/ui.js | 13 + 8 files changed, 2239 insertions(+), 43 deletions(-) create mode 100644 js/defiant-js/defiant-latest.js create mode 100644 js/marked.js create mode 100644 js/removeDiacritics.js diff --git a/css/lexiconga.css b/css/lexiconga.css index 56a47b3..ca1f8ef 100644 --- a/css/lexiconga.css +++ b/css/lexiconga.css @@ -102,7 +102,7 @@ input, textarea, select, option, button { } #loginLink, #logoutLink, -#descriptionToggle, #settingsButton, +#descriptionToggle, #searchFilterToggle, #settingsButton, .deleteCancelButton, .deleteConfirmButton, #settingsScreenCloseButton, #infoScreenCloseButton, .helperlink { diff --git a/css/styles.css b/css/styles.css index f9c3785..8e1a883 100644 --- a/css/styles.css +++ b/css/styles.css @@ -153,7 +153,9 @@ input[type=checkbox] { cursor: pointer; } -#descriptionToggle { +#descriptionToggle, #searchFilterToggle { + display: inline-block; + margin: 8px; font-weight: bold; font-size: 12px; cursor: pointer; diff --git a/index.php b/index.php index cacb5a9..7a88e10 100644 --- a/index.php +++ b/index.php @@ -68,25 +68,31 @@ if ($_GET['adminoverride'] == 'dictionarytotext') { Show Description -
-
@@ -167,9 +173,11 @@ if ($_GET['adminoverride'] == 'dictionarytotext') { - + - + + + diff --git a/js/defiant-js/defiant-latest.js b/js/defiant-js/defiant-latest.js new file mode 100644 index 0000000..c739d6e --- /dev/null +++ b/js/defiant-js/defiant-latest.js @@ -0,0 +1,687 @@ +/* + * Defiant.js v1.2.5 + * Serch JSON structures plus smart templating with XSLT and XPath. + * http://defiantjs.com + * + * Copyright (c) 2013-2015, Hakan Bilgin + * Licensed under the MIT License + * + * NOTE: + * Robbie Antenesse edited line 165's RegExp to search global and case-insensitive in case of multiple contains() groups. + */ + +if (typeof module === "undefined") { + var module = { exports: undefined }; +} else { + // Node env adaptation goes here... +} + +module.exports = Defiant = (function(window, undefined) { + 'use strict'; + + var Defiant = { + is_ie : /msie/i.test(navigator.userAgent), + is_safari : /safari/i.test(navigator.userAgent), + env : 'production', + xml_decl : '', + namespace : 'xmlns:d="defiant-namespace"', + tabsize : 4, + render: function(template, data) { + var processor = new XSLTProcessor(), + span = document.createElement('span'), + opt = {match: '/'}, + tmpltXpath, + scripts, + temp, + sorter; + // handle arguments + switch (typeof(template)) { + case 'object': + this.extend(opt, template); + if (!opt.data) opt.data = data; + break; + case 'string': + opt.template = template; + opt.data = data; + break; + default: + throw 'error'; + } + opt.data = JSON.toXML(opt.data); + tmpltXpath = '//xsl:template[@name="'+ opt.template +'"]'; + + if (!this.xsl_template) this.gatherTemplates(); + + if (opt.sorter) { + sorter = this.node.selectSingleNode(this.xsl_template, tmpltXpath +'//xsl:for-each//xsl:sort'); + if (sorter) { + if (opt.sorter.order) sorter.setAttribute('order', opt.sorter.order); + if (opt.sorter.select) sorter.setAttribute('select', opt.sorter.select); + sorter.setAttribute('data-type', opt.sorter.type || 'text'); + } + } + + temp = this.node.selectSingleNode(this.xsl_template, tmpltXpath); + temp.setAttribute('match', opt.match); + processor.importStylesheet(this.xsl_template); + span.appendChild(processor.transformToFragment(opt.data, document)); + temp.removeAttribute('match'); + + if (this.is_safari) { + scripts = span.getElementsByTagName('script'); + for (var i=0, il=scripts.length; i'+ str.replace(/defiant:(\w+)/g, '$1') +''); + }, + getSnapshot: function(data) { + return JSON.toXML(data, true); + }, + xmlFromString: function(str) { + var parser, + doc; + str = str.replace(/>\s{1,}<'); + if (str.trim().match(/<\?xml/) === null) { + str = this.xml_decl + str; + } + if (this.is_ie) { + doc = new ActiveXObject('Msxml2.DOMDocument'); + doc.loadXML(str); + if (str.indexOf('xsl:stylesheet') === -1) { + doc.setProperty('SelectionLanguage', 'XPath'); + } + } else { + parser = new DOMParser(); + doc = parser.parseFromString(str, 'text/xml'); + } + return doc; + }, + extend: function(src, dest) { + for (var content in dest) { + if (!src[content] || typeof(dest[content]) !== 'object') { + src[content] = dest[content]; + } else { + this.extend(src[content], dest[content]); + } + } + return src; + }, + node: {} + }; + + return Defiant; + +})(this); + + +if (typeof(XSLTProcessor) === 'undefined') { + + // emulating XSLT Processor (enough to be used in defiant) + var XSLTProcessor = function() {}; + XSLTProcessor.prototype = { + importStylesheet: function(xsldoc) { + this.xsldoc = xsldoc; + }, + transformToFragment: function(data, doc) { + var str = data.transformNode(this.xsldoc), + span = document.createElement('span'); + span.innerHTML = str; + return span; + } + }; + +} + + +// extending STRING +if (!String.prototype.fill) { + String.prototype.fill = function(i,c) { + var str = this; + c = c || ' '; + for (; str.length/, + rx_constructor : /<(.+?)( d:contr=".*?")>/, + rx_namespace : / xmlns\:d="defiant\-namespace"/, + rx_data : /(<.+?>)(.*?)(<\/d:data>)/i, + rx_function : /function (\w+)/i, + to_xml: function(tree) { + var str = this.hash_to_xml(null, tree); + return Defiant.xmlFromString(str); + }, + hash_to_xml: function(name, tree, array_child) { + var is_array = tree.constructor === Array, + elem = [], + attr = [], + key, + val, + val_is_array, + type, + is_attr, + cname, + constr, + cnName, + i; + + for (key in tree) { + val = tree[key]; + if (val === null || val === undefined || val.toString() === 'NaN') val = null; + + is_attr = key.slice(0,1) === '@'; + cname = array_child ? name : key; + if (cname == +cname && tree.constructor !== Object) cname = 'd:item'; + if (val === null) { + constr = null; + cnName = false; + } else { + constr = val.constructor; + cnName = constr.toString().match(this.rx_function)[1]; + } + + if (is_attr) { + attr.push( cname.slice(1) +'="'+ this.escape_xml(val) +'"' ); + if (cnName !== 'String') attr.push( 'd:'+ cname.slice(1) +'="'+ cnName +'"' ); + } else if (val === null) { + elem.push( this.scalar_to_xml( cname, val ) ); + } else { + switch (constr) { + case Function: + // if constructor is function, then it's not a JSON structure + // throw ERROR ? + break; + case Object: + elem.push( this.hash_to_xml( cname, val ) ); + break; + case Array: + if (key === cname) { + val_is_array = val.constructor === Array; + if (val_is_array) { + i = val.length; + while (i--) { + if (val[i].constructor === Array) val_is_array = true; + if (!val_is_array && val[i].constructor === Object) val_is_array = true; + } + } + elem.push( this.scalar_to_xml( cname, val, val_is_array ) ); + break; + } + /* falls through */ + case String: + if (typeof(val) === 'string') { + val = val.toString().replace(/\&/g, '&') + .replace(/\r|\n/g, ' '); + } + if (cname === '#text') { + // prepare map + this.map.push(tree); + attr.push('d:mi="'+ this.map.length +'"'); + attr.push('d:constr="'+ cnName +'"'); + elem.push( this.escape_xml(val) ); + break; + } + /* falls through */ + case Number: + case Boolean: + if (cname === '#text' && cnName !== 'String') { + // prepare map + this.map.push(tree); + attr.push('d:mi="'+ this.map.length +'"'); + attr.push('d:constr="'+ cnName +'"'); + elem.push( this.escape_xml(val) ); + break; + } + elem.push( this.scalar_to_xml( cname, val ) ); + break; + } + } + } + if (!name) { + name = 'd:data'; + attr.push(Defiant.namespace); + if (is_array) attr.push('d:constr="Array"'); + } + if (name.match(this.rx_validate_name) === null) { + attr.push( 'd:name="'+ name +'"' ); + name = 'd:name'; + } + if (array_child) return elem.join(''); + // prepare map + this.map.push(tree); + attr.push('d:mi="'+ this.map.length +'"'); + + return '<'+ name + (attr.length ? ' '+ attr.join(' ') : '') + (elem.length ? '>'+ elem.join('') +'' : '/>' ); + }, + scalar_to_xml: function(name, val, override) { + var attr = '', + text, + constr, + cnName; + + // check whether the nodename is valid + if (name.match(this.rx_validate_name) === null) { + attr += ' d:name="'+ name +'"'; + name = 'd:name'; + override = false; + } + if (val === null || val.toString() === 'NaN') val = null; + if (val === null) return '<'+ name +' d:constr="null"/>'; + if (val.length === 1 && val[0].constructor === Object) { + + text = this.hash_to_xml(false, val[0]); + + var a1 = text.match(this.rx_node), + a2 = text.match(this.rx_constructor); + a1 = (a1 !== null)? a1[2] + .replace(this.rx_namespace, '') + .replace(/>/, '') + .replace(/"\/$/, '"') : ''; + a2 = (a2 !== null)? a2[2] : ''; + + text = text.match(this.rx_data); + text = (text !== null)? text[2] : ''; + + return '<'+ name + a1 +' '+ a2 +' d:type="ArrayItem">'+ text +''; + } else if (val.length === 0 && val.constructor === Array) { + return '<'+ name +' d:constr="Array"/>'; + } + // else + if (override) { + return this.hash_to_xml( name, val, true ); + } + + constr = val.constructor; + cnName = constr.toString().match(this.rx_function)[1]; + text = (constr === Array) ? this.hash_to_xml( 'd:item', val, true ) + : this.escape_xml(val); + + attr += ' d:constr="'+ cnName +'"'; + // prepare map + this.map.push(val); + attr += ' d:mi="'+ this.map.length +'"'; + + return (name === '#text') ? this.escape_xml(val) : '<'+ name + attr +'>'+ text +''; + }, + escape_xml: function(text) { + return String(text) .replace(//g, '>') + .replace(/"/g, '"') + .replace(/ /g, ' '); + } + }, + doc = interpreter.to_xml.call(interpreter, tree); + + // snapshot distinctly improves performance + if (snapshot) { + return { + doc: doc, + src: tree, + map: interpreter.map + }; + } + + this.search.map = interpreter.map; + return doc; + }; +} + +if (!JSON.search) { + JSON.search = function(tree, xpath, single) { + 'use strict'; + + var isSnapshot = tree.doc && tree.doc.nodeType, + doc = isSnapshot ? tree.doc : JSON.toXML(tree), + map = isSnapshot ? tree.map : this.search.map, + src = isSnapshot ? tree.src : tree, + xres = Defiant.node[ single ? 'selectSingleNode' : 'selectNodes' ](doc, xpath.xTransform()), + ret = [], + mapIndex, + i; + + if (single) xres = [xres]; + i = xres.length; + + while (i--) { + switch(xres[i].nodeType) { + case 2: + case 3: + ret.unshift( xres[i].nodeValue ); + break; + default: + mapIndex = +xres[i].getAttribute('d:mi'); + if (map[mapIndex-1]) ret.unshift( map[mapIndex-1] ); + } + } + + // if environment = development, add search tracing + if (Defiant.env === 'development') { + this.trace = JSON.mtrace(src, ret, xres); + } + + return ret; + }; +} + +if (!JSON.mtrace) { + JSON.mtrace = function(root, hits, xres) { + 'use strict'; + + var win = window, + stringify = JSON.stringify, + sroot = stringify( root, null, '\t' ).replace(/\t/g, ''), + trace = [], + i = 0, + il = xres.length, + od = il ? xres[i].ownerDocument.documentElement : false, + map = this.search.map, + hstr, + cConstr, + fIndex = 0, + mIndex, + lStart, + lEnd; + + for (; i 0)? xI[0] : null; + } else { + return XNode.selectSingleNode(XPath); + } +}; + + +Defiant.node.prettyPrint = function(node) { + var root = Defiant, + tabs = root.tabsize, + decl = root.xml_decl.toLowerCase(), + ser, + xstr; + if (root.is_ie) { + xstr = node.xml; + } else { + ser = new XMLSerializer(); + xstr = ser.serializeToString(node); + } + if (root.env !== 'development') { + // if environment is not development, remove defiant related info + xstr = xstr.replace(/ \w+\:d=".*?"| d\:\w+=".*?"/g, ''); + } + var str = xstr.trim().replace(/(>)\s*(<)(\/*)/g, '$1\n$2$3'), + lines = str.split('\n'), + indent = -1, + i = 0, + il = lines.length, + start, + end; + for (; i/g) !== null; + //start = lines[i].match(/<[^\/]+>/g) !== null; + end = lines[i].match(/<\/[\w\:]+>/g) !== null; + if (lines[i].match(/<.*?\/>/g) !== null) start = end = true; + if (start) indent++; + lines[i] = String().fill(indent, '\t') + lines[i]; + if (start && end) indent--; + if (!start && end) indent--; + } + return lines.join('\n').replace(/\t/g, String().fill(tabs, ' ')); +}; + + +Defiant.node.toJSON = function(xnode, stringify) { + 'use strict'; + + var interpret = function(leaf) { + var obj = {}, + win = window, + attr, + type, + item, + cname, + cConstr, + cval, + text, + i, il, a; + + switch (leaf.nodeType) { + case 1: + cConstr = leaf.getAttribute('d:constr'); + if (cConstr === 'Array') obj = []; + else if (cConstr === 'String' && leaf.textContent === '') obj = ''; + + attr = leaf.attributes; + i = 0; + il = attr.length; + for (; i 0) { for (var i = 0; i < currentDictionary.words.length; i++) { if (filter == "" || (filter != "" && currentDictionary.words[i].partOfSpeech == filter)) { - if (search == "" || (search != "" && searchResults.indexOf(htmlEntities(currentDictionary.words[i].name)) >= 0)) { + if (search == "" || (search != "" && (searchByWord || searchBySimple || searchByLong) && searchResults.indexOf(currentDictionary.words[i].wordId) >= 0)) { if (!currentDictionary.words[i].hasOwnProperty("pronunciation")) { currentDictionary.words[i].pronunciation = ""; //Account for new property } @@ -246,32 +261,67 @@ function ShowDictionary() { } dictionaryArea.innerHTML = dictionaryText; + console.log("dictionary shown"); } function DictionaryEntry(itemIndex) { var entryText = "🔗"; - var searchTerm = htmlEntities(document.getElementById("searchBox").value); - var searchRegEx = new RegExp(searchTerm, "gi"); + var searchTerm = document.getElementById("searchBox").value; + var searchByWord = document.getElementById("searchOptionWord").checked; + var searchBySimple = document.getElementById("searchOptionSimple").checked; + var searchByLong = document.getElementById("searchOptionLong").checked; + var searchIgnoreCase = !document.getElementById("searchCaseSensitive").checked; //It's easier to negate case here instead of negating it every use since ignore case is default. + var searchIgnoreDiacritics = document.getElementById("searchIgnoreDiacritics").checked; + + var searchRegEx = new RegExp("(" + ((searchIgnoreDiacritics) ? removeDiacritics(searchTerm) + "|" + searchTerm : searchTerm) + ")", "g" + ((searchIgnoreCase) ? "i" : "")); - entryText += "" + ((searchTerm != "" && document.getElementById("searchOptionWord").checked) ? currentDictionary.words[itemIndex].name.replace(searchRegEx, "" + searchTerm + "") : currentDictionary.words[itemIndex].name) + ""; + entryText += ""; + + if (searchTerm != "" && searchByWord) { + entryText += htmlEntitiesParse(currentDictionary.words[itemIndex].name).replace(searchRegEx, "$1"); + } else { + entryText += currentDictionary.words[itemIndex].name; + } + + entryText += ""; if (currentDictionary.words[itemIndex].pronunciation != "") { - entryText += "" + markdown.toHTML(htmlEntitiesParse(currentDictionary.words[itemIndex].pronunciation)).replace("

","").replace("

","") + "
"; + entryText += ""; + entryText += marked(htmlEntitiesParse(currentDictionary.words[itemIndex].pronunciation)).replace("

","").replace("

",""); + entryText += "
"; } if (currentDictionary.words[itemIndex].partOfSpeech != "") { - entryText += "" + currentDictionary.words[itemIndex].partOfSpeech + ""; + entryText += ""; + entryText += currentDictionary.words[itemIndex].partOfSpeech; + entryText += ""; } entryText += "
"; if (currentDictionary.words[itemIndex].simpleDefinition != "") { - entryText += "" + ((searchTerm != "" && document.getElementById("searchOptionSimple").checked) ? currentDictionary.words[itemIndex].simpleDefinition.replace(searchRegEx, "" + searchTerm + "") : currentDictionary.words[itemIndex].simpleDefinition) + ""; + entryText += ""; + + if (searchTerm != "" && searchBySimple) { + entryText += htmlEntitiesParse(currentDictionary.words[itemIndex].simpleDefinition).replace(searchRegEx, "$1"); + } else { + entryText += currentDictionary.words[itemIndex].simpleDefinition; + } + + entryText += ""; } if (currentDictionary.words[itemIndex].longDefinition != "") { - entryText += "" + ((searchTerm != "" && document.getElementById("searchOptionLong").checked) ? markdown.toHTML(htmlEntitiesParse(currentDictionary.words[itemIndex].longDefinition)).replace(searchRegEx, "" + searchTerm + "") : markdown.toHTML(htmlEntitiesParse(currentDictionary.words[itemIndex].longDefinition))) + ""; + entryText += ""; + + if (searchTerm != "" && searchByLong) { + entryText += marked(htmlEntitiesParse(currentDictionary.words[itemIndex].longDefinition).replace(searchRegEx, "$1")); + } else { + entryText += marked(htmlEntitiesParse(currentDictionary.words[itemIndex].longDefinition)); + } + + entryText += ""; } if (!currentDictionary.settings.isComplete) { @@ -371,7 +421,7 @@ function LoadDictionary() { } function ExportDictionary() { - var downloadName = currentDictionary.name.replace(/\W/g, ''); + var downloadName = removeDiacritics(stripHtmlEntities(currentDictionary.name)).replace(/\W/g, ''); if (downloadName == "") { downloadName = "export"; } @@ -444,6 +494,19 @@ function htmlEntitiesParse(string) { return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/
/g, '\n'); } +function stripHtmlEntities(string) { + // This is for the export name. + return String(string).replace(/&/g, '').replace(/</g, '').replace(/>/g, '').replace(/"/g, '').replace(/'/g, "").replace(/
/g, ''); +} + +function htmlEntitiesParseForSearchEntry(string) { + return String(string).replace(/"/g, '%%').replace(/'/g, "``"); +} + +function htmlEntitiesParseForSearch(string) { + return String(string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '%%').replace(/'/g, "``"); +} + function dynamicSort(property) { /* Retrieved from http://stackoverflow.com/a/4760279 Usage: theArray.sort(dynamicSort("objectProperty"));*/ diff --git a/js/marked.js b/js/marked.js new file mode 100644 index 0000000..bcea7a6 --- /dev/null +++ b/js/marked.js @@ -0,0 +1,1285 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/chjj/marked + */ + +;(function() { + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^( *[-*_]){3,} *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + nptable: noop, + lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, + blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, + list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, + def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + table: noop, + paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, + text: /^[^\n]+/ +}; + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = replace(block.item, 'gm') + (/bull/g, block.bullet) + (); + +block.list = replace(block.list) + (/bull/g, block.bullet) + ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') + ('def', '\\n+(?=' + block.def.source + ')') + (); + +block.blockquote = replace(block.blockquote) + ('def', block.def) + (); + +block._tag = '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; + +block.html = replace(block.html) + ('comment', //) + ('closed', /<(tag)[\s\S]+?<\/\1>/) + ('closing', /])*?>/) + (/tag/g, block._tag) + (); + +block.paragraph = replace(block.paragraph) + ('hr', block.hr) + ('heading', block.heading) + ('lheading', block.lheading) + ('blockquote', block.blockquote) + ('tag', '<' + block._tag) + ('def', block.def) + (); + +/** + * Normal Block Grammar + */ + +block.normal = merge({}, block); + +/** + * GFM Block Grammar + */ + +block.gfm = merge({}, block.normal, { + fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, + paragraph: /^/, + heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ +}); + +block.gfm.paragraph = replace(block.paragraph) + ('(?!', '(?!' + + block.gfm.fences.source.replace('\\1', '\\2') + '|' + + block.list.source.replace('\\1', '\\3') + '|') + (); + +/** + * GFM + Tables Block Grammar + */ + +block.tables = merge({}, block.gfm, { + nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, + table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ +}); + +/** + * Block Lexer + */ + +function Lexer(options) { + this.tokens = []; + this.tokens.links = {}; + this.options = options || marked.defaults; + this.rules = block.normal; + + if (this.options.gfm) { + if (this.options.tables) { + this.rules = block.tables; + } else { + this.rules = block.gfm; + } + } +} + +/** + * Expose Block Rules + */ + +Lexer.rules = block; + +/** + * Static Lex Method + */ + +Lexer.lex = function(src, options) { + var lexer = new Lexer(options); + return lexer.lex(src); +}; + +/** + * Preprocessing + */ + +Lexer.prototype.lex = function(src) { + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' ') + .replace(/\u00a0/g, ' ') + .replace(/\u2424/g, '\n'); + + return this.token(src, true); +}; + +/** + * Lexing + */ + +Lexer.prototype.token = function(src, top, bq) { + var src = src.replace(/^ +$/gm, '') + , next + , loose + , cap + , bull + , b + , item + , space + , i + , l; + + while (src) { + // newline + if (cap = this.rules.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + this.tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + this.tokens.push({ + type: 'code', + text: !this.options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = this.rules.fences.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] || '' + }); + continue; + } + + // heading + if (cap = this.rules.heading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // table no leading pipe (gfm) + if (top && (cap = this.rules.nptable.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = item.cells[i].split(/ *\| */); + } + + this.tokens.push(item); + + continue; + } + + // lheading + if (cap = this.rules.lheading.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // hr + if (cap = this.rules.hr.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = this.rules.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + this.tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + this.token(cap, top, true); + + this.tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = this.rules.list.exec(src)) { + src = src.substring(cap[0].length); + bull = cap[2]; + + this.tokens.push({ + type: 'list_start', + ordered: bull.length > 1 + }); + + // Get each top-level item. + cap = cap[0].match(this.rules.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !this.options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether the next list item belongs here. + // Backpedal if it does not belong in this list. + if (this.options.smartLists && i !== l - 1) { + b = block.bullet.exec(cap[i + 1])[0]; + if (bull !== b && !(bull.length > 1 && b.length > 1)) { + src = cap.slice(i + 1).join('\n') + src; + i = l - 1; + } + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item.charAt(item.length - 1) === '\n'; + if (!loose) loose = next; + } + + this.tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + this.token(item, false, bq); + + this.tokens.push({ + type: 'list_item_end' + }); + } + + this.tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = this.rules.html.exec(src)) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: this.options.sanitize + ? 'paragraph' + : 'html', + pre: !this.options.sanitizer + && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), + text: cap[0] + }); + continue; + } + + // def + if ((!bq && top) && (cap = this.rules.def.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.links[cap[1].toLowerCase()] = { + href: cap[2], + title: cap[3] + }; + continue; + } + + // table (gfm) + if (top && (cap = this.rules.table.exec(src))) { + src = src.substring(cap[0].length); + + item = { + type: 'table', + header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), + align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), + cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') + }; + + for (i = 0; i < item.align.length; i++) { + if (/^ *-+: *$/.test(item.align[i])) { + item.align[i] = 'right'; + } else if (/^ *:-+: *$/.test(item.align[i])) { + item.align[i] = 'center'; + } else if (/^ *:-+ *$/.test(item.align[i])) { + item.align[i] = 'left'; + } else { + item.align[i] = null; + } + } + + for (i = 0; i < item.cells.length; i++) { + item.cells[i] = item.cells[i] + .replace(/^ *\| *| *\| *$/g, '') + .split(/ *\| */); + } + + this.tokens.push(item); + + continue; + } + + // top-level paragraph + if (top && (cap = this.rules.paragraph.exec(src))) { + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'paragraph', + text: cap[1].charAt(cap[1].length - 1) === '\n' + ? cap[1].slice(0, -1) + : cap[1] + }); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + this.tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + + if (src) { + throw new + Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return this.tokens; +}; + +/** + * Inline-Level Grammar + */ + +var inline = { + escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + url: noop, + tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + link: /^!?\[(inside)\]\(href\)/, + reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, + em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, + code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + del: noop, + text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; + +inline.link = replace(inline.link) + ('inside', inline._inside) + ('href', inline._href) + (); + +inline.reflink = replace(inline.reflink) + ('inside', inline._inside) + (); + +/** + * Normal Inline Grammar + */ + +inline.normal = merge({}, inline); + +/** + * Pedantic Inline Grammar + */ + +inline.pedantic = merge({}, inline.normal, { + strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ +}); + +/** + * GFM Inline Grammar + */ + +inline.gfm = merge({}, inline.normal, { + escape: replace(inline.escape)('])', '~|])')(), + url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, + del: /^~~(?=\S)([\s\S]*?\S)~~/, + text: replace(inline.text) + (']|', '~]|') + ('|', '|https?://|') + () +}); + +/** + * GFM + Line Breaks Inline Grammar + */ + +inline.breaks = merge({}, inline.gfm, { + br: replace(inline.br)('{2,}', '*')(), + text: replace(inline.gfm.text)('{2,}', '*')() +}); + +/** + * Inline Lexer & Compiler + */ + +function InlineLexer(links, options) { + this.options = options || marked.defaults; + this.links = links; + this.rules = inline.normal; + this.renderer = this.options.renderer || new Renderer; + this.renderer.options = this.options; + + if (!this.links) { + throw new + Error('Tokens array requires a `links` property.'); + } + + if (this.options.gfm) { + if (this.options.breaks) { + this.rules = inline.breaks; + } else { + this.rules = inline.gfm; + } + } else if (this.options.pedantic) { + this.rules = inline.pedantic; + } +} + +/** + * Expose Inline Rules + */ + +InlineLexer.rules = inline; + +/** + * Static Lexing/Compiling Method + */ + +InlineLexer.output = function(src, links, options) { + var inline = new InlineLexer(links, options); + return inline.output(src); +}; + +/** + * Lexing/Compiling + */ + +InlineLexer.prototype.output = function(src) { + var out = '' + , link + , text + , href + , cap; + + while (src) { + // escape + if (cap = this.rules.escape.exec(src)) { + src = src.substring(cap[0].length); + out += cap[1]; + continue; + } + + // autolink + if (cap = this.rules.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = cap[1].charAt(6) === ':' + ? this.mangle(cap[1].substring(7)) + : this.mangle(cap[1]); + href = this.mangle('mailto:') + text; + } else { + text = escape(cap[1]); + href = text; + } + out += this.renderer.link(href, null, text); + continue; + } + + // url (gfm) + if (!this.inLink && (cap = this.rules.url.exec(src))) { + src = src.substring(cap[0].length); + text = escape(cap[1]); + href = text; + out += this.renderer.link(href, null, text); + continue; + } + + // tag + if (cap = this.rules.tag.exec(src)) { + if (!this.inLink && /^/i.test(cap[0])) { + this.inLink = false; + } + src = src.substring(cap[0].length); + out += this.options.sanitize + ? this.options.sanitizer + ? this.options.sanitizer(cap[0]) + : escape(cap[0]) + : cap[0] + continue; + } + + // link + if (cap = this.rules.link.exec(src)) { + src = src.substring(cap[0].length); + this.inLink = true; + out += this.outputLink(cap, { + href: cap[2], + title: cap[3] + }); + this.inLink = false; + continue; + } + + // reflink, nolink + if ((cap = this.rules.reflink.exec(src)) + || (cap = this.rules.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = this.links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0].charAt(0); + src = cap[0].substring(1) + src; + continue; + } + this.inLink = true; + out += this.outputLink(cap, link); + this.inLink = false; + continue; + } + + // strong + if (cap = this.rules.strong.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.strong(this.output(cap[2] || cap[1])); + continue; + } + + // em + if (cap = this.rules.em.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.em(this.output(cap[2] || cap[1])); + continue; + } + + // code + if (cap = this.rules.code.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.codespan(escape(cap[2], true)); + continue; + } + + // br + if (cap = this.rules.br.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.br(); + continue; + } + + // del (gfm) + if (cap = this.rules.del.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.del(this.output(cap[1])); + continue; + } + + // text + if (cap = this.rules.text.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.text(escape(this.smartypants(cap[0]))); + continue; + } + + if (src) { + throw new + Error('Infinite loop on byte: ' + src.charCodeAt(0)); + } + } + + return out; +}; + +/** + * Compile Link + */ + +InlineLexer.prototype.outputLink = function(cap, link) { + var href = escape(link.href) + , title = link.title ? escape(link.title) : null; + + return cap[0].charAt(0) !== '!' + ? this.renderer.link(href, title, this.output(cap[1])) + : this.renderer.image(href, title, escape(cap[1])); +}; + +/** + * Smartypants Transformations + */ + +InlineLexer.prototype.smartypants = function(text) { + if (!this.options.smartypants) return text; + return text + // em-dashes + .replace(/---/g, '\u2014') + // en-dashes + .replace(/--/g, '\u2013') + // opening singles + .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') + // closing singles & apostrophes + .replace(/'/g, '\u2019') + // opening doubles + .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') + // closing doubles + .replace(/"/g, '\u201d') + // ellipses + .replace(/\.{3}/g, '\u2026'); +}; + +/** + * Mangle Links + */ + +InlineLexer.prototype.mangle = function(text) { + if (!this.options.mangle) return text; + var out = '' + , l = text.length + , i = 0 + , ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +}; + +/** + * Renderer + */ + +function Renderer(options) { + this.options = options || {}; +} + +Renderer.prototype.code = function(code, lang, escaped) { + if (this.options.highlight) { + var out = this.options.highlight(code, lang); + if (out != null && out !== code) { + escaped = true; + code = out; + } + } + + if (!lang) { + return '
'
+      + (escaped ? code : escape(code, true))
+      + '\n
'; + } + + return '
'
+    + (escaped ? code : escape(code, true))
+    + '\n
\n'; +}; + +Renderer.prototype.blockquote = function(quote) { + return '
\n' + quote + '
\n'; +}; + +Renderer.prototype.html = function(html) { + return html; +}; + +Renderer.prototype.heading = function(text, level, raw) { + return '' + + text + + '\n'; +}; + +Renderer.prototype.hr = function() { + return this.options.xhtml ? '
\n' : '
\n'; +}; + +Renderer.prototype.list = function(body, ordered) { + var type = ordered ? 'ol' : 'ul'; + return '<' + type + '>\n' + body + '\n'; +}; + +Renderer.prototype.listitem = function(text) { + return '
  • ' + text + '
  • \n'; +}; + +Renderer.prototype.paragraph = function(text) { + return '

    ' + text + '

    \n'; +}; + +Renderer.prototype.table = function(header, body) { + return '\n' + + '\n' + + header + + '\n' + + '\n' + + body + + '\n' + + '
    \n'; +}; + +Renderer.prototype.tablerow = function(content) { + return '\n' + content + '\n'; +}; + +Renderer.prototype.tablecell = function(content, flags) { + var type = flags.header ? 'th' : 'td'; + var tag = flags.align + ? '<' + type + ' style="text-align:' + flags.align + '">' + : '<' + type + '>'; + return tag + content + '\n'; +}; + +// span level renderer +Renderer.prototype.strong = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.em = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.codespan = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.br = function() { + return this.options.xhtml ? '
    ' : '
    '; +}; + +Renderer.prototype.del = function(text) { + return '' + text + ''; +}; + +Renderer.prototype.link = function(href, title, text) { + if (this.options.sanitize) { + try { + var prot = decodeURIComponent(unescape(href)) + .replace(/[^\w:]/g, '') + .toLowerCase(); + } catch (e) { + return ''; + } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { + return ''; + } + } + var out = '
    '; + return out; +}; + +Renderer.prototype.image = function(href, title, text) { + var out = '' + text + '' : '>'; + return out; +}; + +Renderer.prototype.text = function(text) { + return text; +}; + +/** + * Parsing & Compiling + */ + +function Parser(options) { + this.tokens = []; + this.token = null; + this.options = options || marked.defaults; + this.options.renderer = this.options.renderer || new Renderer; + this.renderer = this.options.renderer; + this.renderer.options = this.options; +} + +/** + * Static Parse Method + */ + +Parser.parse = function(src, options, renderer) { + var parser = new Parser(options, renderer); + return parser.parse(src); +}; + +/** + * Parse Loop + */ + +Parser.prototype.parse = function(src) { + this.inline = new InlineLexer(src.links, this.options, this.renderer); + this.tokens = src.reverse(); + + var out = ''; + while (this.next()) { + out += this.tok(); + } + + return out; +}; + +/** + * Next Token + */ + +Parser.prototype.next = function() { + return this.token = this.tokens.pop(); +}; + +/** + * Preview Next Token + */ + +Parser.prototype.peek = function() { + return this.tokens[this.tokens.length - 1] || 0; +}; + +/** + * Parse Text Tokens + */ + +Parser.prototype.parseText = function() { + var body = this.token.text; + + while (this.peek().type === 'text') { + body += '\n' + this.next().text; + } + + return this.inline.output(body); +}; + +/** + * Parse Current Token + */ + +Parser.prototype.tok = function() { + switch (this.token.type) { + case 'space': { + return ''; + } + case 'hr': { + return this.renderer.hr(); + } + case 'heading': { + return this.renderer.heading( + this.inline.output(this.token.text), + this.token.depth, + this.token.text); + } + case 'code': { + return this.renderer.code(this.token.text, + this.token.lang, + this.token.escaped); + } + case 'table': { + var header = '' + , body = '' + , i + , row + , cell + , flags + , j; + + // header + cell = ''; + for (i = 0; i < this.token.header.length; i++) { + flags = { header: true, align: this.token.align[i] }; + cell += this.renderer.tablecell( + this.inline.output(this.token.header[i]), + { header: true, align: this.token.align[i] } + ); + } + header += this.renderer.tablerow(cell); + + for (i = 0; i < this.token.cells.length; i++) { + row = this.token.cells[i]; + + cell = ''; + for (j = 0; j < row.length; j++) { + cell += this.renderer.tablecell( + this.inline.output(row[j]), + { header: false, align: this.token.align[j] } + ); + } + + body += this.renderer.tablerow(cell); + } + return this.renderer.table(header, body); + } + case 'blockquote_start': { + var body = ''; + + while (this.next().type !== 'blockquote_end') { + body += this.tok(); + } + + return this.renderer.blockquote(body); + } + case 'list_start': { + var body = '' + , ordered = this.token.ordered; + + while (this.next().type !== 'list_end') { + body += this.tok(); + } + + return this.renderer.list(body, ordered); + } + case 'list_item_start': { + var body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.token.type === 'text' + ? this.parseText() + : this.tok(); + } + + return this.renderer.listitem(body); + } + case 'loose_item_start': { + var body = ''; + + while (this.next().type !== 'list_item_end') { + body += this.tok(); + } + + return this.renderer.listitem(body); + } + case 'html': { + var html = !this.token.pre && !this.options.pedantic + ? this.inline.output(this.token.text) + : this.token.text; + return this.renderer.html(html); + } + case 'paragraph': { + return this.renderer.paragraph(this.inline.output(this.token.text)); + } + case 'text': { + return this.renderer.paragraph(this.parseText()); + } + } +}; + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function unescape(html) { + return html.replace(/&([#\w]+);/g, function(_, n) { + n = n.toLowerCase(); + if (n === 'colon') return ':'; + if (n.charAt(0) === '#') { + return n.charAt(1) === 'x' + ? String.fromCharCode(parseInt(n.substring(2), 16)) + : String.fromCharCode(+n.substring(1)); + } + return ''; + }); +} + +function replace(regex, opt) { + regex = regex.source; + opt = opt || ''; + return function self(name, val) { + if (!name) return new RegExp(regex, opt); + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return self; + }; +} + +function noop() {} +noop.exec = noop; + +function merge(obj) { + var i = 1 + , target + , key; + + for (; i < arguments.length; i++) { + target = arguments[i]; + for (key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + obj[key] = target[key]; + } + } + } + + return obj; +} + + +/** + * Marked + */ + +function marked(src, opt, callback) { + if (callback || typeof opt === 'function') { + if (!callback) { + callback = opt; + opt = null; + } + + opt = merge({}, marked.defaults, opt || {}); + + var highlight = opt.highlight + , tokens + , pending + , i = 0; + + try { + tokens = Lexer.lex(src, opt) + } catch (e) { + return callback(e); + } + + pending = tokens.length; + + var done = function(err) { + if (err) { + opt.highlight = highlight; + return callback(err); + } + + var out; + + try { + out = Parser.parse(tokens, opt); + } catch (e) { + err = e; + } + + opt.highlight = highlight; + + return err + ? callback(err) + : callback(null, out); + }; + + if (!highlight || highlight.length < 3) { + return done(); + } + + delete opt.highlight; + + if (!pending) return done(); + + for (; i < tokens.length; i++) { + (function(token) { + if (token.type !== 'code') { + return --pending || done(); + } + return highlight(token.text, token.lang, function(err, code) { + if (err) return done(err); + if (code == null || code === token.text) { + return --pending || done(); + } + token.text = code; + token.escaped = true; + --pending || done(); + }); + })(tokens[i]); + } + + return; + } + try { + if (opt) opt = merge({}, marked.defaults, opt); + return Parser.parse(Lexer.lex(src, opt), opt); + } catch (e) { + e.message += '\nPlease report this to https://github.com/chjj/marked.'; + if ((opt || marked.defaults).silent) { + return '

    An error occured:

    '
    +        + escape(e.message + '', true)
    +        + '
    '; + } + throw e; + } +} + +/** + * Options + */ + +marked.options = +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; + +marked.defaults = { + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: false, + sanitizer: null, + mangle: true, + smartLists: false, + silent: false, + highlight: null, + langPrefix: 'lang-', + smartypants: false, + headerPrefix: '', + renderer: new Renderer, + xhtml: false +}; + +/** + * Expose + */ + +marked.Parser = Parser; +marked.parser = Parser.parse; + +marked.Renderer = Renderer; + +marked.Lexer = Lexer; +marked.lexer = Lexer.lex; + +marked.InlineLexer = InlineLexer; +marked.inlineLexer = InlineLexer.output; + +marked.parse = marked; + +if (typeof module !== 'undefined' && typeof exports === 'object') { + module.exports = marked; +} else if (typeof define === 'function' && define.amd) { + define(function() { return marked; }); +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); \ No newline at end of file diff --git a/js/removeDiacritics.js b/js/removeDiacritics.js new file mode 100644 index 0000000..60b1379 --- /dev/null +++ b/js/removeDiacritics.js @@ -0,0 +1,138 @@ +/* + Retrieved from http://stackoverflow.com/a/18391901/3508346 + + 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. +*/ +var defaultDiacriticsRemovalap = [ + {'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'}, + {'base':'AA','letters':'\uA732'}, + {'base':'AE','letters':'\u00C6\u01FC\u01E2'}, + {'base':'AO','letters':'\uA734'}, + {'base':'AU','letters':'\uA736'}, + {'base':'AV','letters':'\uA738\uA73A'}, + {'base':'AY','letters':'\uA73C'}, + {'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'}, + {'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'}, + {'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'}, + {'base':'DZ','letters':'\u01F1\u01C4'}, + {'base':'Dz','letters':'\u01F2\u01C5'}, + {'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'}, + {'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'}, + {'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'}, + {'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'}, + {'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'}, + {'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'}, + {'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'}, + {'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'}, + {'base':'LJ','letters':'\u01C7'}, + {'base':'Lj','letters':'\u01C8'}, + {'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'}, + {'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'}, + {'base':'NJ','letters':'\u01CA'}, + {'base':'Nj','letters':'\u01CB'}, + {'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'}, + {'base':'OI','letters':'\u01A2'}, + {'base':'OO','letters':'\uA74E'}, + {'base':'OU','letters':'\u0222'}, + {'base':'OE','letters':'\u008C\u0152'}, + {'base':'oe','letters':'\u009C\u0153'}, + {'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'}, + {'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'}, + {'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'}, + {'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'}, + {'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'}, + {'base':'TZ','letters':'\uA728'}, + {'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'}, + {'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'}, + {'base':'VY','letters':'\uA760'}, + {'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'}, + {'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'}, + {'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'}, + {'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'}, + {'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'}, + {'base':'aa','letters':'\uA733'}, + {'base':'ae','letters':'\u00E6\u01FD\u01E3'}, + {'base':'ao','letters':'\uA735'}, + {'base':'au','letters':'\uA737'}, + {'base':'av','letters':'\uA739\uA73B'}, + {'base':'ay','letters':'\uA73D'}, + {'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'}, + {'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'}, + {'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'}, + {'base':'dz','letters':'\u01F3\u01C6'}, + {'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'}, + {'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'}, + {'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'}, + {'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'}, + {'base':'hv','letters':'\u0195'}, + {'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'}, + {'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'}, + {'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'}, + {'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'}, + {'base':'lj','letters':'\u01C9'}, + {'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'}, + {'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'}, + {'base':'nj','letters':'\u01CC'}, + {'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'}, + {'base':'oi','letters':'\u01A3'}, + {'base':'ou','letters':'\u0223'}, + {'base':'oo','letters':'\uA74F'}, + {'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'}, + {'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'}, + {'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'}, + {'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'}, + {'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'}, + {'base':'tz','letters':'\uA729'}, + {'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'}, + {'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'}, + {'base':'vy','letters':'\uA761'}, + {'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'}, + {'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'}, + {'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'}, + {'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'} +]; + +var diacriticsMap = {}; +for (var i=0; i < defaultDiacriticsRemovalap.length; i++){ + var letters = defaultDiacriticsRemovalap[i].letters; + for (var j=0; j < letters.length ; j++){ + diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base; + } +} + +// "what?" version ... http://jsperf.com/diacritics/12 +function removeDiacritics(str) { + return str.replace(/[^\u0000-\u007E]/g, function(a){ + return diacriticsMap[a] || a; + }); +} + +/*var allDiacritics = []; +for (var i=0; i < defaultDiacriticsRemovalap.length; i++) { + var letters = defaultDiacriticsRemovalap[i].letters.split(''); + for (var j=0; j < letters.length ; j++){ + allDiacritics = allDiacritics.push(letters[j]); + } +} + +function getDiacriticIndexes(searchString) { + // Finds starting and ending positions of quoted text + // in double or single quotes with escape char support like \" \' + var patt = new RegExp("[" + allDiacritics.join[''] + "]", "gim"); ///"((?:\\.|[^"])*)"/igm; + var ret = []; + while (match = patt.exec(searchString)) { + ret.push(match.index); + } + + return ret; +}*/ \ No newline at end of file diff --git a/js/ui.js b/js/ui.js index 9f0589e..0983213 100644 --- a/js/ui.js +++ b/js/ui.js @@ -29,6 +29,19 @@ function ToggleDescription() { } } +function ToggleSearchFilter() { + var searchFilterToggle = document.getElementById("searchFilterToggle"); + var searchFilterArea = document.getElementById("searchFilterArea"); + + if (searchFilterArea.style.display == "none") { + searchFilterArea.style.display = "block"; + searchFilterToggle.innerHTML = "Hide Search/Filter Options"; + } else { + searchFilterArea.style.display = "none"; + searchFilterToggle.innerHTML = "Search/Filter Options"; + } +} + function ShowInfo(text) { if (text == "terms") { document.getElementById("infoText").innerHTML = termsText;