O3O DocBuilder và chuyển đổi
o3oscript cho bảng tính
Mọi thao tác của kịch bản type = sheet: trang tính, ghi ô theo vùng, giá trị và công thức, ngày, định dạng vùng, gộp ô, độ rộng cột, chiều cao hàng và cố định hàng cột, mỗi thao tác có bảng tham số và ví dụ.
Trong trang này
Kịch bản type = "sheet" dựng một bảng tính gồm 1 tới 50 trang tính trong mảng sheets. Định dạng lưu: xlsx, ods, pdf. Ô được ghi theo vùng: bạn chỉ ô góc trên bên trái và gửi mảng hai chiều.
Trang tính: sheet#
Đối tượng sheet
namestring, 1–31bắt buộcTên trang tính, không chứa[ ] : * ? / \, không trùng nhau.columnsarray, ≤ 1024tuỳ chọnĐộ rộng cột.rowsarray, ≤ 10.000tuỳ chọnChiều cao hàng.dataarray vùng, ≤ 1000tuỳ chọnGhi ô theo vùng.formatsarray, ≤ 5000tuỳ chọnĐịnh dạng theo vùng, áp theo thứ tự.mergesarray vùng, ≤ 5000tuỳ chọnCác vùng cần gộp ô, ví dụA1:H1.freezeobjecttuỳ chọnCố định hàng trên cùng và cột bên trái.
{
"name": "Doanh thu",
"data": [
{
"at": "A1",
"values": [
[
"Tháng",
"Doanh thu"
],
[
1,
120000000
],
[
2,
135500000
],
[
"Cộng",
{
"f": "=SUM(B2:B3)"
}
]
]
}
]
}Ghi ô: data#
Mỗi phần tử của data
atô, ví dụ A1bắt buộcÔ góc trên bên trái của vùng. Mẫu^[A-Z]{1,3}[1-9][0-9]{0,6}$.valuesmảng hai chiềubắt buộcCác hàng (1 tới 100.000), mỗi hàng 1 tới 1024 ô; đặt từatsang phải và xuống dưới.
Các vùng được ghi theo thứ tự; vùng sau đè vùng trước ở những ô trùng nhau.
Giá trị ô#
| Giá trị gửi | Kết quả trong ô |
|---|---|
null | Bỏ qua ô: không ghi gì, giữ nguyên giá trị đã có. |
1250.5 | Số. |
true | Giá trị logic. |
"Hà Nội" | Chữ. Chuỗi LUÔN là chữ, kể cả khi bắt đầu bằng dấu bằng. |
{"f": "=SUM(B2:B5)"} | Công thức (tối đa 8192 ký tự, phải bắt đầu bằng =). |
{"date": "2026-09-21"} | Giá trị ngày thật, dạng YYYY-MM-DD; định dạng hiển thị bằng number_format. |
Công thức#
- Tên hàm tiếng Anh, tham chiếu kiểu A1, dấu thập phân là dấu chấm.
- Đối số phân tách bằng dấu phẩy hoặc chấm phẩy; dịch vụ đổi dấu phẩy nằm ngoài chuỗi thành chấm phẩy trước khi đặt công thức.
- Tham chiếu sang trang tính khác viết theo cú pháp LibreOffice, dấu chấm sau tên trang:
$'Bảng lương'.C4:C8.
{
"name": "Ví dụ",
"data": [
{
"at": "A1",
"values": [
[
"Chữ",
"Số",
"Logic",
"Ngày",
"Công thức",
"Chuỗi có dấu bằng"
]
]
},
{
"at": "A2",
"values": [
[
"Hà Nội",
1250.5,
true,
{
"date": "2026-09-21"
},
{
"f": "=B2*2"
},
"=B2*2"
]
]
},
{
"at": "E3",
"values": [
[
{
"f": "=IF(B2>1000,\"Lớn\",\"Nhỏ\")"
}
]
]
}
],
"formats": [
{
"range": "D2",
"number_format": "DD/MM/YYYY"
}
]
}Tham chiếu trang tính kiểu Excel có dấu chấm than, ví dụ 'Bảng lương'!C4.
Định dạng vùng: formats#
Mỗi phần tử của formats
rangevùng, ví dụ A1:H1 hoặc B2bắt buộcVùng áp định dạng.boldbooleantuỳ chọnChữ đậm.italicbooleantuỳ chọnChữ nghiêng.underlinebooleantuỳ chọnGạch chân.color#RRGGBBtuỳ chọnMàu chữ.fill#RRGGBBtuỳ chọnMàu nền.fontstringtuỳ chọnTên font.sizenumber 4–400 (pt)tuỳ chọnCỡ chữ.alignleft | center | righttuỳ chọnCăn ngang.valigntop | middle | bottomtuỳ chọnCăn dọc.wrapbooleantuỳ chọnTự xuống dòng trong ô.number_formatstring, 1–128tuỳ chọnMã định dạng số theo cú pháp tiếng Anh (Mỹ):#,##0,0.00%,DD/MM/YYYY.borderfalse | đối tượng viềntuỳ chọnViền{"width", "color"}như ở bảng văn bản.
{
"name": "Định dạng",
"data": [
{
"at": "A1",
"values": [
[
"Mục",
"Số tiền",
"Tỷ lệ",
"Ngày"
],
[
"Lương",
18000000,
0.105,
{
"date": "2026-09-30"
}
]
]
}
],
"formats": [
{
"range": "A1:D1",
"bold": true,
"fill": "#167A41",
"color": "#FFFFFF",
"align": "center",
"valign": "middle",
"wrap": true
},
{
"range": "B2:B100",
"number_format": "#,##0"
},
{
"range": "C2:C100",
"number_format": "0.00%"
},
{
"range": "D2:D100",
"number_format": "DD/MM/YYYY",
"align": "center"
},
{
"range": "A1:D2",
"border": {
"width": 0.5,
"color": "#7F7F7F"
}
}
]
}Định dạng có điều kiện, bảng tổng hợp (pivot) và biểu đồ.
Bố cục: columns, rows, merges, freeze#
Bố cục trang tính
columns[].colchữ cột A tới ZZZbắt buộcCột cần đặt độ rộng.columns[].widthnumber 1–500 (mm)bắt buộcĐộ rộng cột.rows[].rowinteger 1–1048576bắt buộcHàng cần đặt chiều cao.rows[].heightnumber 1–400 (mm)bắt buộcChiều cao hàng.merges[]vùng, ví dụ A1:H1tuỳ chọnVùng gộp thành một ô.freeze.rowsinteger 0–100tuỳ chọnMặc định:0Số hàng trên cùng được cố định.freeze.colsinteger 0–100tuỳ chọnMặc định:0Số cột bên trái được cố định.
{
"name": "Danh sách",
"columns": [
{
"col": "A",
"width": 12
},
{
"col": "B",
"width": 60
},
{
"col": "C",
"width": 30
}
],
"rows": [
{
"row": 1,
"height": 10
}
],
"data": [
{
"at": "A1",
"values": [
[
"DANH SÁCH NHÂN SỰ"
],
[
"STT",
"Họ và tên",
"Phòng ban"
],
[
1,
"Nguyễn Văn An",
"Kỹ thuật"
]
]
}
],
"merges": [
"A1:C1"
],
"formats": [
{
"range": "A1",
"bold": true,
"size": 14,
"align": "center"
}
],
"freeze": {
"rows": 2,
"cols": 1
}
}Ví dụ đầy đủ: bảng lương#
bang-luong.json dựng bảng lương hai trang tính: công thức làm tròn, tổng, MAX, COUNTIF, SUMIF tham chiếu chéo trang tính, ô ngày, định dạng số, gộp ô và cố định hàng cột. Kịch bản có 34 đơn vị.
{
"o3oscript": 1,
"type": "sheet",
"meta": {
"title": "Bảng lương tháng 9 năm 2026",
"author": "Phòng Hành chính Nhân sự",
"lang": "vi-VN"
},
"sheets": [
{
"name": "Bảng lương",
"columns": [
{
"col": "A",
"width": 12
},
{
"col": "B",
"width": 48
},
{
"col": "C",
"width": 32
},
{
"col": "D",
"width": 30
},
{
"col": "E",
"width": 22
},
{
"col": "F",
"width": 30
},
{
"col": "G",
"width": 30
},
{
"col": "H",
"width": 32
}
],
"rows": [
{
"row": 1,
"height": 10
},
{
"row": 3,
"height": 9
}
],
"data": [
{
"at": "A1",
"values": [
[
"BẢNG LƯƠNG THÁNG 9 NĂM 2026"
]
]
},
{
"at": "A2",
"values": [
[
"Ngày lập:",
{
"date": "2026-09-30"
}
]
]
},
{
"at": "A3",
"values": [
[
"STT",
"Họ và tên",
"Phòng ban",
"Lương cơ bản",
"Ngày công",
"Phụ cấp",
"Bảo hiểm (10,5%)",
"Thực lĩnh"
]
]
},
{
"at": "A4",
"values": [
[
1,
"Nguyễn Văn An",
"Kỹ thuật",
18000000,
22,
1500000,
{
"f": "=ROUND(D4*0.105,0)"
},
{
"f": "=ROUND(D4/22*E4,0)+F4-G4"
}
],
[
2,
"Trần Thị Bình",
"Kinh doanh",
15000000,
21,
2000000,
{
"f": "=ROUND(D5*0.105,0)"
},
{
"f": "=ROUND(D5/22*E5,0)+F5-G5"
}
],
[
3,
"Lê Hoàng Cường",
"Kỹ thuật",
22000000,
22,
1500000,
{
"f": "=ROUND(D6*0.105,0)"
},
{
"f": "=ROUND(D6/22*E6,0)+F6-G6"
}
],
[
4,
"Phạm Thu Dung",
"Kế toán",
14000000,
20,
800000,
{
"f": "=ROUND(D7*0.105,0)"
},
{
"f": "=ROUND(D7/22*E7,0)+F7-G7"
}
],
[
5,
"Đỗ Minh Đức",
"Hỗ trợ khách hàng",
12000000,
22,
800000,
{
"f": "=ROUND(D8*0.105,0)"
},
{
"f": "=ROUND(D8/22*E8,0)+F8-G8"
}
]
]
},
{
"at": "A9",
"values": [
[
"Tổng cộng",
null,
null,
{
"f": "=SUM(D4:D8)"
},
{
"f": "=SUM(E4:E8)"
},
{
"f": "=SUM(F4:F8)"
},
{
"f": "=SUM(G4:G8)"
},
{
"f": "=SUM(H4:H8)"
}
]
]
},
{
"at": "A11",
"values": [
[
"Lương thực lĩnh cao nhất:",
null,
null,
{
"f": "=MAX(H4:H8)"
}
]
]
},
{
"at": "A12",
"values": [
[
"Số người đủ 22 ngày công:",
null,
null,
{
"f": "=COUNTIF(E4:E8,22)"
}
]
]
}
],
"merges": [
"A1:H1",
"A9:C9",
"A11:C11",
"A12:C12"
],
"formats": [
{
"range": "A1:H1",
"bold": true,
"size": 14,
"align": "center",
"valign": "middle",
"color": "#167A41"
},
{
"range": "B2",
"number_format": "DD/MM/YYYY",
"align": "left"
},
{
"range": "A3:H3",
"bold": true,
"fill": "#167A41",
"color": "#FFFFFF",
"align": "center",
"valign": "middle",
"wrap": true
},
{
"range": "A4:A8",
"align": "center"
},
{
"range": "D4:D9",
"number_format": "#,##0"
},
{
"range": "E4:E9",
"number_format": "0",
"align": "center"
},
{
"range": "F4:H9",
"number_format": "#,##0"
},
{
"range": "A9:H9",
"bold": true,
"fill": "#E8F3EC"
},
{
"range": "A3:H9",
"border": {
"width": 0.5,
"color": "#7F7F7F"
}
},
{
"range": "D11",
"number_format": "#,##0",
"bold": true
},
{
"range": "D12",
"number_format": "0",
"bold": true
}
],
"freeze": {
"rows": 3,
"cols": 2
}
},
{
"name": "Theo phòng ban",
"columns": [
{
"col": "A",
"width": 40
},
{
"col": "B",
"width": 22
},
{
"col": "C",
"width": 34
}
],
"data": [
{
"at": "A1",
"values": [
[
"Phòng ban",
"Số người",
"Tổng thực lĩnh"
]
]
},
{
"at": "A2",
"values": [
[
"Kỹ thuật",
{
"f": "=COUNTIF($'Bảng lương'.C4:C8,A2)"
},
{
"f": "=SUMIF($'Bảng lương'.C4:C8,A2,$'Bảng lương'.H4:H8)"
}
],
[
"Kinh doanh",
{
"f": "=COUNTIF($'Bảng lương'.C4:C8,A3)"
},
{
"f": "=SUMIF($'Bảng lương'.C4:C8,A3,$'Bảng lương'.H4:H8)"
}
],
[
"Kế toán",
{
"f": "=COUNTIF($'Bảng lương'.C4:C8,A4)"
},
{
"f": "=SUMIF($'Bảng lương'.C4:C8,A4,$'Bảng lương'.H4:H8)"
}
],
[
"Hỗ trợ khách hàng",
{
"f": "=COUNTIF($'Bảng lương'.C4:C8,A5)"
},
{
"f": "=SUMIF($'Bảng lương'.C4:C8,A5,$'Bảng lương'.H4:H8)"
}
]
]
}
],
"formats": [
{
"range": "A1:C1",
"bold": true,
"fill": "#167A41",
"color": "#FFFFFF",
"align": "center"
},
{
"range": "C2:C5",
"number_format": "#,##0"
},
{
"range": "A1:C5",
"border": {
"width": 0.5,
"color": "#7F7F7F"
}
}
]
}
],
"save": [
{
"format": "xlsx",
"filename": "bang-luong.xlsx"
}
]
}curl -sS http://localhost:8080/v1/build \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-H "Content-Type: application/json" \
--data-binary @bang-luong.json \
-o bang-luong.xlsx -w "HTTP %{http_code}\n"import { readFile, writeFile } from "node:fs/promises";
const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };
const script = JSON.parse(await readFile("bang-luong.json", "utf8"));
const res = await fetch(`${BASE_URL}/v1/build`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
body: JSON.stringify(script),
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("bang-luong.xlsx", Buffer.from(await res.arrayBuffer()));import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bang-luong.json", encoding="utf-8") as f:
script = json.load(f)
r = requests.post(f"{BASE_URL}/v1/build", headers=HEADERS, json=script, timeout=90)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bang-luong.xlsx", "wb") as fh:
fh.write(r.content)<?php
$payload = file_get_contents("bang-luong.json");
$ch = curl_init("http://localhost:8080/v1/build");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
throw new RuntimeException("HTTP $status: " . ($body === false ? curl_error($ch) : $body));
}
file_put_contents("bang-luong.xlsx", $body);using System.Net.Http.Headers;
using System.Text;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");
var json = await File.ReadAllTextAsync("bang-luong.json");
using var res = await http.PostAsync("http://localhost:8080/v1/build",
new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("bang-luong.xlsx", await res.Content.ReadAsByteArrayAsync());