Bỏ qua, tới nội dung
Tài liệu APIv1

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ộc
    Tê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ọn
    Chiều cao hàng.
  • dataarray vùng, ≤ 1000tuỳ chọn
    Ghi ô theo vùng.
  • formatsarray, ≤ 5000tuỳ chọn
    Định dạng theo vùng, áp theo thứ tự.
  • mergesarray vùng, ≤ 5000tuỳ chọn
    Các vùng cần gộp ô, ví dụ A1:H1.
  • freezeobjecttuỳ chọn
    Cố định hàng trên cùng và cột bên trái.
JSONTrang tính nhỏ nhất có công thức
{
  "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ộc
    Các hàng (1 tới 100.000), mỗi hàng 1 tới 1024 ô; đặt từ at sang 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ửiKết quả trong ô
nullBỏ qua ô: không ghi gì, giữ nguyên giá trị đã có.
1250.5Số.
trueGiá 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.
JSONĐủ các loại giá trị ô
{
  "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"
    }
  ]
}
Sắp có

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ộc
    Vùng áp định dạng.
  • boldbooleantuỳ chọn
    Chữ đậm.
  • italicbooleantuỳ chọn
    Chữ nghiêng.
  • underlinebooleantuỳ chọn
    Gạch chân.
  • color#RRGGBBtuỳ chọn
    Màu chữ.
  • fill#RRGGBBtuỳ chọn
    Màu nền.
  • fontstringtuỳ chọn
    Tên font.
  • sizenumber 4–400 (pt)tuỳ chọn
    Cỡ chữ.
  • alignleft | center | righttuỳ chọn
    Căn ngang.
  • valigntop | middle | bottomtuỳ chọn
    Căn dọc.
  • wrapbooleantuỳ chọn
    Tự xuống dòng trong ô.
  • number_formatstring, 1–128tuỳ chọn
    Mã đị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ọn
    Viền {"width", "color"} như ở bảng văn bản.
JSONHàng tiêu đề, định dạng số, phần trăm, ngày và viề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"
      }
    }
  ]
}
Sắp có

Đị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ộc
    Cộ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ộc
    Hàng cần đặt chiều cao.
  • rows[].heightnumber 1–400 (mm)bắt buộc
    Chiều cao hàng.
  • merges[]vùng, ví dụ A1:H1tuỳ chọn
    Vùng gộp thành một ô.
  • freeze.rowsinteger 0–100tuỳ chọnMặc định: 0
    Số hàng trên cùng được cố định.
  • freeze.colsinteger 0–100tuỳ chọnMặc định: 0
    Số cột bên trái được cố định.
JSONTiêu đề gộp ô, độ rộng cột, cố định hai hàng đầu
{
  "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ị.

JSONbang-luong.json
{
  "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"
    }
  ]
}
Dựng bảng lương ra 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());