Skip to main content

Numbering and Lists

Overview​

OOXML handles lists through a two-tier system: Abstract Numbering Definitions (templates) and Numbering Instances (actual lists).


Numbering Structure​

Files Involved​

  • /word/numbering.xml - List definitions
  • /word/document.xml - Paragraphs reference lists

Two-Tier System​

Abstract Numbering:

  • Template defining list style
  • Can have multiple levels (0-8)
  • Reusable across multiple lists

Numbering Instance:

  • References an abstract numbering
  • Tracks current counter state
  • Each independent list needs own instance

Abstract Numbering Definition​

Structure​

<w:abstractNum w:abstractNumId="0">
<w:multiLevelType w:val="hybridMultilevel"/>

<!-- Level 0 -->
<w:lvl w:ilvl="0">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:lvlText w:val="%1."/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:ind w:left="720" w:hanging="360"/>
</w:pPr>
</w:lvl>

<!-- Level 1 -->
<w:lvl w:ilvl="1">
<w:start w:val="1"/>
<w:numFmt w:val="lowerLetter"/>
<w:lvlText w:val="%2)"/>
<w:lvlJc w:val="left"/>
<w:pPr>
<w:ind w:left="1440" w:hanging="360"/>
</w:pPr>
</w:lvl>
</w:abstractNum>

Number Formats​

FormatResultXML Value
1, 2, 3Decimaldecimal
a, b, cLower letterlowerLetter
A, B, CUpper letterupperLetter
i, ii, iiiLower RomanlowerRoman
I, II, IIIUpper RomanupperRoman
•Bulletbullet

Level Definition​

Complete Level​

<w:lvl w:ilvl="0">
<!-- Start value -->
<w:start w:val="1"/>

<!-- Number format -->
<w:numFmt w:val="decimal"/>

<!-- Level text (how number appears) -->
<w:lvlText w:val="%1."/>

<!-- Justification -->
<w:lvlJc w:val="left"/>

<!-- Paragraph properties -->
<w:pPr>
<w:ind w:left="720" w:hanging="360"/>
</w:pPr>

<!-- Run properties (number formatting) -->
<w:rPr>
<w:b/>
<w:color w:val="FF0000"/>
</w:rPr>
</w:lvl>

Level Text Placeholders​

%1 = Level 1 number
%2 = Level 2 number
%3 = Level 3 number

Examples:

  • %1. → "1.", "2.", "3."
  • %1.%2 → "1.1", "1.2", "2.1"
  • Article %1 → "Article 1", "Article 2"

Numbering Instance​

Create Instance​

<w:num w:numId="1">
<w:abstractNumId w:val="0"/>
</w:num>

Purpose: Links paragraphs to abstract numbering definition

Multiple instances: Each instance maintains independent counters

<w:num w:numId="1">
<w:abstractNumId w:val="0"/>
</w:num>

<w:num w:numId="2">
<w:abstractNumId w:val="0"/> <!-- Same template, different counter -->
</w:num>

Applying Lists to Paragraphs​

In document.xml​

<w:p>
<w:pPr>
<w:numPr>
<w:ilvl w:val="0"/> <!-- Level 0 -->
<w:numId w:val="1"/> <!-- List instance 1 -->
</w:numPr>
</w:pPr>
<w:r><w:t>First item</w:t></w:r>
</w:p>

<w:p>
<w:pPr>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r><w:t>Second item</w:t></w:r>
</w:p>

<w:p>
<w:pPr>
<w:numPr>
<w:ilvl w:val="1"/> <!-- Level 1 (indented) -->
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r><w:t>Sub-item</w:t></w:r>
</w:p>

Result:

1. First item
2. Second item
a) Sub-item

Code Examples​

Create Simple Numbered List​

public static void CreateNumberedList(NumberingDefinitionsPart numberingPart)
{
if (numberingPart.Numbering == null)
{
numberingPart.Numbering = new Numbering();
}

// Abstract numbering
var abstractNum = new AbstractNum { AbstractNumberId = 0 };
abstractNum.AppendChild(new MultiLevelType { Val = MultiLevelValues.HybridMultilevel });

// Level 0: 1, 2, 3
var level0 = new Level { LevelIndex = 0 };
level0.AppendChild(new StartNumberingValue { Val = 1 });
level0.AppendChild(new NumberingFormat { Val = NumberFormatValues.Decimal });
level0.AppendChild(new LevelText { Val = "%1." });
level0.AppendChild(new LevelJustification { Val = LevelJustificationValues.Left });

var level0Props = new PreviousParagraphProperties();
level0Props.AppendChild(new Indentation { Left = "720", Hanging = "360" });
level0.AppendChild(level0Props);

abstractNum.AppendChild(level0);
numberingPart.Numbering.AppendChild(abstractNum);

// Numbering instance
var numberingInstance = new NumberingInstance { NumberID = 1 };
numberingInstance.AppendChild(new AbstractNumId { Val = 0 });
numberingPart.Numbering.AppendChild(numberingInstance);
}

Create Bullet List​

public static void CreateBulletList(NumberingDefinitionsPart numberingPart)
{
if (numberingPart.Numbering == null)
{
numberingPart.Numbering = new Numbering();
}

// Abstract numbering
var abstractNum = new AbstractNum { AbstractNumberId = 1 };
abstractNum.AppendChild(new MultiLevelType { Val = MultiLevelValues.HybridMultilevel });

// Level 0: Bullet
var level0 = new Level { LevelIndex = 0 };
level0.AppendChild(new StartNumberingValue { Val = 1 });
level0.AppendChild(new NumberingFormat { Val = NumberFormatValues.Bullet });
level0.AppendChild(new LevelText { Val = "●" });
level0.AppendChild(new LevelJustification { Val = LevelJustificationValues.Left });

var level0Props = new PreviousParagraphProperties();
level0Props.AppendChild(new Indentation { Left = "720", Hanging = "360" });
level0.AppendChild(level0Props);

// Bullet font
var level0RunProps = new NumberingSymbolRunProperties();
level0RunProps.AppendChild(new RunFonts
{
Ascii = "Symbol",
HighAnsi = "Symbol",
Hint = FontTypeHintValues.Default
});
level0.AppendChild(level0RunProps);

abstractNum.AppendChild(level0);
numberingPart.Numbering.AppendChild(abstractNum);

// Numbering instance
var numberingInstance = new NumberingInstance { NumberID = 2 };
numberingInstance.AppendChild(new AbstractNumId { Val = 1 });
numberingPart.Numbering.AppendChild(numberingInstance);
}

Apply List to Paragraph​

public static Paragraph CreateListItem(string text, int listId, int level = 0)
{
var paragraph = new Paragraph();

var paragraphProperties = new ParagraphProperties();

// Numbering properties
var numberingProperties = new NumberingProperties();
numberingProperties.AppendChild(new NumberingLevelReference { Val = level });
numberingProperties.AppendChild(new NumberingId { Val = listId });
paragraphProperties.AppendChild(numberingProperties);

paragraph.AppendChild(paragraphProperties);

var run = new Run();
run.AppendChild(new Text(text));
paragraph.AppendChild(run);

return paragraph;
}

// Usage:
body.AppendChild(CreateListItem("First item", 1, 0));
body.AppendChild(CreateListItem("Second item", 1, 0));
body.AppendChild(CreateListItem("Sub-item", 1, 1));

Multi-Level Lists​

Three-Level List​

public static void CreateMultiLevelList(NumberingDefinitionsPart numberingPart)
{
var abstractNum = new AbstractNum { AbstractNumberId = 2 };
abstractNum.AppendChild(new MultiLevelType { Val = MultiLevelValues.HybridMultilevel });

// Level 0: 1, 2, 3
var level0 = CreateLevel(0, NumberFormatValues.Decimal, "%1.", 720);
abstractNum.AppendChild(level0);

// Level 1: a, b, c
var level1 = CreateLevel(1, NumberFormatValues.LowerLetter, "%2)", 1440);
abstractNum.AppendChild(level1);

// Level 2: i, ii, iii
var level2 = CreateLevel(2, NumberFormatValues.LowerRoman, "%3.", 2160);
abstractNum.AppendChild(level2);

numberingPart.Numbering.AppendChild(abstractNum);

var numberingInstance = new NumberingInstance { NumberID = 3 };
numberingInstance.AppendChild(new AbstractNumId { Val = 2 });
numberingPart.Numbering.AppendChild(numberingInstance);
}

private static Level CreateLevel(
int levelIndex,
NumberFormatValues format,
string levelText,
int indentLeft)
{
var level = new Level { LevelIndex = levelIndex };
level.AppendChild(new StartNumberingValue { Val = 1 });
level.AppendChild(new NumberingFormat { Val = format });
level.AppendChild(new LevelText { Val = levelText });
level.AppendChild(new LevelJustification { Val = LevelJustificationValues.Left });

var props = new PreviousParagraphProperties();
props.AppendChild(new Indentation
{
Left = indentLeft.ToString(),
Hanging = "360"
});
level.AppendChild(props);

return level;
}

Common List Patterns​

Restart Numbering​

<w:p>
<w:pPr>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
<w:ins w:id="0" w:author="User" w:date="..."/>
</w:numPr>
</w:pPr>
<w:r><w:t>Restarted at 1</w:t></w:r>
</w:p>

Use new numbering instance to restart:

// Create new instance with same abstract numbering
var newInstance = new NumberingInstance { NumberID = 4 };
newInstance.AppendChild(new AbstractNumId { Val = 0 }); // Same template
numberingPart.Numbering.AppendChild(newInstance);

Custom Start Number​

<w:lvl w:ilvl="0">
<w:start w:val="5"/> <!-- Start at 5 instead of 1 -->
...
</w:lvl>

Skip Level​

To skip numbering on a specific paragraph:

<w:p>
<!-- No w:numPr -->
<w:r><w:t>This paragraph has no number</w:t></w:r>
</w:p>

Custom Bullet Characters​

Unicode Bullets​

<w:lvlText w:val="●"/>  <!-- Bullet -->
<w:lvlText w:val="○"/> <!-- Circle -->
<w:lvlText w:val="■"/> <!-- Square -->
<w:lvlText w:val="▪"/> <!-- Small square -->
<w:lvlText w:val="►"/> <!-- Arrow -->
<w:lvlText w:val="✓"/> <!-- Checkmark -->

Symbol Font Characters​

var runProps = new NumberingSymbolRunProperties();
runProps.AppendChild(new RunFonts
{
Ascii = "Wingdings",
HighAnsi = "Wingdings"
});
level.AppendChild(runProps);

level.AppendChild(new LevelText { Val = "Ø" }); // Wingdings character

Indentation​

Understanding Hanging Indent​

|<------ left (720) ------>|
| |
1. Text starts here and
wraps to this position
|<- hanging (360) ->|

left: Total indent from margin
hanging: Distance number hangs out

Code​

var indentation = new Indentation 
{
Left = "720", // 0.5 inch from margin
Hanging = "360" // Number hangs 0.25 inch left of text
};

AppraiSys Usage​

Extract from Codebase​

AppraiSys uses lists minimally in templates, primarily for:

  • Inspection checklists
  • Comparable property adjustments
  • Terms and conditions

Pattern:

  1. Define abstract numbering in DefaultDocument.cs
  2. Reference in templates
  3. Apply through mail merge or manual insertion

SFDT Lists​

SFDT List Format​

{
"listFormat": {
"listId": 0,
"listLevelNumber": 0
}
}

SFDT List Definition​

{
"lists": [
{
"listId": 0,
"abstractListId": 0
}
],
"abstractLists": [
{
"abstractListId": 0,
"levels": [
{
"listLevelPattern": "Arabic",
"numberFormat": "%1.",
"startAt": 1,
"followCharacter": "Tab",
"paragraphFormat": {
"leftIndent": 36,
"firstLineIndent": -18
}
}
]
}
]
}

Common Mistakes​

❌ Mistake 1: Forgetting Numbering Instance​

<!-- WRONG: Only abstract numbering, no instance -->
<w:numbering>
<w:abstractNum w:abstractNumId="0">...</w:abstractNum>
<!-- Missing w:num -->
</w:numbering>

Fix: Always create both abstract and instance

❌ Mistake 2: Wrong Level Reference​

<!-- WRONG: Referencing non-existent level -->
<w:numPr>
<w:ilvl w:val="5"/> <!-- But only levels 0-2 defined -->
<w:numId w:val="1"/>
</w:numPr>

Fix: Ensure level exists in abstract numbering

❌ Mistake 3: Reusing Number ID​

// WRONG: Multiple instances with same numId
var num1 = new NumberingInstance { NumberID = 1 };
var num2 = new NumberingInstance { NumberID = 1 }; // Conflict!

Fix: Each instance needs unique NumberID


Debugging Lists​

Check numbering.xml​

  1. Extract DOCX
  2. Open /word/numbering.xml
  3. Verify abstract numbering exists
  4. Verify numbering instance exists
  5. Check level definitions

Validate References​

// Get all list references in document
var listRefs = document.Descendants<NumberingId>()
.Select(n => n.Val.Value)
.Distinct();

// Check all exist in numbering definitions
foreach (var numId in listRefs)
{
var instance = numberingPart.Numbering.Descendants<NumberingInstance>()
.FirstOrDefault(n => n.NumberID == numId);

if (instance == null)
{
Console.WriteLine($"Missing numbering instance: {numId}");
}
}

Quick Reference​

Minimum Valid List​

<!-- numbering.xml -->
<w:numbering>
<w:abstractNum w:abstractNumId="0">
<w:lvl w:ilvl="0">
<w:start w:val="1"/>
<w:numFmt w:val="decimal"/>
<w:lvlText w:val="%1."/>
<w:lvlJc w:val="left"/>
</w:lvl>
</w:abstractNum>

<w:num w:numId="1">
<w:abstractNumId w:val="0"/>
</w:num>
</w:numbering>

<!-- document.xml -->
<w:p>
<w:pPr>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
<w:r><w:t>List item</w:t></w:r>
</w:p>

Next Steps​

➡️ Next: Styles In Depth

Related:


💡 Remember: Abstract numbering = template, Numbering instance = actual list counter!