# Subtext Parts

> Mark ranges of text with SubTextPart in ServerTextControl: add and nest subtext parts, store data, find them by name or ID, save their content and remove them.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Document Processing > Subtext Parts
- **5479 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/subtext-parts/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/subtext-parts/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/subtext-parts/llms-full.txt

---

A clause of a contract, an address block or a section that your application stores in a database on its own is a range of formatted text that code must find again later. A subtext part marks such a range with a name, an ID and optional data, and keeps the mark when the document is saved. With `ServerTextControl`, you add and nest subtext parts, find them in all parts of a document, read and save their content and remove them again.

Subtext parts are also the building block of merge blocks: a merge block is a subtext part whose name starts with `txmb_`, as [Merge Blocks](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/blocks/#how-blocks-are-stored) explains. How users work with subtext parts in the browser is the subject of [Subtext Parts](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/subtext-parts/llms-full.txt) in the Document Editor section.

### How Subtext Parts Work

A [SubTextPart](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/llms-full.txt) covers a continuous range of characters, which can contain any other element, such as tables, images, fields and further subtext parts. The following rules apply when you add one:

- **Selectable text.** A subtext part must cover a part of the document that can be selected, and its length can't be 0.
- **No overlaps.** Two subtext parts can follow each other directly, but they can't overlap. The position between them belongs to the second one.
- **Nesting.** A subtext part can contain other subtext parts. A nested subtext part must be shorter than the outer one, but it can start or end at the same position. The outer subtext part must be added first.
- **Deletion with the text.** When the last character of a subtext part is deleted, the subtext part is deleted too.

Each text part of a document has its own [SubTextPartCollection](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/llms-full.txt). [ServerTextControl.SubTextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/subtextparts-property/llms-full.txt) contains the subtext parts of the main text, and [HeaderFooter.SubTextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/headerfooter/subtextparts-property/llms-full.txt) and [TextFrame.SubTextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/textframe/subtextparts-property/llms-full.txt) those of a header, footer or text frame. A subtext part has these properties:

| Property | Contains |
|---|---|
| [Name](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/name-property/llms-full.txt) | A name of your choice; an empty string means no name |
| [ID](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/id-property/llms-full.txt) | A one-based identifier of your choice; 0 means no ID |
| [Data](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/data-property/llms-full.txt) | A string with additional data, for example JSON; an empty string means no data |
| [Start](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/start-property/llms-full.txt), [Length](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/length-property/llms-full.txt) | The one-based position of the first character and the number of characters, read only |
| [Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/text-property/llms-full.txt) | The text of the subtext part, read only |
| [Number](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/number-property/llms-full.txt) | The position in the text flow, starting with 1; when an outer and a nested subtext part start at the same position, the outer one has the smaller number |
| [NestedLevel](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/nestedlevel-property/llms-full.txt) | 1 for a subtext part that isn't nested, 2 for one nested in it and so on |
| [OuterSubTextPart](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/outersubtextpart-property/llms-full.txt), [OuterMostSubTextPart](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/outermostsubtextpart-property/llms-full.txt) | The subtext part directly around it and the one at level 1, or `null` for a subtext part at level 1 |

### Mark Text With Subtext Parts

The following program writes a short contract with three clauses, marks the whole contract with a subtext part that stores JSON data and marks each clause with a nested subtext part. It prints the result of each `Add` call and all subtext parts, then saves the document in the TX Text Control format:

```csharp
using System.Text.Json;
using TXTextControl;

string[] clauseNames = { "scope", "payment", "liability" };

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Text = "Scope: Acme Corporation licenses TX Text Control .NET Server.\r\n" +
        "Payment: Invoice INV-2025-001 is due within 14 days.\r\n" +
        "Liability: The liability is limited to the license fee.";

    Paragraph first = tx.Paragraphs[1];
    Paragraph last = tx.Paragraphs[tx.Paragraphs.Count];

    // Paragraph.Length includes the paragraph end, which the subtext parts leave out
    int contractLength = last.Start + last.Length - 1 - first.Start;

    // The outer subtext part must be added before the nested ones
    SubTextPart contract = new SubTextPart("contract", 1, first.Start, contractLength)
    {
        Data = JsonSerializer.Serialize(new ContractInfo("Acme Corporation", "INV-2025-001"))
    };
    Console.WriteLine($"contract: {tx.SubTextParts.Add(contract)}");

    // One nested subtext part per clause, with the IDs 2 to 4
    for (int i = 1; i <= tx.Paragraphs.Count; i++)
    {
        Paragraph paragraph = tx.Paragraphs[i];
        SubTextPart clause = new SubTextPart(clauseNames[i - 1], i + 1, paragraph.Start, paragraph.Length - 1);

        SubTextPartCollection.AddResult result = tx.SubTextParts.Add(clause);
        Console.WriteLine($"{clause.Name}: {result}");
    }

    // The numbers follow the text flow, starting with 1
    for (int number = 1; number <= tx.SubTextParts.Count; number++)
    {
        SubTextPart part = tx.SubTextParts[number];
        string indent = new string(' ', (part.NestedLevel - 1) * 2);
        Console.WriteLine($"{indent}{part.Number} {part.Name} (ID {part.ID}): {part.Text}");
    }

    // Only the TX Text Control format keeps the data
    tx.Save("contract.tx", StreamType.InternalUnicodeFormat);
}

record ContractInfo(string Customer, string Invoice);
```

The code works as follows:

- The [SubTextPart constructor](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/constructor/llms-full.txt) takes a name, an ID, the one-based start position and the length. [Paragraph.Start](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/paragraph/start-property/llms-full.txt) is one-based as well, so it can be passed directly.
- [SubTextPartCollection.Add](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/add-method/llms-full.txt) returns an [AddResult](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/addresult-enumeration/llms-full.txt). `Successful` means the subtext part was added. `Overlapping`, `PositionInvalid` and `AlreadyExists` name the reason it wasn't, and `Combined` means it directly followed a subtext part with the same name and ID and was merged with it.
- `Data` holds any string. `JsonSerializer` of .NET turns a record into JSON, which the program reads back in the next example.
- The indexer of the collection, [Item](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/item-property/llms-full.txt), takes the number of a subtext part from 1 to [Count](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/count-property/llms-full.txt).

A subtext part can also be created with only a name and an ID. Its start and length are then 0, and `Add` uses the current selection as its range. `NoSelection` and `SelectionTooComplex` are the results when no text is selected or the selection isn't a continuous sequence of characters.

### Find Subtext Parts and Save Their Content

A subtext part is found by its name, its ID or its number, and its content can be saved without selecting it. The following program loads *contract.tx*, reads the data of the contract, lists the clauses nested in it and saves the liability clause as HTML and DOCX:

```csharp
using System.Text.Json;
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("contract.tx", StreamType.InternalUnicodeFormat);

    SubTextPart contract = tx.SubTextParts.GetItem("contract");

    if (contract == null)
    {
        Console.WriteLine("The document has no contract subtext part.");
        return;
    }

    // An empty string means that no data is stored
    if (!string.IsNullOrEmpty(contract.Data))
    {
        ContractInfo info = JsonSerializer.Deserialize<ContractInfo>(contract.Data);
        Console.WriteLine($"Customer: {info.Customer}, invoice: {info.Invoice}");
    }

    // All nested subtext parts in text order, or null
    foreach (SubTextPart clause in contract.GetChildren() ?? Array.Empty<SubTextPart>())
    {
        Console.WriteLine($"{clause.Name} (level {clause.NestedLevel}) in {clause.OuterSubTextPart.Name}");
    }

    // The subtext part with the ID 4
    SubTextPart liability = tx.SubTextParts.GetItem(4);

    if (liability != null)
    {
        liability.Save(out string html, StringStreamType.HTMLFormat);
        Console.WriteLine(html);

        liability.Save(out byte[] docx, BinaryStreamType.WordprocessingML);
        File.WriteAllBytes("liability.docx", docx);
    }
}

record ContractInfo(string Customer, string Invoice);
```

The code works as follows:

- [GetItem](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/getitem-method/llms-full.txt) takes a name or an ID and returns the first matching subtext part, or `null`. Without a parameter, it returns the subtext part at the current input position.
- [GetChildren](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/getchildren-method/llms-full.txt) returns the nested subtext parts at all levels in the order of the text, or `null` if there are none.
- [SubTextPart.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/save-method/llms-full.txt) writes the content to a string for the formats of [StringStreamType](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/stringstreamtype/enumeration/llms-full.txt), such as HTML and RTF, or to a byte array for the formats of [BinaryStreamType](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/binarystreamtype/enumeration/llms-full.txt), such as DOCX. PDF and PDF/A can only be saved for a complete document. Further overloads take `SaveSettings`.

To save a range that isn't a subtext part, select it and use `Selection.Save`, as [Saving and Converting Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/#save-part-of-a-document) describes.

### Find Subtext Parts in All Text Parts

Headers, footers and text frames can contain subtext parts too. To reach all of them, loop through [ServerTextControl.TextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/textparts-property/llms-full.txt), whose elements implement [IFormattedText](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/iformattedtext/llms-full.txt) with its [SubTextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/iformattedtext/subtextparts-property/llms-full.txt) property. The following program loads an MS Word template, lists its subtext parts and the Word fields inside each of them:

```csharp
using TXTextControl;

LoadSettings loadSettings = new LoadSettings()
{
    ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
    LoadSubTextParts = true
};

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/template.docx", StreamType.WordprocessingML, loadSettings);

    foreach (IFormattedText textPart in tx.TextParts)
    {
        foreach (SubTextPart subTextPart in textPart.SubTextParts)
        {
            Console.WriteLine($"{textPart.GetType().Name}: {subTextPart.Name} (level {subTextPart.NestedLevel})");

            // The fields completely inside the subtext part, or null
            TextField[] fields = subTextPart.GetTextFields(TextFieldType.ApplicationField);

            foreach (TextField field in fields ?? Array.Empty<TextField>())
            {
                if (field is ApplicationField applicationField)
                {
                    Console.WriteLine($"  {applicationField.TypeName} = \"{applicationField.Text}\"");
                }
            }
        }
    }
}
```

The code works as follows:

- [LoadSubTextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/loadsettings/loadsubtextparts-property/llms-full.txt) turns bookmarks that span several characters into subtext parts when a DOCX, DOC or RTF file is loaded. Without it, such bookmarks become document targets at their first character.
- `ApplicationFieldFormat.MSWord` imports the Word fields as application fields, as [Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/fields/#find-the-fields-of-all-text-parts) explains.
- [GetTextFields](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpart/gettextfields-method/llms-full.txt) returns the fields of the [TextFieldType](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/textfieldtype/enumeration/llms-full.txt) values you combine, including the fields of nested subtext parts, or `null`.

### Remove Subtext Parts

[SubTextPartCollection.Remove](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/subtextpartcollection/remove-method/llms-full.txt) removes a subtext part with or without its text. The following program removes the liability clause with its text and then the markers of all remaining subtext parts, keeping their text:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("contract.tx", StreamType.InternalUnicodeFormat);

    // keepText false: the text of the clause is removed as well
    SubTextPart liability = tx.SubTextParts.GetItem("liability");

    if (liability != null)
    {
        tx.SubTextParts.Remove(liability, false, false);
    }

    // Collect the subtext parts at level 1 before removing them
    List<SubTextPart> topLevelParts = new List<SubTextPart>();

    foreach (SubTextPart part in tx.SubTextParts)
    {
        if (part.NestedLevel == 1)
        {
            topLevelParts.Add(part);
        }
    }

    // keepText true, keepNested false: the text stays, the nested subtext parts are removed too
    foreach (SubTextPart part in topLevelParts)
    {
        tx.SubTextParts.Remove(part, true, false);
    }

    Console.WriteLine($"Subtext parts left: {tx.SubTextParts.Count}");
    tx.Save("contract-plain.docx", StreamType.WordprocessingML);
}
```

`Remove` always removes the nested subtext parts along with the outer one, unless `keepText` and `keepNested` are both `true`: then only the outer subtext part is removed, and the nested ones stay. It returns `true` if the subtext part was removed.

To keep the subtext parts in your document but leave them out of a copy you save, set `SaveSettings.OmittedContent` to `OmittedContent.SubTextParts`. The text and its formatting are saved without the markers, as [Saving and Converting Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/#leave-out-content-and-add-properties) shows. This setting can't be used for PDF.

### Formats and Events

- **TX Text Control format.** Only this format keeps the `Data` of subtext parts. Save templates and documents whose subtext parts carry data with `StreamType.InternalUnicodeFormat`.
- **Other formats.** Subtext parts are kept, but without their data. MS Word and RTF files return their subtext parts when you load them with `LoadSubTextParts` set to `true`.
- **Events.** [SubTextPartCreated](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/subtextpartcreated-event/llms-full.txt) and [SubTextPartDeleted](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/subtextpartdeleted-event/llms-full.txt) occur when a subtext part is created or deleted. In a `SubTextPartDeleted` handler, only the `Name`, `ID` and highlight settings of the subtext part are still valid. [Events](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/events/llms-full.txt) lists all events of `ServerTextControl`.

The blog article [The Power of SubTextParts: Typical Use Cases](https://www.textcontrol.com/blog/2024/02/09/the-power-of-subtextparts-typical-use-cases/) shows further uses, such as storing sections of a document in a database. Its code is written for the Windows Forms `TextControl`.

### Next Steps

- [Merge Blocks](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/blocks/llms-full.txt): repeat subtext parts for each record of your data.
- [Sorting and Filtering](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/sorting-filtering/#where-the-instructions-are-stored): see how merge blocks store their sort and filter instructions in `Data`.
- [Document Protection](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/protection/llms-full.txt): mark ranges that users can edit in a protected document.
- [Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/fields/llms-full.txt): mark single pieces of text with fields.

---

## Nearby

- **Previous:** [Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/fields/llms-full.txt)
- **Next:** [Form Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/form-fields/llms-full.txt)
