1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.xmerge.converter.xml.sxc.pexcel.records.formula;
25 
26 import java.io.*;
27 import java.util.Vector;
28 import java.util.Enumeration;
29 
30 import org.openoffice.xmerge.util.Debug;
31 import org.openoffice.xmerge.util.EndianConverter;
32 import org.openoffice.xmerge.converter.xml.sxc.pexcel.records.Workbook;
33 import org.openoffice.xmerge.converter.xml.sxc.pexcel.records.DefinedName;
34 
35 /**
36  * The TokenEncoder encodes a Token to an equivalent pexcel byte[]. The only
37  * public method apart from the default constructor is the getByte method.
38  * This method picks an encoder based onthe Token's type or id field and uses
39  * that encoder to return a byte[] which it returns. This Encoder supports
40  * Operands		Floating point's, Cell references (absolute and relative),
41  *				cell ranges
42  * Operators	+,-,*,/,<,>.<=,>=,<>
43  * Functions	All pexcel fixed and varaible argument functions
44  *
45  */
46 public class TokenEncoder {
47 
48 	private FunctionLookup fl;
49 	private String parseString;
50     private int index;
51 	private Workbook wb;
52 
53     /**
54 	 * Default Constructor
55 	 */
TokenEncoder()56     public TokenEncoder() {
57 
58         parseString = new String();
59         fl = new FunctionLookup();
60     }
61 
62   	/**
63 	 * Sets global workbook data needed for defined names
64 	 */
setWorkbook(Workbook wb)65    	public void setWorkbook(Workbook wb) {
66 
67 		this.wb = wb;
68 	}
69 
70 
71     /**
72      * Return the byte[] equivalent of a <code>Token</code>. The various
73 	 * encoders return <code>Vector</code> of <code>Byte</code> instead
74 	 * of byte[] because the number of bytes returned varies with each
75 	 * <code>Token</code> encoded. After the encoding is finished the Vector
76 	 * in converted to a byte[].
77      *
78 	 * @param t The <code>Token</code> to be encoded
79 	 * @return An equivalent Pocket Excel byte[]
80      */
getByte(Token t)81     public byte[] getByte(Token t) throws IOException {
82 
83 		Vector tmpByteArray = null;     // we use this cause we don't know till after
84                                         // the encoding takes place how big the byte [] will be
85        	//index=0; 						// This class is declared static in
86 										// FormulaHelper so better make sure our index is 0
87         if(t.getTokenType()==ParseToken.TOKEN_OPERATOR) {
88             tmpByteArray = operatorEncoder(t);
89         } else if (t.getTokenType()==ParseToken.TOKEN_FUNCTION_VARIABLE || t.getTokenType()==ParseToken.TOKEN_FUNCTION_FIXED){
90             tmpByteArray = functionEncoder(t);
91         } else {                                    // Operands and functions
92             switch(t.getTokenID()) {
93                 case TokenConstants.TNAME :
94                     tmpByteArray = nameDefinitionEncoder(t);
95                     break;
96                 case TokenConstants.TREF3D :
97                     tmpByteArray = threeDCellRefEncoder(t);
98                     break;
99                 case TokenConstants.TAREA3D:
100                     tmpByteArray = threeDAreaRefEncoder(t);
101                     break;
102                 case TokenConstants.TREF :
103                     tmpByteArray = cellRefEncoder(t);
104                     break;
105                 case TokenConstants.TAREA :
106                     tmpByteArray = areaRefEncoder(t);
107                     break;
108                 case TokenConstants.TNUM :
109                     tmpByteArray = numEncoder(t);
110                     break;
111                 case TokenConstants.TSTRING :
112                     tmpByteArray = stringEncoder(t);
113                     break;
114 				default :
115 					Debug.log(Debug.ERROR, "Encoder found unrecognized Token");
116             }
117         }
118 
119         byte cellRefArray[] = new byte[tmpByteArray.size()];
120         int i = 0;
121 		String s = new String();
122         for(Enumeration e = tmpByteArray.elements();e.hasMoreElements();) {
123             Byte tmpByte = (Byte) e.nextElement();
124 			s = s + tmpByte + " ";
125             cellRefArray[i] = tmpByte.byteValue();
126             i++;
127         }
128 		Debug.log(Debug.TRACE, "Encoding Token " + t.getValue() + " as [" + s + "]");
129         return cellRefArray;
130     }
131 
132     /**
133      * An Operator Encoder.
134 	 *
135 	 * @param t <code>Token</code> to be encoded
136 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
137 	 */
operatorEncoder(Token t)138     private Vector operatorEncoder(Token t) {
139 
140         Vector tmpByteArray = new Vector();
141         tmpByteArray.add(new Byte((byte)t.getTokenID()));
142         return tmpByteArray;
143     }
144 
145 
146     /**
147      * A String Encoder.
148 	 *
149 	 * @param t <code>Token</code> to be encoded
150 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
151 	 */
stringEncoder(Token t)152     private Vector stringEncoder(Token t) throws IOException{
153 
154         Vector tmpByteArray = new Vector();
155 		tmpByteArray.add(new Byte((byte)t.getTokenID()));
156 		tmpByteArray.add(new Byte((byte)(t.getValue().length())));
157 		tmpByteArray.add(new Byte((byte)0x01));
158 		byte [] stringBytes = t.getValue().getBytes("UTF-16LE");
159 		for (int i=0; i<stringBytes.length; i++) {
160 			tmpByteArray.add(new Byte(stringBytes[i]));
161 		}
162         return tmpByteArray;
163     }
164 
165 
166     /**
167      * An Integer Encoder.
168 	 *
169 	 * @param t <code>Token</code> to be encoded
170 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
171 	 */
numEncoder(Token t)172     private Vector numEncoder(Token t) {
173 
174         Vector tmpByteArray = new Vector();
175 
176         double cellLong = (double) Double.parseDouble(t.getValue());
177         tmpByteArray.add(new Byte((byte)t.getTokenID()));
178         byte[] tempByte = EndianConverter.writeDouble(cellLong);
179         for(int byteIter=0;byteIter<tempByte.length;byteIter++) {
180             tmpByteArray.add(new Byte(tempByte[byteIter]));
181         }
182         return tmpByteArray;
183     }
184 
185     /**
186      * Converts a char to an int. It is zero based
187      * so a=0, b=1 etc.
188 	 *
189 	 * @param ch the character to be converted
190 	 * @return -1 if not a character otherwise a 0 based index
191      */
char2int(char ch)192     private int char2int(char ch) {
193         if(!Character.isLetter(ch))
194             return -1;
195 
196         ch = Character.toUpperCase(ch);
197         return ch-'A';
198     }
199 
200  	/**
201  	 * Identify letters
202 	 *
203 	 * @param  c The character which is to be identified
204 	 * @return A boolean returning the result of the comparison
205  	 */
isAlpha(char c)206     private boolean isAlpha(char c) {
207             return(Character.isLetter(c));
208     }
209 
210  	/**
211  	 * Identify numbers
212 	 *
213 	 * @param  c The character which is to be identified
214 	 * @return A boolean returning the result of the comparison
215  	 */
isDigit(char c)216     private boolean isDigit(char c) {
217             return(Character.isDigit(c));
218     }
219 
220  	/**
221  	 * Identify letters or numbers
222 	 *
223 	 * @param  c The character which is to be identified
224 	 * @return A boolean returning the result of the comparison
225  	 */
isAlphaNum(char c)226     private boolean isAlphaNum(char c) {
227         return(isAlpha(c) || isDigit(c));
228     }
229 
230     /**
231      * Parses a column reference and returns it's integer equivalent. (eg.
232 	 * A=0, D=3, BA=27)
233 	 *
234 	 * @return an 0 based index to a column
235      */
column()236     private int column() {
237         char ch = parseString.charAt(index);
238         String columnStr = new String();
239         int col = 0;
240 
241         while(isAlpha(ch)) {
242             columnStr += ch;
243             index++;
244             ch = parseString.charAt(index);
245         }
246 
247         if(columnStr.length()==1) {
248             col = char2int(columnStr.charAt(0));
249         } else if (columnStr.length()==2) {
250             col = char2int(columnStr.charAt(0)) + 1;
251             col = (col*26) + char2int(columnStr.charAt(1));
252         } else {
253 			Debug.log(Debug.ERROR, "Invalid Column Reference " + columnStr );
254         }
255 
256 
257         return col;
258     }
259 
260     /**
261      * Parses a column reference and returns it's integer equivalent. (eg.
262 	 * A=0, D=3, BA=27)
263 	 *
264 	 * @return an 0 based index to a column
265      */
row()266     private int row() {
267         char ch = parseString.charAt(index);
268         String rowStr = new String();
269         int row = 0;
270         boolean status = true;
271 
272         do {
273             rowStr += ch;
274             index++;
275             if(index>=parseString.length()) {
276                 status = false;
277             } else  {
278                 ch = parseString.charAt(index);
279             }
280         } while(isDigit(ch) && status);
281         return Integer.parseInt(rowStr)-1;  // Pexcel uses a 0 based index
282     }
283 
284     /**
285      * A Cell Reference Encoder (It supports absolute and relative addressing)
286 	 *
287 	 * @param cellCoordinates
288 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
289 	 */
encodeCellCoordinates(String cellCoordinates)290 	private byte[] encodeCellCoordinates(String cellCoordinates) {
291         int col = 0, row = 0;
292         int addressing = 0xC000;
293 
294 		index = 0;
295 		parseString = cellCoordinates;
296 		Debug.log(Debug.TRACE,"Encoding cell coordinates " + cellCoordinates);
297 		if(cellCoordinates.charAt(index)=='$') {
298             addressing &= 0x8000;
299             index++;
300         }
301         col = column();
302         if(cellCoordinates.charAt(index)=='$') {
303             addressing &= 0x4000;
304             index++;
305         }
306         row = row();    // Pexcel uses a 0 based index
307         row |= addressing;
308 		byte tokenBytes[] = new byte[3];
309 		tokenBytes[0] = (byte)row;
310 		tokenBytes[1] = (byte)(row>>8);
311 		tokenBytes[2] = (byte)col;
312 		return tokenBytes;
313 	}
314 
315     /**
316      * A name definition Encoder
317 	 *
318 	 * @param t <code>Token</code> to be encoded
319 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
320 	 */
nameDefinitionEncoder(Token t)321     private Vector nameDefinitionEncoder(Token t) {
322 
323         Vector tmpByteArray = new Vector();
324 
325         String nameString = t.getValue();
326 		Debug.log(Debug.TRACE,"NameDefinitionEncoder : " + nameString);
327         tmpByteArray.add(new Byte((byte)t.getTokenID()));
328 		Enumeration e = wb.getDefinedNames();
329 		DefinedName dn;
330 		String name;
331 		int definedNameIndex = 0;
332 		do {
333 			dn = (DefinedName)e.nextElement();
334 			name = dn.getName();
335 			Debug.log(Debug.TRACE,"Name pulled from DefinedName : " + name);
336 			definedNameIndex++;
337 		} while(!nameString.equalsIgnoreCase(name) && e.hasMoreElements());
338 
339 		tmpByteArray.add(new Byte((byte)definedNameIndex));
340 		tmpByteArray.add(new Byte((byte)0x00));
341 
342 		for(int i = 0;i < 12;i++) {
343 			tmpByteArray.add(new Byte((byte)0x00));
344 		}
345 
346         return tmpByteArray;
347     }
348     /**
349      * A Cell Reference Encoder. It supports absolute and relative addressing
350 	 * but not sheetnames.
351 	 *
352 	 * @param t <code>Token</code> to be encoded
353 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
354 	 */
cellRefEncoder(Token t)355     private Vector cellRefEncoder(Token t) {
356 
357         Vector tmpByteArray = new Vector();
358 
359         tmpByteArray.add(new Byte((byte)t.getTokenID()));
360 		byte cellRefBytes[] = encodeCellCoordinates(t.getValue());
361 		for(int i = 0;i < cellRefBytes.length;i++) {
362 			tmpByteArray.add(new Byte(cellRefBytes[i]));
363 		}
364         return tmpByteArray;
365     }
366 
367     /**
368      * This function will find the sheetname index for a given String
369 	 *
370 	 * @param s
371 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
372 	 */
findSheetIndex(String s)373 	private short findSheetIndex(String s) {
374 
375 		short sheetIndex = 0;
376 		String savedName;
377 		String sheetName;
378 		if (s.startsWith("$")) {
379 			sheetName = s.substring(1,s.length());	// Remove $
380 		} else {
381 			sheetName = s.substring(0,s.length());
382 		}
383 		Debug.log(Debug.TRACE,"Searching for Worksheet : " + sheetName);
384 		Vector names = wb.getWorksheetNames();
385 		Enumeration e = names.elements();
386 		do {
387 			savedName = (String) e.nextElement();
388 			sheetIndex++;
389 		} while(!savedName.equalsIgnoreCase(sheetName) && e.hasMoreElements());
390 
391 		Debug.log(Debug.TRACE,"Setting sheetindex to " + sheetIndex);
392 		return (short)(sheetIndex-1);
393 	}
394 
395     /**
396      * A 3D Cell reference encoder
397 	 *
398 	 * @param t <code>Token</code> to be encoded
399 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
400 	 */
threeDCellRefEncoder(Token t)401     private Vector threeDCellRefEncoder(Token t) {
402 
403         Vector tmpByteArray = new Vector();
404         parseString = t.getValue();
405         Debug.log(Debug.TRACE,"Encoding 3D Cell reference " + t);
406         tmpByteArray.add(new Byte((byte)t.getTokenID()));
407 		tmpByteArray.add(new Byte((byte)0xFF));
408 		tmpByteArray.add(new Byte((byte)0xFF));
409 		for(int i = 0;i < 8;i++) {
410 			tmpByteArray.add(new Byte((byte)0x00));
411 		}
412 
413 		String sheetRef = parseString.substring(0, parseString.indexOf('.') + 1);
414 		if (sheetRef.indexOf(':')!=-1) {
415 			sheetRef = parseString.substring(0, parseString.indexOf(':'));
416 			short sheetNum1 = findSheetIndex(sheetRef);
417 			sheetRef = parseString.substring(parseString.indexOf(':') + 1, parseString.length());
418 			short sheetNum2 = findSheetIndex(sheetRef);
419 			tmpByteArray.add(new Byte((byte)sheetNum1));
420 			tmpByteArray.add(new Byte((byte)0x00));
421 			tmpByteArray.add(new Byte((byte)sheetNum2));
422 			tmpByteArray.add(new Byte((byte)0x00));
423 		} else {
424 			sheetRef = parseString.substring(0, parseString.indexOf('.'));
425 			short sheetNum = findSheetIndex(sheetRef);
426 			tmpByteArray.add(new Byte((byte)sheetNum));
427 			tmpByteArray.add(new Byte((byte)0x00));
428 			tmpByteArray.add(new Byte((byte)sheetNum));
429 			tmpByteArray.add(new Byte((byte)0x00));
430 		}
431 		String s = parseString.substring(parseString.indexOf('.') + 1, parseString.length());
432 		Debug.log(Debug.TRACE,"Parsing : " + s);
433 		byte cellRefBytes[] = encodeCellCoordinates(s);
434 		for(int i = 0;i < cellRefBytes.length;i++) {
435 			tmpByteArray.add(new Byte(cellRefBytes[i]));
436 		}
437         return tmpByteArray;
438     }
439     /**
440      * A 3D Area Reference Encoder.
441 	 *
442 	 * @param t <code>Token</code> to be encoded
443 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
444 	 */
threeDAreaRefEncoder(Token t)445     private Vector threeDAreaRefEncoder(Token t) {
446 
447         Vector tmpByteArray = new Vector();
448         parseString = t.getValue();
449         Debug.log(Debug.TRACE,"Encoding 3D Area reference " + t);
450         tmpByteArray.add(new Byte((byte)t.getTokenID()));
451 		tmpByteArray.add(new Byte((byte)0xFF));
452 		tmpByteArray.add(new Byte((byte)0xFF));
453 		for(int i = 0;i < 8;i++) {
454 			tmpByteArray.add(new Byte((byte)0x00));
455 		}
456 
457 		String param1= parseString.substring(0, parseString.indexOf(':'));
458 		String cellRef1 = param1.substring(parseString.indexOf('.') + 1, param1.length());
459 		String sheetRef1 = param1.substring(0, param1.indexOf('.'));
460 		short sheetNum1 = findSheetIndex(sheetRef1);
461 
462 		String param2 = parseString.substring(parseString.indexOf(':') + 1, parseString.length());
463 		Debug.log(Debug.TRACE,"param2: " + param2);
464 		String cellRef2 = param2.substring(param2.indexOf('.') + 1, param2.length());
465 		Debug.log(Debug.TRACE,"cellRef2: " + cellRef2);
466 
467 		if(param2.indexOf('.')==-1) {
468 			tmpByteArray.add(new Byte((byte)sheetNum1));
469 			tmpByteArray.add(new Byte((byte)0x00));
470 			tmpByteArray.add(new Byte((byte)sheetNum1));
471 			tmpByteArray.add(new Byte((byte)0x00));
472 		} else {
473 			String sheetRef2 = param2.substring(0, param2.indexOf('.'));
474 			short sheetNum2 = findSheetIndex(sheetRef2);
475 			tmpByteArray.add(new Byte((byte)sheetNum1));
476 			tmpByteArray.add(new Byte((byte)0x00));
477 			tmpByteArray.add(new Byte((byte)sheetNum2));
478 			tmpByteArray.add(new Byte((byte)0x00));
479 		}
480 
481 		byte cellRefBytes1[] = encodeCellCoordinates(cellRef1);
482 		byte cellRefBytes2[] = encodeCellCoordinates(cellRef2);
483 
484 		tmpByteArray.add(new Byte(cellRefBytes1[0]));
485 		tmpByteArray.add(new Byte(cellRefBytes1[1]));
486 
487 		tmpByteArray.add(new Byte(cellRefBytes2[0]));
488 		tmpByteArray.add(new Byte(cellRefBytes2[1]));
489 
490 		tmpByteArray.add(new Byte(cellRefBytes1[2]));
491 		tmpByteArray.add(new Byte(cellRefBytes2[2]));
492 
493         return tmpByteArray;
494     }
495 
496     /**
497      * A Cell Range Encoder.
498 	 *
499 	 * @param t <code>Token</code> to be encoded
500 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
501 	 */
areaRefEncoder(Token t)502     private Vector areaRefEncoder(Token t) {
503 
504         Vector tmpByteArray = new Vector();
505 
506         tmpByteArray.add(new Byte((byte)t.getTokenID()));
507         String param = t.getValue();
508 		String cellRef1 = new String();
509 		String cellRef2 = new String();
510 
511         if(param.indexOf(':')==-1) {
512 			Debug.log(Debug.ERROR, "Invalid Cell Range, could not find :");
513         } else {
514 			cellRef1 = param.substring(0, param.indexOf(':'));
515 			cellRef2 = param.substring(param.indexOf(':') + 1, param.length());
516         }
517 		byte cellRefBytes1[] = encodeCellCoordinates(cellRef1);
518 		byte cellRefBytes2[] = encodeCellCoordinates(cellRef2);
519 
520 		tmpByteArray.add(new Byte(cellRefBytes1[0]));
521 		tmpByteArray.add(new Byte(cellRefBytes1[1]));
522 
523 		tmpByteArray.add(new Byte(cellRefBytes2[0]));
524 		tmpByteArray.add(new Byte(cellRefBytes2[1]));
525 
526 		tmpByteArray.add(new Byte(cellRefBytes1[2]));
527 		tmpByteArray.add(new Byte(cellRefBytes2[2]));
528 
529         return tmpByteArray;
530     }
531 
532     /**
533      * A Function Encoder.
534 	 *
535 	 * @param t <code>Token</code> to be encoded
536 	 * @return A <code>Vector</code> of pexcel <code>Byte</code>
537 	 */
functionEncoder(Token t)538     private Vector functionEncoder(Token t) {
539         Vector tmpByteArray = new Vector();
540 
541         int id = t.getTokenID();
542         if(t.getTokenType()==ParseToken.TOKEN_FUNCTION_VARIABLE) {
543             tmpByteArray.add(new Byte((byte)TokenConstants.TFUNCVAR));
544             tmpByteArray.add(new Byte((byte)t.getNumArgs()));
545         } else {
546             tmpByteArray.add(new Byte((byte)TokenConstants.TFUNC));
547         }
548 
549         tmpByteArray.add(new Byte((byte)id));
550         tmpByteArray.add(new Byte((byte)(id>>8)));
551         return tmpByteArray;
552     }
553 
554 
555 }
556