How to Use unparsed-text-lines in XSLT 3.0?
The unparsed-text-lines() function in XSLT 3.0 provides
a native, streamable mechanism to read non-XML text files, such as CSV
or plain text, directly into a sequence of strings separated by line
breaks. Introduced to simplify plain-text transformations, it eliminates
the need to load entire files into a single string before splitting
them. This guide explains how unparsed-text-lines()
operates, how it compares to legacy approaches, and how to combine it
with XPath tokenization functions to convert raw delimited data into
structured XML documents.
Core Mechanics of unparsed-text-lines()
The unparsed-text-lines() function reads an external
resource identified by a URI and returns the contents as a sequence of
xs:string items, where each item represents an individual
line.
Syntax and Signatures
fn:unparsed-text-lines($href as xs:string?) as xs:string*
fn:unparsed-text-lines($href as xs:string?, $encoding as xs:string) as xs:string*
$href: The relative or absolute URI of the target text file. If supplied as an empty sequence, the function returns an empty sequence.$encoding: An optional string specifying the character encoding (for example,utf-8oriso-8859-1). When omitted, the processor defaults to UTF-8 or implementation-defined detection rules.
Line Ending Handling
The function automatically normalizes line endings across different
operating systems. It treats standard line-break characters—such as
carriage return (\r), line feed (\n), and
carriage-return-line-feed combinations (\r\n)—as
delimiters. The returned strings do not contain the trailing newline
characters.
Step-by-Step Implementation: Parsing a CSV File
Transforming a CSV file into structured XML using
unparsed-text-lines() involves three primary phases:
reading the lines, separating header definitions from data rows, and
tokenizing individual fields.
1. The Input Data
Assume a plain-text file named users.csv located in the
same directory as the stylesheet:
id,name,role,department
101,Alice Smith,Engineer,Platform
102,Bob Jones,Analyst,Operations
103,Carol White,Designer,Product
2. The XSLT 3.0 Stylesheet
The following stylesheet reads the CSV file, treats the first record as field names, and creates an element for each subsequent record.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="csv-uri" as="xs:string" select="'users.csv'"/>
<xsl:template name="xsl:initial-template">
<xsl:variable name="lines" as="xs:string*" select="unparsed-text-lines($csv-uri, 'utf-8')"/>
<xsl:if test="exists($lines)">
<xsl:variable name="headers" as="xs:string*" select="tokenize($lines[1], ',')"/>
<users>
<xsl:for-each select="tail($lines)[normalize-space()]">
<xsl:variable name="fields" as="xs:string*" select="tokenize(., ',')"/>
<user>
<xsl:for-each select="$headers">
<xsl:variable name="pos" as="xs:integer" select="position()"/>
<xsl:element name="{normalize-space(.)}">
<xsl:value-of select="normalize-space($fields[$pos])"/>
</xsl:element>
</xsl:for-each>
</user>
</xsl:for-each>
</users>
</xsl:if>
</xsl:template>
</xsl:stylesheet>3. Resulting XML Output
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<id>101</id>
<name>Alice Smith</name>
<role>Engineer</role>
<department>Platform</department>
</user>
<user>
<id>102</id>
<name>Bob Jones</name>
<role>Analyst</role>
<department>Operations</department>
</user>
<user>
<id>103</id>
<name>Carol White</name>
<role>Designer</role>
<department>Product</department>
</user>
</users>Parsing Advanced Delimited Formats
While simple comma-separated files can be broken apart with
tokenize(., ','), real-world CSV files often contain edge
cases like quoted fields containing commas or escaped characters.
Handling Quoted
Fields with xsl:analyze-string
For CSV records containing embedded commas inside quotes (e.g.,
104,"Davis, Miller",Manager,Sales), standard tokenization
fails. In XSLT 3.0, xsl:analyze-string or
regular-expression tokenization provides robust field extraction:
<xsl:variable name="regex" select="'"([^"]*)"|([^,]+)|,'"/>Alternatively, csv-to-xml extensions or custom XPath 3.1
recursive functions can parse quoted strings without external
dependencies.
Key Advantages Over XSLT 2.0 Approaches
| Feature | unparsed-text() (XSLT 2.0) |
unparsed-text-lines() (XSLT 3.0) |
|---|---|---|
| Return Type | Single xs:string containing whole file |
Sequence of xs:string* items |
| Memory Footprint | Higher; entire file buffered in memory | Lower; sequence can be processed lazily |
| Line Splitting | Requires manual tokenize(., '\r?\n') |
Built-in automatic normalization |
| Streaming Support | Not streamable | Streamable in compliant XSLT 3.0 processors |
Error Handling and Best Practices
- Verify Resource Existence: Use the companion
function
unparsed-text-available($href)prior to reading files if the URI might be missing or invalid. Callingunparsed-text-lines()on a non-existent file produces dynamic errorFOUT1170. - Filter Empty Rows: Trailing newlines can produce
empty string items at the end of the sequence. Filter items using
predicates like
[normalize-space()]or[string-length() gt 0]. - Explicit Encodings: Always specify the second
parameter (such as
'utf-8'or'windows-1252') when source data contains extended characters, preventing platform-specific decoding errors.