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