Đổi tên một trang tính từ "Summary" thành "Overview" trong một workbook được tạo ra, và mọi hyperlink nội bộ từng trỏ tới Summary!A1 sẽ không còn dẫn tới đâu cả. Không có ngoại lệ nào khi lưu, cũng không có khi mở. Liên kết vẫn hiển thị, vẫn trông như có thể nhấp được, và âm thầm trỏ tới hư không. Kiểu hỏng hóc tương tự cũng xuất hiện sau một lần chuyển đổi save-as hoặc một vòng round-trip .xls/.xlsx, khi một comment bị lệch một cột hoặc một liên kết tương đối mất đích đến. Cả hai tính năng đều mang trạng thái review mà con người thật sự hành động theo, nên khi chúng hỏng, lỗi vẫn vô hình cho tới khi người review nhấp vào và không có gì xảy ra
Đó là lý do thực tế khiến comment và hyperlink xứng đáng được chăm chút kỹ hơn vẻ ngoài mang tính thẩm mỹ của chúng. HotXLS cho phép mã Delphi và C++Builder truy cập ghi trực tiếp vào cả hai, trong cả XLS và XLSX, không cần Excel automation can thiệp. Mặt trái của quyền kiểm soát đó là trách nhiệm: thư viện ghi chính xác những đích đến bạn đưa cho nó và không xác thực bất kỳ đích nào trong số đó, nên việc giữ cho quy trình review nguyên vẹn là việc của mã bạn viết, không phải của Excel
Comment trong ô như những bản ghi review do máy sinh ra
Trong mô hình class của XLSX, một comment là một đối tượng ở cấp worksheet: nó biết hàng của mình, cột của mình, tác giả, và một nội dung văn bản. Trường author xứng đáng có mặt ở đó. Khi một workbook do mã của bạn tạo ra đi qua một chuỗi review, câu hỏi đầu tiên mà một auditor đặt ra là ai đã viết ghi chú này, và một ghi chú không có tác giả sẽ trả lời câu hỏi đó bằng một khoảng trống. Hãy đóng dấu các comment được tạo ra với một service identity để nguồn gốc không bao giờ mơ hồ
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Note: TXLSXComment;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('reconciliation.xlsx');
Sheet := Book.Sheets[0];
// Ghi chú có tác giả trên số liệu đã điều chỉnh
Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
'recon-service');
// Cập nhật ghi chú đã có thay vì chồng thêm một ghi chú thứ hai
Note := Sheet.Comments.FindAt(14, 4);
if Note <> nil then
Note.Text := Note.Text + ' [verified 2026-06-11]';
Book.SaveAs('reconciliation-reviewed.xlsx');
finally
Book.Free;
end;
end;
Phép thăm dò FindAt mang trọng lượng lớn hơn vẻ ngoài của nó. Một batch job thử lại sau một lỗi tạm thời sẽ vô tư gọi AddComment lần thứ hai trên một ô nó đã chú thích rồi, và ô đó kết thúc với hai ghi chú chồng lên nhau mà chẳng ai yêu cầu. Hãy thăm dò bằng FindAt trước, rồi cập nhật đối tượng nó trả về. Collection Comments cũng lộ ra DeleteAt và DeleteInRange. Biến thể theo vùng đó là thứ nên dùng khi bạn dọn dẹp một workbook trước khi nó rời khỏi công ty: xóa các chú thích QA nội bộ khỏi cả một vùng chỉ là một lệnh gọi thay vì một vòng lặp viết tay qua từng ô
URL bên ngoài và các bước nhảy trong nội bộ workbook là hai API khác nhau
OOXML giữ hai loại liên kết này ở hai nơi khác nhau. Một URL bên ngoài trở thành một mục relationship trong phần .rels của sheet, với ô trỏ tới relationship đó bằng id. Một bước nhảy nội bộ hoàn toàn không chạm tới lớp relationship; nó chỉ là một chuỗi vị trí đơn giản như Summary!A1 được lưu trực tiếp trên liên kết. HotXLS giữ sự phân biệt đó hiển thị rõ trong API thay vì dồn tất cả vào một phương thức duy nhất, nghĩa là bạn chọn đúng lệnh gọi bằng cách biết đích đến nằm ở đâu:
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
'Open record 2214', 'ERP source entry');
Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');
Trên đối tượng TXLSXHyperlink kết quả, Url và Location loại trừ lẫn nhau, và IsInternal cho bạn biết thuộc tính nào trong hai thuộc tính đó được điền giá trị. Đó là cờ bạn kiểm tra khi kiểm kê các liên kết trong một workbook đã mở và cần xử lý "rời khỏi file" và "ở lại trong file" theo hai quy tắc khác nhau: một host bên ngoài có thể phải đối mặt với một allowlist trong khi một đích nội bộ chỉ cần nêu tên một sheet đang tồn tại. Liên kết nội bộ không mang theo phần relationship nào phía sau, điều này cũng khiến chúng rẻ hơn khi cần viết lại hàng loạt
Sự hỏng hóc được nêu ở phần mở đầu hoàn toàn nằm ở phía nội bộ, và nó xuất phát từ một sự thật: một chuỗi vị trí không phải là một tham chiếu đã được phân tích cú pháp. HotXLS ghi lại chính xác đoạn văn bản bạn đưa cho nó, và không có gì trỏ lại đoạn văn bản đó khi một sheet bị đổi tên về sau. Hai cách phòng vệ tỏ ra hiệu quả trong thực tế. Cách thứ nhất là kỷ luật về thứ tự: đổi tên mọi sheet trước khi bạn tạo ra bất kỳ liên kết nào, rồi coi tên sheet như những định danh cố định. Cách thứ hai chắc chắn hơn và vẫn đứng vững qua các lần đổi tên thực hiện sau đó. Trỏ liên kết vào một defined name ở cấp workbook thay vì một địa chỉ Sheet!Cell thô, bởi vì Excel sẽ viết lại định nghĩa của tên đó khi sheet bên dưới thay đổi, nên liên kết tự động đi theo. Cách tiếp cận thứ hai này kết hợp tự nhiên với các kỹ thuật trong defined names và công thức liên trang tính trong HotXLS
Phía XLS: cùng khái niệm, hạ tầng cũ hơn
Lớp giao diện BIFF8 gắn comment vào các range thay vì một collection ở cấp worksheet. Bạn gọi AddComment trên một IXLSRange và nhận về một TXLSComment; thuộc tính Comment của range đọc lại một ghi chú đã có, còn ClearComments xóa sạch chúng. Điểm gai góc ở đây nằm ở vị trí. Một TXLSComment không công khai lộ ra hàng và cột của chính nó, nên vòng lặp tự nhiên, "duyệt qua mọi comment và báo cáo nó nằm ở đâu," lại chạy ngược với API này. Bạn phải bắt đầu từ các ô. Hoặc dẫn dắt việc kiểm tra từ danh sách địa chỉ bạn đã chú thích, hoặc tự giữ một nhật ký vị trí riêng khi bạn ghi, bởi vì đối tượng comment sẽ không cho bạn biết sau đó nó nằm ở đâu
var
Book: IXLSWorkbook;
Sheet: IXLSWorksheet;
Remark: TXLSComment;
begin
Book := TXLSWorkbook.Create;
Sheet := Book.Sheets.Add;
Sheet.Name := 'Review';
Sheet.Cells.Item[5, 2].Value := 4821.50;
Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
Remark.Visible := True; // mở sẵn ghi chú ngay từ lần xem đầu tiên
Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
'Sign-off form', 'Opens the controller queue');
Book.SaveAs('review.xls');
end;
Đặt Visible thành True là cách truyền thống để khiến một ghi chú không thể bị bỏ qua: ô màu vàng luôn mở sẵn trên sheet thay vì chờ người dùng hover chuột vào. TXLSComment đi xa hơn một bước so với đối tác XLSX của nó bằng cách lộ ra TextRuns, nhờ đó một ghi chú duy nhất có thể mang một cảnh báo in đậm nằm cạnh một lời giải thích thông thường, kiểu định dạng mà API comment của XLSX không lộ ra theo cách tương tự. Hyperlink ở phía này đến qua ba overload tăng dần (chỉ địa chỉ, rồi kèm display text, rồi kèm screen tip) và được đọc lại qua collection HyperLinks của worksheet, nơi mỗi liên kết lộ ra Address, SubAddress, DisplayText, và ScreenTip
Một sheet chỉ mục review tốt hơn những ghi chú rải rác
Qua khỏi khoảng chục chú thích, cách đọc kiểu hover-để-đọc âm thầm mất khả năng mở rộng. Ghi chú chất đống trên những sheet mà người review chẳng bao giờ mở, và những ghi chú quan trọng nhất lại chính là những ghi chú dễ bị bỏ sót nhất. Cấu trúc đã chứng tỏ hiệu quả nhất là một sheet chỉ mục được tạo tự động: mỗi hàng ứng với một vị trí đã chú thích, liệt kê tên sheet, địa chỉ ô, tác giả, và một đoạn trích ngắn của ghi chú. Cột cuối cùng mang một hyperlink nội bộ được dựng bằng AddHyperlinkToCell, nhảy thẳng tới ô đã chú thích. Giờ người review đọc xuôi theo một danh sách thay vì lùng sục khắp lưới ô, và số hàng của chỉ mục đó đồng thời đóng vai trò như bảng kiểm kê comment của bạn cho bước audit dưới đây
Việc dựng chỉ mục này tốn ít công vì trình tạo của bạn đã biết mọi vị trí nó từng chạm tới. Thêm một tuple (sheet, row, column, author, summary) vào một danh sách mỗi khi bạn ghi một comment, rồi xuất sheet chỉ mục sau cùng để số hàng của nó là con số cuối cùng trước khi bạn lưu. Hai cải tiến nhỏ đem lại lợi ích rõ rệt: sắp xếp chỉ mục theo mức độ nghiêm trọng hoặc theo sheet thay vì theo thứ tự chèn, và đặt một liên kết quay lại ở phần đầu chỉ mục để người review có thể bật ngược lên đầu sau mỗi mục. Vì liên kết nội bộ chỉ là những chuỗi vị trí đơn thuần không có gì ở lớp relationship phía sau, nên ngay cả một chỉ mục cả nghìn hàng cũng gần như không làm tăng kích thước file hay thời gian lưu
Chính sheet đó lại phát huy tác dụng thêm một lần nữa trên chặng đường quay về. Khi workbook đã được review quay trở lại, mã của bạn đọc các giá trị trạng thái được gõ vào ô bên cạnh các hàng chỉ mục thay vì quét lại toàn bộ sheet để tìm các comment có thể đã thay đổi. Một cột ô trạng thái có cấu trúc thì phân tích cú pháp sạch sẽ; một mớ ghi chú văn bản tự do rải rác thì không
Một bước audit trước khi giao hàng thực sự bắt được lỗi hỏng hóc
Không có API nào trong số này xác thực một đích đến. Một liên kết trỏ tới sheet bạn đã xóa, một intranet host bị viết sai chính tả, một file share đã ngừng hoạt động từ quý trước: tất cả đều lưu được mà không phàn nàn một lời. ECMA-376 quy định cách một liên kết được lưu trữ, chứ không quy định rằng nó phải trỏ tới được điều gì đó. Vì vậy, một workbook mang theo metadata review xứng đáng có một bước audit ngắn của riêng bạn, chạy ngay trước SaveAs:
- Thu thập mọi vị trí nội bộ được ghi trong quá trình tạo và xác nhận tên sheet đứng trước dấu chấm than vẫn còn tồn tại trong collection sheet của workbook
- Kiểm tra các URL bên ngoài đối chiếu với một allowlist gồm scheme và host. Đường dẫn
file://trần và đường dẫn UNC làm lộ chi tiết môi trường và hỏng ngay khi file rời khỏi mạng của bạn - Đếm số comment trên mỗi sheet và so sánh với những gì trình tạo của bạn dự định ghi. Một lần thử lại làm nhân đôi ghi chú sẽ lộ ra ở đây thay vì trong hộp thư của người review
- Loại bỏ các chú thích chỉ dùng nội bộ bằng
DeleteInRangebất cứ khi nào người nhận nằm ngoài tổ chức
Các nhóm xây dựng workbook của họ từ một lớp dữ liệu có thể gộp bước này vào đúng bước pipeline đã xác thực dữ liệu sẵn, nhờ đó việc kiểm tra metadata đi kèm miễn phí. Cơ chế ở đây chính là những gì được mô tả trong xuất kết quả truy vấn cơ sở dữ liệu ra báo cáo Excel, chỉ chuyển hướng sang liên kết và comment thay vì hàng dữ liệu
Một chi tiết về dấu nháy khiến nhiều người vấp phải khi họ tự tay dựng chuỗi vị trí. Một sheet có tên chứa dấu cách phải được đặt trong dấu nháy bên trong chuỗi vị trí, đúng như cách formula bar đặt dấu nháy cho nó: 'Quarterly Totals'!A1, chứ không phải Quarterly Totals!A1. HotXLS áp dụng cùng những quy tắc mà formula engine dùng cho tham chiếu liên trang tính, nên nếu một liên kết hoạt động được trong một công thức worksheet thì cách đặt dấu nháy của nó cũng sẽ hoạt động ở đây. Đưa cho nó một tên không có dấu nháy nhưng có dấu cách và bạn sẽ nhận được đúng kiểu liên kết chết âm thầm mà phần mở đầu đã cảnh báo
Comment và hyperlink là những phần của một workbook được tạo ra mà người review hành động theo mà không cần nhìn lại lần hai, và đó chính xác là lý do vì sao một đích đến trỏ tới hư không lại gây thiệt hại thật sự trước khi bất kỳ ai nhận ra. Hãy dựng bước xác thực đó một lần, chạy nó trên mọi workbook trước khi phát hành, và quy trình review sẽ giữ nguyên vẹn qua các lần đổi tên và chuyển đổi. Toàn bộ bề mặt API cho cả hai lớp giao diện XLS và XLSX được ghi chép trên trang sản phẩm HotXLS Delphi Component