added Info.plist
[TestXSLT.git] / libsablot / src / engine / numbering.cpp
1 /* 
2  * The contents of this file are subject to the Mozilla Public
3  * License Version 1.1 (the "License"); you may not use this file
4  * except in compliance with the License. You may obtain a copy of
5  * the License at http://www.mozilla.org/MPL/
6  * 
7  * Software distributed under the License is distributed on an "AS
8  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9  * implied. See the License for the specific language governing
10  * rights and limitations under the License.
11  * 
12  * The Original Code is the Sablotron XSLT Processor.
13  * 
14  * The Initial Developer of the Original Code is Ginger Alliance Ltd.
15  * Portions created by Ginger Alliance are Copyright (C) 2000-2002
16  * Ginger Alliance Ltd. All Rights Reserved.
17  * 
18  * Contributor(s):
19  * 
20  * Alternatively, the contents of this file may be used under the
21  * terms of the GNU General Public License Version 2 or later (the
22  * "GPL"), in which case the provisions of the GPL are applicable 
23  * instead of those above.  If you wish to allow use of your 
24  * version of this file only under the terms of the GPL and not to
25  * allow others to use your version of this file under the MPL,
26  * indicate your decision by deleting the provisions above and
27  * replace them with the notice and other provisions required by
28  * the GPL.  If you do not delete the provisions above, a recipient
29  * may use your version of this file under either the MPL or the
30  * GPL.
31  */
32
33 #include "numbering.h"
34 #include "verts.h"
35 #include "expr.h"
36 #include "context.h"
37 #include "domprovider.h"
38
39 Bool cmpNames(Sit S, NodeHandle v, NodeHandle w)
40 {
41   int ret = 0;
42   const char *aux1, *aux2;
43   aux1 = S.dom().getNodeNameLocal(v);
44   aux2 = S.dom().getNodeNameLocal(w);
45   ret = strcmp(aux1, aux2);
46   S.dom().freeName(v, (char*)aux1);
47   S.dom().freeName(w, (char*)aux2);
48   if (!ret) 
49     {
50       aux1 = S.dom().getNodeNameURI(v);
51       aux2 = S.dom().getNodeNameURI(w);
52       ret = strcmp(aux1, aux2);
53       S.dom().freeName(v, (char*)aux1);
54       S.dom().freeName(w, (char*)aux2);
55     }
56   return ret;
57 }
58
59 Bool similarVerts(Sit S, NodeHandle v, NodeHandle w)
60 {
61   //some optimalization for native node may be done (compare QNames)
62   assert(!nhNull(v) && !nhNull(w));
63   SXP_NodeType typeV = S.dom().getNodeType(v);
64   // vertices of different types don't match
65   if (typeV != S.dom().getNodeType(w) )
66     return FALSE;
67   //test similarity
68   switch(typeV)
69     {
70     case DOCUMENT_NODE:
71     case TEXT_NODE:
72     case COMMENT_NODE:
73       // no expanded name, they match
74       return TRUE;
75     case ELEMENT_NODE:
76       //return toE(v) -> getName() == toE(w) -> getName();
77     case NAMESPACE_NODE:
78       //return toNS(v) -> getName() == toNS(w) -> getName();
79     case ATTRIBUTE_NODE:
80       //return toA(v) -> getName() == toA(w) -> getName();
81     case PROCESSING_INSTRUCTION_NODE:
82       //return toPI(v) -> getName() == toPI(w) -> getName();
83       return !cmpNames(S, v, w);
84     default:
85       // do this to keep compiler happy
86       return FALSE;
87     }
88 }
89
90 NodeHandle gotoPreceding(Sit S, NodeHandle v, Bool siblingOnly)
91 {
92     assert(v);
93     switch(S.dom().getNodeType(v))
94     {
95     case DOCUMENT_NODE:
96     case ATTRIBUTE_NODE:
97     case NAMESPACE_NODE:
98         // no preceding nodes according to XPath spec
99         return NULL;
100     default:
101       {
102         NodeHandle par = S.dom().getParent(v);
103         
104         if (siblingOnly) 
105           {
106             return S.dom().getPreviousSibling(v);
107             //if (v -> ordinal)
108             //  return toD(par) -> contents[v -> ordinal - 1];
109             //else
110             //  return NULL;
111           }
112         else //preceding and ancestor-or-self union
113           {
114             //if we have preceding sibling we switch to it and dril
115             NodeHandle w = S.dom().getPreviousSibling(v);
116             if (!nhNull(w)) 
117               {
118                 for (v = w;
119                      !nhNull(v) && S.dom().getChildCount(v); 
120                      v = S.dom().getChildNo(v, S.dom().getChildCount(v) - 1));
121                 return v;
122               }
123             else //go to parent
124               {
125                 return S.dom().getNodeType(par) == DOCUMENT_NODE ? NULL : par;
126               }
127           }
128       }
129     }
130 }
131
132 eFlag countMatchingSiblings(Sit S, int& num, NodeHandle v, Expression *count)
133 {
134     num = 0;
135     NodeHandle w;
136     Bool doesMatch;
137     Context c(NULL); //_cn_ we're matching a pattern
138     for (w = v; !nhNull(w); w = gotoPreceding(S, w, /* siblingOnly = */ TRUE))
139     {
140         if (count)
141         {
142             c.deppendall();
143             c.set(w);
144             E( count -> matchesPattern(S, &c, doesMatch) );
145         }
146         else
147             doesMatch = similarVerts(S, v, w);
148         if (doesMatch)
149             num++;
150     }
151     return OK;
152 }
153
154 eFlag xslNumberCount(
155     Sit S, NumberingLevel level, 
156     Expression* count, Expression* from, 
157     NodeHandle curr, ListInt& result)
158 {
159     result.deppendall();
160     int num;
161     NodeHandle w = NULL;
162     List<NodeHandle> matchingList;
163     Bool doesMatch;
164     Context c(NULL); //_cn_ we're matching a pattern
165     // construct the list of matching ancestors/preceding nodes
166     for (w = curr; !nhNull(w); )
167     {
168         c.deppendall();
169         c.set(w);
170         if  (from)
171         {
172             E( from -> matchesPattern(S, &c, doesMatch) );
173             if (doesMatch) break; // leave the for loop
174         }
175         if (count)
176             E( count -> matchesPattern(S, &c, doesMatch) )
177         else
178             doesMatch = similarVerts(S, curr, w);
179         if (doesMatch)
180         {
181             matchingList.append(w);
182             if (level == NUM_SINGLE) break; // leave the for loop after finding a match
183         }
184         if (level == NUM_ANY)
185             w = gotoPreceding(S, w, /* siblingOnly = */ FALSE);
186         else
187             w = S.dom().getParent(w);
188     }
189     // construct the integer list out of matchingList
190     if (level == NUM_ANY)
191         result.append(matchingList.number());
192     else
193         for (int i = matchingList.number() - 1; i >= 0; i--)
194         {
195             E( countMatchingSiblings(S, num, matchingList[i], count) );
196             result.append(num);
197         }
198     return OK;
199 }
200
201 Bool isAlnumFToken(const Str& s)
202 {
203     unsigned long c = utf8CharCode((const char*)s);
204     return utf8IsDigit(c) || utf8IsLetter(c);
205 }
206
207 Bool getFToken(const char *&p, Str& fmt)
208 {
209     if (!*p)
210         return FALSE;
211     const char* pOrig = p;
212     Bool alnum = isAlnumFToken(p);
213     do
214         p += utf8SingleCharLength(p);
215     while(*p && isAlnumFToken(p) == alnum);
216     fmt.nset(pOrig, (int)(p - pOrig));
217     return TRUE;
218 }
219
220 void getFTokenParams(const Str& fmt, char& type, int& width)
221 {
222     // defaults:
223     type = '1';
224     width = 1;
225     int len = utf8StrLength(fmt);
226     assert(len);
227     if (len > 1 && fmt[0] != '0') return; // with default values
228     switch(fmt[0])
229     {
230     case 'A':
231     case 'a':
232     case 'I':
233     case 'i':
234     {
235         type = fmt[0];
236         return;
237     }
238     case '0':
239         break;
240     default:
241         return;
242     }
243     // it remains to take care of the '0':
244     for (int i = 1; i < len - 1; i++)
245         if (fmt[i] != '0') return;
246     if (fmt[len - 1] != '1') return;
247     width = len;
248 }
249
250 void appendABC(int num, Bool uppercase, DStr& result)
251 {
252     DStr reversed;
253     do
254     {
255         num--;
256         reversed += (char)((uppercase ? 'A' : 'a') + num % 26);
257         num /= 26;
258     }
259     while (num > 0);
260     for (int i = reversed.length() - 1; i >= 0; i--)
261         result += reversed[i];
262 }
263
264 struct RomanDef
265 {
266     int value;
267     char symbol[3];
268 };
269
270 RomanDef romans[] =
271 {
272     { 1000, "mM"},
273     { 500, "dD"},
274     { 100, "cC"},
275     { 50, "lL"},
276     { 10, "xX"},
277     { 5, "vV"},
278     { 1, "iI"},
279     { 0, "oO"}
280 };
281
282 void appendRoman(int num, Bool uppercase, DStr& result)
283 {
284     int step = 0,
285         prefix,
286         val;
287     if (uppercase != 0)
288         uppercase = 1;
289     while (num > 0)
290     {
291         if (num >= (val = romans[step].value))
292         {
293             result += romans[step].symbol[uppercase];
294             num -= val;
295         }
296         else 
297         {
298             prefix = step + 2 - step % 2;
299             if (val > 1 && num >= (val - romans[prefix].value))
300             {
301                 result += romans[prefix].symbol[uppercase];
302                 result += romans[step].symbol[uppercase];
303                 num -= (val - romans[prefix].value);
304             }
305             else
306                 step++;
307         }
308     }
309 }
310
311 void appendArabic(
312     int num, int width, 
313     const Str& groupingSep, int groupingSize, DStr& tempResult)
314 {
315     // just put separators and leading zeroes in the number
316     DStr sprFmt = DStr("%0") + width + "d";
317     char buff[32],
318         *p = buff;
319     int len = snprintf(buff, 32, (char*)sprFmt, num);
320     if (!groupingSize)
321         tempResult += buff;
322     else
323     {
324         int first = len % groupingSize;
325         if (first)
326         {
327             tempResult.nadd(p, first);
328             p += first;
329             len -= first;
330             if (len)
331                 tempResult += groupingSep;
332         }
333         for (; len > 0; len -= groupingSize, p += groupingSize)
334         {
335             tempResult.nadd(p, groupingSize);
336             if (len > groupingSize)
337                 tempResult += groupingSep;
338         }
339     }
340 }
341
342 void formatSingleNumber(
343     Sit S, int num, const Str& fmt, 
344     const Str& lang, NumberingLetterValue letterValue, 
345     const Str& groupingSep, int groupingSize, DStr& tempResult)
346 {
347     // we only add to tempResult, do not initialize it
348     char type;
349     int width;
350     // check value of num
351     if (num <= 0)
352     {
353         S.message(MT_WARN, W_NUMBER_NOT_POSITIVE, (char*)NULL, (char*)NULL);
354         num = num ? abs(num) : 1;
355     }
356     getFTokenParams(fmt, type, width);
357     switch(type)
358     {
359     case 'A':
360     case 'a':
361         appendABC(num, /* uppercase = */ type == 'A', tempResult);
362         break;
363     case 'I':
364     case 'i':
365         appendRoman(num, /* uppercase = */ type == 'I', tempResult);
366         break;
367     default:
368         appendArabic(num, width, groupingSep, groupingSize, tempResult);
369     }
370 }
371
372 eFlag xslNumberFormat(
373     Sit S, ListInt& nums, const Str& format, 
374     const Str& lang, NumberingLetterValue letterValue, 
375     const Str& groupingSep, int groupingSize, Str& result)
376 {
377     DStr tempResult;
378     Str sep = ".", 
379         sepRightmost, 
380         alpha = "1",
381         firstToken;
382
383     const char *p = (const char*) format;
384     int ndx = 0;
385
386     if (getFToken(p, firstToken))
387     {
388         if (isAlnumFToken(firstToken) && nums.number())
389         {
390             alpha = firstToken;
391             formatSingleNumber(
392                 S, nums[0], alpha, lang, letterValue,
393                 groupingSep, groupingSize, tempResult);
394             ndx = 1;
395         }
396         else
397         {
398             // reset p to the beginning
399             p = (const char*) format;
400             if (!nums.number())
401                 tempResult += sepRightmost = firstToken;;
402         }
403     }
404     // p points at the first separator (if any)
405     Bool readAllFormat = *p ? FALSE : TRUE;
406     for (; ndx < nums.number(); ndx++)
407     {
408         if (!readAllFormat)
409         {
410             // always update the rightmost separator
411             if (getFToken(p, sepRightmost))
412             {
413                 if (getFToken(p, alpha))
414                 {
415                     // alpha token found
416                     // use the current separator and reset the rightmost one
417                     sep = sepRightmost;
418                     sepRightmost.empty();
419                 }
420                 else
421                 {
422                     // no alpha token, use the same separator as last time
423                     // keep current separator in sepRightmost
424                     readAllFormat = TRUE;
425                     if (!ndx)
426                         sep = sepRightmost;
427                 }
428                 
429             }
430             else
431                 // both empty, use the same separator and alpha as last time
432                 readAllFormat = TRUE;
433         } // if (!readAllFormat)
434         // sep and alpha are the last valid tokens of each kind
435         tempResult += sep;
436         formatSingleNumber(
437             S, nums[ndx], alpha, lang, letterValue,
438             groupingSep, groupingSize, tempResult);
439     } // for loop
440     // get the real rightmost separator
441     if (!readAllFormat)
442     {
443         while(getFToken(p, sepRightmost));
444         if (isAlnumFToken(sepRightmost))
445             sepRightmost.empty();
446     }
447     tempResult += sepRightmost;
448     result = tempResult;
449     return OK;
450 }
451
452