Data formats

Property lists

Apple property lists, XML and binary: Info.plist, profiles and preferences read as values, and values written back as either.

Version 1.0.0 · 5 functions · 2 crates

#apple#ios#config#import

Functions

What use plist.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use plist_parse, plist_decode from plist (Quillo 0.6).

plist_parse(xml: str) → anyor an error

An XML property list's text as a value: dictionaries as maps, <data> as base64 text, <date> as 2026-11-02T09:00:00Z.

plist_decode(base64_text: str) → anyor an error

A property list given as base64, binary (bplist00) or XML, as a value; as plist_parse gives it.

plist_read_file(path: str) → anyor an error

A .plist file, binary or XML, as a value.

plist_to_xml(value: any) → stror an error

value as an XML property list (with its <?xml and <!DOCTYPE plist lines), as Xcode and profile tools write it. Keys holding null are left out; ints are <integer>, floats <real>.

plist_to_binary(value: any) → stror an error

value as a binary property list (bplist00), in base64.

Get it

$ quillo install plist               # plist.qwer into this folder$ quillo install plist --example     # and its example, plist.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/plist.qwer$ curl -O https://quillolang.org/plugins/files/plist.qwe$ curl -O https://quillolang.org/plugins/files/plist_sample.plist

Then, in your program, all of it or only what you call:

main.qwe
use plist.qweruse plist_parse, plist_decode from plist      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
plist = "1"base64 = "0.22"

Example

plist.qwe
// An app-release dashboard reading an iOS app's Info.plist, and a binary plist made and read back.////   quillo run plist.qwe      then open http://localhost:8080/appuse "plist.qwer"route GET /app {    let info = plist_read_file("plist_sample.plist")?    { name: info.CFBundleDisplayName, bundle: info.CFBundleIdentifier, version: `${info.CFBundleShortVersionString} (${info.CFBundleVersion})`, built: info.BuildDate, icon_hash: info.IconHash, orientations: len(info.UISupportedInterfaceOrientations) }}route GET /binary {    let packed = plist_to_binary({ server: "https://shop.example.com", retries: 3, timeout: 2.5, beta: true, unset: null })?    { packed, binary: starts_with(packed, "YnBsaXN0"), back: plist_decode(packed)? }}route GET /profile.plist = respond(200, plist_to_xml({ PayloadDisplayName: "Quillo Shop Wi-Fi", PayloadType: "Configuration", PayloadVersion: 1, PayloadContent: [{ SSID_STR: "QuilloGuest", EncryptionType: "WPA2" }] })?, { "content-type": "application/xml" })
$ quillo run plist.qwe$ curl 'localhost:8080/app'$ curl 'localhost:8080/binary'$ curl 'localhost:8080/profile.plist'

Source

plist.qwer
//! Apple property lists (.plist), XML and binary: Info.plist, .mobileconfig profiles, macOS preferences and iOS exports read as values, and values written back as either.//! version: 1.0.0//!//! [dependencies]//! plist = "1"//! base64 = "0.22"use base64::engine::general_purpose::STANDARD;use base64::Engine;use plist::Value as P;use serde_json::{Map, Value};/// Property lists up to 50 MB are read.const MAX: usize = 50 * 1024 * 1024;fn to_json(v: P) -> Value {    match v {        P::Array(a) => Value::Array(a.into_iter().map(to_json).collect()),        P::Dictionary(d) => Value::Object(d.into_iter().map(|(k, v)| (k, to_json(v))).collect::<Map<_, _>>()),        P::Boolean(b) => Value::Bool(b),        // Data has no JSON form: it comes back as base64 text.        P::Data(d) => Value::String(STANDARD.encode(d)),        P::Date(d) => Value::String(d.to_xml_format()),        P::Real(f) => serde_json::Number::from_f64(f).map(Value::Number).unwrap_or(Value::Null),        P::Integer(i) => i.as_signed().map(Value::from).or_else(|| i.as_unsigned().map(Value::from)).unwrap_or(Value::Null),        P::String(s) => Value::String(s),        P::Uid(u) => Value::from(u.get()),        _ => Value::Null,    }}fn from_json(v: &Value) -> Option<P> {    Some(match v {        // A property list has no null: keys holding null are left out.        Value::Null => return None,        Value::Bool(b) => P::Boolean(*b),        Value::Number(n) => match n.as_i64() {            Some(i) => P::Integer(i.into()),            None => P::Real(n.as_f64().unwrap_or(0.0)),        },        Value::String(s) => P::String(s.clone()),        Value::Array(a) => P::Array(a.iter().filter_map(from_json).collect()),        Value::Object(m) => P::Dictionary(m.iter().filter_map(|(k, v)| from_json(v).map(|p| (k.clone(), p))).collect()),    })}fn decode(bytes: &[u8]) -> Result<Value, String> {    if bytes.len() > MAX {        return Err("property lists up to 50 MB".into());    }    P::from_reader(std::io::Cursor::new(bytes)).map(to_json).map_err(|e| format!("not a property list: {}", e))}/// An XML property list's text as a value: dictionaries as maps, `<data>` as base64 text, `<date>` as `2026-11-02T09:00:00Z`.pub fn plist_parse(xml: &str) -> Result<Value, String> {    decode(xml.as_bytes())}/// A property list given as base64, binary (`bplist00`) or XML, as a value; as plist_parse gives it.pub fn plist_decode(base64_text: &str) -> Result<Value, String> {    decode(&STANDARD.decode(base64_text.trim()).map_err(|_| "not base64".to_string())?)}/// A .plist file, binary or XML, as a value.pub fn plist_read_file(path: &str) -> Result<Value, String> {    let size = std::fs::metadata(path).map_err(|e| format!("{}: {}", path, e))?.len();    if size as usize > MAX {        return Err(format!("{}: property lists up to 50 MB", path));    }    decode(&std::fs::read(path).map_err(|e| format!("{}: {}", path, e))?)}/// `value` as an XML property list (with its `<?xml` and `<!DOCTYPE plist` lines), as Xcode and profile tools write it./// Keys holding null are left out; ints are `<integer>`, floats `<real>`.pub fn plist_to_xml(value: serde_json::Value) -> Result<String, String> {    let p = from_json(&value).ok_or("a property list cannot be null")?;    let mut out = Vec::new();    p.to_writer_xml(&mut out).map_err(|e| e.to_string())?;    String::from_utf8(out).map_err(|e| e.to_string())}/// `value` as a binary property list (`bplist00`), in base64.pub fn plist_to_binary(value: serde_json::Value) -> Result<String, String> {    let p = from_json(&value).ok_or("a property list cannot be null")?;    let mut out = Vec::new();    p.to_writer_binary(&mut out).map_err(|e| e.to_string())?;    Ok(STANDARD.encode(out))}
Thread
69Useful? Like it, or add to its thread below.

Thread 0 posts

On Property lists: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.