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:
-
Body (most common)
<w:body>
<w:tbl>...</w:tbl>
</w:body> -
Table cell (nested tables)
<w:tc>
<w:p>...</w:p> <!-- Required: At least one paragraph -->
<w:tbl>...</w:tbl> <!-- Nested table -->
</w:tc> -
SDT content
<w:sdt>
<w:sdtContent>
<w:tbl>...</w:tbl>
</w:sdtContent>
</w:sdt> -
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:
- ✅ Are all SDTs at block level or inside paragraphs (not inside runs)?
- ✅ Are all tables at body/cell/SDT level (not inside runs)?
- ✅ Does every text node have a run parent?
- ✅ Does every run have a paragraph parent (or inline SDT)?
- ✅ 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:
- Open your DOCX
- Look at document structure tree
- 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:
- WordprocessingML Core - Element basics
- Content Controls - SDT deep dive
- Tables - Table complete guide
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!