Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guide/built-in-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ Total number of functions: **{{ $page.functionsCount }}**
| SEARCH | Returns the location of Search_string inside Text. Case-insensitive. Allows the use of wildcards. | SEARCH(Search_string, Text[, Start_position]) |
| SPLIT | Divides the provided text using the space character as a separator and returns the substring at the zero-based position specified by the second argument.<br>`SPLIT("Lorem ipsum", 0) -> "Lorem"`<br>`SPLIT("Lorem ipsum", 1) -> "ipsum"` | SPLIT(Text, Index) |
| SUBSTITUTE | Returns string where occurrences of Old_text are replaced by New_text. Replaces only specific occurrence if last parameter is provided. | SUBSTITUTE(Text, Old_text, New_text, [Occurrence]) |
| TEXTJOIN | Joins text from multiple strings and/or ranges with a delimiter. Supports array/range delimiters that cycle through gaps. When ignore_empty is TRUE, empty strings are skipped. Returns #VALUE! if result exceeds 32,767 characters. | TEXTJOIN(Delimiter, Ignore_empty, Text1, [Text2, ...]) |
| T | Returns text if given value is text, empty string otherwise. | T(Value) |
| TEXT | Converts a number into text according to a given format.<br>By default, accepts the same formats that can be passed to the [`dateFormats`](../api/interfaces/configparams.md#dateformats) option, but can be further customized with the [`stringifyDateTime`](../api/interfaces/configparams.md#stringifydatetime) option. | TEXT(Number, Format) |
| TRIM | Strips extra spaces from text. | TRIM("Text") |
Expand Down
1 change: 1 addition & 0 deletions src/error-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class ErrorMessage {
public static ComplexNumberExpected = 'Complex number expected.'
public static ShouldBeIorJ = 'Should be \'i\' or \'j\'.'
public static SizeMismatch = 'Array dimensions mismatched.'
public static TextJoinResultTooLong = 'TEXTJOIN result exceeds the maximum allowed length of 32,767 characters.'
public static FunctionName = (arg: string) => `Function name ${arg} not recognized.`
public static NamedExpressionName = (arg: string) => `Named expression ${arg} not recognized.`
public static LicenseKey = (arg: string) => `License key is ${arg}.`
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/csCZ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'TBILLPRICE',
TBILLYIELD: 'TBILLYIELD',
TEXT: 'HODNOTA.NA.TEXT',
TEXTJOIN: 'TEXTJOIN',
TIME: 'ČAS',
TIMEVALUE: 'ČASHODN',
TODAY: 'DNES',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/daDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'STATSOBLIGATION.KURS',
TBILLYIELD: 'STATSOBLIGATION.AFKAST',
TEXT: 'TEKST',
TEXTJOIN: 'TEKST.KOMBINER',
TIME: 'TID',
TIMEVALUE: 'TIDSVÆRDI',
TODAY: 'IDAG',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/deDE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'TBILLKURS',
TBILLYIELD: 'TBILLRENDITE',
TEXT: 'TEXT',
TEXTJOIN: 'TEXTVERKETTEN',
TIME: 'ZEIT',
TIMEVALUE: 'ZEITWERT',
TODAY: 'HEUTE',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/enGB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'TBILLPRICE',
TBILLYIELD: 'TBILLYIELD',
TEXT: 'TEXT',
TEXTJOIN: 'TEXTJOIN',
TIME: 'TIME',
TIMEVALUE: 'TIMEVALUE',
TODAY: 'TODAY',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/esES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export const dictionary: RawTranslationPackage = {
TBILLPRICE: 'LETRA.DE.TES.PRECIO',
TBILLYIELD: 'LETRA.DE.TES.RENDTO',
TEXT: 'TEXTO',
TEXTJOIN: 'UNIRCADENAS',
TIME: 'NSHORA',
TIMEVALUE: 'HORANUMERO',
TODAY: 'HOY',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/fiFI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'OBLIG.HINTA',
TBILLYIELD: 'OBLIG.TUOTTO',
TEXT: 'TEKSTI',
TEXTJOIN: 'TEKSTI.YHDISTÄ',
TIME: 'AIKA',
TIMEVALUE: 'AIKA_ARVO',
TODAY: 'TÄMÄ.PÄIVÄ',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/frFR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'PRIX.BON.TRESOR',
TBILLYIELD: 'RENDEMENT.BON.TRESOR',
TEXT: 'TEXTE',
TEXTJOIN: 'JOINDRE.TEXTE',
TIME: 'TEMPS',
TIMEVALUE: 'TEMPSVAL',
TODAY: 'AUJOURDHUI',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/huHU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'KJEGY.ÁR',
TBILLYIELD: 'KJEGY.HOZAM',
TEXT: 'SZÖVEG',
TEXTJOIN: 'SZÖVEGÖSSZEFŰZÉS',
TIME: 'IDŐ',
TIMEVALUE: 'IDŐÉRTÉK',
TODAY: 'MA',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/itIT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'BOT.PREZZO',
TBILLYIELD: 'BOT.REND',
TEXT: 'TESTO',
TEXTJOIN: 'UNISCI.TESTO',
TIME: 'ORARIO',
TIMEVALUE: 'ORARIO.VALORE',
TODAY: 'OGGI',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/nbNO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'TBILLPRIS',
TBILLYIELD: 'TBILLAVKASTNING',
TEXT: 'TEKST',
TEXTJOIN: 'TEKST.KOMBINER',
TIME: 'TID',
TIMEVALUE: 'TIDSVERDI',
TODAY: 'IDAG',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/nlNL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'SCHATK.PRIJS',
TBILLYIELD: 'SCHATK.REND',
TEXT: 'TEKST',
TEXTJOIN: 'TEKST.KOPPELEN',
TIME: 'TIJD',
TIMEVALUE: 'TIJDWAARDE',
TODAY: 'VANDAAG',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/plPL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'CENA.BS',
TBILLYIELD: 'RENT.BS',
TEXT: 'TEKST',
TEXTJOIN: 'POŁĄCZ.TEKSTY',
TIME: 'CZAS',
TIMEVALUE: 'CZAS.WARTOŚĆ',
TODAY: 'DZIŚ',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/ptPT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'OTNVALOR',
TBILLYIELD: 'OTNLUCRO',
TEXT: 'TEXTO',
TEXTJOIN: 'UNIRTEXTO',
TIME: 'TEMPO',
TIMEVALUE: 'VALOR.TEMPO',
TODAY: 'HOJE',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/ruRU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'ЦЕНАКЧЕК',
TBILLYIELD: 'ДОХОДКЧЕК',
TEXT: 'ТЕКСТ',
TEXTJOIN: 'ОБЪЕДИНИТЬ',
TIME: 'ВРЕМЯ',
TIMEVALUE: 'ВРЕМЗНАЧ',
TODAY: 'СЕГОДНЯ',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/svSE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'SSVXPRIS',
TBILLYIELD: 'SSVXRÄNTA',
TEXT: 'TEXT',
TEXTJOIN: 'TEXTJOIN',
TIME: 'KLOCKSLAG',
TIMEVALUE: 'TIDVÄRDE',
TODAY: 'IDAG',
Expand Down
1 change: 1 addition & 0 deletions src/i18n/languages/trTR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ const dictionary: RawTranslationPackage = {
TBILLPRICE: 'HTAHDEĞER',
TBILLYIELD: 'HTAHÖDEME',
TEXT: 'METNEÇEVİR',
TEXTJOIN: 'METİNBİRLEŞTİR',
TIME: 'ZAMAN',
TIMEVALUE: 'ZAMANSAYISI',
TODAY: 'BUGÜN',
Expand Down
78 changes: 78 additions & 0 deletions src/interpreter/plugin/TextPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {CellError, ErrorType} from '../../Cell'
import {ErrorMessage} from '../../error-message'
import {Maybe} from '../../Maybe'
import {ProcedureAst} from '../../parser'
import {coerceScalarToString} from '../ArithmeticHelper'
import {InterpreterState} from '../InterpreterState'
import {SimpleRangeValue} from '../../SimpleRangeValue'
import {ExtendedNumber, InterpreterValue, isExtendedNumber, RawScalarValue, InternalScalarValue} from '../InterpreterValue'
Expand Down Expand Up @@ -156,6 +157,15 @@ export class TextPlugin extends FunctionPlugin implements FunctionPluginTypechec
{argumentType: FunctionArgumentType.SCALAR}
]
},
'TEXTJOIN': {
method: 'textjoin',
repeatLastArgs: 1,
parameters: [
{argumentType: FunctionArgumentType.ANY},
{argumentType: FunctionArgumentType.BOOLEAN},
{argumentType: FunctionArgumentType.ANY},
],
},
}

/**
Expand Down Expand Up @@ -443,4 +453,72 @@ export class TextPlugin extends FunctionPlugin implements FunctionPluginTypechec
private escapeRegExpSpecialCharacters(text: string): string {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

/**
* Corresponds to TEXTJOIN(delimiter, ignore_empty, text1, [text2], …)
*
* Joins text from multiple strings/ranges with a configurable delimiter.
* Supports array/range delimiters that cycle through gaps between text values.
* When ignore_empty is TRUE, empty strings are skipped.
* Returns #VALUE! if the result exceeds 32,767 characters (Excel cell content limit).
*
* @param {ProcedureAst} ast - The procedure AST node
* @param {InterpreterState} state - The interpreter state
*/
public textjoin(ast: ProcedureAst, state: InterpreterState): InterpreterValue {
return this.runFunction(ast.args, state, this.metadata('TEXTJOIN'),
(delimiterArg: InternalScalarValue | SimpleRangeValue,
ignoreEmpty: boolean,
...textArgs: (InternalScalarValue | SimpleRangeValue)[]) => {

const delimiters = this.flattenArgToStrings(delimiterArg)
if (delimiters instanceof CellError) {
return delimiters
}

const texts: string[] = []
for (const arg of textArgs) {
const coerced = this.flattenArgToStrings(arg)
if (coerced instanceof CellError) {
return coerced
}
texts.push(...coerced)
}

const parts = ignoreEmpty ? texts.filter((t) => t !== '') : texts

if (parts.length === 0) {
return ''
}
let result = parts[0]
for (let i = 1; i < parts.length; i++) {
result += delimiters[(i - 1) % delimiters.length] + parts[i]
}

if (result.length > 32767) {
return new CellError(ErrorType.VALUE, ErrorMessage.TextJoinResultTooLong)
}
return result
}
)
}

/**
* Flattens a scalar or range argument into an array of coerced strings.
* Returns a CellError immediately if any value in the argument is an error or cannot be coerced.
*
* @param {InternalScalarValue | SimpleRangeValue} arg - Scalar or range to flatten
* @returns {string[] | CellError} - Array of string values, or the first error encountered
*/
private flattenArgToStrings(arg: InternalScalarValue | SimpleRangeValue): string[] | CellError {
const values = arg instanceof SimpleRangeValue ? arg.valuesFromTopLeftCorner() : [arg]
const result: string[] = []
for (const val of values) {
if (val instanceof CellError) {
return val
}
result.push(coerceScalarToString(val as InternalScalarValue) as string)
}
return result
}
}
Loading
Loading