1/*var tablecalc_table;
2var tablecalc_crow;
3var tablecalc_ccol;
4var tablecalc_labels;
5var tablecalc_defer;*/
6
7function tablecalcXY(st) {
8	var r=st.match(/r\d+/);
9	y=r[0].substr(1)*1;
10	var c=st.match(/c\d+/);
11	x=c[0].substr(1)*1;
12	return new Array(x,y);
13}
14function tablecalcVal(x,y,table,tostring) {
15	if (typeof tostring == 'undefined') {
16		tostring=0;
17	}
18	var v='notset';
19	if ((x>=0) && (y>=0)) {
20		if (typeof table.rows != 'undefined') {
21			if (typeof table.rows[y] != 'undefined') {
22				if (typeof table.rows[y].cells[x] != 'undefined') {
23					var mr=stripHTML(table.rows[y].cells[x].innerHTML);
24					mr=mr.trim();
25					m=parseFloat(mr);
26					if (!isNaN(m)) {
27						v=m;
28					} else {
29						if (!tostring) {
30							v="notnum";
31						} else {
32							v=String(mr);
33						}
34					}
35				}
36			}
37		}
38	}
39	return v;
40}
41
42
43function tablecalcToArray(a) {
44	if (!Array.isArray(a)) {
45		return [a];
46	} else {
47		return a;
48	}
49}
50
51function tablecalcToNumArray(a) {
52	if (!Array.isArray(a)) {
53		a=[a];
54	}
55	var b=[];
56	for (var i=0;i<a.length;i++) {
57		if (!isNaN(a[i]*1)) {
58			b.push(a[i]);
59		}
60	}
61	return b;
62}
63
64function correctFloat(a) {
65	var x=10000000000000;
66	return Math.round(a*x)/x;
67}
68
69function sum(a) {
70	a=tablecalcToNumArray(a);
71	var s=0;
72	for (var i=0;i<a.length;i++) {
73		tablecalc_log(a[i]*1);
74		s+=a[i]*1;
75		s=correctFloat(s);
76	}
77	return s;
78}
79
80function average(a) {
81	a=tablecalcToNumArray(a);
82	return correctFloat(sum(a)/a.length);
83}
84
85function min(a) {
86	a=tablecalcToNumArray(a);
87	var s=1*a[0];
88	for (var i=1;i<a.length;i++) {
89		if (1*a[i]<s) {
90			s=1*a[i];
91		}
92	}
93	return s;
94}
95
96
97function max(a) {
98	a=tablecalcToNumArray(a);
99	var s=1*a[0];
100	for (var i=1;i<a.length;i++) {
101		if (1*a[i]>s) {
102			s=1*a[i];
103		}
104	}
105	return s;
106}
107
108function label(st) {
109	if (typeof tablecalc_labels[st] == 'undefined') {
110		tablecalc_labels[st]=tablecalc_table;
111	}
112	return "";
113}
114
115function col() {
116	return tablecalc_ccol;
117}
118
119function row() {
120	return tablecalc_crow;
121}
122
123function rows(label) {
124	if (typeof label == 'undefined') {
125		var tbl=tablecalc_table;
126	} else {
127		var tbl=tablecalc_labels[label];
128	}
129	if (!tbl) {
130		return 0;
131	} else {
132		return tbl.rows.length;
133	}
134}
135
136function cols(label) {
137	if (typeof label == 'undefined') {
138		var tbl=tablecalc_table;
139	} else {
140		var tbl=tablecalc_labels[label];
141	}
142	if (!tbl) {
143		return 0;
144	} else {
145		let cols=0;
146		for (const row of tbl.rows) {
147			cols = Math.max(cols,row.cells.length);
148		}
149		return cols;
150	}
151}
152
153function cell(x,y) {
154	var tmp=tablecalcVal(x,y,tablecalc_table, 1);
155	if ( tmp=='notset' ) {
156		return '';
157	} else {
158		return tmp;
159	}
160}
161
162function range(x1,y1,x2,y2) {
163	var members=new Array();
164	for (var x=x1;x<=x2;x++) {
165		for (var y=y1;y<=y2;y++) {
166			var tmp=cell(x,y);
167			if (tmp!='') {
168				members[members.length]=tmp;
169			}
170		}
171	}
172	var result="";
173	if (members.length>0) {
174		result="new Array(";
175		for (var k=0;k<members.length;k++) {
176			if (k) {
177				result+=',';
178			}
179			result+="'"+members[k]+"'";
180		}
181		result+=")"
182	} else if (!tablecalc_checkfinal())  {
183		throw "norange";
184	}
185	return eval(result);
186}
187
188function count(a) {
189	a=tablecalcToArray(a);
190	return a.length;
191}
192
193function calc() {
194	return nop();
195}
196
197function round(num,digits) {
198	var d=1;
199	for (var i=0;i<digits;i++) {
200		d*=10;
201	}
202	var n=Math.round(num*d)/d;
203	return n.toFixed(digits);
204}
205
206
207function nop() {
208	return "";
209}
210
211function check(condition,whenTrue,whenFalse) {
212	if (typeof condition == 'undefined') {
213		condition=0;
214	}
215	if (typeof whenTrue == 'undefined') {
216		whenTrue="";
217	}
218	if (typeof whenFalse == 'undefined') {
219		whenFalse="";
220	}
221	if (condition) {
222		return whenTrue;
223	} else {
224		return whenFalse;
225	}
226}
227
228function countif(range,check,operation) {
229	a=tablecalcToArray(range);
230	var cnt=0;
231	for (var i=0;i<a.length;i++) {
232		cnt+=compare(a[i],check,operation);
233	}
234	return cnt;
235}
236
237function criterionMatch(value, criterion) {
238    if (typeof value === 'undefined') {
239        return false;
240    }
241
242    if (typeof criterion === 'string') {
243        if (criterion.startsWith('>=')) {
244            return parseFloat(value) >= parseFloat(criterion.substring(2));
245        } else if (criterion.startsWith('<=')) {
246            return parseFloat(value) <= parseFloat(criterion.substring(2));
247        } else if (criterion.startsWith('>')) {
248            return parseFloat(value) > parseFloat(criterion.substring(1));
249        } else if (criterion.startsWith('<')) {
250            return parseFloat(value) < parseFloat(criterion.substring(1));
251        } else if (criterion.startsWith('=')) {
252            return parseFloat(value) == parseFloat(criterion.substring(1));
253        } else if (criterion.includes('*') || criterion.includes('?')) {
254            // Mitigate ReDoS: collapse multiple wildcards
255            let safeCriterion = criterion.replace(/\*+/g, '*');
256
257            // Build regex from wildcard string
258            let regexStr = "^";
259            for (let i = 0; i < safeCriterion.length; i++) {
260                let char = safeCriterion[i];
261                if (char === '~') {
262                    // Next char is literal
263                    i++;
264                    if (i < safeCriterion.length) {
265                        // escape for regex
266                        regexStr += safeCriterion[i].replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
267                    }
268                } else if (char === '*') {
269                    regexStr += ".*";
270                } else if (char === '?') {
271                    regexStr += ".";
272                } else {
273                    // escape for regex
274                    regexStr += char.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
275                }
276            }
277            regexStr += "$";
278
279            try {
280                let regex = new RegExp(regexStr);
281                return regex.test(String(value));
282            } catch (e) {
283                // Invalid regex, treat as no match
284                return false;
285            }
286        }
287    }
288    return String(value) == String(criterion);
289}
290
291function sumif(range, criterion, sum_range) {
292    range = tablecalcToArray(range);
293    if (typeof sum_range === 'undefined') {
294        sum_range = range;
295    } else {
296        sum_range = tablecalcToArray(sum_range);
297    }
298
299    var s = 0;
300    for (var i = 0; i < range.length; i++) {
301        if (criterionMatch(range[i], criterion)) {
302            var val = parseFloat(sum_range[i]);
303            if (!isNaN(val)) {
304                s += val;
305                s = correctFloat(s);
306            }
307        }
308    }
309    return s;
310}
311
312
313function compare(a,b,operation) {
314	if (typeof operation == 'undefined') {
315		operation='=';
316	}
317	if (typeof a == 'undefined') {
318		a=0;
319	}
320	if (typeof b == 'undefined') {
321		b=0;
322	}
323	switch (operation) {
324		case ">":
325			if (a>b) {return 1;} else {return 0;}
326			break;
327		case "<":
328			if (a<b) {return 1;} else {return 0;}
329			break;
330		case ">=":
331			if (a>=b) {return 1;} else {return 0;}
332			break;
333		case "<=":
334			if (a<=b) {return 1;} else {return 0;}
335			break;
336		case "<>":
337		case "!=":
338			if (a!=b) {return 1;} else {return 0;}
339			break;
340		case "=":
341		default:
342			if (a==b) {return 1;} else {return 0;}
343	}
344	return 0;
345}
346
347function tablecalc_log(st) {
348	if (!tablecalc_debug) {return;}
349	if (tablecalc_debug==1) {
350		alert(st);
351	} else {
352		console.log(st);
353	}
354}
355
356function tablecalc_checkfinal() {
357	if (typeof tablecalc_isfinal === "undefined") {
358		return 0;
359	} else {
360		return tablecalc_isfinal;
361	}
362}
363
364function tablecalc(divID, formula, final) {
365
366	if (isNaN(final)) {final=0;}
367
368	if (typeof tablecalc_debug === "undefined") {
369    	window.tablecalc_debug=0;
370	}
371	if (typeof tablecalc_labels === "undefined") {
372    	window.tablecalc_labels=[];
373	}
374	if (typeof tablecalc_defer === "undefined") {
375		window.tablecalc_defer=[];
376	}
377	if (typeof tablecalc_crow === "undefined") {
378		window.tablecalc_crow=null;
379	}
380	if (typeof tablecalc_ccol === "undefined") {
381		window.tablecalc_ccol=null;
382	}
383	if (typeof tablecalc_table === "undefined") {
384		window.tablecalc_table=null;
385	}
386	if (typeof tablecalc_isfinal === "undefined") {
387		window.tablecalc_isfinal=final;
388	}
389	if (typeof tablecalc_setfinal === "undefined") {
390	    window.tablecalc_setfinal=1;
391		setTimeout(tablecalc_final,100);
392	}
393
394	var oFormula=formula;
395
396	tablecalc_log("Entering: "+divID+"=>"+formula+"; is final: "+final);
397	var div = document.getElementById(divID);
398	//getting parent TD
399	var table=0;
400	var cCol=0;
401	var cRow=0;
402	var cRows=0;
403	var cCols=0;
404	var pNode=findParentNodeByName(div,"TD");
405	if (!pNode) {
406		pNode=findParentNodeByName(div,"TH");
407	}
408	if (pNode) {
409		cCol = pNode.cellIndex;
410		pNode=findParentNodeByName(pNode,"TR");
411		if (pNode) {
412			cRow = pNode.rowIndex;
413			table=findParentNodeByName(pNode,"TABLE");
414		}
415	}
416	tablecalc_crow=cRow;
417	tablecalc_ccol=cCol;
418	tablecalc_table=table;
419
420	var matchA=formula.match(/([a-z0-9_]+\.)?(r|c)\d+(r|c)\d+(\:(r|c)\d+(r|c)\d+)?(\,([a-z0-9]+\.)?(r|c)\d+(r|c)\d+(\:(r|c)\d+(r|c)\d+)?){0,99}/g);
421	if (matchA != null) {
422		for (var i = 0; i<matchA.length; i++) {
423			var members=new Array();
424
425			var matchL=matchA[i].split(',');
426			for (var j=0;j<matchL.length;j++) {
427				var tmp_table=table;
428				var matchB=matchL[j].split('.',2);
429				if (matchB.length<2) {
430					matchB[1]=matchB[0];
431				} else {
432					if (typeof tablecalc_labels[matchB[0]] != 'undefined') {
433						tmp_table=tablecalc_labels[matchB[0]];
434					} else {
435						if (final) {
436							tmp_table="notable";
437						} else {
438							tablecalcAddDefer(divID,oFormula);
439							//tablecalcProcessDefer();
440							return false;
441						}
442					}
443				}
444				if (tmp_table!="notable") {
445					var matchC=matchB[1].split(':',2);
446					if (matchC.length<2) {
447						matchC[1]=matchC[0];
448					}
449					from=tablecalcXY(matchC[0]);
450					to=tablecalcXY(matchC[1]);
451					if (from[0]>to[0]) {
452						var tmp=to[0];
453						to[0]=from[0];
454						from[0]=tmp;
455					}
456					if (from[1]>to[1]) {
457						var tmp=to[1];
458						to[1]=from[1];
459						from[1]=tmp;
460					}
461					for (var fx=from[0];fx<=to[0];fx++) {
462						for (var fy=from[1];fy<=to[1];fy++) {
463							if ((fx==cCol) && (fy==cRow) && (tmp_table==table)) {continue;}
464							var tmp=tablecalcVal(fx,fy,tmp_table);
465							tablecalc_log("member["+fx+","+fy+"]="+tmp);
466							if ( (tmp == 'notnum') || (tmp == 'notset') ) {
467								tablecalcAddDefer(divID,oFormula);
468								if (!final) {
469									//tablecalcProcessDefer();
470									return false;
471								} else {
472									members[members.length]=tablecalcVal(fx,fy,tmp_table,1);
473								}
474							} else {/*if (tmp!='notset') {*/
475								members[members.length]=tmp;
476							}
477						}
478					}
479				} else {
480					tablecalc_log("table not found by label: "+matchB[0]);
481				}
482			}
483			var result="";
484			if (members.length>0) {
485				if (members.length==1) {
486					var tmp=parseFloat(members[0]);
487					if (isNaN(tmp)) {
488						result="'"+members[0]+"'";
489					} else {
490						result=members[0]*1;
491					}
492				} else {
493					result="new Array(";
494					for (var k=0;k<members.length;k++) {
495						if (k) {
496							result+=',';
497						}
498						result+="'"+members[k]+"'";
499					}
500					result+=")"
501				}
502			}
503			formula=formula.replace(matchA[i],result);
504		}
505	}
506
507	formula=formula.replace(/;/g,",");
508	tablecalc_log("Evaluating: "+formula);
509	var rc;
510	try {
511		eval('calcresult = '+formula);
512		//if (!isNaN(calcresult)) {
513		tablecalc_log("Got result: "+calcresult+" ("+(typeof calcresult)+")");
514		if ((typeof calcresult === "number") && (isNaN(calcresult))) {
515			tablecalcAddDefer(divID,oFormula);
516			rc=false;
517		} else {
518			div.innerHTML=calcresult;
519			rc=true;
520		}
521	} catch (e) {
522		rc=false;
523		tablecalc_log("Exception: "+e);
524		tablecalcAddDefer(divID,oFormula);
525	}
526	if (!final) {
527		tablecalcProcessDefer();
528	}
529	return rc;
530}
531
532function tablecalcAddDefer(divID,formula) {
533	if (typeof tablecalc_defer[divID] == 'undefined') {
534		tablecalc_defer[divID]=formula;
535		tablecalc_log("Added defer: "+divID+"=>"+tablecalc_defer[divID]);
536	}
537}
538
539function tablecalcProcessDefer() {
540	var exit;
541	var steps=0;
542	do {
543		steps++;
544		exit=1;
545		for (var divID in tablecalc_defer) {
546			if (tablecalc_defer[divID].length) {
547				tablecalc_log("calling defer: "+divID+"=>"+tablecalc_defer[divID]);
548				var tmp=tablecalc_defer[divID];
549				tablecalc_defer[divID]="";
550				if (!tablecalc(divID,tmp,0)) {
551					tablecalc_defer[divID]=tmp;
552				} else {
553					exit=0;
554				}
555			}
556		}
557	} while ( (!exit) && (steps<99) );
558	if (steps>=99) {
559		tablecalc_log("max steps reached!");
560	}
561}
562
563function tablecalc_final() {
564	tablecalc_log("entering final");
565	tablecalc_isfinal=1;
566	tablecalcProcessDefer();
567	for (var divID in tablecalc_defer) {
568		if (tablecalc_defer[divID].length) {
569			tablecalc_log("calling final defer: "+divID+"=>"+tablecalc_defer[divID]);
570			if (tablecalc(divID,tablecalc_defer[divID],1,99)) {
571				tablecalc_defer[divID]="";
572			}
573		}
574	}
575}
576
577function findParentNodeByName(pNode, tag) {
578	tag = tag.toUpperCase();
579
580	while (pNode && pNode.nodeName !== tag) {
581		pNode = pNode.parentNode;
582	}
583
584	return (pNode && pNode.nodeName === tag) ? pNode : null;
585}
586
587
588function stripHTML(oldString) {
589	return oldString.replace(/<[^>]*>/g, "");
590}
591