Skip to content

Commit

Permalink
[IMP] xlsx_import: support more date formats
Browse files Browse the repository at this point in the history
We now try to parse and modify the xlsx date formats at import to get a
format supported by o_spreadsheet.

Odoo task 3222986

closes #2293

Signed-off-by: Lucas Lefèvre (lul) <[email protected]>
  • Loading branch information
hokolomopo committed Mar 31, 2023
1 parent 3792988 commit c712b02
Show file tree
Hide file tree
Showing 20 changed files with 703 additions and 958 deletions.
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const DEFAULT_BORDER_DESC: BorderDescr = ["thin", "#000"];
export const DEFAULT_FILTER_BORDER_DESC: BorderDescr = ["thin", FILTERS_COLOR];

// DateTimeRegex
export const DATETIME_FORMAT = /[ymd:]/;
export const DATETIME_FORMAT = /[ymdhs:]/;

// Ranges
export const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
Expand Down
87 changes: 87 additions & 0 deletions src/xlsx/conversion/format_conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { formatValue } from "../../helpers";
import { XLSXNumFormat } from "../../types/xlsx";
import { WarningTypes, XLSXImportWarningManager } from "../helpers/xlsx_parser_error_manager";
import { XLSX_FORMATS_CONVERSION_MAP } from "./conversion_maps";

const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;

/**
* Convert excel format to o_spreadsheet format
*
* Excel format are defined in openXML §18.8.31
*/
export function convertXlsxFormat(
numFmtId: number,
formats: XLSXNumFormat[],
warningManager: XLSXImportWarningManager
): string | undefined {
if (numFmtId === 0) {
return undefined;
}
// Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
let format =
XLSX_FORMATS_CONVERSION_MAP[numFmtId] || formats.find((f) => f.id === numFmtId)?.format;

if (format) {
try {
let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks

// Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
const numberOfQuotes = convertedFormat.match(/"/g)?.length || 0;
const numberOfOpenBrackets = convertedFormat.match(/\[/g)?.length || 0;
if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
throw new Error("Multiple escaped blocks in format");
}
convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'

convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM

convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces

convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters

if (isXlsxDateFormat(convertedFormat)) {
convertedFormat = convertDateFormat(convertedFormat);
}

if (isFormatSupported(convertedFormat)) {
return convertedFormat;
}
} catch (e) {}
}

warningManager.generateNotSupportedWarning(
WarningTypes.NumFmtIdNotSupported,
format || `nmFmtId ${numFmtId}`
);
return undefined;
}

function isFormatSupported(format: string): boolean {
try {
formatValue(0, format);
return true;
} catch (e) {
return false;
}
}

function isXlsxDateFormat(format: string): boolean {
return format.match(XLSX_DATE_FORMAT_REGEX) !== null;
}

function convertDateFormat(format: string): string {
// Some of these aren't defined neither in the OpenXML spec not the Xlsx extension of OpenXML,
// but can still occur and are supported by Excel/Google sheets

format = format.toLowerCase();
format = format.replace(/mmmmm/g, "mmm");
format = format.replace(/am\/pm|a\/m/g, "a");
format = format.replace(/hhhh/g, "hh");
format = format.replace(/\bh\b/g, "hh");

return format;
}
52 changes: 1 addition & 51 deletions src/xlsx/conversion/style_conversion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fontSizeMap } from "../../fonts";
import { formatValue } from "../../helpers";
import { Border, BorderDescr, Style } from "../../types";
import {
XLSXBorder,
Expand All @@ -9,7 +8,6 @@ import {
XLSXFont,
XLSXHorizontalAlignment,
XLSXImportData,
XLSXNumFormat,
XLSXVerticalAlignment,
} from "../../types/xlsx";
import { arrayToObject } from "../helpers/misc";
Expand All @@ -22,8 +20,8 @@ import {
SUPPORTED_FILL_PATTERNS,
SUPPORTED_FONTS,
SUPPORTED_HORIZONTAL_ALIGNMENTS,
XLSX_FORMATS_CONVERSION_MAP,
} from "./conversion_maps";
import { convertXlsxFormat } from "./format_conversion";

interface StyleStruct {
fontStyle?: XLSXFont;
Expand Down Expand Up @@ -124,54 +122,6 @@ export function convertFormats(
return arrayToObject(formats, 1);
}

/**
* Convert excel format to o_spreadsheet format
*
* Excel format are defined in openXML §18.8.31
*/
export function convertXlsxFormat(
numFmtId: number,
formats: XLSXNumFormat[],
warningManager: XLSXImportWarningManager
): string | undefined {
if (numFmtId === 0) {
return undefined;
}
// Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
let format =
XLSX_FORMATS_CONVERSION_MAP[numFmtId] || formats.find((f) => f.id === numFmtId)?.format;

if (format) {
try {
let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks

// Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
const numberOfQuotes = convertedFormat.match(/"/g)?.length || 0;
const numberOfOpenBrackets = convertedFormat.match(/\[/g)?.length || 0;
if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
throw new Error("Multiple escaped blocks in format");
}
convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'

convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore with of next char for align purposes. Not supported ATM
convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM

convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces

formatValue(0, convertedFormat);
return convertedFormat;
} catch (e) {}
}

warningManager.generateNotSupportedWarning(
WarningTypes.NumFmtIdNotSupported,
format || `nmFmtId ${numFmtId}`
);
return undefined;
}

/**
* We currently only support only a set of font sizes, we cannot define new font sizes.
* This function adapts an arbitrary font size to the closest supported font size.
Expand Down
2 changes: 1 addition & 1 deletion tests/__xlsx__/xlsx_demo_data/docProps/app.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<vt:lpstr>Worksheets</vt:lpstr>
</vt:variant>
<vt:variant>
<vt:i4>10</vt:i4>
<vt:i4>11</vt:i4>
</vt:variant>
</vt:vector>
</HeadingPairs>
Expand Down
2 changes: 1 addition & 1 deletion tests/__xlsx__/xlsx_demo_data/docProps/core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<cp:lastModifiedBy>Adrien</cp:lastModifiedBy>
<dcterms:created xsi:type="dcterms:W3CDTF">2022-02-10T15:47:12Z</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2022-10-11T11:39:36Z</dcterms:modified>
<dcterms:modified xsi:type="dcterms:W3CDTF">2023-03-28T12:16:40Z</dcterms:modified>
</cp:coreProperties>
2 changes: 1 addition & 1 deletion tests/__xlsx__/xlsx_demo_data/xl/calcChain.xml
Original file line number Diff line number Diff line change
Expand Up @@ -370,5 +370,5 @@
<c r="D154" i="3"/>
<c r="D38" i="3"/>
<c r="D65" i="3"/>
<c r="C14" i="1" l="1"/>
<c r="C14" i="1"/>
</calcChain>
5 changes: 1 addition & 4 deletions tests/__xlsx__/xlsx_demo_data/xl/sharedStrings.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="458" uniqueCount="410">
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="457" uniqueCount="409">
<si>
<t>CF =42</t>
</si>
Expand Down Expand Up @@ -1210,9 +1210,6 @@
<si>
<t>#,##0[$ EUR €]</t>
</si>
<si>
<t>d/m/yy</t>
</si>
<si>
<t>not supported: multiple escaped sequences</t>
</si>
Expand Down
35 changes: 3 additions & 32 deletions tests/__xlsx__/xlsx_demo_data/xl/styles.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" mc:Ignorable="x14ac x16r2 xr">
<numFmts count="8">
<numFmts count="7">
<numFmt numFmtId="164" formatCode="m/d/yyyy"/>
<numFmt numFmtId="165" formatCode="m/d/yyyy\ hh:mm:ss"/>
<numFmt numFmtId="166" formatCode="#,##0.00\ &quot;&quot;"/>
<numFmt numFmtId="167" formatCode="[$$-409]#,##0.000"/>
<numFmt numFmtId="168" formatCode="d/m/yy;@"/>
<numFmt numFmtId="169" formatCode="[$₪-40D]\ #,##0"/>
<numFmt numFmtId="170" formatCode="#,##0&quot; EUR €&quot;"/>
<numFmt numFmtId="171" formatCode="&quot;&quot;#,##0.00\ &quot;&quot;"/>
Expand Down Expand Up @@ -700,7 +699,7 @@
<xf numFmtId="0" fontId="10" fillId="0" borderId="0"/>
<xf numFmtId="0" fontId="22" fillId="0" borderId="0" applyNumberFormat="0" applyFill="0" applyBorder="0" applyAlignment="0" applyProtection="0"/>
</cellStyleXfs>
<cellXfs count="132">
<cellXfs count="110">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
Expand Down Expand Up @@ -765,15 +764,10 @@
<xf numFmtId="19" fontId="2" fillId="0" borderId="0" xfId="0" applyNumberFormat="1" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyFill="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="11" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment horizontal="left"/>
</xf>
Expand All @@ -795,7 +789,6 @@
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment horizontal="distributed"/>
</xf>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="top"/>
</xf>
Expand All @@ -808,17 +801,12 @@
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="distributed"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="8" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="8" fillId="0" borderId="21" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="21" xfId="0" applyBorder="1"/>
<xf numFmtId="0" fontId="8" fillId="0" borderId="23" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="22" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyFill="1" applyBorder="1"/>
<xf numFmtId="14" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="22" xfId="0" applyFont="1" applyFill="1" applyBorder="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="10" fillId="5" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
<xf numFmtId="0" fontId="10" fillId="6" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
<xf numFmtId="0" fontId="10" fillId="7" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
Expand Down Expand Up @@ -848,10 +836,6 @@
<xf numFmtId="0" fontId="10" fillId="0" borderId="19" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="20" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="12" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="8" fillId="0" borderId="0" xfId="0" applyFont="1" applyFill="1" applyBorder="1"/>
<xf numFmtId="0" fontId="8" fillId="0" borderId="21" xfId="0" applyFont="1" applyFill="1" applyBorder="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyBorder="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="9" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="13" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="14" fillId="0" borderId="0" xfId="0" applyFont="1"/>
Expand All @@ -867,29 +851,17 @@
<xf numFmtId="0" fontId="10" fillId="26" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
<xf numFmtId="0" fontId="10" fillId="27" borderId="0" xfId="0" applyFont="1" applyFill="1"/>
<xf numFmtId="0" fontId="17" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="18" fillId="0" borderId="0" xfId="0" applyFont="1" applyFill="1" applyBorder="1"/>
<xf numFmtId="0" fontId="18" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="19" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="20" fillId="0" borderId="0" xfId="0" applyFont="1"/>
<xf numFmtId="0" fontId="2" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
</xf>
<xf numFmtId="0" fontId="2" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="22" fillId="0" borderId="0" xfId="2"/>
<xf numFmtId="0" fontId="2" fillId="0" borderId="0" xfId="0" applyNumberFormat="1" applyFont="1" applyAlignment="1">
<alignment vertical="center"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="8" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="24" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="25" xfId="0" applyFont="1" applyBorder="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" pivotButton="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">
<alignment horizontal="left"/>
</xf>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">
<alignment horizontal="left" indent="1"/>
</xf>
Expand All @@ -902,7 +874,6 @@
<xf numFmtId="14" fontId="0" fillId="0" borderId="27" xfId="0" applyNumberFormat="1" applyBorder="1"/>
<xf numFmtId="166" fontId="0" fillId="0" borderId="27" xfId="0" applyNumberFormat="1" applyBorder="1"/>
<xf numFmtId="167" fontId="0" fillId="0" borderId="27" xfId="0" applyNumberFormat="1" applyBorder="1"/>
<xf numFmtId="168" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="0" fontId="10" fillId="0" borderId="0" xfId="0" applyFont="1" applyAlignment="1">
<alignment wrapText="1"/>
</xf>
Expand Down
Loading

0 comments on commit c712b02

Please sign in to comment.