You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
15 KiB
640 lines
15 KiB
7 years ago
|
<?php
|
||
|
/**
|
||
|
* Part of the Fuel framework.
|
||
|
*
|
||
|
* @package Fuel
|
||
|
* @version 1.8
|
||
|
* @author Fuel Development Team
|
||
|
* @license MIT License
|
||
|
* @copyright 2010 - 2016 Fuel Development Team
|
||
|
* @link http://fuelphp.com
|
||
|
*/
|
||
|
|
||
|
namespace Fuel\Core;
|
||
|
|
||
|
/**
|
||
|
* Format class
|
||
|
*
|
||
|
* Help convert between various formats such as XML, JSON, CSV, etc.
|
||
|
*
|
||
|
* @package Fuel
|
||
|
* @category Core
|
||
|
* @author Fuel Development Team
|
||
|
* @copyright 2010 - 2012 Fuel Development Team
|
||
|
* @link http://docs.fuelphp.com/classes/format.html
|
||
|
*/
|
||
|
class Format
|
||
|
{
|
||
|
/**
|
||
|
* Returns an instance of the Format object.
|
||
|
*
|
||
|
* echo Format::forge(array('foo' => 'bar'))->to_xml();
|
||
|
*
|
||
|
* @param mixed $data general date to be converted
|
||
|
* @param string $from_type data format the file was provided in
|
||
|
* @param mixed $param additional parameter that can be passed on to a 'from' method
|
||
|
* @return Format
|
||
|
*/
|
||
|
public static function forge($data = null, $from_type = null, $param = null)
|
||
|
{
|
||
|
return new static($data, $from_type, $param);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @var array|mixed input to convert
|
||
|
*/
|
||
|
protected $_data = array();
|
||
|
|
||
|
/**
|
||
|
* @var bool whether to ignore namespaces when parsing xml
|
||
|
*/
|
||
|
protected $ignore_namespaces = true;
|
||
|
|
||
|
/**
|
||
|
* Do not use this directly, call forge()
|
||
|
*
|
||
|
* @param mixed $data general date to be converted
|
||
|
* @param string $from_type data format the file was provided in
|
||
|
* @param mixed $param additional parameter that can be passed on to a 'from' method
|
||
|
* @throws \FuelException
|
||
|
*/
|
||
|
public function __construct($data = null, $from_type = null, $param = null)
|
||
|
{
|
||
|
// If the provided data is already formatted we should probably convert it to an array
|
||
|
if ($from_type !== null)
|
||
|
{
|
||
|
|
||
|
if ($from_type == 'xml:ns')
|
||
|
{
|
||
|
$this->ignore_namespaces = false;
|
||
|
$from_type = 'xml';
|
||
|
}
|
||
|
|
||
|
if (method_exists($this, '_from_' . $from_type))
|
||
|
{
|
||
|
$data = call_user_func_array(array($this, '_from_' . $from_type), array($data, $param));
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
throw new \FuelException('Format class does not support conversion from "' . $from_type . '".');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$this->_data = $data;
|
||
|
}
|
||
|
|
||
|
// FORMATING OUTPUT ---------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* To array conversion
|
||
|
*
|
||
|
* Goes through the input and makes sure everything is either a scalar value or array
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @return array
|
||
|
*/
|
||
|
public function to_array($data = null)
|
||
|
{
|
||
|
if ($data === null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
$array = array();
|
||
|
|
||
|
if (is_object($data) and ! $data instanceof \Iterator)
|
||
|
{
|
||
|
$data = get_object_vars($data);
|
||
|
}
|
||
|
|
||
|
if (empty($data))
|
||
|
{
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
foreach ($data as $key => $value)
|
||
|
{
|
||
|
if (is_object($value) or is_array($value))
|
||
|
{
|
||
|
$array[$key] = $this->to_array($value);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$array[$key] = $value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $array;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To XML conversion
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @param null $structure
|
||
|
* @param null|string $basenode
|
||
|
* @param null|bool $use_cdata whether to use CDATA in nodes
|
||
|
* @param mixed $bool_representation if true, element values are true/false. if 1, 1/0.
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_xml($data = null, $structure = null, $basenode = null, $use_cdata = null, $bool_representation = null)
|
||
|
{
|
||
|
if ($data == null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
is_null($basenode) and $basenode = \Config::get('format.xml.basenode', 'xml');
|
||
|
is_null($use_cdata) and $use_cdata = \Config::get('format.xml.use_cdata', false);
|
||
|
is_null($bool_representation) and $bool_representation = \Config::get('format.xml.bool_representation', null);
|
||
|
|
||
|
// turn off compatibility mode as simple xml throws a wobbly if you don't.
|
||
|
if (ini_get('zend.ze1_compatibility_mode') == 1)
|
||
|
{
|
||
|
ini_set('zend.ze1_compatibility_mode', 0);
|
||
|
}
|
||
|
|
||
|
if ($structure == null)
|
||
|
{
|
||
|
$structure = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><$basenode />");
|
||
|
}
|
||
|
|
||
|
// Force it to be something useful
|
||
|
if ( ! is_array($data) and ! is_object($data))
|
||
|
{
|
||
|
$data = (array) $data;
|
||
|
}
|
||
|
|
||
|
foreach ($data as $key => $value)
|
||
|
{
|
||
|
// replace anything not alpha numeric
|
||
|
$key = preg_replace('/[^a-z_\-0-9]/i', '', $key);
|
||
|
|
||
|
// no numeric keys in our xml please!
|
||
|
if (is_numeric($key))
|
||
|
{
|
||
|
// make string key...
|
||
|
$key = (\Inflector::singularize($basenode) != $basenode) ? \Inflector::singularize($basenode) : 'item';
|
||
|
}
|
||
|
|
||
|
// if there is another array found recrusively call this function
|
||
|
if (is_array($value) or is_object($value))
|
||
|
{
|
||
|
$node = $structure->addChild($key);
|
||
|
|
||
|
// recursive call if value is not empty
|
||
|
if( ! empty($value))
|
||
|
{
|
||
|
$this->to_xml($value, $node, $key, $use_cdata, $bool_representation);
|
||
|
}
|
||
|
}
|
||
|
elseif ($bool_representation and is_bool($value))
|
||
|
{
|
||
|
if ($bool_representation === true)
|
||
|
{
|
||
|
$bool = $value ? 'true' : 'false';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$bool = $value ? '1' : '0';
|
||
|
}
|
||
|
$structure->addChild($key, $bool);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// add single node.
|
||
|
$encoded = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, "UTF-8");
|
||
|
|
||
|
if ($use_cdata and ($encoded !== (string) $value))
|
||
|
{
|
||
|
$dom = dom_import_simplexml($structure->addChild($key));
|
||
|
$owner = $dom->ownerDocument;
|
||
|
$dom->appendChild($owner->createCDATASection($value));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$structure->addChild($key, $encoded);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// pass back as string. or simple xml object if you want!
|
||
|
return $structure->asXML();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To CSV conversion
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @param mixed $delimiter
|
||
|
* @param mixed $enclose_numbers
|
||
|
* @param array $headings Custom headings to use
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_csv($data = null, $delimiter = null, $enclose_numbers = null, array $headings = array())
|
||
|
{
|
||
|
// csv format settings
|
||
|
$newline = \Config::get('format.csv.newline', \Config::get('format.csv.export.newline', "\n"));
|
||
|
$delimiter or $delimiter = \Config::get('format.csv.delimiter', \Config::get('format.csv.export.delimiter', ','));
|
||
|
$enclosure = \Config::get('format.csv.enclosure', \Config::get('format.csv.export.enclosure', '"'));
|
||
|
$escape = \Config::get('format.csv.escape', \Config::get('format.csv.export.escape', '\\'));
|
||
|
is_null($enclose_numbers) and $enclose_numbers = \Config::get('format.csv.enclose_numbers', true);
|
||
|
|
||
|
// escape, delimit and enclose function
|
||
|
$escaper = function($items, $enclose_numbers) use($enclosure, $escape, $delimiter) {
|
||
|
return implode($delimiter, array_map(function($item) use($enclosure, $escape, $delimiter, $enclose_numbers) {
|
||
|
if ( ! is_numeric($item) or $enclose_numbers)
|
||
|
{
|
||
|
$item = $enclosure.str_replace($enclosure, $escape.$enclosure, $item).$enclosure;
|
||
|
}
|
||
|
return $item;
|
||
|
}, $items));
|
||
|
};
|
||
|
|
||
|
if ($data === null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
if (is_object($data) and ! $data instanceof \Iterator)
|
||
|
{
|
||
|
$data = $this->to_array($data);
|
||
|
}
|
||
|
|
||
|
// Multi-dimensional array
|
||
|
if (empty($headings))
|
||
|
{
|
||
|
if (is_array($data) and \Arr::is_multi($data))
|
||
|
{
|
||
|
$data = array_values($data);
|
||
|
|
||
|
if (\Arr::is_assoc($data[0]))
|
||
|
{
|
||
|
$headings = array_keys($data[0]);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$headings = array_shift($data);
|
||
|
}
|
||
|
}
|
||
|
// Single array
|
||
|
else
|
||
|
{
|
||
|
$headings = array_keys((array) $data);
|
||
|
$data = array($data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$output = $escaper($headings, true).$newline;
|
||
|
|
||
|
foreach ($data as $row)
|
||
|
{
|
||
|
$output .= $escaper($row, $enclose_numbers).$newline;
|
||
|
}
|
||
|
|
||
|
return rtrim($output, $newline);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To JSON conversion
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @param bool $pretty whether to make the json pretty
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_json($data = null, $pretty = false)
|
||
|
{
|
||
|
if ($data === null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
// To allow exporting ArrayAccess objects like Orm\Model instances they need to be
|
||
|
// converted to an array first
|
||
|
$data = (is_array($data) or is_object($data)) ? $this->to_array($data) : $data;
|
||
|
return $pretty ? static::pretty_json($data) : json_encode($data, \Config::get('format.json.encode.options', JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* To JSONP conversion
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @param bool $pretty whether to make the json pretty
|
||
|
* @param string $callback JSONP callback
|
||
|
* @return string formatted JSONP
|
||
|
*/
|
||
|
public function to_jsonp($data = null, $pretty = false, $callback = null)
|
||
|
{
|
||
|
$callback or $callback = \Input::param('callback');
|
||
|
is_null($callback) and $callback = 'response';
|
||
|
|
||
|
return $callback.'('.$this->to_json($data, $pretty).')';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Serialize
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_serialized($data = null)
|
||
|
{
|
||
|
if ($data === null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
return serialize($data);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return as a string representing the PHP structure
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_php($data = null)
|
||
|
{
|
||
|
if ($data === null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
return var_export($data, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convert to YAML
|
||
|
*
|
||
|
* @param mixed $data
|
||
|
* @return string
|
||
|
*/
|
||
|
public function to_yaml($data = null)
|
||
|
{
|
||
|
if ($data == null)
|
||
|
{
|
||
|
$data = $this->_data;
|
||
|
}
|
||
|
|
||
|
if ( ! function_exists('spyc_load'))
|
||
|
{
|
||
|
import('spyc/spyc', 'vendor');
|
||
|
}
|
||
|
|
||
|
return \Spyc::YAMLDump($data);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import XML data
|
||
|
*
|
||
|
* @param string $string
|
||
|
* @param bool $recursive
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function _from_xml($string, $recursive = false)
|
||
|
{
|
||
|
// If it forged with 'xml:ns'
|
||
|
if ( ! $this->ignore_namespaces)
|
||
|
{
|
||
|
static $escape_keys = array();
|
||
|
$recursive or $escape_keys = array('_xmlns' => 'xmlns');
|
||
|
|
||
|
if ( ! $recursive and strpos($string, 'xmlns') !== false and preg_match_all('/(\<.+?\>)/s', $string, $matches))
|
||
|
{
|
||
|
foreach ($matches[1] as $tag)
|
||
|
{
|
||
|
$escaped_tag = $tag;
|
||
|
|
||
|
strpos($tag, 'xmlns=') !== false and $escaped_tag = str_replace('xmlns=', '_xmlns=', $tag);
|
||
|
|
||
|
if (preg_match_all('/[\s\<\/]([^\/\s\'"]*?:\S*?)[=\/\>\s]/s', $escaped_tag, $xmlns))
|
||
|
{
|
||
|
foreach ($xmlns[1] as $ns)
|
||
|
{
|
||
|
$escaped = \Arr::search($escape_keys, $ns);
|
||
|
$escaped or $escape_keys[$escaped = str_replace(':', '_', $ns)] = $ns;
|
||
|
$string = str_replace($tag, $escaped_tag = str_replace($ns, $escaped, $escaped_tag), $string);
|
||
|
$tag = $escaped_tag;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$_arr = is_string($string) ? simplexml_load_string($string, 'SimpleXMLElement', LIBXML_NOCDATA) : $string;
|
||
|
|
||
|
// Convert all objects SimpleXMLElement to array recursively
|
||
|
$arr = array();
|
||
|
foreach ((array) $_arr as $key => $val)
|
||
|
{
|
||
|
$this->ignore_namespaces or $key = \Arr::get($escape_keys, $key, $key);
|
||
|
if ( ! $val instanceOf \SimpleXMLElement or $val->count() or $val->attributes())
|
||
|
{
|
||
|
$arr[$key] = (is_array($val) or is_object($val)) ? $this->_from_xml($val, true) : $val;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$arr[$val->getName()] = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $arr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import YAML data
|
||
|
*
|
||
|
* @param string $string
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function _from_yaml($string)
|
||
|
{
|
||
|
if ( ! function_exists('spyc_load'))
|
||
|
{
|
||
|
import('spyc/spyc', 'vendor');
|
||
|
}
|
||
|
|
||
|
return \Spyc::YAMLLoadString($string);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import CSV data
|
||
|
*
|
||
|
* @param string $string
|
||
|
* @param bool $no_headings
|
||
|
* @return array
|
||
|
*/
|
||
|
protected function _from_csv($string, $no_headings = false)
|
||
|
{
|
||
|
$data = array();
|
||
|
|
||
|
// csv config
|
||
|
$newline = \Config::get('format.csv.regex_newline', "\n");
|
||
|
$delimiter = \Config::get('format.csv.delimiter', \Config::get('format.csv.import.delimiter', ','));
|
||
|
$escape = \Config::get('format.csv.escape', \Config::get('format.csv.import.escape', '"'));
|
||
|
// have to do this in two steps, empty string is a valid value for enclosure!
|
||
|
$enclosure = \Config::get('format.csv.enclosure', \Config::get('format.csv.import.enclosure', null));
|
||
|
$enclosure === null and $enclosure = '"';
|
||
|
|
||
|
if (empty($enclosure))
|
||
|
{
|
||
|
$rows = preg_split('/(['.$newline.'])/m', trim($string), -1, PREG_SPLIT_NO_EMPTY);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$rows = preg_split('/(?<=[0-9'.preg_quote($enclosure).'])'.$newline.'/', trim($string));
|
||
|
}
|
||
|
|
||
|
// Get the headings
|
||
|
if ($no_headings !== false)
|
||
|
{
|
||
|
$headings = str_replace($escape.$enclosure, $enclosure, str_getcsv(array_shift($rows), $delimiter, $enclosure, $escape));
|
||
|
$headcount = count($headings);
|
||
|
}
|
||
|
|
||
|
// Process the rows
|
||
|
$incomplete = '';
|
||
|
foreach ($rows as $row)
|
||
|
{
|
||
|
// process the row
|
||
|
$data_fields = str_replace($escape.$enclosure, $enclosure, str_getcsv($incomplete.($incomplete ? $newline : '').$row, $delimiter, $enclosure, $escape));
|
||
|
|
||
|
// if we didn't have headers, the first row determines the number of fields
|
||
|
if ( ! isset($headcount))
|
||
|
{
|
||
|
$headcount = count($data_fields);
|
||
|
}
|
||
|
|
||
|
// finish the row if the have the correct field count, otherwise add the data to the next row
|
||
|
if (count($data_fields) == $headcount)
|
||
|
{
|
||
|
$data[] = $no_headings === false ? $data_fields : array_combine($headings, $data_fields);
|
||
|
$incomplete = '';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$incomplete = $row;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import JSON data
|
||
|
*
|
||
|
* @param string $string
|
||
|
* @return mixed
|
||
|
*/
|
||
|
private function _from_json($string)
|
||
|
{
|
||
|
return json_decode(trim($string));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Import Serialized data
|
||
|
*
|
||
|
* @param string $string
|
||
|
* @return mixed
|
||
|
*/
|
||
|
private function _from_serialize($string)
|
||
|
{
|
||
|
return unserialize(trim($string));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Makes json pretty the json output.
|
||
|
* Borrowed from http://www.php.net/manual/en/function.json-encode.php#80339
|
||
|
*
|
||
|
* @param string $data json encoded array
|
||
|
* @return string|false pretty json output or false when the input was not valid
|
||
|
*/
|
||
|
protected static function pretty_json($data)
|
||
|
{
|
||
|
$json = json_encode($data, \Config::get('format.json.encode.options', JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP));
|
||
|
|
||
|
if ( ! $json)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$tab = "\t";
|
||
|
$newline = "\n";
|
||
|
$new_json = "";
|
||
|
$indent_level = 0;
|
||
|
$in_string = false;
|
||
|
$len = strlen($json);
|
||
|
|
||
|
for ($c = 0; $c < $len; $c++)
|
||
|
{
|
||
|
$char = $json[$c];
|
||
|
switch($char)
|
||
|
{
|
||
|
case '{':
|
||
|
case '[':
|
||
|
if ( ! $in_string)
|
||
|
{
|
||
|
$new_json .= $char.$newline.str_repeat($tab, $indent_level+1);
|
||
|
$indent_level++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$new_json .= $char;
|
||
|
}
|
||
|
break;
|
||
|
case '}':
|
||
|
case ']':
|
||
|
if ( ! $in_string)
|
||
|
{
|
||
|
$indent_level--;
|
||
|
$new_json .= $newline.str_repeat($tab, $indent_level).$char;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$new_json .= $char;
|
||
|
}
|
||
|
break;
|
||
|
case ',':
|
||
|
if ( ! $in_string)
|
||
|
{
|
||
|
$new_json .= ','.$newline.str_repeat($tab, $indent_level);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$new_json .= $char;
|
||
|
}
|
||
|
break;
|
||
|
case ':':
|
||
|
if ( ! $in_string)
|
||
|
{
|
||
|
$new_json .= ': ';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$new_json .= $char;
|
||
|
}
|
||
|
break;
|
||
|
case '"':
|
||
|
if ($c > 0 and $json[$c-1] !== '\\')
|
||
|
{
|
||
|
$in_string = ! $in_string;
|
||
|
}
|
||
|
default:
|
||
|
$new_json .= $char;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $new_json;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads Format config.
|
||
|
*/
|
||
|
public static function _init()
|
||
|
{
|
||
|
\Config::load('format', true);
|
||
|
}
|
||
|
}
|