Skip to content

Instantly share code, notes, and snippets.

@lokielse
Created November 1, 2013 17:28
Show Gist options
  • Save lokielse/7268829 to your computer and use it in GitHub Desktop.
Save lokielse/7268829 to your computer and use it in GitHub Desktop.
php convert array to plist
<?PHP
/**
* PropertyList class
* Implements writing Apple Property List (.plist) XML and text files from an array.
*
* @author Jesus A. Alvarez <zydeco@namedfork.net>
*/
//====================================================demo
$json = '{"安徽":["合肥","安庆","蚌埠","亳州","巢湖","滁州","阜阳","贵池","淮北","淮化","淮南","黄山","九华山","六安","马鞍山","宿州","铜陵","屯溪","芜湖","宣城"],"北京":["东城","西城","崇文","宣武","朝阳","丰台","石景山","海淀","门头沟","房山","通州","顺义","昌平","大兴","平谷","怀柔","密云","延庆"],"重庆":["万州","涪陵","渝中","大渡口","江北","沙坪坝","九龙坡","南岸","北碚","万盛","双挢","渝北","巴南","黔江","长寿","綦江","潼南","铜梁","大足","荣昌","壁山","梁平","城口","丰都","垫江","武隆","忠县","开县","云阳","奉节","巫山","巫溪","石柱","秀山","酉阳","彭水","江津","合川","永川","南川"],"福建":["福州","福安","龙岩","南平","宁德","莆田","泉州","三明","邵武","石狮","晋江","永安","武夷山","厦门","漳州"],"甘肃":["兰州","白银","定西","敦煌","甘南","金昌","酒泉","临夏","平凉","天水","武都","武威","西峰","嘉峪关","张掖"],"广东":["广州","潮阳","潮州","澄海","东莞","佛山","河源","惠州","江门","揭阳","开平","茂名","梅州","清远","汕头","汕尾","韶关","深圳","顺德","阳江","英德","云浮","增城","湛江","肇庆","中山","珠海"],"广西":["南宁","百色","北海","桂林","防城港","河池","贺州","柳州","来宾","钦州","梧州","贵港","玉林"],"贵州":["贵阳","安顺","毕节","都匀","凯里","六盘水","铜仁","兴义","玉屏","遵义"],"海南":["海口","三亚","五指山","琼海","儋州","文昌","万宁","东方","定安","屯昌","澄迈","临高","万宁","白沙黎族","昌江黎族","乐东黎族","陵水黎族","保亭黎族","琼中黎族","西沙群岛","南沙群岛","中沙群岛"],"河北":["石家庄","保定","北戴河","沧州","承德","丰润","邯郸","衡水","廊坊","南戴河","秦皇岛","唐山","新城","邢台","张家口"],"黑龙江":["哈尔滨","北安","大庆","大兴安岭","鹤岗","黑河","佳木斯","鸡西","牡丹江","齐齐哈尔","七台河","双鸭山","绥化","伊春"],"河南":["郑州","安阳","鹤壁","潢川","焦作","济源","开封","漯河","洛阳","南阳","平顶山","濮阳","三门峡","商丘","新乡","信阳","许昌","周口","驻马店"],"香港":["香港","九龙","新界"],"湖北":["武汉","恩施","鄂州","黄冈","黄石","荆门","荆州","潜江","十堰","随州","武穴","仙桃","咸宁","襄阳","襄樊","孝感","宜昌"],"湖南":["长沙","常德","郴州","衡阳","怀化","吉首","娄底","邵阳","湘潭","益阳","岳阳","永州","张家界","株洲"],"江苏":["南京","常熟","常州","海门","淮安","江都","江阴","昆山","连云港","南通","启东","沭阳","宿迁","苏州","太仓","泰州","同里","无锡","徐州","盐城","扬州","宜兴","仪征","张家港","镇江","周庄"],"江西":["南昌","抚州","赣州","吉安","景德镇","井冈山","九江","庐山","萍乡","上饶","新余","宜春","鹰潭"],"吉林":["长春","白城","白山","珲春","辽源","梅河","吉林","四平","松原","通化","延吉"],"辽宁":["沈阳","鞍山","本溪","朝阳","大连","丹东","抚顺","阜新","葫芦岛","锦州","辽阳","盘锦","铁岭","营口"],"澳门":["澳门"],"内蒙古":["阿拉善盟","包头","赤峰","东胜","海拉尔","集宁","临河","通辽","乌海","乌兰浩特","锡林浩特"],"宁夏":["银川","固原","中卫","石嘴山","吴忠"],"青海":["西宁","德令哈","格尔木","共和","海东","海晏","玛沁","同仁","玉树"],"山东":["济南","滨州","兖州","德州","东营","菏泽","济宁","莱芜","聊城","临沂","蓬莱","青岛","曲阜","日照","泰安","潍坊","威海","烟台","枣庄","淄博"],"上海":["崇明","黄浦","卢湾","徐汇","长宁","静安","普陀","闸北","虹口","杨浦","闵行","宝山","嘉定","浦东","金山","松江","青浦","南汇","奉贤","朱家角"],"山西":["太原","长治","大同","候马","晋城","离石","临汾","宁武","朔州","忻州","阳泉","榆次","运城"],"陕西":["西安","安康","宝鸡","汉中","渭南","商州","绥德","铜川","咸阳","延安","榆林"],"四川":["成都","巴中","达州","德阳","都江堰","峨眉山","涪陵","广安","广元","九寨沟","康定","乐山","泸州","马尔康","绵阳","眉山","南充","内江","攀枝花","遂宁","汶川","西昌","雅安","宜宾","自贡","资阳"],"台湾":["台北","基隆","台南","台中","高雄","屏东","南投","云林","新竹","彰化","苗栗","嘉义","花莲","桃园","宜兰","台东","金门","马祖","澎湖","其它"],"天津":["天津","和平","东丽","河东","西青","河西","津南","南开","北辰","河北","武清","红挢","塘沽","汉沽","大港","宁河","静海","宝坻","蓟县"],"新疆":["阿克苏","阿勒泰","阿图什","博乐","昌吉","东山","哈密","和田","喀什","克拉玛依","库车","库尔勒","奎屯","石河子","塔城","吐鲁番","伊宁"],"西藏":["拉萨","阿里","昌都","林芝","那曲","日喀则","山南"],"云南":["昆明","大理","保山","楚雄","大理","东川","个旧","景洪","开远","临沧","丽江","六库","潞西","曲靖","思茅","文山","西双版纳","玉溪","中甸","昭通"],"浙江":["杭州","安吉","慈溪","定海","奉化","海盐","黄岩","湖州","嘉兴","金华","临安","临海","丽水","宁波","瓯海","平湖","千岛湖","衢州","江山","瑞安","绍兴","嵊州","台州","温岭","温州","余姚","舟山"],"海外":["美国","英国","法国","瑞士","澳洲","新西兰","加拿大","奥地利","韩国","日本","德国","意大利","西班牙","俄罗斯","泰国","印度","荷兰","新加坡","欧洲","北美","南美","亚洲","非洲","大洋洲"]}';
$obj = (array)json_decode($json);
$plist = plist_encode_xml($obj);
print_r($plist) ;
//====================================================demo
function plist_encode_text ($obj) {
$plist = new PropertyList($obj);
return $plist->text();
}
function plist_encode_xml ($obj) {
$plist = new PropertyList($obj);
return $plist->xml();
}
class PropertyList
{
private $obj, $xml, $text;
public function __construct ($obj) {
$this->obj = $obj;
}
private static function is_assoc ($array) {
return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array)))));
}
public function xml () {
if (isset($this->xml)) return $this->xml;
$x = new XMLWriter();
$x->openMemory();
$x->setIndent(TRUE);
$x->startDocument('1.0', 'UTF-8');
$x->writeDTD('plist', '-//Apple//DTD PLIST 1.0//EN', 'http://www.apple.com/DTDs/PropertyList-1.0.dtd');
$x->startElement('plist');
$x->writeAttribute('version', '1.0');
$this->xmlWriteValue($x, $this->obj);
$x->endElement(); // plist
$x->endDocument();
$this->xml = $x->outputMemory();
return $this->xml;
}
public function text() {
if (isset($this->text)) return $this->text;
$text = '';
$this->textWriteValue($text, $this->obj);
$this->text = $text;
return $this->text;
}
private function xmlWriteDict(XMLWriter $x, &$dict) {
$x->startElement('dict');
foreach($dict as $k => &$v) {
$x->writeElement('key', $k);
$this->xmlWriteValue($x, $v);
}
$x->endElement(); // dict
}
private function xmlWriteArray(XMLWriter $x, &$arr) {
$x->startElement('array');
foreach($arr as &$v)
$this->xmlWriteValue($x, $v);
$x->endElement(); // array
}
private function xmlWriteValue(XMLWriter $x, &$v) {
if (is_int($v) || is_long($v))
$x->writeElement('integer', $v);
elseif (is_float($v) || is_real($v) || is_double($v))
$x->writeElement('real', $v);
elseif (is_string($v))
$x->writeElement('string', $v);
elseif (is_bool($v))
$x->writeElement($v?'true':'false');
elseif (PropertyList::is_assoc($v))
$this->xmlWriteDict($x, $v);
elseif (is_array($v))
$this->xmlWriteArray($x, $v);
elseif (is_a($v, 'PlistData'))
$x->writeElement('data', $v->base64EncodedData());
elseif (is_a($v, 'PlistDate'))
$x->writeElement('date', $v->encodedDate());
else {
trigger_error("Unsupported data type in plist ($v)", E_USER_WARNING);
$x->writeElement('string', $v);
}
}
private function textWriteValue(&$text, &$v, $indentLevel = 0) {
if (is_int($v) || is_long($v))
$text .= sprintf("%d", $v);
elseif (is_float($v) || is_real($v) || is_double($v))
$text .= sprintf("%g", $v);
elseif (is_string($v))
$this->textWriteString($text, $v);
elseif (is_bool($v))
$text .= $v?'YES':'NO';
elseif (PropertyList::is_assoc($v))
$this->textWriteDict($text, $v, $indentLevel);
elseif (is_array($v))
$this->textWriteArray($text, $v, $indentLevel);
elseif (is_a($v, 'PlistData'))
$text .= '<' . $v->hexEncodedData() . '>';
elseif (is_a($v, 'PlistDate'))
$text .= '"' . $v->ISO8601Date() . '"';
else {
trigger_error("Unsupported data type in plist ($v)", E_USER_WARNING);
$this->textWriteString($text, $v);
}
}
private function textWriteString(&$text, &$str) {
$oldlocale = setlocale(LC_CTYPE, "0");
if (ctype_alnum($str)) $text .= $str;
else $text .= '"' . $this->textEncodeString($str) . '"';
setlocale(LC_CTYPE, $oldlocale);
}
private function textEncodeString(&$str) {
$newstr = '';
$i = 0;
$len = strlen($str);
while($i < $len) {
$ch = ord(substr($str, $i, 1));
if ($ch == 0x22 || $ch == 0x5C) {
// escape double quote, backslash
$newstr .= '\\' . chr($ch);
$i++;
} else if ($ch >= 0x07 && $ch <= 0x0D ){
// control characters with escape sequences
$newstr .= '\\' . substr('abtnvfr', $ch - 7, 1);
$i++;
} else if ($ch < 32) {
// other non-printable characters escaped as unicode
$newstr .= sprintf('\U%04x', $ch);
$i++;
} else if ($ch < 128) {
// ascii printable
$newstr .= chr($ch);
$i++;
} else if ($ch == 192 || $ch == 193) {
// invalid encoding of ASCII characters
$i++;
} else if (($ch & 0xC0) == 0x80){
// part of a lost multibyte sequence, skip
$i++;
} else if (($ch & 0xE0) == 0xC0) {
// U+0080 - U+07FF (2 bytes)
$u = (($ch & 0x1F) << 6) | (ord(substr($str, $i+1, 1)) & 0x3F);
$newstr .= sprintf('\U%04x', $u);
$i += 2;
} else if (($ch & 0xF0) == 0xE0) {
// U+0800 - U+FFFF (3 bytes)
$u = (($ch & 0x0F) << 12) | ((ord(substr($str, $i+1, 1)) & 0x3F) << 6) | (ord(substr($str, $i+2, 1)) & 0x3F);
$newstr .= sprintf('\U%04x', $u);
$i += 3;
} else if (($ch & 0xF8) == 0xF0) {
// U+10000 - U+3FFFF (4 bytes)
$u = (($ch & 0x07) << 18) | ((ord(substr($str, $i+1, 1)) & 0x3F) << 12) | ((ord(substr($str, $i+2, 1)) & 0x3F) << 6) | (ord(substr($str, $i+3, 1)) & 0x3F);
$newstr .= sprintf('\U%04x', $u);
$i += 4;
} else {
// 5 and 6 byte sequences are not valid UTF-8
$i++;
}
}
return $newstr;
}
private function textWriteDict(&$text, &$dict, $indentLevel) {
if (count($dict) == 0) {
$text .= '{}';
return;
}
$text .= "{\n";
$indent = '';
$indentLevel++;
while(strlen($indent) < $indentLevel) $indent .= "\t";
foreach($dict as $k => &$v) {
$text .= $indent;
$this->textWriteValue($text, $k);
$text .= ' = ';
$this->textWriteValue($text, $v, $indentLevel);
$text .= ";\n";
}
$text .= substr($indent, 0, -1) . '}';
}
private function textWriteArray(&$text, &$arr, $indentLevel) {
if (count($arr) == 0) {
$text .= '()';
return;
}
$text .= "(\n";
$indent = '';
$indentLevel++;
while(strlen($indent) < $indentLevel) $indent .= "\t";
foreach($arr as &$v) {
$text .= $indent;
$this->textWriteValue($text, $v, $indentLevel);
$text .= ",\n";
}
$text .= substr($indent, 0, -1) . ')';
}
}
class PlistData
{
private $data;
public function __construct($str) {
$this->data = $str;
}
public function base64EncodedData () {
return base64_encode($this->data);
}
public function hexEncodedData () {
$len = strlen($this->data);
$hexstr = '';
for($i = 0; $i < $len; $i += 4)
$hexstr .= bin2hex(substr($this->data, $i, 4)) . ' ';
return substr($hexstr, 0, -1);
}
}
class PlistDate
{
private $dateval;
public function __construct($init = NULL) {
if (is_int($init))
$this->dateval = $init;
elseif (is_string($init))
$this->dateval = strtotime($init);
elseif ($init == NULL)
$this->dateval = time();
}
public function ISO8601Date() {
return gmdate('Y-m-d\TH:i:s\Z', $this->dateval);
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment