XML Feed Normalizer
Parses XML feed items into a normalized array while handling namespaces and missing fields.
Function signature
ogNormalizeXmlFeedItems(xml, field_map = array(), options = array())
Categories
- Import and Export
Parameters
xmlXML document content.field_mapOutput field names mapped to XML paths such as title, link, guid, or media:content.url.optionsParsing options including max_bytes and item_paths. Recognized keys: `item_paths`, `max_bytes`.Return value
Short public-safe status message.
- items
- item_count
Compatibility
Existing function name and call order preserved; metadata signature corrected to source.
Minimum PHP version: 7.4
Security notes
Validate request method, identity, permissions, policy arrays, URLs, signatures, and caller-owned allowlists before use; keep secrets and internal paths out of public output.
Code
<?php
/*
* Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
* Free for personal and internal use. Paid project use requires visible credit
* to Jeffery L. Paris. Corporate use requires a paid license fee unless a
* separate written license states otherwise.
*/
/**
* Parses XML feed items into a normalized array while handling namespaces and missing fields.
*
* This helper validates XML size, disables network access during parsing, extracts feed items,
* and maps configured XML paths into plain PHP arrays. It does not fetch remote URLs.
*
* @param string $xml XML document content.
* @param array $field_map Output field names mapped to XML paths such as title, link, guid, or media:content.url.
* @param array $options Parsing options including max_bytes and item_paths.
* @return array Normalized feed item rows and warnings.
*/
function ogNormalizeXmlFeedItems($xml, $field_map = array(), $options = array()) {
$result = array(
'success' => false,
'message' => '',
'data' => array()
);
$xml = trim((string)$xml);
if (empty($xml)) {
$result['message'] = 'XML content is required.';
return $result;
}
if (!is_array($field_map)) {
$field_map = array();
}
if (!is_array($options)) {
$options = array();
}
$max_bytes = 1048576;
if (!empty($options['max_bytes'])) {
$max_bytes = (int)$options['max_bytes'];
}
if ($max_bytes < 1024) {
$max_bytes = 1024;
}
if (strlen($xml) > $max_bytes) {
$result['message'] = 'XML content is larger than the allowed limit.';
return $result;
}
$previous = libxml_use_internal_errors(true);
$feed = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA);
$errors = libxml_get_errors();
libxml_clear_errors();
libxml_use_internal_errors($previous);
if ($feed === false) {
$error_messages = array();
foreach ($errors as $error) {
$error_messages[] = trim($error->message);
}
$result['message'] = 'XML content could not be parsed.';
$result['data'] = array('errors' => $error_messages);
return $result;
}
if (empty($field_map)) {
$field_map = array(
'title' => 'title',
'link' => 'link',
'guid' => 'guid',
'description' => 'description',
'pub_date' => 'pubDate'
);
}
$item_paths = array('//item', '//entry');
if (!empty($options['item_paths']) && is_array($options['item_paths'])) {
$item_paths = $options['item_paths'];
}
$items = array();
foreach ($item_paths as $item_path) {
$item_path = (string)$item_path;
$found = $feed->xpath($item_path);
if (!empty($found)) {
foreach ($found as $node) {
$row = array();
foreach ($field_map as $field_name => $xml_path) {
$field_name = preg_replace('/[^a-zA-Z0-9_]/', '', (string)$field_name);
if (empty($field_name)) {
continue;
}
$xml_path = (string)$xml_path;
$value = '';
if (strpos($xml_path, '@') === 0) {
$attribute = substr($xml_path, 1);
$attributes = $node->attributes();
if (!empty($attributes[$attribute])) {
$value = (string)$attributes[$attribute];
}
} else {
$child = $node->xpath($xml_path);
if (!empty($child[0])) {
$value = (string)$child[0];
}
}
$row[$field_name] = trim($value);
}
$items[] = $row;
}
}
}
$result['success'] = true;
$result['message'] = 'XML feed items normalized.';
$result['data'] = array(
'items' => $items,
'item_count' => count($items)
);
return $result;
}