Creating tables in Word documents is one of the most common requirements in daily office development — whether for data reports, product catalogs, or statistical analysis, tables present information clearly in a structured format. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage font and file resources — no backend server required.

This article covers three core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Create a Formatted Data Table

In real-world development, the most common scenario is writing data returned from a backend into a Word document as a table. The core workflow involves three steps: first, load font files into the WASM virtual file system via FetchFileToVFS; then instantiate a Document to create the document, add a table via AddTable, populate it with data, and configure header rows, alignment, and alternating row colors; finally, save the document, read the generated file from VFS, wrap it as a Blob, and trigger a browser download.

function addTable(section) {
  let docModule = window.wasmModule.spiredoc;
  let header = ["Name", "Capital", "Continent", "Area", "Population"];
  let data =
    [
      ["Argentina", "Buenos Aires", "South America", "2777815", "32300003"],
      ["Bolivia", "La Paz", "South America", "1098575", "7300000"],
      ["Brazil", "Brasilia", "South America", "8511196", "150400000"],
      ["Canada", "Ottawa", "North America", "9976147", "26500000"],
      ["Chile", "Santiago", "South America", "756943", "13200000"],
      ["Colombia", "Bagota", "South America", "1138907", "33000000"],
      ["Cuba", "Havana", "North America", "114524", "10600000"],
      ["Ecuador", "Quito", "South America", "455502", "10600000"],
      ["El Salvador", "San Salvador", "North America", "20865", "5300000"],
      ["Guyana", "Georgetown", "South America", "214969", "800000"],
      ["Jamaica", "Kingston", "North America", "11424", "2500000"],
      ["Mexico", "Mexico City", "North America", "1967180", "88600000"],
      ["Nicaragua", "Managua", "North America", "139000", "3900000"],
      ["Paraguay", "Asuncion", "South America", "406576", "4660000"],
      ["Peru", "Lima", "South America", "1285215", "21600000"],
      ["United States of America", "Washington", "North America", "9363130", "249200000"],
      ["Uruguay", "Montevideo", "South America", "176140", "3002000"],
      ["Venezuela", "Caracas", "South America", "912047", "19700000"]
    ];
  let table = section.AddTable({ showBorder: true });
  table.ResetCells(data.length + 1, header.length);

  // Set up the header row
  let row = table.Rows.get_Item(0);
  row.IsHeader = true;
  row.Height = 20;
  row.HeightType = docModule.TableRowHeightType.Exactly;
  for (let i = 0; i < row.Cells.Count; i++) {
    row.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = docModule.Color.get_Gray();
  }

  for (let i = 0; i < header.length; i++) {
    row.Cells.get_Item(i).CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
    let p = row.Cells.get_Item(i).AddParagraph();
    p.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
    let txtRange = p.AppendText(header[i]);
    txtRange.CharacterFormat.Bold = true;
  }

  // Populate data rows with alternating row colors
  for (let r = 0; r < data.length; r++) {
    let dataRow = table.Rows.get_Item(r + 1);
    dataRow.Height = 20;
    dataRow.HeightType = docModule.TableRowHeightType.Exactly;
    for (let i = 0; i < dataRow.Cells.Count; i++) {
      dataRow.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = docModule.Color.Empty();
    }
    for (let c = 0; c < data[r].length; c++) {
      dataRow.Cells.get_Item(c).CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
      dataRow.Cells.get_Item(c).AddParagraph().AppendText(data[r][c]);
    }
  }

  // Apply light blue background to even rows
  for (let j = 1; j < table.Rows.Count; j++) {
    if (j % 2 == 0) {
      let row2 = table.Rows.get_Item(j);
      for (let f = 0; f < row2.Cells.Count; f++) {
        row2.Cells.get_Item(f).CellFormat.Shading.BackgroundPatternColor = docModule.Color.get_LightBlue();
      }
    }
  }
}

function App() {
  const CreateTable = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load font files into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
 
    // Create a blank document
    let doc = new docModule.Document();
    let section = doc.AddSection();

    // Add the table
    addTable(section);

    // Define the output file name
    const outputFileName = "CreateTable_output.docx";

    // Save the document
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

    // Read the generated file from VFS and trigger download
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Click the button below to create a table in a Word document.</h1>
      <button onClick={CreateTable}>
        Generate
      </button>
    </div>
  );
}

export default App;

Word output generated from a formatted data table

Word output generated from a formatted data table


Create a Table from HTML

In web development, HTML tables are a universal format for displaying data. Spire.Doc provides the AppendHTML method, which can parse an HTML string directly into a Word document table, greatly simplifying the conversion from web content to Word documents. This is particularly useful for scenarios where you need to export table data from a web page to a Word document.

function App() {
  const CreateTableFromHTML = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load font files into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
 
    // HTML string
    let HTML = "<table border='2px'>" +
      "<tr>" +
      "<td>Row 1, Cell 1</td>" +
      "<td>Row 1, Cell 2</td>" +
      "</tr>" +
      "<tr>" +
      "<td>Row 2, Cell 2</td>" +
      "<td>Row 2, Cell 2</td>" +
      "</tr>" +
      "</table>";

    // Create a Word document
    let doc = new docModule.Document();

    // Add a section
    let section = doc.AddSection();

    // Add a paragraph and append the HTML string
    section.AddParagraph().AppendHTML(HTML);

    // Define the output file name
    const outputFileName = "CreateTableFromHTML_output.docx";

    // Save the document
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

    // Read the generated file from VFS and trigger download
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Click the button below to create a table from HTML in a Word document.</h1>
      <button onClick={CreateTableFromHTML}>
        Generate
      </button>
    </div>
  );
}

export default App;

Word table created from HTML

Word table created from HTML


Create a Nested Table

A nested table is a table inserted within a cell of another table. This is commonly used for complex document layouts — for example, in a product catalog, the main table displays product names and descriptions, while a sub-table containing specification parameters (number, item, price) is embedded inside the description cell. Spire.Doc makes nested table creation easy with the Cell.AddTable method.

function App() {
  const CreateNestedTable = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

   // Load font and document files into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a new document
    let doc = new docModule.Document();
    let section = doc.AddSection();

    // Add main table (2 rows, 2 columns)
    let table = section.AddTable({ showBorder: true });
    table.ResetCells(1, 2);

    // Set column widths
    table.Rows.get_Item(0).Cells.get_Item(0).SetCellWidth(70, docModule.CellWidthType.Point);
    table.Rows.get_Item(0).Cells.get_Item(1).SetCellWidth(150, docModule.CellWidthType.Point);
    table.Rows.get_Item(0).Height=200;
    table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);

    // Insert content into cells
    table.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("Spire.Doc for JavaScript");
    let text = "Spire.Doc for JavaScript is a professional Word " +
      "JavaScript library designed for developers to quickly and " +
      "high-quality create, read, write, convert, and print Word " +
      "document files on any JavaScript platform.";
    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText(text);

    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph();
    
    // Add a nested table in the cell (first row, second column)
    let nestedTable = table.Rows.get_Item(0).Cells.get_Item(1).AddTable({ showBorder: true });
    nestedTable.ResetCells(5, 2);
    nestedTable.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);

    // Fill nested table content
    nestedTable.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("Feature Module");
    nestedTable.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText("Typical Use Cases");

    nestedTable.Rows.get_Item(1).Cells.get_Item(0).AddParagraph().AppendText("Document Generation");
    nestedTable.Rows.get_Item(2).Cells.get_Item(0).AddParagraph().AppendText("Format Conversion");
    nestedTable.Rows.get_Item(3).Cells.get_Item(0).AddParagraph().AppendText("Content Editing");
    nestedTable.Rows.get_Item(4).Cells.get_Item(0).AddParagraph().AppendText("Print Service");

    nestedTable.Rows.get_Item(1).Cells.get_Item(1).AddParagraph().AppendText("Dynamically generate contracts, invoices, data reports, and mail merge");
    nestedTable.Rows.get_Item(2).Cells.get_Item(1).AddParagraph().AppendText("Convert between Word and PDF, HTML, RTF, XML, images, and more");
    nestedTable.Rows.get_Item(3).Cells.get_Item(1).AddParagraph().AppendText("Extract text/images, add watermarks, track revisions, and fill forms");
    nestedTable.Rows.get_Item(4).Cells.get_Item(1).AddParagraph().AppendText("Background silent printing, custom paper size, and page setup");

    // Define the output file name
    const outputFileName = "CreateNestedTable_output-en.docx";

    // Save the document
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // Release resources
    doc.Dispose();

    // Read the generated file from VFS and trigger download
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Click the button below to create a nested table in a Word document.</h1>
      <button onClick={CreateNestedTable}>
        Generate
      </button>
    </div>
  );
}

export default App;

Nested table output in Word

Nested table output in Word


FAQ

The downloaded file cannot be opened or appears corrupted

Cause: The MIME type is incorrect when creating the Blob, so the browser cannot properly identify the file format.

Solution: Use the correct Word document MIME type:

const modifiedFile = new Blob([fileArray], {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});

Table borders are missing or inconsistent

Cause: When adding a table with AddTable({ showBorder: true }), borders are enabled by default, but tables created directly via the Table constructor need borders set manually.

Solution: When creating a table via the constructor, explicitly set the border type:

let table = new wasmModule.Table(doc, false);
table.Format.Borders.BorderType = wasmModule.BorderStyle.Single;

Get a Free License

If you want to remove the evaluation message from the result document, or eliminate functional limitations, contact our sales team to request a 30-day temporary license.

In document management systems, splitting Word documents is a common requirement — separating merged multi-chapter documents into independent files by section breaks, or dividing long documents into shorter ones by page breaks. Spire.Doc for JavaScript performs document splitting directly in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required.

This article covers two core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Split by Section Break

Section breaks in a Word document separate different chapters or page layouts. Each section can have its own headers, footers, page numbering, and page setup. Splitting by section breaks is the most common and stable approach, ideal for restoring merged multi-chapter documents back into independent files.

The core workflow consists of three stages: first, load the font files and the target Word file into the WASM virtual file system; then instantiate a Document, load the file, iterate through all sections, and clone each section into a new Document object via Section.Clone(); finally, package the split files into a ZIP archive and trigger a browser download.

function App() {
  const splitBySectionBreak = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName = 'Template_Docx_4.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Create output directory
    let outputDir = 'output/';
    window.dotnetRuntime.Module.FS.mkdirTree(outputDir);

    // Load the document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Iterate through each section and clone into independent documents
    for (let i = 0; i < doc.Sections.Count; i++) {
      const newWord = new docModule.Document();
      newWord.Sections.Add(doc.Sections.get_Item(i).Clone());
      newWord.SaveToFile({
        fileName: outputDir + `Section-${i}.docx`,
        fileFormat: docModule.FileFormat.Docx2013
      });
      newWord.Dispose();
    }
    doc.Dispose();

    // Read output files from VFS and package into ZIP for download
    const JSZip = require('jszip');
    const zip = new JSZip();
    let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
    items = items.filter(item => item !== '.' && item !== '..');
    for (const item of items) {
      const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
      zip.file(item, fileData);
    }
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'SplitBySectionBreak.zip';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Word Document By Section Break</h1>
      <button onClick={splitBySectionBreak}>Generate</button>
    </div>
  );
}

export default App;

Independent Word documents generated after splitting by section break

Independent Word documents generated after splitting by section break


Split by Page Break

Page breaks are manual or automatic pagination markers inserted within a document. Splitting by page breaks is suitable for dividing long documents by page, saving each page's content as an independent document — commonly used for report pagination, contract clause splitting, and similar scenarios.

Unlike splitting by section breaks, page breaks reside at the child-object level within paragraphs. This requires traversing the document's sections, paragraphs, and paragraph child objects layer by layer to detect Break elements with the PageBreak type. When splitting, you also need to clone the original document's styles and themes using methods such as CloneDefaultStyleTo, CloneThemesTo, and CloneCompatibilityTo to ensure the split documents retain full formatting.

function App() {
  const splitByPageBreak = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName = 'SplitWordFileByPageBreak.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Create output directory
    let outputDir = 'output/';
    window.dotnetRuntime.Module.FS.mkdirTree(outputDir);

    // Load the original document
    const original = new docModule.Document();
    original.LoadFromFile(inputFileName);

    // Create a new document and clone styles and themes
    let newWord = new docModule.Document();
    let section = newWord.AddSection();
    original.CloneDefaultStyleTo(newWord);
    original.CloneThemesTo(newWord);
    original.CloneCompatibilityTo(newWord);

    let index = 0;

    // Iterate through all sections
    for (let i = 0; i < original.Sections.Count; i++) {
      let sec = original.Sections.get_Item(i);

      // Iterate through all child objects in the section (paragraphs, tables, etc.)
      for (let j = 0; j < sec.Body.ChildObjects.Count; j++) {
        let obj = sec.Body.ChildObjects.get_Item(j);

        if (obj instanceof docModule.Paragraph) {
          let para = obj;
          sec.CloneSectionPropertiesTo(section);
          section.Body.ChildObjects.Add(para.Clone());

          // Detect page breaks within paragraph child objects
          for (let k = 0; k < para.ChildObjects.Count; k++) {
            let parobj = para.ChildObjects.get_Item(k);
            if (parobj instanceof docModule.Break &&
                parobj.BreakType === docModule.BreakType.PageBreak) {

              let breakIndex = para.ChildObjects.IndexOf(parobj);

              // Remove the page break from the paragraph
              section.Body.LastParagraph.ChildObjects.RemoveAt(breakIndex);

              // Save the current document
              newWord.SaveToFile({
                fileName: outputDir + `Page-${index}.docx`,
                fileFormat: docModule.FileFormat.Docx2013
              });
              index++;

              // Create a new document to continue
              newWord = new docModule.Document();
              section = newWord.AddSection();
              original.CloneDefaultStyleTo(newWord);
              original.CloneThemesTo(newWord);
              original.CloneCompatibilityTo(newWord);
              sec.CloneSectionPropertiesTo(section);

              // Handle remaining content after the page break
              section.Body.ChildObjects.Add(para.Clone());
              if (section.Paragraphs.get_Item(0).ChildObjects.Count === 0) {
                section.Body.ChildObjects.RemoveAt(0);
              } else {
                while (breakIndex >= 0) {
                  section.Paragraphs.get_Item(0).ChildObjects.RemoveAt(breakIndex);
                  breakIndex--;
                }
              }
            }
          }
        }

        if (obj instanceof docModule.Table) {
          section.Body.ChildObjects.Add(obj.Clone());
        }
      }
    }

    // Save the last document
    newWord.SaveToFile({
      fileName: outputDir + `Page-${index}.docx`,
      fileFormat: docModule.FileFormat.Docx2013
    });

    original.Dispose();
    newWord.Dispose();

    // Package into ZIP for download
    const JSZip = require('jszip');
    const zip = new JSZip();
    let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
    items = items.filter(item => item !== '.' && item !== '..');
    for (const item of items) {
      const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
      zip.file(item, fileData);
    }
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'SplitByPageBreak.zip';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Word Document By Page Break</h1>
      <button onClick={splitByPageBreak}>Generate</button>
    </div>
  );
}

export default App;

Word documents generated after splitting by page break

Word documents generated after splitting by page break


FAQ

Split document formatting differs from the original

Cause: When splitting by page break, only the paragraph content is cloned without also cloning the original document's styles, themes, and section properties. This causes the split documents to lose formatting information such as fonts, colors, and page setup.

Solution: After creating a new document, clone the original document's styles and themes using the following methods:

original.CloneDefaultStyleTo(newWord);
original.CloneThemesTo(newWord);
original.CloneCompatibilityTo(newWord);
sec.CloneSectionPropertiesTo(section);

Page break cannot be detected

Cause: Page breaks reside within paragraph child objects and are identified via the BreakType enumeration. If the traversal hierarchy is incorrect, or the instanceof check is not used to determine the object type, the page break may not be properly recognized.

Solution: Ensure detection follows the order: Paragraph.ChildObjects → instanceof Break → BreakType == BreakType.PageBreak:

for (let k = 0; k < para.ChildObjects.Count; k++) {
  let parobj = para.ChildObjects.get_Item(k);
  if (parobj instanceof docModule.Break &&
      parobj.BreakType === docModule.BreakType.PageBreak) {
    // Page break found
  }
}

Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

Comments are an indispensable feature in Word document collaboration, widely used for review, proofreading, and team discussion scenarios. Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage document files — no backend server or Microsoft Word installation required.

This article covers three core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Add a Comment to Specific Text

In real-world document review scenarios, we often need to add comments to a specific piece of text. The approach is: first, use the FindString method to locate the target text; then insert CommentMarkStart and CommentMarkEnd markers before and after the text; finally, add the Comment object to the paragraph, associating the comment with the specified text.

function App() {
  const AddCommentForSpecificText = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the target Word document into VFS
    const inputFileName = "CommentTemplate.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the file
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Call the custom function to add a comment to the specified text
    InsertComments(doc, "Development", docModule);

    // Save the document
    const outputFileName = "AddCommentForSpecificText.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });

    // Release resources
    doc.Dispose();

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  // Custom function: add a comment for the specified keyword
  function InsertComments(doc, keystring, wasmModule) {
      // Find the target string in the document
      let find = doc.FindString(keystring, false, true);

      // Create comment start and end markers
      let commentMarkStart = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentStart);
      let commentMarkEnd = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentEnd);

      // Create a comment object, set content and author
      let comment = new wasmModule.Comment(doc);
      comment.Body.AddParagraph().Text = "Test comments";
      comment.Format.Author = "Administrator";

      // Get the found text range and its containing paragraph
      let range = find.GetAsOneRange();
      let para = range.OwnerParagraph;

      // Get the index of the text range within the paragraph
      let index = para.ChildObjects.IndexOf(range);

      // Add the comment to the paragraph
      para.ChildObjects.Add(comment);

      // Insert comment start and end markers before and after the target text
      para.ChildObjects.Insert(index, commentMarkStart);
      para.ChildObjects.Insert(index + 2, commentMarkEnd);
  }

  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>Add Comment for Specific Text</h1>
        <button onClick={AddCommentForSpecificText}>
          Generate
        </button>
      </div>
    );
};

export default App;

With the code above, you can search for a specific keyword in the document, insert comment markers at its position, and accurately associate the comment with the target text.

Add a comment to specific text


Extract Comments from a Document

After a document has been reviewed by multiple people, it often contains numerous comments. Extracting these comments in bulk makes it easy to consolidate review feedback or perform further processing. Spire.Doc provides the Comments collection, which we can iterate through to read the text content of each comment and then export it as a text file.

function App() {
  const ExtractComment = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
    
    // Load the target Word document into VFS
    const inputFileName = "CommentSample.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the file
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Iterate through all comments and extract text content
    let stringB = [];
    for (let i = 0; i < doc.Comments.Count; i++) {
        let comment = doc.Comments.get_Item(i);
        for (let j = 0; j < comment.Body.Paragraphs.Count; j++) {
            let p = comment.Body.Paragraphs.get_Item(j);
            stringB.push(p.Text + "\n");
        }
    }

    // Save the extracted comment content as a text file
    const outputFileName = 'ExtractComment.txt';

    const blob = new Blob([stringB.toString()], { type: "text/plain;charset=utf-8" });

    // Release resources
    doc.Dispose();

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };
  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>Extract Comments from Document</h1>
        <button onClick={ExtractComment}>
          Generate
        </button>
      </div>
    );
};

export default App;

The doc.Comments collection provides access to all comments in the document, and the text content of each comment is retrieved via comment.Body.Paragraphs. The extracted content can be saved as a plain text file for easy review or import into other systems.

Extract comments from a document


Reply to and Modify Comments

In team collaboration scenarios, replying to existing comments and modifying their content are common needs. Spire.Doc supports adding replies to comments via the ReplyToComment method, as well as modifying comment content or deleting unwanted comments. The following example demonstrates how to get the first comment in a document and add a reply containing an image.

function App() {
  const ReplyToComment = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the target document and image into VFS
    const inputFileName = "Comment.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    const imageFile = "logo.png";
    await window.spire.FetchFileToVFS(imageFile, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the file
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Get the first comment in the document
    let comment1 = doc.Comments.get_Item(0);

    // Create a reply comment, set author and content
    let replyComment1 = new docModule.Comment(doc);
    replyComment1.Format.Author = "E-iceblue";
    replyComment1.Body.AddParagraph().AppendText("Spire.Doc is a professional Word library for operating Word documents.");

    // Add the reply comment to the original comment
    comment1.ReplyToComment(replyComment1);

    // Load the image and insert it into the reply comment
    let docPicture = new docModule.DocPicture(doc);
    docPicture.LoadImage(imageFile);
    replyComment1.Body.Paragraphs.get_Item(0).ChildObjects.Add(docPicture);

    // Save the document
    const outputFileName = "ReplyToComment.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });

    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });

    // Release resources
    doc.Dispose();

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };
  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>Reply to Comment</h1>
        <button onClick={ReplyToComment}>
          Generate
        </button>
      </div>
    );
};

export default App;

In addition to replying to comments, you can delete a specific comment using the Comments.RemoveAt(index) method, or modify the text content of a comment using Body.Paragraphs.get_Item(0).Replace(...):

// Modify the content of the first comment
doc.Comments.get_Item(0).Body.Paragraphs.get_Item(0).Replace({ given: "original text", replace: "modified text", caseSensitive: false, wholeWord: false });

// Delete the second comment
doc.Comments.RemoveAt(1);

Reply to a comment


FAQ

Comments not displaying correctly in Word

Cause: The CommentMarkStart and CommentMarkEnd markers are inserted at incorrect positions, or the Comment object is not properly added to the paragraph. The start marker must be placed before the commented text, the end marker after it, and the Comment object itself must also be added to the same paragraph.

Solution: Ensure the correct sequence — first get the index of the target text, then insert CommentMarkStart, add the Comment to the paragraph, and finally insert CommentMarkEnd after the text.

Empty output when extracting comments

Cause: The document may contain no comments, or the comment content is stored in a nested structure. Additionally, if the document is loaded from an incorrect path or the file is not successfully loaded into VFS, comments cannot be read.

Solution: Check doc.Comments.Count before extraction to confirm it is greater than 0. Also ensure the document file is properly loaded via FetchFileToVFS:

await window.spire.FetchFileToVFS(
  'CommentSample.docx', '', `${process.env.PUBLIC_URL}/static/data/`
);

Original comment lost after replying

Cause: The ReplyToComment method should be called on the original comment. If the reply comment is mistakenly used as the caller, or the method is called multiple times causing reference confusion, the comment structure may become corrupted.

Solution: Always call ReplyToComment on the existing original comment object in the document, passing the newly created Comment object as the parameter:

// Correct: original comment calls ReplyToComment, new comment as parameter
existingComment.ReplyToComment(replyComment);

Get a Free License

If you want to remove the evaluation message from the result documents or eliminate functional limitations, please contact sales to obtain a 30-day temporary license.

page 38