1 16 17 18 23 package org.apache.poi.hssf.usermodel; 24 25 import org.apache.poi.hssf.eventmodel.EventRecordFactory; 26 import org.apache.poi.hssf.model.Sheet; 27 import org.apache.poi.hssf.model.Workbook; 28 import org.apache.poi.hssf.record.*; 29 import org.apache.poi.hssf.record.formula.Area3DPtg; 30 import org.apache.poi.hssf.record.formula.MemFuncPtg; 31 import org.apache.poi.hssf.record.formula.UnionPtg; 32 import org.apache.poi.hssf.util.CellReference; 33 import org.apache.poi.poifs.filesystem.*; 34 import org.apache.poi.util.POILogFactory; 35 import org.apache.poi.util.POILogger; 36 37 import java.io.ByteArrayInputStream ; 38 import java.io.IOException ; 39 import java.io.InputStream ; 40 import java.io.OutputStream ; 41 import java.util.ArrayList ; 42 import java.util.Iterator ; 43 import java.util.List ; 44 import java.util.Stack ; 45 46 58 59 public class HSSFWorkbook 60 extends java.lang.Object 61 { 62 private static final int DEBUG = POILogger.DEBUG; 63 64 70 71 public final static int INITIAL_CAPACITY = 3; 72 73 76 77 private Workbook workbook; 78 79 82 83 protected ArrayList sheets; 84 85 88 89 private ArrayList names; 90 91 95 private boolean preserveNodes; 96 97 101 private POIFSFileSystem poifs; 102 103 109 private HSSFDataFormat formatter; 110 111 private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class); 112 113 117 118 public HSSFWorkbook() 119 { 120 this(Workbook.createWorkbook()); 121 } 122 123 protected HSSFWorkbook( Workbook book ) 124 { 125 workbook = book; 126 sheets = new ArrayList ( INITIAL_CAPACITY ); 127 names = new ArrayList ( INITIAL_CAPACITY ); 128 } 129 130 public HSSFWorkbook(POIFSFileSystem fs) throws IOException { 131 this(fs,true); 132 } 133 134 145 146 public HSSFWorkbook(POIFSFileSystem fs, boolean preserveNodes) 147 throws IOException 148 { 149 this.preserveNodes = preserveNodes; 150 151 if (preserveNodes) { 152 this.poifs = fs; 153 } 154 155 sheets = new ArrayList (INITIAL_CAPACITY); 156 names = new ArrayList (INITIAL_CAPACITY); 157 158 InputStream stream = fs.createDocumentInputStream("Workbook"); 159 160 EventRecordFactory factory = new EventRecordFactory(); 161 162 163 164 List records = RecordFactory.createRecords(stream); 165 166 workbook = Workbook.createWorkbook(records); 167 setPropertiesFromWorkbook(workbook); 168 int recOffset = workbook.getNumRecords(); 169 int sheetNum = 0; 170 171 while (recOffset < records.size()) 172 { 173 Sheet sheet = Sheet.createSheet(records, sheetNum++, recOffset ); 174 175 recOffset = sheet.getEofLoc()+1; 176 sheet.convertLabelRecords( 177 workbook); HSSFSheet hsheet = new HSSFSheet(workbook, sheet); 179 180 sheets.add(hsheet); 181 182 } 184 185 for (int i = 0 ; i < workbook.getNumNames() ; ++i){ 186 HSSFName name = new HSSFName(workbook, workbook.getNameRecord(i)); 187 names.add(name); 188 } 189 } 190 191 public HSSFWorkbook(InputStream s) throws IOException { 192 this(s,true); 193 } 194 195 207 208 public HSSFWorkbook(InputStream s, boolean preserveNodes) 209 throws IOException 210 { 211 this(new POIFSFileSystem(s), preserveNodes); 212 } 213 214 217 218 private void setPropertiesFromWorkbook(Workbook book) 219 { 220 this.workbook = book; 221 222 } 224 225 231 232 public void setSheetOrder(String sheetname, int pos ) { 233 workbook.setSheetOrder(sheetname, pos); 234 } 235 236 public final static byte ENCODING_COMPRESSED_UNICODE = 0; 237 public final static byte ENCODING_UTF_16 = 1; 238 239 240 246 public void setSheetName(int sheet, String name) 247 { 248 if (workbook.doesContainsSheetName( name, sheet )) 249 throw new IllegalArgumentException ( "The workbook already contains a sheet with this name" ); 250 251 workbook.setSheetName( sheet, name, ENCODING_COMPRESSED_UNICODE ); 252 } 253 254 public void setSheetName( int sheet, String name, short encoding ) 255 { 256 if (workbook.doesContainsSheetName( name, sheet )) 257 throw new IllegalArgumentException ( "The workbook already contains a sheet with this name" ); 258 259 if (sheet > (sheets.size() - 1)) 260 { 261 throw new RuntimeException ("Sheet out of bounds"); 262 } 263 264 switch ( encoding ) { 265 case ENCODING_COMPRESSED_UNICODE: 266 case ENCODING_UTF_16: 267 break; 268 269 default: 270 throw new RuntimeException ( "Unsupported encoding" ); 272 } 273 274 workbook.setSheetName( sheet, name, encoding ); 275 } 276 277 282 283 public String getSheetName(int sheet) 284 { 285 if (sheet > (sheets.size() - 1)) 286 { 287 throw new RuntimeException ("Sheet out of bounds"); 288 } 289 return workbook.getSheetName(sheet); 290 } 291 292 297 298 302 public int getSheetIndex(String name) 303 { 304 int retval = workbook.getSheetIndex(name); 305 306 return retval; 307 } 308 309 315 316 public HSSFSheet createSheet() 317 { 318 319 HSSFSheet sheet = new HSSFSheet(workbook); 322 323 sheets.add(sheet); 324 workbook.setSheetName(sheets.size() - 1, 325 "Sheet" + (sheets.size() - 1)); 326 WindowTwoRecord windowTwo = (WindowTwoRecord) sheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid); 327 windowTwo.setSelected(sheets.size() == 1); 328 windowTwo.setPaged(sheets.size() == 1); 329 return sheet; 330 } 331 332 337 338 public HSSFSheet cloneSheet(int sheetNum) { 339 HSSFSheet srcSheet = (HSSFSheet)sheets.get(sheetNum); 340 String srcName = workbook.getSheetName(sheetNum); 341 if (srcSheet != null) { 342 HSSFSheet clonedSheet = srcSheet.cloneSheet(workbook); 343 WindowTwoRecord windowTwo = (WindowTwoRecord) clonedSheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid); 344 windowTwo.setSelected(sheets.size() == 1); 345 windowTwo.setPaged(sheets.size() == 1); 346 347 sheets.add(clonedSheet); 348 if (srcName.length()<28) { 349 workbook.setSheetName(sheets.size()-1, srcName+"(2)"); 350 }else { 351 workbook.setSheetName(sheets.size()-1,srcName.substring(0,28)+"(2)"); 352 } 353 return clonedSheet; 354 } 355 return null; 356 } 357 358 365 366 public HSSFSheet createSheet(String sheetname) 367 { 368 if (workbook.doesContainsSheetName( sheetname, -1 )) 369 throw new IllegalArgumentException ( "The workbook already contains a sheet of this name" ); 370 371 HSSFSheet sheet = new HSSFSheet(workbook); 372 373 sheets.add(sheet); 374 workbook.setSheetName(sheets.size() - 1, sheetname); 375 WindowTwoRecord windowTwo = (WindowTwoRecord) sheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid); 376 windowTwo.setSelected(sheets.size() == 1); 377 windowTwo.setPaged(sheets.size() == 1); 378 return sheet; 379 } 380 381 385 386 public int getNumberOfSheets() 387 { 388 return sheets.size(); 389 } 390 391 396 397 public HSSFSheet getSheetAt(int index) 398 { 399 return (HSSFSheet) sheets.get(index); 400 } 401 402 407 408 public HSSFSheet getSheet(String name) 409 { 410 HSSFSheet retval = null; 411 412 for (int k = 0; k < sheets.size(); k++) 413 { 414 String sheetname = workbook.getSheetName(k); 415 416 if (sheetname.equals(name)) 417 { 418 retval = (HSSFSheet) sheets.get(k); 419 } 420 } 421 return retval; 422 } 423 424 428 429 public void removeSheetAt(int index) 430 { 431 sheets.remove(index); 432 workbook.removeSheet(index); 433 } 434 435 440 441 public void setBackupFlag(boolean backupValue) 442 { 443 BackupRecord backupRecord = workbook.getBackupRecord(); 444 445 backupRecord.setBackup(backupValue ? (short) 1 446 : (short) 0); 447 } 448 449 454 455 public boolean getBackupFlag() 456 { 457 BackupRecord backupRecord = workbook.getBackupRecord(); 458 459 return (backupRecord.getBackup() == 0) ? false 460 : true; 461 } 462 463 488 public void setRepeatingRowsAndColumns(int sheetIndex, 489 int startColumn, int endColumn, 490 int startRow, int endRow) 491 { 492 if (startColumn == -1 && endColumn != -1) throw new IllegalArgumentException ("Invalid column range specification"); 494 if (startRow == -1 && endRow != -1) throw new IllegalArgumentException ("Invalid row range specification"); 495 if (startColumn < -1 || startColumn >= 0xFF) throw new IllegalArgumentException ("Invalid column range specification"); 496 if (endColumn < -1 || endColumn >= 0xFF) throw new IllegalArgumentException ("Invalid column range specification"); 497 if (startRow < -1 || startRow > 65535) throw new IllegalArgumentException ("Invalid row range specification"); 498 if (endRow < -1 || endRow > 65535) throw new IllegalArgumentException ("Invalid row range specification"); 499 if (startColumn > endColumn) throw new IllegalArgumentException ("Invalid column range specification"); 500 if (startRow > endRow) throw new IllegalArgumentException ("Invalid row range specification"); 501 502 HSSFSheet sheet = getSheetAt(sheetIndex); 503 short externSheetIndex = getWorkbook().checkExternSheet(sheetIndex); 504 505 boolean settingRowAndColumn = 506 startColumn != -1 && endColumn != -1 && startRow != -1 && endRow != -1; 507 boolean removingRange = 508 startColumn == -1 && endColumn == -1 && startRow == -1 && endRow == -1; 509 510 boolean isNewRecord = false; 511 NameRecord nameRecord; 512 nameRecord = findExistingRowColHeaderNameRecord(sheetIndex); 513 if (removingRange ) 514 { 515 if (nameRecord != null) 516 workbook.removeName(findExistingRowColHeaderNameRecordIdx(sheetIndex+1)); 517 return; 518 } 519 if ( nameRecord == null ) 520 { 521 nameRecord = workbook.createBuiltInName(NameRecord.BUILTIN_PRINT_TITLE, sheetIndex+1); 522 isNewRecord = true; 524 } 525 526 short definitionTextLength = settingRowAndColumn ? (short)0x001a : (short)0x000b; 527 nameRecord.setDefinitionTextLength(definitionTextLength); 528 529 Stack ptgs = new Stack (); 530 531 if (settingRowAndColumn) 532 { 533 MemFuncPtg memFuncPtg = new MemFuncPtg(); 534 memFuncPtg.setLenRefSubexpression(23); 535 ptgs.add(memFuncPtg); 536 } 537 if (startColumn >= 0) 538 { 539 Area3DPtg area3DPtg1 = new Area3DPtg(); 540 area3DPtg1.setExternSheetIndex(externSheetIndex); 541 area3DPtg1.setFirstColumn((short)startColumn); 542 area3DPtg1.setLastColumn((short)endColumn); 543 area3DPtg1.setFirstRow((short)0); 544 area3DPtg1.setLastRow((short)0xFFFF); 545 ptgs.add(area3DPtg1); 546 } 547 if (startRow >= 0) 548 { 549 Area3DPtg area3DPtg2 = new Area3DPtg(); 550 area3DPtg2.setExternSheetIndex(externSheetIndex); 551 area3DPtg2.setFirstColumn((short)0); 552 area3DPtg2.setLastColumn((short)0x00FF); 553 area3DPtg2.setFirstRow((short)startRow); 554 area3DPtg2.setLastRow((short)endRow); 555 ptgs.add(area3DPtg2); 556 } 557 if (settingRowAndColumn) 558 { 559 UnionPtg unionPtg = new UnionPtg(); 560 ptgs.add(unionPtg); 561 } 562 nameRecord.setNameDefinition(ptgs); 563 564 if (isNewRecord) 565 { 566 HSSFName newName = new HSSFName(workbook, nameRecord); 567 names.add(newName); 568 } 569 570 HSSFPrintSetup printSetup = sheet.getPrintSetup(); 571 printSetup.setValidSettings(false); 572 573 WindowTwoRecord w2 = (WindowTwoRecord) sheet.getSheet().findFirstRecordBySid(WindowTwoRecord.sid); 574 w2.setPaged(true); 575 } 576 577 private NameRecord findExistingRowColHeaderNameRecord( int sheetIndex ) 578 { 579 int index = findExistingRowColHeaderNameRecordIdx(sheetIndex); 580 if (index == -1) 581 return null; 582 else 583 return (NameRecord)workbook.findNextRecordBySid(NameRecord.sid, index); 584 } 585 586 private int findExistingRowColHeaderNameRecordIdx( int sheetIndex ) 587 { 588 int index = 0; 589 NameRecord r = null; 590 while ((r = (NameRecord) workbook.findNextRecordBySid(NameRecord.sid, index)) != null) 591 { 592 int nameRecordSheetIndex = workbook.getSheetIndexFromExternSheetIndex(r.getEqualsToIndexToSheet() - 1); 593 if (isRowColHeaderRecord( r ) && nameRecordSheetIndex == sheetIndex) 594 { 595 return index; 596 } 597 index++; 598 } 599 600 return -1; 601 } 602 603 private boolean isRowColHeaderRecord( NameRecord r ) 604 { 605 return r.getOptionFlag() == 0x20 && ("" + ((char)7)).equals(r.getNameText()); 606 } 607 608 612 613 public HSSFFont createFont() 614 { 615 FontRecord font = workbook.createNewFont(); 616 short fontindex = (short) (getNumberOfFonts() - 1); 617 618 if (fontindex > 3) 619 { 620 fontindex++; } 622 HSSFFont retval = new HSSFFont(fontindex, font); 623 624 return retval; 625 } 626 627 630 public HSSFFont findFont(short boldWeight, short color, short fontHeight, 631 String name, boolean italic, boolean strikeout, 632 short typeOffset, byte underline) 633 { 634 for (short i = 0; i < workbook.getNumberOfFontRecords(); i++) 636 { 637 if (i == 4) 638 continue; 639 640 FontRecord font = workbook.getFontRecordAt(i); 641 HSSFFont hssfFont = new HSSFFont(i, font); 642 if (hssfFont.getBoldweight() == boldWeight 644 && hssfFont.getColor() == color 645 && hssfFont.getFontHeight() == fontHeight 646 && hssfFont.getFontName().equals(name) 647 && hssfFont.getItalic() == italic 648 && hssfFont.getStrikeout() == strikeout 649 && hssfFont.getTypeOffset() == typeOffset 650 && hssfFont.getUnderline() == underline) 651 { 652 return hssfFont; 654 } 655 } 656 657 return null; 659 } 660 661 665 666 public short getNumberOfFonts() 667 { 668 return (short) workbook.getNumberOfFontRecords(); 669 } 670 671 676 677 public HSSFFont getFontAt(short idx) 678 { 679 FontRecord font = workbook.getFontRecordAt(idx); 680 HSSFFont retval = new HSSFFont(idx, font); 681 682 return retval; 683 } 684 685 689 690 public HSSFCellStyle createCellStyle() 691 { 692 ExtendedFormatRecord xfr = workbook.createCellXF(); 693 short index = (short) (getNumCellStyles() - 1); 694 HSSFCellStyle style = new HSSFCellStyle(index, xfr); 695 696 return style; 697 } 698 699 703 704 public short getNumCellStyles() 705 { 706 return (short) workbook.getNumExFormats(); 707 } 708 709 714 715 public HSSFCellStyle getCellStyleAt(short idx) 716 { 717 ExtendedFormatRecord xfr = workbook.getExFormatAt(idx); 718 HSSFCellStyle style = new HSSFCellStyle(idx, xfr); 719 720 return style; 721 } 722 723 733 734 public void write(OutputStream stream) 735 throws IOException 736 { 737 byte[] bytes = getBytes(); 738 POIFSFileSystem fs = new POIFSFileSystem(); 739 740 fs.createDocument(new ByteArrayInputStream (bytes), "Workbook"); 741 742 if (preserveNodes) { 743 List excepts = new ArrayList (1); 744 excepts.add("Workbook"); 745 copyNodes(this.poifs,fs,excepts); 746 } 747 fs.writeFilesystem(stream); 748 } 750 751 762 763 public byte[] getBytes() 764 { 765 if (log.check( POILogger.DEBUG )) 766 log.log(DEBUG, "HSSFWorkbook.getBytes()"); 767 768 for (int k = 0; k < sheets.size(); k++) 771 ((HSSFSheet) sheets.get(k)).getSheet().preSerialize(); 772 773 int wbsize = workbook.getSize(); 774 775 int totalsize = wbsize; 778 779 for (int k = 0; k < sheets.size(); k++) 780 { 781 workbook.setSheetBof(k, totalsize); 782 totalsize += ((HSSFSheet) sheets.get(k)).getSheet().getSize(); 783 } 784 785 786 790 byte[] retval = new byte[totalsize]; 791 int pos = workbook.serialize(0, retval); 792 793 for (int k = 0; k < sheets.size(); k++) 795 { 796 797 pos += ((HSSFSheet) sheets.get(k)).getSheet().serialize(pos, 800 retval); } 802 806 return retval; 807 } 808 809 public int addSSTString(String string) 810 { 811 return workbook.addSSTString(string); 812 } 813 814 public String getSSTString(int index) 815 { 816 return workbook.getSSTString(index); 817 } 818 819 Workbook getWorkbook() 820 { 821 return workbook; 822 } 823 824 827 public int getNumberOfNames(){ 828 int result = names.size(); 829 return result; 830 } 831 832 836 public HSSFName getNameAt(int index){ 837 HSSFName result = (HSSFName) names.get(index); 838 839 return result; 840 } 841 842 846 public String getNameName(int index){ 847 String result = getNameAt(index).getNameName(); 848 849 return result; 850 } 851 852 859 public void setPrintArea(int sheetIndex, String reference) 860 { 861 NameRecord name = workbook.getSpecificBuiltinRecord(NameRecord.BUILTIN_PRINT_AREA, sheetIndex+1); 862 863 864 if (name == null) 865 name = workbook.createBuiltInName(NameRecord.BUILTIN_PRINT_AREA, sheetIndex+1); 866 868 short externSheetIndex = getWorkbook().checkExternSheet(sheetIndex); 869 name.setExternSheetNumber(externSheetIndex); 870 name.setAreaReference(reference); 871 872 873 } 874 875 884 public void setPrintArea(int sheetIndex, int startColumn, int endColumn, 885 int startRow, int endRow) { 886 887 CellReference cell = new CellReference(startRow, startColumn, true, true); 889 String reference = cell.toString(); 890 891 cell = new CellReference(endRow, endColumn, true, true); 892 reference = reference+":"+cell.toString(); 893 894 setPrintArea(sheetIndex, reference); 895 } 896 897 898 903 public String getPrintArea(int sheetIndex) 904 { 905 NameRecord name = workbook.getSpecificBuiltinRecord(NameRecord.BUILTIN_PRINT_AREA, sheetIndex+1); 906 if (name == null) return null; 907 909 return name.getAreaReference(workbook); 910 } 911 912 916 public void removePrintArea(int sheetIndex) { 917 getWorkbook().removeBuiltinRecord(NameRecord.BUILTIN_PRINT_AREA, sheetIndex+1); 918 } 919 920 923 public HSSFName createName(){ 924 NameRecord nameRecord = workbook.createName(); 925 926 HSSFName newName = new HSSFName(workbook, nameRecord); 927 928 names.add(newName); 929 930 return newName; 931 } 932 933 937 public int getNameIndex(String name) 938 { 939 int retval = -1; 940 941 for (int k = 0; k < names.size(); k++) 942 { 943 String nameName = getNameName(k); 944 945 if (nameName.equals(name)) 946 { 947 retval = k; 948 break; 949 } 950 } 951 return retval; 952 } 953 954 955 958 public void removeName(int index){ 959 names.remove(index); 960 workbook.removeName(index); 961 } 962 963 969 public HSSFDataFormat createDataFormat() { 970 if (formatter == null) 971 formatter = new HSSFDataFormat(workbook); 972 return formatter; 973 } 974 975 978 public void removeName(String name){ 979 int index = getNameIndex(name); 980 981 removeName(index); 982 983 } 984 985 public HSSFPalette getCustomPalette() 986 { 987 return new HSSFPalette(workbook.getCustomPalette()); 988 } 989 990 996 private void copyNodes(POIFSFileSystem source, POIFSFileSystem target, 997 List excepts) throws IOException { 998 1000 DirectoryEntry root = source.getRoot(); 1001 DirectoryEntry newRoot = target.getRoot(); 1002 1003 Iterator entries = root.getEntries(); 1004 1005 while (entries.hasNext()) { 1006 Entry entry = (Entry)entries.next(); 1007 if (!isInList(entry.getName(), excepts)) { 1008 copyNodeRecursively(entry,newRoot); 1009 } 1010 } 1011 } 1012 1013 private boolean isInList(String entry, List list) { 1014 for (int k = 0; k < list.size(); k++) { 1015 if (list.get(k).equals(entry)) { 1016 return true; 1017 } 1018 } 1019 return false; 1020 } 1021 1022 private void copyNodeRecursively(Entry entry, DirectoryEntry target) 1023 throws IOException { 1024 DirectoryEntry newTarget = null; 1027 if (entry.isDirectoryEntry()) { 1028 newTarget = target.createDirectory(entry.getName()); 1029 Iterator entries = ((DirectoryEntry)entry).getEntries(); 1030 1031 while (entries.hasNext()) { 1032 copyNodeRecursively((Entry)entries.next(),newTarget); 1033 } 1034 } else { 1035 DocumentEntry dentry = (DocumentEntry)entry; 1036 DocumentInputStream dstream = new DocumentInputStream(dentry); 1037 target.createDocument(dentry.getName(),dstream); 1038 dstream.close(); 1039 } 1040 } 1041 1042 1043 public void insertChartRecord() 1044 { 1045 int loc = workbook.findFirstRecordLocBySid(SSTRecord.sid); 1046 byte[] data = { 1047 (byte)0x0F, (byte)0x00, (byte)0x00, (byte)0xF0, (byte)0x52, 1048 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 1049 (byte)0x06, (byte)0xF0, (byte)0x18, (byte)0x00, (byte)0x00, 1050 (byte)0x00, (byte)0x01, (byte)0x08, (byte)0x00, (byte)0x00, 1051 (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, 1052 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, 1053 (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x00, 1054 (byte)0x00, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x00, 1055 (byte)0x33, (byte)0x00, (byte)0x0B, (byte)0xF0, (byte)0x12, 1056 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xBF, (byte)0x00, 1057 (byte)0x08, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x81, 1058 (byte)0x01, (byte)0x09, (byte)0x00, (byte)0x00, (byte)0x08, 1059 (byte)0xC0, (byte)0x01, (byte)0x40, (byte)0x00, (byte)0x00, 1060 (byte)0x08, (byte)0x40, (byte)0x00, (byte)0x1E, (byte)0xF1, 1061 (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0D, 1062 (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x0C, (byte)0x00, 1063 (byte)0x00, (byte)0x08, (byte)0x17, (byte)0x00, (byte)0x00, 1064 (byte)0x08, (byte)0xF7, (byte)0x00, (byte)0x00, (byte)0x10, 1065 }; 1066 UnknownRecord r = new UnknownRecord((short)0x00EB,(short)0x005a, data); 1067 workbook.getRecords().add(loc, r); 1068 } 1069 1070 1071 1072} 1073 | Popular Tags |