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 
cmpNames(Sit S,NodeHandle v,NodeHandle w)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 
similarVerts(Sit S,NodeHandle v,NodeHandle w)59 Bool similarVerts(Sit S, NodeHandle v, NodeHandle w)
60 {
61   //some optimalization for native node may be done (compare QNames)
62   sabassert(!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 
gotoPreceding(Sit S,NodeHandle v,Bool siblingOnly)90 NodeHandle gotoPreceding(Sit S, NodeHandle v, Bool siblingOnly)
91 {
92     sabassert(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 
countMatchingSiblings(Sit S,int & num,NodeHandle v,Expression * count)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 
xslNumberCount(Sit S,NumberingLevel level,Expression * count,Expression * from,NodeHandle curr,ListInt & result)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 
isAlnumFToken(const Str & s)201 Bool isAlnumFToken(const Str& s)
202 {
203     unsigned long c = utf8CharCode((const char*)s);
204     return utf8IsDigit(c) || utf8IsLetter(c);
205 }
206 
getFToken(const char * & p,Str & fmt)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 
getFTokenParams(const Str & fmt,char & type,int & width)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     sabassert(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 
appendABC(int num,Bool uppercase,DStr & result)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 
appendRoman(int num,Bool uppercase,DStr & result)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 
appendArabic(int num,int width,const Str & groupingSep,int groupingSize,DStr & tempResult)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 
formatSingleNumber(Sit S,int num,const Str & fmt,const Str & lang,NumberingLetterValue letterValue,const Str & groupingSep,int groupingSize,DStr & tempResult)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 
xslNumberFormat(Sit S,ListInt & nums,const Str & format,const Str & lang,NumberingLetterValue letterValue,const Str & groupingSep,int groupingSize,Str & result)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