1    
2    /* ====================================================================
3     * The Apache Software License, Version 1.1
4     *
5     * Copyright (c) 2002 The Apache Software Foundation.  All rights
6     * reserved.
7     *
8     * Redistribution and use in source and binary forms, with or without
9     * modification, are permitted provided that the following conditions
10    * are met:
11    *
12    * 1. Redistributions of source code must retain the above copyright
13    *    notice, this list of conditions and the following disclaimer.
14    *
15    * 2. Redistributions in binary form must reproduce the above copyright
16    *    notice, this list of conditions and the following disclaimer in
17    *    the documentation and/or other materials provided with the
18    *    distribution.
19    *
20    * 3. The end-user documentation included with the redistribution,
21    *    if any, must include the following acknowledgment:
22    *       "This product includes software developed by the
23    *        Apache Software Foundation (http://www.apache.org/)."
24    *    Alternately, this acknowledgment may appear in the software itself,
25    *    if and wherever such third-party acknowledgments normally appear.
26    *
27    * 4. The names "Apache" and "Apache Software Foundation" and
28    *    "Apache POI" must not be used to endorse or promote products
29    *    derived from this software without prior written permission. For
30    *    written permission, please contact apache@apache.org.
31    *
32    * 5. Products derived from this software may not be called "Apache",
33    *    "Apache POI", nor may "Apache" appear in their name, without
34    *    prior written permission of the Apache Software Foundation.
35    *
36    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    * SUCH DAMAGE.
48    * ====================================================================
49    *
50    * This software consists of voluntary contributions made by many
51    * individuals on behalf of the Apache Software Foundation.  For more
52    * information on the Apache Software Foundation, please see
53    * <http://www.apache.org/>.
54    */
55   
56   package org.apache.poi.hssf.record;
57   
58   import org.apache.poi.util.LittleEndian;
59   import org.apache.poi.util.StringUtil;
60   import java.util.Stack;
61   import org.apache.poi.hssf.record.formula.Ptg;
62   import org.apache.poi.hssf.record.formula.Area3DPtg;
63   import org.apache.poi.hssf.record.formula.Ref3DPtg;
64   import java.util.List;
65   import org.apache.poi.hssf.util.RangeAddress;
66   
67   /**
68    * Title:        Name Record (aka Named Range) <P>
69    * Description:  Defines a named range within a workbook. <P>
70    * REFERENCE:  <P>
71    * @author Libin Roman (Vista Portal LDT. Developer)
72    * @version 1.0-pre
73    */
74   
75   public class NameRecord extends Record {
76       /**
77        */
78       public final static short sid = 0x18; //Docs says that it is 0x218
79       private short             field_1_option_flag;
80       private byte              field_2_keyboard_shortcut;
81       private byte              field_3_length_name_text;
82       private short             field_4_length_name_definition;
83       private short             field_5_index_to_sheet;
84       private short             field_6_equals_to_index_to_sheet;
85       private byte              field_7_length_custom_menu;
86       private byte              field_8_length_description_text;
87       private byte              field_9_length_help_topic_text;
88       private byte              field_10_length_status_bar_text;
89       private byte              field_11_compressed_unicode_flag;   // not documented
90       private String            field_12_name_text;
91       private Stack             field_13_name_definition;
92       private byte[]            field_13_raw_name_definition = null; // raw data
93       private String            field_14_custom_menu_text;
94       private String            field_15_description_text;
95       private String            field_16_help_topic_text;
96       private String            field_17_status_bar_text;
97   
98       /** Creates new NameRecord */
99       public NameRecord() {
100          field_13_name_definition = new Stack();
101  
102          field_12_name_text = new String();
103          field_14_custom_menu_text = new String();
104          field_15_description_text = new String();
105          field_16_help_topic_text = new String();
106          field_17_status_bar_text = new String();
107      }
108  
109      /**
110       * Constructs a Name record and sets its fields appropriately.
111       *
112       * @param id     id must be 0x18 or an exception will be throw upon validation
113       * @param size  the size of the data area of the record
114       * @param data  data of the record (should not contain sid/len)
115       */
116      public NameRecord(short id, short size, byte [] data) {
117          super(id, size, data);
118      }
119  
120      /**
121       * Constructs a Name record and sets its fields appropriately.
122       *
123       * @param id     id must be 0x18 or an exception will be throw upon validation
124       * @param size  the size of the data area of the record
125       * @param data  data of the record (should not contain sid/len)
126       * @param offset of the record's data
127       */
128      public NameRecord(short id, short size, byte [] data, int offset) {
129          super(id, size, data, offset);
130      }
131  
132      /** sets the option flag for the named range
133       * @param flag option flag
134       */
135      public void setOptionFlag(short flag){
136          field_1_option_flag = flag;
137      }
138  
139      /** sets the keyboard shortcut
140       * @param shortcut keyboard shortcut
141       */
142      public void setKeyboardShortcut(byte shortcut){
143          field_2_keyboard_shortcut = shortcut;
144      }
145  
146      /** sets the name of the named range length
147       * @param length name length
148       */
149      public void setNameTextLength(byte length){
150          field_3_length_name_text = length;
151      }
152  
153      /** sets the definition (reference - formula) length
154       * @param length defenition length
155       */
156      public void setDefinitionTextLength(short length){
157          field_4_length_name_definition = length;
158      }
159  
160      /** sets the index number to the extern sheet (thats is what writen in ducomentetion
161       *  but as i saw , its work direrent)
162       * @param index extern sheet index
163       */
164      public void setIndexToSheet(short index){
165          field_5_index_to_sheet = index;
166  
167          // field_6_equals_to_index_to_sheet is equal to field_5_index_to_sheet
168          field_6_equals_to_index_to_sheet = index;
169      }
170  
171      /** sets the custom menu length
172       * @param length custom menu length
173       */
174      public void setCustomMenuLength(byte length){
175          field_7_length_custom_menu = length;
176      }
177  
178      /** sets the length of named range description
179       * @param length description length
180       */
181      public void setDescriptionTextLength(byte length){
182          field_8_length_description_text = length;
183      }
184  
185      /** sets the help topic length
186       * @param length help topic length
187       */
188      public void setHelpTopicLength(byte length){
189          field_9_length_help_topic_text = length;
190      }
191  
192      /** sets the length of the status bar text
193       * @param length status bar text length
194       */
195      public void setStatusBarLength(byte length){
196          field_10_length_status_bar_text = length;
197      }
198  
199      /** sets the compressed unicode flag
200       * @param flag unicode flag
201       */
202      public void setCompressedUnicodeFlag(byte flag) {
203          field_11_compressed_unicode_flag = flag;
204      }
205  
206      /** sets the name of the named range
207       * @param name named range name
208       */
209      public void setNameText(String name){
210          field_12_name_text = name;
211      }
212  
213      //    public void setNameDefintion(String definition){
214      //        test = definition;
215      //    }
216  
217      /** sets the custom menu text
218       * @param text custom menu text
219       */
220      public void setCustomMenuText(String text){
221          field_14_custom_menu_text = text;
222      }
223  
224      /** sets the description text
225       * @param text the description text
226       */
227      public void setDescriptionText(String text){
228          field_15_description_text = text;
229      }
230  
231      /** sets the help topic text
232       * @param text help topix text
233       */
234      public void setHelpTopicText(String text){
235          field_16_help_topic_text = text;
236      }
237  
238      /** sets the status bar text
239       * @param text status bar text
240       */
241      public void setStatusBarText(String text){
242          field_17_status_bar_text = text;
243      }
244  
245      /** gets the option flag
246       * @return option flag
247       */
248      public short getOptionFlag(){
249          return field_1_option_flag;
250      }
251  
252      /** returns the keyboard shortcut
253       * @return keyboard shortcut
254       */
255      public byte getKeyboardShortcut(){
256          return field_2_keyboard_shortcut ;
257      }
258  
259      /** gets the name length
260       * @return name length
261       */
262      public byte getNameTextLength(){
263          return field_3_length_name_text;
264      }
265  
266      /** get the definition length
267       * @return definition length
268       */
269      public short getDefinitionTextLength(){
270          return field_4_length_name_definition;
271      }
272  
273      /** gets the index to extern sheet
274       * @return index to extern sheet
275       */
276      public short getIndexToSheet(){
277          return field_5_index_to_sheet;
278      }
279  
280      /** gets the custom menu length
281       * @return custom menu length
282       */
283      public byte getCustomMenuLength(){
284          return field_7_length_custom_menu;
285      }
286  
287      /** gets the description text length
288       * @return description text length
289       */
290      public byte getDescriptionTextLength(){
291          return field_8_length_description_text;
292      }
293  
294      /** gets the help topic length
295       * @return help topic length
296       */
297      public byte getHelpTopicLength(){
298          return field_9_length_help_topic_text;
299      }
300  
301      /** get the status bar text length
302       * @return satus bar length
303       */
304      public byte getStatusBarLength(){
305          return field_10_length_status_bar_text;
306      }
307  
308      /** gets the name compressed Unicode flag
309       * @return compressed unicode flag
310       */
311      public byte getCompressedUnicodeFlag() {
312          return field_11_compressed_unicode_flag;
313      }
314  
315      /** gets the name
316       * @return name
317       */
318      public String getNameText(){
319          return field_12_name_text;
320      }
321  
322      /** gets the definition, reference (Formula)
323       * @return definition -- can be null if we cant parse ptgs
324       */
325      protected List getNameDefinition() {
326          return ( List ) field_13_name_definition;
327      }
328  
329      /** get the custom menu text
330       * @return custom menu text
331       */
332      public String getCustomMenuText(){
333          return field_14_custom_menu_text;
334      }
335  
336      /** gets the description text
337       * @return description text
338       */
339      public String getDescriptionText(){
340          return field_15_description_text;
341      }
342  
343      /** get the help topic text
344       * @return gelp topic text
345       */
346      public String getHelpTopicText(){
347          return field_16_help_topic_text;
348      }
349  
350      /** gets the status bar text
351       * @return status bar text
352       */
353      public String getStatusBarText(){
354          return field_17_status_bar_text;
355      }
356  
357      /**
358       * called by constructor, should throw runtime exception in the event of a
359       * record passed with a differing ID.
360       *
361       * @param id alleged id for this record
362       */
363      protected void validateSid(short id) {
364          if (id != sid) {
365              throw new RecordFormatException("NOT A valid Name RECORD");
366          }
367      }
368  
369      /**
370       * called by the class that is responsible for writing this sucker.
371       * Subclasses should implement this so that their data is passed back in a
372       * byte array.
373       *
374       * @param offset to begin writing at
375       * @param data byte array containing instance data
376       * @return number of bytes written
377       */
378      public int serialize(int offset, byte[] data) {
379          LittleEndian.putShort(data, 0 + offset, sid);
380          LittleEndian.putShort(data, 2 + offset, (short)( 15 + getTextsLength()));
381          LittleEndian.putShort(data, 4 + offset, getOptionFlag());
382          data[6 + offset] = getKeyboardShortcut();
383          data[7 + offset] = getNameTextLength();
384          LittleEndian.putShort(data, 8 + offset, getDefinitionTextLength());
385          LittleEndian.putShort(data, 10 + offset, getIndexToSheet());
386          LittleEndian.putShort(data, 12 + offset, getIndexToSheet());
387          data [14 + offset] =  getCustomMenuLength();
388          data [15 + offset] =  getDescriptionTextLength();
389          data [16 + offset] =  getHelpTopicLength();
390          data [17 + offset] =  getStatusBarLength();
391          data [18 + offset] =  getCompressedUnicodeFlag();
392  
393          StringUtil.putCompressedUnicode(getNameText(), data , 19 + offset);
394  
395          int start_of_name_definition    = 19  + field_3_length_name_text;
396          if (this.field_13_name_definition != null) {
397              serializePtgs(data, start_of_name_definition + offset);
398          } else {
399              System.arraycopy(field_13_raw_name_definition,0,data
400              ,start_of_name_definition + offset,field_13_raw_name_definition.length);
401          }
402  
403          int start_of_custom_menu_text   = start_of_name_definition + field_4_length_name_definition;
404          StringUtil.putCompressedUnicode(getCustomMenuText(), data , start_of_custom_menu_text + offset);
405  
406          int start_of_description_text   = start_of_custom_menu_text + field_8_length_description_text;
407          StringUtil.putCompressedUnicode(getDescriptionText(), data , start_of_description_text + offset);
408  
409          int start_of_help_topic_text    = start_of_description_text + field_9_length_help_topic_text;
410          StringUtil.putCompressedUnicode(getHelpTopicText(), data , start_of_help_topic_text + offset);
411  
412          int start_of_status_bar_text       = start_of_help_topic_text + field_10_length_status_bar_text;
413          StringUtil.putCompressedUnicode(getStatusBarText(), data , start_of_status_bar_text + offset);
414  
415  
416          return getRecordSize();
417      }
418  
419      private void serializePtgs(byte [] data, int offset) {
420          int pos = offset;
421  
422          for (int k = 0; k < field_13_name_definition.size(); k++) {
423              Ptg ptg = ( Ptg ) field_13_name_definition.get(k);
424  
425              ptg.writeBytes(data, pos);
426              pos += ptg.getSize();
427          }
428      }
429  
430  
431      /** gets the length of all texts
432       * @return total length
433       */
434      public int getTextsLength(){
435          int result;
436  
437          result = getNameTextLength() + getDefinitionTextLength() + getDescriptionTextLength() +
438          getHelpTopicLength() + getStatusBarLength();
439  
440  
441          return result;
442      }
443  
444      /** returns the record size
445       */
446      public int getRecordSize(){
447          int result;
448  
449          result = 19 + getTextsLength();
450  
451          return result;
452      }
453  
454      /** gets the extern sheet number
455       * @return extern sheet index
456       */
457      public short getExternSheetNumber(){
458          if (field_13_name_definition == null) return 0;
459          Ptg ptg = (Ptg) field_13_name_definition.peek();
460          short result = 0;
461  
462          if (ptg.getClass() == Area3DPtg.class){
463              result = ((Area3DPtg) ptg).getExternSheetIndex();
464  
465          } else if (ptg.getClass() == Ref3DPtg.class){
466              result = ((Ref3DPtg) ptg).getExternSheetIndex();
467          }
468  
469          return result;
470      }
471  
472      /** sets the extern sheet number
473       * @param externSheetNumber extern sheet number
474       */
475      public void setExternSheetNumber(short externSheetNumber){
476          Ptg ptg;
477  
478          if (field_13_name_definition == null || field_13_name_definition.isEmpty()){
479              field_13_name_definition = new Stack();
480              ptg = createNewPtg();
481          } else {
482              ptg = (Ptg) field_13_name_definition.peek();
483          }
484  
485          if (ptg.getClass() == Area3DPtg.class){
486              ((Area3DPtg) ptg).setExternSheetIndex(externSheetNumber);
487  
488          } else if (ptg.getClass() == Ref3DPtg.class){
489              ((Ref3DPtg) ptg).setExternSheetIndex(externSheetNumber);
490          }
491  
492      }
493  
494      private Ptg createNewPtg(){
495          Ptg ptg = new Area3DPtg();
496          field_13_name_definition.push(ptg);
497  
498          return ptg;
499      }
500  
501      /** gets the reference , the area only (range)
502       * @return area reference
503       */
504      public String getAreaReference(){
505          if (field_13_name_definition == null) return "#REF!";
506          Ptg ptg = (Ptg) field_13_name_definition.peek();
507          String result = "";
508  
509          if (ptg.getClass() == Area3DPtg.class){
510              result = ((Area3DPtg) ptg).getArea();
511  
512          } else if (ptg.getClass() == Ref3DPtg.class){
513              result = ((Ref3DPtg) ptg).getArea();
514          }
515  
516          return result;
517      }
518  
519      /** sets the reference , the area only (range)
520       * @param ref area reference
521       */
522      public void setAreaReference(String ref){
523          //Trying to find if what ptg do we need
524          RangeAddress ra = new RangeAddress(ref);
525          Ptg oldPtg;
526          Ptg ptg;
527  
528          if (field_13_name_definition==null ||field_13_name_definition.isEmpty()){
529              field_13_name_definition = new Stack();
530              oldPtg = createNewPtg();
531          } else {
532              //Trying to find extern sheet index
533              oldPtg = (Ptg) field_13_name_definition.pop();
534          }
535  
536          short externSheetIndex = 0;
537  
538          if (oldPtg.getClass() == Area3DPtg.class){
539              externSheetIndex =  ((Area3DPtg) oldPtg).getExternSheetIndex();
540  
541          } else if (oldPtg.getClass() == Ref3DPtg.class){
542              externSheetIndex =  ((Ref3DPtg) oldPtg).getExternSheetIndex();
543          }
544  
545          if (ra.hasRange()) {
546              ptg = new Area3DPtg();
547              ((Area3DPtg) ptg).setExternSheetIndex(externSheetIndex);
548              ((Area3DPtg) ptg).setArea(ref);
549              this.setDefinitionTextLength((short)((Area3DPtg) ptg).getSize());
550          } else {
551              ptg = new Ref3DPtg();
552              ((Ref3DPtg) ptg).setExternSheetIndex(externSheetIndex);
553              ((Ref3DPtg) ptg).setArea(ref);
554              this.setDefinitionTextLength((short)((Ref3DPtg) ptg).getSize());
555          }
556  
557          field_13_name_definition.push(ptg);
558  
559      }
560  
561      /**
562       * called by the constructor, should set class level fields.  Should throw
563       * runtime exception for bad/icomplete data.
564       *
565       * @param data raw data
566       * @param size size of data
567       * @param offset of the record's data (provided a big array of the file)
568       */
569      protected void fillFields(byte[] data, short size, int offset) {
570          field_1_option_flag             = LittleEndian.getShort(data, 0 + offset);
571          field_2_keyboard_shortcut       = data [2 + offset];
572          field_3_length_name_text        = data [3 + offset];
573          field_4_length_name_definition  = LittleEndian.getShort(data, 4 + offset);
574          field_5_index_to_sheet          = LittleEndian.getShort(data, 6 + offset);
575          field_6_equals_to_index_to_sheet= LittleEndian.getShort(data, 8 + offset);
576          field_7_length_custom_menu      = data [10 + offset];
577          field_8_length_description_text = data [11 + offset];
578          field_9_length_help_topic_text  = data [12 + offset];
579          field_10_length_status_bar_text = data [13 + offset];
580  
581          field_11_compressed_unicode_flag= data [14 + offset];
582          field_12_name_text = new String(data, 15 + offset,
583          LittleEndian.ubyteToInt(field_3_length_name_text));
584  
585          int start_of_name_definition    = 15 + field_3_length_name_text;
586          field_13_name_definition = getParsedExpressionTokens(data, field_4_length_name_definition,
587          offset, start_of_name_definition);
588  
589          int start_of_custom_menu_text   = start_of_name_definition + field_4_length_name_definition;
590          field_14_custom_menu_text       = new String(data, start_of_custom_menu_text + offset,
591          LittleEndian.ubyteToInt(field_7_length_custom_menu));
592  
593          int start_of_description_text   = start_of_custom_menu_text + field_8_length_description_text;
594          field_15_description_text       = new String(data, start_of_description_text + offset,
595          LittleEndian.ubyteToInt(field_8_length_description_text));
596  
597          int start_of_help_topic_text    = start_of_description_text + field_9_length_help_topic_text;
598          field_16_help_topic_text        = new String(data, start_of_help_topic_text + offset,
599          LittleEndian.ubyteToInt(field_9_length_help_topic_text));
600  
601          int start_of_status_bar_text       = start_of_help_topic_text + field_10_length_status_bar_text;
602          field_17_status_bar_text        = new String(data, start_of_status_bar_text +  offset,
603          LittleEndian.ubyteToInt(field_10_length_status_bar_text));
604  
605      }
606  
607      private Stack getParsedExpressionTokens(byte [] data, short size,
608      int offset, int start_of_expression) {
609          Stack stack = new Stack();
610          int   pos           = start_of_expression + offset;
611          int   sizeCounter   = 0;
612          try {
613              while (sizeCounter < size) {
614                  Ptg ptg = Ptg.createPtg(data, pos);
615  
616                  pos += ptg.getSize();
617                  sizeCounter += ptg.getSize();
618                  stack.push(ptg);
619              }
620          } catch (java.lang.UnsupportedOperationException uoe) {
621              System.err.println("[WARNING] Unknown Ptg "
622                      + uoe.getMessage() );
623              field_13_raw_name_definition=new byte[size];
624              System.arraycopy(data,offset,field_13_raw_name_definition,0,size);
625              return null;
626          }
627          return stack;
628      }
629  
630  
631      /**
632       * return the non static version of the id for this record.
633       */
634      public short getSid() {
635          return this.sid;
636      }
637  
638  }
639