Variables: a CSV header that names one variable twice is refused

dataset.import read two header cells that named one variable (City
and City, or Shown and #Shown) into the same column, so each row kept
only the last non-blank cell and the other value was dropped without a
word. The import is now refused with the two column numbers, and the
message has a row in every complete-message catalog.
This commit is contained in:
dotCooCoo
2026-11-02 12:03:40 +07:00
committed by bflatastic
parent 386bfc881e
commit 06221ce545
26 changed files with 71 additions and 4 deletions
+1 -1
View File
@@ -731,7 +731,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Import Data Sets…",
["Window", "Variables"],
None,
"{path? | data? | dataBase64? | csv?} load datasets from CSV, comma- or tab-separated (header: name, then variable names; one row per dataset; without the name column every header is a variable and the rows take the first free names Data Set 1, 2…). A header that names no variable defines one, unbound: `#Name` defines a visibility variable, `@Name` defines an image variable, and a header without a prefix defines a text variable. A prefixed header whose name is a variable of another kind, and a `%` header that isn't a variable's exact name (graph data files), are refused, and so is a header that would define a variable while the variables are locked. Rows whose name already exists are replaced → {imported, updated, names, defined}",
"{path? | data? | dataBase64? | csv?} load datasets from CSV, comma- or tab-separated (header: name, then variable names; one row per dataset; without the name column every header is a variable and the rows take the first free names Data Set 1, 2…). A header that names no variable defines one, unbound: `#Name` defines a visibility variable, `@Name` defines an image variable, and a header without a prefix defines a text variable. A prefixed header whose name is a variable of another kind, and a `%` header that isn't a variable's exact name (graph data files), are refused, and so are two headers that name one variable and a header that would define a variable while the variables are locked. Rows whose name already exists are replaced → {imported, updated, names, defined}",
has_doc,
dataset_import
),
+6 -2
View File
@@ -252,8 +252,8 @@ fn header_variable(
/// names; the other headers name variables, as [`header_variable`] reads them, and the
/// variables the document doesn't define yet are defined, unbound. Without the `name` column,
/// every header names a variable and the rows take the first free names "Data Set 1", "Data
/// Set 2" and so on. Existing rows of the same name are replaced; new names are appended. One
/// undo step.
/// Set 2" and so on. Two headers that name one variable are refused. Existing rows of the same
/// name are replaced; new names are appended. One undo step.
pub(super) fn dataset_import(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "dataset.import";
let bytes = datasets_csv_bytes(p, C)?;
@@ -277,6 +277,10 @@ pub(super) fn dataset_import(s: &mut Session, p: &Value) -> Result<Value> {
return Err(bad(C, format!("empty header in column {}", i + 1)));
}
let (name, kind) = header_variable(cell, i + 1, &kinds, &mut defined, C)?;
if let Some(&(first, ..)) = columns.iter().find(|(_, n, _)| *n == name) {
let (first, column) = (first + 1, i + 1);
return Err(bad(C, format!("columns {first} and {column} both name the variable `{name}`")));
}
columns.push((i, name, kind));
}
// Lock Variables refuses New Variable, so the import is refused when a header would define one.
+22
View File
@@ -1519,6 +1519,28 @@ fn datasets_csv_refuses_graph_data_file_columns() {
assert!(vars.variables.is_empty() && vars.datasets.is_empty());
}
/// Two header cells that name one variable, by the same name or by a name and its prefix, are
/// refused with both columns, and the import changes nothing.
#[test]
fn datasets_csv_refuses_two_columns_for_one_variable() {
let mut s = session();
s.execute("variable.define", &json!({"name": "Shown", "kind": "visibility"})).unwrap();
let error = |s: &mut Session, csv: &str| s.execute("dataset.import", &json!({"csv": csv})).unwrap_err().to_string();
for (csv, first, second, var) in [
("name,City,City\nR,Paris,Rome", 2, 3, "City"),
("name,City,City\nR,Paris,", 2, 3, "City"),
("City,Title,City\nParis,x,Rome", 1, 3, "City"),
("name,Shown,#Shown\nR,true,false", 2, 3, "Shown"),
("Title\t@Photo\t@ Photo\nx\ta.png\tb.png", 2, 3, "Photo"),
] {
let err = error(&mut s, csv);
assert!(err.contains(&format!("columns {first} and {second} both name the variable `{var}`")), "{csv}: {err}");
}
let vars = &s.doc().unwrap().doc.variables;
assert_eq!(vars.variables.len(), 1);
assert!(vars.datasets.is_empty());
}
/// While the variables are locked, an import is refused when a header cell would define a
/// variable, as New Variable is, and a file whose headers all name variables imports.
#[test]
+1
View File
@@ -5248,6 +5248,7 @@ digits Malayalam Malaiàlam
@msg not found no s’ha trobat
@msg not in pattern editing mode no esteu en mode d’edició de motius
@msg finish editing the pattern first acabeu primer l’edició del motiu
@msg columns {first} and {column} both name the variable `{name}` les columnes {first} i {column} anomenen totes dues la variable `{name}`
@msg nothing encoded no s’ha codificat res
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for no s’ha aplicat res del conjunt de dades {name}: cap objecte està vinculat a una variable per a la qual tingui un valor
@msg nothing selected no hi ha res seleccionat
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5473,6 +5473,7 @@ mark-style Japanese Japonské
@msg Batch: {count} processed, {failed} failed Dávka: zpracováno {count}, selhalo {failed}
@msg choose a destination folder vyberte cílovou složku
@msg choose a source folder vyberte zdrojovou složku
@msg columns {first} and {column} both name the variable `{name}` sloupce {first} a {column} uvádějí stejnou proměnnou `{name}`
@msg open a document with data sets otevřete dokument se sadami dat
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for ze sady dat {name} se nic nepoužilo: žádný objekt není svázán s proměnnou, pro kterou má hodnotu
@msg the action has no steps akce nemá žádné kroky
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5050,6 +5050,7 @@ effect Soft Weich
@msg Batch: {count} processed, {failed} failed Stapelverarbeitung: {count} verarbeitet, {failed} fehlgeschlagen
@msg choose a destination folder wählen Sie einen Zielordner
@msg choose a source folder wählen Sie einen Quellordner
@msg columns {first} and {column} both name the variable `{name}` die Spalten {first} und {column} nennen beide die Variable `{name}`
@msg open a document with data sets öffnen Sie ein Dokument mit Datensätzen
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for aus dem Datensatz {name} wurde nichts angewendet: kein Objekt ist an eine Variable gebunden, für die er einen Wert hat
@msg the action has no steps die Aktion hat keine Schritte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5404,6 +5404,7 @@ effect Points Σημεία
@msg `{key}` must be an array of these names: {_1} το `{key}` πρέπει να είναι πίνακας με αυτά τα ονόματα: {_1}
@msg choose a destination folder επιλέξτε φάκελο προορισμού
@msg choose a source folder επιλέξτε φάκελο προέλευσης
@msg columns {first} and {column} both name the variable `{name}` οι στήλες {first} και {column} ονομάζουν και οι δύο τη μεταβλητή `{name}`
@msg open a document with data sets ανοίξτε ένα έγγραφο με σύνολα δεδομένων
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for δεν εφαρμόστηκε τίποτα από το σύνολο δεδομένων {name}: κανένα αντικείμενο δεν είναι συνδεδεμένο με μεταβλητή για την οποία έχει τιμή
@msg select an Intertwine group επιλέξτε μια ομάδα διαπλοκής
Can't render this file because it contains an unexpected character in line 164 and column 5.
+1
View File
@@ -5414,6 +5414,7 @@ mark-style Japanese Japonesas
@msg Batch: {count} processed, {failed} failed Lote: {count} procesados, {failed} con error
@msg choose a destination folder elija una carpeta de destino
@msg choose a source folder elija una carpeta de origen
@msg columns {first} and {column} both name the variable `{name}` las columnas {first} y {column} nombran la misma variable `{name}`
@msg open a document with data sets abra un documento con conjuntos de datos
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for no se aplicó nada del conjunto de datos {name}: ningún objeto está vinculado a una variable para la que tenga un valor
@msg the action has no steps la acción no tiene pasos
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5416,6 +5416,7 @@ mark-style Japanese Japanilainen
@msg `{key}` must be an array of these names: {_1} `{key}`-arvon on oltava taulukko, jossa on näitä nimiä: {_1}
@msg choose a destination folder valitse kohdekansio
@msg choose a source folder valitse lähdekansio
@msg columns {first} and {column} both name the variable `{name}` sarakkeet {first} ja {column} nimeävät molemmat muuttujan `{name}`
@msg open a document with data sets avaa asiakirja, jossa on tietojoukkoja
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for tietojoukosta {name} ei käytetty mitään: mitään objektia ei ole sidottu muuttujaan, jolle siinä on arvo
@msg select an Intertwine group valitse lomitusryhmä
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5470,6 +5470,7 @@ theme Appearance Apparence
@msg Batch: {count} processed, {failed} failed Traitement par lots : {count} traités, {failed} en échec
@msg choose a destination folder choisissez un dossier de destination
@msg choose a source folder choisissez un dossier source
@msg columns {first} and {column} both name the variable `{name}` les colonnes {first} et {column} nomment toutes deux la variable `{name}`
@msg open a document with data sets ouvrez un document avec des jeux de données
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for rien du jeu de données {name} n’a été appliqué, car aucun objet n’est lié à une variable pour laquelle il a une valeur
@msg the action has no steps l'action n'a aucune étape
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5458,6 +5458,7 @@ imageTrace Outline Körvonal
@msg Batch: {count} processed, {failed} failed Kötegelt feldolgozás: {count} feldolgozva, {failed} sikertelen
@msg choose a destination folder válasszon célmappát
@msg choose a source folder válasszon forrásmappát
@msg columns {first} and {column} both name the variable `{name}` a(z) {first}. és a(z) {column}. oszlop ugyanazt a változót nevezi meg: `{name}`
@msg open a document with data sets nyisson meg egy adatkészleteket tartalmazó dokumentumot
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for ebből az adatkészletből semmi sem lett alkalmazva: {name}; egyetlen objektum sincs olyan változóhoz kötve, amelynek értéke van benne
@msg the action has no steps a műveletnek nincsenek lépései
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5085,6 +5085,7 @@ lighting Light Luce
@msg Batch: {count} processed, {failed} failed Elaborazione batch: {count} elaborati, {failed} non riusciti
@msg choose a destination folder scegli una cartella di destinazione
@msg choose a source folder scegli una cartella di origine
@msg columns {first} and {column} both name the variable `{name}` le colonne {first} e {column} indicano entrambe la variabile `{name}`
@msg open a document with data sets apri un documento con set di dati
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for non è stato applicato nulla del set di dati {name}: nessun oggetto è associato a una variabile per cui ha un valore
@msg the action has no steps l'azione non ha passaggi
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5130,6 +5130,7 @@ lighting Light ライト
@msg Batch: {count} processed, {failed} failed バッチ: {count} 件を処理、{failed} 件が失敗しました
@msg choose a destination folder 出力先フォルダーを選択してください
@msg choose a source folder ソースフォルダーを選択してください
@msg columns {first} and {column} both name the variable `{name}` 列 {first} と列 {column} が同じ変数 `{name}` を指定しています
@msg open a document with data sets データセットのあるドキュメントを開いてください
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for データセット {name} から何も適用されませんでした: 値のある変数にバインドされたオブジェクトがありません
@msg the action has no steps アクションにステップがありません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5121,6 +5121,7 @@ imageTrace Outline კონტური
@msg Batch: {count} processed, {failed} failed პაკეტური დამუშავება: დამუშავდა {count}, ვერ დამუშავდა {failed}
@msg choose a destination folder აირჩიეთ დანიშნულების საქაღალდე
@msg choose a source folder აირჩიეთ წყარო საქაღალდე
@msg columns {first} and {column} both name the variable `{name}` სვეტები {first} და {column} ორივე ასახელებს ცვლადს `{name}`
@msg open a document with data sets გახსენით დოკუმენტი მონაცემთა ნაკრებებით
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for მონაცემთა ნაკრები {name}: არაფერი გამოყენებულა; არცერთი ობიექტი არ არის მიბმული ცვლადზე, რომელსაც მასში მნიშვნელობა აქვს
@msg the action has no steps მოქმედებას ნაბიჯები არ აქვს
Can't render this file because it is too large.
+1
View File
@@ -5048,6 +5048,7 @@ lighting Light 광원
@msg Batch: {count} processed, {failed} failed 일괄 처리: {count}개 처리됨, {failed}개 실패
@msg choose a destination folder 대상 폴더를 선택하세요
@msg choose a source folder 소스 폴더를 선택하세요
@msg columns {first} and {column} both name the variable `{name}` {first}열과 {column}열이 모두 같은 변수 `{name}`을(를) 가리킵니다
@msg open a document with data sets 데이터 세트가 있는 문서를 여세요
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for 데이터 세트 {name}에서 적용된 항목이 없습니다: 값이 있는 변수에 연결된 개체가 없습니다
@msg the action has no steps 액션에 단계가 없습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5325,6 +5325,7 @@ mark-style Japanese Japońskie
@msg Batch: {count} processed, {failed} failed Przetwarzanie wsadowe: przetworzono {count}, niepowodzenia: {failed}
@msg choose a destination folder wybierz folder docelowy
@msg choose a source folder wybierz folder źródłowy
@msg columns {first} and {column} both name the variable `{name}` kolumny {first} i {column} wskazują tę samą zmienną `{name}`
@msg open a document with data sets otwórz dokument z zestawami danych
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for z zestawu danych {name} nic nie zastosowano: żaden obiekt nie jest powiązany ze zmienną, dla której ma on wartość
@msg the action has no steps operacja nie ma kroków
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5347,6 +5347,7 @@ mark-style Japanese Japonesas
@msg Batch: {count} processed, {failed} failed Lote: {count} processados, {failed} com falha
@msg choose a destination folder escolha uma pasta de destino
@msg choose a source folder escolha uma pasta de origem
@msg columns {first} and {column} both name the variable `{name}` as colunas {first} e {column} nomeiam a mesma variável `{name}`
@msg open a document with data sets abra um documento com conjuntos de dados
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for nada do conjunto de dados {name} foi aplicado: nenhum objeto está vinculado a uma variável para a qual ele tem um valor
@msg the action has no steps a ação não tem etapas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -5080,6 +5080,7 @@ lighting Light Свет
@msg Batch: {count} processed, {failed} failed Пакетная обработка: обработано {count}, с ошибкой {failed}
@msg choose a destination folder выберите папку назначения
@msg choose a source folder выберите исходную папку
@msg columns {first} and {column} both name the variable `{name}` столбцы {first} и {column} указывают одну и ту же переменную `{name}`
@msg open a document with data sets откройте документ с наборами данных
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for из набора данных {name} ничего не применено: ни один объект не привязан к переменной, для которой в нём есть значение
@msg the action has no steps в действии нет шагов
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5408,6 +5408,7 @@ theme Appearance Utseende
@msg `{key}` must be an array of these names: {_1} `{key}` måste vara en lista med dessa namn: {_1}
@msg choose a destination folder välj en målmapp
@msg choose a source folder välj en källmapp
@msg columns {first} and {column} both name the variable `{name}` kolumnerna {first} och {column} anger båda variabeln `{name}`
@msg open a document with data sets öppna ett dokument med datauppsättningar
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for inget i datauppsättningen {name} användes: inget objekt är bundet till en variabel som den har ett värde för
@msg select an Intertwine group markera en sammanflätningsgrupp
Can't render this file because it contains an unexpected character in line 5 and column 25.
+19
View File
@@ -1705,6 +1705,25 @@ fn blend_spine_param_errors_say_what_is_wrong() {
assert!(matches!(&e, EngineError::BadParams { msg, .. } if msg == "select a blend or give `id`"), "{e}");
}
/// Import Data Sets' refusal of two header cells that name one variable is translated in every
/// language that translates every message.
#[test]
fn dataset_import_repeated_column_error_is_translated() {
use serde_json::json;
use vectorcraft_engine::{EngineError, Session};
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let e = s.execute("dataset.import", &json!({"csv": "name,City,City\nR,Paris,Rome"})).unwrap_err();
let want = "columns 2 and 3 both name the variable `City`";
assert!(matches!(&e, EngineError::BadParams { msg, .. } if msg == want), "{e}");
let e = e.to_string();
for code in COMPLETE_MESSAGES {
let lang = Lang::from_code(code).expect("registered");
let shown = message(lang, &e);
assert!(!shown.contains(want), "{code}: {shown}");
}
}
/// The errors of saving, renaming and deleting a tracing preset are translated in every language
/// that translates every message.
#[test]
+1
View File
@@ -5359,6 +5359,7 @@ theme Appearance Görünüm
@msg its zlib compression is damaged zlib sıkıştırması hasarlı
@msg its Zstandard compression is damaged Zstandard sıkıştırması hasarlı
@msg it decompresses to more than {_1} MB açıldığında {_1} MB'tan fazla alan kaplıyor
@msg columns {first} and {column} both name the variable `{name}` {first} ve {column} numaralı sütunların ikisi de `{name}` değişkenini adlandırıyor
@msg the file's variables and data sets weren't read (variables: {variables}, data sets: {data_sets}): the Variables panel doesn't list them dosyanın değişkenleri ve veri kümeleri okunmadı (değişkenler: {variables}, veri kümeleri: {data_sets}): Değişkenler paneli bunları listelemiyor
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for {name} veri kümesinden hiçbir şey uygulanmadı: değer içerdiği bir değişkene bağlı hiçbir nesne yok
@msg select an Objects on Path group bir Yol Üzerindeki Nesneler grubu seçin
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -5065,6 +5065,7 @@ lighting Light Світло
@msg Batch: {count} processed, {failed} failed Пакетна обробка: оброблено {count}, з помилкою {failed}
@msg choose a destination folder виберіть папку призначення
@msg choose a source folder виберіть вихідну папку
@msg columns {first} and {column} both name the variable `{name}` стовпці {first} і {column} вказують одну й ту саму змінну `{name}`
@msg open a document with data sets відкрийте документ із наборами даних
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for з набору даних {name} нічого не застосовано: жоден об’єкт не прив’язано до змінної, для якої в ньому є значення
@msg the action has no steps у дії немає кроків
Can't render this file because it contains an unexpected character in line 178 and column 5.
+1
View File
@@ -5068,6 +5068,7 @@ theme Appearance Giao diện
@msg Batch: {count} processed, {failed} failed Hàng loạt: đã xử lý {count}, lỗi {failed}
@msg choose a destination folder hãy chọn thư mục đích
@msg choose a source folder hãy chọn thư mục nguồn
@msg columns {first} and {column} both name the variable `{name}` cột {first} và cột {column} cùng chỉ định biến số `{name}`
@msg open a document with data sets hãy mở một tài liệu có tập dữ liệu
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for không có gì trong tập dữ liệu {name} được áp dụng: không có đối tượng nào được gắn với biến số mà tập dữ liệu có giá trị
@msg the action has no steps hành động không có bước nào
Can't render this file because it contains an unexpected character in line 200 and column 5.
+1
View File
@@ -4532,6 +4532,7 @@ imageTrace Outline 轮廓
@msg Batch: {count} processed, {failed} failed 批处理:已处理 {count} 个,失败 {failed} 个
@msg choose a destination folder 请选择目标文件夹
@msg choose a source folder 请选择源文件夹
@msg columns {first} and {column} both name the variable `{name}` 第 {first} 列和第 {column} 列都指定了变量 `{name}`
@msg open a document with data sets 请打开包含数据集的文档
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for 数据集 {name} 未应用任何内容:没有对象绑定到该数据集中有值的变量
@msg the action has no steps 该动作没有步骤
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2448,6 +2448,7 @@ file-filter Templates 範本
@msg Batch: {count} processed, {failed} failed 批次:已處理 {count} 個,失敗 {failed} 個
@msg choose a destination folder 請選擇目的地資料夾
@msg choose a source folder 請選擇來源資料夾
@msg columns {first} and {column} both name the variable `{name}` 第 {first} 欄和第 {column} 欄都指定了變數 `{name}`
@msg open a document with data sets 請開啟含有資料集的文件
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for 資料集 {name} 未套用任何內容:沒有物件綁定到該資料集中有值的變數
@msg the action has no steps 動作沒有步驟
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2 -1
View File
@@ -1648,7 +1648,8 @@ already walks Image Trace. Short agent entry points:
rows. `#Shown` defines a visibility variable `Shown`, `@Photo` defines an image variable `Photo`, and a cell without
a prefix defines a text variable. A prefixed cell whose name is already a variable of the prefix's kind names it;
one whose name is a variable of another kind fails the import. A `%` cell that isn't a variable's exact name (graph
data files) is refused. The reply's `defined` lists the variables the import defined. While the variables are
data files) is refused, and so are two cells that name one variable (`City` twice, or `Shown` and `#Shown`). The
reply's `defined` lists the variables the import defined. While the variables are
locked, an import whose header would define a variable fails with "variables are locked".
- Variable libraries: `variable.loadLibrary {path|xml|dataBase64}` (the Variables panel menu's Load Variable
Library…) replaces the document's variables and data sets with a variable library's in one undo step. The library