xml_set_character_data_handler()
The xml_set_character_data_handler() function is a PHP built-in function that sets a user-defined function as the handler for character data in an XML parser.
The xml_set_character_data_handler() function registers a user-defined callback that the XML parser calls every time it encounters character data — the text content that sits between XML tags. It belongs to the legacy xml extension, which wraps the event-driven Expat parser, so it is part of PHP's stream-based ("SAX-style") parsing API rather than the tree-based DOMDocument or SimpleXML approaches.
You reach for this function whenever you want to capture or transform the text inside elements as the document streams past — for example, collecting the title of each <item> in an RSS feed, or running a transformation on extracted text without ever building the whole document in memory. It is almost always used together with xml_set_element_handler(), which tells you which element the text belongs to.
Heads up: The
xmlextension is legacy. For brand-new code, preferSimpleXMLorDOMDocument. Use this handler when you are maintaining existing Expat-based parsers or genuinely need event-driven streaming for very large documents.
Syntax
xml_set_character_data_handler(XMLParser $parser, callable $handler): bool$parser— the parser created byxml_parser_create().$handler— the callback to invoke for character data. Pass a function name as a string, a closure, or an array for a method callback. Passing an empty string""unsets a previously registered handler.
The function returns true on success.
The handler signature
Your handler receives two arguments — the parser and the text chunk:
function handler(XMLParser $parser, string $data): voidOlder documentation sometimes shows a third $length parameter, but the Expat-based xml extension does not pass one. On PHP 8+, declaring a third required parameter throws an ArgumentCountError, so keep your handler to two parameters (or make extra ones optional).
Usage Examples
Example 1: A minimal character data handler
Create a parser, attach a handler, then feed it an XML string. The handler simply echoes the trimmed text it receives:
function handle_character_data($parser, $data) {
echo trim($data);
}
$xml_parser = xml_parser_create();
xml_set_character_data_handler($xml_parser, "handle_character_data");
$xml_data = '<root><item>Hello World</item></root>';
xml_parse($xml_parser, $xml_data, true);
xml_parser_free($xml_parser);
// Output: Hello Worldxml_parser_create() builds the parser, xml_set_character_data_handler() wires up the callback, xml_parse() streams the data (the final true marks it as the last chunk), and xml_parser_free() releases the parser.
The chunking gotcha
The parser can call your handler multiple times for a single element, and it also fires for the whitespace between elements. It does not hand you one tidy string per element. Watch what happens with two items and some indentation:
function show($parser, $data) {
echo '[' . $data . "]\n";
}
$p = xml_parser_create();
xml_set_character_data_handler($p, "show");
$xml = '<root><item>Hello World</item> <item>Goodbye</item></root>';
xml_parse($p, $xml, true);
xml_parser_free($p);
// Output:
// [Hello World]
// [ ]
// [Goodbye]The two-space gap between the </item> and the next <item> triggers its own call. Long text can also arrive split across several calls. Because of this, you should buffer the data and only act on it once you know the element has ended.
Example 2: Buffering text with an element handler
The reliable pattern is: reset a buffer when an element starts, append every character-data chunk to it, and read the buffer when the element ends. That is why xml_set_element_handler() is almost always paired with this function.
$buffer = '';
function start_element($parser, $name, $attrs) {
global $buffer;
$buffer = ''; // start collecting fresh text
}
function character_data($parser, $data) {
global $buffer;
$buffer .= $data; // chunks may arrive in pieces — append
}
function end_element($parser, $name) {
global $buffer;
$text = trim($buffer);
if ($text !== '') {
echo "$name: $text\n";
}
}
$parser = xml_parser_create();
xml_set_element_handler($parser, "start_element", "end_element");
xml_set_character_data_handler($parser, "character_data");
$xml = '<books><title>PHP Basics</title><title>Advanced XML</title></books>';
xml_parse($parser, $xml, true);
xml_parser_free($parser);
// Output:
// TITLE: PHP Basics
// TITLE: Advanced XMLElement names arrive upper-cased by default because the parser folds case. To keep the original casing, disable case folding with xml_parser_set_option():
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);Common pitfalls
- Assuming one call per element. As shown above, character data can be delivered in several chunks; always accumulate into a buffer.
- Whitespace-only calls. Indentation and newlines between tags trigger the handler with whitespace. Filter these out with
trim()before acting. - A third handler parameter. The Expat handler passes only
$parserand$data; a required$lengthparameter causes anArgumentCountError. - Registering the handler after parsing. Attach all handlers before you call
xml_parse(), or events will already have passed.
Conclusion
xml_set_character_data_handler() registers a callback for the text between XML tags in PHP's legacy, Expat-based xml extension. Its handler takes two arguments — the parser and the data chunk — and may fire many times per element, including for whitespace, so the dependable approach is to buffer text and process it when the element ends via xml_set_element_handler(). For new projects, prefer the modern SimpleXML or DOMDocument APIs; reach for this function when maintaining existing parsers or streaming very large documents.