100 Commits
Author SHA1 Message Date
Tomaž Vajngerl a1e5a20bd9 tdf#173608: sc: fix relative sheet refs of a sheet copied before its source
Copying a sheet to a position at or before its source left the
relative sheet references in the copy pointing at the wrong
sheets, one or two places too far to the right. CopyTab moves the
source index by one when the new sheet is inserted before it, and
AdjustRelativeTabRefs received that moved index. The offsets in
the cloned formulas still count from the position that the source
had before the insert.

Pass the source position from before the insert instead, which is
the same one that CopyTab already gives to the range name copy.

This follows "sc: Fix backward reference when copying a sheet"
(Ie32d0ceac34525ccaf13aed8fb9630ae42d4225e), which fixed copies
placed after the source.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I063818265e0d56498bc9e09f04f85d3f5191b4d2
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13662
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 2539d0afe0d68409f5e43b2d92c4d85b697566cd)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211515
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-28 17:38:38 +02:00
Tomaž Vajngerl 8c2f779a93 sd: hide text boundaries for PDF in Draw
A PDF opened in Draw fills every page with a single imported image,
so the text boundary guides carry no meaning there. Drawing the page
background as transparent recently let those guides show through,
where the opaque background used to hide them.

Skip the guides whenever the document is a PDF, no matter what the
"show boundary for margins" setting says. Ordinary Draw documents
still follow the setting. The flag is recomputed on every redraw in
DrawView::CompleteRedraw, so the PDF exclusion goes there.

Add a test that renders a PDF page and checks the inner page border
is not drawn in the boundary color.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia2ea9116ef0a32ef0fb78cd35d926f668fa78705
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4812
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 5fa4bf9c49fb1416f005607c72b904388fc7170a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209434
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-28 16:28:27 +02:00
Tomaž Vajngerl 8a18cad8ef sc: name the swarm solver infeasibility penalty weight
The graded penalty scaled the constraint violation by a bare 1.0e7
literal. Give it a named constant so its role is clear at the use
site.

Change-Id: I6a0fe4d599bfd3169e027219d28d3ef8484c825c
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9312
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit c845018b19b366ce0f8bd27a983bf5915838f743)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211023
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-17 08:58:30 +02:00
Tomaž Vajngerl 8efda4882c sc: document the Particle struct and move the swarm classes into sc
The swarm classes all sat in the global namespace. Wrap them in the
sc namespace so the whole family stays in one place: the particle,
the swarm, the two per-generation algorithms and the three solvers
that drive them. The uses in the solver component and in its test
are qualified to match.

The classes that only the solver component itself uses stay in an
unnamed namespace, now nested inside sc, so they keep the linkage
they had before.

Particle also gets a short comment saying what one of them holds.

Change-Id: I8a2f4e8c7f90d71c774f9b297204e67728994422
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9311
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 2be6d1a30307bebd8baea813295bb3a1221322ee)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211022
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-17 08:58:25 +02:00
Tomaž Vajngerl 521dd9a5fb sc: fix the ParticleSwarmOptimization header file name
The file name spelled Particle as Particel. The class inside was
corrected earlier, so rename the file to match it and update the
two includes.

Change-Id: I4f3d551e955349b8c639703c2d3697988e762d35
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9310
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit b4af9ce5ce9fd8337a3d89797f8005786ed070d1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211021
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-17 08:58:21 +02:00
Tomaž Vajngerl 508dfcabb8 sc: stop over-solving the solver's quadratic subproblem
Each major iteration of the sequential quadratic solver solves a
quadratic subproblem with Frank-Wolfe, where every inner step is
a CoinMP linear solve. The loop ran until the duality gap fell below
a tiny absolute floor, which on harder models meant many inner linear
solves spent driving the gap to a precision the outer loop does not
need: it re-samples and re-solves around the accepted step anyway.

Also stop once the gap has shrunk to a small fraction of its value
on the first inner step. The step is then good enough to accept,
and the outer trust-region loop corrects any remaining error. This
cuts inner linear solves with no loss of solution quality: on the
resource model the solver still reaches the same optimum.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I80d118176840ab6c63ac939f962e8f8d3bb9a3b9
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4924
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit b63997355fee236cded23bc32e813bc412ed8984)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209439
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-16 09:44:20 +02:00
Tomaž Vajngerl 8e0d1c26c5 sc: default the swarm solver to the hybrid algorithm
The swarm solver offers differential evolution, particle swarm,
and the hybrid of the two. On the test models the hybrid reaches
the best  solutions: it keeps the spread that lets the search
escape weak local optima while still converging well, and it stays
feasible on the tightly constrained models where plain differential
evolution does not. Make it the default so a solve that does not
pick an algorithm gets the strongest one. The other two remain
selectable through the Algorithm property.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib47a51ed4980e03e44b4d757abffa5c8e4824bdc
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4923
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit d699993f78761bf33f31db2ff9f1150e33006e61)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209438
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 09:44:14 +02:00
Tomaž Vajngerl bdf3a86c44 sc: add a hybrid swarm solver alternating the two methods
The swarm solver could run either differential evolution or particle
swarm optimization. Add a third choice that combines them, selected
by the Algorithm property value 2. With the swarm framework in place
the hybrid reuses both engines instead of copying either one.

The hybrid owns one swarm and both engines and alternates them
generation by generation: a particle swarm generation on even counts,
a differential evolution generation that mutates toward the swarm's
best on odd ones. Each generation still costs one fitness evaluation
per particle, the same as either method alone.

Alternating combines the social convergence of particle swarm
optimization with the selection pressure and diversity of
differential evolution. On rugged models with many local optima
it reaches a good solution in less time than particle swarm
optimization alone, and on tightly constrained models it stays
feasible where plain differential evolution does not.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I01cf454a72a75fe2937b73560b699862fdc3779e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4947
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 757b50253891e35fd08ef9147431aed5a41ae002)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209437
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 09:44:07 +02:00
Tomaž Vajngerl 7fb5ef1054 sc: share one swarm framework across the swarm solvers
The particle swarm and differential evolution solvers each carried
their own copy of the same swarm bookkeeping: the particle list, the
global best, the generation counters, the random generator, and the
loop that seeds, restarts, and steps the swarm. Only the per-particle
update differed between them. Two copies had to be kept in step, and
a third method would have meant a third copy.

A Swarm now holds that shared state and runs the per-generation loop,
the initial seeding, and the restart. It folds each particle's
fitness into the personal and global bests and tracks when the
best last moved.

PSOAlgorithm and DEAlgorithm are one-generation engines bound to a
swarm. Each carries only its own per-particle update and asks the
swarm to advance, so the loop lives in one place.

The standalone particle swarm and differential evolution solvers
become thin wrappers that own a swarm and their engine and delegate
to it. This only moves code: the search each solver runs is
unchanged.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I0f94ecf51e94899be4521685ef6a175c6fa8f1dc
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4946
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 1c3fe258c816a52bd2fe20450aa1a426a896c601)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209436
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 09:43:59 +02:00
Tomaž Vajngerl 4b1594cd40 sc: help the swarm solver reach feasible and better solutions
The swarm solver gave up easily on models with many variables or
tight constraints. These changes let it find feasible and better
solutions there, while leaving small models unchanged.

Score an infeasible point by how far it breaks the constraints,
giving the search a slope to follow toward the feasible region. A
strict feasibility test still decides what is reported, so a
nearly-feasible point is never returned as the answer.

Seed the first half of the starting population from the document's
current values, leaving the rest random. A variable often has no
upper bound, so a fully random start would sit far from any
sensible value.

Scale the population with the number of variables, between a floor
and a cap, so high-dimensional models keep enough diversity while
small ones keep their previous size and cost.

Restart from a fresh spread when the search stalls, keeping the best
point found so far. A run that has settled in a local optimum then
spends its remaining time looking elsewhere instead of stopping at
the first stall.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I62b24b8914e67ee4c70adc66d685e6acee065405
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4921
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit b744979728a347d7eda9f11ba2af662e43b125c2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209435
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 09:43:53 +02:00
Tomaž Vajngerl d64ee176b7 sc: fix the area for a table reference reached through a name
The earlier change only handled the simplest case of a list validation
that points at a name wrapping a table reference. A name defined as
MyTable[Column] still came up empty, and a table with a header row showed
its values shifted up by one, with the header as the first entry.

Match the name's leading token against a table reference instead of
requiring the whole body to be a single token, so column-qualified
references are handled too. Take the area from the reference the compiler
already resolved, which is the right column without the header and totals
rows, and fall back to the database range with those rows dropped when
the reference is not yet compiled.

Move the table-reference-to-area lookup into one helper shared by the
direct branch and the through-a-name branch.

Add tests for a numeric table with a header and for a column-qualified
reference.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I105fee3a677d5e982403562adc8f9a4e6bb94a25
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5531
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 4c9e6da406be75d90323e1728556a1cd3afbaa09)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210986
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-16 08:56:35 +02:00
Tomaž Vajngerl 320d19cb2b sc: save a plain =@ref# cell without the _xlfn.SINGLE call
OOXML leaves the @ of =@A1# out of the text and expresses it by
keeping the cell a non-array formula, and the import already puts
it back for that shape. The export wrapped it in _xlfn.SINGLE, a
call an older reader doesn't know, for a formula that is a cell
reference and the # operator. It now leaves the call out for the
same shape, anything wider keeps it.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I812a9c371720fe4be2e04a7b9afe67272b1c666b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10852
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 420ed5695652a34ab7d329d9930b5d981029e439)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210479
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-15 11:16:20 +02:00
Tomaž Vajngerl a074008d58 formula: don't take the blank before an operator into the wrapper
The operand of an @ or of a union list ends where an operator further
out ends it, and the whitespace in front of that operator was still
inside it. So the blank was written inside the call parentheses, and
since a range ending in whitespace is no longer one parenthesised
group, the operand got a second pair of parentheses on top of the
ones it had - @(A1) + 1 came out as _xlfn.SINGLE((A1) )+ 1.

The wrapper now ends in front of those tokens. A blank before the #
stays with the operand, that one is the operator being rewritten.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ie6fe2df4e3e05b0ef845ebdb8a1aedb54b3f2a66
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10851
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit fd39c3a52d1cab7c0429bb98c4749bbfed088e6a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210478
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-15 11:16:16 +02:00
Tomaž Vajngerl 1d475cdae0 Keep a shape's color and its theme reference in step
The default drawing style names a theme color now, and a PPTX saved
to ODF and back came out with every shape green. A shape that really
did name a theme color lost the reference and came back plain.

A color item holds the color and the theme reference together.
Setting a plain color started from the item the shape inherits from
its style and overwrote the color alone, so the inherited reference
won. Clear the reference, which is what an explicit color means.

The ODF export repeats each shape's fill on a paragraph style,
applied after the graphic style, and the paragraph properties had no
complex color. Carry it there too.

A style's graphic properties took a complex color only when it was
the character one, matched by element name. Match on the context id,
which the fill and stroke colors share.

Add a round trip test through ODF and PPTX with a plain shape, a
themed one, and a themed one with a luminance modulation.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia9550222d18a243287240e4184666d78d0abdd22
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10060
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 07cf37b104983f765d3e1005f677e0058df12026)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210510
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-06 21:22:35 +02:00
Tomaž Vajngerl 3aa0160c31 Bound the number of histogram bins
A bin width much smaller than the data range asks for a bin count
with no upper bound. Over a range of 1000 a width of a millionth
resolves to a billion bins. The conversion from double to sal_Int32
had no range check either, so it could overflow.

Cap the count at MAX_HISTOGRAM_BINS. The division stays in double
and saturates at the cap, and a fixed count from a document is
clamped the same way.

A capped count no longer spans the range with the requested width,
so the range is divided evenly instead. Scott's rule gets the same
treatment when it yields no width at all.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib59ee462edade94799cc58e503b843696743ecfe
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10257
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 93a9df897a14ec12457aa40d328616eb071a9175)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210337
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-01 19:49:22 +02:00
Tomaž Vajngerl 123f934680 sc: check the reference operator combinations against a real file
UnionSpillIntersectionOperatorTest.xlsx was written by the producing
application, so the results stored in it are the ones that application
computes. Each row compares one with ours, the sheet's master cell says
whether every row agrees, and rows refused at the formula bar keep the
refused formula as text. It covers the @, # and % operators next to the
range, union and intersection operators, alone and combined.

The rows are checked against the stored results, then again after a
recalculation and after an xlsx save and reload, so both the values the
file came with and a fresh evaluation of our own are covered.

The whole verdict column is reported in one go, naming for each failing
row the two values compared and the tested cell's value and formula.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I26a970d82ba5c96737455d75e85b90aec3d22004
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10165
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit f45435e3cf797d08dc13f85c4b2a6cad0dd8d77a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210315
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-01 09:08:55 +02:00
Tomaž Vajngerl 38fb197b05 sc: put back the @ that OOXML leaves out of a plain =@ref# cell
OOXML leaves the @ of =@$I$112# out of the saved text and expresses it
by keeping the cell a plain non-array formula, while an @ over
anything wider keeps its _xlfn.SINGLE. The import didn't put it back,
so the cell showed the row-aligned value while its formula read
=$I$112#, the spelling for a spill.

The @ goes back the way the binary format import already does, and
only for that thinned shape: a non-master cell whose whole formula is
one reference with the operator after it, not even parentheses.
Anywhere wider, injecting an @ would force the operand into an array
and change the result, so (A1)# is left alone.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib8071804eaa805880e20552eeee92665756c5cd7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10164
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 273dbf42009efe6d9f59edf28a4272cfe366196f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210314
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-09-01 09:08:51 +02:00
Tomaž Vajngerl d9dcb8a4cc sc: take the error constants from the grammar in use
The formula writer substitutes an error constant for anything that has
no spelling in the target format, and it took that string from the
native symbol table. That table is translated, so a build in another
language wrote a translated "#REF!" into the saved formula, where only
the English spelling reads back. It now takes the string from the
table of the grammar being written, which changes nothing for the
native grammar.

Reading had a gap of its own. The OOXML table had a second "#REF!" row
for the stop opcode, and the symbol map keeps the first spelling it
finds in opcode order, so that row shadowed the error constant and a
bare #REF! read from a file gave #NAME?. Nothing needs a spelling for
the stop opcode, so the row is gone.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Idbe8ce87d6ad6346106622379ff59908a36eb7f7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10163
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 75160522f6a9de4999b824725326475ba8d713f1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210313
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-09-01 09:08:38 +02:00
Tomaž Vajngerl 62eced2131 sc: allow parentheses on both sides of the intersection blank
The compiler decides whether a blank is the intersection operator by
looking at the tokens on either side. Both parenthesis tokens have the
separator type, which those checks read as "not a reference", so
=SUM((A1:B1,A2:B2) A1:A2) didn't parse even though it should intersect
the list with the range. The parentheses are now accepted by their
opcode, and so is a reference list built from a converted separator on
either side.

The blank also keeps the parenthesis after it a group. An operand
followed by a parenthesis normally opens an argument list for a
callable, but with the intersection blank in between there is no call,
so ocCall is not injected. A name in front still calls either way.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8e4b5fb7136419b4e937c8abb521711a8d418af8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10162
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit b12a7af0e1aab942aa5014b799a96271d03c472f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210305
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 23:49:41 +02:00
Tomaž Vajngerl d0b3ad7b83 sc: read a function call after the range operator
A symbol like F25:IF lexes as a single range reference because IF is
also a valid column name, and then it doesn't name a valid range. The
whole symbol was kept as an unknown name, so =SUM(F25:IF(TRUE,F26))
gave #NAME? instead of summing up to the cell the call returns.

When such a symbol is followed by an opening parenthesis the lexer now
backs up to the range operator and reads it again (a symbol that isn't
a reference at all is already handled elsewhere). The reference then
stands on its own and the call after the operator is read separately.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I709de89206008894b1a07b482ac1ca5457ab059d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10161
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit cc8e9277c510bb8f95d11a37e326a68bd8bca513)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210304
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 20:27:29 +02:00
Tomaž Vajngerl c56ee53c8a sc: join list parts that start with a reference
A reference list is built from the separator between two parts of a
parenthesised expression, and which parts join was decided from the
opcodes at the top of their compiled code. That was wrong both ways:
INDEX returns a reference but wasn't counted, so (INDEX(A1:A3,1),B1)
fell apart, while (F25, F26%) didn't parse at all even though it
should give #VALUE!.

The left part now has to end in a reference or an opcode that returns
one, INDEX among them. The right part joins if it starts with one, or
with a nested list, IF, CHOOSE, a lookup, the @ operator or an error
constant. What it then makes of that reference is the calculation's
business.

A part starting with anything else leaves the separator a separator
and the formula doesn't parse, which is what the producing
application refuses at its formula bar too.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: If65d2f0f9cc599729564cfc5369c0479b07c93e8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10160
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 50ddf65b640a9cb88aea56be4367a3e44152c230)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210303
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 20:27:22 +02:00
Tomaž Vajngerl 72751755b5 sc: report #VALUE! when a reference list is used as a single value
A reference list holds several ranges, so there is no single value an
operator could work on. GetDouble and GetString had no case for it and
fell through to their parameter error, so =(A1,B1)% and =(A1,B1)&"x"
came out as Err:504 instead of #VALUE!. They report #VALUE! now. A
function that takes the list itself, like SUM, still reads it whole.

The union operator gave the generic Err:502 when one of its parts was
a value instead of a reference, as in =(A1~5). That reports #VALUE!
now too.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8da8a30a691f2b939d04af7f1abd58e586cd5b7f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10159
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 2cd6e380f8aec3b691dc15ade6798191b8d64d89)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210302
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 20:27:09 +02:00
Tomaž Vajngerl 850bbbc51a sc: keep a spilled range's place through an operator
A # spilled range travels as a matrix that carries the range it was
read from, so a plain cell can reduce it to the value of the row it
lines up with. Every operator dropped that range on the way through
GetMatrix and PushMatrix, so =A1#% on a master spilling 1, 2 and 3
came out as 0.01 instead of the row-aligned 0.02.

The range is now noted when a matrix operand that carries one is
popped, and put back on a result of the same shape - those are the
only way in and out. The note only lasts for one token, and two
operands with different ranges cancel it. Functions don't get it,
because something like SORT is free to move the values around.

The values of such a result are the operator's own, not the cells'.
So ScMatrixRangeToken now answers the two questions separately: the
range says where the values sit, and a second flag says whether the
values are still the ones the cells hold.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ie4851dcc208f20220f07cd75a691aee88cfdc5ac
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10158
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit a393976584be143df7cb40623f31322e46889756)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210301
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 20:27:00 +02:00
Tomaž Vajngerl 53d7ae86a0 sc: let a # over more than one cell report no reference
The # operator refers to the master cell before it. ScSpilledRange
took its operand with PopSingleRef, which turns anything that isn't a
single reference into a parameter error, so a # after a range or a
reference list came out as Err:504 instead of #REF!. Anything covering
more than one cell has no master, so it now reports #REF! too, same as
an empty cell or a non-master already did.

An array formula has to give the same single #REF!. The operand's
parameter class wasn't declared, so in an array context the range
operand got turned into a matrix and the operator ran once per cell -
a dynamic-array master then spilled the error matrix or gave #SPILL!.
Declaring the operand as a reference makes the operator take the range
as a whole.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I196f67b644035fbbf87b08c605a5dd2719831ba0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10157
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 54382bfb0bf025ee4839a6f6d86a07123e893400)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210294
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 17:35:29 +02:00
Tomaž Vajngerl 1ff770e5bf sc: bind the # operator to the reference it follows
The # postfix operator was parsed at the same level as the percent
sign, which is looser than the prefix operators. Because of this
=@A1# compiled to the spill range of an implicit intersection and
=-A1# to the spill range of a negated value. Neither of those means
anything, and OOXML writes the # around the reference alone in both
cases.

The # is now parsed right after the factor it follows, tighter than any
operator that can come before it. A whole range reference goes inside,
because the lexer reads A1:A3 as a single reference. The percent sign
stays above the reference operators, so =A1%# leaves the # with nothing
to bind to and doesn't parse, and the export drops such a # (OOXML has
no spelling for it either).

A blank between two operands is only the intersection operator if both
sides yield a reference. The # does, so the checks that keep the blank
alive and that keep the operator once the operands are known had to be
told about it.

The interpreter passes the spill range on as a matrix that carries the
range it was read from. The intersection, range and union operators
only took references and refused that, so PopReferenceOperand now
hands them the carried range as a double reference.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I1a587bf291ff639b326a366b8efc332106eb5fd3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10156
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit c589c34aaec00d60fc47f1933762b105f9d22de3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210293
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-31 17:35:25 +02:00
Tomaž Vajngerl 1ac50fddc7 sc: rewrite the token array into the OOXML shape
OOXML writes @ and # as calls and a reference union as a parenthesised
list, so for each of them we need to know how far the sub-expression
under it reaches. CreateStringFromTokenArray worked that out from the
text it had already written. The problem with this is that every pair
it spliced in shifted all the offsets that were still waiting behind
it, so it kept breaking.

Instead of patching the text, OOXMLRewriter builds a second
token array in the shape OOXML spells out and the writer walks that as
it stands. The call names and the parentheses are ordinary tokens, so
there is nothing to splice and no offsets to keep track of. It is the
same trick RewriteMissing already plays a few lines further up, and it
means the input and the output can be looked at side by side.

The # changes place, from behind its operand to a call in front of it,
so it gets an opcode of its own. _xlfn.ANCHORARRAY spells ocAnchorArray
now, and ocSpill keeps the native postfix spelling with no OOXML one at
all, so the postfix form cannot end up in a file by accident. The
import already rewrote the call back into the postfix operator and now
recognises it by opcode instead of by position. The @ stays
ocSingleValue, it is already in front of its operand so only the
parentheses are new.

The OFFSET() literal argument is swapped for the reference error in
the same pass, instead of the writing loop jumping over tokens.

A unit test drives the rewrite on its own: token array in, token array
out, one case per shape. The shapes that end up with nothing to wrap
are in there too - a lone @, one with a missing argument, and text that
never parsed - since those are the ones where dropping a token is the
only change.

Also drop the dead "@" and "#" rows from the OOXML symbol table, the
rows above them already have _xlfn.SINGLE and _xlfn.ANCHORARRAY.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I63944fb85b5c4388f304f7d94afd0b4f347d7782
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/10155
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 4feac84950f5b524d99e277481de50f7b45a8d2f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210292
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-31 15:56:50 +02:00
Tomaž Vajngerl 27fdb4291e sc: keep the @ and # wrapper starts right as the text moves
The _xlfn.SINGLE and _xlfn.ANCHORARRAY wrappers go into the formula
text once the operand they enclose stands in the buffer, and each
splice moves the text behind it along. Several of the recorded
starts did not move with it, so =SUM( @A1#) saved as
SUM(__xlfn.SINGLE(xlfn.ANCHORARRAY( A1))), and A1~-B2# and @-A1%#
came out wrong in the same way.

Whitespace ahead of a scope or of an expression is no longer part of
it, so the starts the wrappers and the union list use begin behind
the blank.

The spilled-range splice pulls an @ that begins inside its wrapper
back to the wrapper's own start, so the @ takes the whole wrapper as
its operand.

The union list's parenthesis and the @ splice both move the start of
the expression they go in front of, so a postfix operator behind
them still finds where its operand begins.

A scope's parentheses count as taken when a wrapper begins ahead of
the scope's text, as they already did when one begins exactly at it.

The writer of the pending @ wrappers asserts that they stand in the
order their operands begin. That order is what keeps the starts
still waiting correct, and each of these bugs broke it.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I87ca7c67be9d2f11ca34661a79401a2fa1c60bbe
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9638
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 3d52e137f229d87bc5633dc7966bace3044f1c3a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209504
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-23 13:32:49 +02:00
Tomaž Vajngerl 30134a14fc sc: test that pivot table values keep the source number format
The result cells of the pivot table output, the grand total row and
the grand total column included, are shown with the number format of
the source data. There was no test for it.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I22eaacf72152bda1d24787cb5b106519411333c3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8556
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 9630cf4f95123ed5de8450658868a06d4ec88084)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209433
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-21 10:32:55 +02:00
Tomaž Vajngerl 5826712e95 sc: draw all pivot table frames with one thin line width
The block frames of the pivot table output used a 1pt line inside
the table and a 2pt line on the outer edges of the table, and the
field cell frames used the 1pt line. Draw all of them with the
thin line width (0.75pt) instead, which is close to the line width
that the default OOXML pivot table style uses, so the whole table
uses one slim border.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Iec99b6890ffa5c8dfc699a30e2fc58d452e5d9ff
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8255
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 411cfdb91ee02a10361c3d72e13bd64161bba4d5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209432
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-21 10:32:40 +02:00
Tomaž Vajngerl 2795944b41 sc: stop writing pivot table cell styles to the document
The pivot table output set pivot table specific cell styles on its
cells ("Pivot Table Value", "Pivot Table Result", ...) and created
them in the document on first use. Remove this, so adding a pivot
table no longer adds any cell styles.

The attributes that these styles carried are applied as direct
formatting instead. The member cells are aligned to the left, and
the subtotal and grand total labels and results are bold. The left
alignment is also what makes the member indents visible, an indent
has no effect in a centered cell. The style name mapping in
stylehelper.cxx stays, so documents that already contain these
styles keep working.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib33d170d2da38a60c0739eda8fa79ce4f1fcf745
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8254
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit fee8a092ac3388c873d2b0763f7ce3d8cd032b76)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209431
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-21 10:32:29 +02:00
Tomaž Vajngerl 0753600cc2 sc: draw all pivot table output formatting in one class
The formatting of the pivot table output was spread across
ScDPOutput: cell styles and frames were applied inline while the
table content was written, and ScDPOutputImpl drew the block
frames.

Add sc::pivot::StyleOutput, which handles all the drawing. ScDPOutput
now only records the sections of the table (layout geometry, field
cells, member spans, subtotal and grand total rows and columns,
indents and expand or collapse buttons). The cell styles are applied
while the sections are recorded, at the same points where they were
applied before, so they always come before any direct formatting
(number formats, pivot table format attributes) reaches the cells.
The block frames, the indents and the expander buttons are then drawn
by apply() in one pass over the collected sections. The drawn output
stays the same as before, and a different look, like the OOXML pivot
table styles, can later be drawn from the same sections.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia628de6badf511d299f36bf474194ecad17a9c56
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8253
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 9f5a36c822b170835c55143716be9eca28ac3e91)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209430
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-21 10:32:13 +02:00
Tomaž Vajngerl 6be9e4a7bc sc: anchor the spilled-range wrapper on the whole expression
XLSX writes the # operator as _xlfn.ANCHORARRAY around its operand.
The walk spliced that wrapper at the start of the last factor, but the
grammar puts the # above the range, intersection and union operators,
so all of those belong inside the operand. A1~B2# came out as
(A1,_xlfn.ANCHORARRAY(B2)) and A1:INDEX(B1:B10;5)# as
A1:_xlfn.ANCHORARRAY(INDEX(B1:B10,5)).

Each parenthesis scope already records where its union-level
expression begins, which is in front of every operator the # binds
looser than. The wrapper splices there instead, and an open union list
takes its own parentheses first.

Nothing reads the separate stack of factor starts now. Its depth
matched the scope stack's, so that stack carries it and the vector is
gone.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6fd212a0e97ee40ce04c27a758f78669d3fce5f8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9309
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 82f4f2386f5d752a890de306fb89c314c4d87a1f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209412
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-21 00:02:32 +02:00
Tomaž Vajngerl cccd6f9f1a sc: parenthesize a reference union on OOXML save
OOXML writes the union operator as the same comma that separates
function arguments, so a union with no parentheses of its own fell
apart on save: SUM(A1~B2) became SUM(A1,B2), which reads as two
arguments, and INDEX(A1:B3~D1:E3;1;1;2) changed its meaning even on
Calc's own reload. A union already inside a parenthesised group was
safe, since the group's tokens are kept.

The walk now records where the union-level expression of each
parenthesis scope starts. The first union operator opens a list
there, and the list takes one pair of parentheses when the
expression ends: at a looser operator, an argument separator, the
parenthesis that closes the scope, or the end of the formula.

A list that fills a grouping parenthesis exactly keeps that pair, so
a formula holds its shape over repeated round trips. Two kinds of
parenthesis cannot be reused that way: the one behind AND, OR or the
call operator, which opens an argument list rather than a group, and
one that an @ wrapper beginning at the same place has already taken.

The group check behind the @ wrapper refuses a group with a comma
directly inside it, passing over the commas an inline matrix or a
bracketed reference carries. Text that no parse could read takes no
parentheses at all, so a list reaching such text drops its pair.

Unit tests pin the written string for each union shape, and the
positions the inserted parenthesis shifts. A round-trip test checks
the saved XML and the reloaded formulas and values.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I970f3b4ecea680f6e70d146a2f1608c4adff9c1f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9308
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit dd8104342d7147f07006bebb1dbeec60b48250f6)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209411
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-21 00:02:28 +02:00
Tomaž Vajngerl 44c66f0b84 sc: wrap the whole @ operand in _xlfn.SINGLE, or none of it
The @ operand walk on OOXML save took the token behind the @ as the
whole operand, so any operand longer than one token was cut apart by
the wrapper: a leading sign, a structured table reference, a range
built with the range operator, and a nested @ or # inside an
argument list all left parts of themselves outside.

The @ now writes no text of its own. The main token loop notes where
the wrapper has to go and which parenthesis scope the operand lives
in, and splices _xlfn.SINGLE(...) in once the operand ends. The #
splice already worked that way, so there is one walk with one set of
special cases and no recursion.

Prefix signs and whitespace go inside the wrapper with the operand,
and a sign moves the factor start past itself, so a # wraps the
reference it follows and leaves the sign outside. Table reference
brackets scope like parentheses, which keeps a multi-item reference
whole. AND and OR are written as a name with an argument list, so
such a name does not end the operand either and the whole call stays
inside.

An operand that writes no text leaves the wrapper out. One that is
exactly one parenthesised group lends its parentheses to the
wrapper, so a saved formula keeps its shape over round trips. Text
that never parsed takes no wrapper and drops a # behind it, the way
an ODF save leaves the @ out.

The error strings for tokens the writer cannot spell out sit in
AppendTokenOrError, which tests the cheap conditions first. The
whitespace opcode test moved to opcode.hxx as isWhitespaceOpCode,
replacing the private copies, and the operator range tests join it
there as isUnaryOperatorOpCode and isBinaryOperatorOpCode. Unit
tests pin the saved output for each operand shape.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I9b6c12bbc8d0ddb9efa043a2364d13c4699e7bd5
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9290
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 2b8d9c58e8f7b5f138d44f41d756421c3f7932cd)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209393
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-20 18:32:18 +02:00
Tomaž Vajngerl 0e10904d3c test an external name used as an argument has no parameters
An add-in name that stands in an argument position rather than
being called must compile with a parameter count of zero,
otherwise the interpreter pops phantom arguments off the stack
and underflows it.

The test compiles VLOOKUP(MONTH(D8)&"/"&YEAR(D8),MONTHS,2) with
the OOXML grammar, the path the xlsx import uses to recompile
formulas. MONTHS is a date add-in function filling the
lookup-array position, so it resolves to a plain external
operand rather than a call. Without the fix its count is left at
3, so the test guards the behaviour rather than passing either
way.

This covers the fix in commit
26fea7f668f463de7d5671b7002213e73799a557, "start an external
token's parameter count at zero".

Change-Id: Ic8515d9b4e6d46e129ee95c05c7fcd1643f66793
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/8060
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 5bccbfadc20bd31ac0ccc497052b3a5623010c19)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/209391
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-08-20 16:44:13 +02:00
Tomaž Vajngerl cd0b10a6fb sc: make LAMBDA and callable evaluation more robust
Follow-up hardening for the recently added first-class callables
(LAMBDA, LET and macros).

Bound the nesting depth of callable evaluation. A lambda body runs in a
fresh nested interpreter that does not pass through the per-cell
recursion counter, so a lambda that ends up calling itself could keep
nesting. Cap the depth at the same limit used for ordinary formula cell
evaluation and return an error once it is reached.

When cloning a lambda body, skip an empty or inverted token span and
copy only the tokens that are actually present, so no uninitialized slot
is ever handed to the new token array.

Cast a callable to its concrete type directly instead of doing a runtime
type check on every call, since the opcode already identifies the type.
Widen the parameter and argument counts from short.

Rewrite the copied macro comments in plain English and remove the
commented-out matrix callable code and its declaration.

Add tests for a terminating self-referential lambda and for a lambda
that would otherwise recurse without bound.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib520c48b5fa726f883ba4595d61786b45eeedbf5
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/7114
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 5b8c9b85c20257449907c8c69c0145ccdb017b03)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208345
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-08-19 08:39:59 +02:00
Tomaž Vajngerl acd586803b SwarmSolver: cache cell lookups and individual fitness
Two changes that cut the cost of the search, with the same result.

getCell resolved a cell from scratch on every read and write: fetch
the sheets, pick the sheet, query interfaces, then ask for the cell.
The search touches the same few cells many thousands of times. Keep
the resolved cells in a small cache keyed by sheet, column and row,
cleared at the start of each solve.

Differential Evolution recomputed the fitness of the original
candidate on every comparison, and each fitness writes every variable
into the document and reads the objective back. Store the fitness on
the individual when it is created, then reuse the stored value. This
roughly halves the number of fitness evaluations per generation.

On a four variable integer model the whole run dropped from about 16
to about 11 seconds, most of the remainder being fixed start up cost.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I96ea8c324c194dc217199f0b5318782937d43b92
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4057
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 7c97a43dc1b0127da468dd92ac0e7823e8788086)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208442
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:52:37 +02:00
Tomaž Vajngerl f4c5255cd6 SwarmSolver: add a constrained linear program test
The existing tests are single variable or bound only. Add a small
two variable linear program with real inequality constraints on
formula cells: maximize 2x + 4y subject to x + y <= 4 and x + 3y <= 6,
with both variables in [0, 10]. The two constraints meet at x = 3,
y = 1, where the objective reaches its maximum of 10, and the solver
finds that point.

The model is written in a small FODF spreadsheet so the formulas are
easy to read in the diff.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ie9bd269a92da0db07927a6b7500e2580801369b6
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4056
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 3befd66155132064c8543c0688eadd8ba2010192)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208441
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:52:28 +02:00
Tomaž Vajngerl 0209dc2476 SwarmSolver: always unlock the controllers when solve returns
solve locked the document controllers and only unlocked them at the
very end. Any of the cell accesses in between can throw, and the
unlock was then skipped, leaving the document locked for good.

Unlock from a scope guard so it runs on every exit, including an
exception.

Add a test that points a variable at a sheet that does not exist, so a
cell access throws, and checks the controllers are unlocked
afterwards.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8fc123ba5fe85dee0ec26119423a1c26f0c0ba4b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4055
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 78a71ff46d077dbe76643b3a2275834cf0f556f1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208440
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:52:13 +02:00
Tomaž Vajngerl eb0bacce10 SwarmSolver: reset per run state at the start of solve
Solving twice with the same solver carried state over from the first
run. The list of non-bounded constraints was only ever appended to,
and the bounds were resized rather than refilled, so old Bound values
stayed in place. A second solve then saw leftover constraints and
stale bounds and could fail on a model it should solve.

Refill the bounds with fresh defaults and clear the constraint list at
the start of each solve.

Add a test that solves an impossible model and then a solvable one on
the same solver, and checks the second run succeeds with the right
result.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6ca6ca63a2ebbe7ee8a419bdf04ec462430069e8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4054
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit e835a5c4d6a6383fc1fc17a8690620ee2796de0b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208439
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:52:03 +02:00
Tomaž Vajngerl 41af7cf401 SwarmSolver: keep an unbounded integer variable in range when seeding
An integer variable with no explicit bound keeps the default range,
which sits around the float limits. initializeVariables turned those
limits into a 64 bit integer, but a double that far out of range does
not fit, and the conversion is undefined. On a common platform both
ends collapsed to the same value, so every member of the population
started at one point, the search never varied, and it returned
nothing.

Clamp the bounds to a magnitude that converts safely before drawing
the random integer.

Add a test with an unbounded integer variable that checks the model is
reported as solved and the value comes back finite and whole.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6da89670eaa65683c09df6009cec9e8a1f58ef88
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4053
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit e695dc2d189f1f4a84c3f85ce0943a1b7f5800fd)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208438
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:51:51 +02:00
Tomaž Vajngerl 22f84b1b58 SwarmSolver: handle an empty or reversed variable range
When a variable's lower and upper bound are equal, or when
contradictory constraints leave the lower bound above the upper one,
the range is empty or reversed. boundVariable kept wrapping an out
of range value without the step ever bringing it inside, so solve
never returned. The random distributions in initializeVariables and
std::clamp in clampVariable are also undefined when the lower bound is
not below the upper one.

Treat an empty or reversed range as having only the lower bound:
return it directly in clampVariable and boundVariable, and seed it as
the value in initializeVariables. boundVariable now also wraps a
normal out of range value in one step instead of looping.

Add a test with a variable bounded both below 5 and above 10.
Before the fix solve looped forever. It now returns and reports the
model as not solved.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia1789490160f93ef986d8eabb564f3aa78d94dc0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4052
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 40306021956262ebeac85f575780904b644f56ec)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208437
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:51:42 +02:00
Tomaž Vajngerl 5a487764c0 SwarmSolver: skip unreadable constraint instead of passing the point
When a constraint's right hand side could not be read as a cell or a
number, the feasibility check returned "satisfied" for the whole
point. That stopped the loop and hid every constraint after the
unreadable one, so an infeasible model could be reported as solved.
Skip the constraint that cannot be evaluated and carry on checking
the rest.

Add a test that places an unreadable constraint before an impossible
one and checks the model is still reported as not solved.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I51fb10e3a7c1863ad24e1899fa51d4aaf6382349
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4051
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 216843854e4035fe71a39e908b744d67863dd406)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208436
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:51:31 +02:00
Tomaž Vajngerl 67b9784801 SwarmSolver: fix PSO position and velocity handling
Three problems in the particle swarm code:

The best-position vectors were updated with insert at the front rather
than assign, so each improvement prepended another copy. The vectors
grew on every step and the returned solution ended up longer than the
number of variables.

The velocity was seeded from initializeVariables, which fills it with a
random position. The first step then moved each particle by a whole
coordinate. The velocity now starts at zero.

The result of clamping the velocity was thrown away, so the call did
nothing. Clamping the velocity to the position bounds would in fact be
wrong when the bounds do not straddle zero, so the dead call is removed
and a comment explains why the velocity is left alone.

Also drops a redundant second clamp of the position during setup.

Tests drive the algorithm through a small mock data provider so the
checks do not depend on the random seed: one asserts the result has one
value per variable, the other asserts setup calls initializeVariables
once per particle. The large-objective test now runs Particle Swarm
Optimization too.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8197aa3368d20108c06bd3643ef1138ac6f2254a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4050
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 9353efb6419c16f0bcb104e57c4eb08ddcb4da81)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208435
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:51:22 +02:00
Tomaž Vajngerl 1b87464d79 SwarmSolver: use one consistent worst-case fitness sentinel
The penalty for a constraint-breaking candidate was the lowest float,
but the algorithms started their best fitness from the lowest double.
When a feasible objective fell below the float range, the penalty
compared as the better value, so the search preferred infeasible points
and gave up on a model that was actually solvable. Use the lowest double
for the penalty and for Particle Swarm Optimization's starting fitness so
an infeasible candidate always ranks below every feasible one. Add a test
that minimizes an objective into the 1e39 range.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib8cd067c7f12e43c6cd1927cccfac3ccec4ee6ff
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4049
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 30537d9289e47ae9281ebfcb99f274e717dafbaa)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208434
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 21:51:15 +02:00
Tomaž Vajngerl 0b054a016a SwarmSolver: do not report success for an infeasible model
Check the returned values before setting mbSuccess. They must cover
every variable, stay within each variable's bound, and satisfy the
remaining constraints. Otherwise report failure and set the status to
the infeasible-model message.

Add a test covering both algorithms.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Iad8056045dc198d184c3858e1167f484f1e55512
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4048
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit c7f7aafb35a9bedd6b70883da90441c26f457a3e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208433
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-22 14:29:56 +02:00
Tomaž Vajngerl 21b9909afa sc: add the ARRAYTOTEXT function
ARRAYTOTEXT returns a cell range or array as one text string,
taking the range and an optional format number.

Format 0 (or omitted) joins the values in row order with ", ".
Format 1 gives an array constant: braces around the array, ","
between columns, ";" between rows, and text quoted with any
inner double quote doubled. Any other format returns #VALUE!.

A number keeps its input-line text, a boolean reads TRUE or
FALSE in the current locale, an empty cell adds nothing, and an
error in a range lists as its error text while a reference to a
single error cell returns that error.

The function is wired up with its name tables, wizard entry,
parameter classification, and OOXML and binary filter mappings.

A self-checking OOXML workbook is verified after import and
after an XLSX and an ODS round trip, each with a recalc, and the
exported files are checked for the ARRAYTOTEXT formula. A ucalc
test covers the locale-dependent boolean text for a value and
a range.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I0cdd9737a51f402e39cf9a1346f4c5326680fe8f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/7219
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit f66673d65ee307f17512a4239dd4b52ac189ac20)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208395
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-22 08:05:35 +02:00
Tomaž Vajngerl 1f06d8b421 SLP solver: use a quadratic subproblem so it converges in fewer steps
The solver linearized the objective each step and solved a linear
subproblem. With no curvature the step always landed on a
trust-region corner, so on a larger model it crept toward the
optimum and ran out of time far short of it.

Replace the linear subproblem with a quadratic one. A curvature
matrix built by damped BFGS updates from the history of gradient
changes turns the model from a line into a paraboloid, so the
step aims at the model's minimum rather than a corner. The
constraints stay hard: the quadratic subproblem is solved by
Frank-Wolfe, where each inner step minimizes a linear objective
over the same feasible region with CoinMP, so every step stays
feasible. The outer trust region and the merit-function accept
or reject test are unchanged.

On a 100-period resource model that the linear version left far
from the optimum in the time limit, the quadratic version reaches
it: the curvature cuts the number of expensive finite-difference
samplings by enough to converge. The small linear,
nonlinear-objective, and nonlinear-constraint tests still pass,
the last one now landing on the constraint boundary to nine
digits.

The service keeps the name SLPSolver for continuity. The
user-visible description now says Sequential Quadratic Solver.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I51e1739cb6e2c46e41cc52e6687a796aaf6fcab3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4777
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit f0a4bb35743ef0aafdbe6c708666fd1c811e9ea5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208341
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-21 16:24:21 +02:00
Tomaž Vajngerl 17d713fdd4 solvers: cover the sequential linear solver on a large model
Add ResourceDepletion.fods, an original 100-period depletable-resource
model. It maximizes the sum over periods of discount^t times
(2*sqrt(extraction) minus a cost that rises as the stock falls,
cost*extraction/stock), choosing the extraction path B10:B109. The stock
column cascades, so each period's choice changes every later period, and
the remaining-stock column must stay non-negative throughout. The
starting path scores about 29.36 and the best known optimum, found with
an external gradient solver, is about 63.78.

The swarm solver cannot handle a model of this dimension and returns no
solution. The sequential linear solver loads it, stays feasible, and
improves the objective, which is what the test checks. The time limit is
kept short and only progress is checked, so the test does not depend on
machine speed.

Also raise the iteration cap in the solver from a fixed small number to
a high safety limit, so the time budget and the convergence test decide
when to stop. Smaller models still stop on convergence well before the
limit, so their behaviour is unchanged.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I4102c2ccb921888881179b23e305d652ceca86c3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4660
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 06e03222b6a4e58d07df409d3f86deb263e444a2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208340
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-21 16:23:52 +02:00
Tomaž Vajngerl d5e094de9b solvers: test the sequential linear solver on a nonlinear constraint
The earlier tests covered a nonlinear objective with plain bounds. Add
one with a curved feasible boundary: maximize x + y subject to
x*x + y*y <= 1, whose optimum sits on the circle at x = y = 1/sqrt(2),
about 0.7071. The constraint cell is a formula, so the solver linearizes
the circle at each step and has to converge to the tangent point. This
exercises the merit function against a nonlinear constraint rather than
a nonlinear objective.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Iaf5e35146a400143793786fe8f4a398b6f55ae6c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4659
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 0084a27588e3113a83c7ec22e563e9acf759f3b8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208339
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-21 16:23:41 +02:00
Tomaž Vajngerl 3b691f7215 solvers: add a sequential linear programming (SLP) solver on CoinMP
This adds a second nonlinear solver alongside the swarm one. It
treats the model as a black box and converges by repeated
linearization, which suits smooth models and reuses the linear
solver already shipped.

SLPSolver linearizes with a private SampleAffineModel helper. Around
a given point it approximates the objective and every constraint cell
with an affine model: a constant plus a slope for each variable. It
finds the slopes by nudging one variable at a time and measuring how
the cell value changes. The result uses the layout the CoinMP helper
expects: the constant term first (the value when all variables are
zero), then the slope for each variable.

SLPSolver runs the outer loop. Each iteration linearizes around the
current point, bounds the step to a trust region scaled per
variable, and solves that linear problem with the shared CoinMP
helper. A merit function that adds a penalty for constraint
violation decides whether to accept the step, and the trust radius
grows or shrinks with how well the linear model predicted the real
change. Success is reported only when the final point satisfies the
constraints.

The solver is registered as com.sun.star.comp.Calc.SLPSolver in the
CoinMP component group, so it is present only when CoinMP is built.

Tests: the solver solves a plain linear model, and minimizes a
parabola held in a formula cell, converging to its known minimum.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I2f79961844abbdd1412a318f1e4116995ea35955
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4649
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit a416cbede28604d2d7203113ce80d504aceb49f1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208338
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-21 11:39:59 +02:00
Tomaž Vajngerl 2c87d392dc solvers: extract the CoinMP problem build and solve into a helper
CoinMPSolver did the whole job in one function: sample the model by
perturbing each variable, build the CoinMP matrix from those
coefficients, solve, and read the result back. Move the matrix
build and solve into a standalone helper, coinmpSolveLinearModel,
that  takes the sampled coefficients plus the variable bounds and
column  types and returns the solution and status.

The helper treats the first entry of each cell's coefficient vector
as the value of an affine model at the origin and the rest as the
per variable slopes, which is what the existing sampling already
produces. That keeps the same convention while letting a future
caller supply coefficients sampled around a different point and its
own bounds, which is what a sequential linear programming loop
needs.

No behaviour change. CoinMPSolver still samples and sets up bounds
and column types exactly as before, then calls the helper. The
CoinMP solver unit test passes unchanged.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6d373a48caa9d67a932a599068368bae978f1f22
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4648
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 197f790a93880f56dd53db43e6bbb61c22ccfdc2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208337
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-21 11:39:34 +02:00
Tomaž Vajngerl 72da83e66f sc: read and write solver cells through ScDocument, not the UNO API
The solver components reached every cell through the spreadsheet UNO API:
getSheets, then getByIndex for the sheet, then getCellByPosition, each with
a QueryInterface. That indirection is cheap once but expensive when it runs
for every variable on every evaluation.

Now that the solvers live in sc, recover the ScDocument from the document
through the UNO tunnel (ScModelObj) in setDocument and read and write cells
directly with ScDocument::SetValue and GetValue. Automatic recalculation is
turned on there as well, so writing the input cells and then reading the
recalculated objective and constraint cells behaves exactly as before. Both
the shared SolverComponent base (used by the CoinMP solver) and the
standalone SwarmSolver are converted.

The results are unchanged because the recalculation engine is the same. Only
the cost of getting values in and out of the document drops. The swarm solver
evaluates the objective for a whole population every generation, so this is
its hot path: on a 100-variable model its evaluation loop runs about four
times faster. CoinMP touches the cells only once, in a single pass that reads
off the linear coefficients, so it gains less, about 1.7 times faster on the
same model.

Library_solver now links sc and includes sc/inc. SwarmSolver keeps the UNO
cell lookup only for the uncommon case where a constraint's right-hand side
is given as a cell address.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I03ccec74cdc4d6cde30c86e048bf4314c3ca1397
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4047
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 929c17cf8e51a1510c32399de4a94ba946bb9919)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208281
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-20 21:09:30 +02:00
Tomaž Vajngerl 9a09794354 Move the Calc solvers from sccomp into the sc module
Relocate the whole solver component out of the standalone sccomp module
and into sc. This moves the SolverComponent base class, the CoinMP and
Swarm solvers, the differential evolution and particle swarm headers, the
UNO service registration, the scc string resource, and the two unit tests.
The now-empty sccomp module is removed.

The solvers are Calc specific. Moving them inside sc is the first step
toward letting them work directly with ScDocument, and eventually clone a
document or its sheets so trial calculations can run in parallel. That
direct access is only possible from inside sc, so the relocation has to
come first.

This is a pure relocation with no behaviour change. The translation domain
stays "scc" and its polocation still points at "sccomp", so the existing
translations keep loading unchanged. Re-homing the scc translation domain
into sc is left as a follow-up.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I5b972daae4c51f04ca394fc9a92f89ac0c4dd599
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/3832
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 7f1bea8723761f97c053f2e7e46aab7081b1bb21)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208080
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-19 02:18:56 +02:00
Tomaž Vajngerl bfc9cfdecf Remove the lp_solve solver, which CoinMP makes redundant
The Lpsolve solver and the CoinMP solver are both used for the same
thing: a linear model, with optional integer variables by branch
and bound. CoinMP (CLP plus CBC) is the more capable and better
maintained of the two, so the lp_solve one adds a second bundled
dependency and a parallel copy of the solver code for no capability
the other lacks.

Remove the LpsolveSolver component, the bundled lp_solve external
(and its colamd dependency, which only lp_solve used), and all the
build, configure, packaging and license references to them.

A document may still name com.sun.star.comp.Calc.LpsolveSolver as
its engine. SolverSettings now reads that name as the CoinMP
solver, which solves the same model, so such documents keep
working.

The Calc solver test that exercised the engine now uses CoinMP. Its
knapsack model has a single optimal total value of 6987, which fills
the weight capacity of 1000 exactly and which CoinMP finds. The
lp_solve run had returned a suboptimal 6981, so the test now expects
the true optimum.

The build setup changes only take effect after re-running autogen.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia14d8c491779873da6b0157aa524056275adf8f5
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/3810
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit e77738a7ca7d44fd9d1f852e26d46e3a6d60467b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/208038
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-14 12:49:42 +02:00
Tomaž Vajngerl 65414dc5ac Extend the LAMBDA test document with optional and callable cases
Add cases for the OOXML forms the import now understands: optional
parameters written with the _xlop. prefix and a built-in function passed
as a value with the _xleta. prefix, along with further callable and MAP
cases. Loading and reloading the document as XLSX then checks that these
forms survive the round-trip.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I0e9acad066de12577cd40d410e25270241a406fe
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5784
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 6498928cf2ab2e03f8597d85717b0545d03985a9)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207893
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-12 14:24:45 +02:00
Tomaž Vajngerl 227fd26505 Import built-in functions passed as values with the _xleta. prefix
The OOXML format writes a built-in function that is passed as a value,
rather than called in place, with the _xleta. prefix, for example when
it is handed to a higher-order function or a LAMBDA parameter. Calc did
not recognise the prefix, so the function name did not resolve and the
reference was lost.

Strip the _xleta. prefix in the opcode lookup so the plain function name
resolves. The compiler already turns a function name that is not
followed by a parenthesis into a callable, so the value can be called
later with no further handling.

Add a test that imports COUNT passed as a value to a LAMBDA parameter
and calls it on an array.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I86ef5b71bb4e7cc655cb758dfc5d75caf74e5251
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5783
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit aa314fffc0ef133430e820a6d3b0ef784e20e6ee)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207892
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-12 14:24:40 +02:00
Tomaž Vajngerl d9241ddd7a Import optional LAMBDA parameters written with the _xlop. prefix
The OOXML format marks an optional LAMBDA parameter with the _xlop.
prefix in the parameter list, while the body refers to the same
parameter through the _xlpm. prefix. Calc recognised only _xlpm., so an
optional parameter kept its prefix, did not match the bare name used in
the body, and lost the mark that made it optional. It failed to bind,
and a call that left it out reported an error.

Recognise _xlop. as a parameter marker and strip it to the bare name, so
the declaration and the body references match. Carry the optional mark
onto the parameter token so the compiled code treats it the same way as
the native square-bracket form.

Write an optional parameter back with the marker the grammar uses: the
_xlop. prefix in OOXML and square brackets in the native grammars.

Add a test that imports an optional parameter in the OOXML form and
checks that ISOMITTED sees it as omitted and that concatenating it
yields the other operand.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia1df0fa6ca498f4c607507b3a865b3b367e5c11a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5782
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit f198d7a984654aabd5e988d3c327da5552bc68e0)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207891
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-12 14:24:34 +02:00
Tomaž Vajngerl ca01a68c8d Read the boolean format tag only from real double operands
When a double operand carries a boolean tag, string concatenation
renders it as TRUE or FALSE. The check for that tag used the resolved
stack type, which also reports a double for a missing argument or an
empty cell, and then read the tag from the operand as though it were a
double token.

Check the operand's own token type first, so the boolean tag is read
only from a real double operand. A missing argument or an empty cell
concatenates as an empty string.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I0da982b709fd968dd61e6b935979358cc8db8bab
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5781
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit ee2ca5da39bb81402b83f1cef7730c62adeffb12)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207890
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-12 14:24:04 +02:00
Tomaž Vajngerl dd2decfe19 sc: recreate a pasted table only when formulas are pasted
CopyFromClip() recreates a named table the copy fully covered so the
structured-reference tokens in the pasted formulas can resolve
against it. It ran that step for any content paste, so a values-only
paste also registered the table, with its autofilter and style, even
though it carries no formula that would reference the table.

Gate the table recreation on the formula content flag, so a
values-only paste leaves the destination without a table.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8bf7b30ef4fc415229d05f57955ac9d6cf65910e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5097
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
(cherry picked from commit c0ec48adf51d34a50469b78df4136aaaa1dce074)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207946
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 23:04:26 +02:00
Tomaž Vajngerl 4c787b42eb sc: add a regression test for a grouped-sheet copy of a named table
A grouped-sheet copy applies the same area to each selected sheet, so
only the tables on the copied sheet are carried into the clipboard,
not a same-area table living on another selected sheet. The paste
side therefore never receives tables from several sheets that it
would then place onto a single destination sheet.

The engine already behaves this way. This test guards against a
regression that would carry a table from another sheet into the
clipboard.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Icb6b5c0db0df4819ae2c2b45d3d81e173e586993
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5096
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
(cherry picked from commit a9ab487333670a2cbfd03c2382af87f2cc1bb387)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207945
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 23:04:14 +02:00
Tomaž Vajngerl 499ca3e23e sc: carry a table into the clipboard only from a copied range
CopyToClip() copies named database ranges into the clipboard document
through CopyDBsToClip(), which decided whether to carry a table by
testing it against the bounding box of the copied area. For a
non-contiguous selection the bounding box also covers the gaps
between the selected ranges, so a table sitting in such a gap was
carried even though none of its cells were copied.

Test the table against the actually copied ranges instead of their
bounding box, so a table that lies entirely in a gap is left out.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I1c8663e5190247dae0edf3878efe37186a7f6a4d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5095
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 37543af628671f723c9a661e2add91d6bc641591)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207944
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 23:04:00 +02:00
Tomaž Vajngerl 1722be4cb4 sc: make a cut-paste-created database range undoable
A cut paste can add a named database range to the destination
document, just as a copy paste can, when the clipboard carries the
range and the destination does not yet own it.
ScViewFunc::PasteFromClip() forced the database-range snapshot for
a copy paste so that the addition could be undone even into an
empty collection, but the cut-paste branch built its ScRefUndoData
without that flag. Undoing a cut paste that added the range
therefore left it orphaned over the now-empty cells.

Compute the force flag once from the clipboard's named ranges and
pass it to ScRefUndoData on both the cut and the copy path.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ic942f722c89b9a2e34b4d83345e16f123a775848
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5094
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit ed7fbe013519bf313c7d2bf12166938872b77b75)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207943
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 14:48:23 +02:00
Tomaž Vajngerl a84060c9ec sc: test that a cross-doc table-name clash leaves paste unresolved
Pasting a fully-copied named table into a different document that
already owns an unrelated table of the same name must not bind the
pasted structured-reference formula to that destination table. The
name is document-global and the copied table cannot be recreated
under it, so the reference stays unresolved (#REF!) instead of
silently computing the unrelated table's data.

The engine already behaves this way. This test guards against a
regression that would bind the pasted reference to the destination's
table and produce a silent wrong result.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I7304951c03ea090703f8e4232f92b1d14179b537
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5093
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
(cherry picked from commit cd8c7ad4578b5a7c5225e82b212f45b20cbb7d96)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207942
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 14:48:11 +02:00
Tomaž Vajngerl 21221fe1f0 sc: keep table column names when re-anchoring a pasted table
CopyDBsFromClip() re-anchors a fully-copied named table at the
paste location with MoveTo(). Changing the area resets the table
column names, because the header range moves, so the names the
clipboard copy had carried were discarded. A structured reference
could then no longer resolve a column by name against the pasted
table, and its value became #REF!.

Save the carried names before the move and set them again
afterwards. The paste is a pure translation, so the names map to
the same columns as before.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib5e836daa4c9739b38fd4fa8c8dc3d58c6793b75
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5092
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Balázs Varga <balazs.varga@collabora.com>
(cherry picked from commit 5f08075266d8337a91f0ed3c3f4a1a18d81d03d8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207941
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 14:48:01 +02:00
Tomaž Vajngerl 39d10139e0 sc: add an ODS round-trip test for the # spilled-range operator
The # operator uses the native # symbol in ODFF, so it needs no
special handling to survive an ODS save and reload. Cover it the
same way as the XLSX round-trip: load the fixture, check the
formulas, save and reload as ODS, and check again.

Change-Id: I900c418fb57b79a8e0434bdc2b4b8b9e88f5b05d
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/6265
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 5a5d2aa0cd4ca72be0288855889ae75562d6e34a)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207909
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-10 01:10:07 +02:00
Tomaž Vajngerl 0d303ccfd2 sc: add the dynamic-array XLSX import functions test
DynamicArrayFixture.xlsx is a self-checking workbook. The
test loads it, walks column C of each sheet asserting every
verdict cell reads TRUE, and checks each sheet's master cell.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I9f0ad05c5071eb3a0baea5aa5996ea526565b480
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5791
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 5ccb4ccdd9dc29883c46169b93c642da6e1e4458)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207908
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 01:09:58 +02:00
Tomaž Vajngerl e703f728ff sc: wrap the # operator in _xlfn.ANCHORARRAY on XLSX export
XLSX stores the # spilled-range operator as the prefix call
_xlfn.ANCHORARRAY(<ref>). The exporter emitted the bare #
glyph, which older readers reject.

The operand is already in the buffer when the parse walk
reaches ocSpill, so CreateStringFromTokenArray keeps a
per-parenthesis-depth stack of where the current factor
began. ocSpill splices _xlfn.ANCHORARRAY( at that position
and closes at the buffer's end.

For =@A1# the # binds tighter than @, giving the nested form
_xlfn.SINGLE(_xlfn.ANCHORARRAY(A1)). EmitSingleValueOperandOOXML
splices ANCHORARRAY around the operand and consumes the
trailing ocSpill, so the main loop does not wrap it again in
the wrong order.

Tests check the ANCHORARRAY form in the saved XML and
round-trip an imported spill formula through save and reload.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I9e92350d2574adeebf44fae903ee32dd63654d7c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5789
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 89715a92489305230b52f2874d768dc117a71eee)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207907
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-10 01:09:50 +02:00
Tomaž Vajngerl 454999a9e9 sc: rewrite _xlfn.ANCHORARRAY on XLSX import to the # operator
The XLSX f-element stores the spilled-range operator as the
prefix call _xlfn.ANCHORARRAY(<ref>). The compiler did not
know the wrapper, so _xlfn.ANCHORARRAY(C2) + 10 imported as a
name error.

Map _xlfn.ANCHORARRAY to ocSpill in the OOXML symbol table so
the wrapper compiles to the same RPN as <ref>#. A new
liftAnchorArrayToPostfix pass then rewrites the source-order
parse array to <ref># so the cell reads back as <ref># in the
formula bar.

Token-level tests cover the lift on a bare reference and an
arithmetic composition, and a file-import test loads an XLSX
fixture to confirm cells read back as # inside UNIQUE and SUM.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I687150ac0bb26d02ced9297dc2039c15655d5c0a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5788
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 91e9a978c85458dc5a7c557588da1c402bc43887)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207906
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 01:09:43 +02:00
Tomaž Vajngerl fc9f1f66ee sc: report ISFORMULA per cell over a spilled-range reference
A # spilled-range reference evaluates to a matrix that
ScIsFormula did not recognise, so ISFORMULA(B2#) returned a
single FALSE instead of the per-cell result a plain range
gives.

ScIsFormula now treats a range-carrying matrix like a range
reference, as the @ operator and ISREF already do. In an array
context ISFORMULA(B2#) matches ISFORMULA over the spill range,
and in a plain cell it reduces to the row-aligned cell.

Change-Id: Iceabe9d9a3ae72219291f8c7bf6986d355d8336a
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/6264
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit e0e6eb3c11380b6787a96c1f6fccfb3d6722022e)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207905
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 01:08:55 +02:00
Tomaž Vajngerl 6612d4f785 sc: add the # postfix operator for spilled-range references
A1# is the postfix counterpart of the @ prefix. When A1 is
the origin of a dynamic-array formula spilled into A1:A4, A1#
refers to the whole spill range, so SUM(A1#) totals the four
values and ROWS(A1#) returns 4.

The new opcode ocSpill sits in the unary-operator range. The
tokenizer produces it from a bare # after a reference or
value. Otherwise # stays the start of a #REF! or #NAME!
token. The compiler routes it through PostOpLine so it
follows its operand in the RPN.

ScSpilledRange pushes the spill range as an ScMatrixRangeToken
so aggregating functions see the whole range, and ISREF
reports it as a reference. A master yields its matrix. A
plain number, string, or non-master matrix slot yields the
the cell value.

A freshly typed =A1# counts as an array result, so it
promotes to a dynamic-array master and spills again. A cell
that does not promote collapses the range by implicit
intersection to the row-aligned value, or #VALUE! outside the
range, keeping that cell's number format.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Idf2752af3f7575f6bc95b940344aa365415e4a2f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5785
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 9b97b0fc91bce4b8726ea210347e8ea84270e148)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207904
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-10 01:08:46 +02:00
Tomaž Vajngerl f0b1496469 sc: report a dynamic-array spill cell as a value in ISFORMULA
A spilled cell is stored as a reference formula cell pointing
back at the master, so ISFORMULA reported true across the
whole spill range. Only the master holds the formula.

ISFORMULA now returns true only for the master and false for
the cells it spilled into, for both the single-cell and the
array-context range form. A classic array formula is
unchanged, as it has no dynamic-array master.

Change-Id: If4f495b45677001ad13e1f1a2a669d3d235d117b
Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/6244
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 5361d8e25d09ccde6922f5758a4d2d35df5c0418)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207894
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-10 01:08:40 +02:00
Tomaž Vajngerl b3f3a2458b sc: simplify stripRedundantParentheses and add unit tests
The strip walked the array twice with the same parenthesis
depth. Start past the inner open with nDepth = 2 and watch
for depth dropping to 1. If the next token is the outer
close, the inner pair spans the whole argument and is
stripped. Otherwise the parentheses stay.

Expose the helper from formulabuffer.hxx so the new
sc_oox_helpers_test target can drive it directly.
RedundantParenthesesStripperTest covers the strip cases
through the OOXML compiler.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I6ab95382b7776833b343754cdafb746454739d51
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5652
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 41557a39d420e45880070b6d942ebfbe32ef8b49)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207873
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-09 17:09:37 +02:00
Tomaž Vajngerl 47c226418e sc: extend mismatched-extent matrix arithmetic to the longer side
Element-wise matrix arithmetic between non-broadcast operands
of unequal length used to clip the result to the shorter side
and lose the trailing slots. The result should follow the
longer length, with #N/A in the slots past the shorter
operand.

Pair non-broadcast operands to the larger extent in
lcl_GetExtent (renamed from lcl_GetMinExtent), and switch the
per-cell out-of-bounds error in ExecuteBinaryOp from #VALUE!
to #N/A so the visible result matches the spec.

testMatrixArithmeticMismatchedExtent multiplies A1:A4 by the
column vector {1|2|3} and checks that the fourth row holds

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I34eb5b14c77f8d414677c8ebeaf7e331c413c2a3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5651
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit b4957ed105bcaa4a73666a6a5e119d523284b0c9)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207872
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:09:31 +02:00
Tomaž Vajngerl 46394856a9 sc: pick the row-aligned slot when @ collapses a bare range
The @ implicit-intersection operator on a bare multi-cell range
should return the slot of the range row-aligned with the
formula cell, and #VALUE! when the formula sits outside the
range. Calc returned the upper-left slot in every position,
matching the array-function case but not the bare-range case
the OOXML spec calls out.

ScSingleValue gains a branch for the bare-range and matrix-
with-range operand shapes. It reads the source range, picks
the slot whose row (or column for a row vector) matches the
formula address, and pushes #VALUE! when no slot matches.

ConvertMatrixParameters pairs the source range with the matrix
it builds for an @ operand. The new ScMatrixRangeToken keeps
the range coordinates alongside the materialised matrix. Other
operators continue to receive a plain ScMatrixToken.

testSingleValueOperator covers the row-aligned case: a formula
in each row of B2:B4 returns the matching slot, and the row
past the range returns #VALUE!.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I8eebc8550c3764508cf008eb324c73129ef6fde7
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5650
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit a98f64bf7d7067159c6caacbf895a1717ca5c083)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207871
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:09:23 +02:00
Tomaž Vajngerl b57cc044fe sc: auto-spill UI-typed CHOOSE and LET when a branch is an array
intendsArrayResultInRange walked branch slices for the IF
family but bailed on ocChoose and ocLet, so a plain-cell
CHOOSE or LET never auto-promoted even when one of its
branches produced a multi-cell array.

Treat ocChoose and ocLet like the IF family. The jump token
layout is identical, so the same branch-walk picks the right
slices.

LET conflates a binding value being array with the LET result
being array. The typical LET(x, A1:A3, x*2) propagates the
array shape through the body, so this over-approximation
matches the user's intent. A false positive enters CSE mode
for a scalar result, and the spill check catches it.

testAutoSpillFromChooseAndLet enters a CHOOSE that picks a
range branch and a LET that doubles a range binding.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I3339c748be32ccefa23c9f069f917299ec8ffe5c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5537
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 029292a4b5621f73401145b0269682e286eac98b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207870
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:09:16 +02:00
Tomaž Vajngerl 49e9da81fa sc: import @-wrapped XLSX cells as scalar formulas, not CSE arrays
XLSX tags a cell holding =@(expr) with t="array" ref=<self>,
because the OOXML writer evaluates the @ in array context. The
import path honoured the tag and routed such cells through
setMatrixCells, so GetFormula wrapped the imported text in {}
braces. A user-typed =@(F3:F5+0) came back as {=@(F3:F5+0)},
reading as a CSE array even though @ keeps the cell scalar.

When the t="array" range is a single cell and the parse array
starts with @, construct a plain ScFormulaCell instead of
calling setMatrixCells. The @ collapses the operand, so there
is nothing to spill.

testIntersectionOperatorXlsxRoundTrip loads the fixture and
asserts the @-wrapped cells read back as plain formulas, both
on import and after a round trip.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I05020fa2810a8f2d4f0c9374a9a8afc39e2d9e5e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5536
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 75eda52e374810561261485ed038e70d9e2edab1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207869
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:09:01 +02:00
Tomaž Vajngerl 14d723ea36 sc: let @ collapse arrays from range arithmetic, not just functions
The @ operator collapses an array operand to its upper-left
value. A function operand like SEQUENCE pushes a matrix and @
collapses it. A range arithmetic operand like (B2:B3+0) does
implicit intersection first and hands @ a scalar, so @ has
nothing to collapse.

Mark @'s operand as ForceArray in the parameter classifier and
wrap UnaryLine in a CurrentFactor scope so the classification
reaches the nested operators. Range arithmetic inside @ then
materialises as a matrix and reaches @ for the upper-left pick.

testSingleValueOperator covers @ on a range expression and a
chained @ binary.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I7f59bfa1efbceeceb9a3d7f71a0243e9a521b24a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5517
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 4148cbecbdaa3164c53d3869252a3ca714ba5726)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207868
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:08:49 +02:00
Tomaž Vajngerl ee4bd7f201 sc: ignore the dynamic-array master flag when @ ends the RPN
An @-prefixed formula opts out of dynamic-array spilling, but
the OOXML import still arrives with cm="1" on such cells. The
cell then turned into #SPILL! because the spill check ran
against the 1x1 declared range.

Reject the dynamic-array master request when the RPN ends with
@. The @ collapses the array at interpret time.

testAtFormulaIgnoresDynamicArrayMasterFlag checks that
SetDynamicArrayMaster has no effect on an @-prefixed cell.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I33322626a32893287c8859580a719dbeb9b915b4
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5490
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 02c7d85b6a0aa6269582072642501148da2d092d)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207867
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:08:36 +02:00
Tomaž Vajngerl 5663da9fa7 sc: render booleans as TRUE or FALSE in string concatenation
When a boolean value is concatenated with &, the result should
spell out TRUE or FALSE. Three layers dropped the type tag and
returned "1" or "0" instead.

ScCompiler::ParseValue now compiles bare TRUE and FALSE in
native grammar as the ocTrue or ocFalse function call, so the
interpreter pushes a typed double tagged LOGICAL.

ScAmpersand peeks the operand and, when it carries the LOGICAL
tag, formats through the LOGICAL standard format. The
concatenation operator is the only caller, so paths that want
the numeric string form, such as BIN2HEX(TRUE()), are
unaffected.

ScMatrixImpl::GetString and ScMatrixImpl::MatConcat format
element_boolean cells through the same LOGICAL key.

testMatrixConcatRendersBooleansAsTrueFalse covers the scalar
=TRUE&"!" path, the matrix-scalar GetString path, and the
matrix-matrix MatConcat path.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I726b3bb1b7733111153a7b100b0e4703052ea80c
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5589
Reviewed-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit e156dbdd2bfe57c2edd4c8ec64f340335c0142e2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207866
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 17:08:29 +02:00
Tomaž Vajngerl 45473674e8 Size MAP to the longest argument array, not the shortest
MAP used the smallest dimensions across its argument arrays, so
when the arrays had different lengths the result was clipped to
the shorter one, dropping the elements only the longer array
reached.

Size the result to the largest argument array instead. A position
one array does not reach has no value to map, so it is left as a
not-available error, the way the OOXML format treats a length
mismatch.

Add a test that maps an addition over a three-element and a
two-element array and confirms the result has three elements, the
third a not-available error.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I11e756c74598de408da01cbc6844b674dda64ceb
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5607
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit bc64a61d28bc9b1eeee0e5c08f67a0945a2781d1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207865
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 15:58:42 +02:00
Tomaž Vajngerl cb9b0f4550 Isolate per-element errors in the element-wise array functions
MAP, BYCOL, BYROW and MAKEARRAY call a lambda once per result cell
but did not clear the error state between cells, so an error in one
cell leaked into every cell after it and the whole result came back
as a single error. Clear the error state after each cell is stored,
so a failing cell keeps its own error and the rest compute cleanly.
REDUCE and SCAN are left as is, since a fold is meant to carry an
error through the accumulator.

Add a test mapping a division over an array whose middle element is
zero, checking the cells around it keep their values and IFERROR
recovers only the failing one.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I449ffb7aa7366d63e5e62cc377d4de783dfeef50
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5605
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 0969cc4d424c1074580ba918e1271e4a10bcc9d8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207864
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 15:58:31 +02:00
Tomaž Vajngerl 5a797fca07 Check the LAMBDA document on load and across an XLSX round-trip
The document test for LAMBDA and the callable functions only looked
at the combined result after a recalculation. That left two things
unchecked: whether the result stored in the file is right as loaded,
and whether the formulas survive being written back out as XLSX.

Check the result cell B3 at four points now: as loaded, after a
recalculation, and both again after saving to XLSX and reading it
back. The as-loaded checks cover the stored results, the
recalculation checks cover the engine recomputing them, and the
round-trip covers the export path.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I490a764c71f83925425de70595611c67404ed761
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5592
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit a5e8980cbf689590a8a984effd51440607ed623f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207823
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-09 11:42:17 +02:00
Tomaž Vajngerl 9da48950f8 Add a document test for LAMBDA and the callable functions
A stored document gives broad coverage of the LAMBDA and ISOMITTED
functions and the higher-order functions BYCOL, BYROW, MAKEARRAY,
MAP, REDUCE and SCAN, in more combinations than the unit tests
cover.

Every function has its own sheet that compares each formula result
against the expected value stored beside it. The first sheet
combines all of those per-function checks into a single cell that
is true only when every formula matched.

A filter test loads the document, recalculates it and confirms that
combined cell is true, so a regression in any of the covered
functions makes the test fail.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I9c56e5bae343d53139ad5ac3f68b050678778640
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5587
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 2b7e77fb22b0430f528ebb4d8efeef14c52909e3)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207822
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 08:45:43 +02:00
Tomaž Vajngerl 9c34dddef2 Pass array arguments to a callable whole, not per element
The call operator was missing from the parameter classification
table, so its arguments defaulted to the value class, which in
array context iterates a range argument element by element. A
callable such as LAMBDA whose body reduces its argument then
spilled to a #SPILL! error in a dynamic-array formula instead of
returning a single value. Classify the call arguments so each
reaches the callable as a whole array.

Add a regression test covering this case.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ia13c7b1d1942bd92b0ec4f7f79106237eaac1d01
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5586
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 6aa937093e66d1a5a9b212a7cc8e53c24edde4d1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207821
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-09 08:45:34 +02:00
Tomaž Vajngerl dee27ddfb6 Add tests for callables in Calc formulas
These cover the formula behaviour that first-class callables add, and
guard the regressions found while bringing the feature in.

The tests check that:

- an unknown name reports a name error and keeps its text in the stored
  formula expression
- a LET whose bound parameters use the ODFF _xlpm. prefix resolves those
  parameters and evaluates to the right value
- a built-in used as a value and then called returns the same result as
  calling it directly, with no arguments, with one argument, and with
  several
- a built-in chosen by IF and then called returns the chosen function's
  value
- a call used as an argument to another call evaluates the inner call
  first and feeds its result to the outer one
- a callable used where a number is expected reports an illegal
  parameter error instead of crashing

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ic7150f32b7db613811669fbc1f1bbe83520f9ae8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5585
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 941f444ac8267c266ab6ce3d63212ce62b09ff39)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207820
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-07-09 08:45:05 +02:00
Tomaž Vajngerl 0522c17c22 sc: round-trip the @ operator through XLSX as _xlfn.SINGLE
The OOXML spec writes =@(expr) as =_xlfn.SINGLE(expr). Calc
emitted the bare @ glyph on save and dropped the wrapper on
load, so =@(F3:F5+0) fell back to a non-array context after a
round trip and lost the authored value.

Map "_xlfn.SINGLE" to ocSingleValue in the OOXML symbol
table. The regular exporter walk now spells ocSingleValue as
"_xlfn.SINGLE" and the import compiler recognises the
wrapper, so a parenthesised operand round-trips with no
further work. A small exporter branch wraps bare and
function-call operands in the wrapper's own parentheses.

OOXML files in the wild put the @ operand inside a redundant
parenthesis pair, _xlfn.SINGLE((expr)). A post-compile pass
on import drops the inner pair so the cell reads back as
=@(expr). The helper takes a trigger-opcode list for reuse.

testSingleOperatorXlsxRoundTrip covers the export path,
testIntersectionOperatorXlsxRoundTrip covers the import strip
from a fixture file.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I2648da125945295fd8548842aba9c82978d96aa9
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5573
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 5ac9db1f563bbde01f9e24fc9bbb8c3599eb0c06)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207789
Tested-by: Jenkins
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 23:47:16 +02:00
Tomaž Vajngerl 3b880b81f7 sc: scope auto-promotion eligibility to freshly typed cells
The mbAutoDynamicArrayEligible bit marks a cell as allowed to
auto-promote to a dynamic-array master on its first interpret.
The copy constructor propagated the bit, so a paste or fill
clone of a still-eligible source inherited the eligibility and
could auto-promote at the destination position.

Clear the bit on copy and move the explicit set onto the cell
after the local-to-document copy in the UI entry path. The
clone starts non-eligible, and only the freshly typed cell
handed to the document keeps the bit. A paste no longer leaks
into auto-promotion.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I164cdf2411bb4d31148c3a73e1e70440c5f604dc
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5194
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 8b46008b1f709f06cc88040d674659e15ea6f53b)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207788
Tested-by: Jenkins
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 23:47:06 +02:00
Tomaž Vajngerl 27990ac97f sc: carry the dynamic-array flag through Undo and Redo of EnterMatrix
The undo object for an EnterMatrix command stored the cell
range and the formula but not the dynamic-array flag. The Redo
path then rebuilt the cell with the default flag value, so a
dynamic-array master entered through the UI came back as a
static Ctrl+Shift+Enter array after a Redo.

Store the flag on the undo object and thread it through the
Redo path. The redone cell keeps the dynamic-array master
status it had before the Undo.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Iee9881fff3b0acb1a5ab6c71e02222a4a4d7d7a0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5193
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit 4c1bc56498cc7200eb7e72b251a73c7dba9e5bbc)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207787
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 23:46:56 +02:00
Tomaž Vajngerl 936fb78484 sc: keep multi-cell ranges unfolded in the @-restore compiler
XLS-imported cells arrive without an RPN, so the @-restore path
compiles the parse to RPN on demand. That on-demand compile
used to run with the implicit-intersection optimisation on, and
multi-cell range pushes were rewritten to single-cell picks
before the array-intent walk could see them. A plain =A1:A4+10
reloaded from an XLS file then walked as a scalar expression
and never got the @ marker restored.

Disable the optimisation on the on-demand compile. The walk
now sees range pushes as ranges, the binary + with a range
operand walks as array, and the @ marker is restored. Shapes
like =@TRANSPOSE(B1:B4) keep working through the matrix-
function branch.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I92879aa6604864dfc5a5b41999177dbfcffce21a
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5192
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 196cf615e544e62add27fd5eeb42b1cc5f47fbc2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207786
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 23:46:44 +02:00
Tomaž Vajngerl ea5d5dceab sc: keep @ on each operand from propagating array intent
The pre-Interpret walk that decides whether a freshly typed
formula should auto-promote to a dynamic-array master treats
the @ implicit-intersection operator as a transparent unary.
A formula whose every operand carries an @ then walks as
array-and-array and the cell promotes to a master, even though
the interpreter evaluates each @ operand as a scalar. The
displayed value is correct, but the saved file carries the
master flag on a cell that should stay plain.

Treat ocSingleValue as forcing scalar in the walk. The result
of @ is scalar, so the array-ness of its operand no longer
propagates through it.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ifa2a676bc62a2733b17b728773e6693669ad7717
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5106
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit fbc6b948f675f7787cbe48e55c5f55302eef5e32)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207785
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 20:59:01 +02:00
Tomaž Vajngerl 53b3f5a6f8 sc: spill an IF formula whose branches are multi-cell ranges
The pre-Interpret walk that decides whether a freshly typed
formula should auto-promote to a dynamic-array master used to
bail on every jump command. An IF whose branches are multi-
cell ranges then stayed a plain single-cell formula and only
the upper-left of the chosen branch showed in the cell.

The walk now descends into the branches of IF, IFERROR and
IFNA, reading the branch offsets off the jump token. The
formula promotes to a dynamic-array master and spills when any
branch produces an array.

CHOOSE and LET still bail. Their jump-offset layouts differ
from the IF family and need dedicated handling.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I75a76a2af3634588d76c410e5487b4a87571f3ba
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5105
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit c1fa705be1b53ff677d70040e002bd5d9d7b84c2)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207784
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 20:58:48 +02:00
Tomaž Vajngerl 3c95eed2ba sc: promote UI-typed array formulas to dynamic-array masters
When the user types a formula that returns an array (UNIQUE,
TRANSPOSE, a range plus a range, ...) and presses Enter, the
cell now becomes a dynamic-array master. The array result
spills into the empty cells to the right and below the typed
cell. The user no longer has to press Ctrl+Shift+Enter to get
array behaviour.

Formulas loaded from a file and formulas created from a macro
keep their old single-value behaviour, so existing documents
are not affected.

A new per-cell bit, mbAutoDynamicArrayEligible, marks a cell
as allowed to promote. The view code sets it on freshly typed
formulas. The importers and the macro entry path leave it off.

When such a cell first calculates, the formula tokens are
walked in execution order to ask the question: does this
formula produce an array result? If yes, the cell switches to
dynamic-array master mode before the calculation runs, so the
array result is kept and the cells to the right and below get
filled in.

The walk tracks whether the value sitting on the stack at each
step is an array or a scalar:

 - Pushing a multi-cell range or an inline matrix puts an
   array on the stack.
 - An operator like + or * keeps the result an array if any
   operand was an array.
 - A function known to return an array (UNIQUE, TRANSPOSE,
   MMULT, SEQUENCE, ...) puts an array on the stack. Other
   functions reduce to a scalar.
 - Branching functions like IF, CHOOSE, IFERROR and LET do
   not fit this simple model, so the walk gives up and the
   cell stays a plain single-cell formula.

A formula that begins with the @ implicit-intersection
operator (=@UNIQUE(...)) opts out of promotion. Cells that
are part of a formula group share a single compiled formula
and stay single-cell so the group is not broken.

Ctrl+Shift+Enter still produces a classic static array master.
The shortcut now routes straight to the matrix-entry code, so
the auto-detect described above does not interfere.

The formula compiler has an optimization that rewrites a
multi-cell range as a single-cell pick when the formula sits
in a single-cell context. For example =A1:A4 typed in row 2
becomes =A2 before evaluation. That is the long-standing
implicit-intersection rule.

For typed input the optimization has to be off, otherwise the
multi-cell range disappears from the formula before the walk
can see it. The optimization stays in place everywhere else,
and the engine still applies implicit intersection at run
time when the cell remains a plain single-cell formula.

XLSX export

XLSX marks a dynamic-array master with cm="1" on the cell.
The exporter used to fall back to "matrix master plus a
dynamic-array function" when the explicit flag was missing.
The fallback is now removed. Every cell that should export as
dynamic carries the flag explicitly, set by the importer or
by the UI entry path.

One trade-off: a Ctrl+Shift+Enter (CSE) array of a dynamic-
array function imported from an XLSX file that has no cm="1"
marker no longer round-trips as dynamic. The cell flag is now
the single source of truth.

Tests

Tests cover the array-intent walk for =MINVERSE(...), ={1;2},
={1;2}+3, ={1;2}+{3;4}, ={1;2}+A1, =ABS({-1;-2}), =A1:A2,
=A2:A5+B2:B5, a long chain of binary additions over the same
range, a range times a scalar, =IF(...) with array branches,
and =SUM(...) over a range as the scalar case.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Icb4b5e108634cd1529ac829cb34865c56ed8ec02
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4684
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit c3d67d3ec81621c385744fac7fd7a722084a122f)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207219
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 20:58:41 +02:00
Tomaž Vajngerl eb239f1a15 sc: round-trip dynamic-array masters through XLSX, strip @ on XLS
XLSX marks a dynamic-array master with cm="1" on the cell and a
matching dynamicArrayProperties metadata block. Both import and
export now travel through mbDynamicArrayMaster on the cell.

XLSX import plumbs the flag through the three formula paths.
XML_normal and XML_shared land in applyCellFormulas and
applySharedFormulas through setCellFormula. XML_array lands in
applyArrayFormulas through setCellArrayFormula. Each path sets
mbDynamicArrayMaster on the imported cell or on the matrix master.

XLSX export reads the flag in XclExpFormulaCell::SaveXml to drive
cm="1" and the dynamicArrayProperties block. The legacy pair of a
Ctrl+Shift+Enter (CSE) master that calls a dynamic-array function
stays as a fallback while the importers and the UI entry path are
being wired through.

XLS has no @ encoding and no dynamic-array marker. Save and load
mirror the ODF behaviour:
- The exporter strips ocSingleValue (alongside ocSpaces and
  ocWhitespace) in GetNextToken so the BIFF stream stays valid.
  The saved formula evaluates to the upper-left value the way XLS
  always did.
- The importer walks the just-built RPN of every plain non-array
  formula and prepends @ when the formula intends an array. The
  resolve runs before the formula cell is constructed, while the
  parse array is still mutable. The static helper compiles a
  missing RPN on demand so the array-intent walk has the post-fix
  order it needs.
- XLS array records keep the legacy CSE behaviour. They have no
  dynamic-array encoding, so loading them as static arrays matches
  what the file format can express.

Tests cover an XLSX import of Spill.xlsx, an XLSX round trip of the
same file, a direct-XML XLSX export check that the spilled master
at C2 keeps cm="1" and the dynamicArrayProperties metadata block,
and an XLS round trip of =@TRANSPOSE(B1:B4) where the @ reappears
after reload.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I30318bad4cdb50bbd92240fd71d70df0959a0eda
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4664
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit 79eda1a177abf9a348dbc67c211b26cabd0cb4c1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207218
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-07-08 18:00:00 +02:00
Tomaž Vajngerl 5286d91e80 sc: round-trip dynamic-array masters and @ through ODF
Import classifies each formula cell:
- Plain non-array, no coext:spill: the import-time walk over
  the freshly compiled RPN decides if the formula intends an
  array. If yes, prepend @ at the front of the parse and at
  the back of the RPN.
- Plain non-array + coext:spill="true": a master saved while in
  spill state. Comes back at 1x1 with the dynamic flag, the
  position marked spilled. The next interpret re-checks the
  blockers.
- Array formula (matrix dims): static array master by default.
  coext:spill="true" promotes it to a dynamic-array master.
  Legacy Ctrl+Shift+Enter (CSE) arrays without the marker keep
  their static iteration-and-pad behaviour.

Export reverses:
- ODF always strips @ on save. The @ is an in-memory marker
  only. OOXML and the UI grammar keep it.
- Dynamic master: coext:spill="true" in extended ODF, matrix
  dims as before. In spill state, write as plain non-array with
  coext:spill="true".
- Static array master: matrix dims, no coext:spill.

GetFormula skips the {} wrapping for dynamic-array masters so
they read back as plain formulas. Static array masters keep {}.

XML_SPILL is added to the xmloff token machinery and the ODF
extension schema (OpenDocument-v1.4+libreoffice-schema.rng)
gains coext:spill on table-cell.

Tests cover each import shape, the FODS export of regular and
spilled masters, the @ insertion on CompileXML, and the @
stripping at the formula-compiler level. ArrayFormulasStatic.ods
is a legacy fixture whose B1 = AND(C4:C52) summarises 49 per-row
value checks. The test runs the grid on import and after an ODS
round trip and confirms plain bare-range cells gain @ while the
five matrix masters stay static, keep their {} wrapping, and
get no coext:spill in the saved XML.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ib8ae345528185c824415ec5d039b6d5cf0d7eac8
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4663
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
(cherry picked from commit a21f6a3a0eae72fef90576d249d96dc00beeb2c5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207171
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-06-24 00:04:16 +02:00
Tomaž Vajngerl 4b445e5a0c vcl: make WebP images native
The WebP filter only attached a NativeWebp GfxLink when running
unit tests, so older readers would not get .webp images in saved
ODT files. WebP is widely supported now, so drop the gate. The
native bytes survive import and export writes the picture back as
.webp instead of re-encoding it to PNG.

Tests: testLoadWEBP now also checks the imported Graphic carries
a NativeWebp GfxLink. testWebpImageRoundtrip inserts a WebP into
a Writer document, saves as ODT and reloads, then checks the
picture is stored as .webp with no .png fallback and that the
reloaded graphic reports image/webp and keeps its NativeWebp
GfxLink.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Change-Id: I7874a121ce414da2ff37444a5e725771daf7d79e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4748
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Michael Meeks <michael.meeks@collabora.com>
(cherry picked from commit b10e1a22c7c1f8b9d9053dd938a117d763e89588)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206996
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-06-22 16:04:24 +02:00
Tomaž Vajngerl a3253ebf8d basegfx: speed up B2DHomMatrix a bit
translate/scale/rotate/shearX/shearY each used to build a
temporary B2DHomMatrix in the obvious sparse form and run the
generic doMulMatrix to combine it with *this, which multiplies and
adds a lot of zeros for every call. Make it faster by applying each
transform directly to the six stored cells instead.

Also unroll doMulMatrix itself (caching the six old values in
locals since the result overwrites them) and unroll the isIdentity
/ identity loops.

Using the micro-bench the speedup can be 50% for translate and
shear operations. In practice this will not have much measurable
effect (it could accumulate as B2DHomMatrix is used a lot for
rendering) but as the code is also more straight forward it is
worth merging.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: If13d91d4c7353be4e4433d8d8bb7f40866578a5b
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/2755
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit 3db39371454fa7de994dbc9b214668a4818ed357)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/205801
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-06-16 22:27:31 +02:00
Tomaž Vajngerl 4a903f85b8 sc: add mbDynamicArrayMaster and gate matrix auto-resize on it
Introduce a per-cell bit on ScFormulaCell that marks a matrix master
as dynamic. A matrix master with the flag set recomputes its range
at interpret time so the result grows or shrinks with the source
data. A matrix master without the flag is a static array master and
keeps its declared range frozen.

SetDynamicArrayMaster(true) on a plain single-cell formula promotes
the cell to a 1x1 matrix master so the first interpret expands it
through the auto-resize gate. Callers that opt a NONE cell into the
dynamic-array path only have to flip the one flag.

ScFormulaCell::InterpretTail uses the flag as the single opt-in for
the auto-resize path. The path fires when the cell has the dynamic
flag, or when the cell is currently in spill state and needs to
re-check on the next interpret. Static masters entered through
ScDocFunc::EnterMatrix or ScDocument::InsertMatrixFormula keep the
flag false so their declared range is the user's intent and never
changes underneath them.

InsertMatrixFormula and EnterMatrix grow a new bDynamicArrayMaster
parameter so callers can choose. The existing spill tests opt in
to keep their auto-resize behaviour. The two contraction tests in
uicalc2 entered their matrix masters with Ctrl+Shift+Enter, which
no longer drives auto-resize, so insertArrayToCell grows an opt-in
bDynamicArrayMaster parameter and those two tests pass true to
flip the master into the dynamic gate after entry.

The paste path in cliputil.cxx re-creates a matrix master at the
destination via ScDocFunc::EnterMatrix. The call now reads the
source cell's dynamic-array flag and passes it on, so a dynamic
master pasted to a new location stays a dynamic master and the
auto-resize gate keeps firing.

Importers (XLSX cm, ODS coext:spill) and the UI entry path will
set the flag in the follow-up commits.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: Ie6fbf6972aa796f3833b726ea0184fc34ed92a3d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4173
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 006b0c6577c4e35087595cb92b629757c8c4d166)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206674
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
2026-06-16 09:14:51 +02:00
Tomaž Vajngerl 6f1fb25a94 sc: add @ prefix operator for opting out of array spill
The @ operator collapses an array operand to its upper-left value.
It is the implicit-intersection prefix used to opt a formula out
of the dynamic-array spill behaviour. =@SEQUENCE(4) returns 1
instead of the four-row spill.

The new ocSingleValue opcode joins the unary-operator range so the
existing compile and interpret machinery treats it like any other
unary prefix. The @ character stops being part of name syntax in
the OOXML grammar, so sheet names that contain @ have to be quoted
from now on.

In-session only for now. The operator is parsed and interpreted
but not yet serialised into XLSX or ODS.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I18cf56e34f933ebb7f6e43c157618c9fe28dbe0f
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4172
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit fbd04d7c394ad7445a667bf63820f88551b176e1)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206673
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-06-16 09:14:42 +02:00
Tomaž Vajngerl 0ee4ae5aa3 sc: Drop assert on stale sheet view ID in GetSheetViewNumber
In a multi-view session one view can hold a sheet view ID that
another view has just removed. Looking that ID up returns null and
the function then hit assert(false), crashing debug builds for
something that is a normal race. Release builds already fall through
to the existing `return -1`. Drop the assert.

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I17686a81566b4cb06284718bee0fa575064e6af3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/3811
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 1a6a1e2559d42ff4e372d49ac53b34d91c58fbe9)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206339
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2026-06-12 08:53:00 +02:00