OOXML ↔ SFDT Element Mapping
Overview
This guide shows exactly how OOXML structures transform to Syncfusion's SFDT format and back. Understanding these mappings is critical for working with both formats in the AppraiSys system.
Core Concept Differences
OOXML (Office Open XML)
- Format: XML files in ZIP archive
- Structure: Hierarchical XML elements
- Namespaces: Multiple (
w:,r:,a:, etc.) - Relationships: Separate .rels files
- Images: Stored in
/word/media/
SFDT (Syncfusion Document Format)
- Format: Single JSON object
- Structure: Nested JSON objects and arrays
- No namespaces: Just property names
- Relationships: Embedded in JSON
- Images: Base64 encoded or URLs inline
Document Structure Mapping
OOXML Document Root
<w:document xmlns:w="...">
<w:body>
<w:p>...</w:p>
<w:tbl>...</w:tbl>
</w:body>
</w:document>
SFDT Document Root
{
"sections": [
{
"blocks": [
{ "paragraphFormat": {...}, "inlines": [...] },
{ "table": {...} }
],
"sectionFormat": {...}
}
]
}
Section Mapping
OOXML Section
Sections defined by w:sectPr elements:
<w:body>
<w:p>...</w:p>
<w:p>...</w:p>
<!-- Section break -->
<w:p>
<w:pPr>
<w:sectPr>
<w:pgSz w:w="12240" w:h="15840"/> <!-- Letter size -->
<w:pgMar w:top="1440" w:right="1440"
w:bottom="1440" w:left="1440"/>
<w:cols w:space="720"/>
</w:sectPr>
</w:pPr>
</w:p>
<w:p>...</w:p> <!-- New section -->
</w:body>
SFDT Section
{
"sections": [
{
"sectionFormat": {
"pageWidth": 612, // 8.5 inches in points
"pageHeight": 792, // 11 inches in points
"leftMargin": 72, // 1 inch in points
"rightMargin": 72,
"topMargin": 72,
"bottomMargin": 72,
"differentFirstPage": false,
"differentOddAndEvenPages": false,
"headerDistance": 36,
"footerDistance": 36
},
"blocks": [...]
}
]
}
Key differences:
- OOXML: Twips (1440 per inch)
- SFDT: Points (72 per inch)
- OOXML: Width/height in page size
- SFDT: Width/height directly
Conversion: points = twips / 20
Paragraph Mapping
OOXML Paragraph
<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
<w:jc w:val="center"/>
<w:spacing w:before="120" w:after="120"
w:line="276" w:lineRule="auto"/>
<w:ind w:left="720" w:right="0" w:firstLine="360"/>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>Hello World</w:t>
</w:r>
</w:p>
SFDT Paragraph (Block)
{
"paragraphFormat": {
"styleName": "Normal",
"textAlignment": "Center",
"beforeSpacing": 6, // 120 twips / 20 = 6 points
"afterSpacing": 6,
"lineSpacing": 13.8, // 276 / 20 = 13.8 points
"lineSpacingType": "Multiple",
"leftIndent": 36, // 720 / 20 = 36 points
"rightIndent": 0,
"firstLineIndent": 18 // 360 / 20 = 18 points
},
"inlines": [
{
"text": "Hello World",
"characterFormat": {
"bold": true,
"fontSize": 14 // 28 half-points / 2 = 14 points
}
}
]
}
Key mapping:
w:p→blockobject withparagraphFormatw:pPr→paragraphFormatpropertiesw:r→inlineobject ininlinesarrayw:t→textpropertyw:rPr→characterFormatproperties
Run (Inline) Mapping
OOXML Run
<w:r>
<w:rPr>
<w:rFonts w:ascii="Times New Roman"/>
<w:sz w:val="24"/> <!-- 12pt -->
<w:color w:val="FF0000"/> <!-- Red -->
<w:b/>
<w:i/>
<w:u w:val="single"/>
</w:rPr>
<w:t xml:space="preserve">Text content</w:t>
</w:r>
SFDT Inline
{
"text": "Text content",
"characterFormat": {
"fontFamily": "Times New Roman",
"fontSize": 12, // 24 half-points / 2
"fontColor": "#FF0000FF", // RGBA format
"bold": true,
"italic": true,
"underline": "Single"
}
}
Property mappings:
| OOXML | SFDT | Notes |
|---|---|---|
w:rFonts/@w:ascii | fontFamily | Font name |
w:sz/@w:val | fontSize | Divide by 2 (half-points → points) |
w:color/@w:val | fontColor | Add alpha channel (#RRGGBBAA) |
w:b | bold: true | Boolean |
w:i | italic: true | Boolean |
w:u/@w:val | underline | Enum value |
Table Mapping
OOXML Table
<w:tbl>
<w:tblPr>
<w:tblW w:w="5000" w:type="pct"/>
<w:tblBorders>
<w:top w:val="single" w:sz="4"/>
<w:left w:val="single" w:sz="4"/>
<w:bottom w:val="single" w:sz="4"/>
<w:right w:val="single" w:sz="4"/>
</w:tblBorders>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3000"/>
<w:gridCol w:w="3000"/>
</w:tblGrid>
<w:tr>
<w:tc>
<w:tcPr>
<w:tcW w:w="3000" w:type="dxa"/>
</w:tcPr>
<w:p><w:r><w:t>Cell 1</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3000" w:type="dxa"/>
</w:tcPr>
<w:p><w:r><w:t>Cell 2</w:t></w:r></w:p>
</w:tc>
</w:tr>
</w:tbl>
SFDT Table
{
"table": {
"rows": [
{
"cells": [
{
"cellFormat": {
"cellWidth": 150, // 3000 / 20 = 150 points
"preferredWidth": 150,
"preferredWidthType": "Point"
},
"blocks": [
{
"inlines": [
{ "text": "Cell 1" }
]
}
]
},
{
"cellFormat": {
"cellWidth": 150,
"preferredWidth": 150,
"preferredWidthType": "Point"
},
"blocks": [
{
"inlines": [
{ "text": "Cell 2" }
]
}
]
}
]
}
],
"tableFormat": {
"preferredWidth": 100,
"preferredWidthType": "Percent",
"borders": {
"top": { "lineStyle": "Single", "lineWidth": 0.5 },
"left": { "lineStyle": "Single", "lineWidth": 0.5 },
"bottom": { "lineStyle": "Single", "lineWidth": 0.5 },
"right": { "lineStyle": "Single", "lineWidth": 0.5 }
}
}
}
}
Key differences:
- OOXML: Grid definition separate from cells
- SFDT: No explicit grid, cells define their own width
- OOXML: Cell contains paragraphs
- SFDT: Cell contains
blocksarray (paragraphs)
Image Mapping
OOXML Image
<!-- In document.xml -->
<w:p>
<w:r>
<w:drawing>
<wp:inline>
<wp:extent cx="914400" cy="914400"/> <!-- 1 inch × 1 inch in EMUs -->
<a:graphic>
<a:graphicData>
<pic:pic>
<pic:blipFill>
<a:blip r:embed="rId5"/> <!-- Relationship reference -->
</pic:blipFill>
</pic:pic>
</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>
</w:p>
<!-- In document.xml.rels -->
<Relationship Id="rId5"
Type="...relationships/image"
Target="media/image1.png"/>
<!-- Actual file -->
<!-- /word/media/image1.png (binary) -->
SFDT Image
{
"inlines": [
{
"imageString": "data:image/png;base64,iVBORw0KGgoAAAANSUhEU...",
"width": 72, // 1 inch in points
"height": 72,
"name": "image1.png",
"alternateText": "",
"isInlineImage": true
}
]
}
Or with URL:
{
"inlines": [
{
"imageString": "https://example.com/image.png",
"width": 72,
"height": 72,
"isInlineImage": true
}
]
}
Conversion process:
Unit conversion:
- OOXML: EMUs (914,400 per inch)
- SFDT: Points (72 per inch)
- Formula:
points = emus / 12700
From AppraiSys (DocumentToPdfConverter.cs):
// Extract images from SFDT
var sfdtWrapper = JsonSerializer.Deserialize<SfdtWrapper>(sfdt);
var imagesToDownload = sfdtWrapper?.Images
.Where(x => x.Value.Count == 1 &&
(x.Value.FirstOrDefault()?.StartsWith("http") ?? false))
.ToList();
// Download and convert to base64
foreach (var downloadedImage in downloadedImages)
{
sfdtWrapper.Images[downloadedImage.Success.BaseData.Key].Insert(0,
$"data:{contentType};base64,{Convert.ToBase64String(bytes)}"
);
}
Hyperlink Mapping
OOXML Hyperlink
<!-- In document.xml -->
<w:hyperlink r:id="rId7">
<w:r>
<w:rPr>
<w:color w:val="0000FF"/>
<w:u w:val="single"/>
</w:rPr>
<w:t>Click here</w:t>
</w:r>
</w:hyperlink>
<!-- In document.xml.rels -->
<Relationship Id="rId7"
Type="...relationships/hyperlink"
Target="https://example.com"
TargetMode="External"/>
SFDT Hyperlink
{
"inlines": [
{
"fieldType": 0,
"hasFieldEnd": true,
"formFieldData": null
},
{
"text": "HYPERLINK \"https://example.com\""
},
{
"fieldType": 2
},
{
"text": "Click here",
"characterFormat": {
"fontColor": "#0000FFFF",
"underline": "Single"
}
},
{
"fieldType": 1
}
]
}
SFDT uses field codes for hyperlinks (similar to MERGEFIELD approach)
Style Mapping
OOXML Style Definition
<!-- In styles.xml -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:name w:val="Heading 1"/>
<w:basedOn w:val="Normal"/>
<w:next w:val="Normal"/>
<w:qFormat/>
<w:pPr>
<w:spacing w:before="240" w:after="120"/>
<w:jc w:val="center"/>
</w:pPr>
<w:rPr>
<w:b/>
<w:sz w:val="32"/> <!-- 16pt -->
</w:rPr>
</w:style>
SFDT Style
{
"name": "Heading 1",
"type": "Paragraph",
"paragraphFormat": {
"beforeSpacing": 12, // 240 / 20
"afterSpacing": 6, // 120 / 20
"textAlignment": "Center"
},
"characterFormat": {
"bold": true,
"fontSize": 16 // 32 / 2
},
"basedOn": "Normal",
"next": "Normal"
}
Styles location:
- OOXML:
/word/styles.xml - SFDT:
stylesarray in root object
Content Control (SDT) Mapping
OOXML SDT
<w:sdt>
<w:sdtPr>
<w:id w:val="123456"/>
<w:tag w:val="PropertyAddress"/>
<w:alias w:val="Property Address"/>
<w:lock w:val="sdtContentLocked"/>
</w:sdtPr>
<w:sdtContent>
<w:p>
<w:r><w:t>123 Main Street</w:t></w:r>
</w:p>
</w:sdtContent>
</w:sdt>
SFDT Content Control
{
"contentControlProperties": {
"lockContentControl": true,
"lockContents": false,
"tag": "PropertyAddress",
"title": "Property Address",
"appearance": "BoundingBox",
"type": "RichText",
"hasPlaceHolderText": false,
"multiline": false,
"isTemporary": false,
"characterFormat": {},
"contentControlListItems": []
},
"blocks": [
{
"inlines": [
{ "text": "123 Main Street" }
]
}
]
}
Property mappings:
| OOXML | SFDT |
|---|---|
w:id | (not directly mapped) |
w:tag | tag |
w:alias | title |
w:lock="sdtContentLocked" | lockContentControl: true, lockContents: false |
Complete Transformation Example
OOXML Document
<w:document>
<w:body>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>Title</w:t>
</w:r>
</w:p>
<w:p>
<w:r><w:t>Normal text.</w:t></w:r>
</w:p>
<w:tbl>
<w:tblGrid>
<w:gridCol w:w="3000"/>
<w:gridCol w:w="3000"/>
</w:tblGrid>
<w:tr>
<w:tc>
<w:p><w:r><w:t>A1</w:t></w:r></w:p>
</w:tc>
<w:tc>
<w:p><w:r><w:t>B1</w:t></w:r></w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
</w:document>
SFDT Document
{
"sections": [
{
"blocks": [
{
"paragraphFormat": {
"textAlignment": "Center"
},
"inlines": [
{
"text": "Title",
"characterFormat": {
"bold": true,
"fontSize": 14
}
}
]
},
{
"inlines": [
{ "text": "Normal text." }
]
},
{
"table": {
"rows": [
{
"cells": [
{
"blocks": [
{
"inlines": [
{ "text": "A1" }
]
}
]
},
{
"blocks": [
{
"inlines": [
{ "text": "B1" }
]
}
]
}
]
}
]
}
}
],
"sectionFormat": {}
}
]
}
Unit Conversion Reference
Length Units
| Unit | OOXML | SFDT | Conversion |
|---|---|---|---|
| Font size | Half-points (w:sz) | Points | Divide by 2 |
| Spacing | Twips (1/20 pt) | Points | Divide by 20 |
| Width/Height | Twips | Points | Divide by 20 |
| Image size | EMUs (914400/inch) | Points (72/inch) | Divide by 12700 |
Examples
OOXML → SFDT
w:sz="24" → fontSize: 12
w:spacing before="240" → beforeSpacing: 12
w:tcW w="3000" → cellWidth: 150
wp:extent cx="914400" → width: 72
Common Transformation Issues
Issue 1: Lost Formatting
Problem: Bold/italic lost in conversion
Cause: Run properties not preserved
Solution: Ensure characterFormat includes all properties
Issue 2: Image Size Wrong
Problem: Images appear too large or small
Cause: Unit conversion error
Solution: Use correct formula (EMUs / 12700 = points)
Issue 3: Table Layout Broken
Problem: Cells wrong width after conversion
Cause: Grid definition vs cell widths mismatch
Solution: Recalculate cell widths based on table format
Issue 4: Extra Paragraphs
Problem: Blank paragraphs appear
Cause: SFDT merge artifacts
Solution: Clean empty blocks after conversion
From AppraiSys: Known Syncfusion limitation documented in patches
AppraiSys Conversion Patterns
DOCX to SFDT
// From DocumentToSfdtFormat.cs
public static string Convert(MemoryStream stream)
{
var doc = new Syncfusion.DocIO.DLS.WordDocument(
stream,
Syncfusion.DocIO.FormatType.Docx
);
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Load(doc);
sfdtDocument.OptimizeSfdt = true; // Optimize output
var sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(sfdtDocument);
sfdtDocument.Dispose();
doc.Dispose();
return sfdt;
}
SFDT to DOCX
// From DocumentToPdfConverter.cs
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Save(
mergedSfdt,
Syncfusion.EJ2.DocumentEditor.FormatType.Docx
);
using var wordDocument = new WordDocument(
sfdtDocument,
Syncfusion.DocIO.FormatType.Docx
);
Debugging Transformations
Compare Structures
// 1. Start with DOCX
var docx = WordprocessingDocument.Open("original.docx", false);
// 2. Convert to SFDT
var sfdt = DocumentToSfdtFormat.Convert(docxStream);
// 3. Convert back to DOCX
var sfdtStream = WordDocument.Save(sfdt, FormatType.Docx);
var docx2 = WordprocessingDocument.Open(sfdtStream, false);
// 4. Compare
CompareDocuments(docx, docx2);
Use Syncfusion Document Editor
// In browser with Syncfusion Document Editor
const documentEditor = new DocumentEditor();
documentEditor.open(sfdtJson);
// Inspect structure
console.log(documentEditor.documentEditor.document);
Quick Reference
Structure Mappings
| OOXML | SFDT |
|---|---|
w:document → w:body | sections array |
w:p | block with inlines |
w:r + w:t | inline with text |
w:tbl | table object |
w:tr | row in rows array |
w:tc | cell in cells array |
w:pPr | paragraphFormat |
w:rPr | characterFormat |
w:drawing | inline with imageString |
Next Steps
➡️ Next: Diagnostics & Debugging
Related:
💡 Remember: OOXML is explicit and verbose, SFDT is compact and JSON-based. Both represent the same document—just different formats!