Skip to main content

Common Fatal Mistakes

The Big Three Errors That Corrupt Documents​

These three mistakes account for 90% of document corruption issues in the AppraiSys system. Memorize them, avoid them, and save hours of debugging.


❌ Fatal Mistake #1: SDT Inside Run​

The Problem​

Content controls (SDT) cannot be children of runs.

They are block-level or inline containers themselves—they contain runs, not the other way around.

Wrong Code​

<!-- ❌ WRONG: SDT inside run -->
<w:p>
<w:r>
<w:sdt>
<w:sdtContent>
<w:t>Text</w:t>
</w:sdtContent>
</w:sdt>
</w:r>
</w:p>

Result: Document corruption, Word shows "repair" dialog, content may disappear.

Correct Code​

Option A: Block-Level SDT (Preferred for Content Controls)​

<!-- ✅ CORRECT: SDT contains paragraph with run -->
<w:sdt>
<w:sdtPr>
<w:id w:val="123456"/>
<w:tag w:val="fieldName"/>
</w:sdtPr>
<w:sdtContent>
<w:p>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:p>
</w:sdtContent>
</w:sdt>

Option B: Inline SDT (For Text Within Paragraph)​

<!-- ✅ CORRECT: Inline SDT inside paragraph -->
<w:p>
<w:sdt>
<w:sdtPr>
<w:id w:val="123456"/>
<w:tag w:val="fieldName"/>
</w:sdtPr>
<w:sdtContent>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:sdtContent>
</w:sdt>
</w:p>

Visual Explanation​

Why This Breaks​

  • Runs (w:r) are inline containers for text and simple content
  • SDTs are content controls that can contain blocks (paragraphs, tables)
  • Nesting a block container inside an inline container violates the schema

Word's reaction: "This doesn't make sense structurally. I'll either crash or try to repair it (and probably break something else)."

How to Fix in AppraiSys​

Wrong approach (seen in code reviews):

// ❌ BAD: Creating SDT inside run
var run = new Run();
var sdt = new SdtRun(); // Don't do this!
run.Append(sdt);
paragraph.Append(run);

Correct approach:

// ✅ GOOD: Creating block SDT
var sdt = new SdtBlock();
sdt.SdtProperties = new SdtProperties(
new Tag { Val = "fieldName" }
);

var sdtContent = new SdtContentBlock();
var paragraph = new Paragraph();
var run = new Run(new Text("Content"));

paragraph.Append(run);
sdtContent.Append(paragraph);
sdt.Append(sdtContent);

body.Append(sdt); // Add SDT to body, not inside run!

🔗 See Also: Content Controls (SDT) for complete SDT guide


❌ Fatal Mistake #2: Table Inside Run​

The Problem​

Tables are block-level elements and cannot be children of runs.

Tables must be direct children of body, table cells, or SDT content—never inside runs.

Wrong Code​

<!-- ❌ WRONG: Table inside run -->
<w:p>
<w:r>
<w:tbl>
<w:tr>
<w:tc>
<w:p><w:r><w:t>Cell</w:t></w:r></w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:r>
</w:p>

Result: Immediate document corruption. Word cannot repair this automatically.

Correct Code​

<!-- ✅ CORRECT: Table at body level -->
<w:body>
<w:p>
<w:r>
<w:t>Text before table</w:t>
</w:r>
</w:p>

<w:tbl> <!-- Table is sibling to paragraphs, not inside run -->
<w:tblPr>
<w:tblW w:w="5000" w:type="pct"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="2500"/>
<w:gridCol w:w="2500"/>
</w:tblGrid>
<w:tr>
<w:tc>
<w:p>
<w:r><w:t>Cell content</w:t></w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>

<w:p>
<w:r>
<w:t>Text after table</w:t>
</w:r>
</w:p>
</w:body>

Visual Explanation​

Valid Table Locations​

Tables can go in these places:

  1. Body (most common)

    <w:body>
    <w:tbl>...</w:tbl>
    </w:body>
  2. Table cell (nested tables)

    <w:tc>
    <w:p>...</w:p> <!-- Required: At least one paragraph -->
    <w:tbl>...</w:tbl> <!-- Nested table -->
    </w:tc>
  3. SDT content

    <w:sdt>
    <w:sdtContent>
    <w:tbl>...</w:tbl>
    </w:sdtContent>
    </w:sdt>
  4. Header/Footer

    <w:hdr>
    <w:tbl>...</w:tbl>
    </w:hdr>

❌ Never inside: w:r, w:hyperlink, inline SDT, or any inline element

How to Fix in AppraiSys​

Wrong approach:

// ❌ BAD: Adding table to run
var paragraph = new Paragraph();
var run = new Run();
var table = new Table(); // Don't add table to run!

run.Append(table); // ❌ This will corrupt the document
paragraph.Append(run);

Correct approach:

// ✅ GOOD: Adding table to body
var body = mainPart.Document.Body;

// Add paragraph before table
var paraBefore = new Paragraph(
new Run(new Text("Before table"))
);
body.Append(paraBefore);

// Add table at body level
var table = new Table();
var tableProperties = new TableProperties(
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct }
);
table.Append(tableProperties);

// Add rows and cells
var row = new TableRow();
var cell = new TableCell();
cell.Append(new Paragraph(new Run(new Text("Cell content"))));
row.Append(cell);
table.Append(row);

body.Append(table); // ✅ Table added to body, not run

// Add paragraph after table
var paraAfter = new Paragraph(
new Run(new Text("After table"))
);
body.Append(paraAfter);

🔗 See Also: Tables in OOXML for complete table guide


❌ Fatal Mistake #3: Text Without Run or Paragraph​

The Problem​

Text nodes (w:t) must be inside runs (w:r), and runs must be inside paragraphs (w:p).

You cannot have "naked" text directly in the body or in other containers.

Wrong Code Examples​

Wrong: Text directly in body​

<!-- ❌ WRONG: Text in body -->
<w:body>
<w:t>Text</w:t>
</w:body>

Wrong: Text directly in paragraph​

<!-- ❌ WRONG: Text in paragraph without run -->
<w:p>
<w:t>Text</w:t>
</w:p>

Wrong: Text directly in table cell​

<!-- ❌ WRONG: Text in cell without paragraph and run -->
<w:tc>
<w:t>Text</w:t>
</w:tc>

Result: Word cannot parse the content. Document may open with missing text or show corruption.

Correct Code​

<!-- ✅ CORRECT: Complete hierarchy -->
<w:body>
<w:p>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:p>
</w:body>

The Required Hierarchy​

Rule: Text always needs this wrapper: <w:p><w:r><w:t>Text</w:t></w:r></w:p>

Special Case: Table Cells​

Table cells MUST contain at least one paragraph, even if empty:

<!-- ✅ CORRECT: Cell with content -->
<w:tc>
<w:tcPr>
<w:tcW w:w="2500" w:type="dxa"/>
</w:tcPr>
<w:p> <!-- Paragraph required! -->
<w:r>
<w:t>Cell text</w:t>
</w:r>
</w:p>
</w:tc>

<!-- ✅ CORRECT: Empty cell (still needs paragraph) -->
<w:tc>
<w:tcPr>
<w:tcW w:w="2500" w:type="dxa"/>
</w:tcPr>
<w:p/> <!-- Empty paragraph, but required! -->
</w:tc>

❌ Wrong: Cell without paragraph:

<!-- ❌ WRONG: No paragraph in cell -->
<w:tc>
<w:tcPr>...</w:tcPr>
<w:r><w:t>Text</w:t></w:r> <!-- Missing w:p wrapper! -->
</w:tc>

How to Fix in AppraiSys​

Wrong approach:

// ❌ BAD: Adding text directly
var body = mainPart.Document.Body;
var text = new Text("Hello");
body.Append(text); // ❌ Missing paragraph and run!

Correct approach:

// ✅ GOOD: Complete hierarchy
var body = mainPart.Document.Body;

var paragraph = new Paragraph();
var run = new Run();
var text = new Text("Hello");

run.Append(text);
paragraph.Append(run);
body.Append(paragraph); // ✅ Proper nesting

// Or more concisely:
body.Append(
new Paragraph(
new Run(
new Text("Hello")
)
)
);

For table cells:

// ✅ GOOD: Cell with paragraph
var cell = new TableCell();

// Add cell properties
cell.Append(new TableCellProperties(
new TableCellWidth { Width = "2500", Type = TableWidthUnitValues.Dxa }
));

// Add paragraph (required!)
cell.Append(
new Paragraph(
new Run(
new Text("Cell content")
)
)
);

row.Append(cell);

Quick Reference: Valid Hierarchies​

Minimum Valid Document​

<w:document>
<w:body>
<w:p>
<w:r>
<w:t>Text</w:t>
</w:r>
</w:p>
</w:body>
</w:document>

Document with Table​

<w:body>
<w:p>
<w:r><w:t>Before table</w:t></w:r>
</w:p>

<w:tbl> <!-- Block level -->
<w:tr>
<w:tc>
<w:p> <!-- Required in cell -->
<w:r><w:t>Cell</w:t></w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>

<w:p>
<w:r><w:t>After table</w:t></w:r>
</w:p>
</w:body>

Document with Content Control​

<w:body>
<w:sdt> <!-- Block level -->
<w:sdtPr>
<w:tag w:val="field1"/>
</w:sdtPr>
<w:sdtContent>
<w:p>
<w:r><w:t>Content</w:t></w:r>
</w:p>
</w:sdtContent>
</w:sdt>
</w:body>

How to Catch These Mistakes​

Code Review Checklist​

When reviewing document generation code, ask:

  1. ✅ Are all SDTs at block level or inside paragraphs (not inside runs)?
  2. ✅ Are all tables at body/cell/SDT level (not inside runs)?
  3. ✅ Does every text node have a run parent?
  4. ✅ Does every run have a paragraph parent (or inline SDT)?
  5. ✅ Does every table cell contain at least one paragraph?

Using Validator​

using DocumentFormat.OpenXml.Validation;

var validator = new OpenXmlValidator();
var errors = validator.Validate(wordDoc);

foreach (var error in errors)
{
Console.WriteLine($"Error: {error.Description}");
Console.WriteLine($"Path: {error.Path.XPath}");

// Look for common mistakes
if (error.Description.Contains("SdtRun"))
{
Console.WriteLine("⚠️ Possible SDT in run issue!");
}
if (error.Description.Contains("Table"))
{
Console.WriteLine("⚠️ Possible table placement issue!");
}
if (error.Description.Contains("Text"))
{
Console.WriteLine("⚠️ Possible text without run issue!");
}
}

Visual Inspection​

Use OpenXML SDK Productivity Tool:

  1. Open your DOCX
  2. Look at document structure tree
  3. Check that:
    • Tables are at body level (not inside runs)
    • SDTs contain paragraphs (not vice versa for block SDTs)
    • All text is inside runs inside paragraphs

Real-World Examples from AppraiSys​

Example 1: Mail Merge Field (Correct)​

From SyncfusionFieldsPipelineStep:

// ✅ This is correct - field creates SDT at paragraph level
var sdt = new SdtBlock();
var sdtProperties = new SdtProperties();
sdtProperties.Append(new Tag { Val = "MergeField1" });
sdt.Append(sdtProperties);

var sdtContent = new SdtContentBlock();
var paragraph = new Paragraph();
var run = new Run(new Text("Value"));

paragraph.Append(run);
sdtContent.Append(paragraph);
sdt.Append(sdtContent);

body.Append(sdt); // ✅ SDT added to body

Example 2: Property Images Table (Correct)​

From your table creation code:

// ✅ Correct table structure
var table = new Table();

// Table properties
var tableProperties = new TableProperties(
new TableWidth { Width = "4500", Type = TableWidthUnitValues.Pct }
);
table.Append(tableProperties);

// Row with cell
var row = new TableRow();
var cell = new TableCell();

// ✅ Cell contains paragraph (required!)
cell.Append(new Paragraph(
new Run(new Text("Image caption"))
));

row.Append(cell);
table.Append(row);
body.Append(table); // ✅ Table added to body

Prevention Strategies​

1. Use OpenXML SDK (Not Manual XML)​

// ✅ SDK prevents most mistakes
body.Append(
new Paragraph( // SDK ensures proper nesting
new Run(
new Text("Content")
)
)
);

The SDK's type system makes it harder (but not impossible) to create invalid structures.

2. Create Helper Methods​

// Helper to create proper text structure
public static Paragraph CreateTextParagraph(string text)
{
return new Paragraph(
new Run(
new Text(text)
)
);
}

// Usage
body.Append(CreateTextParagraph("Hello World"));

3. Validate After Generation​

public static bool ValidateDocument(WordprocessingDocument doc)
{
var validator = new OpenXmlValidator();
var errors = validator.Validate(doc);

if (!errors.Any())
{
return true;
}

foreach (var error in errors)
{
Console.WriteLine($"Validation Error: {error.Description}");
}

return false;
}

4. Unit Tests​

[Test]
public void GeneratedDocument_ShouldBeValid()
{
// Arrange
var doc = GenerateTestDocument();

// Act
var validator = new OpenXmlValidator();
var errors = validator.Validate(doc);

// Assert
Assert.IsEmpty(errors, "Document has validation errors");
}

Summary: The Golden Rules​

✅ Rule 1: SDT Placement​

  • SDTs are block or inline containers
  • They contain paragraphs/tables/runs
  • They are never children of runs

✅ Rule 2: Table Placement​

  • Tables are block-level elements
  • Valid locations: body, table cells, SDT content, headers/footers
  • Never inside runs or inline elements

✅ Rule 3: Text Hierarchy​

  • Text always needs: paragraph → run → text
  • Never skip the wrappers
  • Table cells must contain at least one paragraph

Next Steps​

Now that you know what NOT to do, learn the complete rules for what you CAN do:

➡️ Next: Element Hierarchy Rules - Complete nesting rules

Related:


Test Your Understanding​

Question 1​

Which is correct?

A)

<w:p>
<w:r>
<w:sdt><w:sdtContent><w:t>Text</w:t></w:sdtContent></w:sdt>
</w:r>
</w:p>

B)

<w:sdt>
<w:sdtContent>
<w:p><w:r><w:t>Text</w:t></w:r></w:p>
</w:sdtContent>
</w:sdt>
Answer

B is correct.

A places an SDT inside a run, which violates Fatal Mistake #1. SDTs must contain paragraphs, not be contained by runs.

Question 2​

Is this table placement valid?

<w:body>
<w:p>
<w:tbl>
<w:tr><w:tc><w:p><w:r><w:t>Cell</w:t></w:r></w:p></w:tc></w:tr>
</w:tbl>
</w:p>
</w:body>
Answer

No! The table is inside a paragraph (w:p), which violates Fatal Mistake #2.

Tables must be direct children of body, not inside paragraphs:

<w:body>
<w:tbl>
<w:tr><w:tc><w:p><w:r><w:t>Cell</w:t></w:r></w:p></w:tc></w:tr>
</w:tbl>
</w:body>

Question 3​

What's wrong with this table cell?

<w:tc>
<w:tcPr><w:tcW w:w="2500" w:type="dxa"/></w:tcPr>
<w:r><w:t>Cell content</w:t></w:r>
</w:tc>
Answer

Missing paragraph! Table cells must contain at least one paragraph:

<w:tc>
<w:tcPr><w:tcW w:w="2500" w:type="dxa"/></w:tcPr>
<w:p> <!-- Add paragraph -->
<w:r><w:t>Cell content</w:t></w:r>
</w:p>
</w:tc>

This violates Fatal Mistake #3 (text without proper hierarchy).


💡 Remember: These three mistakes cause 90% of corruption. Master them, and you'll avoid most document generation bugs!