├── .gitignore
├── Demo
├── d1.dpr
└── d1.dproj
├── LICENSE.TXT
├── ProjectGroup1.groupproj
├── README.MD
├── packages
├── Excel4DelphiLib.dpk
└── Excel4DelphiLib.dproj
└── source
├── Excel4Delphi.Common.pas
├── Excel4Delphi.Formula.pas
├── Excel4Delphi.NumberFormats.pas
├── Excel4Delphi.Stream.pas
├── Excel4Delphi.Utils.pas
├── Excel4Delphi.Xml.pas
└── Excel4Delphi.pas
/.gitignore:
--------------------------------------------------------------------------------
1 | #
2 | # NOTE! Don't add files that are generated in specific
3 | # subdirectories here. Add them in the ".gitignore" file
4 | # in that subdirectory instead.
5 | #
6 | # NOTE! Please use 'git-ls-files -i --exclude-standard'
7 | # command after changing this file, to see if there are
8 | # any tracked files which get ignored after the change.
9 | #
10 | # Normal rules
11 | #
12 |
13 | *.vrc
14 | *.vlb
15 | *.dcu
16 | *.res
17 | *.cfg
18 | *.dcp
19 | *.dsk
20 | *.identcache
21 | *.~dsk
22 | *.hlp
23 | *.gid
24 | *.cnt
25 | *.fts
26 | *.diff
27 | *.stat
28 | *.tmp
29 |
30 | *.pdb
31 | *.vshost.exe.manifest
32 | *.~*~
33 | *.bak
34 | *.$$$
35 |
36 | *.inf
37 | *.dat
38 | *.ini
39 | *.exe
40 | *.dll
41 | *.log
42 | *.cbk
43 | *.orig
44 | *.rar
45 | *.zip
46 |
47 | *.dproj.local
48 |
49 |
50 | *.groupproj.local
51 | *.tvsconfig
52 |
53 | *.patch
54 |
55 | */lib/*
56 | */LOG/*
57 | */Skin/*
58 | **/__history/*
59 | **/__recovery/*
60 | **/__astcache/*
61 | _Help/*
62 |
--------------------------------------------------------------------------------
/Demo/d1.dpr:
--------------------------------------------------------------------------------
1 | program d1;
2 |
3 | {$APPTYPE CONSOLE}
4 | {$R *.res}
5 |
6 | uses
7 | Excel4Delphi,
8 | Excel4Delphi.Stream,
9 | System.SysUtils;
10 |
11 | procedure CreateNewBook;
12 | // Creating new workbook
13 | var
14 | workBook: TZWorkBook;
15 | begin
16 | workBook := TZWorkBook.Create(nil);
17 | try
18 | workBook.Sheets.Add('My sheet');
19 | workBook.Sheets[0].ColCount := 10;
20 | workBook.Sheets[0].RowCount := 10;
21 | workBook.Sheets[0].CellRef['A', 0].AsString := 'Hello';
22 | workBook.Sheets[0].RangeRef['A', 0, 'B', 2].Merge();
23 | workBook.SaveToFile('file.xlsx');
24 | finally
25 | workBook.Free();
26 | end;
27 | end;
28 |
29 | begin
30 | try
31 | { TODO -oUser -cConsole Main : Insert code here }
32 | CreateNewBook;
33 | except
34 | on E: Exception do
35 | Writeln(E.ClassName, ': ', E.Message);
36 | end;
37 |
38 | end.
39 |
--------------------------------------------------------------------------------
/Demo/d1.dproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {816DAEA1-9E11-497C-A824-7E1883933742}
4 | 18.8
5 | FMX
6 | d1.dpr
7 | True
8 | Debug
9 | Win32
10 | 1
11 | Console
12 |
13 |
14 | true
15 |
16 |
17 | true
18 | Base
19 | true
20 |
21 |
22 | true
23 | Base
24 | true
25 |
26 |
27 | true
28 | Cfg_1
29 | true
30 | true
31 |
32 |
33 | true
34 | Base
35 | true
36 |
37 |
38 | .\$(Platform)\$(Config)
39 | .\$(Platform)\$(Config)
40 | false
41 | false
42 | false
43 | false
44 | false
45 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
46 | d1
47 | ..\source;$(DCC_UnitSearchPath)
48 | 1033
49 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
50 |
51 |
52 | DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;SilpoUa;vclFireDAC;IndySystem;tethering;svnui;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;svn;horse_wizard;FireDACSqliteDriver;FireDACPgDriver;inetdb;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;GalaxyComponents;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;DOSCommandDR;vclx;bindcomp;appanalytics;dsnap;fgx;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;vclie;CEF4Delphi_FMX;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
53 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
54 | Debug
55 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
56 | 1033
57 | true
58 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
59 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
60 |
61 |
62 | DEBUG;$(DCC_Define)
63 | true
64 | false
65 | true
66 | true
67 | true
68 |
69 |
70 | false
71 | 1033
72 | (None)
73 |
74 |
75 | false
76 | RELEASE;$(DCC_Define)
77 | 0
78 | 0
79 |
80 |
81 |
82 | MainSource
83 |
84 |
85 | Cfg_2
86 | Base
87 |
88 |
89 | Base
90 |
91 |
92 | Cfg_1
93 | Base
94 |
95 |
96 |
97 | Delphi.Personality.12
98 | Application
99 |
100 |
101 |
102 | d1.dpr
103 |
104 |
105 | Microsoft Office 2000 Sample Automation Server Wrapper Components
106 | Microsoft Office XP Sample Automation Server Wrapper Components
107 |
108 |
109 |
110 |
111 |
112 | true
113 |
114 |
115 |
116 |
117 | true
118 |
119 |
120 |
121 |
122 | true
123 |
124 |
125 |
126 |
127 | d1.exe
128 | true
129 |
130 |
131 |
132 |
133 | 1
134 |
135 |
136 | Contents\MacOS
137 | 1
138 |
139 |
140 | 0
141 |
142 |
143 |
144 |
145 | classes
146 | 1
147 |
148 |
149 | classes
150 | 1
151 |
152 |
153 |
154 |
155 | res\xml
156 | 1
157 |
158 |
159 | res\xml
160 | 1
161 |
162 |
163 |
164 |
165 | library\lib\armeabi-v7a
166 | 1
167 |
168 |
169 |
170 |
171 | library\lib\armeabi
172 | 1
173 |
174 |
175 | library\lib\armeabi
176 | 1
177 |
178 |
179 |
180 |
181 | library\lib\armeabi-v7a
182 | 1
183 |
184 |
185 |
186 |
187 | library\lib\mips
188 | 1
189 |
190 |
191 | library\lib\mips
192 | 1
193 |
194 |
195 |
196 |
197 | library\lib\armeabi-v7a
198 | 1
199 |
200 |
201 | library\lib\arm64-v8a
202 | 1
203 |
204 |
205 |
206 |
207 | library\lib\armeabi-v7a
208 | 1
209 |
210 |
211 |
212 |
213 | res\drawable
214 | 1
215 |
216 |
217 | res\drawable
218 | 1
219 |
220 |
221 |
222 |
223 | res\values
224 | 1
225 |
226 |
227 | res\values
228 | 1
229 |
230 |
231 |
232 |
233 | res\values-v21
234 | 1
235 |
236 |
237 | res\values-v21
238 | 1
239 |
240 |
241 |
242 |
243 | res\values
244 | 1
245 |
246 |
247 | res\values
248 | 1
249 |
250 |
251 |
252 |
253 | res\drawable
254 | 1
255 |
256 |
257 | res\drawable
258 | 1
259 |
260 |
261 |
262 |
263 | res\drawable-xxhdpi
264 | 1
265 |
266 |
267 | res\drawable-xxhdpi
268 | 1
269 |
270 |
271 |
272 |
273 | res\drawable-ldpi
274 | 1
275 |
276 |
277 | res\drawable-ldpi
278 | 1
279 |
280 |
281 |
282 |
283 | res\drawable-mdpi
284 | 1
285 |
286 |
287 | res\drawable-mdpi
288 | 1
289 |
290 |
291 |
292 |
293 | res\drawable-hdpi
294 | 1
295 |
296 |
297 | res\drawable-hdpi
298 | 1
299 |
300 |
301 |
302 |
303 | res\drawable-xhdpi
304 | 1
305 |
306 |
307 | res\drawable-xhdpi
308 | 1
309 |
310 |
311 |
312 |
313 | res\drawable-mdpi
314 | 1
315 |
316 |
317 | res\drawable-mdpi
318 | 1
319 |
320 |
321 |
322 |
323 | res\drawable-hdpi
324 | 1
325 |
326 |
327 | res\drawable-hdpi
328 | 1
329 |
330 |
331 |
332 |
333 | res\drawable-xhdpi
334 | 1
335 |
336 |
337 | res\drawable-xhdpi
338 | 1
339 |
340 |
341 |
342 |
343 | res\drawable-xxhdpi
344 | 1
345 |
346 |
347 | res\drawable-xxhdpi
348 | 1
349 |
350 |
351 |
352 |
353 | res\drawable-xxxhdpi
354 | 1
355 |
356 |
357 | res\drawable-xxxhdpi
358 | 1
359 |
360 |
361 |
362 |
363 | res\drawable-small
364 | 1
365 |
366 |
367 | res\drawable-small
368 | 1
369 |
370 |
371 |
372 |
373 | res\drawable-normal
374 | 1
375 |
376 |
377 | res\drawable-normal
378 | 1
379 |
380 |
381 |
382 |
383 | res\drawable-large
384 | 1
385 |
386 |
387 | res\drawable-large
388 | 1
389 |
390 |
391 |
392 |
393 | res\drawable-xlarge
394 | 1
395 |
396 |
397 | res\drawable-xlarge
398 | 1
399 |
400 |
401 |
402 |
403 | res\values
404 | 1
405 |
406 |
407 | res\values
408 | 1
409 |
410 |
411 |
412 |
413 | 1
414 |
415 |
416 | Contents\MacOS
417 | 1
418 |
419 |
420 | 0
421 |
422 |
423 |
424 |
425 | Contents\MacOS
426 | 1
427 | .framework
428 |
429 |
430 | Contents\MacOS
431 | 1
432 | .framework
433 |
434 |
435 | 0
436 |
437 |
438 |
439 |
440 | 1
441 | .dylib
442 |
443 |
444 | 1
445 | .dylib
446 |
447 |
448 | 1
449 | .dylib
450 |
451 |
452 | Contents\MacOS
453 | 1
454 | .dylib
455 |
456 |
457 | Contents\MacOS
458 | 1
459 | .dylib
460 |
461 |
462 | 0
463 | .dll;.bpl
464 |
465 |
466 |
467 |
468 | 1
469 | .dylib
470 |
471 |
472 | 1
473 | .dylib
474 |
475 |
476 | 1
477 | .dylib
478 |
479 |
480 | Contents\MacOS
481 | 1
482 | .dylib
483 |
484 |
485 | Contents\MacOS
486 | 1
487 | .dylib
488 |
489 |
490 | 0
491 | .bpl
492 |
493 |
494 |
495 |
496 | 0
497 |
498 |
499 | 0
500 |
501 |
502 | 0
503 |
504 |
505 | 0
506 |
507 |
508 | 0
509 |
510 |
511 | Contents\Resources\StartUp\
512 | 0
513 |
514 |
515 | Contents\Resources\StartUp\
516 | 0
517 |
518 |
519 | 0
520 |
521 |
522 |
523 |
524 | 1
525 |
526 |
527 | 1
528 |
529 |
530 | 1
531 |
532 |
533 |
534 |
535 | 1
536 |
537 |
538 | 1
539 |
540 |
541 | 1
542 |
543 |
544 |
545 |
546 | 1
547 |
548 |
549 | 1
550 |
551 |
552 | 1
553 |
554 |
555 |
556 |
557 | 1
558 |
559 |
560 | 1
561 |
562 |
563 | 1
564 |
565 |
566 |
567 |
568 | 1
569 |
570 |
571 | 1
572 |
573 |
574 | 1
575 |
576 |
577 |
578 |
579 | 1
580 |
581 |
582 | 1
583 |
584 |
585 | 1
586 |
587 |
588 |
589 |
590 | 1
591 |
592 |
593 | 1
594 |
595 |
596 | 1
597 |
598 |
599 |
600 |
601 | 1
602 |
603 |
604 | 1
605 |
606 |
607 | 1
608 |
609 |
610 |
611 |
612 | 1
613 |
614 |
615 | 1
616 |
617 |
618 | 1
619 |
620 |
621 |
622 |
623 | 1
624 |
625 |
626 | 1
627 |
628 |
629 | 1
630 |
631 |
632 |
633 |
634 | 1
635 |
636 |
637 | 1
638 |
639 |
640 | 1
641 |
642 |
643 |
644 |
645 | 1
646 |
647 |
648 | 1
649 |
650 |
651 | 1
652 |
653 |
654 |
655 |
656 | 1
657 |
658 |
659 | 1
660 |
661 |
662 | 1
663 |
664 |
665 |
666 |
667 | 1
668 |
669 |
670 | 1
671 |
672 |
673 | 1
674 |
675 |
676 |
677 |
678 | 1
679 |
680 |
681 | 1
682 |
683 |
684 | 1
685 |
686 |
687 |
688 |
689 | 1
690 |
691 |
692 | 1
693 |
694 |
695 | 1
696 |
697 |
698 |
699 |
700 | 1
701 |
702 |
703 | 1
704 |
705 |
706 | 1
707 |
708 |
709 |
710 |
711 | 1
712 |
713 |
714 | 1
715 |
716 |
717 | 1
718 |
719 |
720 |
721 |
722 | 1
723 |
724 |
725 | 1
726 |
727 |
728 | 1
729 |
730 |
731 |
732 |
733 | 1
734 |
735 |
736 | 1
737 |
738 |
739 | 1
740 |
741 |
742 |
743 |
744 | 1
745 |
746 |
747 | 1
748 |
749 |
750 | 1
751 |
752 |
753 |
754 |
755 | 1
756 |
757 |
758 | 1
759 |
760 |
761 | 1
762 |
763 |
764 |
765 |
766 | 1
767 |
768 |
769 | 1
770 |
771 |
772 | 1
773 |
774 |
775 |
776 |
777 | 1
778 |
779 |
780 | 1
781 |
782 |
783 | 1
784 |
785 |
786 |
787 |
788 | 1
789 |
790 |
791 | 1
792 |
793 |
794 |
795 |
796 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
797 | 1
798 |
799 |
800 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
801 | 1
802 |
803 |
804 |
805 |
806 | 1
807 |
808 |
809 | 1
810 |
811 |
812 |
813 |
814 | ..\
815 | 1
816 |
817 |
818 | ..\
819 | 1
820 |
821 |
822 |
823 |
824 | 1
825 |
826 |
827 | 1
828 |
829 |
830 | 1
831 |
832 |
833 |
834 |
835 | 1
836 |
837 |
838 | 1
839 |
840 |
841 | 1
842 |
843 |
844 |
845 |
846 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
847 | 1
848 |
849 |
850 |
851 |
852 | ..\
853 | 1
854 |
855 |
856 | ..\
857 | 1
858 |
859 |
860 |
861 |
862 | Contents
863 | 1
864 |
865 |
866 | Contents
867 | 1
868 |
869 |
870 |
871 |
872 | Contents\Resources
873 | 1
874 |
875 |
876 | Contents\Resources
877 | 1
878 |
879 |
880 |
881 |
882 | library\lib\armeabi-v7a
883 | 1
884 |
885 |
886 | library\lib\arm64-v8a
887 | 1
888 |
889 |
890 | 1
891 |
892 |
893 | 1
894 |
895 |
896 | 1
897 |
898 |
899 | 1
900 |
901 |
902 | Contents\MacOS
903 | 1
904 |
905 |
906 | Contents\MacOS
907 | 1
908 |
909 |
910 | 0
911 |
912 |
913 |
914 |
915 | library\lib\armeabi-v7a
916 | 1
917 |
918 |
919 |
920 |
921 | 1
922 |
923 |
924 | 1
925 |
926 |
927 |
928 |
929 | Assets
930 | 1
931 |
932 |
933 | Assets
934 | 1
935 |
936 |
937 |
938 |
939 | Assets
940 | 1
941 |
942 |
943 | Assets
944 | 1
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 |
958 |
959 | True
960 |
961 |
962 | 12
963 |
964 |
965 |
966 |
967 |
968 |
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | Copyright (C) 2012 Ruslan Neborak
2 |
3 | Read/write xlsx (Office Open XML file format (Spreadsheet))
4 | Author: Ruslan V. Neborak
5 | e-mail: avemey@tut.by
6 | URL: http://avemey.com
7 | License: zlib
8 | Last update: 2016.07.03
9 |
10 | This software is provided 'as-is', without any express or implied
11 | warranty. In no event will the authors be held liable for any damages
12 | arising from the use of this software.
13 |
14 | Permission is granted to anyone to use this software for any purpose,
15 | including commercial applications, and to alter it and redistribute it
16 | freely, subject to the following restrictions:
17 |
18 | 1. The origin of this software must not be misrepresented; you must not
19 | claim that you wrote the original software. If you use this software
20 | in a product, an acknowledgment in the product documentation would be
21 | appreciated but is not required.
22 |
23 | 2. Altered source versions must be plainly marked as such, and must not be
24 | misrepresented as being the original software.
25 |
26 | 3. This notice may not be removed or altered from any source
27 | distribution.
--------------------------------------------------------------------------------
/ProjectGroup1.groupproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {C109B1B1-616E-4033-A1CA-7BB780922A8C}
4 |
5 |
6 |
7 |
8 |
9 |
10 | packages\Excel4DelphiLib.dproj
11 |
12 |
13 |
14 | Default.Personality.12
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 |
2 | # Excel4Delphi
3 | Read, Write excel 2002/2003 XML (SpreadsheetML / XML Spreadsheet) library.
4 |
5 | fork from https://github.com/Avemey/zexmlss
6 |
7 | ## Exchamples
8 |
9 | ```pas
10 | // Creating new workbook
11 | var workBook: TZWorkBook;
12 | ...
13 | workBook := TZWorkBook.Create();
14 | try
15 | workBook.Sheets.Add('My sheet');
16 | workBook.Sheets[0].ColCount := 10;
17 | workBook.Sheets[0].RowCount := 10;
18 | workBook.Sheets[0].CellRef['A', 0].AsString := 'Hello';
19 | workBook.Sheets[0].RangeRef['A', 0, 'B', 2].Merge();
20 | workBook.SaveToFile('file.xlsx');
21 | finally
22 | workBook.Free();
23 | end
24 | ```
25 |
26 | ```pas
27 | // Editing exists workbook
28 | var workBook: TZWorkBook;
29 | ...
30 | workBook := TZWorkBook.Create();
31 | try
32 | workBook.LoadFromFile('file.xlsx');
33 | workBook.Sheets[0].CellRef['A', 0].AsString := 'Hello';
34 | workBook.Sheets[0].CellRef['A', 0].FontStyle := [fsBold];
35 | workBook.SaveToFile('file.xlsx');
36 | finally
37 | workBook.Free();
38 | end
39 | ```
--------------------------------------------------------------------------------
/packages/Excel4DelphiLib.dpk:
--------------------------------------------------------------------------------
1 | package Excel4DelphiLib;
2 |
3 | {$IFDEF IMPLICITBUILDING This IFDEF should not be used by users}
4 | {$ALIGN 8}
5 | {$ASSERTIONS ON}
6 | {$BOOLEVAL OFF}
7 | {$DEBUGINFO OFF}
8 | {$EXTENDEDSYNTAX ON}
9 | {$IMPORTEDDATA ON}
10 | {$IOCHECKS ON}
11 | {$LOCALSYMBOLS ON}
12 | {$LONGSTRINGS ON}
13 | {$OPENSTRINGS ON}
14 | {$OPTIMIZATION OFF}
15 | {$OVERFLOWCHECKS OFF}
16 | {$RANGECHECKS OFF}
17 | {$REFERENCEINFO ON}
18 | {$SAFEDIVIDE OFF}
19 | {$STACKFRAMES ON}
20 | {$TYPEDADDRESS OFF}
21 | {$VARSTRINGCHECKS ON}
22 | {$WRITEABLECONST ON}
23 | {$MINENUMSIZE 1}
24 | {$IMAGEBASE $400000}
25 | {$DEFINE DEBUG}
26 | {$ENDIF IMPLICITBUILDING}
27 | {$DESCRIPTION 'Excel4Delphi Component'}
28 | {$IMPLICITBUILD ON}
29 |
30 | requires
31 | vcl;
32 |
33 | contains
34 | Excel4Delphi in '..\source\Excel4Delphi.pas',
35 | Excel4Delphi.Xml in '..\source\Excel4Delphi.Xml.pas',
36 | Excel4Delphi.Common in '..\source\Excel4Delphi.Common.pas',
37 | Excel4Delphi.Formula in '..\source\Excel4Delphi.Formula.pas',
38 | Excel4Delphi.Utils in '..\source\Excel4Delphi.Utils.pas',
39 | Excel4Delphi.Stream in '..\source\Excel4Delphi.Stream.pas',
40 | Excel4Delphi.NumberFormats in '..\source\Excel4Delphi.NumberFormats.pas';
41 |
42 | end.
43 |
--------------------------------------------------------------------------------
/packages/Excel4DelphiLib.dproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | {24D460A3-E03B-4BE3-AEE9-B3212F356CCD}
4 | Excel4DelphiLib.dpk
5 | True
6 | Debug
7 | 4225
8 | Package
9 | VCL
10 | 19.2
11 | Win32
12 |
13 |
14 | true
15 |
16 |
17 | true
18 | Base
19 | true
20 |
21 |
22 | true
23 | Base
24 | true
25 |
26 |
27 | true
28 | Base
29 | true
30 |
31 |
32 | true
33 | Cfg_2
34 | true
35 | true
36 |
37 |
38 | false
39 | false
40 | false
41 | false
42 | false
43 | 00400000
44 | true
45 | true
46 | Excel4DelphiLib
47 | Excel4Delphi Component
48 | Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Data;Datasnap;Web;Soap;DUnitX.Loggers.GUI;Winapi;$(DCC_Namespace)
49 | 1049
50 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=
51 |
52 |
53 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
54 | Debug
55 | true
56 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)
57 | 1033
58 |
59 |
60 | RELEASE;$(DCC_Define)
61 | 0
62 | false
63 | 0
64 |
65 |
66 | DEBUG;$(DCC_Define)
67 | false
68 | true
69 |
70 |
71 | Debug
72 |
73 |
74 |
75 | MainSource
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Cfg_2
87 | Base
88 |
89 |
90 | Base
91 |
92 |
93 | Cfg_1
94 | Base
95 |
96 |
97 |
98 | Delphi.Personality.12
99 | Package
100 |
101 |
102 |
103 | Excel4DelphiLib.dpk
104 |
105 |
106 |
107 | False
108 | False
109 | False
110 | True
111 | True
112 | True
113 | False
114 |
115 |
116 | 12
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/source/Excel4Delphi.Common.pas:
--------------------------------------------------------------------------------
1 | unit Excel4Delphi.Common;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.Types, System.Classes, Excel4Delphi, Excel4Delphi.Xml;
7 |
8 | const
9 | ZE_MMinInch: real = 25.4;
10 |
11 | type
12 | TTempFileStream = class(THandleStream)
13 | private
14 | FFileName: string;
15 | public
16 | constructor Create();
17 | destructor Destroy; override;
18 | property FileName: string read FFileName;
19 | end;
20 |
21 | // Попытка преобразовать строку в число
22 | function ZEIsTryStrToFloat(const st: string; out retValue: double): boolean;
23 | function ZETryStrToFloat(const st: string; valueIfError: double = 0): double; overload;
24 | function ZETryStrToFloat(const st: string; out isOk: boolean; valueIfError: double = 0): double; overload;
25 |
26 | // Попытка преобразовать строку в boolean
27 | function ZETryStrToBoolean(const st: string; valueIfError: boolean = false): boolean;
28 |
29 | // заменяет все запятые на точки
30 | function ZEFloatSeparator(st: string): string;
31 |
32 | // Проверяет заголовки страниц, при необходимости корректирует
33 | function ZECheckTablesTitle(var XMLSS: TZWorkBook; const SheetsNumbers: array of integer;
34 | const SheetsNames: array of string; out _pages: TIntegerDynArray; out _names: TStringDynArray;
35 | out retCount: integer): boolean;
36 |
37 | // Очищает массивы
38 | procedure ZESClearArrays(var _pages: TIntegerDynArray; var _names: TStringDynArray);
39 |
40 | // Переводит строку в boolean
41 | function ZEStrToBoolean(const val: string): boolean;
42 |
43 | // Заменяет в строке последовательности на спецсимволы
44 | function ZEReplaceEntity(const st: string): string;
45 |
46 | // despite formal angle datatype declaration in default "range check off" mode
47 | // it can be anywhere -32K to +32K
48 | // This fn brings it back into -90 .. +90 range
49 | function ZENormalizeAngle90(const value: TZCellTextRotate): integer;
50 |
51 | ///
52 | /// Despite formal angle datatype declaration in default "range check off" mode it can be anywhere -32K to +32K
53 | /// This fn brings it back into 0 .. +179 range
54 | ///
55 | function ZENormalizeAngle180(const value: TZCellTextRotate): integer;
56 |
57 | implementation
58 |
59 | uses
60 | {$IFDEF MSWINDOWS}
61 | Winapi.windows,
62 | {$ENDIF}
63 | System.DateUtils, System.IOUtils;
64 |
65 | function FileCreateTemp(var tempName: string): THandle;
66 | {$IFNDEF MSWINDOWS}
67 | var
68 | FS: TFileStream;
69 | {$ENDIF}
70 | begin
71 | Result := INVALID_HANDLE_VALUE;
72 | tempName := TPath.GetTempFileName();
73 | if tempName <> '' then
74 | begin
75 | {$IFDEF MSWINDOWS}
76 | Result := CreateFile(PChar(tempName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING,
77 | FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
78 | {$ELSE}
79 | Result := FileCreate(tempName, fmCreate);
80 | {$ENDIF}
81 | if Result = INVALID_HANDLE_VALUE then
82 | TFile.Delete(tempName);
83 | end;
84 | end;
85 |
86 | constructor TTempFileStream.Create();
87 | var
88 | FileHandle: THandle;
89 | begin
90 | FileHandle := FileCreateTemp(FFileName);
91 | if FileHandle = INVALID_HANDLE_VALUE then
92 | raise Exception.Create('Не удалось создать временный файл');
93 | inherited Create(FileHandle);
94 | end;
95 |
96 | destructor TTempFileStream.Destroy;
97 | begin
98 | if THandle(Handle) <> INVALID_HANDLE_VALUE then
99 | FileClose(Handle);
100 | inherited Destroy;
101 | end;
102 |
103 | // despite formal angle datatype declaration in default "range check off" mode
104 | // it can be anywhere -32K to +32K
105 | // This fn brings it back into -90 .. +90 range for Excel XML
106 | function ZENormalizeAngle90(const value: TZCellTextRotate): integer;
107 | var
108 | Neg: boolean;
109 | A: integer;
110 | begin
111 | if (value >= -90) and (value <= +90) then
112 | Result := value
113 | else
114 | begin (* Special values: 270; 450; -450; 180; -180; 135 *)
115 | Neg := value < 0; (* F, F, T, F, T, F *)
116 | A := Abs(value) mod 360; // 0..359 (* 270, 90, 90, 180, 180, 135 *)
117 | if A > 180 then
118 | A := A - 360; // -179..+180 (* -90, 90, 90, 180, 180, 135 *)
119 | if A < 0 then
120 | begin
121 | Neg := not Neg; (* T, -"- F, T, F, T, F *)
122 | A := -A; // 0..180 (* 90, -"- 90, 90, 180, 180, 135 *)
123 | end;
124 | if A > 90 then
125 | A := A - 180; // 91..180 -> -89..0 (* 90, 90, 90, 0, 0, -45 *)
126 | Result := A;
127 | If Neg then
128 | Result := -Result; (* -90, +90, -90, 0, 0, -45 *)
129 | end;
130 | end;
131 |
132 | // despite formal angle datatype declaration in default "range check off" mode
133 | // it can be anywhere -32K to +32K
134 | // This fn brings it back into 0 .. +180 range
135 | function ZENormalizeAngle180(const value: TZCellTextRotate): integer;
136 | begin
137 | Result := ZENormalizeAngle90(value);
138 | If Result < 0 then
139 | Result := 90 - Result;
140 | end;
141 |
142 | // Заменяет в строке последовательности на спецсимволы
143 | // INPUT
144 | // const st: string - входящая строка
145 | // RETURN
146 | // string - обработанная строка
147 | function ZEReplaceEntity(const st: string): string;
148 | var
149 | s, s1: string;
150 | i: integer;
151 | isAmp: boolean;
152 | ch: char;
153 |
154 | procedure CheckS();
155 | begin
156 | s1 := UpperCase(s);
157 | if (s1 = '>') then
158 | s := '>'
159 | else if (s1 = '<') then
160 | s := '<'
161 | else if (s1 = '&') then
162 | s := '&'
163 | else if (s1 = '&APOS;') then
164 | s := ''''
165 | else if (s1 = '"') then
166 | s := '"';
167 | end; // _checkS
168 |
169 | begin
170 | s := '';
171 | Result := '';
172 | isAmp := false;
173 | for i := 1 to length(st) do
174 | begin
175 | ch := st[i];
176 | case ch of
177 | '&':
178 | begin
179 | if (isAmp) then
180 | begin
181 | Result := Result + s;
182 | s := ch;
183 | end
184 | else
185 | begin
186 | isAmp := true;
187 | s := ch;
188 | end;
189 | end;
190 | ';':
191 | begin
192 | if (isAmp) then
193 | begin
194 | s := s + ch;
195 | CheckS();
196 | Result := Result + s;
197 | s := '';
198 | isAmp := false;
199 | end
200 | else
201 | begin
202 | Result := Result + s + ch;
203 | s := '';
204 | end;
205 | end;
206 | else
207 | if (isAmp) then
208 | s := s + ch
209 | else
210 | Result := Result + ch;
211 | end; // case
212 | end; // for
213 | if (s > '') then
214 | begin
215 | CheckS();
216 | Result := Result + s;
217 | end;
218 | end; // ZEReplaceEntity
219 |
220 | // Переводит строку в boolean
221 | // INPUT
222 | // const val: string - переводимая строка
223 | function ZEStrToBoolean(const val: string): boolean;
224 | begin
225 | if (val = '1') or (UpperCase(val) = 'TRUE') then
226 | Result := true
227 | else
228 | Result := false;
229 | end;
230 |
231 | // Попытка преобразовать строку в boolean
232 | // const st: string - строка для распознавания
233 | // valueIfError: boolean - значение, которое подставляется при ошибке преобразования
234 | function ZETryStrToBoolean(const st: string; valueIfError: boolean = false): boolean;
235 | begin
236 | Result := valueIfError;
237 | if (st > '') then
238 | begin
239 | if (CharInSet(st[1], ['T', 't', '1', '-'])) then
240 | Result := true
241 | else if (CharInSet(st[1], ['F', 'f', '0'])) then
242 | Result := false
243 | else
244 | Result := valueIfError;
245 | end;
246 | end; // ZETryStrToBoolean
247 |
248 | function ZEIsTryStrToFloat(const st: string; out retValue: double): boolean;
249 | begin
250 | retValue := ZETryStrToFloat(st, Result);
251 | end;
252 |
253 | // Попытка преобразовать строку в число
254 | // INPUT
255 | // const st: string - строка
256 | // out isOk: boolean - если true - ошибки небыло
257 | // valueIfError: double - значение, которое подставляется при ошибке преобразования
258 | function ZETryStrToFloat(const st: string; out isOk: boolean; valueIfError: double = 0): double;
259 | var
260 | s: string;
261 | i: integer;
262 | begin
263 | Result := 0;
264 | isOk := true;
265 | if (length(trim(st)) <> 0) then
266 | begin
267 | s := '';
268 | for i := 1 to length(st) do
269 | if (CharInSet(st[i], ['.', ','])) then
270 | s := s + FormatSettings.DecimalSeparator
271 | else if (st[i] <> ' ') then
272 | s := s + st[i];
273 |
274 | isOk := TryStrToFloat(s, Result);
275 | if (not isOk) then
276 | Result := valueIfError;
277 | end;
278 | end; // ZETryStrToFloat
279 |
280 | // Попытка преобразовать строку в число
281 | // INPUT
282 | // const st: string - строка
283 | // valueIfError: double - значение, которое подставляется при ошибке преобразования
284 | function ZETryStrToFloat(const st: string; valueIfError: double = 0): double;
285 | var
286 | s: string;
287 | i: integer;
288 | begin
289 | Result := 0;
290 | if (trim(st) <> '') then
291 | begin
292 | s := '';
293 | for i := 1 to length(st) do
294 | if (CharInSet(st[i], ['.', ','])) then
295 | s := s + FormatSettings.DecimalSeparator
296 | else if (st[i] <> ' ') then
297 | s := s + st[i];
298 | try
299 | Result := StrToFloat(s);
300 | except
301 | Result := valueIfError;
302 | end;
303 | end;
304 | end; // ZETryStrToFloat
305 |
306 | // заменяет все запятые на точки
307 | function ZEFloatSeparator(st: string): string;
308 | var
309 | k: integer;
310 | begin
311 | Result := '';
312 | for k := 1 to length(st) do
313 | if (st[k] = ',') then
314 | Result := Result + '.'
315 | else
316 | Result := Result + st[k];
317 | end;
318 |
319 | // Очищает массивы
320 | procedure ZESClearArrays(var _pages: TIntegerDynArray; var _names: TStringDynArray);
321 | begin
322 | SetLength(_pages, 0);
323 | SetLength(_names, 0);
324 | _names := nil;
325 | _pages := nil;
326 | end;
327 |
328 | resourcestring
329 | DefaultSheetName = 'Sheet';
330 |
331 | // делает уникальную строку, добавляя к строке '(num)'
332 | // топорно, но работает
333 | // INPUT
334 | // var st: string - строка
335 | // n: integer - номер
336 | procedure ZECorrectStrForSave(var st: string; n: integer);
337 | var
338 | l, i, m, num: integer;
339 | s: string;
340 | begin
341 | if trim(st) = '' then
342 | st := DefaultSheetName; // behave uniformly with ZECheckTablesTitle
343 |
344 | l := length(st);
345 | if st[l] <> ')' then
346 | st := st + '(' + inttostr(n) + ')'
347 | else
348 | begin
349 | m := l;
350 | for i := l downto 1 do
351 | if st[i] = '(' then
352 | begin
353 | m := i;
354 | break;
355 | end;
356 | if m <> l then
357 | begin
358 | s := copy(st, m + 1, l - m - 1);
359 | try
360 | num := StrToInt(s) + 1;
361 | except
362 | num := n;
363 | end;
364 | Delete(st, m, l - m + 1);
365 | st := st + '(' + inttostr(num) + ')';
366 | end
367 | else
368 | st := st + '(' + inttostr(n) + ')';
369 | end;
370 | end; // ZECorrectStrForSave
371 |
372 | // делаем уникальные значения массивов
373 | // INPUT
374 | // var mas: array of string - массив со значениями
375 | procedure ZECorrectTitles(var mas: array of string);
376 | var
377 | i, num, k, _kol: integer;
378 | s: string;
379 | begin
380 | num := 0;
381 | _kol := High(mas);
382 | while (num < _kol) do
383 | begin
384 | s := UpperCase(mas[num]);
385 | k := 0;
386 | for i := num + 1 to _kol do
387 | begin
388 | if (s = UpperCase(mas[i])) then
389 | begin
390 | inc(k);
391 | ZECorrectStrForSave(mas[i], k);
392 | end;
393 | end;
394 | inc(num);
395 | if k > 0 then
396 | num := 0;
397 | end;
398 | end; // CorrectTitles
399 |
400 | // Проверяет заголовки страниц, при необходимости корректирует
401 | // INPUT
402 | // var XMLSS: TZWorkBook
403 | // const SheetsNumbers:array of integer
404 | // const SheetsNames: array of string
405 | // var _pages: TIntegerDynArray
406 | // var _names: TStringDynArray
407 | // var retCount: integer
408 | // RETURN
409 | // boolean - true - всё нормально, можно продолжать дальше
410 | // false - что-то не то подсунули, дальше продолжать нельзя
411 | function ZECheckTablesTitle(var XMLSS: TZWorkBook; const SheetsNumbers: array of integer;
412 | const SheetsNames: array of string; out _pages: TIntegerDynArray; out _names: TStringDynArray;
413 | out retCount: integer): boolean;
414 | var
415 | t1, t2, i: integer;
416 | // '!' is allowed; ':' is not; whatever else ?
417 | procedure SanitizeTitle(var s: string);
418 | var
419 | i: integer;
420 | begin
421 | s := trim(s);
422 | for i := 1 to length(s) do
423 | if s[i] = ':' then
424 | s[i] := ';';
425 | end;
426 | function CoalesceTitle(const i: integer; const checkArray: boolean): string;
427 | begin
428 | if checkArray then
429 | begin
430 | Result := SheetsNames[i];
431 | SanitizeTitle(Result);
432 | end
433 | else
434 | Result := '';
435 |
436 | if Result = '' then
437 | begin
438 | Result := XMLSS.Sheets[_pages[i]].Title;
439 | SanitizeTitle(Result);
440 | end;
441 |
442 | if Result = '' then
443 | Result := DefaultSheetName + ' ' + inttostr(_pages[i] + 1);
444 | end;
445 |
446 | begin
447 | Result := false;
448 | t1 := Low(SheetsNumbers);
449 | t2 := High(SheetsNumbers);
450 | retCount := 0;
451 | // если пришёл пустой массив SheetsNumbers - берём все страницы из Sheets
452 | if t1 = t2 + 1 then
453 | begin
454 | retCount := XMLSS.Sheets.Count;
455 | SetLength(_pages, retCount);
456 | for i := 0 to retCount - 1 do
457 | _pages[i] := i;
458 | end
459 | else
460 | begin
461 | // иначе берём страницы из массива SheetsNumbers
462 | for i := t1 to t2 do
463 | begin
464 | if (SheetsNumbers[i] >= 0) and (SheetsNumbers[i] < XMLSS.Sheets.Count) then
465 | begin
466 | inc(retCount);
467 | SetLength(_pages, retCount);
468 | _pages[retCount - 1] := SheetsNumbers[i];
469 | end;
470 | end;
471 | end;
472 |
473 | if (retCount <= 0) then
474 | exit;
475 |
476 | // названия страниц
477 | // t1 := Low(SheetsNames); // we anyway assume later that Low(_names) == t1 - then let us just skip this.
478 | t2 := High(SheetsNames);
479 | SetLength(_names, retCount);
480 | // if t1 = t2 + 1 then
481 | // begin
482 | // for i := 0 to retCount - 1 do
483 | // begin
484 | // _names[i] := XMLSS.Sheets[_pages[i]].Title;
485 | // if trim(_names[i]) = '' then _names[i] := 'list';
486 | // end;
487 | // end else
488 | // begin
489 | // if (t2 > retCount) then
490 | // t2 := retCount - 1;
491 | // for i := t1 to t2 do
492 | // _names[i] := SheetsNames[i];
493 | // if (t2 < retCount) then
494 | // for i := t2 + 1 to retCount - 1 do
495 | // begin
496 | // _names[i] := XMLSS.Sheets[_pages[i]].Title;
497 | // if trim(_names[i]) = '' then _names[i] := 'list';
498 | // end;
499 | // end;
500 | for i := Low(_names) to High(_names) do
501 | begin
502 | _names[i] := CoalesceTitle(i, i <= t2);
503 | end;
504 |
505 | ZECorrectTitles(_names);
506 | Result := true;
507 | end; // ZECheckTablesTitle
508 |
509 | end.
510 |
--------------------------------------------------------------------------------
/source/Excel4Delphi.Formula.pas:
--------------------------------------------------------------------------------
1 | unit Excel4Delphi.Formula;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils;
7 |
8 | const
9 | // ZE_RTA = ZE R1C1 to A1
10 | ZE_RTA_ODF = 1; // преобразовывать для ODF (=[.A1] + [.B1])
11 | ZE_RTA_ODF_PREFIX = 2; // добавлять префикс для ODF, если первый символ в формуле '=' (of:=[.A1] + [.B1])
12 | ZE_RTA_NO_ABSOLUTE = 4; // все абсолютные ссылки заменять на относительные (R1C1 => A1) (относительные не меняет)
13 | ZE_RTA_ONLY_ABSOLUTE = 8; // все относительные ссылки заменять на абсолютные (R[1]C[1] => $C$3) (абсолютные не меняет)
14 | ZE_RTA_ODF_NO_BRACKET = $10; // Для ODF, но не добавлять квадратные скобки, разделитель лист/ячейка - точка ".".
15 | ZE_ATR_DEL_PREFIX = 1; // Удалять все символы до первого '='
16 |
17 | function ZEGetA1byCol(ColNum: integer; StartZero: boolean = true): string;
18 | function ZERangeToRow(range: string): integer;
19 | function ZEGetColByA1(AA: string; StartZero: boolean = true): integer;
20 | function ZER1C1ToA1(const Formula: string; CurCol, CurRow: integer; options: integer;
21 | StartZero: boolean = true): string;
22 | function ZEA1ToR1C1(const Formula: string; CurCol, CurRow: integer; options: integer;
23 | StartZero: boolean = true): string;
24 | function ZEGetCellCoords(const cell: string; out column, row: integer; StartZero: boolean = true): boolean;
25 |
26 | implementation
27 |
28 | const
29 | ZE_STR_ARRAY: array [0 .. 25] of char = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
30 | 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
31 |
32 | // Получает номер строки и столбца по строковому значению (для A1 стилей)
33 | // INPUT
34 | // const cell: string - номер ячейки в A1 стиле
35 | // out column: integer - возвращаемый номер столбца
36 | // out row: integer - возвращаемый номер строки
37 | // StartZero: boolean - признак нумерации с нуля
38 | // RETURN
39 | // boolean - true - координаты успешно определены
40 | function ZEGetCellCoords(const cell: string; out column, row: integer; StartZero: boolean = true): boolean;
41 | var
42 | i: integer;
43 | s1, s2: string;
44 | _isOk: boolean;
45 | b: boolean;
46 |
47 | begin
48 | _isOk := true;
49 | s1 := '';
50 | s2 := '';
51 | b := false;
52 | for i := 1 to length(cell) do
53 | case cell[i] of
54 | 'A' .. 'Z', 'a' .. 'z':
55 | begin
56 | s1 := s1 + cell[i];
57 | b := true;
58 | end;
59 | '0' .. '9':
60 | begin
61 | if (not b) then
62 | begin
63 | _isOk := false;
64 | break;
65 | end;
66 | s2 := s2 + cell[i];
67 | end;
68 | else
69 | begin
70 | _isOk := false;
71 | break;
72 | end;
73 | end;
74 | if (_isOk) then
75 | begin
76 | if (not TryStrToInt(s2, row)) then
77 | _isOk := false
78 | else
79 | begin
80 | if (StartZero) then
81 | dec(row);
82 | column := ZEGetColByA1(s1, StartZero);
83 | if (column < 0) then
84 | _isOk := false;
85 | end;
86 | end;
87 | result := _isOk;
88 | end; // ZEGetCellCoords
89 |
90 | // Попытка преобразовать номер ячейки из R1C1 в A1 стиль
91 | // если не удалось распознать номер ячейки, то возвратит обратно тот же текст
92 | // INPUT
93 | // const st: string - предположительно номер ячеки (диапазон)
94 | // CurCol: integer - номер столбца ячейки
95 | // CurRow: integer - номер строки ячейки
96 | // options: integer - параметры преобразования
97 | // StartZero: boolean - признак нумерации с нуля
98 | // RETURN
99 | // string - номер ячейки в стиле A1
100 | function ReturnA1(const st: string; CurCol, CurRow: integer; options: integer; StartZero: boolean = true): string;
101 | var
102 | s: string;
103 | i, kol: integer;
104 | retTxt: string;
105 | isApos: boolean;
106 | t: integer;
107 | isList: boolean;
108 | isODF: boolean;
109 | isSq: boolean;
110 | isOk: boolean;
111 | isNumber: boolean;
112 | isR, isC: boolean;
113 | isNotLast: boolean;
114 | _c, _r: string;
115 | is_only_absolute: boolean;
116 | is_no_absolute: boolean;
117 | isDelim: boolean;
118 | _num: integer;
119 | _use_bracket: boolean;
120 |
121 | // Возвращает строку
122 | procedure _getR(num: integer);
123 | begin
124 | if (isSq or (num = 0)) then
125 | num := CurRow + num;
126 | if (is_only_absolute and isSq) then
127 | isSq := false
128 | else if (is_no_absolute and (not isSq)) then
129 | isSq := true;
130 | _r := IntToStr(num);
131 | if (not isSq) then
132 | _r := '$' + _r;
133 | isNumber := false;
134 | inc(_num);
135 | end; // _getR
136 |
137 | // Возвращает столбец
138 | procedure _getC(num: integer);
139 | begin
140 | if (isSq or (num = 0)) then
141 | num := CurCol + num;
142 | if (is_only_absolute and isSq) then
143 | isSq := false
144 | else if (is_no_absolute and (not isSq)) then
145 | isSq := true;
146 | _c := ZEGetA1byCol(num, false);
147 | if (not isSq) then
148 | _c := '$' + _c;
149 | isNumber := false;
150 | inc(_num);
151 | end; // _getС
152 |
153 | // Проверяет символ
154 | procedure _checksymbol(ch: char);
155 | begin
156 | if (isApos) then
157 | begin
158 | if (ch <> '''') then
159 | begin
160 | s := s + ch;
161 | exit;
162 | end;
163 | end
164 | else
165 | begin
166 | if (isNumber) then
167 | begin
168 | if (not CharInSet(ch, ['-', '0' .. '9', ']', '[', ''''])) then
169 | begin
170 | if (not(isC xor isR)) then
171 | begin
172 | isOk := false;
173 | exit;
174 | end;
175 | if (not TryStrToInt(s, t)) then
176 | begin
177 | isOk := false;
178 | exit;
179 | end;
180 |
181 | if (isC) then
182 | _getC(t);
183 | if (isR) then
184 | _getR(t);
185 | isSq := false;
186 | s := '';
187 | end;
188 | end
189 | else // if (isNumber)
190 | begin
191 | // если адрес: RC (без чисел - нули)
192 | if (isR and CharInSet(ch, ['C', 'c'])) then
193 | begin
194 | _getR(0);
195 | s := '';
196 | isSq := false;
197 | end
198 | else if (isC and (not isNotLast)) then
199 | begin
200 | _getC(0);
201 | s := '';
202 | isSq := false;
203 | end;
204 | end;
205 | end;
206 | case ch of
207 | '''':
208 | begin
209 | s := s + ch;
210 | isApos := not isApos;
211 | end;
212 | '[': { хм.. }
213 | ;
214 | ']':
215 | isSq := true;
216 | 'R', 'r':
217 | begin
218 | // R - ok, CR - что-то не то
219 | if (isR or isC) then
220 | isOk := false;
221 | isR := true;
222 | s := '';
223 | isDelim := false;
224 | end;
225 | 'C', 'c':
226 | begin
227 | if (isC or (not isR)) then
228 | isOk := false
229 | else
230 | begin
231 | isC := true;
232 | isR := false;
233 | end;
234 | s := '';
235 | isDelim := false;
236 | end;
237 | '-', '0' .. '9':
238 | begin
239 | s := s + ch;
240 | if (isC or isR) then
241 | if (not isNumber) then
242 | isNumber := true;
243 | end;
244 | '!': // разделитель страницы
245 | begin
246 | retTxt := retTxt + s;
247 | if (isODF) then // ODF
248 | retTxt := retTxt + '.'
249 | else
250 | retTxt := retTxt + ch;
251 | s := '';
252 | isList := true;
253 | isDelim := false;
254 | end;
255 | else
256 | if (isDelim and isNotLast) then
257 | s := s + ch
258 | else if (isNotLast) then
259 | isOk := false; // O_o - вроде как не ячейка, выходим и возвращаем всё как есть
260 | end; // case
261 | end; // _checksymbol
262 |
263 | begin
264 | result := '';
265 | if (TryStrToInt(st, t)) then
266 | begin
267 | result := st;
268 | exit;
269 | end;
270 | kol := length(st);
271 | s := '';
272 | retTxt := '';
273 | isApos := false;
274 | isList := false;
275 | isSq := false;
276 | isOk := true;
277 | isNumber := false;
278 | isR := false;
279 | isC := false;
280 | isNotLast := true;
281 | isDelim := true;
282 | _c := '';
283 | _r := '';
284 | _num := 0;
285 |
286 | is_no_absolute := (options and ZE_RTA_NO_ABSOLUTE = ZE_RTA_NO_ABSOLUTE);
287 | is_only_absolute := (options and ZE_RTA_ONLY_ABSOLUTE = ZE_RTA_ONLY_ABSOLUTE);
288 | isODF := (options and ZE_RTA_ODF = ZE_RTA_ODF);
289 | for i := 1 to kol do
290 | begin
291 | _checksymbol(st[i]);
292 | if (not isOk) then
293 | break;
294 | end;
295 | isNotLast := false;
296 | // нужно подумать, что делать, если было не 2 преобразования
297 | if ((kol <= 0) or (_num = 0)) then
298 | isOk := false;
299 | _checksymbol(';');
300 | if (not isOk) then
301 | begin
302 | result := st;
303 | exit;
304 | end;
305 | result := retTxt + _c + _r + s;
306 | _use_bracket := not(options and ZE_RTA_ODF_NO_BRACKET = ZE_RTA_ODF_NO_BRACKET);
307 | if (isODF and _use_bracket) then
308 | begin
309 | if (not isList) then
310 | result := '.' + result;
311 | result := '[' + result + ']';
312 | end;
313 | end; // ReturnA1
314 |
315 | // Переводит формулу из стиля R1C1 в стиль A1
316 | // INPUT
317 | // const formula: string - формула в стиле R1C1
318 | // CurRow: integer - номер строки ячейки
319 | // CurCol: integer - номер столбца ячейки
320 | // options: integer - настройки преобразования (ZE_RTA_ODF и ZE_RTA_ODF_PREFIX)
321 | // options and ZE_RTA_ODF = ZE_RTA_ODF - преобразовывать для ODF (=[.A1] + [.B1])
322 | // options and ZE_RTA_ODF_PREFIX = ZE_RTA_ODF_PREFIX - добавлять префикс для ODF, если первый символ в формуле '=' (of:=[.A1] + [.B1])
323 | // StartZero: boolean- при true счёт строки/ячейки начинается с 0.
324 | // RETURN
325 | // string - текст формулы в стиле R1C1
326 | function ZER1C1ToA1(const Formula: string; CurCol, CurRow: integer; options: integer;
327 | StartZero: boolean = true): string;
328 | var
329 | kol: integer;
330 | i: integer;
331 | retFormula: string;
332 | s: string;
333 | isQuote: boolean; // " ... "
334 | isApos: boolean; // ' ... '
335 | isNotLast: boolean;
336 | isSq: boolean;
337 |
338 | procedure _checksymbol(ch: char);
339 | begin
340 | case ch of
341 | '"':
342 | begin
343 | if (isApos) then
344 | s := s + ch
345 | else
346 | begin
347 | if (isQuote) then
348 | begin
349 | retFormula := retFormula + s + ch;
350 | s := '';
351 | end
352 | else
353 | begin
354 | if (s > '') then
355 | begin
356 | // O_o Странно
357 | retFormula := retFormula + ReturnA1(s, CurCol, CurRow, options, StartZero);
358 | s := '';
359 | end;
360 | s := ch
361 | end;
362 | isQuote := not isQuote;
363 | end;
364 | end;
365 | '''':
366 | begin
367 | s := s + ch;
368 | if (not isQuote) then
369 | isApos := not isApos;
370 | end;
371 | '[':
372 | begin
373 | s := s + ch;
374 | if (not(isQuote or isApos)) then
375 | isSq := true;
376 | end;
377 | ']':
378 | begin
379 | s := s + ch;
380 | if (not(isQuote or isApos)) then
381 | isSq := false;
382 | end;
383 | ':', ';', ' ', '-', '%', '^', '*', '/', '+', '&', '<', '>', '(', ')', '=': // разделители
384 | begin
385 | if (isApos or isQuote or isSq) then
386 | s := s + ch
387 | else
388 | begin
389 | retFormula := retFormula + ReturnA1(s, CurCol, CurRow, options, StartZero);
390 | if (isNotLast) then
391 | retFormula := retFormula + ch;
392 | s := '';
393 | end;
394 | end;
395 | else
396 | s := s + ch;
397 | end;
398 | end; // _checksymbol
399 |
400 | begin
401 | result := '';
402 | kol := length(Formula);
403 | retFormula := '';
404 | s := '';
405 | if (StartZero) then
406 | begin
407 | inc(CurRow);
408 | inc(CurCol);
409 | end;
410 | isApos := false;
411 | isQuote := false;
412 | isNotLast := true;
413 | isSq := false;
414 | for i := 1 to kol do
415 | _checksymbol(Formula[i]);
416 | isNotLast := false;
417 | _checksymbol(';');
418 | result := retFormula;
419 | if (options and ZE_RTA_ODF = ZE_RTA_ODF) and (options and ZE_RTA_ODF_PREFIX = ZE_RTA_ODF_PREFIX) then
420 | if (kol > 0) then
421 | if (Formula[1] = '=') then
422 | result := 'of:' + result;
423 | end; // ZER1C1ToA1
424 |
425 | // Попытка преобразовать номер ячейки из A1 в R1C1 стиль
426 | // если не удалось распознать номер ячейки, то возвратит обратно тот же текст
427 | // INPUT
428 | // const st: string - предположительно номер ячеки (диапазон)
429 | // CurCol: integer - номер столбца ячейки
430 | // CurRow: integer - номер строки ячейки
431 | // options: integer - настройки
432 | // StartZero: boolean - признак нумерации с нуля
433 | // RETURN
434 | // string - номер ячейки в стиле R1C1
435 | function ReturnR1C1(const st: string; CurCol, CurRow: integer; StartZero: boolean = true): string;
436 | var
437 | i: integer;
438 | s: string;
439 | isApos: boolean;
440 | _startNumber: boolean;
441 | kol: integer;
442 | num: integer;
443 | t: integer;
444 | isAbsolute: byte;
445 | sa: string;
446 | isNotLast: boolean;
447 | column: string;
448 | isC: boolean;
449 |
450 | procedure _GetColumn();
451 | begin
452 | // попробовать преобразовать
453 | num := ZEGetColByA1(s, false);
454 | if (num >= 0) then // распознался вроде нормально
455 | begin
456 | if (num > 25000) then // сколько там колонок возможно?
457 | result := result + sa + s
458 | else
459 | begin
460 | column := '';
461 | if (isAbsolute > 0) then
462 | column := 'C' + IntToStr(num)
463 | else
464 | begin
465 | t := num - CurCol;
466 | if (t <> 0) then
467 | column := 'C[' + IntToStr(t) + ']'
468 | else
469 | column := 'C';
470 | end;
471 | end;
472 | end
473 | else // что-то не то
474 | result := result + sa + s;
475 | if (isAbsolute > 0) then
476 | dec(isAbsolute);
477 | sa := '';
478 | s := '';
479 | isC := true;
480 | end; // _GetColumn
481 |
482 | procedure _checksymbol(ch: char);
483 | begin
484 | if (not CharInSet(ch, ['0' .. '9'])) then
485 | if (not isApos) then
486 | begin
487 | if (_startNumber) then
488 | begin
489 | if (TryStrToInt(s, t)) then // удалось получить число
490 | begin
491 | if (isAbsolute > 0) then
492 | result := result + 'R' + s + column
493 | else
494 | begin
495 | t := t - CurRow;
496 | if (t <> 0) then
497 | result := result + 'R[' + IntToStr(t) + ']' + column
498 | else
499 | result := result + 'R' + column;
500 | end;
501 | if (isAbsolute > 0) then
502 | dec(isAbsolute);
503 | isC := false;
504 | end
505 | else
506 | result := result + sa + s;
507 | s := '';
508 | sa := '';
509 | end;
510 | _startNumber := false;
511 | end;
512 | case ch of
513 | '''':
514 | begin
515 | s := s + ch;
516 | if (isApos) then
517 | begin
518 | result := result + s;
519 | s := '';
520 | end;
521 | isApos := not isApos;
522 | end;
523 | '.': // разделитель для листа (OpenOffice/LibreOffice)
524 | begin
525 | if (isApos) then
526 | s := s + ch
527 | else
528 | begin
529 | if (s > '') then
530 | result := result + s + '!';
531 | s := '';
532 | end;
533 | end;
534 | '!': // разделитель для листа (excel)
535 | begin
536 | if (isApos) then
537 | s := s + ch
538 | else
539 | begin
540 | result := result + s + ch;
541 | s := '';
542 | end;
543 | end;
544 | '$':
545 | begin
546 | if (isApos) then
547 | s := s + ch
548 | else
549 | begin
550 | if (not _startNumber) and (s > '') then
551 | _GetColumn();
552 | inc(isAbsolute);
553 | sa := ch;
554 | end;
555 | end;
556 | '[':
557 | begin
558 | if (isApos) then
559 | s := s + ch
560 | else
561 | begin
562 | end;
563 | end;
564 | ']':
565 | begin
566 | if (isApos) then
567 | s := s + ch
568 | else
569 | begin
570 | end;
571 | end;
572 | '0' .. '9':
573 | begin
574 | if (isApos) then
575 | s := s + ch
576 | else
577 | begin
578 | if ((not _startNumber) and (not isC)) then
579 | begin
580 | _GetColumn();
581 | s := '';
582 | end;
583 | s := s + ch;
584 | _startNumber := true;
585 | end;
586 | end;
587 | else
588 | if (isNotLast) then
589 | s := s + ch;
590 | end; // case
591 | end; // _CheckSymbol
592 |
593 | // Проверяет, с какого символа в строке начать
594 | procedure FindStartNumber(out num: integer);
595 | var
596 | i: integer;
597 | z: boolean;
598 | begin
599 | num := 1;
600 | z := false;
601 | for i := 1 to kol do
602 | case st[i] of
603 | '''':
604 | begin
605 | s := s + st[i];
606 | z := not z;
607 | end;
608 | '!', '.':
609 | if (not z) then
610 | begin
611 | num := i;
612 | exit;
613 | end;
614 | else
615 | s := s + st[i];
616 | end; // case
617 | s := '';
618 | end; // FindStartNumber
619 |
620 | begin
621 | result := '';
622 | s := '';
623 | isApos := false;
624 | kol := length(st);
625 | if (kol >= 1) then
626 | if (st[1] <> '$') then
627 | if (TryStrToInt(st, t)) then
628 | begin
629 | result := st;
630 | exit;
631 | end;
632 | FindStartNumber(i);
633 | _startNumber := false;
634 | isAbsolute := 0;
635 | sa := '';
636 | column := '';
637 | isNotLast := true;
638 | isC := false;
639 | while (i <= kol) do
640 | begin
641 | _checksymbol(st[i]);
642 | inc(i);
643 | end; // while
644 | isNotLast := false;
645 | _checksymbol(';');
646 | if (s > '') then
647 | result := result + s;
648 | end; // ReturnR1C1
649 |
650 | // Переводит формулу из стиля A1 в стиль R1C1
651 | // INPUT
652 | // const formula: string - формула в стиле A1
653 | // CurRow: integer - номер строки ячейки
654 | // CurCol: integer - номер столбца ячейки
655 | // options: integer - настройки преобразования
656 | // StartZero: boolean- при true счёт строки/ячейки начинается с 0.
657 | // RETURN
658 | // string - текст формулы в стиле R1C1
659 | function ZEA1ToR1C1(const Formula: string; CurCol, CurRow: integer; options: integer;
660 | StartZero: boolean = true): string;
661 | var
662 | i, l: integer;
663 | s: string;
664 | retFormula: string;
665 | isQuote: boolean; // " ... "
666 | isApos: boolean; // ' ... '
667 | isNotLast: boolean;
668 | start_num: integer;
669 |
670 | // Проверить символ
671 | // INPUT
672 | // const ch: char - символ для проверки
673 | procedure _checksymbol(const ch: char);
674 | begin
675 | case ch of
676 | '"':
677 | begin;
678 | if (isApos) then
679 | s := s + ch
680 | else
681 | begin
682 | if (isQuote) then
683 | begin
684 | retFormula := retFormula + s + ch;
685 | s := '';
686 | end
687 | else
688 | begin
689 | if (s > '') then
690 | begin
691 | // O_o Странно
692 | retFormula := retFormula + ReturnR1C1(s, CurCol, CurRow, StartZero);
693 | s := '';
694 | end;
695 | s := ch
696 | end;
697 | isQuote := not isQuote;
698 | end;
699 | end;
700 | '''':
701 | begin
702 | s := s + ch;
703 | if (not isQuote) then
704 | isApos := not isApos;
705 | end;
706 | ':', ';', ' ', '-', '%', '^', '*', '/', '+', '&', '<', '>', '(', ')', ']', '[', '=': // разделители
707 | begin
708 | if (isQuote or isApos) then
709 | s := s + ch
710 | else
711 | begin
712 | retFormula := retFormula + ReturnR1C1(s, CurCol, CurRow, StartZero);
713 | if (isNotLast) then
714 | if (not CharInSet(ch, ['[', ']'])) then
715 | retFormula := retFormula + ch;
716 | s := '';
717 | end;
718 | end;
719 | else
720 | s := s + ch;
721 | end;
722 | end; // _CheckSymbol
723 |
724 | procedure FindStartNum(var start_num: integer);
725 | var
726 | i: integer;
727 | begin
728 | for i := 1 to l do
729 | if (Formula[i] = '=') then
730 | begin
731 | start_num := i;
732 | exit;
733 | end;
734 | end; // FindStartNum
735 |
736 | begin
737 | result := '';
738 | l := length(Formula);
739 | s := '';
740 | retFormula := '';
741 | isQuote := false;
742 | isApos := false;
743 | isNotLast := true;
744 | if (StartZero) then
745 | begin
746 | inc(CurRow);
747 | inc(CurCol);
748 | end;
749 |
750 | start_num := 1;
751 | if (options and ZE_ATR_DEL_PREFIX = ZE_ATR_DEL_PREFIX) then
752 | FindStartNum(start_num);
753 |
754 | for i := start_num to l do
755 | _checksymbol(Formula[i]);
756 | isNotLast := false;
757 | _checksymbol(';');
758 | if (isQuote or isApos) then
759 | retFormula := retFormula + s;
760 | result := retFormula;
761 | end; // ZEA1ToR1C1
762 |
763 | function ZERangeToRow(range: string): integer;
764 | var
765 | i: integer;
766 | begin
767 | for i := 1 to length(range) - 1 do
768 | begin
769 | if CharInSet(range.Chars[i], ['0' .. '9']) then
770 | begin
771 | exit(StrToInt(range.Substring(i)));
772 | end;
773 | end;
774 | raise Exception.Create('Не удалось вычислить номер строки из формулы: ' + range);
775 | end;
776 |
777 | // Возвращает номер столбца по буквенному обозначению
778 | // INPUT
779 | // const AA: string - буквенное обозначение столбца
780 | // StartZero: boolean - если true, то счёт начинает с нуля (т.е. A = 0), в противном случае с 1.
781 | // RETURN
782 | // integer - -1 - не удалось преобразовать
783 | function ZEGetColByA1(AA: string; StartZero: boolean = true): integer;
784 | var
785 | i: integer;
786 | num, t, kol, s: integer;
787 | begin
788 | result := -1;
789 | num := 0;
790 | AA := UpperCase(AA);
791 | kol := length(AA);
792 | s := 1;
793 | for i := kol downto 1 do
794 | begin
795 | if not CharInSet(AA[i], ['A' .. 'Z']) then
796 | continue;
797 | t := ord(AA[i]) - ord('A');
798 | num := num + (t + 1) * s;
799 | s := s * 26;
800 | if (s < 0) or (num < 0) then
801 | exit;
802 | end;
803 | result := num;
804 | if (StartZero) then
805 | result := result - 1;
806 | end; // ZEGetColByAA
807 |
808 | // Возвращает буквенное обозначение столбца для АА стиля
809 | // INPUT
810 | // ColNum: integer - номер столбца
811 | // StartZero: boolean - если true, то счёт начинается с 0, в противном случае - с 1.
812 | function ZEGetA1byCol(ColNum: integer; StartZero: boolean = true): string;
813 | var
814 | t, n: integer;
815 | s: string;
816 | begin
817 | t := ColNum;
818 | if (not StartZero) then
819 | dec(t);
820 | result := '';
821 | s := '';
822 | while t >= 0 do
823 | begin
824 | n := t mod 26;
825 | t := (t div 26) - 1;
826 | // ХЗ как там с кодировками будет
827 | s := s + ZE_STR_ARRAY[n];
828 | end;
829 | for t := length(s) downto 1 do
830 | result := result + s[t];
831 | end; // ZEGetAAbyCol
832 |
833 | end.
834 |
--------------------------------------------------------------------------------
/source/Excel4Delphi.NumberFormats.pas:
--------------------------------------------------------------------------------
1 | unit Excel4Delphi.NumberFormats;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, Excel4Delphi.Xml;
7 |
8 | const
9 | // Main number formats
10 | ZE_NUMFORMAT_IS_UNKNOWN = 0;
11 | ZE_NUMFORMAT_IS_NUMBER = 1;
12 | ZE_NUMFORMAT_IS_DATETIME = 2;
13 | ZE_NUMFORMAT_IS_STRING = 4;
14 |
15 | // Additional properties for number styles
16 | ZE_NUMFORMAT_NUM_IS_PERCENTAGE = 1 shl 10;
17 | ZE_NUMFORMAT_NUM_IS_SCIENTIFIC = 1 shl 11;
18 | ZE_NUMFORMAT_NUM_IS_CURRENCY = 1 shl 12;
19 | ZE_NUMFORMAT_NUM_IS_FRACTION = 1 shl 13;
20 |
21 | ZE_NUMFORMAT_DATE_IS_ONLY_TIME = 1 shl 14;
22 |
23 | // DateStyles
24 | ZETag_number_date_style = 'number:date-style';
25 | ZETag_number_time_style = 'number:time-style';
26 |
27 | ZETag_number_day = 'number:day';
28 | ZETag_number_text = 'number:text';
29 | ZETag_number_style = 'number:style';
30 | ZETag_number_month = 'number:month';
31 | ZETag_number_year = 'number:year';
32 | ZETag_number_hours = 'number:hours';
33 | ZETag_number_minutes = 'number:minutes';
34 | ZETag_number_seconds = 'number:seconds';
35 | ZETag_number_day_of_week = 'number:day-of-week';
36 | ZETag_number_textual = 'number:textual';
37 | ZETag_number_possessive_form = 'number:possessive-form';
38 | ZETag_number_am_pm = 'number:am-pm';
39 | ZETag_number_quarter = 'number:quarter';
40 | ZETag_number_week_of_year = 'number:week-of-year';
41 | ZETag_number_era = 'number:era';
42 |
43 | // NumberStyles:
44 | // WARNING: number style = currency style = percentage style!
45 | // TODO:
46 | // Is need separate number/currency/percentage number styles?
47 | ZETag_number_number_style = 'number:number-style';
48 | ZETag_number_currency_style = 'number:currency-style';
49 | ZETag_number_percentage_style = 'number:percentage-style';
50 |
51 | // for currency
52 | ZETag_number_currency_symbol = 'number:currency-symbol';
53 | ZETag_number_language = 'number:language';
54 | ZETag_number_country = 'number:country';
55 |
56 | ZETag_number_fraction = 'number:fraction';
57 | ZETag_number_scientific_number = 'number:scientific-number';
58 | ZETag_number_embedded_text = 'number:embedded-text';
59 | ZETag_number_number = 'number:number';
60 | ZETag_number_decimal_places = 'number:decimal-places';
61 | ZETag_number_decimal_replacement = 'number:decimal-replacement';
62 | ZETag_number_display_factor = 'number:display-factor';
63 | ZETag_number_grouping = 'number:grouping';
64 | ZETag_number_min_integer_digits = 'number:min-integer-digits';
65 | ZETag_number_position = 'number:position';
66 | ZETag_number_min_exponent_digits = 'number:min-exponent-digits';
67 |
68 | ZETag_number_min_numerator_digits = 'number:min-numerator-digits';
69 | ZETag_number_min_denominator_digits = 'number:min-denominator-digits';
70 | ZETag_number_denominator_value = 'number:denominator-value';
71 |
72 | ZETag_number_text_style = 'number:text-style';
73 | ZETag_number_text_content = 'number:text-content';
74 |
75 | ZETag_style_text_properties = 'style:text-properties';
76 | ZETag_style_map = 'style:map';
77 | ZETag_fo_color = 'fo:color';
78 |
79 | ZETag_Attr_StyleName = 'style:name';
80 | ZETag_style_condition = 'style:condition';
81 | ZETag_style_apply_style_name = 'style:apply-style-name';
82 | ZETag_long = 'long';
83 | ZETag_short = 'short';
84 | ZETag_style_volatile = 'style:volatile';
85 |
86 | type
87 | TZODSNumberItemOptions = record
88 | isColor: boolean;
89 | ColorStr: string;
90 | StyleType: byte;
91 | end;
92 |
93 | TODSEmbeded_text_props = record
94 | Txt: string;
95 | NumberPosition: integer;
96 | end;
97 |
98 | // Date/Time item for processing date number style
99 | TZDateTimeProcessItem = record
100 | // Item type:
101 | // -1 - error item (ignore)
102 | // 0 - text
103 | // 1 - year (Y/YY/YYYY)
104 | // 2 - month (M/MM/MMM/MMMM/MMMMM)
105 | // 3 - day (D/DD/DDD/DDDD/NN/NNN/NNNN)
106 | // 4 - hour (h/hh)
107 | // 5 - minute (m/mm)
108 | // 6 - second (s/ss)
109 | // 7 - week (WW)
110 | // 8 - quarterly (Q/QQ)
111 | // 9 - era jap (G/GG/GGG/RR/GGGEE)
112 | // 10 - number of the year in era (E/EE/R)
113 | // 11 - AM/PM (a/p AM/PM)
114 | ItemType: integer;
115 | // Text value (for ItemType = 0)
116 | TextValue: string;
117 | // Length for item
118 | Len: integer;
119 | // Additional settings for item
120 | Settings: integer;
121 | end;
122 |
123 | // Simple parser for number format
124 | TNumFormatParser = class
125 | private
126 | FStr: string;
127 | FLen: integer;
128 | FPos: integer;
129 | FReadedSymbol: string;
130 | FReadedSymbolType: integer;
131 | FIsError: integer;
132 | FFirstSymbol: char;
133 | protected
134 | procedure Clear();
135 | public
136 | constructor Create();
137 | procedure BeginRead(const AStr: string);
138 | function ReadSymbol(): boolean;
139 | procedure IncPos(ADelta: integer);
140 | property FirstSymbol: char read FFirstSymbol;
141 | property ReadedSymbol: string read FReadedSymbol;
142 | property ReadedSymbolType: integer read FReadedSymbolType;
143 | property StrLength: integer read FLen;
144 | property CurrentPos: integer read FPos;
145 | property IsError: integer read FIsError;
146 | end;
147 |
148 | // Parser for ODS datetime format
149 | TZDateTimeODSFormatParser = class
150 | private
151 | FCount: integer;
152 | FMaxCount: integer;
153 | protected
154 | procedure IncCount(ADelta: integer = 1);
155 | procedure CheckMonthMinute();
156 | public
157 | FItems: array of TZDateTimeProcessItem;
158 | constructor Create();
159 | destructor Destroy(); override;
160 | procedure DeleteRepeatedItems();
161 | function GetValidCount(): integer;
162 | function TryToParseDateFormat(const AFmtStr: string; const AFmtParser: TNumFormatParser = nil): integer;
163 | property Count: integer read FCount;
164 | end;
165 |
166 | // Number format item for write
167 | TODSNumberFormatMapItem = class
168 | private
169 | FCondition: string;
170 | FisCondition: boolean;
171 | FColorStr: string;
172 | FisColor: boolean;
173 | FNumberFormat: string;
174 | FConditionsArray: array [0 .. 1] of array [0 .. 1] of string;
175 | FConditionsCount: integer;
176 | FEmbededTextCount: integer;
177 | FEmbededMaxCount: integer;
178 | FEmbededTextArray: array of TODSEmbeded_text_props;
179 | FNumberFormatParser: TNumFormatParser;
180 | FDateTimeODSFormatParser: TZDateTimeODSFormatParser;
181 | protected
182 | procedure PrepareCommonStyleAttributes(const Xml: TZsspXMLWriterH; const AStyleName: string;
183 | isVolatile: boolean = false);
184 | public
185 | constructor Create();
186 | destructor Destroy(); override;
187 | procedure Clear();
188 | function TryToParse(const FNStr: string): boolean;
189 | // Add condition for this number format (max 2)
190 | // INPUT
191 | // const ACondition: string
192 | // const AStyleName: string
193 | function AddCondition(const ACondition, AStyleName: string): boolean;
194 |
195 | // Write number style item ( )
196 | // INPUT
197 | // const xml: TZsspXMLWriterH - xml
198 | // const AStyleName: string - style name
199 | // const NumProperties: integer - additional number properties (currency/percentage etc)
200 | // isVolatile: boolean - is volatile?
201 | procedure WriteNumberStyle(const Xml: TZsspXMLWriterH; const AStyleName: string; const NumProperties: integer;
202 | isVolatile: boolean = false);
203 |
204 | // Write number text style item ( )
205 | // INPUT
206 | // const xml: TZsspXMLWriterH - xml
207 | // const AStyleName: string - style name
208 | // isVolatile: boolean - is volatile? (for now - ignore)
209 | procedure WriteTextStyle(const Xml: TZsspXMLWriterH; const AStyleName: string; isVolatile: boolean = false);
210 |
211 | // Write datetime style item ( )
212 | // INPUT
213 | // const xml: TZsspXMLWriterH - xml
214 | // const AStyleName: string - style name
215 | // isVolatile: boolean - is volatile? (for now - ignore)
216 | // RETURN
217 | // integer - additional properties for datetime style
218 | function WriteDateTimeStyle(const Xml: TZsspXMLWriterH; const AStyleName: string;
219 | isVolatile: boolean = false): integer;
220 |
221 | property Condition: string read FCondition write FCondition;
222 | property isCondition: boolean read FisCondition write FisCondition;
223 | property ColorStr: string read FColorStr write FColorStr;
224 | property isColor: boolean read FisColor write FisColor;
225 | property NumberFormat: string read FNumberFormat write FNumberFormat;
226 | end;
227 |
228 | // Reads and stores number formats for ODS
229 | TZEODSNumberFormatReader = class
230 | private
231 | FItems: array of array [0 .. 1] of string; // index 0 - format num
232 | // index 1 - format
233 | FItemsOptions: array of TZODSNumberItemOptions;
234 | FCount: integer;
235 | FCountMax: integer;
236 |
237 | FEmbededTextCount: integer;
238 | FEmbededMaxCount: integer;
239 | FEmbededTextArray: array of TODSEmbeded_text_props;
240 | procedure AddEmbededText(const AText: string; ANumberPosition: integer);
241 | protected
242 | procedure AddItem();
243 | procedure ReadNumberFormatCommon(const Xml: TZsspXMLReaderH; const NumberFormatTag: string;
244 | sub_number_type: integer);
245 | function BeginReadFormat(const Xml: TZsspXMLReaderH; out retStartString: string; const NumFormat: integer): integer;
246 | public
247 | constructor Create();
248 | destructor Destroy(); override;
249 | procedure ReadKnownNumberFormat(const Xml: TZsspXMLReaderH);
250 | procedure ReadDateFormat(const Xml: TZsspXMLReaderH; const ATagName: string);
251 | procedure ReadNumberFormat(const Xml: TZsspXMLReaderH);
252 | procedure ReadCurrencyFormat(const Xml: TZsspXMLReaderH);
253 | procedure ReadPercentageFormat(const Xml: TZsspXMLReaderH);
254 | procedure ReadStringFormat(const Xml: TZsspXMLReaderH);
255 | function TryGetFormatStrByNum(const DataStyleName: string; out retFormatStr: string): boolean;
256 | property Count: integer read FCount;
257 | end;
258 |
259 | TZEODSNumberFormatWriterItem = record
260 | StyleIndex: integer;
261 | NumberFormatName: string;
262 | NumberFormat: string;
263 | end;
264 |
265 | // Writes to ODS number formats and stores number formats names
266 | TZEODSNumberFormatWriter = class
267 | private
268 | FItems: array of TZEODSNumberFormatWriterItem;
269 | FCount: integer;
270 | FCountMax: integer;
271 | FCurrentNFIndex: integer;
272 |
273 | FNFItems: array of TODSNumberFormatMapItem;
274 | FNFItemsCount: integer;
275 | // Additional properties for number formats (currency, percentage etc)
276 | FNumberAdditionalProps: array of integer;
277 |
278 | protected
279 | function TryAddNFItem(const NFStr: string): boolean;
280 | function SeparateNFItems(const NFStr: string): integer;
281 | public
282 | constructor Create(const AMaxCount: integer);
283 | destructor Destroy(); override;
284 | function TryGetNumberFormatName(StyleID: integer; out NumberFormatName: string): boolean;
285 | // Try to find additional properties for number format
286 | // INPUT
287 | // StyleID: integer - style ID
288 | // out NumberFormatProp: integer - finded number additional properties
289 | // RETURN
290 | // boolean - true - additional properties is found
291 | function TryGetNumberFormatAddProp(StyleID: integer; out NumberFormatProp: integer): boolean;
292 | // Try to write number format to xml
293 | // INPUT
294 | // const xml: TZsspXMLWriterH - xml
295 | // StyleID: integer - Style ID
296 | // ANumberFormat: string - number format
297 | // RETURN
298 | // boolean - true - NumberFormat was written ok
299 | function TryWriteNumberFormat(const Xml: TZsspXMLWriterH; StyleID: integer; ANumberFormat: string): boolean;
300 | property Count: integer read FCount;
301 | end;
302 |
303 | // Try to get xlsx number format type by string (very simplistic)
304 | // INPUT
305 | // const FormatStr: string - format ("YYYY.MM.DD" etc)
306 | // RETURN
307 | // integer - 0 - unknown
308 | // 1 and 1 = 1 - number
309 | // 2 and 2 = 2 - datetime
310 | // 4 and 4 = 4 - string
311 | function GetXlsxNumberFormatType(const FormatStr: string): integer;
312 |
313 | // Try to get native number format type by string (very simplistic)
314 | // INPUT
315 | // const FormatStr: string - format ("YYYY.MM.DD" etc)
316 | // RETURN
317 | // integer - 0 - unknown
318 | // 1 and 1 = 1 - number
319 | // 2 and 2 = 2 - datetime
320 | // 4 and 4 = 4 - string
321 | function GetNativeNumberFormatType(const FormatStr: string): integer;
322 |
323 | // Convert native number format to xlsx
324 | // INPUT
325 | // const FormatNative: string - number format
326 | // const AFmtParser: TNumFormatParser - format parser (not NIL!)
327 | // const ADateParser: TZDateTimeODSFormatParser - date parser (not NIL!)
328 | // RETURN
329 | // string - number format fox xlsx and excel 2003 xml
330 | function ConvertFormatNativeToXlsx(const FormatNative: string; const AFmtParser: TNumFormatParser;
331 | const ADateParser: TZDateTimeODSFormatParser): string; overload;
332 |
333 | // Convert native number format to xlsx
334 | // INPUT
335 | // const FormatNative: string - number format
336 | // RETURN
337 | // string - number format fox xlsx and excel 2003 xml
338 | function ConvertFormatNativeToXlsx(const FormatNative: string): string; overload;
339 | function ConvertFormatXlsxToNative(const FormatXlsx: string): string;
340 | function TryXlsxTimeToDateTime(const XlsxDateTime: string; out retDateTime: TDateTime; is1904: boolean = false)
341 | : boolean;
342 |
343 | implementation
344 |
345 | uses
346 | Excel4Delphi.Common, System.StrUtils;
347 |
348 | const
349 | ZE_NUMBER_FORMAT_DECIMAL_SEPARATOR = '.';
350 | ZE_MAX_NF_ITEMS_COUNT = 3;
351 | ZE_MAP_CONDITIONAL_COLORS_COUNT = 8;
352 |
353 | ZE_MAP_CONDITIONAL_COLORS: array [0 .. ZE_MAP_CONDITIONAL_COLORS_COUNT - 1] of array [0 .. 1]
354 | of string = (('#000000', 'BLACK'), ('#FFFFFF', 'WHITE'), ('#FF0000', 'RED'), ('#00FF00', 'GREEN'),
355 | ('#0000FF', 'BLUE'), ('#FF00FF', 'MAGENTA'), ('#00FFFF', 'CYAN'), ('#FFFF00', 'YELLOW'));
356 |
357 | ZE_VALID_CONDITIONS_STR: TArray = ['>', '<', '>=', '<=', '='];
358 |
359 | ZE_VALID_NAMED_FORMATS_COUNT = 15;
360 |
361 | ZE_VALID_NAMED_FORMATS: array [0 .. ZE_VALID_NAMED_FORMATS_COUNT - 1] of array [0 .. 1] of string = (('GENERAL', ''),
362 | ('FIXED', '0.00'), ('CURRENCY', '0.00'), ('STANDARD', ''), ('PERCENT', '0.00%'), ('SCIENTIFIC', '0,00E+00'),
363 | ('GENERAL DATE', 'DD.MM.YYYY'), ('DATE', 'DD.MM.YYYY'), ('LONG DATE', 'DD.MM.YYYY'), ('MEDIUM DATE', 'DD-MMM-YY'),
364 | ('SHORT DATE', 'DD.MM.YY'), ('LONG TIME', 'HH:MM:SS'), ('MEDIUM TIME', 'HH:MM AM/PM'), ('SHORT TIME', 'HH:MM'),
365 | ('TIME', 'HH:MM'));
366 |
367 | ZE_DATETIME_ITEM_ERROR = -1;
368 | ZE_DATETIME_ITEM_TEXT = 0;
369 | ZE_DATETIME_ITEM_YEAR = 1;
370 | ZE_DATETIME_ITEM_MONTH = 2;
371 | ZE_DATETIME_ITEM_DAY = 3;
372 | ZE_DATETIME_ITEM_HOUR = 4;
373 | ZE_DATETIME_ITEM_MINUTE = 5;
374 | ZE_DATETIME_ITEM_SECOND = 6;
375 | ZE_DATETIME_ITEM_WEEK = 7;
376 | ZE_DATETIME_ITEM_QUARTER = 8;
377 | ZE_DATETIME_ITEM_ERA_JAP = 9;
378 | ZE_DATETIME_ITEM_ERA_YEAR = 10;
379 | ZE_DATETIME_ITEM_AMPM = 11;
380 |
381 | ZE_DATETIME_AMPM_SHORT_LOW = 0;
382 | ZE_DATETIME_AMPM_SHORT_UP = 1;
383 | ZE_DATETIME_AMPM_LONG_LOW = 2;
384 | ZE_DATETIME_AMPM_LONG_UP = 3;
385 |
386 | {
387 |
388 | LO:
389 |
390 | M Month as 3.
391 | MM Month as 03.
392 | MMM Month as Jan-Dec
393 | MMMM Month as January-December MMMM
394 | MMMMM First letter of Name of Month MMMMM
395 | D Day as 2 D
396 | DD Day as 02 DD
397 | NN or DDD Day as Sun-Sat
398 | NNN or DDDD Day as Sunday to Saturday
399 | NNNN Day followed by comma, as in "Sunday," NNNN
400 | YY Year as 00-99 YY
401 | YYYY Year as 1900-2078 YYYY
402 | WW Calendar week
403 | Q Quarterly as Q1 to Q4 Q
404 | QQ Quarterly as 1st quarter to 4th quarter QQ
405 | G Era on the Japanese Gengou calendar, single character (possible values are: M, T, S, H)
406 | GG Era, abbreviation
407 | GGG Era, full name
408 | E Number of the year within an era, without a leading zero for single-digit years
409 | EE or R Number of the year within an era, with a leading zero for single-digit years
410 | RR or GGGEE Era, full name and year
411 |
412 | h Hours as 0-23 h
413 | hh Hours as 00-23
414 | m Minutes as 0-59
415 | mm Minutes as 00-59
416 | s Seconds as 0-59
417 | ss Seconds as 00-59
418 |
419 | [~buddhist] Thai Buddhist Calendar
420 | [~gengou] Japanese Gengou Calendar
421 | [~gregorian] Gregorian Calendar
422 | [~hanja] Korean Calendar
423 | [~hanja_yoil] Korean Calendar
424 | [~hijri] Arabic Islamic Calendar, currently supported for the following locales: ar_EG, ar_LB, ar_SA, and ar_TN
425 | [~jewish] Jewish Calendar
426 | [~ROC] Republic Of China Calendar
427 |
428 | m$
429 | m Displays the month as a number without a leading zero.
430 | mm Displays the month as a number with a leading zero when appropriate.
431 | mmm Displays the month as an abbreviation (Jan to Dec).
432 | mmmm Displays the month as a full name (January to December).
433 | mmmmm Displays the month as a single letter (J to D).
434 | d Displays the day as a number without a leading zero.
435 | dd Displays the day as a number with a leading zero when appropriate.
436 | ddd Displays the day as an abbreviation (Sun to Sat).
437 | dddd Displays the day as a full name (Sunday to Saturday).
438 | yy Displays the year as a two-digit number.
439 | yyyy Displays the year as a four-digit number.
440 |
441 |
442 |
443 | h Displays the hour as a number without a leading zero.
444 | [h] Displays elapsed time in hours. If you are working with a formula that returns a time in which the number of hours exceeds 24, use a number format that resembles [h]:mm:ss.
445 | hh Displays the hour as a number with a leading zero when appropriate. If the format contains AM or PM, the hour is based on the 12-hour clock. Otherwise, the hour is based on the 24-hour clock.
446 | m Displays the minute as a number without a leading zero.
447 | [m] Displays elapsed time in minutes. If you are working with a formula that returns a time in which the number of minutes exceeds 60, use a number format that resembles [mm]:ss.
448 | mm Displays the minute as a number with a leading zero when appropriate.
449 | Note The m or mm code must appear immediately after the h or hh code or immediately before the ss code; otherwise, Excel displays the month instead of minutes.
450 | s Displays the second as a number without a leading zero.
451 | [s] Displays elapsed time in seconds. If you are working with a formula that returns a time in which the number of seconds exceeds 60, use a number format that resembles [ss].
452 | ss Displays the second as a number with a leading zero when appropriate. If you want to display fractions of a second, use a number format that resembles h:mm:ss.00.
453 |
454 | AM/PM, am/pm, A/P, a/p Displays the hour using a 12-hour clock. Excel displays AM, am, A, or a for times from midnight unt
455 |
456 | }
457 |
458 | // Return true if in string AStr after position AStartPos have one of symbols SymbolsArr
459 | // This function checks quotas and brackets. If desired symbol between the quotas - function return FALSE.
460 | // INPUT
461 | // AStartPos: integer - start position
462 | // ALen: integer - string length
463 | // out retPos: integer - returned position of symbol
464 | // const AStr: string - string
465 | // const SymbolsArr: array of string - searching symbols
466 | // RETURN
467 | // boolean - true - one of symbols was found in string after AStartPos (and not between quotas)
468 | function IsHaveSymbolsAfterPosQuotas(AStartPos: integer; ALen: integer; out retPos: integer; const AStr: string;
469 | const SymbolsArr: array of string): boolean; overload;
470 | var
471 | i, j: integer;
472 | _IsQuote: boolean;
473 | _IsBracket: boolean;
474 | ch: char;
475 | _max, _min: integer;
476 |
477 | begin
478 | Result := false;
479 | _IsQuote := false;
480 | _IsBracket := false;
481 | retPos := -1;
482 | _min := Low(SymbolsArr);
483 | _max := High(SymbolsArr);
484 | i := AStartPos + 1;
485 | while (i <= ALen) do
486 | begin
487 | ch := AStr[i];
488 |
489 | if (not _IsQuote) then
490 | begin
491 | case (ch) of
492 | '[':
493 | _IsBracket := true;
494 | ']':
495 | _IsBracket := false;
496 | '\':
497 | begin
498 | inc(i, 2);
499 | if (i > ALen) then
500 | break;
501 | ch := AStr[i];
502 | end;
503 | end;
504 | end;
505 |
506 | if ((not _IsBracket) and (ch = '"')) then
507 | _IsQuote := not _IsQuote;
508 |
509 | if ((not _IsQuote) and (not _IsBracket)) then
510 | for j := _min to _max do
511 | if (ch = SymbolsArr[j]) then
512 | begin
513 | retPos := i;
514 | Result := true;
515 | exit;
516 | end;
517 | inc(i);
518 | end; // while i
519 | end; // IsHaveSymbolsAfterPosQuotas
520 |
521 | // Return true if in string AStr after position AStartPos have one of symbols SymbolsArr
522 | // This function checks quotas and brackets. If desired symbol between the quotas - function return FALSE.
523 | // INPUT
524 | // AStartPos: integer - start position
525 | // ALen: integer - string length
526 | // const AStr: string - string
527 | // const SymbolsArr: array of string - searching symbols
528 | // RETURN
529 | // boolean - true - one of symbols was found in string after AStartPos (and not between quotas)
530 | function IsHaveSymbolsAfterPosQuotas(AStartPos: integer; ALen: integer; const AStr: string;
531 | const SymbolsArr: array of string): boolean; overload;
532 | var
533 | retPos: integer;
534 | begin
535 | Result := IsHaveSymbolsAfterPosQuotas(AStartPos, ALen, retPos, AStr, SymbolsArr);
536 | end; // IsHaveSymbolsAfterPosQuotas
537 |
538 | // Try to get xlsx number format type by string (very simplistic)
539 | // INPUT
540 | // const FormatStr: string - format ("YYYY.MM.DD" etc)
541 | // RETURN
542 | // integer - 0 - unknown
543 | // 1 and 1 = 1 - number
544 | // 2 and 2 = 2 - datetime
545 | // 4 and 4 = 4 - string
546 | function GetXlsxNumberFormatType(const FormatStr: string): integer;
547 | var
548 | i, l: integer;
549 | ch: char;
550 | _IsQuote: boolean;
551 | _IsBracket: boolean;
552 | _isFraction: boolean;
553 |
554 | begin
555 | Result := ZE_NUMFORMAT_IS_UNKNOWN;
556 | _isFraction := false;
557 |
558 | // General is not for dates
559 | if ((UpperCase(FormatStr) = 'GENERAL') or (FormatStr = '')) then
560 | exit(ZE_NUMFORMAT_IS_NUMBER);
561 |
562 | _IsQuote := false;
563 | _IsBracket := false;
564 |
565 | l := length(FormatStr);
566 | for i := 1 to l do
567 | begin
568 | ch := FormatStr[i];
569 |
570 | if ((ch = '"') and (not _IsBracket)) then
571 | _IsQuote := not _IsQuote;
572 |
573 | if ((ch = '[') and (not _IsQuote)) then
574 | _IsBracket := true;
575 |
576 | if ((ch = ']') and (not _IsQuote) and _IsBracket) then
577 | _IsBracket := false;
578 |
579 | // [$RUB] / [$UAH] etc
580 | // TODO: need check for valid country code
581 | if (_IsBracket and (ch = '$')) then
582 | Result := Result or ZE_NUMFORMAT_NUM_IS_CURRENCY;
583 |
584 | if ((not _IsQuote) and (not _IsBracket)) then
585 | case (ch) of
586 | '0', '#', 'E', 'e', '%', '?':
587 | begin
588 | Result := ZE_NUMFORMAT_IS_NUMBER;
589 |
590 | if (IsHaveSymbolsAfterPosQuotas(i - 1, l, FormatStr, ['e', 'E'])) then
591 | Result := Result or ZE_NUMFORMAT_NUM_IS_SCIENTIFIC
592 | else if (IsHaveSymbolsAfterPosQuotas(i - 1, l, FormatStr, ['%'])) then
593 | Result := Result or ZE_NUMFORMAT_NUM_IS_PERCENTAGE
594 | else if (IsHaveSymbolsAfterPosQuotas(i - 1, l, FormatStr, ['/']) or _isFraction) then
595 | Result := Result or ZE_NUMFORMAT_NUM_IS_FRACTION;
596 |
597 | exit;
598 | end;
599 | '@':
600 | begin
601 | Result := ZE_NUMFORMAT_IS_STRING;
602 | exit;
603 | end;
604 | '/':
605 | _isFraction := true;
606 | 'H', 'h', 'S', 's', 'm', 'M', 'd', 'D', 'Y', 'y', ':':
607 | begin
608 | Result := ZE_NUMFORMAT_IS_DATETIME;
609 | exit;
610 | end;
611 | end;
612 | end; // for i
613 | end; // GetXlsxNumberFormatType
614 |
615 | // Try to get native number format type by string (very simplistic)
616 | // INPUT
617 | // const FormatStr: string - format ("YYYY.MM.DD" etc)
618 | // RETURN
619 | // integer - 0 - unknown
620 | // 1 and 1 = 1 - number
621 | // 2 and 2 = 2 - datetime
622 | // 4 and 4 = 4 - string
623 | function GetNativeNumberFormatType(const FormatStr: string): integer;
624 | var
625 | i, l: integer;
626 | ch, _prev: char;
627 | _IsQuote: boolean;
628 | _IsBracket: boolean;
629 | _isSemicolon: boolean;
630 | t: integer;
631 |
632 | function _CheckSemicolon(): boolean;
633 | begin
634 | if not _isSemicolon then
635 | begin
636 | t := i - 1;
637 | _isSemicolon := IsHaveSymbolsAfterPosQuotas(t, l, i, FormatStr, [';']);
638 | end;
639 |
640 | Result := not _isSemicolon;
641 | end;
642 |
643 | begin
644 | Result := ZE_NUMFORMAT_IS_UNKNOWN;
645 |
646 | _isSemicolon := false;
647 | _IsBracket := false;
648 | _IsQuote := false;
649 | _prev := #0;
650 |
651 | l := length(FormatStr);
652 | i := 1;
653 | while (i <= l) do
654 | begin
655 | ch := FormatStr[i];
656 |
657 | if ((ch = '"') and (not _IsBracket)) then
658 | _IsQuote := not _IsQuote;
659 |
660 | if ((ch = '[') and (not _IsQuote)) then
661 | _IsBracket := true;
662 |
663 | if ((ch = ']') and (not _IsQuote) and _IsBracket) then
664 | _IsBracket := false;
665 |
666 | // [$RUB] / [$UAH] etc
667 | // TODO: need check for valid country code
668 | if (_IsBracket and (ch = '$')) then
669 | Result := Result or ZE_NUMFORMAT_NUM_IS_CURRENCY;
670 |
671 | if ((not _IsQuote) and (not _IsBracket)) then
672 | case (ch) of
673 | '0', '#', '?':
674 | begin
675 | if (_isSemicolon) then
676 | Result := Result or ZE_NUMFORMAT_IS_NUMBER
677 | else
678 | Result := ZE_NUMFORMAT_IS_NUMBER;
679 |
680 | if (IsHaveSymbolsAfterPosQuotas(i - 1, l, t, FormatStr, ['e', 'E'])) then
681 | begin
682 | Result := Result or ZE_NUMFORMAT_NUM_IS_SCIENTIFIC;
683 | i := t;
684 | end
685 | else if (IsHaveSymbolsAfterPosQuotas(i - 1, l, t, FormatStr, ['%'])) then
686 | begin
687 | Result := Result or ZE_NUMFORMAT_NUM_IS_PERCENTAGE;
688 | i := t;
689 | end
690 | else if (IsHaveSymbolsAfterPosQuotas(i - 1, l, t, FormatStr, ['/'])) then
691 | begin
692 | Result := Result or ZE_NUMFORMAT_NUM_IS_FRACTION;
693 | i := t;
694 | end;
695 |
696 | if (_CheckSemicolon()) then
697 | exit;
698 | end;
699 | '%':
700 | begin
701 | if (_isSemicolon) then
702 | Result := Result or ZE_NUMFORMAT_IS_NUMBER or ZE_NUMFORMAT_NUM_IS_PERCENTAGE
703 | else
704 | Result := ZE_NUMFORMAT_IS_NUMBER or ZE_NUMFORMAT_NUM_IS_PERCENTAGE;
705 |
706 | if (_CheckSemicolon()) then
707 | exit;
708 | end;
709 | 'E', 'e':
710 | begin
711 | if ((_prev = '0') or (_prev = '#')) then
712 | Result := ZE_NUMFORMAT_IS_NUMBER or ZE_NUMFORMAT_NUM_IS_SCIENTIFIC
713 | else
714 | Result := ZE_NUMFORMAT_IS_DATETIME;
715 | exit;
716 | end;
717 | '@':
718 | begin
719 | Result := ZE_NUMFORMAT_IS_STRING;
720 | exit;
721 | end;
722 | 'H', 'h', 'S', 's', 'm', 'M', 'd', 'D', 'Y', 'y', ':', 'G', 'Q', 'R', 'W', 'N':
723 | begin
724 | Result := ZE_NUMFORMAT_IS_DATETIME;
725 | exit;
726 | end;
727 | ';':
728 | _isSemicolon := true;
729 | end;
730 | _prev := ch;
731 | inc(i);
732 | end; // while i
733 | end; // GetNativeNumberFormatType
734 |
735 | // Convert native number format to xlsx
736 | // INPUT
737 | // const FormatNative: string - number format
738 | // const AFmtParser: TNumFormatParser - format parser (not NIL!)
739 | // const ADateParser: TZDateTimeODSFormatParser - date parser (not NIL!)
740 | // RETURN
741 | // string - number format fox xlsx and excel 2003 xml
742 | function ConvertFormatNativeToXlsx(const FormatNative: string; const AFmtParser: TNumFormatParser;
743 | const ADateParser: TZDateTimeODSFormatParser): string; overload;
744 | var
745 | _FmtParser: TNumFormatParser;
746 | _DateParser: TZDateTimeODSFormatParser;
747 | _delP: boolean;
748 | _delD: boolean;
749 | _fmt: integer;
750 | _len: integer;
751 |
752 | function _AddText(const AStr: string): string;
753 | begin
754 | Result := '';
755 | _len := length(AStr);
756 | if (_len = 1) then
757 | case (AStr[1]) of
758 | ' ', '.', ',', ':', '-', '+', '/':
759 | Result := AStr;
760 | else
761 | Result := '\' + AStr;
762 | end // case
763 | else
764 | Result := '"' + AStr + '"';
765 | end; // _AddText
766 |
767 | function _RepeatSymbol(const ASymbol: string; ALen: integer; AMin, AMax: integer): string;
768 | var
769 | i: integer;
770 | begin
771 | Result := '';
772 | if (ALen < AMin) then
773 | ALen := AMin;
774 | if (ALen > AMax) then
775 | ALen := AMax;
776 | for i := 1 to ALen do
777 | Result := Result + ASymbol;
778 | end; // _RepeatSymbol
779 |
780 | function _AddMonth(var item: TZDateTimeProcessItem): string;
781 | begin
782 | Result := _RepeatSymbol('M', item.Len, 1, 5);
783 | end; // _AddMonth
784 |
785 | function _AddYear(var item: TZDateTimeProcessItem): string;
786 | begin
787 | if (item.Len <= 2) then
788 | Result := 'YY'
789 | else
790 | Result := 'YYYY';
791 | end; // _AddYear
792 |
793 | function _AddDay(var item: TZDateTimeProcessItem): string;
794 | begin
795 | Result := _RepeatSymbol('D', item.Len, 1, 4);
796 | end; // _AddDay
797 |
798 | function _AddHour(var item: TZDateTimeProcessItem): string;
799 | begin
800 | Result := _RepeatSymbol('H', item.Len, 1, 2);
801 | end; // _AddHour
802 |
803 | function _AddMinute(var item: TZDateTimeProcessItem): string;
804 | begin
805 | Result := _RepeatSymbol('M', item.Len, 1, 2);
806 | end; // _AddMinute
807 |
808 | function _AddSecond(var item: TZDateTimeProcessItem): string;
809 | begin
810 | Result := _RepeatSymbol('S', item.Len, 1, 2);
811 |
812 | if (item.Settings > 0) then
813 | Result := Result + '.' + _RepeatSymbol('0', item.Settings, 1, item.Settings);
814 | end; // _AddSecond
815 |
816 | function _AddAMPM(var item: TZDateTimeProcessItem): string;
817 | begin
818 | case (item.Settings) of
819 | ZE_DATETIME_AMPM_SHORT_LOW:
820 | Result := 'a/p';
821 | ZE_DATETIME_AMPM_SHORT_UP:
822 | Result := 'A/P';
823 | ZE_DATETIME_AMPM_LONG_LOW:
824 | Result := 'am/pm';
825 | else
826 | Result := 'AM/PM';
827 | end;
828 | end; // _AddAMPM
829 |
830 | function _GetXlsxDateFormat(): string;
831 | var
832 | i: integer;
833 | begin
834 | Result := '';
835 | for i := 0 to _DateParser.Count - 1 do
836 | case (_DateParser.FItems[i].ItemType) of
837 | ZE_DATETIME_ITEM_TEXT:
838 | Result := Result + _AddText(_DateParser.FItems[i].TextValue);
839 | ZE_DATETIME_ITEM_YEAR:
840 | Result := Result + _AddYear(_DateParser.FItems[i]);
841 | ZE_DATETIME_ITEM_MONTH:
842 | Result := Result + _AddMonth(_DateParser.FItems[i]);
843 | ZE_DATETIME_ITEM_DAY:
844 | Result := Result + _AddDay(_DateParser.FItems[i]);
845 | ZE_DATETIME_ITEM_HOUR:
846 | Result := Result + _AddHour(_DateParser.FItems[i]);
847 | ZE_DATETIME_ITEM_MINUTE:
848 | Result := Result + _AddMinute(_DateParser.FItems[i]);
849 | ZE_DATETIME_ITEM_SECOND:
850 | Result := Result + _AddSecond(_DateParser.FItems[i]);
851 | ZE_DATETIME_ITEM_WEEK:
852 | ; // ??
853 | ZE_DATETIME_ITEM_QUARTER:
854 | ; // ??
855 | ZE_DATETIME_ITEM_ERA_JAP:
856 | ; // ??
857 | ZE_DATETIME_ITEM_ERA_YEAR:
858 | ; // ??
859 | ZE_DATETIME_ITEM_AMPM:
860 | Result := Result + _AddAMPM(_DateParser.FItems[i]);
861 | end; // case
862 |
863 | end; // _GetXlsxDateFormat
864 |
865 | begin
866 | _fmt := GetNativeNumberFormatType(FormatNative);
867 |
868 | // For now difference only for datetime
869 | if (_fmt and ZE_NUMFORMAT_IS_DATETIME = ZE_NUMFORMAT_IS_DATETIME) then
870 | begin
871 | Result := '';
872 | _FmtParser := AFmtParser;
873 | _DateParser := ADateParser;
874 | _delP := AFmtParser = Nil;
875 | _delD := ADateParser = Nil;
876 | try
877 | if (_delD) then
878 | _DateParser := TZDateTimeODSFormatParser.Create();
879 | if (_delP) then
880 | _FmtParser := TNumFormatParser.Create();
881 |
882 | if (_DateParser.TryToParseDateFormat(FormatNative, _FmtParser) > 0) then
883 | Result := _GetXlsxDateFormat();
884 |
885 | finally
886 | if (_delP) then
887 | FreeAndNil(_FmtParser);
888 | if (_delD) then
889 | FreeAndNil(_DateParser);
890 | end;
891 | end
892 | else
893 | Result := FormatNative;
894 | end; // ConvertFormatNativeToXlsx
895 |
896 | function ConvertFormatNativeToXlsx(const FormatNative: string): string; overload;
897 | begin
898 | Result := ConvertFormatNativeToXlsx(FormatNative, nil, nil);
899 | end; // ConvertFormatNativeToXlsx
900 |
901 | function ConvertFormatXlsxToNative(const FormatXlsx: string): string;
902 | var
903 | i, l: integer;
904 | _IsQuote: boolean;
905 | _IsBracket: boolean;
906 | s: string;
907 | ch: char;
908 | _semicolonCount: integer;
909 | _prevCh: char;
910 | _strDateList: string;
911 | z: string;
912 | b: boolean;
913 | _isSlash: boolean;
914 | t: integer;
915 |
916 | procedure _AddToResult(const strItem: string; charDate: char);
917 | begin
918 | Result := Result + strItem;
919 | _strDateList := _strDateList + charDate;
920 | s := '';
921 | end;
922 |
923 | procedure _CheckStringItem(currCh: char);
924 | begin
925 | z := UpperCase(s);
926 |
927 | if ((z = 'YY') or (z = 'YYYY')) then
928 | _AddToResult(z, 'Y')
929 | else if ((z = 'D') or (z = 'DD')) then
930 | _AddToResult(z, 'D')
931 | else if (z = 'DDD') then
932 | _AddToResult('NN', 'D')
933 | else if (z = 'DDDD') then
934 | _AddToResult('NNN', 'D')
935 | else if (z = 'H') then
936 | _AddToResult('h', 'H')
937 | else if (z = 'HH') then
938 | _AddToResult('hh', 'H')
939 | else if (z = 'S') then
940 | _AddToResult('s', 'S')
941 | else if (z = 'SS') then
942 | _AddToResult('ss', 'S')
943 | else if ((z = 'MMM') or (z = 'MMMM') or (z = 'MMMMM')) then
944 | _AddToResult(z, 'M')
945 | else
946 | // Minute or Month?
947 | // If M/MM between 'H/S' - minutes
948 | if (z = 'M') or (z = 'MM') then
949 | begin
950 | // Is it minute?
951 | b := (_prevCh = ':') or (_prevCh = 'H') or (currCh = 'S') or (currCh = ':');
952 | if (not b) then
953 | begin
954 | t := length(_strDateList);
955 | // if some spaces (or some other symbols) between date symbols
956 | if (t > 0) then
957 | begin
958 | if (_strDateList[t] = 'H') or (_strDateList[t] = 'S') then
959 | b := true;
960 | if (not b) then
961 | // If previous date symbol was "month" then for now - "minute"
962 | b := pos('M', _strDateList) <> 0;
963 | end;
964 | end;
965 |
966 | // If previous date symbal was "minute" then for now - "month"
967 | if (b) then
968 | b := pos('N', _strDateList) = 0;
969 |
970 | // minutes
971 | if (b) then
972 | begin
973 | if (z = 'M') then
974 | _AddToResult('m', 'N')
975 | else
976 | _AddToResult('mm', 'N')
977 | end
978 | else
979 | _AddToResult(z, 'M'); // months
980 | end
981 | else
982 | Result := Result + s;
983 |
984 | _prevCh := currCh;
985 | s := '';
986 | end; // _CheckStringItem
987 |
988 | procedure _ProcessOpenBracket();
989 | begin
990 | if _IsQuote then
991 | s := s + ch
992 | else if not _IsBracket then
993 | begin
994 | _CheckStringItem(ch);
995 | _IsBracket := true;
996 | end;
997 | end; // _ProcessOpenBracket
998 |
999 | // [some data]
1000 | procedure _ProcessCloseBracket();
1001 | var
1002 | z: string;
1003 | begin
1004 | _IsBracket := not _IsBracket;
1005 |
1006 | z := UpperCase(s);
1007 | if (z = 'COLOR1') then
1008 | s := 'BLACK'
1009 | else if (z = 'COLOR2') then
1010 | s := 'WHITE'
1011 | else if (z = 'COLOR3') then
1012 | s := 'RED';
1013 | // TODO: need add all possible colorXX (1..64??)
1014 |
1015 | Result := Result + '[' + s + ']';
1016 | _prevCh := ch;
1017 | s := '';
1018 | end; // _ProcessCloseBracket
1019 |
1020 | procedure _ProcessQuote(addCloseQuote: boolean = true);
1021 | begin
1022 | if (addCloseQuote) then
1023 | _IsQuote := not _IsQuote;
1024 |
1025 | if (_IsQuote) then
1026 | begin
1027 | if (addCloseQuote) then
1028 | Result := Result + '"';
1029 |
1030 | Result := Result + s;
1031 |
1032 | if (addCloseQuote) then
1033 | Result := Result + '"';
1034 | end;
1035 |
1036 | s := '';
1037 | _prevCh := ch;
1038 | end; // _ProcessQuote
1039 |
1040 | procedure _ProcessSemicolon();
1041 | begin
1042 | inc(_semicolonCount);
1043 | _CheckStringItem(ch);
1044 | end; // _ProcessSemicolon
1045 |
1046 | procedure _ProcessDateTimeSymbol(DTSymbol: char);
1047 | begin
1048 | if (_prevCh = #0) then
1049 | _prevCh := DTSymbol;
1050 |
1051 | if (_prevCh <> DTSymbol) then
1052 | _CheckStringItem(DTSymbol);
1053 |
1054 | s := s + ch;
1055 | end;
1056 |
1057 | begin
1058 | Result := '';
1059 | _IsQuote := false;
1060 | _IsBracket := false;
1061 | s := '';
1062 | _semicolonCount := 0;
1063 | _prevCh := #0;
1064 |
1065 | _strDateList := '';
1066 |
1067 | l := length(FormatXlsx);
1068 |
1069 | _isSlash := false;
1070 |
1071 | for i := 1 to l do
1072 | begin
1073 | ch := FormatXlsx[i];
1074 |
1075 | if _isSlash then
1076 | begin
1077 | Result := Result + ch;
1078 | _isSlash := false;
1079 | end
1080 | else if (((_IsQuote and (ch <> '"')) or (_IsBracket and (ch <> ']')))) then
1081 | s := s + ch
1082 | else
1083 | case (ch) of
1084 | '[':
1085 | _ProcessOpenBracket();
1086 | ']':
1087 | _ProcessCloseBracket();
1088 | '"':
1089 | _ProcessQuote();
1090 | ';':
1091 | begin
1092 | // only 3 sections maximum available!
1093 | _ProcessSemicolon();
1094 | if (_semicolonCount >= 3) then
1095 | break;
1096 | Result := Result + ch;
1097 | end;
1098 | 'y', 'Y':
1099 | _ProcessDateTimeSymbol('Y');
1100 | 'm', 'M':
1101 | _ProcessDateTimeSymbol('M');
1102 | 'd', 'D':
1103 | _ProcessDateTimeSymbol('D');
1104 | 's', 'S':
1105 | _ProcessDateTimeSymbol('S');
1106 | 'h', 'H':
1107 | _ProcessDateTimeSymbol('H');
1108 | '\':
1109 | begin
1110 | _CheckStringItem(ch);
1111 | Result := Result + ch;
1112 | _isSlash := true;
1113 | end;
1114 | else
1115 | begin
1116 | _CheckStringItem(ch);
1117 | s := s + ch;
1118 | end;
1119 | end; // case ch
1120 | end; // for i
1121 |
1122 | _CheckStringItem(#0);
1123 | end; // TryConvertXlsxToNative
1124 |
1125 | // Try to convert xlsx datetime as number to DateTime
1126 | // INPUT
1127 | // const XlsxDateTime: string - datetime string from xlsx cell value
1128 | // out retDateTime: TDateTime - output datetime (no sense if function returns false!)
1129 | // is1904: boolean - if true than calc dates from 1904 and from 1900 otherwise
1130 | // RETURN
1131 | // boolean - true - ok
1132 | function TryXlsxTimeToDateTime(const XlsxDateTime: string; out retDateTime: TDateTime; is1904: boolean = false)
1133 | : boolean;
1134 | var
1135 | t: Double;
1136 | s1, s2: string;
1137 | i: integer;
1138 | b: boolean;
1139 | ch: char;
1140 | begin
1141 | b := false;
1142 | Result := false;
1143 | s1 := '';
1144 | s2 := '';
1145 |
1146 | for i := 1 to length(XlsxDateTime) do
1147 | begin
1148 | ch := XlsxDateTime[i];
1149 | if ((ch = '.') or (ch = ',')) then
1150 | begin
1151 | if (b) then
1152 | exit;
1153 | b := true;
1154 | end
1155 | else if (b) then
1156 | s2 := s2 + ch
1157 | else
1158 | s1 := s1 + ch;
1159 | end;
1160 |
1161 | if (s1 = '') then
1162 | s1 := '0';
1163 |
1164 | if (TryStrToInt(s1, i)) then
1165 | begin
1166 | retDateTime := i;
1167 | if (is1904) then
1168 | retDateTime := IncMonth(retDateTime, 12 * 4);
1169 |
1170 | if (s2 <> '') then
1171 | if (TryStrToFloat('0' + FormatSettings.DecimalSeparator + s2, t)) then
1172 | retDateTime := retDateTime + t;
1173 | Result := true;
1174 | end;
1175 | end; // TryXlsxTimeToDateTime
1176 |
1177 | function TryGetNumFormatByName(ANamedFormat: string; out retNumFormat: string): boolean;
1178 | var
1179 | i: integer;
1180 | begin
1181 | Result := false;
1182 | ANamedFormat := UpperCase(ANamedFormat);
1183 | for i := 0 to ZE_VALID_NAMED_FORMATS_COUNT - 1 do
1184 | if (ZE_VALID_NAMED_FORMATS[i][0] = ANamedFormat) then
1185 | begin
1186 | Result := true;
1187 | retNumFormat := ZE_VALID_NAMED_FORMATS[i][1];
1188 | break;
1189 | end;
1190 | end;
1191 |
1192 | function TryGetMapColorName(AColor: string; out retColorName: string): boolean;
1193 | var
1194 | i: integer;
1195 | begin
1196 | Result := false;
1197 | for i := 0 to ZE_MAP_CONDITIONAL_COLORS_COUNT - 1 do
1198 | if (ZE_MAP_CONDITIONAL_COLORS[i][0] = AColor) then
1199 | begin
1200 | Result := true;
1201 | retColorName := ZE_MAP_CONDITIONAL_COLORS[i][1];
1202 | break;
1203 | end;
1204 | end; // TryGetMapColor
1205 |
1206 | function TryGetMapColorColor(AColorName: string; out retColor: string): boolean;
1207 | var
1208 | i: integer;
1209 | begin
1210 | Result := false;
1211 | for i := 0 to ZE_MAP_CONDITIONAL_COLORS_COUNT - 1 do
1212 | if (ZE_MAP_CONDITIONAL_COLORS[i][1] = AColorName) then
1213 | begin
1214 | Result := true;
1215 | retColor := ZE_MAP_CONDITIONAL_COLORS[i][0];
1216 | break;
1217 | end;
1218 | end; // TryGetMapColorColor
1219 |
1220 | function TryGetMapCondition(AConditionStr: string; out retODSCondution: string): boolean;
1221 | var
1222 | i: integer;
1223 | s: string;
1224 | a: array [0 .. 3] of string;
1225 | kol: integer;
1226 | ch: char;
1227 | _isNumber: boolean;
1228 | _isCond: boolean;
1229 |
1230 | procedure _AddItem();
1231 | begin
1232 | if (kol < 3) then
1233 | begin
1234 | a[kol] := s;
1235 | s := '';
1236 | inc(kol);
1237 | end;
1238 | end; // _AddItem
1239 |
1240 | procedure _ProcessSymbol(var isPrevTypeSymbol: boolean; var newTypeSymbol: boolean);
1241 | begin
1242 | if (isPrevTypeSymbol and (s <> '')) then
1243 | _AddItem();
1244 |
1245 | s := s + ch;
1246 |
1247 | isPrevTypeSymbol := false;
1248 | newTypeSymbol := true;
1249 | end; // _ProcessSymbol
1250 |
1251 | function _CheckCondition(): boolean;
1252 | var
1253 | i: integer;
1254 | d: Double;
1255 | begin
1256 | Result := false;
1257 | for i := Low(ZE_VALID_CONDITIONS_STR) to High(ZE_VALID_CONDITIONS_STR) do
1258 | if (a[0] = ZE_VALID_CONDITIONS_STR[i]) then
1259 | begin
1260 | Result := true;
1261 | break;
1262 | end;
1263 |
1264 | if (Result) then
1265 | Result := ZEIsTryStrToFloat(a[1], d);
1266 |
1267 | if (Result) then
1268 | retODSCondution := 'value()' + a[0] + a[1];
1269 | end; // _CheckCondition
1270 |
1271 | begin
1272 | Result := false;
1273 | retODSCondution := '';
1274 | kol := 0;
1275 | _isNumber := false;
1276 | _isCond := false;
1277 |
1278 | for i := 1 to length(AConditionStr) do
1279 | begin
1280 | ch := AConditionStr[i];
1281 | case (ch) of
1282 | '0' .. '9', '.', ',':
1283 | begin
1284 | if (ch = ',') then
1285 | ch := '.';
1286 | _ProcessSymbol(_isCond, _isNumber);
1287 | end;
1288 | '>', '<', '=':
1289 | _ProcessSymbol(_isNumber, _isCond);
1290 | ' ':
1291 | if (s <> '') then
1292 | _AddItem();
1293 | end;
1294 | end; // for i
1295 |
1296 | if (s <> '') then
1297 | _AddItem();
1298 |
1299 | if (kol >= 2) then
1300 | Result := _CheckCondition();
1301 | end; // TryGetMapCondition
1302 |
1303 | /// /::::::::::::: TZDateTimeODSFormatParser :::::::::::::::::////
1304 |
1305 | constructor TZDateTimeODSFormatParser.Create();
1306 | begin
1307 | FCount := 0;
1308 | FMaxCount := 16;
1309 | SetLength(FItems, FMaxCount);
1310 | end;
1311 |
1312 | destructor TZDateTimeODSFormatParser.Destroy();
1313 | begin
1314 | SetLength(FItems, 0);
1315 | inherited Destroy;
1316 | end;
1317 |
1318 | procedure TZDateTimeODSFormatParser.IncCount(ADelta: integer = 1);
1319 | begin
1320 | if (ADelta > 0) then
1321 | begin
1322 | inc(FCount, ADelta);
1323 | if (FCount >= FMaxCount) then
1324 | begin
1325 | FMaxCount := FCount + 10;
1326 | SetLength(FItems, FMaxCount);
1327 | end;
1328 | end;
1329 | end; // IncCount
1330 |
1331 | procedure TZDateTimeODSFormatParser.CheckMonthMinute();
1332 | var
1333 | i: integer;
1334 | _left, _right: boolean;
1335 | // Return FALSE if date and TRUE if time
1336 | function _CheckNeighbors(ADateType: integer): boolean;
1337 | begin
1338 | Result := false;
1339 | case (ADateType) of
1340 | (*
1341 | ZE_DATETIME_ITEM_DAY,
1342 | ZE_DATETIME_ITEM_YEAR,
1343 | ZE_DATETIME_ITEM_WEEK,
1344 | ZE_DATETIME_ITEM_MONTH,
1345 | ZE_DATETIME_ITEM_QUARTER:
1346 | Result := false;
1347 | *)
1348 | ZE_DATETIME_ITEM_AMPM, ZE_DATETIME_ITEM_HOUR, ZE_DATETIME_ITEM_SECOND:
1349 | Result := true;
1350 | end;
1351 | end; // _CheckNeighbors
1352 |
1353 | procedure _TryToCheckMonth(AIndex: integer);
1354 | var
1355 | i: integer;
1356 | begin
1357 | _right := false;
1358 | _left := false;
1359 | FItems[AIndex].Settings := 0;
1360 |
1361 | for i := AIndex - 1 downto 0 do
1362 | if (FItems[i].ItemType > 0) then
1363 | begin
1364 | _left := _CheckNeighbors(FItems[i].ItemType);
1365 | break;
1366 | end;
1367 |
1368 | for i := AIndex + 1 to FCount - 1 do
1369 | if (FItems[i].ItemType > 0) then
1370 | begin
1371 | _right := _CheckNeighbors(FItems[i].ItemType);
1372 | break;
1373 | end;
1374 |
1375 | if (_left or _right) then
1376 | FItems[AIndex].ItemType := ZE_DATETIME_ITEM_MINUTE;
1377 | end; // _TryToCheckMonth
1378 |
1379 | begin
1380 | for i := 0 to FCount - 1 do
1381 | if ((FItems[i].ItemType = ZE_DATETIME_ITEM_MONTH) and (FItems[i].Settings = 1)) then
1382 | _TryToCheckMonth(i);
1383 | end; // CheckMonthMinute
1384 |
1385 | function TZDateTimeODSFormatParser.TryToParseDateFormat(const AFmtStr: string;
1386 | const AFmtParser: TNumFormatParser): integer;
1387 | var
1388 | _parser: TNumFormatParser;
1389 | _isFree: boolean;
1390 | s: string;
1391 | _ch, _prevCh: char;
1392 | _tmp: string;
1393 | _len: integer;
1394 | t: integer;
1395 | _pos: integer;
1396 |
1397 | procedure _ProcessDateTimeItem();
1398 | begin
1399 | if (s <> '') then
1400 | begin
1401 | _tmp := UpperCase(s);
1402 | _len := length(_tmp);
1403 |
1404 | FItems[FCount].Settings := 0;
1405 | FItems[FCount].Len := _len;
1406 | FItems[FCount].TextValue := s;
1407 |
1408 | case (_tmp[1]) of
1409 | 'Y', 'J', 'V':
1410 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_YEAR;
1411 | 'M':
1412 | begin
1413 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_MONTH;
1414 | // If can't recognize month / minute
1415 | if (_len <= 2) then
1416 | FItems[FCount].Settings := 1;
1417 | end;
1418 | 'D':
1419 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_DAY;
1420 | 'N':
1421 | begin
1422 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_DAY;
1423 | inc(FItems[FCount].Len);
1424 | end;
1425 | 'H':
1426 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_HOUR;
1427 | 'W':
1428 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_WEEK;
1429 | 'Q':
1430 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_QUARTER;
1431 | 'R':
1432 | if (_len = 1) then
1433 | begin
1434 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_ERA_YEAR;
1435 | FItems[FCount].Len := 2;
1436 | end
1437 | else
1438 | begin
1439 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_ERA_JAP;
1440 | FItems[FCount].Len := 4;
1441 | end;
1442 | 'E':
1443 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_ERA_YEAR;
1444 | else
1445 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_TEXT;
1446 | end; // case
1447 |
1448 | IncCount();
1449 |
1450 | s := '';
1451 | end;
1452 | end; // _ProcessDateTimeItem()
1453 |
1454 | procedure _AddItemCommon(const AStr: string; ALen: integer; AItemType: integer; ASettings: integer = 0);
1455 | begin
1456 | FItems[FCount].ItemType := AItemType;
1457 | FItems[FCount].Len := ALen;
1458 | FItems[FCount].TextValue := AStr;
1459 | FItems[FCount].Settings := ASettings;
1460 | IncCount();
1461 | end;
1462 |
1463 | procedure _ProcessAMPM();
1464 | begin
1465 | // check for a/p A/P
1466 | if (_parser.CurrentPos + 1 <= _parser.StrLength) then
1467 | begin
1468 | _tmp := Copy(AFmtStr, _parser.CurrentPos - 1, 3);
1469 |
1470 | if (UpperCase(_tmp) = 'A/P') then
1471 | begin
1472 | if (_parser.FirstSymbol = 'a') then
1473 | t := ZE_DATETIME_AMPM_SHORT_LOW
1474 | else
1475 | t := ZE_DATETIME_AMPM_SHORT_UP;
1476 | _AddItemCommon(_tmp, 3, ZE_DATETIME_ITEM_AMPM, t);
1477 | _parser.IncPos(2);
1478 |
1479 | exit;
1480 | end;
1481 | end;
1482 |
1483 | // check for am/pm AM/PM
1484 | if (_parser.CurrentPos + 3 <= _parser.StrLength) then
1485 | begin
1486 | _tmp := Copy(AFmtStr, _parser.CurrentPos - 1, 5);
1487 |
1488 | if (UpperCase(_tmp) = 'AM/PM') then
1489 | begin
1490 | if (_parser.FirstSymbol = 'a') then
1491 | t := ZE_DATETIME_AMPM_LONG_LOW
1492 | else
1493 | t := ZE_DATETIME_AMPM_LONG_UP;
1494 | _AddItemCommon(_tmp, 5, ZE_DATETIME_ITEM_AMPM, t);
1495 | _parser.IncPos(4);
1496 |
1497 | exit;
1498 | end;
1499 | end;
1500 |
1501 | // It is not AM/PM. May be a year?
1502 | s := s + 'Y';
1503 | end; // _ProcessAMPM
1504 |
1505 | procedure _ProcessSeconds();
1506 | begin
1507 | s := _ch;
1508 | _len := 1;
1509 | t := 0;
1510 | while (_parser.CurrentPos <= _parser.StrLength) do
1511 | begin
1512 | _ch := AFmtStr[_parser.CurrentPos];
1513 | case (_ch) of
1514 | 's', 'S':
1515 | begin
1516 | s := s + _ch;
1517 | inc(_len);
1518 | end
1519 | else
1520 | begin
1521 | if ((_ch = '.') or (_ch = ',')) then
1522 | begin
1523 | s := s + '.';
1524 | while (_parser.CurrentPos <= _parser.StrLength) do
1525 | begin
1526 | _parser.IncPos(1);
1527 | if (AFmtStr[_parser.CurrentPos] = '0') then
1528 | begin
1529 | s := s + '0';
1530 | inc(t);
1531 | end
1532 | else
1533 | break;
1534 | end; // while
1535 | end; // if
1536 |
1537 | break;
1538 | end;
1539 | end;
1540 | _parser.IncPos(1);
1541 | end; // while
1542 | _AddItemCommon(s, _len, ZE_DATETIME_ITEM_SECOND, t);
1543 | s := '';
1544 | end; // _ProcessSeconds
1545 |
1546 | procedure _TryToAddEraJap();
1547 | begin
1548 | _tmp := UpperCase(s);
1549 | t := -1;
1550 | if (_tmp = 'G') then
1551 | t := 1
1552 | else if (_tmp = 'GG') then
1553 | t := 2
1554 | else if (_tmp = 'GGG') then
1555 | t := 3
1556 | else if (_tmp = 'GGGEE') then
1557 | t := 4;
1558 |
1559 | if (t > 0) then
1560 | _AddItemCommon(s, t, ZE_DATETIME_ITEM_ERA_JAP, t);
1561 |
1562 | s := '';
1563 | end;
1564 |
1565 | procedure _ProcessEraJap();
1566 | begin
1567 | s := _ch;
1568 | _pos := _parser.CurrentPos;
1569 | while (_pos <= _parser.StrLength) do
1570 | begin
1571 | _ch := AFmtStr[_pos];
1572 | case (_ch) of
1573 | 'g', 'G', 'e', 'E':
1574 | s := s + _ch;
1575 | else
1576 | begin
1577 | _TryToAddEraJap();
1578 | exit;
1579 | end;
1580 | end;
1581 | inc(_pos);
1582 | _parser.IncPos(1);
1583 | end; // while
1584 |
1585 | if (s <> '') then
1586 | _TryToAddEraJap();
1587 | end; // _ProcessEraJap
1588 |
1589 | procedure _ProcessSymbol();
1590 | begin
1591 | _ch := _parser.FirstSymbol;
1592 |
1593 | if (UpperCase(_prevCh) = UpperCase(_ch)) then
1594 | s := s + _ch
1595 | else
1596 | begin
1597 | _ProcessDateTimeItem();
1598 |
1599 | // TODO:
1600 | // Need check all symbols for other locales (A/J/V - as year etc)
1601 | case (_ch) of
1602 | 'a', 'A':
1603 | _ProcessAMPM();
1604 | 'y', 'Y', 'j', 'J', // German year ??
1605 | 'v', 'V', // Finnish year ??
1606 | 'm', 'M', 'd', 'D', 'n', 'N', 'h', 'H', 'w', 'W', 'r', 'R', 'q', 'Q', 'e', 'E':
1607 | s := s + _ch;
1608 | 's', 'S':
1609 | _ProcessSeconds();
1610 | 'g', 'G':
1611 | _ProcessEraJap();
1612 | else
1613 | s := s + _ch;
1614 | end; // case
1615 | end;
1616 |
1617 | _prevCh := _ch;
1618 | end; // _ProcessSymbol
1619 |
1620 | procedure _ProcessText();
1621 | begin
1622 | _ProcessDateTimeItem();
1623 | FItems[FCount].ItemType := ZE_DATETIME_ITEM_TEXT;
1624 | FItems[FCount].TextValue := _parser.ReadedSymbol;
1625 | IncCount();
1626 | end; // _ProcessText
1627 |
1628 | begin
1629 | FCount := 0;
1630 | _parser := AFmtParser;
1631 | _isFree := AFmtParser = nil;
1632 | if (_isFree) then
1633 | _parser := TNumFormatParser.Create();
1634 |
1635 | s := '';
1636 | _prevCh := #0;
1637 |
1638 | try
1639 | _parser.BeginRead(AFmtStr);
1640 |
1641 | while (_parser.ReadSymbol()) do
1642 | begin
1643 | case (_parser.ReadedSymbolType) of
1644 | 0:
1645 | _ProcessSymbol();
1646 | 1:
1647 | ; // brackets - modiefier: color, calendar or conditions ??
1648 | 2, 3:
1649 | _ProcessText();
1650 | end;
1651 | end; // while
1652 |
1653 | _ProcessDateTimeItem();
1654 |
1655 | CheckMonthMinute();
1656 |
1657 | finally
1658 | if (_isFree) then
1659 | FreeAndNil(_parser);
1660 | end;
1661 |
1662 | Result := FCount;
1663 | end; // TryToParseDateFormat
1664 |
1665 | procedure TZDateTimeODSFormatParser.DeleteRepeatedItems();
1666 | var
1667 | i, j: integer;
1668 | begin
1669 | for i := 1 to FCount - 2 do
1670 | if (FItems[i].ItemType > 0) then
1671 | for j := FCount - 1 downto i + 1 do
1672 | if (FItems[i].ItemType = FItems[j].ItemType) then
1673 | begin
1674 | if (FItems[i].ItemType = ZE_DATETIME_ITEM_DAY) then
1675 | begin
1676 | if (FItems[i].Len = FItems[j].Len) then
1677 | FItems[j].ItemType := ZE_DATETIME_ITEM_ERROR;
1678 | end
1679 | else
1680 | FItems[j].ItemType := ZE_DATETIME_ITEM_ERROR;
1681 | end;
1682 | end; // DeleteRepeatedItems
1683 |
1684 | function TZDateTimeODSFormatParser.GetValidCount(): integer;
1685 | var
1686 | i: integer;
1687 | begin
1688 | Result := 0;
1689 | for i := 1 to FCount - 1 do
1690 | if (FItems[i].ItemType >= 0) then
1691 | inc(Result);
1692 | end; // GetValidCount
1693 |
1694 | /// /::::::::::::: TNumFormatParser :::::::::::::::::////
1695 |
1696 | procedure TNumFormatParser.Clear();
1697 | begin
1698 | FLen := -1;
1699 | FPos := 1;
1700 | FStr := '';
1701 | FIsError := 0;
1702 | FReadedSymbolType := 0;
1703 | FReadedSymbol := '';
1704 | FFirstSymbol := #0;
1705 | end;
1706 |
1707 | constructor TNumFormatParser.Create();
1708 | begin
1709 | Clear();
1710 | end;
1711 |
1712 | procedure TNumFormatParser.BeginRead(const AStr: string);
1713 | begin
1714 | Clear();
1715 | FStr := AStr;
1716 | FLen := length(AStr);
1717 | end;
1718 |
1719 | function TNumFormatParser.ReadSymbol(): boolean;
1720 | var
1721 | ch: char;
1722 | procedure _ReadBeforeSymbol(Symbol: char);
1723 | begin
1724 | if (FPos <= FLen) then
1725 | FFirstSymbol := FStr[FPos];
1726 |
1727 | while (FPos <= FLen) do
1728 | begin
1729 | ch := FStr[FPos];
1730 | inc(FPos);
1731 |
1732 | if (ch = Symbol) then
1733 | exit;
1734 |
1735 | FReadedSymbol := FReadedSymbol + ch;
1736 | end; // while
1737 |
1738 | FIsError := FIsError or 2;
1739 | end; // _ReadBeforeSymbol
1740 |
1741 | begin
1742 | FFirstSymbol := #0;
1743 | if (FPos > FLen) then
1744 | begin
1745 | Result := false;
1746 | exit;
1747 | end;
1748 |
1749 | FReadedSymbol := '';
1750 |
1751 | Result := true;
1752 | while (FPos <= FLen) do
1753 | begin
1754 | ch := FStr[FPos];
1755 | inc(FPos);
1756 |
1757 | case ch of
1758 | '[':
1759 | begin
1760 | FReadedSymbolType := 1;
1761 | _ReadBeforeSymbol(']');
1762 | exit;
1763 | end;
1764 | '"':
1765 | begin
1766 | FReadedSymbolType := 2;
1767 | _ReadBeforeSymbol('"');
1768 | exit;
1769 | end;
1770 | '\':
1771 | begin
1772 | if (FPos <= FLen) then
1773 | begin
1774 | FFirstSymbol := FStr[FPos];
1775 | FReadedSymbol := FFirstSymbol;
1776 | end
1777 | else
1778 | begin
1779 | FIsError := FIsError or 4;
1780 | FReadedSymbol := '';
1781 | end;
1782 | inc(FPos);
1783 | FReadedSymbolType := 3;
1784 | exit;
1785 | end;
1786 | else
1787 | begin
1788 | FReadedSymbol := ch;
1789 | FFirstSymbol := ch;
1790 | FReadedSymbolType := 0;
1791 | exit;
1792 | end;
1793 | end; // case
1794 | end; // while
1795 |
1796 | FIsError := FIsError or 1;
1797 | end; // ReadSymbol
1798 |
1799 | procedure TNumFormatParser.IncPos(ADelta: integer);
1800 | begin
1801 | inc(FPos, ADelta);
1802 | if (FPos < 1) then
1803 | FPos := 1;
1804 | end; // IncPos
1805 |
1806 | /// /::::::::::::: TZEODSNumberFormatReader :::::::::::::::::////
1807 |
1808 | procedure TZEODSNumberFormatReader.AddEmbededText(const AText: string; ANumberPosition: integer);
1809 | var
1810 | i, _pos: integer;
1811 | begin
1812 | if (FEmbededTextCount >= FEmbededMaxCount) then
1813 | begin
1814 | inc(FEmbededMaxCount, 10);
1815 | SetLength(FEmbededTextArray, FEmbededMaxCount);
1816 | end;
1817 |
1818 | _pos := -1;
1819 |
1820 | for i := 0 to FEmbededTextCount - 1 do
1821 | if (ANumberPosition < FEmbededTextArray[i].NumberPosition) then
1822 | begin
1823 | _pos := i;
1824 | break;
1825 | end;
1826 |
1827 | if (_pos >= 0) then
1828 | begin
1829 | for i := FEmbededTextCount + 1 downto _pos + 1 do
1830 | FEmbededTextArray[i] := FEmbededTextArray[i - 1];
1831 | end
1832 | else
1833 | _pos := FEmbededTextCount;
1834 |
1835 | FEmbededTextArray[_pos].Txt := AText;
1836 | FEmbededTextArray[_pos].NumberPosition := ANumberPosition;
1837 |
1838 | inc(FEmbededTextCount);
1839 | end;
1840 |
1841 | procedure TZEODSNumberFormatReader.AddItem();
1842 | var
1843 | i: integer;
1844 | begin
1845 | inc(FCount);
1846 | if (FCount >= FCountMax) then
1847 | begin
1848 | inc(FCountMax, 20);
1849 | SetLength(FItems, FCountMax);
1850 | SetLength(FItemsOptions, FCountMax);
1851 | for i := FCount to FCount - 1 do
1852 | begin
1853 | FItemsOptions[i].isColor := false;
1854 | FItemsOptions[i].ColorStr := '';
1855 | end;
1856 | end;
1857 | end;
1858 |
1859 | constructor TZEODSNumberFormatReader.Create();
1860 | var
1861 | i: integer;
1862 | begin
1863 | FCount := 0;
1864 | FCountMax := 20;
1865 | FEmbededMaxCount := 10;
1866 | SetLength(FItems, FCountMax);
1867 | SetLength(FItemsOptions, FCountMax);
1868 | SetLength(FEmbededTextArray, FEmbededMaxCount);
1869 | for i := 0 to FCountMax - 1 do
1870 | begin
1871 | FItemsOptions[i].isColor := false;
1872 | FItemsOptions[i].ColorStr := '';
1873 | FItemsOptions[i].StyleType := 0;
1874 | end;
1875 | end;
1876 |
1877 | destructor TZEODSNumberFormatReader.Destroy();
1878 | begin
1879 | SetLength(FItems, 0);
1880 | SetLength(FItemsOptions, 0);
1881 | SetLength(FEmbededTextArray, 0);
1882 | inherited;
1883 | end;
1884 |
1885 | function TZEODSNumberFormatReader.BeginReadFormat(const Xml: TZsspXMLReaderH; out retStartString: string;
1886 | const NumFormat: integer): integer;
1887 | begin
1888 | Result := FCount;
1889 | AddItem();
1890 | FItems[Result][0] := Xml.Attributes[ZETag_Attr_StyleName];
1891 | FItemsOptions[Result].StyleType := NumFormat;
1892 | retStartString := '';
1893 | end; // BeginReadFormat
1894 |
1895 | // Read date format: ..
1896 | procedure TZEODSNumberFormatReader.ReadDateFormat(const Xml: TZsspXMLReaderH; const ATagName: string);
1897 | var
1898 | num: integer;
1899 | s, _result: string;
1900 | _isLong: boolean;
1901 | t: integer;
1902 | i: integer;
1903 |
1904 | function CheckIsLong(const isTrue, isFalse: string): string;
1905 | begin
1906 | if (Xml.Attributes[ZETag_number_style] = ZETag_long) then
1907 | Result := isTrue
1908 | else
1909 | Result := isFalse;
1910 | end;
1911 |
1912 | begin
1913 | _isLong := false;
1914 | num := BeginReadFormat(Xml, _result, ZE_NUMFORMAT_IS_DATETIME);
1915 |
1916 | while Xml.ReadToEndTagByName(ATagName) do
1917 | begin
1918 | // Day
1919 | if ((Xml.TagName = ZETag_number_day) and (Xml.IsTagOfData)) then
1920 | _result := _result + CheckIsLong('DD', 'D')
1921 | else
1922 | // Text
1923 | if Xml.IsTagEndByName(ZETag_number_text) then
1924 | begin
1925 | s := Xml.TextBeforeTag;
1926 | t := length(s);
1927 | if (t = 1) then
1928 | begin
1929 | case (s[1]) of
1930 | ' ', '.', ':', '-', '/', '*':
1931 | ;
1932 | else
1933 | s := '\' + s;
1934 | end; // case
1935 | end
1936 | else
1937 | s := '"' + s + '"';
1938 |
1939 | _result := _result + s;
1940 | end
1941 | else
1942 | // Month
1943 | if (Xml.TagName = ZETag_number_month) then
1944 | begin
1945 | _isLong := Xml.Attributes[ZETag_number_style] = ZETag_long;
1946 | s := Xml.Attributes[ZETag_number_textual];
1947 | if (ZEStrToBoolean(s)) then
1948 | _result := _result + IfThen(_isLong, 'MMMM', 'MMM')
1949 | else
1950 | _result := _result + IfThen(_isLong, 'MM', 'M')
1951 | end
1952 | else
1953 | // Year
1954 | if (Xml.TagName = ZETag_number_year) then
1955 | _result := _result + CheckIsLong('YYYY', 'YY')
1956 | else
1957 | // Hours
1958 | if (Xml.TagName = ZETag_number_hours) then
1959 | _result := _result + CheckIsLong('HH', 'H')
1960 | else
1961 | // Minutes
1962 | if (Xml.TagName = ZETag_number_minutes) then
1963 | _result := _result + CheckIsLong('mm', 'm')
1964 | else
1965 | // Seconds
1966 | if (Xml.TagName = ZETag_number_seconds) then
1967 | begin
1968 | _result := _result + CheckIsLong('ss', 's');
1969 | s := Xml.Attributes[ZETag_number_decimal_places];
1970 | if (s <> '') then
1971 | if (TryStrToInt(s, t)) then
1972 | if (t > 0) then
1973 | begin
1974 | _result := _result + '.';
1975 | for i := 1 to t do
1976 | _result := _result + '0';
1977 | end;
1978 | end
1979 | else
1980 | // AM/PM
1981 | if (Xml.TagName = ZETag_number_am_pm) then
1982 | begin
1983 | _result := _result + 'AM/PM';
1984 | end
1985 | else
1986 | // Era
1987 | if (Xml.TagName = ZETag_number_era) then
1988 | begin
1989 | // Attr: number:calendar
1990 | // number:style
1991 | _result := _result + IfThen(_isLong, 'GG', 'G')
1992 | end
1993 | else
1994 | // Quarter
1995 | if (Xml.TagName = ZETag_number_quarter) then
1996 | begin
1997 | // Attr: number:calendar
1998 | // number:style
1999 | _result := _result + CheckIsLong('QQ', 'Q')
2000 | end
2001 | else
2002 | // Day of week
2003 | if (Xml.TagName = ZETag_number_day_of_week) then
2004 | begin
2005 | // Attr: number:calendar
2006 | // number:style
2007 | _result := _result + CheckIsLong('NNN', 'NN')
2008 | end
2009 | else
2010 | // Week of year
2011 | if (Xml.TagName = ZETag_number_week_of_year) then
2012 | begin
2013 | // Attr: number:calendar
2014 | _result := _result + 'WW';
2015 | end;
2016 |
2017 | if (Xml.Eof()) then
2018 | break;
2019 | end; // while
2020 | FItems[num][1] := _result;
2021 | end; // ReadDateFormat
2022 |
2023 | // Read string format ..
2024 | procedure TZEODSNumberFormatReader.ReadStringFormat(const Xml: TZsspXMLReaderH);
2025 | var
2026 | num: integer;
2027 | _result: string;
2028 |
2029 | begin
2030 | num := BeginReadFormat(Xml, _result, ZE_NUMFORMAT_IS_STRING);
2031 |
2032 | // Possible attributes for tag "number:text-style":
2033 | // number:country
2034 | // number:language
2035 | // number:rfc-language-tag
2036 | // number:script
2037 | // number:title
2038 | // number:transliteration-country
2039 | // number:transliteration-format
2040 | // number:transliteration-language
2041 | // number:transliteration-style
2042 | // style:display-name
2043 | // style:name
2044 | // style:volatile
2045 |
2046 | // Possible child elements:
2047 | // number:text *
2048 | // number:text-content *
2049 | // style:map
2050 | // style:textproperties
2051 | while ((not Xml.IsTagEnd) or (Xml.TagName <> ZETag_number_text_style)) do
2052 | begin
2053 | Xml.ReadTag();
2054 |
2055 | // number:text-content
2056 | if ((Xml.TagName = ZETag_number_text_content)) then
2057 | _result := _result + '@'
2058 | else
2059 | // Text
2060 | if ((Xml.TagName = ZETag_number_text) and (Xml.IsTagEnd)) then
2061 | _result := _result + '"' + Xml.TextBeforeTag + '"';
2062 |
2063 | if (Xml.Eof()) then
2064 | break;
2065 | end; // while
2066 | FItems[num][1] := _result;
2067 | end; // ReadStringFormat
2068 |
2069 | // Read known numbers formats (date/number/percentage etc)
2070 | procedure TZEODSNumberFormatReader.ReadKnownNumberFormat(const Xml: TZsspXMLReaderH);
2071 | begin
2072 | if (Xml.TagName = ZETag_number_number_style) then
2073 | ReadNumberFormat(Xml)
2074 | else if (Xml.TagName = ZETag_number_currency_style) then
2075 | ReadCurrencyFormat(Xml)
2076 | else if (Xml.TagName = ZETag_number_percentage_style) then
2077 | ReadPercentageFormat(Xml)
2078 | else if (Xml.TagName = ZETag_number_date_style) then
2079 | ReadDateFormat(Xml, ZETag_number_date_style)
2080 | else if (Xml.TagName = ZETag_number_time_style) then
2081 | ReadDateFormat(Xml, ZETag_number_time_style)
2082 | else if (Xml.TagName = ZETag_number_text_style) then
2083 | ReadStringFormat(Xml);
2084 | end;
2085 |
2086 | procedure TZEODSNumberFormatReader.ReadCurrencyFormat(const Xml: TZsspXMLReaderH);
2087 | begin
2088 | ReadNumberFormatCommon(Xml, ZETag_number_currency_style, ZE_NUMFORMAT_NUM_IS_CURRENCY);
2089 | end;
2090 |
2091 | // Read number style: ..
2092 | procedure TZEODSNumberFormatReader.ReadNumberFormat(const Xml: TZsspXMLReaderH);
2093 | begin
2094 | ReadNumberFormatCommon(Xml, ZETag_number_number_style, 0);
2095 | end;
2096 |
2097 | // Read number style: ..
2098 | procedure TZEODSNumberFormatReader.ReadPercentageFormat(const Xml: TZsspXMLReaderH);
2099 | begin
2100 | ReadNumberFormatCommon(Xml, ZETag_number_percentage_style, ZE_NUMFORMAT_NUM_IS_PERCENTAGE);
2101 | end;
2102 |
2103 | // Read Number/currency/percentage number format style
2104 | // INPUT
2105 | // const xml: TZsspXMLReaderH - xml
2106 | // const NumberFormatTag: string - tag name
2107 | // sub_number_type: integer - additional flag for number (percentage/scientific etc)
2108 | procedure TZEODSNumberFormatReader.ReadNumberFormatCommon(const Xml: TZsspXMLReaderH; const NumberFormatTag: string;
2109 | sub_number_type: integer);
2110 | var
2111 | num: integer;
2112 | s, _result, _txt, _style_name: string;
2113 | _cond_text: string;
2114 | _cond: string;
2115 | _decimalPlaces: integer;
2116 | _min_int_digits: integer;
2117 | _display_factor: integer;
2118 | _number_grouping: boolean;
2119 | _number_position: integer;
2120 | _is_number_decimal_replacement: boolean;
2121 | ch: char;
2122 |
2123 | _min_numerator_digits: integer;
2124 | _min_denominator_digits: integer;
2125 | _denominator_value: integer;
2126 |
2127 | procedure _TryGetIntValue(const ATagName: string; out retIntValue: integer; const ADefValue: integer = 0);
2128 | begin
2129 | s := Xml.Attributes[ATagName];
2130 | if (not TryStrToInt(s, retIntValue)) then
2131 | retIntValue := ADefValue;
2132 | end;
2133 |
2134 | procedure _ReadNumber_NumberPrepare();
2135 | begin
2136 | FEmbededTextCount := 0;
2137 |
2138 | _TryGetIntValue(ZETag_number_decimal_places, _decimalPlaces);
2139 | _TryGetIntValue(ZETag_number_min_integer_digits, _min_int_digits);
2140 |
2141 | s := Xml.Attributes[ZETag_number_display_factor];
2142 | if (s <> '') then
2143 | begin
2144 | if (not TryStrToInt(s, _display_factor)) then
2145 | _display_factor := 1;
2146 | end
2147 | else
2148 | _display_factor := 1;
2149 |
2150 | _number_grouping := false;
2151 | s := Xml.Attributes[ZETag_number_grouping];
2152 | if (s <> '') then
2153 | _number_grouping := ZEStrToBoolean(s);
2154 |
2155 | _is_number_decimal_replacement := Xml.Attributes.IsContainsAttribute(ZETag_number_decimal_replacement);
2156 | end; // _ReadNumber_NumberPrepare
2157 |
2158 | procedure _ReadEmbededText();
2159 | begin
2160 | _number_position := -100;
2161 | while ((not Xml.IsTagEnd) or (Xml.TagName <> ZETag_number_number)) do
2162 | begin
2163 | Xml.ReadTag();
2164 |
2165 | // ..
2166 | if (Xml.TagName = ZETag_number_embedded_text) then
2167 | begin
2168 | if (Xml.IsTagStart) then
2169 | _TryGetIntValue(ZETag_number_position, _number_position, -100)
2170 | else if ((Xml.IsTagEnd) and (_number_position >= 0)) then
2171 | if (Xml.TextBeforeTag <> '') then
2172 | AddEmbededText(ZEReplaceEntity(Xml.TextBeforeTag), _number_position);
2173 | end;
2174 |
2175 | if (Xml.Eof()) then
2176 | break;
2177 | end; // while
2178 | end; // _ReadEmbededText
2179 |
2180 | function _GetRepeatedString(ACount: integer; const AStr: string): string;
2181 | var
2182 | i: integer;
2183 | begin
2184 | Result := '';
2185 | for i := 1 to ACount do
2186 | Result := Result + AStr;
2187 | end;
2188 |
2189 | // ..
2190 | procedure _ReadNumber_Number();
2191 | var
2192 | i, j: integer;
2193 | _pos: integer;
2194 | _currentpos: integer; // current position for embeded text
2195 |
2196 | begin
2197 | _currentpos := 0;
2198 | _ReadNumber_NumberPrepare();
2199 |
2200 | if (Xml.IsTagStart) then
2201 | _ReadEmbededText();
2202 |
2203 | if (FEmbededTextCount > 0) then
2204 | begin
2205 | s := '';
2206 | _pos := 0;
2207 |
2208 | for i := 0 to FEmbededTextCount - 1 do
2209 | if (FEmbededTextArray[i].NumberPosition >= 0) then
2210 | begin
2211 | _currentpos := FEmbededTextArray[i].NumberPosition;
2212 | // TODO: need test. For example: if symbol "%" not one? (%0%0.00% or "%"0.0)
2213 | (*
2214 | if (FEmbededTextArray[i].Txt = '%') then
2215 | _txt := '%'
2216 | else
2217 | *)
2218 | _txt := '"' + ZEReplaceEntity(FEmbededTextArray[i].Txt) + '"';
2219 |
2220 | if (_currentpos <= _min_int_digits) then
2221 | ch := '0'
2222 | else
2223 | ch := '#';
2224 |
2225 | for j := _pos to _currentpos - 1 do
2226 | s := ch + s;
2227 | s := _txt + s;
2228 | _pos := _currentpos;
2229 | end;
2230 |
2231 | if (_currentpos < _min_int_digits) then
2232 | for j := _pos to _min_int_digits - 1 do
2233 | s := '0' + s;
2234 |
2235 | _result := _result + s;
2236 | end
2237 | else
2238 | begin
2239 | if (_min_int_digits = 0) then
2240 | _result := _result + '#'
2241 | else
2242 | for i := 0 to _min_int_digits - 1 do
2243 | _result := _result + '0';
2244 | end;
2245 |
2246 | if (_decimalPlaces > 0) then
2247 | begin
2248 | if (_is_number_decimal_replacement) then
2249 | ch := '#'
2250 | else
2251 | ch := '0';
2252 |
2253 | _result := _result + ZE_NUMBER_FORMAT_DECIMAL_SEPARATOR;
2254 | for i := 0 to _decimalPlaces - 1 do
2255 | _result := _result + ch;
2256 | end;
2257 | end; // _ReadNumber_Number
2258 |
2259 | //
2260 | procedure _ReadNumber_Fraction();
2261 | begin
2262 | _ReadNumber_NumberPrepare();
2263 |
2264 | _TryGetIntValue(ZETag_number_min_numerator_digits, _min_numerator_digits);
2265 | _TryGetIntValue(ZETag_number_min_denominator_digits, _min_denominator_digits);
2266 | // TODO: do not forget about denominator_value!
2267 | _TryGetIntValue(ZETag_number_denominator_value, _denominator_value);
2268 |
2269 | if (_min_int_digits <= 0) then
2270 | s := '#'
2271 | else
2272 | s := _GetRepeatedString(_min_int_digits, '0');
2273 |
2274 | _result := _result + s;
2275 |
2276 | if ((_min_numerator_digits > 0) and (_min_denominator_digits > 0)) then
2277 | _result := _result + ' ' + _GetRepeatedString(_min_numerator_digits, '?') + '/' +
2278 | _GetRepeatedString(_min_denominator_digits, '?');
2279 |
2280 | FItemsOptions[num].StyleType := FItemsOptions[num].StyleType or ZE_NUMFORMAT_NUM_IS_FRACTION;
2281 | end; // _ReadNumber_Fraction
2282 |
2283 | //
2284 | procedure _ReadNumber_Scientific();
2285 | var
2286 | _min_exponent_digits: integer;
2287 |
2288 | begin
2289 | _ReadNumber_NumberPrepare();
2290 |
2291 | _TryGetIntValue(ZETag_number_min_exponent_digits, _min_exponent_digits);
2292 |
2293 | if (_min_exponent_digits > 0) then
2294 | begin
2295 | _result := _result + _GetRepeatedString(_min_int_digits, '0') + ZE_NUMBER_FORMAT_DECIMAL_SEPARATOR +
2296 | _GetRepeatedString(_decimalPlaces, '0') + 'E+' + _GetRepeatedString(_min_exponent_digits, '0');
2297 |
2298 | FItemsOptions[num].StyleType := FItemsOptions[num].StyleType or ZE_NUMFORMAT_NUM_IS_SCIENTIFIC;
2299 | end;
2300 | end; // _ReadNumber_Scientific
2301 |
2302 | // ..
2303 | procedure _ReadCurrecny_Symbol();
2304 | begin
2305 | // TODO: need get currency symbol by attributes:
2306 | // ZETag_number_language = 'number:language'
2307 | // ZETag_number_country = 'number:country'
2308 | if (Xml.IsTagStart) then
2309 | while ((not Xml.IsTagEnd) or (Xml.TagName <> ZETag_number_currency_symbol)) do
2310 | begin
2311 | Xml.ReadTag();
2312 |
2313 | (* //if need currency name in future
2314 | if (xml.TagName = ZETag_number_currency_symbol) then
2315 | s := xml.TextBeforeTag;
2316 | *)
2317 |
2318 | if (Xml.Eof()) then
2319 | break;
2320 | end; // while
2321 | end; // _ReadCurrecny_Symbol
2322 |
2323 | //
2324 | procedure _ReadStyleMap();
2325 | var
2326 | i: integer;
2327 |
2328 | begin
2329 | _cond := ZEReplaceEntity(Xml.Attributes[ZETag_style_condition]);
2330 | if (_cond <> '') then
2331 | begin
2332 | i := pos('value()', _cond);
2333 | if (i = 1) then
2334 | delete(_cond, 1, 7)
2335 | else
2336 | exit;
2337 |
2338 | if (_cond <> '') then
2339 | begin
2340 | _style_name := Xml.Attributes[ZETag_style_apply_style_name];
2341 | for i := 0 to FCount - 1 do
2342 | if (FItems[i][0] = _style_name) then
2343 | begin
2344 | _txt := FItems[i][1];
2345 |
2346 | _cond_text := _cond_text + '[' + _cond + ']' + _txt + ';';
2347 |
2348 | break;
2349 | end;
2350 | end; // if
2351 | end; // if
2352 | end; // _ReadStyleMap
2353 |
2354 | // (color by condition)
2355 | procedure _ReadStyleTextProperties();
2356 | begin
2357 | // for now only colors
2358 | s := UpperCase(Xml.Attributes[ZETag_fo_color]);
2359 | if (TryGetMapColorName(s, _txt)) then
2360 | begin
2361 | FItemsOptions[num].isColor := true;
2362 | FItemsOptions[num].ColorStr := _txt;
2363 | _result := _result + '[' + _txt + ']';
2364 | end;
2365 | end; // _ReadStyleTextProperties
2366 |
2367 | begin
2368 | num := BeginReadFormat(Xml, _result, ZE_NUMFORMAT_IS_NUMBER or sub_number_type);
2369 | _cond_text := '';
2370 |
2371 | while ((not Xml.IsTagEnd) or (Xml.TagName <> NumberFormatTag)) do
2372 | begin
2373 | Xml.ReadTag();
2374 |
2375 | if (Xml.TagName = ZETag_number_number) then
2376 | _ReadNumber_Number()
2377 | else if (Xml.TagName = ZETag_number_fraction) then
2378 | _ReadNumber_Fraction()
2379 | else if (Xml.TagName = ZETag_number_scientific_number) then
2380 | _ReadNumber_Scientific()
2381 | else if (Xml.TagName = ZETag_number_currency_symbol) then
2382 | _ReadCurrecny_Symbol()
2383 | else if ((Xml.TagName = ZETag_number_text) and (Xml.IsTagEnd)) then
2384 | begin
2385 | s := ZEReplaceEntity(Xml.TextBeforeTag);
2386 | if (s = '"') then
2387 | _result := _result + '\' + s
2388 | else if (s = '%') then
2389 | _result := _result + s
2390 | else
2391 | _result := _result + '"' + s + '"';
2392 | end
2393 | else if (Xml.TagName = ZETag_style_map) then
2394 | _ReadStyleMap()
2395 | else if (Xml.TagName = ZETag_style_text_properties) then
2396 | _ReadStyleTextProperties();
2397 |
2398 | if (Xml.Eof()) then
2399 | break;
2400 | end; // while
2401 |
2402 | // first - map number styles, after - current readed number format
2403 | FItems[num][1] := _cond_text + _result;
2404 | end; // ReadNumberFormatCommon
2405 |
2406 | function TZEODSNumberFormatReader.TryGetFormatStrByNum(const DataStyleName: string; out retFormatStr: string): boolean;
2407 | var
2408 | i: integer;
2409 | begin
2410 | Result := false;
2411 | for i := 0 to FCount - 1 do
2412 | if (FItems[i][0] = DataStyleName) then
2413 | begin
2414 | Result := true;
2415 | retFormatStr := FItems[i][1];
2416 | break;
2417 | end;
2418 | end; // TryGetFormatStrByNum
2419 |
2420 | /// /::::::::::::: TZEODSNumberFormatWriter :::::::::::::::::////
2421 |
2422 | constructor TZEODSNumberFormatWriter.Create(const AMaxCount: integer);
2423 | var
2424 | i: integer;
2425 | begin
2426 | FCount := 0;
2427 | FCountMax := AMaxCount;
2428 | if (FCountMax < 10) then
2429 | FCountMax := 10;
2430 | SetLength(FItems, FCountMax);
2431 |
2432 | SetLength(FNumberAdditionalProps, FCountMax);
2433 | for i := 0 to FCountMax - 1 do
2434 | FNumberAdditionalProps[i] := 0;
2435 |
2436 | FCurrentNFIndex := 100;
2437 |
2438 | FNFItemsCount := 0;
2439 | SetLength(FNFItems, ZE_MAX_NF_ITEMS_COUNT);
2440 | for i := 0 to ZE_MAX_NF_ITEMS_COUNT - 1 do
2441 | FNFItems[i] := TODSNumberFormatMapItem.Create();
2442 | end;
2443 |
2444 | destructor TZEODSNumberFormatWriter.Destroy();
2445 | var
2446 | i: integer;
2447 | begin
2448 | SetLength(FItems, 0);
2449 | for i := 0 to ZE_MAX_NF_ITEMS_COUNT - 1 do
2450 | FreeAndNil(FNFItems[i]);
2451 | SetLength(FNFItems, 0);
2452 |
2453 | SetLength(FNumberAdditionalProps, 0);
2454 |
2455 | inherited;
2456 | end;
2457 |
2458 | // Try to find number format name for style num StyleID
2459 | // INPUT
2460 | // StyleID: integer - style ID
2461 | // out NumberFormatName: string - finded number format name
2462 | // RETURN
2463 | // boolean - true - number format finded
2464 | function TZEODSNumberFormatWriter.TryGetNumberFormatName(StyleID: integer; out NumberFormatName: string): boolean;
2465 | var
2466 | i: integer;
2467 | begin
2468 | Result := false;
2469 | for i := 0 to FCount - 1 do
2470 | if (FItems[i].StyleIndex = StyleID) then
2471 | begin
2472 | NumberFormatName := FItems[i].NumberFormatName;
2473 | Result := true;
2474 | break;
2475 | end;
2476 | end; // TryGetNumberFormatName
2477 |
2478 | // Try to find additional properties for number format
2479 | // INPUT
2480 | // StyleID: integer - style ID
2481 | // out NumberFormatProp: integer - finded number additional properties
2482 | // RETURN
2483 | // boolean - true - additional properties is found
2484 | function TZEODSNumberFormatWriter.TryGetNumberFormatAddProp(StyleID: integer; out NumberFormatProp: integer): boolean;
2485 | begin
2486 | Result := (StyleID >= 0) and (StyleID < FCountMax);
2487 |
2488 | if (Result) then
2489 | NumberFormatProp := FNumberAdditionalProps[StyleID]
2490 | else
2491 | NumberFormatProp := 0;
2492 | end; // TryGetNumberFormatAddProp
2493 |
2494 | // Separate number format string by ";".
2495 | // INPUT
2496 | // const NFStr: string - Number format string (like "nf1;nf2;nf3")
2497 | // RETURN
2498 | // integer - count of number format items
2499 | function TZEODSNumberFormatWriter.SeparateNFItems(const NFStr: string): integer;
2500 | var
2501 | i, l: integer;
2502 | b: boolean;
2503 | s: string;
2504 | ch: char;
2505 | begin
2506 | b := true;
2507 | s := '';
2508 |
2509 | l := length(NFStr);
2510 |
2511 | for i := 1 to l do
2512 | begin
2513 | ch := NFStr[i];
2514 |
2515 | if (ch = '"') then
2516 | b := not b;
2517 |
2518 | if (b) then
2519 | begin
2520 | if (ch = ';') then
2521 | begin
2522 | TryAddNFItem(s);
2523 | s := '';
2524 | end
2525 | else
2526 | s := s + ch;
2527 | end
2528 | else
2529 | s := s + ch;
2530 | end; // for i
2531 |
2532 | if (s <> '') then
2533 | TryAddNFItem(s);
2534 |
2535 | Result := FNFItemsCount;
2536 | end; // PrepareNFItems
2537 |
2538 | // Try to add NumberFormat item (from string "NF1;NF2;NF3")
2539 | // Checks:
2540 | // 1. Count of NF items (max = 3)
2541 | // 2. is NF item valid
2542 | // INPUT
2543 | // const NFStr: string - NF item
2544 | // RETURN
2545 | // boolean - true - item added
2546 | function TZEODSNumberFormatWriter.TryAddNFItem(const NFStr: string): boolean;
2547 | begin
2548 | Result := false;
2549 |
2550 | if (FNFItemsCount < ZE_MAX_NF_ITEMS_COUNT) then
2551 | begin
2552 | Result := FNFItems[FNFItemsCount].TryToParse(NFStr);
2553 | if (Result) then
2554 | inc(FNFItemsCount);
2555 | end;
2556 | end; // TryAddNFItem
2557 |
2558 | // Try to write number format to xml
2559 | // INPUT
2560 | // const xml: TZsspXMLWriterH - xml
2561 | // StyleID: integer - Style ID
2562 | // ANumberFormat: string - number format
2563 | // RETURN
2564 | // boolean - true - NumberFormat was written ok
2565 | function TZEODSNumberFormatWriter.TryWriteNumberFormat(const Xml: TZsspXMLWriterH; StyleID: integer;
2566 | ANumberFormat: string): boolean;
2567 | var
2568 | s: string;
2569 | _nfType: integer;
2570 | _nfName: string;
2571 |
2572 | function _WriteNumberNumber(NumProperties: integer = 0): boolean;
2573 | var
2574 | i: integer;
2575 | num: integer;
2576 | _item_name: string;
2577 |
2578 | begin
2579 | Result := false;
2580 | // NumberFormat = "part1;part2;part3"
2581 | // part1 - for numbers > 0 (or for condition1)
2582 | // part2 - for numbers < 0 (or for condition2)
2583 | // part3 - for 0 (or for other numbers if used condition1 and condition2)
2584 | // partX = [condition][color]number_format
2585 |
2586 | if (SeparateNFItems(ANumberFormat) > 0) then
2587 | begin
2588 | if (FNFItemsCount = 1) then
2589 | begin
2590 | FNFItems[0].WriteNumberStyle(Xml, _nfName, NumProperties);
2591 | Result := true;
2592 | end
2593 | else
2594 | begin
2595 | num := 0;
2596 | for i := 0 to FNFItemsCount - 2 do
2597 | begin
2598 | if (FNFItems[i].isCondition) then
2599 | s := FNFItems[i].Condition
2600 | else
2601 | case i of
2602 | 0:
2603 | s := '>0';
2604 | 1:
2605 | s := '< 0';
2606 | else
2607 | s := '';
2608 | end;
2609 |
2610 | if (s <> '') then
2611 | begin
2612 | _item_name := _nfName + 'P' + IntToStr(num);
2613 | FNFItems[FNFItemsCount - 1].AddCondition(s, _item_name);
2614 | FNFItems[i].WriteNumberStyle(Xml, _item_name, NumProperties, true);
2615 | inc(num);
2616 | end;
2617 | end; // for i
2618 |
2619 | FNFItems[FNFItemsCount - 1].WriteNumberStyle(Xml, _nfName, NumProperties);
2620 | Result := true;
2621 | end;
2622 | end; // if
2623 | end; // _WriteNumberNumber
2624 |
2625 | function _WriteCurrency(): boolean;
2626 | begin
2627 | Result := _WriteNumberNumber(ZE_NUMFORMAT_NUM_IS_CURRENCY);
2628 | end;
2629 |
2630 | function _WritePercentage(): boolean;
2631 | begin
2632 | Result := _WriteNumberNumber(ZE_NUMFORMAT_NUM_IS_PERCENTAGE);
2633 | end;
2634 |
2635 | function _WriteDateTime(): boolean;
2636 | var
2637 | _addProp: integer;
2638 | begin
2639 | Result := false;
2640 |
2641 | if (SeparateNFItems(ANumberFormat) > 0) then
2642 | begin
2643 | // For now use only first NF item
2644 | // TODO:
2645 | // Are conditions implements for date styles?
2646 | _addProp := FNFItems[0].WriteDateTimeStyle(Xml, _nfName);
2647 | _nfType := _nfType or _addProp;
2648 | Result := true;
2649 | end; // if
2650 | end; // _WriteDateTime
2651 |
2652 | function _WriteStringFormat(): boolean;
2653 | begin
2654 | Result := false;
2655 | if (SeparateNFItems(ANumberFormat) > 0) then
2656 | begin
2657 | // For now use only first NF item
2658 | // TODO:
2659 | // Are conditions implements for text styles?
2660 | FNFItems[0].WriteTextStyle(Xml, _nfName);
2661 | Result := true;
2662 | end; // if
2663 | end; // _WriteStringFormat
2664 |
2665 | function _WriteNumberStyle(): boolean;
2666 | begin
2667 | FNFItemsCount := 0;
2668 | _nfName := 'N' + IntToStr(FCurrentNFIndex);
2669 | Result := false;
2670 |
2671 | _nfType := GetNativeNumberFormatType(ANumberFormat);
2672 |
2673 | case (_nfType and $FF) of
2674 | ZE_NUMFORMAT_IS_NUMBER:
2675 | begin
2676 | if (_nfType and ZE_NUMFORMAT_NUM_IS_CURRENCY = ZE_NUMFORMAT_NUM_IS_CURRENCY) then
2677 | Result := _WriteCurrency()
2678 | else if (_nfType and ZE_NUMFORMAT_NUM_IS_PERCENTAGE = ZE_NUMFORMAT_NUM_IS_PERCENTAGE) then
2679 | Result := _WritePercentage()
2680 | else
2681 | Result := _WriteNumberNumber();
2682 | end;
2683 | ZE_NUMFORMAT_IS_DATETIME:
2684 | Result := _WriteDateTime();
2685 | ZE_NUMFORMAT_IS_STRING:
2686 | Result := _WriteStringFormat();
2687 | end;
2688 | end; // _WriteNumberStyle
2689 |
2690 | procedure _AddItem(const NFName: string);
2691 | begin
2692 | if (FCount >= FCountMax) then
2693 | begin
2694 | inc(FCountMax, 10);
2695 | SetLength(FItems, FCountMax);
2696 | SetLength(FNumberAdditionalProps, FCountMax);
2697 | end;
2698 |
2699 | FItems[FCount].StyleIndex := StyleID;
2700 | FItems[FCount].NumberFormatName := NFName;
2701 | FItems[FCount].NumberFormat := ANumberFormat;
2702 |
2703 | FNumberAdditionalProps[StyleID] := _nfType;
2704 |
2705 | inc(FCount);
2706 | end;
2707 |
2708 | function _CheckIsDuplicate(): boolean;
2709 | var
2710 | i: integer;
2711 | begin
2712 | Result := false;
2713 | for i := 0 to FCount - 1 do
2714 | if (FItems[i].NumberFormat = ANumberFormat) then
2715 | begin
2716 | _nfType := FNumberAdditionalProps[StyleID];
2717 | _AddItem(FItems[i].NumberFormatName);
2718 | Result := true;
2719 | break;
2720 | end;
2721 | end; // _CheckIsDuplicate
2722 |
2723 | begin
2724 | Result := false;
2725 | ANumberFormat := Trim(ANumberFormat);
2726 |
2727 | if ((ANumberFormat = '@') or (ANumberFormat = '')) then
2728 | exit;
2729 |
2730 | s := UpperCase(ANumberFormat);
2731 |
2732 | if ((s = 'GENERAL') or (s = 'STANDART')) then
2733 | exit;
2734 |
2735 | if (TryGetNumFormatByName(ANumberFormat, s)) then
2736 | if (s = '') then
2737 | exit
2738 | else
2739 | ANumberFormat := s;
2740 |
2741 | if (_CheckIsDuplicate()) then
2742 | Result := true
2743 | else
2744 | begin
2745 | Result := _WriteNumberStyle();
2746 |
2747 | if (Result) then
2748 | begin
2749 | _AddItem(_nfName);
2750 | inc(FCurrentNFIndex);
2751 | end;
2752 | end;
2753 | end; // TryWriteNumberFormat
2754 |
2755 | /// /::::::::::::: TODSNumberFormatMapItem :::::::::::::::::////
2756 |
2757 | constructor TODSNumberFormatMapItem.Create();
2758 | begin
2759 | FEmbededMaxCount := 10;
2760 | SetLength(FEmbededTextArray, FEmbededMaxCount);
2761 | FNumberFormatParser := TNumFormatParser.Create();
2762 | FDateTimeODSFormatParser := TZDateTimeODSFormatParser.Create();
2763 | end;
2764 |
2765 | destructor TODSNumberFormatMapItem.Destroy();
2766 | begin
2767 | SetLength(FEmbededTextArray, 0);
2768 | FreeAndNil(FNumberFormatParser);
2769 | FreeAndNil(FDateTimeODSFormatParser);
2770 | inherited Destroy;
2771 | end;
2772 |
2773 | procedure TODSNumberFormatMapItem.Clear();
2774 | begin
2775 | FCondition := '';
2776 | FisCondition := false;
2777 | FColorStr := '';
2778 | FisColor := false;
2779 | FNumberFormat := '';
2780 | FConditionsCount := 0;
2781 | end; // Clear
2782 |
2783 | function TODSNumberFormatMapItem.TryToParse(const FNStr: string): boolean;
2784 | var
2785 | s: string;
2786 | i: integer;
2787 | _IsQuote: boolean;
2788 | _IsBracket: boolean;
2789 | ch: char;
2790 | _isError: boolean;
2791 | _raw: string; // raw string without brackets
2792 | _tmp: string;
2793 |
2794 | procedure _ProcessOpenBracket();
2795 | begin
2796 | if (_IsBracket) then
2797 | _isError := true
2798 | else
2799 | begin
2800 | _raw := _raw + s;
2801 | s := '';
2802 | _IsBracket := true;
2803 | end;
2804 | end; // _ProcessOpenBracket
2805 |
2806 | procedure _ProcessCloseBracket();
2807 | begin
2808 | if (_IsBracket) then
2809 | begin
2810 | // is it color?
2811 | if (TryGetMapColorColor(Trim(UpperCase(s)), _tmp)) then
2812 | begin
2813 | FisColor := true;
2814 | FColorStr := _tmp;
2815 | end
2816 | else
2817 | // is it condition?
2818 | if (TryGetMapCondition(s, _tmp)) then
2819 | begin
2820 | FisCondition := true;
2821 | FCondition := _tmp;
2822 | end;
2823 |
2824 | // TODO: need add:
2825 | // calendar:
2826 | // [~buddhist]
2827 | // [~gengou]
2828 | // [~gregorian])
2829 | // [~hanja] [~hanja_yoil]
2830 | // [~hijri]
2831 | // [~jewish]
2832 | // [~ROC]
2833 | // NatNumX / DBNumX transliteration
2834 | // currency
2835 |
2836 | _IsBracket := false;
2837 | s := '';
2838 | end
2839 | else
2840 | _isError := true
2841 | end; // _ProcessCloseBracket
2842 |
2843 | procedure _ProcessQuote();
2844 | begin
2845 | if (not _IsBracket) then
2846 | _IsQuote := not _IsQuote;
2847 | end; // _ProcessQuote
2848 |
2849 | function _FinalCheck(): boolean;
2850 | begin
2851 | Result := true;
2852 |
2853 | if (not _IsQuote) then
2854 | begin
2855 | _raw := _raw + s;
2856 | s := '';
2857 | end;
2858 |
2859 | if (_IsQuote and (not _isError) and (s <> '')) then
2860 | begin
2861 | _raw := _raw + s + '"';
2862 | s := '';
2863 | _ProcessQuote();
2864 | end;
2865 |
2866 | // TODO: add checking for valid NF here
2867 |
2868 | end; // _FinalCheck
2869 |
2870 | begin
2871 | Result := false;
2872 | Clear();
2873 | _raw := '';
2874 |
2875 | _isError := false;
2876 | _IsQuote := false;
2877 | _IsBracket := false;
2878 | s := '';
2879 |
2880 | for i := 1 to length(FNStr) do
2881 | begin
2882 | ch := FNStr[i];
2883 |
2884 | if (ch = '"') then
2885 | _ProcessQuote();
2886 |
2887 | if (_IsQuote) then
2888 | s := s + ch
2889 | else
2890 | case (ch) of
2891 | '[':
2892 | _ProcessOpenBracket();
2893 | ']':
2894 | _ProcessCloseBracket();
2895 | else
2896 | s := s + ch;
2897 | end; // case
2898 | end; // for i
2899 |
2900 | if (_FinalCheck()) then
2901 | Result := not(_isError or _IsQuote or _IsBracket or (_raw = ''));
2902 |
2903 | if (Result) then
2904 | FNumberFormat := _raw;
2905 | end; // TryToParse
2906 |
2907 | function TODSNumberFormatMapItem.AddCondition(const ACondition, AStyleName: string): boolean;
2908 | begin
2909 | Result := FConditionsCount < 2;
2910 | if (Result) then
2911 | begin
2912 | FConditionsArray[FConditionsCount][0] := ACondition;
2913 | FConditionsArray[FConditionsCount][1] := AStyleName;
2914 | inc(FConditionsCount);
2915 | end;
2916 | end; // AddCondition
2917 |
2918 | procedure TODSNumberFormatMapItem.PrepareCommonStyleAttributes(const Xml: TZsspXMLWriterH; const AStyleName: string;
2919 | isVolatile: boolean = false);
2920 | begin
2921 | Xml.Attributes.Clear();
2922 | Xml.Attributes.Add(ZETag_Attr_StyleName, AStyleName);
2923 | if (isVolatile) then
2924 | Xml.Attributes.Add(ZETag_style_volatile, 'true');
2925 | end; // PrepareCommonStyleAttributes
2926 |
2927 | procedure TODSNumberFormatMapItem.WriteNumberStyle(const Xml: TZsspXMLWriterH; const AStyleName: string;
2928 | const NumProperties: integer; isVolatile: boolean = false);
2929 | var
2930 | i: integer;
2931 | _DecimalCount: integer;
2932 | _IntDigitsCount: integer;
2933 | _TotalDigitsCount: integer;
2934 | _MinIntDigitsCount: integer;
2935 | _currentpos: integer;
2936 | _isFirstText: boolean;
2937 | _firstText: string;
2938 | _txt_len: integer;
2939 | _numeratorDigitsCount: integer;
2940 | _denomenatorDigitsCount: integer;
2941 | _isFraction: boolean;
2942 | _isSci: boolean;
2943 | _exponentDigitsCount: integer;
2944 | s: string;
2945 |
2946 | //
2947 | procedure _WriteStyleMap(num: integer);
2948 | begin
2949 | Xml.Attributes.Clear();
2950 | Xml.Attributes.Add(ZETag_style_condition, FConditionsArray[num][0]);
2951 | Xml.Attributes.Add(ZETag_style_apply_style_name, FConditionsArray[num][1]);
2952 | Xml.WriteEmptyTag(ZETag_style_map, true, true);
2953 | end; // _WriteConditionItem
2954 |
2955 | //
2956 | procedure _WriteTextProperties();
2957 | begin
2958 | if (isColor) then
2959 | begin
2960 | Xml.Attributes.Clear();
2961 | Xml.Attributes.Add(ZETag_fo_color, ColorStr);
2962 | Xml.WriteEmptyTag(ZETag_style_text_properties, true, true);
2963 | end;
2964 | end; // _WriteTextProperties
2965 |
2966 | procedure _ParseFormat();
2967 | var
2968 | i: integer;
2969 | _IsQuote: boolean;
2970 | ch: char;
2971 | _isDecimal: boolean;
2972 |
2973 | // Check digit
2974 | // INPUT
2975 | // isExtrazero: boolean - true = 0, false = #
2976 | procedure _CheckDigit(isExtrazero: boolean);
2977 | begin
2978 | if (_isSci) then
2979 | begin
2980 | inc(_exponentDigitsCount);
2981 | exit;
2982 | end;
2983 |
2984 | inc(_TotalDigitsCount);
2985 | inc(_currentpos);
2986 |
2987 | if (_isDecimal) then
2988 | begin
2989 | inc(_DecimalCount);
2990 | end
2991 | else
2992 | begin
2993 | inc(_IntDigitsCount);
2994 | if (isExtrazero) then
2995 | inc(_MinIntDigitsCount);
2996 | end;
2997 | end; // _CheckDigit
2998 |
2999 | // Calc symbols "?" for fraction numerator and denominator
3000 | // TODO: is it possible to use 0 or # in fraction as numerator or/and denominator?
3001 | procedure _CheckFractionDigit();
3002 | begin
3003 | if (_isFraction) then
3004 | inc(_denomenatorDigitsCount)
3005 | else
3006 | inc(_numeratorDigitsCount);
3007 | end; // _CheckFractionDigit
3008 |
3009 | procedure _AddEmbebedText(isAdd: boolean);
3010 | begin
3011 | if (isAdd) then
3012 | begin
3013 | if ((_TotalDigitsCount > 0) or _isFirstText) then
3014 | begin
3015 | if (FEmbededTextCount >= FEmbededMaxCount) then
3016 | begin
3017 | inc(FEmbededMaxCount, 10);
3018 | SetLength(FEmbededTextArray, FEmbededMaxCount);
3019 | end;
3020 | FEmbededTextArray[FEmbededTextCount].Txt := s;
3021 | FEmbededTextArray[FEmbededTextCount].NumberPosition := _currentpos;
3022 | inc(FEmbededTextCount);
3023 | end
3024 | else
3025 | begin
3026 | _isFirstText := true;
3027 | _firstText := s;
3028 | end;
3029 | s := '';
3030 | end;
3031 | end; // _AddEmbebedText
3032 |
3033 | procedure _ProgressPercent();
3034 | begin
3035 | s := s + ch;
3036 | if ((not _isFirstText) and (_TotalDigitsCount = 0) and (FEmbededTextCount = 0)) then
3037 | begin
3038 | _isFirstText := true;
3039 | _firstText := s;
3040 | s := '';
3041 | end
3042 | else if (i <> _txt_len) then
3043 | _AddEmbebedText(true);
3044 | end; // _ProgressPercent
3045 |
3046 | begin
3047 | s := '';
3048 | _IsQuote := false;
3049 | _isDecimal := false;
3050 | i := 1;
3051 | _txt_len := length(FNumberFormat);
3052 | while (i <= _txt_len) do
3053 | begin
3054 | ch := FNumberFormat[i];
3055 |
3056 | if ((ch = '\') and (not _IsQuote)) then
3057 | begin
3058 | inc(i);
3059 | if (i > _txt_len) then
3060 | break;
3061 |
3062 | s := FNumberFormat[i];
3063 | _AddEmbebedText(true);
3064 | ch := #0;
3065 | end;
3066 |
3067 | if (ch = '"') then
3068 | begin
3069 | _AddEmbebedText(_IsQuote and (not _isDecimal));
3070 | _IsQuote := not _IsQuote;
3071 | end;
3072 |
3073 | if (_IsQuote) then
3074 | begin
3075 | if (ch <> '"') then
3076 | s := s + ch
3077 | end
3078 | else
3079 | case (ch) of
3080 | '0':
3081 | _CheckDigit(true);
3082 | '#':
3083 | _CheckDigit(false);
3084 | '.':
3085 | _isDecimal := true;
3086 | '?':
3087 | _CheckFractionDigit();
3088 | '/':
3089 | _isFraction := true;
3090 | 'E', 'e':
3091 | _isSci := true;
3092 | '+':
3093 | ; // ??
3094 | '-':
3095 | ; // ??
3096 | ' ':
3097 | ;
3098 | '%':
3099 | _ProgressPercent();
3100 | end;
3101 | inc(i);
3102 | end; // while i
3103 | end; // _ParseFormat
3104 |
3105 | //
3106 | procedure _WriteNumberMain();
3107 | var
3108 | i: integer;
3109 | procedure _FillMainAttrib();
3110 | begin
3111 | Xml.Attributes.Clear();
3112 |
3113 | if (_isFraction) then
3114 | if ((_numeratorDigitsCount <= 0) or (_denomenatorDigitsCount <= 0)) then
3115 | _isFraction := false;
3116 |
3117 | // TODO: it is trouble. For now ignore fraction and sci.
3118 | if (_isFraction and _isSci) then
3119 | begin
3120 | _isFraction := false;
3121 | _isSci := false;
3122 | end;
3123 |
3124 | if (_isFraction) then
3125 | begin
3126 | Xml.Attributes.Add(ZETag_number_min_numerator_digits, IntToStr(_numeratorDigitsCount));
3127 | Xml.Attributes.Add(ZETag_number_min_denominator_digits, IntToStr(_denomenatorDigitsCount));
3128 | end
3129 | else
3130 | begin
3131 | if (_DecimalCount > 0) then
3132 | Xml.Attributes.Add(ZETag_number_decimal_places, IntToStr(_DecimalCount));
3133 | if (_isSci) then
3134 | Xml.Attributes.Add(ZETag_number_min_exponent_digits, IntToStr(_exponentDigitsCount));
3135 | end;
3136 | Xml.Attributes.Add(ZETag_number_min_integer_digits, IntToStr(_MinIntDigitsCount));
3137 | end; // _FillMainAttrib
3138 |
3139 | procedure _StartEmbededTextTag();
3140 | begin
3141 | // Empty tag for:
3142 | // number:fraction
3143 | // number:scientific-number
3144 |
3145 | if (_isFraction) then
3146 | Xml.WriteEmptyTag(ZETag_number_fraction, true, true)
3147 | else if (_isSci) then
3148 | Xml.WriteEmptyTag(ZETag_number_scientific_number, true, true)
3149 | else
3150 | Xml.WriteTagNode(ZETag_number_number, true, true, false);
3151 | end; // _StartEmbededTextTag
3152 |
3153 | procedure _EndEmbededTextTag();
3154 | begin
3155 | if ((not _isFraction) and (not _isSci)) then
3156 | Xml.WriteEndTagNode(); // number:number
3157 | end; // _EndEmbededTextTag
3158 |
3159 | // or
3160 | // or
3161 | //
3162 | procedure _WriteEmptyNumberTag();
3163 | begin
3164 | if (_isFraction) then
3165 | Xml.WriteEmptyTag(ZETag_number_fraction, true, true)
3166 | else if (_isSci) then
3167 | Xml.WriteEmptyTag(ZETag_number_scientific_number, true, true)
3168 | else
3169 | Xml.WriteEmptyTag(ZETag_number_number, true, true);
3170 | end; // _WriteEmptyNumberTag
3171 |
3172 | begin
3173 | FEmbededTextCount := 0;
3174 | _DecimalCount := 0;
3175 | _currentpos := 0;
3176 | _IntDigitsCount := 0;
3177 | _TotalDigitsCount := 0;
3178 | _MinIntDigitsCount := 0;
3179 | _isFirstText := false;
3180 | _firstText := '';
3181 | _numeratorDigitsCount := 0;
3182 | _denomenatorDigitsCount := 0;
3183 | _isFraction := false;
3184 | _isSci := false;
3185 | _exponentDigitsCount := 0;
3186 |
3187 | _ParseFormat();
3188 |
3189 | if (_isFirstText) then
3190 | begin
3191 | Xml.Attributes.Clear();
3192 | Xml.WriteTag(ZETag_number_text, _firstText, true, false, true);
3193 | end;
3194 |
3195 | _FillMainAttrib();
3196 |
3197 | if (FEmbededTextCount > 0) then
3198 | begin
3199 | _StartEmbededTextTag();
3200 |
3201 | // TODO: Is it possible to use embeded text for fraction and scientific formats?
3202 | if (not _isFraction) then
3203 | for i := 0 to FEmbededTextCount - 1 do
3204 | begin
3205 | Xml.Attributes.Clear();
3206 | Xml.Attributes.Add(ZETag_number_position, IntToStr(_IntDigitsCount - FEmbededTextArray[i].NumberPosition));
3207 | Xml.WriteTag(ZETag_number_embedded_text, FEmbededTextArray[i].Txt, true, false, true);
3208 | end;
3209 |
3210 | _EndEmbededTextTag();
3211 | end
3212 | else
3213 | _WriteEmptyNumberTag();
3214 |
3215 | if (s <> '') then
3216 | begin
3217 | Xml.Attributes.Clear();
3218 | Xml.WriteTag(ZETag_number_text, s, true, false, true);
3219 | end;
3220 | end; // _WriteNumberMain
3221 |
3222 | begin
3223 | PrepareCommonStyleAttributes(Xml, AStyleName, isVolatile);
3224 |
3225 | if (NumProperties = ZE_NUMFORMAT_NUM_IS_PERCENTAGE) then
3226 | Xml.WriteTagNode(ZETag_number_percentage_style, true, true, false)
3227 | else if (NumProperties = ZE_NUMFORMAT_NUM_IS_CURRENCY) then
3228 | Xml.WriteTagNode(ZETag_number_currency_style, true, true, false)
3229 | else
3230 | Xml.WriteTagNode(ZETag_number_number_style, true, true, false);
3231 |
3232 | _WriteTextProperties();
3233 |
3234 | _WriteNumberMain();
3235 |
3236 | for i := 0 to FConditionsCount - 1 do
3237 | _WriteStyleMap(i);
3238 |
3239 | Xml.WriteEndTagNode(); // number:number-style
3240 | end; // WriteNumberStyle
3241 |
3242 | // Write number text style item ( )
3243 | // INPUT
3244 | // const xml: TZsspXMLWriterH - xml
3245 | // const AStyleName: string - style name
3246 | // isVolatile: boolean - is volatile? (for now - ignore)
3247 | procedure TODSNumberFormatMapItem.WriteTextStyle(const Xml: TZsspXMLWriterH; const AStyleName: string;
3248 | isVolatile: boolean = false);
3249 | var
3250 | _isText: boolean;
3251 | begin
3252 | _isText := false;
3253 | PrepareCommonStyleAttributes(Xml, AStyleName, isVolatile);
3254 |
3255 | Xml.WriteTagNode(ZETag_number_text_style, true, true, false);
3256 |
3257 | Xml.Attributes.Clear();
3258 | FNumberFormatParser.BeginRead(FNumberFormat);
3259 |
3260 | while (FNumberFormatParser.ReadSymbol()) do
3261 | begin
3262 | case (FNumberFormatParser.ReadedSymbolType) of
3263 | 0:
3264 | begin
3265 | if (FNumberFormatParser.ReadedSymbol = '@') then
3266 | begin
3267 | _isText := true;
3268 | Xml.WriteEmptyTag(ZETag_number_text_content, true, false);
3269 | end;
3270 | end;
3271 | 2, 3:
3272 | begin
3273 | Xml.WriteTag(ZETag_number_text, FNumberFormatParser.ReadedSymbol, true, false, true);
3274 | end;
3275 | end; // case
3276 | end; // while
3277 |
3278 | if (not _isText) then
3279 | Xml.WriteEmptyTag(ZETag_number_text_content, true, false);
3280 |
3281 | Xml.WriteEndTagNode(); // number:text-style
3282 | end; // WriteTextStyle
3283 |
3284 | function TODSNumberFormatMapItem.WriteDateTimeStyle(const Xml: TZsspXMLWriterH; const AStyleName: string;
3285 | isVolatile: boolean = false): integer;
3286 | var
3287 | s, _tagName: string;
3288 | procedure _WriteYear(var item: TZDateTimeProcessItem);
3289 | begin
3290 | if (item.Len > 2) then
3291 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3292 |
3293 | Xml.WriteEmptyTag(ZETag_number_year, true, false);
3294 | end; // _WriteYear
3295 |
3296 | procedure _WriteMonth(var item: TZDateTimeProcessItem);
3297 | begin
3298 | if ((item.Len >= 4) or (item.Len = 2)) then
3299 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3300 |
3301 | if (item.Len >= 3) then
3302 | Xml.Attributes.Add(ZETag_number_textual, 'true');
3303 |
3304 | Xml.WriteEmptyTag(ZETag_number_month, true, false);
3305 | end; // _WriteMonth
3306 |
3307 | procedure _WriteDay(var item: TZDateTimeProcessItem);
3308 | begin
3309 | if (item.Len > 2) then
3310 | s := ZETag_number_day_of_week
3311 | else
3312 | s := ZETag_number_day;
3313 |
3314 | if ((item.Len >= 4) or (item.Len = 2)) then
3315 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3316 |
3317 | Xml.WriteEmptyTag(s, true, false);
3318 | end; // _WriteDay
3319 |
3320 | procedure _WriteHour(var item: TZDateTimeProcessItem);
3321 | begin
3322 | if (item.Len >= 2) then
3323 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3324 |
3325 | Xml.WriteEmptyTag(ZETag_number_hours, true, false);
3326 | end; // _WriteHour
3327 |
3328 | procedure _WriteMinute(var item: TZDateTimeProcessItem);
3329 | begin
3330 | if (item.Len >= 2) then
3331 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3332 |
3333 | Xml.WriteEmptyTag(ZETag_number_minutes, true, false);
3334 | end; // _WriteMinute
3335 |
3336 | procedure _WriteSecond(var item: TZDateTimeProcessItem);
3337 | begin
3338 | if (item.Len >= 2) then
3339 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3340 |
3341 | if (item.Settings > 0) then
3342 | Xml.Attributes.Add(ZETag_number_decimal_places, IntToStr(item.Settings));
3343 |
3344 | Xml.WriteEmptyTag(ZETag_number_seconds, true, false);
3345 | end; // _WriteSecond
3346 |
3347 | procedure _WriteWeek(var item: TZDateTimeProcessItem);
3348 | begin
3349 | Xml.WriteEmptyTag(ZETag_number_week_of_year, true, false);
3350 | end; // _WriteWeek
3351 |
3352 | procedure _WriteQuarter(var item: TZDateTimeProcessItem);
3353 | begin
3354 | if (item.Len >= 2) then
3355 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3356 |
3357 | Xml.WriteEmptyTag(ZETag_number_quarter, true, false);
3358 | end; // _WriteQuarter
3359 |
3360 | procedure _WriteEraYear(var item: TZDateTimeProcessItem);
3361 | begin
3362 | // TODO
3363 | (*
3364 | if (item.Len >= 2) then
3365 | xml.Attributes.Add(ZETag_number_style, ZETag_long);
3366 |
3367 | xml.WriteEmptyTag(ZETag_number_quarter, true, false);
3368 | *)
3369 | end; // _WriteEraYear
3370 |
3371 | procedure _WriteEraJap(var item: TZDateTimeProcessItem);
3372 | begin
3373 | if (item.Len >= 2) then
3374 | Xml.Attributes.Add(ZETag_number_style, ZETag_long);
3375 |
3376 | Xml.WriteEmptyTag(ZETag_number_era, true, false);
3377 | end; // _WriteEraJap
3378 |
3379 | procedure _WriteItems();
3380 | var
3381 | i: integer;
3382 | begin
3383 | for i := 0 to FDateTimeODSFormatParser.FCount - 1 do
3384 | begin
3385 | Xml.Attributes.Clear();
3386 | case (FDateTimeODSFormatParser.FItems[i].ItemType) of
3387 | ZE_DATETIME_ITEM_TEXT:
3388 | Xml.WriteTag(ZETag_number_text, FDateTimeODSFormatParser.FItems[i].TextValue, true, false, true);
3389 | ZE_DATETIME_ITEM_YEAR:
3390 | _WriteYear(FDateTimeODSFormatParser.FItems[i]);
3391 | ZE_DATETIME_ITEM_MONTH:
3392 | _WriteMonth(FDateTimeODSFormatParser.FItems[i]);
3393 | ZE_DATETIME_ITEM_DAY:
3394 | _WriteDay(FDateTimeODSFormatParser.FItems[i]);
3395 | ZE_DATETIME_ITEM_HOUR:
3396 | _WriteHour(FDateTimeODSFormatParser.FItems[i]);
3397 | ZE_DATETIME_ITEM_MINUTE:
3398 | _WriteMinute(FDateTimeODSFormatParser.FItems[i]);
3399 | ZE_DATETIME_ITEM_SECOND:
3400 | _WriteSecond(FDateTimeODSFormatParser.FItems[i]);
3401 | ZE_DATETIME_ITEM_WEEK:
3402 | _WriteWeek(FDateTimeODSFormatParser.FItems[i]);
3403 | ZE_DATETIME_ITEM_QUARTER:
3404 | _WriteQuarter(FDateTimeODSFormatParser.FItems[i]);
3405 | ZE_DATETIME_ITEM_ERA_JAP:
3406 | _WriteEraJap(FDateTimeODSFormatParser.FItems[i]);
3407 | ZE_DATETIME_ITEM_ERA_YEAR:
3408 | _WriteEraYear(FDateTimeODSFormatParser.FItems[i]);
3409 | ZE_DATETIME_ITEM_AMPM:
3410 | Xml.WriteEmptyTag(ZETag_number_am_pm, true, false);
3411 | end; // case
3412 | end; // for i
3413 | end; // _WriteItems
3414 |
3415 | function _GetAdditionalProperties(): integer;
3416 | var
3417 | i: integer;
3418 | begin
3419 | for i := 0 to FDateTimeODSFormatParser.FCount - 1 do
3420 | case (FDateTimeODSFormatParser.FItems[i].ItemType) of
3421 | ZE_DATETIME_ITEM_YEAR, ZE_DATETIME_ITEM_MONTH, ZE_DATETIME_ITEM_DAY, ZE_DATETIME_ITEM_WEEK,
3422 | ZE_DATETIME_ITEM_QUARTER, ZE_DATETIME_ITEM_ERA_JAP, ZE_DATETIME_ITEM_ERA_YEAR:
3423 | begin
3424 | Result := 0;
3425 | exit;
3426 | end;
3427 | end; // case
3428 | Result := ZE_NUMFORMAT_DATE_IS_ONLY_TIME;
3429 | end; // _GetAdditionalProperties
3430 |
3431 | begin
3432 | Result := 0;
3433 | FDateTimeODSFormatParser.TryToParseDateFormat(FNumberFormat, FNumberFormatParser);
3434 | FDateTimeODSFormatParser.DeleteRepeatedItems();
3435 |
3436 | if (FDateTimeODSFormatParser.GetValidCount() > 0) then
3437 | begin
3438 | Result := _GetAdditionalProperties();
3439 |
3440 | if (Result = 0) then
3441 | _tagName := ZETag_number_date_style
3442 | else
3443 | _tagName := ZETag_number_time_style;
3444 |
3445 | PrepareCommonStyleAttributes(Xml, AStyleName, isVolatile);
3446 | Xml.WriteTagNode(_tagName, true, true, false);
3447 |
3448 | _WriteItems();
3449 |
3450 | Xml.WriteEndTagNode(); // number:date-style / number:time-style
3451 | end;
3452 | end; // WriteDateTimeStyle
3453 |
3454 | end.
3455 |
--------------------------------------------------------------------------------
/source/Excel4Delphi.Utils.pas:
--------------------------------------------------------------------------------
1 | unit Excel4Delphi.Utils;
2 |
3 | interface
4 |
5 | uses
6 | System.SysUtils, System.UITypes, System.Types, System.Classes, System.Math,
7 | Excel4Delphi, Excel4Delphi.Xml, Excel4Delphi.Common;
8 |
9 | ///
10 | /// Сохраняет страницу TZWorkBook в поток в формате HTML
11 | ///
12 | function SaveXmlssToHtml(sheet: TZSheet; CodePageName: string = 'UTF-8'): string;
13 |
14 | implementation
15 |
16 | uses
17 | Excel4Delphi.NumberFormats, System.StrUtils, System.AnsiStrings;
18 |
19 | function SaveXmlssToHtml(sheet: TZSheet; CodePageName: string = 'UTF-8'): string;
20 | var
21 | Xml: TZsspXMLWriterH;
22 | i, j, t, l, r: integer;
23 | NumTopLeft, NumArea: integer;
24 | s, value, numformat: string;
25 | Att: TZAttributesH;
26 | max_width: Real;
27 | strArray: TArray;
28 | Stream: TStringStream;
29 |
30 | function HTMLStyleTable(name: string; const Style: TZStyle): string;
31 | var
32 | s: string;
33 | i, l: integer;
34 | begin
35 | result := #13#10 + ' .' + name + '{'#13#10;
36 | for i := 0 to 3 do
37 | begin
38 | s := 'border-';
39 | l := 0;
40 | case i of
41 | 0:
42 | s := s + 'left:';
43 | 1:
44 | s := s + 'top:';
45 | 2:
46 | s := s + 'right:';
47 | 3:
48 | s := s + 'bottom:';
49 | end;
50 | s := s + '#' + ColorToHTMLHex(Style.Border[TZBordersPos(i)].Color);
51 | if Style.Border[TZBordersPos(i)].Weight <> 0 then
52 | s := s + ' ' + IntToStr(Style.Border[TZBordersPos(i)].Weight) + 'px'
53 | else
54 | inc(l);
55 | case Style.Border[TZBordersPos(i)].LineStyle of
56 | ZEContinuous:
57 | s := s + ' ' + 'solid';
58 | ZEHair:
59 | s := s + ' ' + 'solid';
60 | ZEDot:
61 | s := s + ' ' + 'dotted';
62 | ZEDashDotDot:
63 | s := s + ' ' + 'dotted';
64 | ZEDash:
65 | s := s + ' ' + 'dashed';
66 | ZEDashDot:
67 | s := s + ' ' + 'dashed';
68 | ZESlantDashDot:
69 | s := s + ' ' + 'dashed';
70 | ZEDouble:
71 | s := s + ' ' + 'double';
72 | else
73 | inc(l);
74 | end;
75 | s := s + ';';
76 | if l <> 2 then
77 | result := result + s + #13#10;
78 | end;
79 | result := result + 'background:#' + ColorToHTMLHex(Style.BGColor) + ';}';
80 | end;
81 |
82 | function HTMLStyleFont(name: string; const Style: TZStyle): string;
83 | begin
84 | result := #13#10 + ' .' + name + '{'#13#10;
85 | result := result + 'color:#' + ColorToHTMLHex(Style.Font.Color) + ';';
86 | result := result + 'font-size:' + FloatToStr(Style.Font.Size, TFormatSettings.Invariant) + 'px;';
87 | result := result + 'font-family:' + Style.Font.name + ';}';
88 | end;
89 |
90 | begin
91 | result := '';
92 | Stream := TStringStream.Create('', TEncoding.UTF8);
93 | Xml := TZsspXMLWriterH.Create(Stream);
94 | try
95 | Xml.TabLength := 1;
96 | // start
97 | Xml.Attributes.Clear();
98 | Xml.WriteRaw
99 | ('',
100 | true, false);
101 | Xml.WriteTagNode('HTML', true, true, false);
102 | Xml.WriteTagNode('HEAD', true, true, false);
103 | Xml.WriteTag('TITLE', sheet.Title, true, false, false);
104 |
105 | // styles
106 | s := 'body {';
107 | s := s + 'background:#' + ColorToHTMLHex(sheet.WorkBook.Styles.DefaultStyle.BGColor) + ';';
108 | s := s + 'color:#' + ColorToHTMLHex(sheet.WorkBook.Styles.DefaultStyle.Font.Color) + ';';
109 | s := s + 'font-size:' + FloatToStr(sheet.WorkBook.Styles.DefaultStyle.Font.Size, TFormatSettings.Invariant) + 'px;';
110 | s := s + 'font-family:' + sheet.WorkBook.Styles.DefaultStyle.Font.name + ';}';
111 |
112 | s := s + HTMLStyleTable('T19', sheet.WorkBook.Styles.DefaultStyle);
113 | s := s + HTMLStyleFont('F19', sheet.WorkBook.Styles.DefaultStyle);
114 |
115 | for i := 0 to sheet.WorkBook.Styles.Count - 1 do
116 | begin
117 | s := s + HTMLStyleTable('T' + IntToStr(i + 20), sheet.WorkBook.Styles[i]);
118 | s := s + HTMLStyleFont('F' + IntToStr(i + 20), sheet.WorkBook.Styles[i]);
119 | end;
120 |
121 | Xml.WriteTag('STYLE', s, true, true, false);
122 | Xml.Attributes.Add('HTTP-EQUIV', 'CONTENT-TYPE');
123 |
124 | s := '';
125 | if trim(CodePageName) > '' then
126 | s := '; CHARSET=' + CodePageName;
127 |
128 | Xml.Attributes.Add('CONTENT', 'TEXT/HTML' + s);
129 | Xml.WriteTag('META', '', true, false, false);
130 | Xml.WriteEndTagNode(); // HEAD
131 |
132 | max_width := 0.0;
133 | for i := 0 to sheet.ColCount - 1 do
134 | max_width := max_width + sheet.ColWidths[i];
135 |
136 | // BODY
137 | Xml.Attributes.Clear();
138 | Xml.WriteTagNode('BODY', true, true, false);
139 |
140 | // Table
141 | Xml.Attributes.Clear();
142 | Xml.Attributes.Add('cellSpacing', '0');
143 | Xml.Attributes.Add('border', '0');
144 | Xml.Attributes.Add('width', FloatToStr(max_width).Replace(',', '.'));
145 | Xml.WriteTagNode('TABLE', true, true, false);
146 |
147 | Att := TZAttributesH.Create();
148 | Att.Clear();
149 | for i := 0 to sheet.RowCount - 1 do
150 | begin
151 | Xml.Attributes.Clear();
152 | Xml.Attributes.Add('height', FloatToStr(sheet.RowHeights[i]).Replace(',', '.'));
153 | Xml.WriteTagNode('TR', true, true, true);
154 | Xml.Attributes.Clear();
155 | for j := 0 to sheet.ColCount - 1 do
156 | begin
157 | NumTopLeft := sheet.MergeCells.InLeftTopCorner(j, i);
158 | NumArea := sheet.MergeCells.InMergeRange(j, i);
159 | // если ячейка входит в объединённые области и не является
160 | // верхней левой ячейкой в этой области - пропускаем её
161 | if not((NumArea >= 0) and (NumTopLeft = -1)) then
162 | begin
163 | Xml.Attributes.Clear();
164 | if NumTopLeft >= 0 then
165 | begin
166 | t := sheet.MergeCells.Items[NumTopLeft].Right - sheet.MergeCells.Items[NumTopLeft].Left;
167 | if t > 0 then
168 | Xml.Attributes.Add('colspan', IntToStr(t + 1));
169 | t := sheet.MergeCells.Items[NumTopLeft].Bottom - sheet.MergeCells.Items[NumTopLeft].Top;
170 | if t > 0 then
171 | Xml.Attributes.Add('rowspan', IntToStr(t + 1));
172 | end;
173 | t := sheet.Cell[j, i].CellStyle;
174 | if sheet.WorkBook.Styles[t].Alignment.Horizontal = ZHCenter then
175 | Xml.Attributes.Add('align', 'center')
176 | else if sheet.WorkBook.Styles[t].Alignment.Horizontal = ZHRight then
177 | Xml.Attributes.Add('align', 'right')
178 | else if sheet.WorkBook.Styles[t].Alignment.Horizontal = ZHJustify then
179 | Xml.Attributes.Add('align', 'justify');
180 | numformat := sheet.WorkBook.Styles[t].NumberFormat;
181 | Xml.Attributes.Add('class', 'T' + IntToStr(t + 20));
182 | Xml.Attributes.Add('width', IntToStr(sheet.Columns[j].WidthPix) + 'px');
183 |
184 | Xml.WriteTagNode('TD', true, false, false);
185 | Xml.Attributes.Clear();
186 | Att.Clear();
187 | Att.Add('class', 'F' + IntToStr(t + 20));
188 | if TFontStyle.fsbold in sheet.WorkBook.Styles[t].Font.Style then
189 | Xml.WriteTagNode('B', false, false, false);
190 | if TFontStyle.fsItalic in sheet.WorkBook.Styles[t].Font.Style then
191 | Xml.WriteTagNode('I', false, false, false);
192 | if TFontStyle.fsUnderline in sheet.WorkBook.Styles[t].Font.Style then
193 | Xml.WriteTagNode('U', false, false, false);
194 | if TFontStyle.fsStrikeOut in sheet.WorkBook.Styles[t].Font.Style then
195 | Xml.WriteTagNode('S', false, false, false);
196 |
197 | l := Length(sheet.Cell[j, i].Href);
198 | if l > 0 then
199 | begin
200 | Xml.Attributes.Add('href', sheet.Cell[j, i].Href);
201 | // target?
202 | Xml.WriteTagNode('A', false, false, false);
203 | Xml.Attributes.Clear();
204 | end;
205 |
206 | value := sheet.Cell[j, i].Data;
207 |
208 | // value := value.Replace(#13#10, '
');
209 | case sheet.Cell[j, i].CellType of
210 | TZCellType.ZENumber:
211 | begin
212 | r := numformat.IndexOf('.');
213 | if r > -1 then
214 | begin
215 | value := FloatToStrF(sheet.Cell[j, i].AsDouble, ffNumber, 12,
216 | Min(4, Max(0, numformat.Substring(r).Length - 1)));
217 | end
218 | else
219 | begin
220 | value := FloatToStr(sheet.Cell[j, i].AsDouble);
221 | end;
222 | end;
223 | TZCellType.ZEDateTime:
224 | begin
225 | // todo: make datetimeformat from cell NumberFormat
226 | value := FormatDateTime('dd.mm.yyyy', sheet.Cell[j, i].AsDateTime);
227 | end;
228 | end;
229 | strArray := value.Split([#13, #10], TStringSplitOptions.ExcludeEmpty);
230 | for r := 0 to Length(strArray) - 1 do
231 | begin
232 | if r > 0 then
233 | Xml.WriteTag('BR', '');
234 | Xml.WriteTag('FONT', strArray[r], Att, false, false, true);
235 | end;
236 |
237 | if l > 0 then
238 | Xml.WriteEndTagNode(); // A
239 |
240 | if TFontStyle.fsbold in sheet.WorkBook.Styles[t].Font.Style then
241 | Xml.WriteEndTagNode(); // B
242 | if TFontStyle.fsItalic in sheet.WorkBook.Styles[t].Font.Style then
243 | Xml.WriteEndTagNode(); // I
244 | if TFontStyle.fsUnderline in sheet.WorkBook.Styles[t].Font.Style then
245 | Xml.WriteEndTagNode(); // U
246 | if TFontStyle.fsStrikeOut in sheet.WorkBook.Styles[t].Font.Style then
247 | Xml.WriteEndTagNode(); // S
248 | Xml.WriteEndTagNode(); // TD
249 | end;
250 |
251 | end;
252 | Xml.WriteEndTagNode(); // TR
253 | end;
254 |
255 | Xml.WriteEndTagNode(); // BODY
256 | Xml.WriteEndTagNode(); // HTML
257 | Xml.EndSaveTo();
258 | result := Stream.DataString;
259 | FreeAndNil(Att);
260 | finally
261 | Xml.Free();
262 | Stream.Free();
263 | end;
264 | end;
265 |
266 | end.
267 |
--------------------------------------------------------------------------------