KD2 Framework  Check-in [f4a1bc9853]

Overview
Comment:TableToODS: fix bugs with 0,00 values, and use default locale than can be modified
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: f4a1bc985331586e287fdc632ae10d63dbc3cfcc
User & Date: bohwaz on 2023-04-08 13:26:52
Other Links: manifest | tags
Context
2023-04-08
13:27
Implement TableToXLSX (doesn't support styles yet) check-in: a4f9c6aebb user: bohwaz tags: trunk
13:26
TableToODS: fix bugs with 0,00 values, and use default locale than can be modified check-in: f4a1bc9853 user: bohwaz tags: trunk
13:26
TableExport: implement XLSX export check-in: be28df917f user: bohwaz tags: trunk
Changes

Modified src/lib/KD2/HTML/TableToODS.php from [3fcb97742f] to [2875c39d87].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use DOMNode;


/**
 * This class takes one or more HTML tables, and convert them to a single ODS document.
 *
 * - a basic set of CSS properties are supported!
 * - colspan (but not rowspan)
 * - automatic column width
 * - custom CSS properties
 * - each table is handled as a sheet, the <caption> will act as the name of the sheet
 * - detection of cell type, or force cell type using '-spreadsheet-cell-type'
 * - provide the real number via the "data-spreadsheet-value" HTML attribute
 *   (eg. if the number is displayed as a graph, or something like that)
 * - provide the real date via the "data-spreadsheet-value" attribute
 *
 * What is NOT supported:
 * - rowspan
 * - formulas
 *
 * Usage: $ods = new TableToODS; $ods->import('<table...</table>'); $ods->save('file.ods');
 *
 * Supported CSS properties:
 * - the following color names: black, white, red, green, blue, yellow, magenta, cyan
 * - 'initial' value to restore to default







|









|







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use DOMNode;


/**
 * This class takes one or more HTML tables, and convert them to a single ODS document.
 *
 * - a basic set of CSS properties are supported!
 * - support for colspan and rowspan
 * - automatic column width
 * - custom CSS properties
 * - each table is handled as a sheet, the <caption> will act as the name of the sheet
 * - detection of cell type, or force cell type using '-spreadsheet-cell-type'
 * - provide the real number via the "data-spreadsheet-value" HTML attribute
 *   (eg. if the number is displayed as a graph, or something like that)
 * - provide the real date via the "data-spreadsheet-value" attribute
 *
 * What is NOT supported:
 * - cells using rowspan AND colspan at the same time
 * - formulas
 *
 * Usage: $ods = new TableToODS; $ods->import('<table...</table>'); $ods->save('file.ods');
 *
 * Supported CSS properties:
 * - the following color names: black, white, red, green, blue, yellow, magenta, cyan
 * - 'initial' value to restore to default
77
78
79
80
81
82
83

84
85
86
87
88
89
90
	protected array $rows = [];
	protected int $row_index = 0;
	protected int $col_index = 0;
	protected int $count = 0;
	protected array $columns_widths = [];

	public string $default_sheet_name = 'Sheet%d';


	const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>';

	const DATA_TYPES = [
		'number',
		'date',
		'currency',







>







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
	protected array $rows = [];
	protected int $row_index = 0;
	protected int $col_index = 0;
	protected int $count = 0;
	protected array $columns_widths = [];

	public string $default_sheet_name = 'Sheet%d';
	public string $default_locale = 'fr_FR';

	const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>';

	const DATA_TYPES = [
		'number',
		'date',
		'currency',
299
300
301
302
303
304
305

306
307
308
309
310
311
312
313
	{
		// Skip rowspan
		while (isset($this->rows[$this->row_index][$this->col_index])) {
			$this->col_index++;
		}

		$value = trim($value);

		$type = $this->getCellType($value, $attributes['type'] ?? ($styles['-spreadsheet-cell-type'] ?? null));

		// Use real type in styles, useful to set the correct style
		$styles['-spreadsheet-cell-type'] = $type;

		$end = '';
		$cell = sprintf('<table:table-cell table:style-name="%s"', $this->newStyle('cell', $styles) ?? 'Default');








>
|







300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
	{
		// Skip rowspan
		while (isset($this->rows[$this->row_index][$this->col_index])) {
			$this->col_index++;
		}

		$value = trim($value);
		$type = !empty($attributes['type']) ? $attributes['type'] : ($styles['-spreadsheet-cell-type'] ?? null);
		$type = $this->getCellType($value, $type);

		// Use real type in styles, useful to set the correct style
		$styles['-spreadsheet-cell-type'] = $type;

		$end = '';
		$cell = sprintf('<table:table-cell table:style-name="%s"', $this->newStyle('cell', $styles) ?? 'Default');

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387

			if (null !== $html) {
				// Break in multiple lines if required
				$html = preg_replace("/[\n\r]/", '', $html);

				$html = preg_replace('/<br[^>]*>/U', "\n", $html);
				$html = strip_tags($html);
				$html = html_entity_decode($html, ENT_QUOTES | ENT_XML1, 'UTF-8');
			}
			else {
				// Break in multiple lines if required
				$value = preg_replace("/[\n\r]/", '', $value);
			}

			$value = explode("\n", trim($value));







|







375
376
377
378
379
380
381
382
383
384
385
386
387
388
389

			if (null !== $html) {
				// Break in multiple lines if required
				$html = preg_replace("/[\n\r]/", '', $html);

				$html = preg_replace('/<br[^>]*>/U', "\n", $html);
				$html = strip_tags($html);
				$value = html_entity_decode($html, ENT_QUOTES | ENT_XML1, 'UTF-8');
			}
			else {
				// Break in multiple lines if required
				$value = preg_replace("/[\n\r]/", '', $value);
			}

			$value = explode("\n", trim($value));
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442

			$cells = $this->css->xpath($row, './/td|.//th');

			foreach ($cells as $cell) {
				$attributes = [
					'colspan' => $cell->getAttribute('colspan'),
					'rowspan' => $cell->getAttribute('rowspan'),
					'type'    => $cell->getAttribute('data-spreadsheet-type'),
				];

				$value = $cell->getAttribute('data-spreadsheet-value') ?: $cell->textContent;
				$value = html_entity_decode($value);
				$value = trim($value);
				$html = null;








|







430
431
432
433
434
435
436
437
438
439
440
441
442
443
444

			$cells = $this->css->xpath($row, './/td|.//th');

			foreach ($cells as $cell) {
				$attributes = [
					'colspan' => $cell->getAttribute('colspan'),
					'rowspan' => $cell->getAttribute('rowspan'),
					'type'    => $cell->getAttribute('data-spreadsheet-type') ?: null,
				];

				$value = $cell->getAttribute('data-spreadsheet-value') ?: $cell->textContent;
				$value = html_entity_decode($value);
				$value = trim($value);
				$html = null;

463
464
465
466
467
468
469
470

471
472
473
474
475
476
477
		}

		$number_value = str_replace([' ', "\xC2\xA0"], '', trim($value));

		if (is_object($value) && $value instanceof \DateTimeInterface) {
			return 'date';
		}
		elseif (is_int($value) || is_float($value) || (substr((string) $number_value, 0, 1) != '0' && preg_match('/^-?\d+(?:[,.]\d+)?$/', (string) $number_value))) {

			return 'number';
		}
		elseif (preg_match('!^(?:\d\d?/\d\d?/\d\d(?:\d\d)?|\d{4}-\d{2}-\d{2})(?:\s+\d\d?[:\.]\d\d?(?:[:\.]\d\d?))?$!', $value)) {
			return 'date';
		}
		elseif (preg_match('/^-?\d+(?:[,.]\d+)?\s*%$/', $number_value)) {
			return 'percentage';







|
>







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
		}

		$number_value = str_replace([' ', "\xC2\xA0"], '', trim($value));

		if (is_object($value) && $value instanceof \DateTimeInterface) {
			return 'date';
		}
		elseif (is_int($value) || is_float($value)
			|| (preg_match('/^-?(\d+)(?:[,.]\d+)?$/', (string) $number_value, $match) && ($match[1] == 0 || substr($match[1], 0, 1) != '0'))) {
			return 'number';
		}
		elseif (preg_match('!^(?:\d\d?/\d\d?/\d\d(?:\d\d)?|\d{4}-\d{2}-\d{2})(?:\s+\d\d?[:\.]\d\d?(?:[:\.]\d\d?))?$!', $value)) {
			return 'date';
		}
		elseif (preg_match('/^-?\d+(?:[,.]\d+)?\s*%$/', $number_value)) {
			return 'percentage';
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
		$out .= $this->xml;

		$out .= '</office:spreadsheet></office:body></office:document-content>';

		return $out;
	}

	protected function outputAutomaticStyles(): string
	{
		return '';
	}

	protected function outputStyles(): string
	{
		$xml = '';

		foreach ($this->styles as $style_name => $properties) {
			$type = substr($style_name, 0, strpos($style_name, '_'));
			$tags = [];







<
<
<
<
<







551
552
553
554
555
556
557





558
559
560
561
562
563
564
		$out .= $this->xml;

		$out .= '</office:spreadsheet></office:body></office:document-content>';

		return $out;
	}






	protected function outputStyles(): string
	{
		$xml = '';

		foreach ($this->styles as $style_name => $properties) {
			$type = substr($style_name, 0, strpos($style_name, '_'));
			$tags = [];
810
811
812
813
814
815
816


817
818
819
820
821
822
823
824
825
826
827
828
829
830
831

			$position = $styles['-spreadsheet-currency-position'] ?? 'suffix';

			if ($color === null) {
				$color = 'red';
			}



			if (isset($styles['-spreadsheet-locale']) && preg_match('/^[a-z]{2}[_-][A-Z]{2}$', $styles['-spreadsheet-locale'])) {
				$lang = substr($styles['-spreadsheet-locale'], 0, 2);
				$country = substr($styles['-spreadsheet-locale'], 3, 2);
			}
			else {
				$lang = 'fr';
				$country = 'FR';
			}

			$tag_name = 'number:currency-style';
			$space = '<number:text> </number:text>';

			$currency = sprintf('<number:currency-symbol number:language="%s" number:country="%s">%s</number:currency-symbol>',
				$lang, $country, htmlspecialchars($symbol, ENT_XML1));








>
>

|
<

|
|
|
<







808
809
810
811
812
813
814
815
816
817
818

819
820
821
822

823
824
825
826
827
828
829

			$position = $styles['-spreadsheet-currency-position'] ?? 'suffix';

			if ($color === null) {
				$color = 'red';
			}

			$locale = $this->default_locale;

			if (isset($styles['-spreadsheet-locale']) && preg_match('/^[a-z]{2}[_-][A-Z]{2}$', $styles['-spreadsheet-locale'])) {
				$locale = $styles['-spreadsheet-locale'];

			}

			$lang = substr($locale, 0, 2);
			$country = substr($locale, 3, 2);


			$tag_name = 'number:currency-style';
			$space = '<number:text> </number:text>';

			$currency = sprintf('<number:currency-symbol number:language="%s" number:country="%s">%s</number:currency-symbol>',
				$lang, $country, htmlspecialchars($symbol, ENT_XML1));