nextcloud/server · error · Exception
{xml_error_string} At line: {line_number}
Error message
{xml_error_string} At line: {line_number} What it means
XmlImporter (the xcal import path of ImportService) streams the source file through PHP's expat parser in 4096-byte chunks and throws when xml_parse() fails, combining xml_error_string() (e.g. 'Mismatched tag', 'Not well-formed') with the line number reported by xml_get_current_line_number(). It means the supplied xCal/XML is not well-formed at that line.
Source
Thrown at apps/dav/lib/CalDAV/Import/XmlImporter.php:64
private function analyze() {
$this->praseLevel = 0;
$this->prasePath = [];
$this->componentStart = null;
$this->componentEnd = null;
$this->componentLevel = 0;
$this->componentId = null;
$this->componentType = null;
$this->componentIdProperty = false;
// Create the parser and assign tag handlers
$parser = xml_parser_create();
xml_set_object($parser, $this);
xml_set_element_handler($parser, $this->tagStart(...), $this->tagEnd(...));
xml_set_default_handler($parser, $this->tagContents(...));
// iterate through the source data chuck by chunk to trigger the handlers
@fseek($this->source, 0);
while ($chunk = fread($this->source, 4096)) {
if (!xml_parse($parser, $chunk, feof($this->source))) {
throw new Exception(
xml_error_string(xml_get_error_code($parser))
. ' At line: '
. xml_get_current_line_number($parser)
);
}
}
//Free up the parser
xml_parser_free($parser);
}
/**
* Handles start of tag events from the parser for all tags
*/
private function tagStart(XMLParser $parser, string $tag, array $attributes): void {
// add the tag to the path tracker and increment depth the level
$this->praseLevel++;
$this->prasePath[$this->praseLevel] = $tag;
// determine if the tag is a component type and remember the byte positionView on GitHub (pinned to ecdeb153ff)
Solutions
- Run xmllint --noout file.xcal (or an equivalent) to get the same line-numbered diagnosis and fix that spot
- Re-export or re-transfer the file if it is truncated (compare byte size/checksum with the source)
- Encode special characters (&, <) as XML entities and make the encoding declaration match the actual file encoding
Example fix
// before $raw = "<icalendar><vcalendar><VEVENT><SUMMARY>R&D</SUMMARY>..."; // bare & breaks XML // after $raw = "<icalendar><vcalendar><VEVENT><SUMMARY>R&D</SUMMARY>..."; // or escape programmatically before writing: $summary = htmlspecialchars($summary, ENT_XML1 | ENT_QUOTES, 'UTF-8');
Defensive patterns
Strategy: validation
Validate before calling
$doc = simplexml_load_file($path);
if ($doc === false) {
foreach (libxml_get_errors() as $err) {
error_log("XML error line {$err->line}: {$err->message}");
}
libxml_clear_errors();
exit(1);
} Type guard
function isWellFormedXml(string $xml): bool {
libxml_use_internal_errors(true);
$ok = simplexml_load_string($xml) !== false;
libxml_clear_errors();
return $ok;
} Try / catch
try {
$service->import($source, $calendar, $options)->current();
} catch (\Exception $e) {
// message carries the expat error string and line number; fix that line and retry
} Prevention
- xmllint --noout every generated xCal file in CI
- Escape dynamic text with htmlspecialchars(..., ENT_XML1)
- Verify file checksums after transfer to catch truncation before parsing
When it happens
Trigger: Calling ImportService::import() with format 'xcal' on a resource whose XML is malformed: unbalanced tags, invalid entities, encoding declaration mismatch, or a truncated upload.
Common situations: Converting iCal to xCal with a buggy script producing raw '&' characters; files truncated by upload limits or interrupted transfers; UTF-16 files declared as UTF-8; chunk boundaries exposing a truncated final chunk.
Related errors
- Error importing calendar data: One or more objects discovere
- Error importing calendar data: One or more objects discovere
- Error importing calendar data: One or more objects discovere
- Error importing calendar data: UID <$uid> - $errorMessage
- An error occurred while checking content
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/6965ec3dddb869f8.
Report an issue: GitHub.