IlmHamroh
JavaScript Full-stack/21-qism. React: media, integratsiyalar, PWA15/32-dars22 daqiqa
Mundarija (33)

Excel va CSV React'da: import, eksport, PapaParse va SheetJS

Qisqacha: CSV — eng oddiy jadval formati, lekin uni to'g'ri o'qish uchun RFC 4180 qoidalari kerak: qo'shtirnoq ichidagi vergul va yangi qator, "" — bitta qo'shtirnoq. Excel uchun eksportda uchta narsa: BOM (aks holda o'zbek va kirill harflari buziladi), CRLF va formula himoyasi ('=). Importda — ajratgichni aniqlash (rus/o'zbek Excel ; bilan saqlaydi), har qatorni tekshirish va xatoni qator raqami bilan aytish. PapaParse bularning hammasini qiladi (+6.6 KB gzip); haqiqiy .xlsx uchun SheetJS (+90.3 KB) yoki ExcelJS (+248.6 KB, stillar bilan). vazifalar kanoni o'z 150 qatorlik csv.ts ini yozdi (+0.7 KB).

Bu darsda

  • CSV'ni RFC 4180 bo'yicha o'qiydigan holat mashinasini tushunasiz va ajratgichni aniqlaysiz.
  • Excel'da to'g'ri ochiladigan CSV eksport qilasiz va Excel'dan kelgan faylni import qilasiz.
  • Importni har qator bo'yicha tekshirib, foydalanuvchiga aniq xato aytasiz.
  • PapaParse bilan CSV o'qiysiz va yozasiz, katta faylni bo'laklab o'qish yo'lini bilasiz.
  • .xlsx uchun SheetJS va ExcelJS'ni hajm, imkoniyat va xavfsizlik bo'yicha tanlaysiz.

Oldin bilishingiz kerak: File va Blob (CSV va BOM asoslari), Faylni o'qish va oldindan ko'rish, Fayl yuklash UI, Jadvallar I: TanStack Table asoslari, Compound komponentlar va slot naqshi (CSV injection).

1. Nega bu kerak?

Oldingi darsda PDF yaratdik — u o'qish uchun. Jadval esa ishlash uchun: Jasur akaning buxgalteri oylik sotuvni Excel'da ochib, formulalar bilan hisoblaydi. Teskarisi ham bor: yetkazib beruvchi narxlar ro'yxatini Excel'da yuboradi, uni saytga yuklash kerak.

File va Blob darsida menyuni CSV qilib yuklab olgansiz: ; ajratgich, CRLF, BOM. Compound komponentlar darsida vazifalar statistikasini CSV hisobot qilgansiz va formula injection'dan himoyalagansiz. Bu dars uchta yangi narsa qo'shadi:

  1. O'qish (import) — yozishdan ancha qiyin: foydalanuvchi fayli har xil bo'ladi.
  2. Haqiqiy Excel fayli (.xlsx) — CSV emas, ZIP ichidagi XML.
  3. Katta fayllar — 100 000 qatorni interfeysni muzlatmasdan o'qish.

Hayotiy o'xshatish: pochta. Xat yuborish oson — o'zingiz yozasiz. Xat qabul qilish qiyinroq: konvert yirtiq, manzil chalkash, ichida boshqa narsa bo'lishi mumkin. Qabul qiluvchi har konvertni tekshiradi.

2. CSV'ni o'qish: RFC 4180

2.1 Nega split(",") yetmaydi

Birinchi xayol: qatorlarga split("\n"), kataklarga split(","). Mana Excel saqlagan haqiqiy qator:

text
1,"Non, ""sut"" olish",yo'q

split(",") to'rtta bo'lak beradi: 1, "Non, ""sut"" olish", yo'q. Aslida esa uchta katak: 1, Non, "sut" olish, yo'q. CSV qoidalari (RFC 4180 — CSV formatining standart hujjati):

Qoida Misol Ma'nosi
maxsus belgili katak qo'shtirnoqda "Non, sut" ichidagi vergul — ajratgich emas
ichki qo'shtirnoq — ikki marta "say ""salom""" say "salom"
qo'shtirnoq ichida yangi qator "1-qator↵2-qator" bitta katak, ikki qatorli
qator oxiri — CRLF \r\n LF ham qabul qilinadi

Uchinchi qoida eng ko'p buzadi: bitta "qator" fayldagi ikki qatorga cho'zilishi mumkin. Demak, avval qatorlarga bo'lib bo'lmaydi — faylni belgi-belgi o'qish kerak.

2.2 Holat mashinasi

vazifalar kanonidagi csvOqi aynan shunday qiladi. Bitta o'zgaruvchi "hozir qo'shtirnoq ichidamizmi?" deb eslaydi — bu holat mashinasi (state machine): har belgining ma'nosi holatga bog'liq. Kanon kodi (BOM bu yerda "\uFEFF" bilan yozilgan — kanonda belgining o'zi):

ts
const BOM = "\uFEFF";

function csvOqi(matn: string): string[][] {
  const toza = matn.startsWith(BOM) ? matn.slice(1) : matn;
  const birinchiQator = toza.split(/\r?\n/, 1)[0] ?? "";
  const ajratgich =
    (birinchiQator.match(/;/g)?.length ?? 0) >
    (birinchiQator.match(/,/g)?.length ?? 0)
      ? ";"
      : ",";
  const qatorlar: string[][] = [];
  let qator: string[] = [];
  let katak = "";
  let qoshtirnoqda = false;
  for (let i = 0; i < toza.length; i++) {
    const belgi = toza[i];
    if (qoshtirnoqda) {
      if (belgi === '"' && toza[i + 1] === '"') {
        katak += '"';
        i++;
      } else if (belgi === '"') {
        qoshtirnoqda = false;
      } else {
        katak += belgi;
      }
    } else if (belgi === '"') {
      qoshtirnoqda = true;
    } else if (belgi === ajratgich) {
      qator.push(katak);
      katak = "";
    } else if (belgi === "\n" || belgi === "\r") {
      if (belgi === "\r" && toza[i + 1] === "\n") {
        i++;
      }
      qator.push(katak);
      katak = "";
      if (qator.some((k) => k !== "")) {
        qatorlar.push(qator);
      }
      qator = [];
    } else {
      katak += belgi;
    }
  }
  qator.push(katak);
  if (qator.some((k) => k !== "")) {
    qatorlar.push(qator);
  }
  return qatorlar;
}

console.log(csvOqi('1,"Non, ""sut"" olish",yo\'q\r\n'));
console.log(csvOqi('a\n\n"1-qator\n2-qator"\n'));
console.log(csvOqi(`${BOM}matn;bajarildi\r\nNon, sut;ha\r\n`));

Konsolda:

text
[ [ '1', 'Non, "sut" olish', "yo'q" ] ]
[ [ 'a' ], [ '1-qator\n2-qator' ] ]
[ [ 'matn', 'bajarildi' ], [ 'Non, sut', 'ha' ] ]

Uch natijani qoidalar bilan solishtiring:

  • Birinchi — qo'shtirnoq ichidagi vergul katakda qoldi, "" → ".
  • Ikkinchi — bo'sh qator o'tkazildi (qator.some((k) => k !== "")), qo'shtirnoq ichidagi \n katakning bir qismi bo'ldi. Node uni \n deb ko'rsatadi — aslida bu haqiqiy yangi qator belgisi.
  • Uchinchi — BOM tashlandi, ajratgich ; aniqlandi: birinchi qatorda ; vergullardan ko'p. Shuning uchun Non, sut dagi vergul ajratgich emas.

Ajratgichni aniqlash — taxmin. Birinchi qator matn dan iborat bo'lsa (hech biri yo'q), vergul tanlanadi. Bu kanon uchun yetarli: sarlavha doim kamida ikki ustunli.

Tekshirib ko'ring: Nega csvOqi avval toza.split(/\r?\n/, 1) qiladi, garchi «Nega split(",") yetmaydi» bo'limida "qatorlarga bo'lib bo'lmaydi" degan bo'lsak ham?

Javob

Faqat birinchi qatorni olish uchun — ajratgichni aniqlashga. split ning ikkinchi argumenti 1 — "faqat birinchi bo'lakni ber". Sarlavha qatori odatda oddiy so'zlar (matn, bajarildi) — unda qo'shtirnoqli yangi qator deyarli bo'lmaydi. Ma'lumotning o'zi esa keyin belgi-belgi o'qiladi.

3. Excel bilan do'st CSV

3.1 Eksport: uch qoida (takror)

Eksport qoidalarini oldingi darslardan bilasiz, bu yerda — qisqa ro'yxat:

  1. BOM (\uFEFF) — fayl boshida. Busiz Excel faylni eski kodlashda ochadi va —, «», kirill harflari buziladi.
  2. CRLF — qator oxiri \r\n.
  3. Formula himoyasi — =, +, -, @ bilan boshlangan katak oldiga '. Aks holda =HYPERLINK(…) formula bo'lib bajariladi (Compound komponentlar darsidagi hujum).

Kanonda bu qoidalar bitta csvKatak + csvYoz juftligida va statistika hisobotidan (18-qism) ham shu yerga ko'chdi. Endi vazifalar va statistika — bitta kod.

3.2 Import: Excel ru/uz sozlamasi

Eng qiziq tuzoq — teskari yo'l. Jasur aka eksport faylini Excel'da ochdi, bitta vazifa qo'shdi va «Saqlash» bosdi. Windows'da til sozlamasi rus yoki o'zbek bo'lsa, Excel:

  • ajratgichni ; qiladi (vergul — o'nlik kasr belgisi);
  • "ha/yo'q" ustunida boshqa so'zlar paydo bo'ladi: xodim o'zi да/нет yozib qo'yadi, mantiqiy formulali katakni esa Excel o'z tilida saqlaydi (inglizcha Excel'da TRUE/FALSE, ruschada ИСТИНА/ЛОЖЬ);
  • BOM'ni qoldirishi ham, olib tashlashi ham mumkin.

Kanonning csvdanVazifalar funksiyasi shularning hammasini qabul qiladi. "Ha" deb tushuniladigan qiymatlar ro'yxati (kanon kodi):

ts
const HA = new Set(["ha", "true", "1", "yes", "да", "x", "✓"]);
const YOQ = new Set(["yo'q", "yoq", "false", "0", "no", "нет", ""]);

Ro'yxatda yo'q qiymat (balki, shuningdek ИСТИНА) — xato, va xato qator raqami bilan: «2-qatorda «bajarildi» noto'g'ri: «balki».». Foydalanuvchi faylni Excel'da ochib, aynan o'sha qatorni tuzatadi. "Fayl noto'g'ri" degan xabar esa uni yuzlab qatorni ko'zdan kechirishga majbur qiladi.

4. PapaParse

4.1 O'qish: Papa.parse

O'z o'quvchingiz kichik ilovaga yetadi. Ko'p ustunli, turli manbali fayllar uchun — PapaParse: RFC 4180, ajratgichni aniqlash, sarlavha, turlarni o'girish, xatolar ro'yxati va katta fayllar uchun oqim. Natija oynasida PapaParse 5.7.0 ishlaydi:

jsx
import Papa from "papaparse";

const csv =
  "\uFEFFmatn;bajarildi;soni\r\n" +
  "Non, sut;да;2\r\n" +
  '"Choy; qand";нет;\r\n' +
  "\r\n";

export default function App() {
  const r = Papa.parse(csv, {
    header: true, // birinchi qator — ustun nomlari
    skipEmptyLines: true,
    dynamicTyping: true, // "2" → 2, bo'sh → null
  });
  console.log(r.data);
  console.log(r.meta.delimiter, r.meta.fields, r.errors);
  return <p>{r.data.length} ta qator</p>;
}

Konsolda:

text
(2) [{matn: 'Non, sut', bajarildi: 'да', soni: 2}, {matn: 'Choy; qand', bajarildi: 'нет', soni: null}]
; (3) ['matn', 'bajarildi', 'soni'] (0) []

Ekranda:

text
2 ta qator
  • header: true — har qator obyekt: {matn, bajarildi, soni}. Ustun tartibi o'zgarsa ham kod buzilmaydi.
  • BOM — o'zi tashlandi: birinchi maydon nomi toza matn.
  • meta.delimiter — ; ni o'zi aniqladi.
  • dynamicTyping — "2" son bo'ldi, bo'sh katak — null. Ehtiyot bo'ling: telefon raqami +998900000000 ham songa aylanadi va + yo'qoladi. Matn bo'lishi kerak ustunlar uchun dynamicTyping: { soni: true } — faqat tanlangan ustun.

4.2 Xatolar va qator raqami

Buzuq faylda PapaParse to'xtamaydi — o'qiydi va xatolarni ro'yxatga yig'adi:

jsx
import Papa from "papaparse";

export default function App() {
  const bad = Papa.parse('a,b\n1,2,3\n"x', { header: true });
  for (const e of bad.errors) {
    console.log(e.row, e.code, e.message);
  }
  return null;
}

Konsolda:

text
2 MissingQuotes Quoted field unterminated
0 TooManyFields Too many fields: expected 2 fields but parsed 3
1 TooFewFields Too few fields: expected 2 fields but parsed 1

Tarjimasi: "qo'shtirnoqli maydon yopilmagan", "maydon ko'p: 2 kutilgan, 3 o'qildi", "maydon kam: 2 kutilgan, 1 o'qildi". Diqqat: e.row — ma'lumot qatorining 0 dan boshlangan indeksi, fayldagi qator emas. Foydalanuvchiga ko'rsatishda row + 2 (sarlavha qatori + 1 dan sanash). MissingQuotes ro'yxatda birinchi chiqdi — Papa xatolarni tartiblamaydi.

4.3 React'da import: fayldan jadvalgacha

Endi hammasini bitta komponentga yig'amiz: fayl → matn (file.text()) → PapaParse → har qatorni tekshirish → ro'yxat va xatolar. Natija oynasida fayl tanlash dialogini avtomatik bosib bo'lmaydi, shuning uchun «Namuna fayl» tugmasi o'sha funksiyaga new File(…) beradi:

jsx
import { useState } from "react";
import Papa from "papaparse";

const SAMPLE =
  "\uFEFFmatn;bajarildi\r\n" +
  "Non, sut;ha\r\n" +
  '"Choy; qand";нет\r\n' +
  ";ha\r\n" +
  "Go'sht olish;balki\r\n";

const YES = new Set(["ha", "да", "true", "1"]);
const NO = new Set(["yo'q", "нет", "false", "0", ""]);

function parseTasks(text) {
  const { data, errors } = Papa.parse(text, {
    header: true,
    skipEmptyLines: true,
  });
  const problems = errors.map((e) => `${e.row + 2}-qator: ${e.code}`);
  const tasks = [];
  data.forEach((row, i) => {
    const line = i + 2;
    const title = (row.matn ?? "").trim();
    const done = (row.bajarildi ?? "").trim().toLowerCase();
    if (title === "") {
      problems.push(`${line}-qator: matn bo'sh`);
    } else if (!YES.has(done) && !NO.has(done)) {
      problems.push(`${line}-qator: «${done}» — ha/yo'q emas`);
    } else {
      tasks.push({ title, done: YES.has(done) });
    }
  });
  return { tasks, problems };
}

export default function App() {
  const [result, setResult] = useState(null);

  async function handleFile(file) {
    if (!/\.csv$/i.test(file.name)) {
      setResult({ tasks: [], problems: ["Faqat .csv fayl"] });
      return;
    }
    setResult(parseTasks(await file.text()));
  }

  return (
    <>
      <label htmlFor="csv">CSV fayl</label>{" "}
      <input
        id="csv"
        type="file"
        accept=".csv,text/csv"
        onChange={(e) => {
          const file = e.target.files[0];
          if (file) handleFile(file);
        }}
      />{" "}
      <button
        onClick={() => handleFile(new File([SAMPLE], "royxat.csv"))}
      >
        Namuna fayl
      </button>
      {result && (
        <>
          <p role="status">{result.tasks.length} ta vazifa o'qildi</p>
          <ul>
            {result.tasks.map((t) => (
              <li key={t.title}>
                {t.done ? "✓" : "○"} {t.title}
              </li>
            ))}
          </ul>
          <ul aria-label="Xatolar">
            {result.problems.map((p) => (
              <li key={p}>{p}</li>
            ))}
          </ul>
        </>
      )}
    </>
  );
}

Ekranda:

text
CSV fayl Namuna fayl
2 ta vazifa o'qildi
✓ Non, sut
○ Choy; qand
4-qator: matn bo'sh
5-qator: «balki» — ha/yo'q emas

To'rt qatordan ikkitasi qabul qilindi, ikkitasi — aniq sabab va qator raqami bilan. Excel'da 4-qatorni ochsangiz, birinchi katak haqiqatan bo'sh. role="status" — Malikaning ekran o'quvchisi natijani o'zi o'qiydi.

Kanon boshqacha qaror qildi: birinchi xatoda butun import to'xtaydi («hammasi yoki hech narsa»). Sabab: import ro'yxatni almashtiradi, yarim fayl bilan almashtirish ma'lumot yo'qotishi bo'lardi. Bizning namunada esa xatolar ko'rsatilgach, foydalanuvchi "baribir qo'shish" ni tanlashi mumkin — ikkala yo'l ham to'g'ri, faqat ongli tanlang.

4.4 Yozish: Papa.unparse va formula

PapaParse yozadi ham — lekin formula himoyasi sukut bo'yicha o'chiq:

jsx
import Papa from "papaparse";

export default function App() {
  const rows = [{ matn: "=1+1", izoh: 'say "salom"' }];
  console.log(Papa.unparse(rows));
  console.log(Papa.unparse(rows, { escapeFormulae: true }));
  return null;
}

Konsolda:

text
matn,izoh
=1+1,"say ""salom"""
matn,izoh
"'=1+1","say ""salom"""

Birinchi natijada =1+1 Excel'da formula bo'lib bajariladi. escapeFormulae: true — oldiga '. Excel uchun BOM va CRLF ham o'zingizdan: "\uFEFF" + Papa.unparse(rows, { newline: "\r\n", escapeFormulae: true }).

4.5 Katta fayl: bo'laklab o'qish

100 000 qatorli fayl (≈10 MB) Papa.parse(matn) bilan bir necha soniya asosiy thread'ni band qiladi — tugmalar javob bermaydi (Asosiy oqimni bo'shatish). PapaParse'da ikki yo'l bor (kod hujjat bo'yicha, kanonda sinalmagan):

js
Papa.parse(file, {
  header: true,
  worker: true, // alohida thread'da
  step: (row) => {
    // har qator kelishi bilan — butun faylni xotirada ushlamaydi
    count += 1;
  },
  complete: () => console.log(`${count} ta qator`),
});

step — har qator alohida keladi, fayl xotiraga butunlay yuklanmaydi (oqim g'oyasi). worker: true — tahlil Web Worker da. Worker rejimini qattiq CSP bilan sinab ko'ring: kutubxona worker'ni qanday yaratishiga qarab worker-src yoki Trusted Types siyosati to'sishi mumkin. Ishonchli muqobil — o'z worker faylingiz va uning ichida Papa.parse (Og'ir ishni UI'dan ajratish).

Tekshirib ko'ring: Telefon raqamlari ustuni bor CSV'ni dynamicTyping: true bilan o'qidingiz. +998 90 000 00 00 (bo'shliqlar bilan) va +998900000000 (bo'shliqsiz) qanday bo'ladi?

Javob

Bo'shliqli raqam son emas — satr bo'lib qoladi. Bo'shliqsizi esa son bo'lib, 998900000000 ga aylanadi: + yo'qoldi. Bitta ustunda ikki xil tur — keyingi kod chalkashadi. Shuning uchun telefon, pasport raqami, indeks kabi "raqamga o'xshash matn" ustunlarini songa aylantirmang: dynamicTyping ni faqat haqiqiy sonli ustunlarga bering.

5. Haqiqiy Excel fayli: .xlsx

5.1 .xlsx ichida nima bor

CSV — oddiy matn: bitta varaq, stil yo'q, formula yo'q, tur yo'q. Buxgalter esa qalin sarlavha, ustun eni, bir nechta varaq va sana turini xohlaydi. Bu — .xlsx formati (Office Open XML): ZIP arxiv, ichida har varaq uchun XML fayl, stillar, umumiy satrlar jadvali. Shuning uchun kutubxonalar og'ir: ular ZIP siqish va XML'ni o'zlari yozadi va o'qiydi.

5.2 SheetJS

SheetJS (xlsx paketi) — o'qish uchun eng ko'p ishlatiladigani: .xlsx, .xls, .ods, CSV'ni bir xil API bilan o'qiydi (kod hujjat bo'yicha, natija oynasida yo'q):

js
import * as XLSX from "xlsx";

// O'qish: fayl → varaqlar → obyektlar massivi
const wb = XLSX.read(await file.arrayBuffer());
const sheet = wb.Sheets[wb.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json(sheet); // [{matn, …}, …]

// Yozish: obyektlar → varaq → kitob → fayl
const out = XLSX.utils.book_new();
const tasksSheet = XLSX.utils.json_to_sheet(tasks);
XLSX.utils.book_append_sheet(out, tasksSheet, "Vazifalar");
XLSX.writeFile(out, "vazifalar.xlsx");

Muhim halol fakt — versiya. npm'dagi xlsx paketining oxirgi versiyasi — 0.18.5, 2022-yil martdan beri yangilanmagan. SheetJS yangi versiyalarini faqat o'z saytidan (cdn.sheetjs.com) tarqatadi: npm i https://cdn.sheetjs.com/…/xlsx-….tgz. 0.18.5 da ma'lum xavfsizlik zaifliklari bor: prototype pollution (CVE-2023-30533, 0.19.3 da tuzatilgan) va ReDoS (CVE-2024-22363, 0.20.2 da tuzatilgan). Foydalanuvchi yuklagan .xlsx ni o'qisangiz, ular aynan sizga tegadi. Kanon tajribasi npm versiyasini o'lchadi: +90.3 KB gzip, CSP 0, bir qatorli kitob — 15 950 bayt.

5.3 ExcelJS: stil bilan yozish

ExcelJS — chiroyli hisobot yozish uchun: ustun eni, shrift, rang, chegara, raqam formati:

js
import ExcelJS from "exceljs";

const wb = new ExcelJS.Workbook();
const ws = wb.addWorksheet("Vazifalar");
ws.columns = [
  { header: "Matn", key: "matn", width: 40 },
  { header: "Holat", key: "holat", width: 12 },
];
ws.getRow(1).font = { bold: true };
for (const t of tasks) ws.addRow({ matn: t.matn, holat: t.holat });
const buffer = await wb.xlsx.writeBuffer();
const XLSX_TYPE =
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
const blob = new Blob([buffer], { type: XLSX_TYPE });
// keyin — W-55 dagi Blob → <a download>

Kanon tajribasida (4.4.0) — +248.6 KB gzip, CSP 0, qalin sarlavhali bir qatorli kitob — 6469 bayt. Paket 2023-yildan yangilanmagan va o'rnatishda bir nechta eskirgan bog'liqlik haqida ogohlantirish chiqardi (kutubxonalar-ornatish.txt: inflight, glob@7, rimraf@2, fstream, uuid@8).

5.4 Hajm

CSV va Excel: JS (gzip, React'dan tashqari)
  • o'z csv.ts (vazifalar, holat 15)birinchi yuklash farqi0,7 KB
  • PapaParse 5.7.0CSP 06,6 KB
  • SheetJS (npm 0.18.5)CSP 0; npm versiyasi 202290,3 KB
  • ExcelJS 4.4.0CSP 0; stil bilan248,6 KB

Manba: O'lchandi: kanon tajribasi va holat 12 → 15, Vite 8.3.3, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, tekshiruv-15.txt, 2026-10-17

To'rttasi ham vazifalar CSP'si ostida toza (CSP 0) — ular innerHTML ishlatmaydi va tashqariga bormaydi. Farq — faqat hajm va imkoniyat. Qoida: CSV kifoya qilsa — CSV. .xlsx ni o'qish kerak bo'lsa — SheetJS (cdn versiyasi). Stilli .xlsx yozish kerak bo'lsa — ExcelJS, kech yuklash bilan yoki serverda.

6. Hujumchi nigohi

Import — begona faylni ilovaga kiritish. Hujumchi nima qiladi?

  • Ulkan fayl. 2 GB CSV — brauzer xotirasi tugaydi, tab yiqiladi. Himoya: o'qishdan oldin hajmni tekshirish. Kanon faylniTekshir funksiyasida 1 MB chegara bor (100 ta vazifa ≈ 10 KB).
  • Ulkan katak. Bitta katakda 5 MB matn. Kanon Vazifa.MAKS_UZUNLIK bilan har vazifa matnini tekshiradi va qator raqami bilan rad etadi.
  • Formula qaytib keladi. Eksportda '=1+1 yozdik. Import qilganda ' ni olib tashlamasak, vazifa matni '=1+1 bo'lib qoladi; olib tashlasak — keyingi eksportda yana qo'yiladi. Kanon importda ' ni faqat '=, '+, '-, '@ boshida olib tashlaydi — aylanma (eksport → import) matnni o'zgartirmaydi.
  • HTML katakda. <img src=x onerror=alert(1)> — React matnni {t.title} sifatida xavfsiz chiqaradi. Faqat dangerouslySetInnerHTML ga bermang (XSS va DOM xavfsizligi).
  • Eski kutubxona. Yuqoridagi SheetJS 0.18.5 zaifliklari — maxsus yasalgan .xlsx bilan ishlaydi.

Va asosiysi: klientdagi tekshiruv — qulaylik. Import natijasi serverga ketadi va server uni baribir qayta tekshiradi.

7. Vazifalar qadami: CSV eksport va import (holat 15)

7.1 O'zgarishlar

bash
git switch -c feat/csv

Kanon PapaParse'ni o'lchadi (+6.6 KB, CSP 0) va qo'shmadi. csv.ts boshidagi izoh sababini aytadi:

ts
// csv.ts — CSV yozish va o'qish (21/#15). Kutubxonasiz:
// PapaParse 5.7.0 o'lchandi (+6.6 KB gzip, CSP 0 —
// natijalar/tajriba/kutubxonalar-21.txt), lekin bizga RFC 4180
// ning kichik qismi kerak: vergul, qo'shtirnoq, qator ichidagi
// yangi qator. Statistika CSV'si (18-qism) ham shu yerdan.
// Excel uchun: boshida BOM (UTF-8 — o'zbek/kirill harflari
// buzilmaydi), qatorlar CRLF. Sof funksiyalar — node:test
// (tekshiruv/csv.test.js)

Fayllar:

Fayl Nima
src/csv.ts (yangi) csvKatak, csvYoz, csvOqi, vazifalarCsv, csvdanVazifalar
App.tsx eksportCsv() — faylniYuklabBer(…, "text/csv;charset=utf-8")
Eksport.tsx «CSV (Excel uchun)» tugmasi
Import.tsx, fayl.ts .csv ham qabul qilinadi
batafsil-statistika.ts o'z csvKatak i o'chdi → csvYoz

Eng qiziq o'zgarish — Import.tsx. CSV uchun alohida import yo'li yozilmadi:

diff
+    // 21/#15: CSV — vazifalarga, keyin o'sha JSON paket yo'li (bitta
+    // tekshiruv va bitta import kodi)
+    if (fayl.name.toLowerCase().endsWith(".csv")) {
+      const natija = csvdanVazifalar(matn);
+      if (!natija.ok) {
+        xatoniKorsat(natija.xato, "fayl");
+        return;
+      }
+      matn = eksportMatni(natija.qiymat);
+    }
     importQil(matn, "fayl");

CSV vazifalarga aylanadi, vazifalar — oddiy JSON eksport matniga (eksportMatni), keyin o'sha importQil. JSON importdagi hamma tekshiruv, server bilan sinxron (PUT) va e'lon — CSV uchun bepul. Ikkinchi import yo'li — ikkinchi xatolar manbai bo'lardi.

Eksport tugmasi — kanon App.tsx dan:

tsx
  // 21/#15: CSV — Excel'da ochiladi
  // (BOM, CRLF, formula himoyasi — csv.ts)
  function eksportCsv() {
    const nomi = eksportFaylNomi().replace(/\.json$/, ".csv");
    faylniYuklabBer(
      vazifalarCsv(asosiy),
      nomi,
      "text/csv;charset=utf-8",
    );
    elonQil(`${nomi} yuklab olindi.`);
  }

elonQil — ekran o'quvchiga e'lon: Malika fayl yuklanganini eshitadi (fayl yuklanishi ekranda hech narsani o'zgartirmaydi).

7.2 Import oqimi

flowchart TB
  F["Fayl tanlandi"] --> T{"faylniTekshir: .json/.csv, 0 < hajm ≤ 1 MB"}
  T -- yo'q --> X1["Xato + fokus fayl maydonida"]
  T -- ha --> M["file.text()"]
  M --> C{".csv mi?"}
  C -- ha --> O["csvdanVazifalar: BOM, ajratgich, qatorlar"]
  O -- xato --> X2["«N-qatorda …» + fokus"]
  O -- ok --> J["eksportMatni → JSON"]
  C -- yo'q --> I["importQil"]
  J --> I
  I --> S["Ro'yxat almashdi → server PUT → e'lon"]

Diagrammadagi ikki xato yo'li ham bir xil tugaydi: xabar, fayl maydoni aria-invalid, fokus o'sha maydonda.

7.3 Natija

Testlar: tekshiruv/csv.test.js — 9 ta (yozish, o'qish, BOM, ;, aylanma, formula, import xatolari). node:test 224 → 233, Vitest 65. Brauzer tekshiruvi (e2e-21, 41 ta), CSV'ga oidlari:

text
✅ CSV fayl: nomi, BOM, CRLF, qo'shtirnoq, formula himoyasi, kirill: [true,true,["id,matn,bajarildi","1,\"Non, \"\"sut\"\" olish\",yo'q","2,'=1+1 hisobi,ha","3,Ўзбекча кирилл,yo'q",""]]
✅ e'lon: "vazifalar-2026-10-17.csv yuklab olindi."
✅ CSV import: ro'yxat almashdi, serverga PUT, e'lon: [["Choy; qand","Go'sht olish"],[["Go'sht olish",true],["Choy; qand",false]],"2 ta vazifa import qilindi."]
✅ buzuq CSV: qator raqami bilan xato, fayl maydoni belgilangan, fokus unda: ["2-qatorda «bajarildi» noto'g'ri: «balki».","true","import-fayl"]
✅ CSV: CSP buzilishi yo'q: []

Birinchi qatorni o'qing — bu brauzer haqiqatan yuklab olgan faylning qatorlari: qo'shtirnoqli matn, '=1+1, kirill harflari. Uchinchisi — Excel-ru uslubidagi fayl (;, да/нет) importi. Birinchi yuklash JS — 221.5 → 222.2 KB gzip (+0.7). axe — 0.

bash
git add -A
git commit

Commit (kanon): feat: CSV eksport/import (Excel uchun: BOM, CRLF, ; ajratgich, formula himoyasi).

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Excel'da о kabi belgilar BOM yo'q fayl boshiga \uFEFF
Excel hamma narsani bitta ustunga qo'ydi ajratgich ,, Excel ; kutadi ; yoki Excel'da "Ma'lumot → Matndan"
qator ikkiga bo'lindi split("\n") qo'shtirnoqli yangi qatorni bo'ldi holat mashinasi yoki PapaParse
import: 1-ustun nomi \uFEFFmatn BOM tashlanmagan o'qishda BOM'ni olib tashlang
xato «0-qatorda» Papa row — 0 dan, sarlavhasiz row + 2
+998… → 998… dynamicTyping faqat sonli ustunlarga
Excel =… ni bajardi formula himoyasi yo'q ' (yoki escapeFormulae)
50 MB fayl tabni yiqitdi butun fayl birdan hajm chegarasi, step, worker

9. Mashqlar

Mashqlar kurs/mashqlar/21/15-csv-excel/ da. Yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • Qo'shtirnoq ichidagi qo'shtirnoq CSV'da marta yoziladi
  • Excel CSV'ni UTF-8 deb tanishi uchun fayl boshidagi belgi:
  • Rus/o'zbek Windows'dagi Excel saqlagan CSV ajratgichi:
  • PapaParse'da birinchi qatorni ustun nomlari qiladigan opsiya:
  • npm'dagi xlsx paketining oxirgi versiyasi:
Yechim
  • ikki — "" → ".
  • BOM — \uFEFF, UTF-8 da 3 bayt.
  • ; — vergul o'nlik kasr belgisi bo'lgan tillarda.
  • header — header: true, har qator obyekt bo'ladi.
  • 0.18.5 — 2022; yangilari faqat cdn.sheetjs.com da.

2-mashq (o'rta): eksport tugmasi va formula

Papa.unparse bilan «Bahor» buyurtmalarini CSV qiling: ustunlar taom, soni, izoh. Ma'lumotda ataylab xavfli izoh bor: =HYPERLINK("https://example.com"). Talablar: formula himoyasi, ; ajratgich, CRLF, boshida BOM. Natijani konsolga JSON.stringify bilan chiqaring — BOM va \r\n ko'rinsin.

Ishora: «Yozish: Papa.unparse va formula» bo'limidagi opsiyalar va delimiter.

Yechim
jsx
import Papa from "papaparse";

const orders = [
  { taom: "Osh", soni: 2, izoh: "" },
  {
    taom: "Manti",
    soni: 1,
    izoh: '=HYPERLINK("https://example.com")',
  },
];

export default function App() {
  const csv =
    "\uFEFF" +
    Papa.unparse(orders, {
      delimiter: ";",
      newline: "\r\n",
      escapeFormulae: true,
    });
  console.log(csv.charCodeAt(0).toString(16)); // BOM
  console.log(JSON.stringify(csv.slice(1)));
  return <p>{csv.length} belgi</p>;
}

Konsolda:

text
feff
"taom;soni;izoh\r\nOsh;2;\r\nManti;1;\"'=HYPERLINK(\"\"https://example.com\"\")\""

Ekranda:

text
71 belgi

Birinchi qator — BOM belgisining kodi (feff): JSON.stringify uni ko'rsatmaydi, chunki BOM — oddiy, faqat ko'rinmas belgi. Shuning uchun uni alohida tekshirdik. \r\n — CRLF. Xavfli izoh '= bilan boshlandi va qo'shtirnoqqa olindi (ichida qo'shtirnoq bor). Excel uni matn sifatida ko'rsatadi.

3-mashq (qiyin): ajratgichni aniqlash

Kanon ajratgichni birinchi qatordagi ; va , sonidan aniqlaydi. Uni yaxshilang: detectDelimiter(text) — ,, ; va tab (\t) dan birini qaytarsin. Qoida: dastlabki 5 ta qatorning (bo'sh bo'lmaganlarining) har birida shu belgi soni bir xil va noldan katta bo'lsin; bir nechta mos kelsa — birinchi qatorda ko'prog'i. Hech biri mos kelmasa — ,. Qo'shtirnoq ichidagi belgilarni sanamang.

Sinov: "a;b\n1;2\n3;4" → ;; "nomi,narxi\n\"Osh, katta\",35000" → ,; "a\tb\n1\t2" → \t; "matn\nNon" → ,.

Ishora: qo'shtirnoq ichini olib tashlash — line.replace(/"[^"]*"/g, ""); sanash — line.split(d).length - 1; mos kelish — every.

Yechim
js
const CANDIDATES = [",", ";", "\t"];

function detectDelimiter(text) {
  const lines = text
    .split(/\r?\n/)
    .filter((l) => l.trim() !== "")
    .slice(0, 5)
    .map((l) => l.replace(/"[^"]*"/g, ""));
  const count = (line, d) => line.split(d).length - 1;
  const fits = CANDIDATES.filter((d) => {
    const n = count(lines[0] ?? "", d);
    return n > 0 && lines.every((l) => count(l, d) === n);
  });
  if (fits.length === 0) return ",";
  const first = (d) => count(lines[0], d);
  return fits.sort((a, b) => first(b) - first(a))[0];
}

const tests = [
  "a;b\n1;2\n3;4",
  'nomi,narxi\n"Osh, katta",35000',
  "a\tb\n1\t2",
  "matn\nNon",
];
for (const text of tests) {
  console.log(JSON.stringify(detectDelimiter(text)));
}

Konsolda:

text
";"
","
"\t"
","

Ikkinchi sinov — tuzoq: qo'shtirnoqni olib tashlamasak, 2-qatorda ikkita vergul bo'lardi (1-qatorda bitta) va , mos kelmasdi. .slice(0, 5) — 100 000 qatorli faylda ham tez: faqat boshi tekshiriladi. PapaParse ham shunga o'xshash: bir nechta qatorni sinab, eng barqaror ajratgichni tanlaydi.

4-mashq: Vazifalar qadami

feat/csv branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: src/csv.ts (beshta funksiya), csv.test.js (9 test), App.tsx dagi eksportCsv, Eksport.tsx tugmasi, Import.tsx va fayl.ts dagi .csv, statistika hisobotining csvYoz ga o'tishi.

Yechim

Tekshiruv ro'yxati:

  • npm test — node 233, Vitest 65; npm run tip, lint, knip — toza (knip statistikadagi eski csvKatak qolsa, ishlatilmagan deb aytadi).
  • «CSV (Excel uchun)» → fayl vazifalar-YYYY-MM-DD.csv. Excel'da oching: o'zbekcha harflar to'g'ri, = bilan boshlangan vazifa matn bo'lib turibdi.
  • Excel'da bitta qator qo'shib saqlang (ru/uz sozlamasida ; bilan) va import qiling — ro'yxat almashadi, e'lon «N ta vazifa import qilindi.».
  • Ataylab bajarildi ustuniga balki yozing — «2-qatorda «bajarildi» noto'g'ri: «balki».», fokus fayl maydonida.
  • npm run build — birinchi yuklash ~222.2 KB.

10. Real ishda

  • Admin panellar — "Excel'ga eksport" va "Excel'dan import" deyarli har CRM, buxgalteriya va do'kon panelida bor. O'zbekistonda fayllar ko'pincha ; va kirill harflari bilan keladi — BOM va ajratgich tuzog'i kundalik masala.
  • Katta hisobotlar — serverda, oqim bilan (Excel va CSV: eksport va import); brauzer faqat havolani oladi.
  • Kod ko'rigida so'raladi: "importda qator raqami bormi?", "formula himoyasi bormi?", "fayl hajmi chegaralanganmi?".
  • Intervyu savollari: "CSV'ni nega split bilan o'qib bo'lmaydi?", "Excel'da o'zbek harflari nega buziladi?", "100 000 qatorli faylni qanday o'qiysiz?", "CSV injection nima?".

Xulosa

  • CSV — RFC 4180: qo'shtirnoqli katakda vergul va yangi qator, "" — qo'shtirnoq; o'qish — holat mashinasi.
  • Excel uchun eksport: BOM, CRLF, formula himoyasi; import: BOM'ni tashlash, ; ni aniqlash, да/нет kabi qiymatlar.
  • Import xatosi — qator raqami bilan; PapaParse row 0 dan, sarlavhasiz.
  • PapaParse (+6.6 KB): header, dynamicTyping (ehtiyot bilan), escapeFormulae, step/worker.
  • .xlsx = ZIP + XML: SheetJS (+90.3, npm 0.18.5 eskirgan va zaif) va ExcelJS (+248.6, stil).
  • Kanon: o'z csv.ts (+0.7 KB), CSV → JSON paket → o'sha import yo'li.

Keyingi dars: QR kod va shtrix-kod — vazifa havolasini QR kod qilish (qrcode.react, kech yuklash), kamera bilan skanerlash va BarcodeDetector.

Manbalar

  • RFC 4180: Common Format and MIME Type for CSV Files — rfc-editor.org/rfc/rfc4180
  • PapaParse 5.7: "Config" (header, dynamicTyping, step, worker, escapeFormulae), "Errors" — papaparse.com/docs
  • SheetJS: "Installation" (cdn.sheetjs.com), "Utility Functions" — docs.sheetjs.com; CVE-2023-30533, CVE-2024-22363
  • ExcelJS — github.com/exceljs/exceljs
  • OWASP: CSV Injection — owasp.org/www-community/attacks/CSV_Injection
  • Kanon: vazifalar holat 15, natijalar/tekshiruv-15.txt, brauzer-tekshiruv/natija-21-15.txt, natijalar/tajriba/kutubxonalar-21.txt, kutubxonalar-ornatish.txt (2026-10-17)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Excel va CSV React'da: import, eksport, PapaParse va SheetJS — IlmHamroh