@odoo/o-spreadsheet 16.4.0-alpha.6 → 16.4.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12593,6 +12593,18 @@
12593
12593
  isExported: true,
12594
12594
  };
12595
12595
  // -----------------------------------------------------------------------------
12596
+ // FALSE
12597
+ // -----------------------------------------------------------------------------
12598
+ const FALSE = {
12599
+ description: _lt("Logical value `false`."),
12600
+ args: [],
12601
+ returns: ["BOOLEAN"],
12602
+ compute: function () {
12603
+ return false;
12604
+ },
12605
+ isExported: true,
12606
+ };
12607
+ // -----------------------------------------------------------------------------
12596
12608
  // IF
12597
12609
  // -----------------------------------------------------------------------------
12598
12610
  const IF = {
@@ -12728,6 +12740,18 @@
12728
12740
  isExported: true,
12729
12741
  };
12730
12742
  // -----------------------------------------------------------------------------
12743
+ // TRUE
12744
+ // -----------------------------------------------------------------------------
12745
+ const TRUE = {
12746
+ description: _lt("Logical value `true`."),
12747
+ args: [],
12748
+ returns: ["BOOLEAN"],
12749
+ compute: function () {
12750
+ return true;
12751
+ },
12752
+ isExported: true,
12753
+ };
12754
+ // -----------------------------------------------------------------------------
12731
12755
  // XOR
12732
12756
  // -----------------------------------------------------------------------------
12733
12757
  const XOR = {
@@ -12754,12 +12778,14 @@
12754
12778
  var logical = /*#__PURE__*/Object.freeze({
12755
12779
  __proto__: null,
12756
12780
  AND: AND,
12781
+ FALSE: FALSE,
12757
12782
  IF: IF,
12758
12783
  IFERROR: IFERROR,
12759
12784
  IFNA: IFNA,
12760
12785
  IFS: IFS,
12761
12786
  NOT: NOT,
12762
12787
  OR: OR,
12788
+ TRUE: TRUE,
12763
12789
  XOR: XOR
12764
12790
  });
12765
12791
 
@@ -13946,7 +13972,6 @@
13946
13972
  * is useful for the composer, which needs to be able to work with incomplete
13947
13973
  * formulas.
13948
13974
  */
13949
- const functions$3 = functionRegistry.content;
13950
13975
  const POSTFIX_UNARY_OPERATORS = ["%"];
13951
13976
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
13952
13977
  function tokenize(str, locale = DEFAULT_LOCALE) {
@@ -14077,17 +14102,11 @@
14077
14102
  }
14078
14103
  if (result.length) {
14079
14104
  const value = result;
14080
- const isFunction = value.toUpperCase() in functions$3;
14081
- if (isFunction) {
14082
- return { type: "FUNCTION", value };
14083
- }
14084
14105
  const isReference = rangeReference.test(value);
14085
14106
  if (isReference) {
14086
14107
  return { type: "REFERENCE", value };
14087
14108
  }
14088
- else {
14089
- return { type: "SYMBOL", value };
14090
- }
14109
+ return { type: "SYMBOL", value };
14091
14110
  }
14092
14111
  return null;
14093
14112
  }
@@ -14187,9 +14206,6 @@
14187
14206
  return { type: "NUMBER", value: parseNumber(current.value, DEFAULT_LOCALE) };
14188
14207
  case "STRING":
14189
14208
  return { type: "STRING", value: removeStringQuotes(current.value) };
14190
- case "FUNCTION":
14191
- const args = parseFunctionArgs(tokens);
14192
- return { type: "FUNCALL", value: current.value, args };
14193
14209
  case "INVALID_REFERENCE":
14194
14210
  throw new InvalidReferenceError();
14195
14211
  case "REFERENCE":
@@ -14206,13 +14222,15 @@
14206
14222
  value: current.value,
14207
14223
  };
14208
14224
  case "SYMBOL":
14209
- if (["TRUE", "FALSE"].includes(current.value.toUpperCase())) {
14210
- return { type: "BOOLEAN", value: current.value.toUpperCase() === "TRUE" };
14225
+ const value = current.value;
14226
+ const nextToken = tokens[0];
14227
+ if (nextToken?.type === "LEFT_PAREN" && functionRegex.test(current.value)) {
14228
+ const args = parseFunctionArgs(tokens);
14229
+ return { type: "FUNCALL", value: value, args };
14211
14230
  }
14212
- if (current.value) {
14213
- if (functionRegex.test(current.value) && tokens[0]?.type === "LEFT_PAREN") {
14214
- throw new UnknownFunctionError(current.value);
14215
- }
14231
+ const upperCaseValue = value.toUpperCase();
14232
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
14233
+ return { type: "BOOLEAN", value: upperCaseValue === "TRUE" };
14216
14234
  }
14217
14235
  throw new BadExpressionError(_lt("Invalid formula"));
14218
14236
  case "LEFT_PAREN":
@@ -14314,36 +14332,60 @@
14314
14332
  * to nodes of a specific type.
14315
14333
  * Useful if you want to convert some part of a formula.
14316
14334
  *
14317
- * e.g.
14318
- * ```ts
14335
+ * @example
14319
14336
  * convertAstNodes(ast, "FUNCALL", convertFormulaToExcel)
14320
14337
  *
14321
14338
  * function convertFormulaToExcel(ast: ASTFuncall) {
14322
14339
  * // ...
14323
14340
  * return modifiedAst
14324
14341
  * }
14325
- * ```
14326
14342
  */
14327
14343
  function convertAstNodes(ast, type, fn) {
14328
- if (type === ast.type) {
14329
- ast = fn(ast);
14344
+ return mapAst(ast, (ast) => {
14345
+ if (ast.type === type) {
14346
+ return fn(ast);
14347
+ }
14348
+ return ast;
14349
+ });
14350
+ }
14351
+ function iterateAstNodes(ast) {
14352
+ return Array.from(astIterator(ast));
14353
+ }
14354
+ function* astIterator(ast) {
14355
+ yield ast;
14356
+ switch (ast.type) {
14357
+ case "FUNCALL":
14358
+ for (const arg of ast.args) {
14359
+ yield* astIterator(arg);
14360
+ }
14361
+ break;
14362
+ case "UNARY_OPERATION":
14363
+ yield* astIterator(ast.operand);
14364
+ break;
14365
+ case "BIN_OPERATION":
14366
+ yield* astIterator(ast.left);
14367
+ yield* astIterator(ast.right);
14368
+ break;
14330
14369
  }
14370
+ }
14371
+ function mapAst(ast, fn) {
14372
+ ast = fn(ast);
14331
14373
  switch (ast.type) {
14332
14374
  case "FUNCALL":
14333
14375
  return {
14334
14376
  ...ast,
14335
- args: ast.args.map((child) => convertAstNodes(child, type, fn)),
14377
+ args: ast.args.map((child) => mapAst(child, fn)),
14336
14378
  };
14337
14379
  case "UNARY_OPERATION":
14338
14380
  return {
14339
14381
  ...ast,
14340
- operand: convertAstNodes(ast.operand, type, fn),
14382
+ operand: mapAst(ast.operand, fn),
14341
14383
  };
14342
14384
  case "BIN_OPERATION":
14343
14385
  return {
14344
14386
  ...ast,
14345
- right: convertAstNodes(ast.right, type, fn),
14346
- left: convertAstNodes(ast.left, type, fn),
14387
+ right: mapAst(ast.right, fn),
14388
+ left: mapAst(ast.left, fn),
14347
14389
  };
14348
14390
  default:
14349
14391
  return ast;
@@ -14613,6 +14655,9 @@
14613
14655
  const { args } = ast;
14614
14656
  const functionName = ast.value.toUpperCase();
14615
14657
  const functionDefinition = functions$2[functionName];
14658
+ if (!functionDefinition) {
14659
+ throw new UnknownFunctionError(ast.value);
14660
+ }
14616
14661
  assertEnoughArgs(ast);
14617
14662
  const compiledArgs = [];
14618
14663
  for (let i = 0; i < args.length; i++) {
@@ -14871,7 +14916,7 @@
14871
14916
  functionStarted = "";
14872
14917
  }
14873
14918
  switch (token.type) {
14874
- case "FUNCTION":
14919
+ case "SYMBOL":
14875
14920
  functionStarted = token.value;
14876
14921
  break;
14877
14922
  case "LEFT_PAREN":
@@ -14908,6 +14953,17 @@
14908
14953
  return mapParentFunction(mapParenthesis(enrichTokens(tokens)));
14909
14954
  }
14910
14955
 
14956
+ const functions$1 = functionRegistry.content;
14957
+ function isExportableToExcel(tokens) {
14958
+ try {
14959
+ const nonExportableFunctions = iterateAstNodes(parseTokens(tokens)).filter((ast) => ast.type === "FUNCALL" && !functions$1[ast.value.toUpperCase()]?.isExported);
14960
+ return nonExportableFunctions.length === 0;
14961
+ }
14962
+ catch (error) {
14963
+ return false;
14964
+ }
14965
+ }
14966
+
14911
14967
  function isValidLocale(locale) {
14912
14968
  if (!(locale &&
14913
14969
  typeof locale === "object" &&
@@ -17480,8 +17536,14 @@
17480
17536
  id: "reset_size",
17481
17537
  name: _lt("Reset size"),
17482
17538
  sequence: 4,
17483
- execute: () => {
17484
- const size = env.model.getters.getImageSize(figureId);
17539
+ execute: async () => {
17540
+ const imagePath = env.model.getters.getImagePath(figureId);
17541
+ const size = env.model.getters.getImageSize(figureId) ??
17542
+ (await env.imageProvider?.getImageOriginalSize(imagePath));
17543
+ if (!env.model.getters.getImageSize(figureId)) {
17544
+ const image = env.model.getters.getImage(figureId);
17545
+ image.size = size;
17546
+ }
17485
17547
  const { height, width } = getMaxFigureSize(env.model.getters, size);
17486
17548
  env.model.dispatch("UPDATE_FIGURE", {
17487
17549
  sheetId: env.model.getters.getActiveSheetId(),
@@ -23534,7 +23596,7 @@
23534
23596
  argToFocus: Number,
23535
23597
  };
23536
23598
 
23537
- const functions$1 = functionRegistry.content;
23599
+ const functions = functionRegistry.content;
23538
23600
  const ASSISTANT_WIDTH = 300;
23539
23601
  const selectionIndicatorClass = "selector-flag";
23540
23602
  const selectionIndicatorColor = "#a9a9a9";
@@ -23568,10 +23630,6 @@
23568
23630
  padding-right: 3px;
23569
23631
  outline: none;
23570
23632
 
23571
- &.unfocusable {
23572
- pointer-events: none;
23573
- }
23574
-
23575
23633
  p {
23576
23634
  margin-bottom: 0px;
23577
23635
 
@@ -23819,7 +23877,10 @@
23819
23877
  }
23820
23878
  }
23821
23879
  showAutocomplete(searchTerm) {
23822
- if (!this.env.model.getters.getCurrentContent().startsWith("=")) {
23880
+ const searchTermUpperCase = searchTerm.toUpperCase();
23881
+ if (!this.env.model.getters.getCurrentContent().startsWith("=") ||
23882
+ searchTermUpperCase === "TRUE" ||
23883
+ searchTermUpperCase === "FALSE") {
23823
23884
  return;
23824
23885
  }
23825
23886
  this.autoCompleteState.showProvider = true;
@@ -23937,7 +23998,6 @@
23937
23998
  switch (token.type) {
23938
23999
  case "OPERATOR":
23939
24000
  case "NUMBER":
23940
- case "FUNCTION":
23941
24001
  case "ARG_SEPARATOR":
23942
24002
  case "STRING":
23943
24003
  result.push({ value: token.value, color: tokenColors[token.type] || "#000" });
@@ -23947,10 +24007,14 @@
23947
24007
  result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
23948
24008
  break;
23949
24009
  case "SYMBOL":
23950
- let value = token.value;
23951
- if (["TRUE", "FALSE"].includes(value.toUpperCase())) {
24010
+ const value = token.value;
24011
+ const upperCaseValue = value.toUpperCase();
24012
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
23952
24013
  result.push({ value: token.value, color: tokenColors.NUMBER });
23953
24014
  }
24015
+ else if (upperCaseValue in functionRegistry.content) {
24016
+ result.push({ value: token.value, color: tokenColors.FUNCTION });
24017
+ }
23954
24018
  else {
23955
24019
  result.push({ value: token.value, color: "#000" });
23956
24020
  }
@@ -24046,25 +24110,28 @@
24046
24110
  this.autoCompleteState.showProvider = false;
24047
24111
  this.functionDescriptionState.showDescription = false;
24048
24112
  if (content.startsWith("=")) {
24049
- const tokenAtCursor = this.env.model.getters.getTokenAtCursor();
24050
- if (tokenAtCursor) {
24051
- const { xc } = splitReference(tokenAtCursor.value);
24052
- if (tokenAtCursor.type === "FUNCTION" ||
24053
- (tokenAtCursor.type === "SYMBOL" && !rangeReference.test(xc))) {
24054
- // initialize Autocomplete Dropdown
24055
- this.showAutocomplete(tokenAtCursor.value);
24056
- }
24057
- else if (tokenAtCursor.functionContext && tokenAtCursor.type !== "UNKNOWN") {
24058
- // initialize Formula Assistant
24059
- const tokenContext = tokenAtCursor.functionContext;
24060
- const parentFunction = tokenContext.parent.toUpperCase();
24061
- const description = functions$1[parentFunction];
24062
- const argPosition = tokenContext.argPosition;
24063
- this.functionDescriptionState.functionName = parentFunction;
24064
- this.functionDescriptionState.functionDescription = description;
24065
- this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24066
- this.functionDescriptionState.showDescription = true;
24067
- }
24113
+ const token = this.env.model.getters.getTokenAtCursor();
24114
+ if (!token) {
24115
+ return;
24116
+ }
24117
+ if (token.type === "SYMBOL") {
24118
+ // initialize Autocomplete Dropdown
24119
+ this.showAutocomplete(token.value);
24120
+ return;
24121
+ }
24122
+ const tokenContext = token.functionContext;
24123
+ const parentFunction = tokenContext?.parent.toUpperCase();
24124
+ if (tokenContext &&
24125
+ parentFunction &&
24126
+ parentFunction in functions &&
24127
+ token.type !== "UNKNOWN") {
24128
+ // initialize Formula Assistant
24129
+ const description = functions[parentFunction];
24130
+ const argPosition = tokenContext.argPosition;
24131
+ this.functionDescriptionState.functionName = parentFunction;
24132
+ this.functionDescriptionState.functionDescription = description;
24133
+ this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24134
+ this.functionDescriptionState.showDescription = true;
24068
24135
  }
24069
24136
  }
24070
24137
  }
@@ -26764,7 +26831,12 @@
26764
26831
  };
26765
26832
  const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
26766
26833
  const HEIGHT_FACTOR = 0.75; // 100px => 75 u
26767
- const WIDTH_FACTOR = 0.1317; // 100px => 13.17 u
26834
+ /**
26835
+ * Excel says its default column width is 8.43 characters (64px)
26836
+ * which makes WIDTH_FACTOR = 0.1317, but it doesn't work well
26837
+ * 0.143 is a value from dev's experiments.
26838
+ */
26839
+ const WIDTH_FACTOR = 0.143;
26768
26840
  /** unit : maximum number of characters a column can hold at the standard font size. What. */
26769
26841
  const EXCEL_DEFAULT_COL_WIDTH = 8.43;
26770
26842
  /** unit : points */
@@ -27281,7 +27353,7 @@
27281
27353
  64: "000000",
27282
27354
  65: "FFFFFF", // system background
27283
27355
  };
27284
- const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
27356
+ const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
27285
27357
  "image/avif": "avif",
27286
27358
  "image/bmp": "bmp",
27287
27359
  "image/gif": "gif",
@@ -27290,6 +27362,17 @@
27290
27362
  "image/png": "png",
27291
27363
  "image/tiff": "tiff",
27292
27364
  "image/webp": "webp",
27365
+ };
27366
+ const IMAGE_EXTENSION_TO_MIMETYPE_MAPPING = {
27367
+ avif: "image/avif",
27368
+ bmp: "image/bmp",
27369
+ gif: "image/gif",
27370
+ ico: "image/vnd.microsoft.icon",
27371
+ jpeg: "image/jpeg",
27372
+ png: "image/png",
27373
+ tiff: "image/tiff",
27374
+ webp: "image/webp",
27375
+ jpg: "image/jpeg",
27293
27376
  };
27294
27377
 
27295
27378
  /**
@@ -28093,20 +28176,35 @@
28093
28176
  convertEMUToDotValue(figure.anchors[0].rowOffset);
28094
28177
  const y2 = getRowPosition(figure.anchors[1].row, sheetData) +
28095
28178
  convertEMUToDotValue(figure.anchors[1].rowOffset);
28096
- const width = x2 - x1;
28097
- const height = y2 - y1;
28098
- const chartData = convertChartData(figure.data);
28099
- if (!chartData)
28100
- return undefined;
28101
- return {
28102
- id: id,
28103
- x: x1,
28104
- y: y1,
28105
- width: width,
28106
- height: height,
28107
- tag: "chart",
28108
- data: convertChartData(figure.data),
28109
- };
28179
+ const figureData = { id, x: x1, y: y1 };
28180
+ if (isChartData(figure.data)) {
28181
+ return {
28182
+ ...figureData,
28183
+ width: x2 - x1,
28184
+ height: y2 - y1,
28185
+ tag: "chart",
28186
+ data: convertChartData(figure.data),
28187
+ };
28188
+ }
28189
+ else if (isImageData(figure.data)) {
28190
+ return {
28191
+ ...figureData,
28192
+ width: convertEMUToDotValue(figure.data.size.cx),
28193
+ height: convertEMUToDotValue(figure.data.size.cy),
28194
+ tag: "image",
28195
+ data: {
28196
+ path: figure.data.imageSrc,
28197
+ mimetype: figure.data.mimetype,
28198
+ },
28199
+ };
28200
+ }
28201
+ return undefined;
28202
+ }
28203
+ function isChartData(data) {
28204
+ return "dataSets" in data;
28205
+ }
28206
+ function isImageData(data) {
28207
+ return "imageSrc" in data;
28110
28208
  }
28111
28209
  function convertChartData(chartData) {
28112
28210
  const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
@@ -28813,9 +28911,13 @@
28813
28911
  /**
28814
28912
  * Get the list of all the XLSX files in the XLSX file structure
28815
28913
  */
28816
- getListOfFiles() {
28817
- const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);
28818
- return files;
28914
+ getListOfXMLFiles() {
28915
+ const XMLFiles = Object.entries(this.xlsxFileStructure)
28916
+ .filter(([key]) => key !== "images")
28917
+ .map(([_, value]) => value)
28918
+ .flat()
28919
+ .filter(isDefined$1);
28920
+ return XMLFiles;
28819
28921
  }
28820
28922
  /**
28821
28923
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -28987,20 +29089,31 @@
28987
29089
  return color;
28988
29090
  }
28989
29091
  /**
28990
- * Returns the xlsx file targeted by a relationship.
29092
+ * Returns the xml file targeted by a relationship.
28991
29093
  */
28992
29094
  getTargetXmlFile(relationship) {
28993
29095
  if (!relationship)
28994
29096
  throw new Error("Undefined target file");
28995
- let target = relationship.target;
28996
- target = target.replace("../", "");
28997
- target = target.replace("./", "");
29097
+ const target = this.processRelationshipTargetName(relationship.target);
28998
29098
  // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
28999
- const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));
29099
+ const f = this.getListOfXMLFiles().find((f) => f.file.fileName.endsWith(target));
29000
29100
  if (!f || !f.file)
29001
29101
  throw new Error("Cannot find target file");
29002
29102
  return f;
29003
29103
  }
29104
+ /**
29105
+ * Returns the image parameters targeted by a relationship.
29106
+ */
29107
+ getTargetImageFile(relationship) {
29108
+ if (!relationship)
29109
+ throw new Error("Undefined target file");
29110
+ const target = this.processRelationshipTargetName(relationship.target);
29111
+ // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
29112
+ const f = this.xlsxFileStructure.images.find((f) => f.fileName.endsWith(target));
29113
+ if (!f)
29114
+ throw new Error("Cannot find target file");
29115
+ return f;
29116
+ }
29004
29117
  /**
29005
29118
  * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29006
29119
  *
@@ -29047,6 +29160,10 @@
29047
29160
  return clrScheme[colorId].value;
29048
29161
  }
29049
29162
  }
29163
+ /** Remove signs of relative path. */
29164
+ processRelationshipTargetName(targetName) {
29165
+ return targetName.replace(/\.+\//, "");
29166
+ }
29050
29167
  }
29051
29168
 
29052
29169
  /**
@@ -29276,15 +29393,16 @@
29276
29393
  throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
29277
29394
  }
29278
29395
  const chartElement = this.querySelector(figureElement, "c:chart");
29279
- if (!chartElement) {
29280
- throw new Error("Only chart figures are currently supported.");
29396
+ const imageElement = this.querySelector(figureElement, "a:blip");
29397
+ if (!chartElement && !imageElement) {
29398
+ throw new Error("Only chart and image figures are currently supported.");
29281
29399
  }
29282
29400
  return {
29283
29401
  anchors: [
29284
29402
  this.extractFigureAnchor("xdr:from", figureElement),
29285
29403
  this.extractFigureAnchor("xdr:to", figureElement),
29286
29404
  ],
29287
- data: this.extractChart(chartElement),
29405
+ data: chartElement ? this.extractChart(chartElement) : this.extractImage(figureElement),
29288
29406
  };
29289
29407
  });
29290
29408
  }
@@ -29309,6 +29427,24 @@
29309
29427
  }
29310
29428
  return chartDefinition;
29311
29429
  }
29430
+ extractImage(figureElement) {
29431
+ const imageElement = this.querySelector(figureElement, "a:blip");
29432
+ const imageId = this.extractAttr(imageElement, "r:embed", { required: true }).asString();
29433
+ const image = this.getTargetImageFile(this.relationships[imageId]);
29434
+ if (!image) {
29435
+ throw new Error("Unable to extract image");
29436
+ }
29437
+ const shapePropertyElement = this.querySelector(figureElement, "a:xfrm");
29438
+ const extension = image.fileName.split(".").at(-1);
29439
+ return {
29440
+ imageSrc: image.imageSrc,
29441
+ mimetype: extension ? IMAGE_EXTENSION_TO_MIMETYPE_MAPPING[extension] : undefined,
29442
+ size: {
29443
+ cx: this.extractChildAttr(shapePropertyElement, "a:ext", "cx", { required: true }).asNum(),
29444
+ cy: this.extractChildAttr(shapePropertyElement, "a:ext", "cy", { required: true }).asNum(),
29445
+ },
29446
+ };
29447
+ }
29312
29448
  }
29313
29449
 
29314
29450
  /**
@@ -29878,14 +30014,22 @@
29878
30014
  class XlsxReader {
29879
30015
  warningManager;
29880
30016
  xmls;
30017
+ images;
29881
30018
  constructor(files) {
29882
30019
  this.warningManager = new XLSXImportWarningManager();
29883
30020
  this.xmls = {};
30021
+ this.images = [];
29884
30022
  for (let key of Object.keys(files)) {
29885
30023
  // Random files can be in xlsx (like a bin file for printer settings)
29886
30024
  if (key.endsWith(".xml") || key.endsWith(".rels")) {
29887
30025
  this.xmls[key] = parseXML(new XMLString(files[key]));
29888
30026
  }
30027
+ else if (key.includes("media/image")) {
30028
+ this.images.push({
30029
+ fileName: key,
30030
+ imageSrc: files[key]["imageSrc"],
30031
+ });
30032
+ }
29889
30033
  }
29890
30034
  }
29891
30035
  convertXlsx() {
@@ -29938,6 +30082,7 @@
29938
30082
  tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),
29939
30083
  pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),
29940
30084
  externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),
30085
+ images: this.images,
29941
30086
  };
29942
30087
  if (!xlsxFileStructure.workbook.rels) {
29943
30088
  throw Error(_lt("Cannot find workbook relations file"));
@@ -33088,7 +33233,7 @@
33088
33233
  handle(cmd) {
33089
33234
  switch (cmd.type) {
33090
33235
  case "CREATE_IMAGE":
33091
- this.addFigure(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33236
+ this.addImage(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33092
33237
  this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
33093
33238
  this.syncedImages.add(cmd.definition.path);
33094
33239
  break;
@@ -33152,7 +33297,7 @@
33152
33297
  // ---------------------------------------------------------------------------
33153
33298
  // Private
33154
33299
  // ---------------------------------------------------------------------------
33155
- addFigure(id, sheetId, position, size) {
33300
+ addImage(id, sheetId, position, size) {
33156
33301
  const figure = {
33157
33302
  id,
33158
33303
  x: position.x,
@@ -35659,7 +35804,6 @@
35659
35804
  }
35660
35805
  }
35661
35806
 
35662
- const functions = functionRegistry.content;
35663
35807
  //#region
35664
35808
  // ---------------------------------------------------------------------------
35665
35809
  // INTRODUCTION
@@ -35881,9 +36025,7 @@
35881
36025
  let isExported = true;
35882
36026
  const formulaCell = this.getCorrespondingFormulaCell(position);
35883
36027
  if (formulaCell) {
35884
- isExported = formulaCell.compiledFormula.tokens
35885
- .filter((tk) => tk.type === "FUNCTION")
35886
- .every((tk) => functions[tk.value.toUpperCase()].isExported);
36028
+ isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
35887
36029
  isFormula = isExported;
35888
36030
  if (!isExported) {
35889
36031
  newContent = value.toString();
@@ -44289,7 +44431,7 @@
44289
44431
  async requestImage() {
44290
44432
  const file = await this.getImageFromUser();
44291
44433
  const path = await this.fileStore.upload(file);
44292
- const size = await this.getImageSize(path);
44434
+ const size = await this.getImageOriginalSize(path);
44293
44435
  return { path, size, mimetype: file.type };
44294
44436
  }
44295
44437
  getImageFromUser() {
@@ -44308,7 +44450,7 @@
44308
44450
  input.click();
44309
44451
  });
44310
44452
  }
44311
- getImageSize(path) {
44453
+ getImageOriginalSize(path) {
44312
44454
  return new Promise((resolve, reject) => {
44313
44455
  const image = new Image();
44314
44456
  image.src = path;
@@ -45793,6 +45935,10 @@
45793
45935
  top: 20%;
45794
45936
  }
45795
45937
  }
45938
+
45939
+ .user-select-text {
45940
+ user-select: text;
45941
+ }
45796
45942
  `;
45797
45943
  class TopBarComposer extends owl.Component {
45798
45944
  static template = "o-spreadsheet-TopBarComposer";
@@ -48831,6 +48977,8 @@
48831
48977
  ["name", `Image ${imageId}`],
48832
48978
  ["title", "Image"],
48833
48979
  ];
48980
+ const cx = convertDotValueToEMU(figure.width);
48981
+ const cy = convertDotValueToEMU(figure.height);
48834
48982
  return escapeXml /*xml*/ `
48835
48983
  <xdr:twoCellAnchor editAs="oneCell">
48836
48984
  <xdr:from>
@@ -48857,6 +49005,9 @@
48857
49005
  </a:stretch>
48858
49006
  </xdr:blipFill>
48859
49007
  <xdr:spPr>
49008
+ <a:xfrm>
49009
+ <a:ext cx="${cx}" cy="${cy}" />
49010
+ </a:xfrm>
48860
49011
  <a:prstGeom prst="rect">
48861
49012
  <a:avLst/>
48862
49013
  </a:prstGeom>
@@ -49376,7 +49527,7 @@
49376
49527
  const mimeType = image.data.mimetype;
49377
49528
  if (mimeType === undefined)
49378
49529
  continue;
49379
- const extension = IMAGE_MIMETYPE_EXTENSION_MAPPING[mimeType];
49530
+ const extension = IMAGE_MIMETYPE_TO_EXTENSION_MAPPING[mimeType];
49380
49531
  // only support exporting images with mimetypes specified in the mapping
49381
49532
  if (extension === undefined)
49382
49533
  continue;
@@ -49512,7 +49663,7 @@
49512
49663
  function createContentTypes(files) {
49513
49664
  const overrideNodes = [];
49514
49665
  // hard-code supported image mimetypes
49515
- const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49666
+ const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_TO_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49516
49667
  for (const file of files) {
49517
49668
  if ("contentType" in file && file.contentType) {
49518
49669
  overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
@@ -50142,6 +50293,7 @@
50142
50293
  exports.invalidateCFEvaluationCommands = invalidateCFEvaluationCommands;
50143
50294
  exports.invalidateDependenciesCommands = invalidateDependenciesCommands;
50144
50295
  exports.invalidateEvaluationCommands = invalidateEvaluationCommands;
50296
+ exports.iterateAstNodes = iterateAstNodes;
50145
50297
  exports.links = links;
50146
50298
  exports.load = load;
50147
50299
  exports.parse = parse;
@@ -50154,9 +50306,9 @@
50154
50306
  Object.defineProperty(exports, '__esModule', { value: true });
50155
50307
 
50156
50308
 
50157
- __info__.version = '16.4.0-alpha.6';
50158
- __info__.date = '2023-06-28T11:40:12.377Z';
50159
- __info__.hash = '7d15d3c';
50309
+ __info__.version = '16.4.0-alpha.7';
50310
+ __info__.date = '2023-07-04T13:46:24.130Z';
50311
+ __info__.hash = 'b04cf78';
50160
50312
 
50161
50313
 
50162
50314
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);