1/* DokuWiki MoaiEditor Match_tools.js file 2 Author : MoaiTools <info@moaitools.org> 3 License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ 4 5MoaiEditor.MatchTools = class { 6 7 constructor(outer) { 8 9 // Arguments 10 this.outer = outer; 11 12 // Objects 13 this.media = new MoaiEditor.MatchMedia(this); 14 } 15 16 hasBlockTags (text) { 17 18 const tags = [ 19 /<code\b/, 20 /<file\b/, 21 /<wrap\b/, 22 /<WRAP\b/, 23 '</code>', 24 '</file>', 25 '</wrap>', 26 '</WRAP>', 27 ]; 28 for (let tag of tags) { 29 let [index, next] = this.indexOf(text, tag); 30 if (index > -1) 31 return true; 32 } 33 return false; 34 } 35 36 hasImbaInlineTags (text) { 37 38 const tags = [ 39 ['**','**'], 40 ['__','__'], 41 ['//','//'], 42 ["''","''"], 43 ['%%','%%'], 44 ['<sub>','</sub>'], 45 ['<sup>','</sup>'], 46 ['<del>','</del>'], 47 ['<nowiki>','</nowiki>'] 48 ]; 49 for (let pair of tags) { 50 const imbalanced = this.hasImbaTags(text, pair[0], pair[1]); 51 if (imbalanced) 52 return true; 53 } 54 return false; 55 } 56 57 hasImbaTags (text, open, close) { 58 59 var i = 0; 60 var next = 0; 61 var index; 62 var start = 0; 63 while (true) { 64 65 // Opening tag 66 start = next; 67 [index, next] = this.indexOf(text, open, start); 68 if (index == -1) 69 return false; 70 71 // Closing tag 72 start = next; 73 [index, next] = this.indexOf(text, close, start); 74 if (index == -1) 75 return true; 76 77 // Safety valve 78 i += 1; 79 if (i > 1000) { 80 console.warn("MoaiEditor :: Match Tools :: imbalancedTags :: infinite loop"); 81 return true; 82 } 83 } 84 } 85 86 indexOf (string, re, start=0) { 87 // String 88 if (typeof re === 'string') { 89 const index = string.indexOf(re, start); 90 return [index, index + re.length]; 91 } 92 // Regex 93 var indexOf = string.substring(start).search(re); 94 var index = (indexOf >= 0) ? (indexOf + start) : indexOf; 95 return [index, index + 1] 96 } 97 98 wordsInString (words, string) { 99 100 101 // Count the matching words in the string considering order 102 var n = 0; 103 var m = 0; 104 for (let word of words) { 105 if (word.trim().length == 0) 106 continue; 107 const index = string.indexOf(word); 108 109 if (index > -1) { 110 m += 1; 111 string = string.substring(index + word.length) 112 } 113 n += 1; 114 } 115 // Calc score 116 var score = null; if (n > 0) score = m/n; 117 118 // Return 119 return {n:n, m:m, score:score}; 120 } 121 122 replaceLinks (block) { 123 124 // Get the text from the block 125 var text = block.text; 126 if ('cleantext' in block) 127 text = block.cleantext; 128 var nolink = text; 129 130 // Replace [[square bracket links]] by what 'node.textContent' would show 131 var matches = text.match(/\[\[.*?\]\](?!\])/g); 132 if (matches === null) 133 matches = []; 134 for (let match of matches) { 135 136 // Remove the square brackets and trim space 137 var string = match.substring(2, match.length-2).trim(); 138 139 // If it is empty, show 'start' 140 if (string.length == 0) { 141 text = this.strReplaceFirst(text, match, 'start'); 142 continue; 143 } 144 // If it has a text description, show it 145 var [left, right] = this.lpartition(string, '|'); 146 if (right.length > 0) { 147 text = this.strReplaceFirst(text, match, right.trim()); 148 nolink = this.strReplaceFirst(text, match, ''); 149 continue; 150 } 151 // If it is an external link, show it like it is 152 string = left; 153 if (string.startsWith('http://') || string.startsWith('https://')) { 154 text = this.strReplaceFirst(text, match, string); 155 nolink = this.strReplaceFirst(text, match, ''); 156 continue; 157 } 158 // If it is an internal link with section tag, show the section 159 var [left, right] = this.lpartition(string, '#'); 160 if (right.length > 0) { 161 text = this.strReplaceFirst(text, match, right.trim()); 162 nolink = this.strReplaceFirst(text, match, ''); 163 continue; 164 } 165 // If it is a normal internal link show the part after the last ':' 166 string = left; 167 var lastpart = this.rpartition(string, ':'); 168 if (lastpart.length > 0) { 169 text = this.strReplaceFirst(text, match, lastpart); 170 nolink = this.strReplaceFirst(text, match, ''); 171 continue; 172 } 173 // If that last part is empty (for example in 'animals:mammals::') show 'start' 174 text = this.strReplaceFirst(text, match, 'start'); 175 nolink = this.strReplaceFirst(text, match, ''); 176 } 177 // Replace urls so we dont trigger the '//' inline tag 178 const urls = nolink.match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\b/g); 179 if (urls !== null) 180 for (let url of urls) 181 nolink = nolink.replace(url, ""); 182 183 // Store cleaned text 184 block.cleantext = text; 185 block.nolinktext = nolink; // Used just for not triggering inline tags like '//', etc 186 } 187 188 strReplaceFirst (string, search, replace) { 189 const index = string.indexOf(search); 190 return string.substring(0, index) + replace + string.substring(index + search.length); 191 } 192 193 lpartition (string, char) { 194 const index = string.indexOf(char); 195 if (index == -1) 196 return [string, '']; 197 const left = string.substring(0, index); 198 const right = string.substring(index+1); 199 return [left, right]; 200 } 201 rpartition (string, char) { 202 string = string.replace(/:{1}$/, ''); // If the last character is the separator, remove it 203 //string = string.trim(char); 204 const index = string.lastIndexOf(char); 205 if (index == -1) 206 return string; 207 const left = string.substring(0, index); 208 const right = string.substring(index+1); 209 return right; 210 } 211 // ┌───────────────────────────────────┐ 212 // │ Text content │ 213 // └───────────────────────────────────┘ 214 215 sameWords (score, text1, text2, separator) { 216 217 // Define separators 218 const separators = { 219 whitespace : /\s+/, 220 tables : /falta/, 221 }; 222 const sep = separators[separator]; 223 224 // Split by any whitespace (space, tab, new line) 225 var words1 = text1.split(sep); 226 var words2 = text2.split(sep); 227 228 // Filter out empty strings 229 words1 = words1.filter(str => (str !== '')); 230 words2 = words2.filter(str => (str !== '')); 231 232 233 // Compare words considering order both ways, add the scores 234 const [n1, m1] = this.compareWords(words1, words2); 235 const [n2, m2] = this.compareWords(words2, words1); 236 const m = m1+m2; 237 const n = n1+n2; 238 239 // Update the score 240 score.m += m; 241 score.n += n; 242 } 243 244 compareWords (words1, words2) { 245 246 // Copy array because it will be trimmed 247 var words = words2.slice(); 248 249 // Count the matching words considering order 250 var n = 0; 251 var m = 0; 252 var i = -1; 253 for (let word1 of words1) { 254 if (word1.trim().length == 0) 255 continue; 256 const index = words.indexOf(word1); 257 258 if (index > -1) { 259 if (index > i) 260 m += 1; 261 i = index-1; 262 words.splice(index, 1); 263 } 264 n += 1; 265 } 266 // Return matches and number of comparisons 267 return [n, m]; 268 } 269 270 sameLinks () { 271 // Devolver null si el nodo no tiene <a>, o un porcentaje de coincidencias y el numero de coincidencias (8, 75%) 272 } 273}; // End Class 274