summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.github/FUNDING.yml3
-rw-r--r--.github/workflows/build.yml166
-rw-r--r--.github/workflows/cifuzz.yml26
-rw-r--r--.github/workflows/codeql-analysis.yml53
-rw-r--r--.gitignore74
-rw-r--r--AUTHORS15
-rw-r--r--CMakeLists.txt81
-rw-r--r--Makefile.am32
-rw-r--r--NEWS213
-rw-r--r--README47
-rw-r--r--README.md283
-rwxr-xr-xautogen.sh26
-rw-r--r--cmake/libplistPackaging.cmake15
-rw-r--r--cmake/modules/FindCython.cmake10
-rw-r--r--cmake/modules/FindGLIB2.cmake51
-rw-r--r--cmake/modules/FindLibXml2.cmake57
-rw-r--r--cmake/modules/cmake_uninstall.cmake.in21
-rw-r--r--configure.ac336
-rw-r--r--cython/CMakeLists.txt30
-rw-r--r--cython/Makefile.am57
-rw-r--r--cython/plist.pxd20
-rw-r--r--cython/plist.pyx371
-rw-r--r--cython/plist_util.c48
-rw-r--r--cython/plist_util.h4
-rw-r--r--docs/Makefile.am3
-rw-r--r--docs/doxygen/custom.css1723
-rw-r--r--docs/doxygen/favicon.icobin0 -> 1150 bytes
-rw-r--r--docs/doxygen/folder-open.pngbin0 -> 625 bytes
-rw-r--r--docs/doxygen/folder.pngbin0 -> 498 bytes
-rw-r--r--docs/doxygen/footer.html9
-rw-r--r--docs/doxygen/header.html64
-rw-r--r--docs/doxygen/layout.xml226
-rw-r--r--docs/doxygen/logo-vector-clean.svg1
-rw-r--r--docs/doxygen/text-x-generic.pngbin0 -> 333 bytes
-rw-r--r--docs/plistutil.1135
-rw-r--r--doxygen.cfg1418
-rw-r--r--doxygen.cfg.in2806
-rw-r--r--fuzz/Makefile.am55
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704bin0 -> 79 bytes
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344bin0 -> 123 bytes
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816bin0 -> 77 bytes
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696bin0 -> 140 bytes
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792bin0 -> 1376 bytes
-rw-r--r--fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504bin0 -> 194 bytes
-rw-r--r--fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840bin0 -> 77 bytes
-rw-r--r--fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800bin0 -> 156 bytes
-rw-r--r--fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5bin0 -> 52 bytes
-rw-r--r--fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28bin0 -> 77 bytes
-rw-r--r--fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7bbin0 -> 50 bytes
-rw-r--r--fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210bin0 -> 120 bytes
-rw-r--r--fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392bin0 -> 77 bytes
-rw-r--r--fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032bin0 -> 103 bytes
-rw-r--r--fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fabin0 -> 62 bytes
-rw-r--r--fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9bin0 -> 38 bytes
-rw-r--r--fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5afbin0 -> 77 bytes
-rw-r--r--fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966ebin0 -> 54 bytes
-rw-r--r--fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131bin0 -> 54 bytes
-rw-r--r--fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21bin0 -> 102 bytes
-rw-r--r--fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2bin0 -> 391 bytes
-rw-r--r--fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dacbin0 -> 1156 bytes
-rw-r--r--fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1bin0 -> 1085 bytes
-rw-r--r--fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074bin0 -> 1915 bytes
-rw-r--r--fuzz/bplist.dict1
-rw-r--r--fuzz/bplist_fuzzer.cc (renamed from libcnary/list.c)35
-rw-r--r--fuzz/bplist_fuzzer.options3
-rwxr-xr-xfuzz/fuzzers.test8
-rwxr-xr-xfuzz/init-fuzzers.sh37
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-499761467896627265
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-51494554630881281
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896bin0 -> 725 bytes
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-54868076958842881
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-55768333980794881
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-57040166868746241
-rw-r--r--fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-66390764663603201
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-48819332370923521
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-50698839126179841
-rw-r--r--fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-58161116968386561
-rw-r--r--fuzz/jplist.dict52
-rw-r--r--fuzz/jplist_fuzzer.cc (renamed from libcnary/include/list.h)32
-rw-r--r--fuzz/jplist_fuzzer.options3
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-45038154058301441
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392bin0 -> 164 bytes
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-47161941146992641
-rw-r--r--fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976bin0 -> 224 bytes
-rw-r--r--fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-60435486027284488
-rw-r--r--fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-64974369884733441
-rw-r--r--fuzz/oplist.dict51
-rw-r--r--fuzz/oplist_fuzzer.cc (renamed from libcnary/iterator.c)49
-rw-r--r--fuzz/oplist_fuzzer.options3
-rwxr-xr-xfuzz/test-fuzzers.sh45
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856bin0 -> 408 bytes
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-46638765283737605
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-579907611315404845
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496bin0 -> 1132 bytes
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168bin0 -> 1567 bytes
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936bin0 -> 3845 bytes
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-604750990645657610050
-rw-r--r--fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984bin0 -> 613 bytes
-rw-r--r--fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f1753
-rw-r--r--fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c19
-rw-r--r--fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e69488311
-rw-r--r--fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a8
-rw-r--r--fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64ebin0 -> 1730 bytes
-rw-r--r--fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea65
-rw-r--r--fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33bin0 -> 1013 bytes
-rw-r--r--fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9bin0 -> 4096 bytes
-rw-r--r--fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9bin0 -> 2342 bytes
-rw-r--r--fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd807090
-rw-r--r--fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d72
-rw-r--r--fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840187
-rw-r--r--fuzz/xplist-leaks/clusterfuzz-testcase-605641212166144012
-rw-r--r--fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07bin0 -> 4096 bytes
-rw-r--r--fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7bin0 -> 557 bytes
-rw-r--r--fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf6
-rw-r--r--fuzz/xplist.dict51
-rw-r--r--fuzz/xplist_fuzzer.cc (renamed from plutil/plutil.h)21
-rw-r--r--fuzz/xplist_fuzzer.options3
-rwxr-xr-xgit-version-gen20
-rw-r--r--include/CMakeLists.txt17
-rw-r--r--include/Makefile.am15
-rw-r--r--include/plist/Array.h43
-rw-r--r--include/plist/Boolean.h14
-rw-r--r--include/plist/Data.h15
-rw-r--r--include/plist/Date.h25
-rw-r--r--include/plist/Dictionary.h31
-rw-r--r--include/plist/Integer.h20
-rw-r--r--include/plist/Key.h49
-rw-r--r--include/plist/Node.h15
-rw-r--r--include/plist/Real.h14
-rw-r--r--include/plist/String.h17
-rw-r--r--include/plist/Structure.h15
-rw-r--r--include/plist/Uid.h48
-rw-r--r--include/plist/plist++.h2
-rw-r--r--include/plist/plist.h1150
-rw-r--r--libcnary/CMakeLists.txt16
-rw-r--r--libcnary/Makefile21
-rw-r--r--libcnary/Makefile.am15
-rw-r--r--libcnary/include/iterator.h49
-rw-r--r--libcnary/include/node.h62
-rw-r--r--libcnary/include/node_iterator.h55
-rw-r--r--libcnary/include/node_list.h23
-rw-r--r--libcnary/node.c235
-rw-r--r--libcnary/node_iterator.c80
-rw-r--r--libcnary/node_list.c141
-rw-r--r--libplist++.pc.in12
-rw-r--r--libplist.pc.in12
-rw-r--r--m4/ac_pkg_cython.m467
-rw-r--r--m4/as-compiler-flag.m462
-rw-r--r--m4/ax_pthread.m4522
-rw-r--r--m4/ax_python_devel.m4468
-rw-r--r--m4/cython_python.m47
-rw-r--r--plutil/CMakeLists.txt9
-rw-r--r--plutil/plutil.c164
-rw-r--r--src/Array.cpp159
-rw-r--r--src/Boolean.cpp15
-rw-r--r--src/CMakeLists.txt57
-rw-r--r--src/Data.cpp25
-rw-r--r--src/Date.cpp34
-rw-r--r--src/Dictionary.cpp128
-rw-r--r--src/Integer.cpp49
-rw-r--r--src/Key.cpp78
-rw-r--r--src/Makefile.am71
-rw-r--r--src/Node.cpp40
-rw-r--r--src/Real.cpp14
-rw-r--r--src/String.cpp40
-rw-r--r--src/Structure.cpp32
-rw-r--r--src/Uid.cpp76
-rw-r--r--src/base64.c101
-rw-r--r--src/base64.h5
-rw-r--r--src/bplist.c1684
-rw-r--r--src/bytearray.c49
-rw-r--r--src/bytearray.h11
-rw-r--r--src/common.c115
-rw-r--r--src/common.h44
-rw-r--r--src/hashtable.c48
-rw-r--r--src/hashtable.h18
-rw-r--r--src/jplist.c981
-rw-r--r--src/jsmn.c306
-rw-r--r--src/jsmn.h98
-rw-r--r--src/libplist++-2.0.pc.in11
-rw-r--r--src/libplist-2.0.pc.in10
-rw-r--r--src/oplist.c989
-rw-r--r--src/out-default.c488
-rw-r--r--src/out-limd.c469
-rw-r--r--src/out-plutil.c477
-rw-r--r--src/plist.c2317
-rw-r--r--src/plist.h62
-rw-r--r--src/ptrarray.c55
-rw-r--r--src/ptrarray.h21
-rw-r--r--src/strbuf.h35
-rw-r--r--src/time64.c804
-rw-r--r--src/time64.h92
-rw-r--r--src/time64_limits.h97
-rw-r--r--src/xplist.c1740
-rw-r--r--swig/CMakeLists.txt24
-rw-r--r--swig/__init__.py3
-rw-r--r--swig/plist.i273
-rw-r--r--test/CMakeLists.txt29
-rw-r--r--test/Makefile.am146
-rwxr-xr-xtest/amp.test16
-rwxr-xr-xtest/bigarray++.test15
-rwxr-xr-xtest/bigarray.test15
-rwxr-xr-xtest/cdata.test12
-rw-r--r--test/data/1.plist10
-rw-r--r--test/data/2.plist2
-rw-r--r--test/data/3.plist2
-rw-r--r--test/data/4.plist2
-rw-r--r--test/data/5.plist2
-rw-r--r--test/data/7.plist14
-rw-r--r--test/data/amp.plist6
-rw-r--r--test/data/cdata.plist6
-rw-r--r--test/data/data.bplistbin0 -> 3718 bytes
-rw-r--r--test/data/dictref1byte.bplistbin0 -> 50 bytes
-rw-r--r--test/data/dictref2bytes.bplistbin0 -> 52 bytes
-rw-r--r--test/data/dictref3bytes.bplistbin0 -> 54 bytes
-rw-r--r--test/data/dictref4bytes.bplistbin0 -> 56 bytes
-rw-r--r--test/data/dictref5bytes.bplistbin0 -> 58 bytes
-rw-r--r--test/data/dictref6bytes.bplistbin0 -> 60 bytes
-rw-r--r--test/data/dictref7bytes.bplistbin0 -> 62 bytes
-rw-r--r--test/data/dictref8bytes.bplistbin0 -> 64 bytes
-rw-r--r--test/data/empty_keys.plist8
-rw-r--r--test/data/entities.plist57
-rw-r--r--test/data/hex.plist5
-rw-r--r--test/data/int64_min_max.json1
-rw-r--r--test/data/invalid_tag.plist6
-rw-r--r--test/data/j1.json1
-rw-r--r--test/data/j2.json1
-rw-r--r--test/data/malformed_dict.bplistbin0 -> 77 bytes
-rw-r--r--test/data/o1.ostep45
-rw-r--r--test/data/o2.ostep17
-rw-r--r--test/data/o3.ostep16
-rw-r--r--test/data/off1byte.bplistbin0 -> 50 bytes
-rw-r--r--test/data/off2bytes.bplistbin0 -> 53 bytes
-rw-r--r--test/data/off3bytes.bplistbin0 -> 56 bytes
-rw-r--r--test/data/off4bytes.bplistbin0 -> 59 bytes
-rw-r--r--test/data/off5bytes.bplistbin0 -> 62 bytes
-rw-r--r--test/data/off6bytes.bplistbin0 -> 65 bytes
-rw-r--r--test/data/off7bytes.bplistbin0 -> 68 bytes
-rw-r--r--test/data/off8bytes.bplistbin0 -> 71 bytes
-rw-r--r--test/data/offxml.plist8
-rw-r--r--test/data/order.bplistbin0 -> 77 bytes
-rw-r--r--test/data/order.plist27
-rw-r--r--test/data/recursion.bplistbin0 -> 60 bytes
-rw-r--r--test/data/signed.bplistbin0 -> 50 bytes
-rw-r--r--test/data/signed.plist5
-rw-r--r--test/data/signedunsigned.bplistbin0 -> 113 bytes
-rw-r--r--test/data/signedunsigned.plist11
-rw-r--r--test/data/test.strings12
-rw-r--r--test/data/uid.bplistbin0 -> 43 bytes
-rw-r--r--test/data/unsigned.bplistbin0 -> 58 bytes
-rw-r--r--test/data/unsigned.plist5
-rwxr-xr-xtest/dates.test15
-rwxr-xr-xtest/empty++.test15
-rwxr-xr-xtest/empty.test15
-rwxr-xr-xtest/empty_keys.test12
-rwxr-xr-xtest/entities.test12
-rwxr-xr-xtest/hex.test12
-rwxr-xr-xtest/huge++.test15
-rwxr-xr-xtest/huge.test15
-rw-r--r--test/integer_set.c130
-rwxr-xr-xtest/integer_set.test5
-rwxr-xr-xtest/invalid_tag.test16
-rwxr-xr-xtest/json-int64-min-max.test19
-rwxr-xr-xtest/json-invalid-types.test33
-rwxr-xr-xtest/json1.test19
-rwxr-xr-xtest/json2.test19
-rwxr-xr-xtest/json3.test24
-rwxr-xr-xtest/large++.test15
-rwxr-xr-xtest/large.test15
-rwxr-xr-xtest/malformed_dict.test15
-rwxr-xr-xtest/medium++.test15
-rwxr-xr-xtest/medium.test15
-rwxr-xr-xtest/offsetsize.test12
-rwxr-xr-xtest/order.test13
-rwxr-xr-xtest/ostep-comments.test20
-rwxr-xr-xtest/ostep-invalid-types.test32
-rwxr-xr-xtest/ostep-strings.test20
-rwxr-xr-xtest/ostep1.test20
-rwxr-xr-xtest/ostep2.test19
-rw-r--r--test/plist_btest.c137
-rw-r--r--test/plist_cmp.c58
-rw-r--r--test/plist_jtest.c137
-rw-r--r--test/plist_otest.c135
-rw-r--r--test/plist_test++.cpp103
-rw-r--r--test/plist_test.c26
-rwxr-xr-xtest/recursion.test15
-rwxr-xr-xtest/refsize.test12
-rwxr-xr-xtest/signedunsigned1.test23
-rwxr-xr-xtest/signedunsigned2.test23
-rwxr-xr-xtest/signedunsigned3.test23
-rwxr-xr-xtest/small++.test15
-rwxr-xr-xtest/small.test15
-rwxr-xr-xtest/timezone1.test17
-rwxr-xr-xtest/timezone2.test21
-rwxr-xr-xtest/uid.test15
-rwxr-xr-xtest/xml_behavior.test2
-rw-r--r--test/xml_behavior_test.c167
-rw-r--r--tools/Makefile.am20
-rw-r--r--tools/plistutil.c529
300 files changed, 33140 insertions, 4638 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..e995b30
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+github: nikias
+patreon: nikias
+custom: ["https://www.paypal.me/NikiasBassen"]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..54273b4
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,166 @@
+name: build
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 0 1 * *'
+
+jobs:
+ build-linux-ubuntu:
+ runs-on: ubuntu-latest
+ steps:
+ - name: install dependencies
+ run: |
+ sudo apt-get update
+ pip install cython
+ - name: prepare environment
+ run: |
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: autogen
+ run: |
+ export PYTHON=python3
+ export LDFLAGS="-Wl,-rpath=/usr/local/lib"
+ ./autogen.sh --enable-debug
+ - name: make
+ run: make
+ - name: make check
+ run: make check
+ - name: make install
+ run: sudo make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf libplist.tar usr
+ - name: publish artifact
+ uses: actions/upload-artifact@v6
+ with:
+ name: libplist-latest_${{env.target_triplet}}
+ path: libplist.tar
+ build-macOS:
+ runs-on: macOS-latest
+ steps:
+ - name: install dependencies
+ run: |
+ if test -x "`which port`"; then
+ sudo port install libtool autoconf automake
+ else
+ brew install libtool autoconf automake
+ fi
+ pip3 install --break-system-packages cython
+ shell: bash
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: autogen
+ run: |
+ SDKDIR=`xcrun --sdk macosx --show-sdk-path`
+ TESTARCHS="arm64 x86_64"
+ USEARCHS=
+ for ARCH in $TESTARCHS; do
+ if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then
+ USEARCHS="$USEARCHS -arch $ARCH"
+ fi
+ done
+ export CFLAGS="$USEARCHS -isysroot $SDKDIR"
+ echo "Using CFLAGS: $CFLAGS"
+ PYTHON3_BIN=`xcrun -f python3`
+ if test -x $PYTHON3_BIN; then
+ export PYTHON=$PYTHON3_BIN
+ PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"`
+ PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"`
+ PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib
+ PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3
+ export PYTHON_CPPFLAGS="-I$PYTHON_EXEC_PREFIX/Headers"
+ export PYTHON_LIBS="-L$PYTHON_LIBS_PATH -lpython$PYTHON_VER"
+ export PYTHON_EXTRA_LDFLAGS="-Wl,-stack_size,1000000 -framework CoreFoundation $PYTHON_FRAMEWORK_PATH"
+ fi
+ ./autogen.sh --enable-debug
+ - name: make
+ run: make
+ - name: make check
+ run: make check
+ - name: make install
+ run: sudo make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf libplist.tar usr
+ - name: publish artifact
+ uses: actions/upload-artifact@v6
+ with:
+ name: libplist-latest_macOS
+ path: libplist.tar
+ build-windows:
+ runs-on: windows-latest
+ defaults:
+ run:
+ shell: msys2 {0}
+ strategy:
+ fail-fast: false
+ matrix:
+ include: [
+ { msystem: MINGW64, arch: x86_64 },
+ { msystem: MINGW32, arch: i686 }
+ ]
+ steps:
+ - uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.msystem }}
+ release: false
+ update: false
+ install: >-
+ base-devel
+ git
+ mingw-w64-${{ matrix.arch }}-gcc
+ mingw-w64-${{ matrix.arch }}-pkg-config
+ mingw-w64-${{ matrix.arch }}-python
+ make
+ libtool
+ autoconf
+ automake-wrapper
+ cython
+ - name: prepare environment
+ run: |
+ dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]`
+ echo "dest=$dest" >> $GITHUB_ENV
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ git config --global core.autocrlf false
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - name: autogen
+ run: ./autogen.sh CC=gcc CXX=g++ --enable-debug
+ - name: make
+ run: make
+ - name: make check
+ run: make check
+ - name: print test logs
+ run: |
+ for I in test/*.trs; do
+ RES=`grep ":test-result" $I |cut -d ":" -f 3`
+ if test $RES != PASS; then
+ TESTNAME=`basename $I .trs`
+ echo $TESTNAME:
+ cat test/$TESTNAME.log
+ echo
+ fi
+ done
+ shell: bash
+ - name: make install
+ run: make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf libplist.tar ${{ env.dest }}
+ - name: publish artifact
+ uses: actions/upload-artifact@v6
+ with:
+ name: libplist-latest_${{ matrix.arch }}-${{ env.dest }}
+ path: libplist.tar
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
new file mode 100644
index 0000000..c5d6ec0
--- /dev/null
+++ b/.github/workflows/cifuzz.yml
@@ -0,0 +1,26 @@
+name: CIFuzz
+on: [pull_request]
+jobs:
+ Fuzzing:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Build Fuzzers
+ id: build
+ uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'libplist'
+ dry-run: false
+ language: c++
+ - name: Run Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'libplist'
+ fuzz-seconds: 300
+ dry-run: false
+ language: c++
+ - name: Upload Crash
+ uses: actions/upload-artifact@v4
+ if: failure() && steps.build.outcome == 'success'
+ with:
+ name: artifacts
+ path: ./out/artifacts
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..8f2384a
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,53 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [master]
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ # Override automatic language detection by changing the below list
+ # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+ language: ['cpp']
+ # Learn more...
+ # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 0
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ - name: Build
+ run: |
+ ./autogen.sh --enable-debug --without-cython
+ make
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
diff --git a/.gitignore b/.gitignore
index 85f404f..a59b057 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,74 @@
*.swp
-build/*
+*.o
+*.lo
+*.la
+*.pc
+*.log
+*.exe
+*~
+.kateconfig
+configure
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.*
+depcomp
+doxygen.cfg
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+src/Makefile
+src/Makefile.in
+src/.libs
+src/.deps
+fuzz/.deps
+fuzz/.libs
+include/Makefile
+include/Makefile.in
+libcnary/Makefile
+libcnary/Makefile.in
+libcnary/.libs
+libcnary/.deps
+tools/Makefile
+tools/Makefile.in
+tools/.libs
+tools/.deps
+tools/plistutil
+test/Makefile
+test/Makefile.in
+test/.libs
+test/.deps
+test/plist_cmp
+test/plist_test
+test/plist_test++
+test/plist_btest
+test/plist_jtest
+test/plist_otest
+test/integer_set_test
+test/xml_behavior_test
+test/data/*.out
+test/*.trs
+cython/Makefile
+cython/Makefile.in
+cython/.deps
+cython/.libs
+cython/plist.c
+test-driver
+
+# Generated test output files
+test/data/*.test.bin
+test/data/*.test.signed.bin
+test/data/*.test.unsigned.bin
+test/data/*.test.unsigned.xml
+test/data/*.test.tz*.bin
+test/data/*.test.tz*.xml
+test/data/*.test.xml
diff --git a/AUTHORS b/AUTHORS
index ed520b4..22d6a7d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,13 +1,28 @@
+Aaron Burghardt
Alexander Sack
+Andrew Udvare
+Bryan Forbes
+Chow Loong Jin
Christophe Fergeau
+Dogbert
+Elan Ruusamäe
+Filippo Bigarella
+Frederik Carlier
+Glenn Washburn
+Greg Dennis
Ingmar Vanhassel
+Jim Koning
Jonathan Beck
Julien Blache
Martin Aumueller
Martin Szulecki
Matt Colyer
+Matthias Klose
+Nicolás Alvarez
Nikias Bassen
Patrick von Reth
Patrick Walton
Paul Sladen
+Shane G
+Wang Junjie
Zach C
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index 0bad840..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-PROJECT( libplist )
-
-SET( LIBPLIST_VERSION_MAJOR "1" )
-SET( LIBPLIST_VERSION_MINOR "8" )
-SET( LIBPLIST_SOVERSION "1" )
-SET( LIBPLIST_VERSION "${LIBPLIST_VERSION_MAJOR}.${LIBPLIST_VERSION_MINOR}" )
-SET( LIBPLIST_LIBVERSION "${LIBPLIST_SOVERSION}.${LIBPLIST_VERSION}" )
-SET( PLUTIL_VERSION ${LIBPLIST_VERSION} )
-
-SET( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_SOURCE_DIR}/cmake/modules )
-
-cmake_minimum_required(VERSION 2.6)
-
-IF(NOT DEFINED CMAKE_INSTALL_LIBDIR)
- SET(CMAKE_INSTALL_LIBDIR lib CACHE PATH "Output directory for libraries")
-ENDIF(NOT DEFINED CMAKE_INSTALL_LIBDIR)
-
-FIND_PACKAGE( LibXml2 REQUIRED )
-
-OPTION(ENABLE_SWIG "Enable SWIG Python bindings (needs Swig)" ON)
-OPTION(ENABLE_CYTHON "Enable Cython Python bindings (needs Cython)" ON)
-
-IF(ENABLE_SWIG)
- FIND_PACKAGE( SWIG )
-ENDIF(ENABLE_SWIG)
-
-IF(ENABLE_CYTHON)
- FIND_PACKAGE( Cython )
-ENDIF(ENABLE_CYTHON)
-
-IF(ENABLE_SWIG OR ENABLE_CYTHON)
- FIND_PACKAGE( PythonInterp )
- FIND_PACKAGE( PythonLibs )
-ENDIF(ENABLE_SWIG OR ENABLE_CYTHON)
-
-INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/include
- ${CMAKE_INCLUDE_PATH})
-
-ENABLE_TESTING()
-
-IF (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libcnary/CMakeLists.txt")
- MESSAGE(FATAL_ERROR "
-You need to checkout the 'libcnary' submodule to build libplist.
-To do so, perform the following steps in the base directory:
- git submodule init
- git submodule update
-Then, run cmake again.")
-ENDIF()
-
-INCLUDE (TestBigEndian)
-TEST_BIG_ENDIAN(endianess)
-SET ( PLIST_BYTE_ORDER ${endianess} )
-
-ADD_SUBDIRECTORY( libcnary )
-ADD_SUBDIRECTORY( src )
-ADD_SUBDIRECTORY( plutil )
-ADD_SUBDIRECTORY( include )
-ADD_SUBDIRECTORY( test )
-
-IF ( SWIG_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND )
- ADD_SUBDIRECTORY( swig )
-ENDIF ( SWIG_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND )
-IF ( CYTHON_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND )
- ADD_SUBDIRECTORY( cython )
-ENDIF ( CYTHON_FOUND AND PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND )
-
-# add uninstall target
-CONFIGURE_FILE( "${CMAKE_SOURCE_DIR}/cmake/modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
-ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
-
-########## PkgConfig ############################
-CONFIGURE_FILE( "libplist.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libplist.pc" )
-CONFIGURE_FILE( "libplist++.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libplist++.pc" )
-
-########## INSTALL ##############################
-INSTALL( FILES "${CMAKE_CURRENT_BINARY_DIR}/libplist.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig/ )
-INSTALL( FILES "${CMAKE_CURRENT_BINARY_DIR}/libplist++.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig/ )
-
-########## CPack ################################
-INCLUDE( libplistPackaging )
-LIBPLIST_PACKAGE(LIBPLIST_VERSION_MAJOR LIBPLIST_VERSION_MINOR)
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..e6923a4
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,32 @@
+AUTOMAKE_OPTIONS = foreign
+ACLOCAL_AMFLAGS = -I m4
+SUBDIRS = libcnary src include tools docs
+
+if HAVE_CYTHON
+SUBDIRS += cython
+endif
+
+if BUILD_TESTS
+SUBDIRS += test
+endif
+
+if BUILD_FUZZERS
+SUBDIRS += fuzz
+endif
+
+EXTRA_DIST = \
+ README.md \
+ git-version-gen
+
+dist-hook:
+ @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi
+ echo $(VERSION) > $(distdir)/.tarball-version
+
+docs/html: $(top_builddir)/doxygen.cfg $(top_srcdir)/include/plist/*.h
+ rm -rf docs/html
+ doxygen doxygen.cfg
+
+docs/README.doxygen.md: README.md
+ awk '/\#\# Table of Contents/{while(getline && $$0 != ""){}{print "[TOC]"}}1' README.md > $@
+
+docs: doxygen.cfg docs/README.doxygen.md docs/html
diff --git a/NEWS b/NEWS
index f9aa18c..2627893 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,216 @@
+Version 2.7.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Add plist_new_unix_date, plist_get_unix_date_val, plist_set_unix_date_val functions
+ that work with int64_t values representing a UNIX timestamp instead of
+ using the 'MAC epoch'.
+ These new functions should be used instead of plist_new_date,
+ plist_get_date_val, and plist_set_date_val, which are now marked deprecated
+ and might be removed in a future version of libplist.
+ * Allow building the library without tool(s)
+ * Switch to more generic global initializer method
+ * json: Allow e+/E+ in exponent as per RFC 8259
+ * C++: Add more convenience functions to the interface
+ * C++: Add more type variants to different constructors and operators
+- Bugfixes:
+ * Fix segmentation fault when calling plist_sort() on an empty dictionary
+ * Fix compilation on MSVC
+ * C++: Fix bug in internal helper function of Array class
+ * C++: Fix String::GetValue memory leaking and support assignment of const char*
+
+Version 2.6.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Revert back API change around PLIST_DATA to use char* again
+
+Version 2.5.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Change API around PLIST_DATA to use uint8_t* instead of char*
+ * Add PLIST_DICT helper functions for different operations
+ * Require Cython 3.0 for python bindings
+
+Version 2.4.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Add a PLIST_OPT_NONE value to plist_write_options_t
+ * autoconf: Allow disabling build of test suite
+ * Update doxygen config and document undocumented macros
+ * Add an explicit PLIST_FORMAT_NONE value
+ * Add a libplist_version() function to the interface
+ * docs: Use README.md to generate mainpage with doxygen
+- Bugfixes:
+ * Several compiler-related fixes and code improvements
+ * Plug memory leak in plist_write_to_stream()
+ * Prevent adding NULL items to array/dictionary nodes
+ * Fix parallel running of test suite
+ * Fix cython bindings
+ * Fix OOB read in plist_from_memory()
+
+Version 2.3.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Rename PLIST_UINT to PLIST_INT and add plist_new_int() and plist_get_int_val()
+ * Add support for JSON format
+ * Add support for OpenStep format
+ * Introduce error codes and format constants
+ * Add return value to import/export functions to allow returning error codes
+ * Add new plist_sort function
+ * Add several human-readable output-only formats
+ * Add new plist_write_to_string/_stream/_file functions
+ * Add new plist_print function
+ * Add new plist_read_from_file function
+ * Add new plist_mem_free() function
+ * Add a few C++ methods
+ * Add C++ interface test
+ * Add PLIST_NULL type
+ * Some code housekeeping (mostly clang-tidy)
+- Breaking:
+ * plist_from_memory() gets additional parameter
+- Bugfixes:
+ * Fix multiple bugs in all of the parsers
+ * Fix handling of PLIST_UID nodes
+
+Version 2.2.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * bplist: Improve recursion check performance by at least 30% for large files
+ * test: Fix test suite on Windows
+ * cython: Fix handling of Date nodes (MACH_EPOCH)
+ * Add new plist_*_val_compare(), plist_*_val_contains() helper functions
+ * Fix/suppress several compiler warnings
+ * plistutil: Added ability for files to be read from stdin
+ * plistutil: Added ability to specify output format
+ * Fix: Return NULL from plist_copy() if passed a NULL pointer instead of asserting
+ * Add GitHub Actions integration for automatic build tests
+ * plistutil: Add manual page and usage output
+ * Fix removal of docs directory on `make clean`
+ * Improve README.md with project description, installation, contributing and
+ usage sections
+ * Rename library and all related files by adding an API version resulting
+ in "libplist-2.0" and "libplist++-2.0"
+
+Version 2.1.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * Add new plist_get_data_ptr() and plist_get_string_ptr() for direct access to underlying buffer
+ * Increase precision when converting PLIST_REAL nodes to XML
+ * Fix several issues in libcnary (UaF, segfault, memory leak)
+ * Fix copying of hash table entries when cloning array/dict nodes
+ * cython: Implement load()/loads() and dump()/dumps() to match up with plistlib (Python 3.4)
+ * Add new plist_dict_item_get_key() for retrieving key node of a dict item
+ * Add new plist_array_item_remove() to allow removing an array's child node without relying on the index
+ * Make plist_array_get_item_index() return UINT_MAX instead of 0 when node can't be found
+ * Add index lookup table for large PLIST_ARRAY nodes
+ * Add iterator for array nodes: plist_array_new_iter(), plist_array_next_item()
+ * Improve performance of plist_dict_next_item() drastically
+ * Improve performance and memory usage when writing XML plists
+ * Improve performance and memory usage when writing binary plists
+ * Allow building with sanitizers (without fuzzers)
+ * Prevent store to misaligned address when writing real/date nodes
+ * Work around misaligned reads in binary plist parser
+ * Integrate fuzzing targets (libFuzzer) into project
+ * Add sanitizers (ASAN, UBSAN) when building fuzzers (--with-fuzzers)
+
+Version 2.0.0
+~~~~~~~~~~~~~
+
+- Changes:
+ * New light-weight custom XML parser
+ * Remove libxml2 dependency
+ * Refactor binary plist parsing
+ * Improved malformed XML and binary plist detection and error handling
+ * Add parser debug/error output (when compiled with --enable-debug), controlled via environment variables
+ * Fix unicode character handling
+ * Add PLIST_IS_* helper macros for the different node types
+ * Extend date/time range and date conversion issues
+ * Add plist_is_binary() and plist_from_memory() functions to the interface
+ * Plug several memory leaks
+ * Speed improvements for handling large plist files
+
+- Includes security fixes for:
+ * CVE-2017-6440
+ * CVE-2017-6439
+ * CVE-2017-6438
+ * CVE-2017-6437
+ * CVE-2017-6436
+ * CVE-2017-6435
+ * CVE-2017-5836
+ * CVE-2017-5835
+ * CVE-2017-5834
+ * CVE-2017-5545
+ * CVE-2017-5209
+ ... and several others that didn't receive any CVE (yet).
+
+Version 1.12
+~~~~~~~~~~~~
+
+- Changes:
+ * Fix plist_from_bin() changing value nodes to key nodes in dictionaries
+ * Avoid exporting non-public symbols
+ * Prevent crash in plist_from_bin() when parsing unusual binary plists
+ * Fix crash in String|Key::GetValue() and actually make C++ interface work
+ * Fix memory leaks in new_xml_plist() and parse_real_node()
+ * Fix header guards to conform to C++ standard
+ * Update Cython based Python bindings and remove plist_new_key()
+ * Fix key nodes not being output correctly if they contained XML entities
+ * Fix handling and storage of signed vs. unsigned integer values
+ * Fix date handling to respect the "Mac Epoch" instead of "Unix Epoch"
+ * Remove plist_set_type() as it should not be used
+ * Fix deprecated macros to work with older LLVM/Clang
+ * Fix various shadowed declarations
+ * Add documentation to explicitly describe memory buffer ownership
+ * Fix memory leak in plist_from_bin()
+ * Add various test cases based on fixes
+ * Fix wrong timezone related date/time conversion of date nodes
+ * Fix endian detection on MIPS architecture
+ * Fix parallel build for autotools
+
+Version 1.11
+~~~~~~~~~~~~
+
+- Changes:
+ * Deprecated plist_dict_insert_item() in favor of plist_dict_set_item()
+ * Updated cython bindings for Python 3.x
+ * Removed swig python bindings
+ * Changed build system to autotools
+ * Added new plist_dict_merge() function
+ * WIN32 (MinGW) + OSX compilation fixes
+ * Made base64 decoding thread safe
+
+Version 1.10
+~~~~~~~~~~~~
+
+- Changes:
+ * Renamed plutil to plistutil to not mask Apple's plutil
+ * Fixed cython bindings (broken in 1.9)
+ * Added support for PLIST_UID node types to C++, cython, and swig bindings
+
+- Important Note:
+ * Support for swig python bindings will be dropped with future releases.
+ The bindings will be kept in the source tree for now, but we suggest
+ to update your python code to use the cython bindings instead.
+
+Version 1.9
+~~~~~~~~~~~
+
+- Changes:
+ * Add support for handling UID node types
+ * Fix crash when converting plists containing comments
+ * Fix Bug in plist_data_compare()
+ * Fix DST handling for PLIST_DATE
+ * Fix plist_dict_set_item() and plist_array_set_item()
+ * Fix cython String plist handling
+ * Fix invalid memory access in copy_plist_data()
+ * Fix several compiler warnings
+
Version 1.8
~~~~~~~~~~~
diff --git a/README b/README
deleted file mode 100644
index c359dfa..0000000
--- a/README
+++ /dev/null
@@ -1,47 +0,0 @@
-INSTALLATION
-================================================================================
-
-For:
- Apple Binary and XML Property Lists
-
-You must have:
- libxml2-dev
- cmake
- make
- gcc
-
-Optional:
- swig
- python
-
-To compile run:
- mkdir build
- cd build
- cmake ..
- make
- sudo make install # (if you want to install it into your system directories)
-
-On Ubuntu/Debian, you can do:
- sudo apt-get install build-essential cmake libxml2-dev
-
-
-== Who/what/where? ==
-
-Home:
- http://www.libimobiledevice.org/
-
-Code:
- git clone http://cgit.sukimashita.com/libplist.git/
-
- libplist uses libcnary, see:
- http://github.com/Chronic-Dev/libcnary
-
-Tickets:
- http://libiphone.lighthouseapp.com/
- Please tag libplist related bugs with 'libplist'
-
-Mailing List:
- http://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel
-
-updated:
- 2011-06-25
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad36634
--- /dev/null
+++ b/README.md
@@ -0,0 +1,283 @@
+# libplist
+
+*A small portable C library to handle Apple Property List files in binary, XML,
+JSON, or OpenStep format.*
+
+![](https://github.com/libimobiledevice/libplist/workflows/build/badge.svg)
+![](https://github.com/libimobiledevice/libplist/workflows/CodeQL/badge.svg)
+
+## Table of Contents
+- [Features](#features)
+- [Building](#building)
+ - [Prerequisites](#prerequisites)
+ - [Linux (Debian/Ubuntu based)](#linux-debianubuntu-based)
+ - [macOS](#macos)
+ - [Windows](#windows)
+ - [Configuring the source tree](#configuring-the-source-tree)
+ - [Building and installation](#building-and-installation)
+- [Usage](#usage)
+- [Contributing](#contributing)
+- [Links](#links)
+- [License](#license)
+- [Credits](#credits)
+
+## Features
+
+The project provides an interface to read and write plist files in binary,
+XML, JSON, or OpenStep format alongside a command-line utility named `plistutil`.
+
+Some key features are:
+
+- **Formats:** Supports binary, XML, JSON, and OpenStep format
+- **Utility:** Provides a `plistutil` utility for the command-line
+- **Python:** Provides Cython based bindings for Python
+- **Tested:** Uses fuzzing ([OSS-Fuzz](https://github.com/google/oss-fuzz)) and data compliance tests
+- **Efficient:** Lean library with performance and resources in mind
+
+## Building
+
+### Prerequisites
+
+You need to have a working compiler (gcc/clang) and development environent
+available. This project uses autotools for the build process, allowing to
+have common build steps across different platforms.
+Only the prerequisites differ and they are described in this section.
+
+#### Linux (Debian/Ubuntu based)
+
+* Install all required dependencies and build tools:
+ ```shell
+ sudo apt-get install \
+ build-essential \
+ checkinstall \
+ git \
+ autoconf \
+ automake \
+ libtool-bin
+ ```
+
+ If you want to optionally build the documentation or Python bindings use:
+ ```shell
+ sudo apt-get install \
+ doxygen \
+ cython3
+ ```
+
+#### macOS
+
+* Make sure the Xcode command line tools are installed. Then, use either [MacPorts](https://www.macports.org/)
+ or [Homebrew](https://brew.sh/) to install `automake`, `autoconf`, and `libtool`.
+
+ Using MacPorts:
+ ```shell
+ sudo port install libtool autoconf automake
+ ```
+
+ Using Homebrew:
+ ```shell
+ brew install libtool autoconf automake
+ ```
+
+ In case you want to build the documentation, install `doxygen` using the corresponding install command from above.
+
+ If you want to build Python bindings, you need to install cython:
+ ```shell
+ pip3 install cython
+ ```
+
+ You might need to set a few environment variables if building of the Python bindings fail. For example, the [automated build via GitHub actions](https://github.com/libimobiledevice/libplist/blob/master/.github/workflows/build.yml)
+ is setting the following environment variables:
+ ```shell
+ PYTHON3_BIN=`xcrun -f python3`
+ export PYTHON=$PYTHON3_BIN
+ PYTHON_VER=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('VERSION'))"`
+ PYTHON_EXEC_PREFIX=`$PYTHON3_BIN -c "import distutils.sysconfig; print(distutils.sysconfig.get_config_var('exec_prefix'))"`
+ PYTHON_LIBS_PATH=$PYTHON_EXEC_PREFIX/lib
+ PYTHON_FRAMEWORK_PATH=$PYTHON_EXEC_PREFIX/Python3
+ export PYTHON_LIBS="-L$PYTHON_LIBS_PATH -lpython$PYTHON_VER"
+ export PYTHON_EXTRA_LDFLAGS="-Wl,-stack_size,1000000 -framework CoreFoundation $PYTHON_FRAMEWORK_PATH"
+ ```
+
+#### Windows
+
+* Using [MSYS2](https://www.msys2.org/) is the official way of compiling this project on Windows. Download the MSYS2 installer
+ and follow the installation steps.
+
+ It is recommended to use the _MSYS2 MinGW 64-bit_ shell. Run it and make sure the required dependencies are installed:
+
+ ```shell
+ pacman -S base-devel \
+ git \
+ mingw-w64-x86_64-gcc \
+ make \
+ libtool \
+ autoconf \
+ automake-wrapper
+ ```
+ NOTE: You can use a different shell and different compiler according to your needs. Adapt the above command accordingly.
+
+ If you want to optionally build Python bindings, you need to also install `cython`
+ and make sure you have a working python environment.
+
+ ```shell
+ pacman -S cython
+ ```
+
+### Configuring the source tree
+
+You can build the source code from a git checkout, or from a `.tar.bz2` release tarball from [Releases](https://github.com/libimobiledevice/libplist/releases).
+Before we can build it, the source tree has to be configured for building. The steps depend on where you got the source from.
+
+* **From git**
+
+ If you haven't done already, clone the actual project repository and change into the directory.
+ ```shell
+ git clone https://github.com/libimobiledevice/libplist.git
+ cd libplist
+ ```
+
+ Configure the source tree for building:
+ ```shell
+ ./autogen.sh
+ ```
+
+* **From release tarball (.tar.bz2)**
+
+ When using an official [release tarball](https://github.com/libimobiledevice/libplist/releases) (`libplist-x.y.z.tar.bz2`)
+ the procedure is slightly different.
+
+ Extract the tarball:
+ ```shell
+ tar xjf libplist-x.y.z.tar.bz2
+ cd libplist-x.y.z
+ ```
+
+ Configure the source tree for building:
+ ```shell
+ ./configure
+ ```
+
+Both `./configure` and `./autogen.sh` (which generates and calls `configure`) accept a few options, for example `--enable-debug` to allow
+printing debug messages in the final product, or `--without-cython` to skip building the Python bindings.
+You can simply pass them like this:
+
+```shell
+./autogen.sh --prefix=/usr/local --enable-debug --without-cython
+```
+or
+```shell
+./configure --prefix=/usr/local --enable-debug
+```
+
+Once the command is successful, the last few lines of output will look like this:
+```
+[...]
+config.status: creating config.h
+config.status: executing depfiles commands
+config.status: executing libtool commands
+
+Configuration for libplist 2.3.1:
+-------------------------------------------
+
+ Install prefix ..........: /usr/local
+ Debug code ..............: yes
+ Python bindings .........: yes
+
+ Now type 'make' to build libplist 2.3.1,
+ and then 'make install' for installation.
+```
+
+### Building and installation
+
+If you followed all the steps successfully, and `autogen.sh` or `configure` did not print any errors,
+you are ready to build the project. This is simply done with
+
+```shell
+make
+```
+
+If no errors are emitted you are ready for installation. Depending on whether
+the current user has permissions to write to the destination directory or not,
+you would either run
+```shell
+make install
+```
+_OR_
+```shell
+sudo make install
+```
+
+If you are on Linux, you want to run `sudo ldconfig` after installation to
+make sure the installed libraries are made available.
+
+## Usage
+
+Usage is simple; `libplist` has a straight-forward API. It is used in [libimobiledevice](https://github.com/libimobiledevice/libimobiledevice)
+and [corresponding projects](https://github.com/libimobiledevice/).
+
+Furthermore, it comes with a command line utility `plistutil` that is really easy to use:
+```shell
+plistutil -i foobar.plist -o output.plist
+```
+This converts the `foobar.plist` file to the opposite format, e.g. binary to
+XML or vice versa, and outputs it to the `output.plist` file.
+
+To convert to a specific format - and also to convert from JSON or OpenStep
+format - use the `-f` command line switch:
+```shell
+plistutil -i input.plist -f json
+```
+This will convert `input.plist`, regardless of the input format, to JSON. The
+code auto-detects the input format and parses it accordingly.
+
+Please consult the usage information or manual page for a full documentation of
+available command line options:
+```shell
+plistutil --help
+```
+or
+```shell
+man plistutil
+```
+
+## Contributing
+
+We welcome contributions from anyone and are grateful for every pull request!
+
+If you'd like to contribute, please fork the `master` branch, change, commit and
+send a pull request for review. Once approved it can be merged into the main
+code base.
+
+If you plan to contribute larger changes or a major refactoring, please create a
+ticket first to discuss the idea upfront to ensure less effort for everyone.
+
+Please make sure your contribution adheres to:
+* Try to follow the code style of the project
+* Commit messages should describe the change well without being too short
+* Try to split larger changes into individual commits of a common domain
+* Use your real name and a valid email address for your commits
+
+## Links
+
+* Homepage: https://libimobiledevice.org/
+* Repository: https://github.com/libimobiledevice/libplist.git
+* Repository (Mirror): https://git.libimobiledevice.org/libplist.git
+* Issue Tracker: https://github.com/libimobiledevice/libplist/issues
+* Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel
+* Twitter: https://twitter.com/libimobiledev
+
+## License
+
+This project is licensed under the [GNU Lesser General Public License v2.1](https://www.gnu.org/licenses/lgpl-2.1.en.html),
+also included in the repository in the `COPYING` file.
+
+## Credits
+
+Apple, iPhone, iPad, iPod, iPod Touch, Apple TV, Apple Watch, Mac, iOS,
+iPadOS, tvOS, watchOS, and macOS are trademarks of Apple Inc.
+
+This project is an independent software library and has not been authorized,
+sponsored, or otherwise approved by Apple Inc.
+
+README Updated on: 2024-10-22
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..5a0ec43
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+olddir=`pwd`
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+(
+ cd "$srcdir"
+
+ gprefix=`which glibtoolize 2>&1 >/dev/null`
+ if [ $? -eq 0 ]; then
+ glibtoolize --force
+ else
+ libtoolize --force
+ fi
+ aclocal -I m4
+ autoheader
+ automake --add-missing
+ autoconf
+
+ cd "$olddir"
+)
+
+if [ -z "$NOCONFIGURE" ]; then
+ $srcdir/configure "$@"
+fi
diff --git a/cmake/libplistPackaging.cmake b/cmake/libplistPackaging.cmake
deleted file mode 100644
index 1a5fd92..0000000
--- a/cmake/libplistPackaging.cmake
+++ /dev/null
@@ -1,15 +0,0 @@
-MACRO( LIBPLIST_PACKAGE _major _minor)
-
- SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library to parse and generate Apple's binary and XML PList format")
- SET(CPACK_PACKAGE_VERSION_MAJOR ${_major})
- SET(CPACK_PACKAGE_VERSION_MINOR ${_minor})
- SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README")
- SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING.LESSER")
- SET(CPACK_COMPONENT_LIB_DISPLAY_NAME "PList library")
- SET(CPACK_COMPONENT_DEV_DISPLAY_NAME "PList development files")
- SET(CPACK_COMPONENT_PLUTIL_DISPLAY_NAME "PList conversion tool")
- set(CPACK_COMPONENT_DEV_DEPENDS lib)
- set(CPACK_COMPONENT_PLUTIL_DEPENDS lib)
- INCLUDE( CPack )
-
-ENDMACRO( LIBPLIST_PACKAGE )
diff --git a/cmake/modules/FindCython.cmake b/cmake/modules/FindCython.cmake
deleted file mode 100644
index 89005b7..0000000
--- a/cmake/modules/FindCython.cmake
+++ /dev/null
@@ -1,10 +0,0 @@
-FIND_PROGRAM(CYTHON_EXECUTABLE cython)
-
-INCLUDE(FindPackageHandleStandardArgs)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(Cython DEFAULT_MSG CYTHON_EXECUTABLE)
-
-MARK_AS_ADVANCED(CYTHON_EXECUTABLE)
-
-IF(CYTHON_FOUND)
- SET(CYTHON_USE_FILE ${CMAKE_SOURCE_DIR}/cmake/modules/UseCython.cmake)
-ENDIF(CYTHON_FOUND)
diff --git a/cmake/modules/FindGLIB2.cmake b/cmake/modules/FindGLIB2.cmake
deleted file mode 100644
index 226d551..0000000
--- a/cmake/modules/FindGLIB2.cmake
+++ /dev/null
@@ -1,51 +0,0 @@
-# - Try to find the GLIB2 libraries
-# Once done this will define
-#
-# GLIB2_FOUND - system has glib2
-# GLIB2_INCLUDE_DIR - the glib2 include directory
-# GLIB2_LIBRARIES - glib2 library
-
-# Copyright (c) 2008 Laurent Montel, <montel@kde.org>
-#
-# Redistribution and use is allowed according to the terms of the BSD license.
-# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
-
-
-if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
- # Already in cache, be silent
- set(GLIB2_FIND_QUIETLY TRUE)
-endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES)
-
-if (NOT WIN32)
- FIND_PACKAGE(PkgConfig)
- PKG_CHECK_MODULES(PC_GLIB2 glib-2.0)
- SET(GLIB2_DEFINITIONS ${PC_GLIB2_CFLAGS_OTHER})
-endif(NOT WIN32)
-
-find_path(GLIB2_MAIN_INCLUDE_DIR glib.h
- PATH_SUFFIXES glib-2.0
- PATHS ${_LibGLIB2IncDir} )
-
-# search the glibconfig.h include dir under the same root where the library is found
-find_library(GLIB2_LIBRARIES
- NAMES glib-2.0
- PATHS ${_LibGLIB2LinkDir} )
-
-get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH)
-
-find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h
- PATH_SUFFIXES glib-2.0/include
- PATHS ${_LibGLIB2IncDir} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH})
-
-set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}")
-
-# not sure if this include dir is optional or required
-# for now it is optional
-if(GLIB2_INTERNAL_INCLUDE_DIR)
- set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}")
-endif(GLIB2_INTERNAL_INCLUDE_DIR)
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR)
-
-mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES) \ No newline at end of file
diff --git a/cmake/modules/FindLibXml2.cmake b/cmake/modules/FindLibXml2.cmake
deleted file mode 100644
index 8d3c77e..0000000
--- a/cmake/modules/FindLibXml2.cmake
+++ /dev/null
@@ -1,57 +0,0 @@
-# - Try to find LibXml2
-# Once done this will define
-#
-# LIBXML2_FOUND - System has LibXml2
-# LIBXML2_INCLUDE_DIR - The LibXml2 include directory
-# LIBXML2_LIBRARIES - The libraries needed to use LibXml2
-# LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2
-# LIBXML2_XMLLINT_EXECUTABLE - The XML checking tool xmllint coming with LibXml2
-
-# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
-#
-# Redistribution and use is allowed according to the terms of the BSD license.
-# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
-
-
-IF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES)
- # in cache already
- SET(LibXml2_FIND_QUIETLY TRUE)
-ENDIF (LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARIES)
-
-IF (NOT WIN32)
- # use pkg-config to get the directories and then use these values
- # in the FIND_PATH() and FIND_LIBRARY() calls
- FIND_PACKAGE(PkgConfig)
- PKG_CHECK_MODULES(PC_LIBXML libxml-2.0)
- SET(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER})
-ENDIF (NOT WIN32)
-
-FIND_PATH(LIBXML2_INCLUDE_DIR libxml/xpath.h
- HINTS
- ${PC_LIBXML_INCLUDEDIR}
- ${PC_LIBXML_INCLUDE_DIRS}
- PATH_SUFFIXES libxml2
- )
-
-FIND_LIBRARY(LIBXML2_LIBRARIES NAMES xml2 libxml2
- HINTS
- ${PC_LIBXML_LIBDIR}
- ${PC_LIBXML_LIBRARY_DIRS}
- )
-
-FIND_PROGRAM(LIBXML2_XMLLINT_EXECUTABLE xmllint)
-# for backwards compat. with KDE 4.0.x:
-SET(XMLLINT_EXECUTABLE "${LIBXML2_XMLLINT_EXECUTABLE}")
-
-IF( NOT LIBXML2_XMLLINT_EXECUTABLE )
- MESSAGE(STATUS "xmllint program not found. Install it if you want validate generated doc file.")
-ENDIF(NOT LIBXML2_XMLLINT_EXECUTABLE )
-
-
-INCLUDE(FindPackageHandleStandardArgs)
-
-# handle the QUIETLY and REQUIRED arguments and set LIBXML2_FOUND to TRUE if
-# all listed variables are TRUE
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibXml2 DEFAULT_MSG LIBXML2_LIBRARIES LIBXML2_INCLUDE_DIR)
-
-MARK_AS_ADVANCED(LIBXML2_INCLUDE_DIR LIBXML2_LIBRARIES LIBXML2_XMLLINT_EXECUTABLE)
diff --git a/cmake/modules/cmake_uninstall.cmake.in b/cmake/modules/cmake_uninstall.cmake.in
deleted file mode 100644
index 4bfb0bf..0000000
--- a/cmake/modules/cmake_uninstall.cmake.in
+++ /dev/null
@@ -1,21 +0,0 @@
-IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
- MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
-ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
-
-FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
-STRING(REGEX REPLACE "\n" ";" files "${files}")
-FOREACH(file ${files})
- MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
- IF(EXISTS "$ENV{DESTDIR}${file}")
- EXEC_PROGRAM(
- "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
- OUTPUT_VARIABLE rm_out
- RETURN_VALUE rm_retval
- )
- IF(NOT "${rm_retval}" STREQUAL 0)
- MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
- ENDIF(NOT "${rm_retval}" STREQUAL 0)
- ELSE(EXISTS "$ENV{DESTDIR}${file}")
- MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
- ENDIF(EXISTS "$ENV{DESTDIR}${file}")
-ENDFOREACH(file)
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..6f574d9
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,336 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.68)
+AC_INIT([libplist], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/libplist/issues], [], [https://libimobiledevice.org])
+AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip check-news])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
+AC_CONFIG_SRCDIR([src/])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+
+dnl libtool versioning
+# +1 : 0 : +1 == adds new functions to the interface
+# +1 : 0 : 0 == changes or removes functions (changes include both
+# changes to the signature and the semantic)
+# ? :+1 : ? == just internal changes
+# CURRENT : REVISION : AGE
+LIBPLIST_SO_VERSION=11:0:7
+
+AC_SUBST(LIBPLIST_SO_VERSION)
+
+# Check if we have a version defined
+if test -z $PACKAGE_VERSION; then
+ AC_MSG_ERROR([PACKAGE_VERSION is not defined. Make sure to configure a source tree checked out from git or that .tarball-version is present.])
+fi
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_CXX
+
+# AC_PROG_CXX will return "g++" even if no c++ compiler is installed
+AC_LANG_PUSH([C++])
+AC_MSG_CHECKING([whether $CXX is available and compiles a program])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
+ [AC_MSG_RESULT([yes])],
+ [AC_MSG_RESULT([no])
+ AC_MSG_ERROR([C++ compiler not available or unable to compile])])
+AC_LANG_POP
+
+AM_PROG_CC_C_O
+LT_INIT
+
+# Checks for header files.
+AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT8_T
+
+# Checks for library functions.
+AC_CHECK_FUNCS([strdup strndup strerror gmtime_r localtime_r timegm strptime memmem])
+
+# Checking endianness
+AC_C_BIGENDIAN([AC_DEFINE([__BIG_ENDIAN__], [1], [big endian])],
+ [AC_DEFINE([__LITTLE_ENDIAN__], [1], [little endian])])
+
+
+# Check for operating system
+AC_MSG_CHECKING([for platform-specific build settings])
+case ${host_os} in
+ *mingw32*|*cygwin*)
+ AC_MSG_RESULT([${host_os}])
+ win32=true
+ ;;
+ darwin*|*android*)
+ AC_MSG_RESULT([${host_os}])
+ ;;
+ *)
+ AC_MSG_RESULT([${host_os}])
+ ;;
+esac
+AM_CONDITIONAL(WIN32, test x$win32 = xtrue)
+
+AC_SEARCH_LIBS([fmin],[m])
+
+# Check if struct tm has a tm_gmtoff member
+AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff,
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+ #include <time.h>
+ ], [
+ struct tm tm;
+ tm.tm_gmtoff = 1;
+ ])],
+ [ac_cv_struct_tm_gmtoff=yes],
+ [ac_cv_struct_tm_gmtoff=no]
+ )
+)
+
+if (test "$ac_cv_struct_tm_gmtoff" = "yes"); then
+ AC_DEFINE(HAVE_TM_TM_GMTOFF, 1, [Define if struct tm has a tm_gmtoff member])
+fi
+
+# Check if struct tm has a tm_zone member
+AC_CACHE_CHECK(for tm_zone in struct tm, ac_cv_struct_tm_zone,
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+ #include <time.h>
+ ], [
+ struct tm tm;
+ tm.tm_zone = (char*)"UTC";
+ ])],
+ [ac_cv_struct_tm_zone=yes],
+ [ac_cv_struct_tm_zone=no]
+ )
+)
+
+if (test "$ac_cv_struct_tm_zone" = "yes"); then
+ AC_DEFINE(HAVE_TM_TM_ZONE, 1, [Define if struct tm has a tm_zone member])
+fi
+
+# Cython Python Bindings
+AC_ARG_WITH([cython],
+ [AS_HELP_STRING([--without-cython],
+ [build Python bindings using Cython (default is yes)])],
+ [build_cython=false],
+ [build_cython=true])
+if test "$build_cython" = "true"; then
+ AC_PROG_CYTHON([3.0.0])
+ if [test "x$CYTHON" != "xfalse"]; then
+ AM_PATH_PYTHON([3.0], [CYTHON_PYTHON])
+ fi
+else
+ CYTHON=false
+fi
+if [test "x$CYTHON" != "xfalse"]; then
+ PKG_PROG_PKG_CONFIG
+ AC_MSG_CHECKING([for libplist Cython bindings])
+ if test -x "$PKG_CONFIG"; then
+ CYTHON_PLIST_INCLUDE_DIR=$($PKG_CONFIG --variable=includedir libplist-2.0)/plist/cython
+ fi
+ if [test ! -d "$CYTHON_PLIST_INCLUDE_DIR"]; then
+ CYTHON_PLIST_INCLUDE_DIR=.
+ cython_python_bindings=yes
+ AC_MSG_RESULT([Using built-in libplist Cython bindings (assuming this is a first build)])
+ else
+ AC_SUBST([CYTHON_PLIST_INCLUDE_DIR])
+ AC_MSG_RESULT([$CYTHON_PLIST_INCLUDE_DIR])
+ cython_python_bindings=yes
+ fi
+else
+ cython_python_bindings=no
+fi
+AM_CONDITIONAL([HAVE_CYTHON],[test "x$cython_python_bindings" = "xyes"])
+
+AC_ARG_WITH([tools],
+ [AS_HELP_STRING([--with-tools], [Build libplist tools. [default=yes]])],
+ [build_tools=${withval}],
+ [build_tools=yes]
+)
+AS_IF([test "x$build_tools" = "xyes"], [AC_DEFINE(BUILD_TOOLS, 1, [Define if we are building plist tools])])
+AM_CONDITIONAL(BUILD_TOOLS, test "x$build_tools" = "xyes")
+
+AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Wno-strict-aliasing")
+GLOBAL_LDFLAGS=""
+
+AC_ARG_ENABLE(debug,
+AS_HELP_STRING([--enable-debug],
+ [build debug message output code (default is no)]),
+[case "${enableval}" in
+ yes) debug=yes ;;
+ no) debug=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
+esac],
+[debug=no])
+
+if (test "x$debug" = "xyes"); then
+ AC_DEFINE(DEBUG, 1, [Define if debug message output code should be built.])
+ GLOBAL_CFLAGS+=" -g"
+fi
+
+if test "x$enable_static" = "xyes" -a "x$enable_shared" = "xno"; then
+ GLOBAL_CFLAGS+=" -DLIBPLIST_STATIC"
+fi
+
+GLOBAL_CXXFLAGS=$GLOBAL_CFLAGS
+AS_COMPILER_FLAG([-fvisibility=hidden], [
+ GLOBAL_CFLAGS+=" -fvisibility=hidden"
+], [])
+
+AC_SUBST(GLOBAL_CFLAGS)
+AC_SUBST(GLOBAL_CXXFLAGS)
+AC_SUBST(GLOBAL_LDFLAGS)
+
+case "$GLOBAL_CFLAGS" in
+ *-fvisibility=hidden*)
+ AC_DEFINE([HAVE_FVISIBILITY], [1], [Define if compiled with -fvisibility=hidden])
+esac
+
+AC_ARG_WITH([sanitizers],
+ [AS_HELP_STRING([--with-sanitizers],
+ [build libplist with sanitizers (default is no)])],
+ [build_sanitizers=${withval}],
+ [])
+
+AC_ARG_WITH([fuzzers],
+ [AS_HELP_STRING([--with-fuzzers],
+ [build fuzzers (implies --with-sanitizers, default is no)])],
+ [build_fuzzers=${withval}],
+ [build_fuzzers=no])
+
+AC_ARG_WITH([tests],
+ [AS_HELP_STRING([--without-tests],
+ [Do not build libplist test suite (default is yes)])],
+ [build_tests=${withval}],
+ [build_tests=${build_tools}])
+
+if test "x$build_tests" = "xyes" -a "x$build_tools" != "xyes"; then
+ AC_MSG_ERROR([Building the test suite requires plistutil. Reconfigure with --with-tools=yes to enable.])
+fi
+
+if test "x$build_fuzzers" = "xyes"; then
+ if test "x$build_sanitizers" = "xno"; then
+ AC_MSG_ERROR([--with-fuzzers implies --with-sanitizers, but --without-sanitizers was given. This does not work.])
+ fi
+ build_sanitizers=yes
+fi
+
+if test "x$build_sanitizers" = "xyes"; then
+ AS_COMPILER_FLAG([-fsanitize=address], [
+ SANITIZER_FLAGS+=" -fsanitize=address"
+ ASAN_AVAILABLE=yes
+ ], [])
+ if test "$ASAN_AVAILABLE" = "yes"; then
+ AS_COMPILER_FLAG([-fsanitize=address -fsanitize-address-use-after-scope], [
+ SANITIZER_FLAGS+=" -fsanitize-address-use-after-scope"
+ ], [])
+ SANITIZERS+="ASAN "
+ fi
+
+ AS_COMPILER_FLAG([-fsanitize=undefined], [
+ SANITIZER_FLAGS+=" -fsanitize=undefined"
+ UBSAN_AVAILABLE=yes
+ ], [])
+
+ if test "$UBSAN_AVAILABLE" = "yes"; then
+ SANITIZERS+="UBSAN "
+ fi
+
+ if test -z "$SANITIZER_FLAGS"; then
+ AC_MSG_ERROR([compiler doesn't support -fsanitize=address nor -fsanitize=undefined])
+ fi
+
+ COVERAGE_CHECKS="trace-pc-guard trace-cmp edge"
+ for COV_CHECK in $COVERAGE_CHECKS; do
+ AS_COMPILER_FLAG([-fsanitize-coverage=$COV_CHECK], [
+ if test -z "$SAN_COV_FLAGS"; then
+ SAN_COV_FLAGS="$COV_CHECK"
+ else
+ SAN_COV_FLAGS+=",$COV_CHECK"
+ fi
+ ], [])
+ done
+ if test -n "$SAN_COV_FLAGS"; then
+ SANITIZER_FLAGS+=" -fsanitize-coverage=$SAN_COV_FLAGS"
+ SANITIZERS+="+coverage "
+ else
+ AC_MSG_WARN([No sanitizer coverage supported by compiler])
+ fi
+
+ CFLAGS="-O1"
+
+ AS_COMPILER_FLAG([-fno-omit-frame-pointer], [
+ CFLAGS+=" -fno-omit-frame-pointer"
+ ], [])
+
+ AS_COMPILER_FLAG([-gline-tables-only], [
+ CFLAGS+=" -gline-tables-only"
+ ],
+ [
+ CFLAGS+=" -g"
+ ])
+
+ EXTRA_CONF+=" Enabled sanitizers ......: $SANITIZERS
+"
+
+ CFLAGS+=" $SANITIZER_FLAGS"
+ CXXFLAGS="$CFLAGS -std=c++11"
+fi
+
+if test "x$build_fuzzers" = "xyes"; then
+ IS_CLANG=`$CXX --version 2>/dev/null |grep clang`
+ case "$IS_CLANG" in
+ *clang*)
+ ;;
+ *)
+ AC_MSG_WARN([building fuzzers requires clang/clang++ (continuing anyway)])
+ ;;
+ esac
+
+ CFLAGS+=" -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
+
+ EXTRA_CONF+=" Build fuzzers ...........: yes
+"
+fi
+
+AM_CONDITIONAL([BUILD_FUZZERS],[test "x$build_fuzzers" = "xyes"])
+AM_CONDITIONAL([BUILD_TESTS],[test "x$build_tests" != "xno"])
+
+if test "x$build_fuzzers" = "xyes" || test "x$build_sanitizers" = "xyes"; then
+ AS_COMPILER_FLAGS(TEST_CFLAGS, [$CFLAGS])
+fi
+
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
+
+AC_CONFIG_FILES([
+Makefile
+libcnary/Makefile
+src/Makefile
+src/libplist-2.0.pc
+src/libplist++-2.0.pc
+include/Makefile
+tools/Makefile
+docs/Makefile
+cython/Makefile
+test/Makefile
+fuzz/Makefile
+doxygen.cfg
+])
+AC_OUTPUT
+
+echo "
+Configuration for $PACKAGE $VERSION:
+-------------------------------------------
+
+ Install prefix ..........: $prefix
+ Debug code ..............: $debug
+ Python bindings .........: $cython_python_bindings
+ Build tools .............: $build_tools
+ Build test suite ........: $build_tests
+$EXTRA_CONF
+ Now type 'make' to build $PACKAGE $VERSION,
+ and then 'make install' for installation.
+"
diff --git a/cython/CMakeLists.txt b/cython/CMakeLists.txt
deleted file mode 100644
index eac6bee..0000000
--- a/cython/CMakeLists.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-
-INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_PATH} ${CMAKE_CURRENT_SOURCE_DIR} )
-
-
-SET(plist_SRC
- ${CMAKE_CURRENT_BINARY_DIR}/plist.c )
-
-SET(plist_HDR
- ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd )
-
-ADD_CUSTOM_COMMAND(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/plist.c
- COMMAND ${CYTHON_EXECUTABLE} -o ${CMAKE_CURRENT_BINARY_DIR}/plist.c ${CMAKE_CURRENT_SOURCE_DIR}/plist.pyx
- DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/plist.pyx ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd
-)
-
-
-EXEC_PROGRAM("${PYTHON_EXECUTABLE}"
- ARGS "-c 'try:\n import distutils.sysconfig; print distutils.sysconfig.get_python_lib(1,0,\"${CMAKE_INSTALL_PREFIX}\")\nexcept: pass\n'"
- OUTPUT_VARIABLE DISTUTILS_PYTHON_ILIBRARY_PATH
- )
-
-PYTHON_ADD_MODULE(cython_plist plist.c plist_util.c)
-SET_TARGET_PROPERTIES(cython_plist PROPERTIES PREFIX "" OUTPUT_NAME plist)
-TARGET_LINK_LIBRARIES(cython_plist plist ${PYTHON_LIBRARIES})
-
-INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/plist${CMAKE_SHARED_MODULE_SUFFIX}
- DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH} )
-INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/plist.pxd
- DESTINATION include/plist/cython COMPONENT dev)
diff --git a/cython/Makefile.am b/cython/Makefile.am
new file mode 100644
index 0000000..bbc51c9
--- /dev/null
+++ b/cython/Makefile.am
@@ -0,0 +1,57 @@
+AM_CPPFLAGS = -I$(top_srcdir)/include
+
+AM_CFLAGS = $(GLOBAL_CFLAGS)
+AM_LDFLAGS = $(GLOBAL_LDFLAGS)
+
+EXTRA_DIST = \
+ plist.pyx \
+ plist.pxd
+
+if HAVE_CYTHON
+
+BUILT_SOURCES = plist.c
+
+PXDINCLUDES = \
+ plist.pxd \
+ $(CYTHON_PLIST_INCLUDE_DIR)/plist.pxd
+
+CLEANFILES = \
+ *.pyc \
+ *.pyo \
+ plist.c
+
+plistdir = $(pyexecdir)
+plist_LTLIBRARIES = plist.la
+plist_la_SOURCES = \
+ plist_util.c \
+ plist_util.h \
+ plist.pyx
+
+plist_la_CFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/src \
+ $(PYTHON_CPPFLAGS) \
+ $(AM_CFLAGS) \
+ -Wno-shadow \
+ -Wno-redundant-decls \
+ -Wno-switch-default \
+ -Wno-strict-aliasing \
+ -Wno-implicit-function-declaration \
+ -fvisibility=default
+
+plist_la_LDFLAGS = -module -avoid-version $(PYTHON_LIBS) $(AM_LDFLAGS) -shared -export-dynamic
+plist_la_LIBADD = $(top_builddir)/src/libplist-2.0.la
+
+if WIN32
+plist_la_LDFLAGS += -no-undefined
+endif
+
+plist.c: plist.pyx $(PXDINCLUDES) $(PXIINCLUDES)
+
+.pyx.c:
+ $(CYTHON) -I$(CYTHON_PLIST_INCLUDE_DIR) -I$(top_srcdir)/src -o $@ $<
+
+pxddir = $(includedir)/plist/cython
+pxd_DATA = plist.pxd
+
+endif
diff --git a/cython/plist.pxd b/cython/plist.pxd
index a31419d..5a41bf8 100644
--- a/cython/plist.pxd
+++ b/cython/plist.pxd
@@ -1,3 +1,5 @@
+from libc.stdint cimport *
+
cdef extern from "plist/plist.h":
ctypedef void *plist_t
ctypedef void *plist_dict_iter
@@ -14,10 +16,19 @@ cdef class Node:
cdef class Bool(Node):
cpdef set_value(self, object value)
cpdef bint get_value(self)
-
+
cdef class Integer(Node):
cpdef set_value(self, object value)
- cpdef int get_value(self)
+ cpdef get_value(self)
+ cpdef bint is_negative(self)
+
+cdef class Uid(Node):
+ cpdef set_value(self, object value)
+ cpdef uint64_t get_value(self)
+
+cdef class Key(Node):
+ cpdef set_value(self, object value)
+ cpdef unicode get_value(self)
cdef class Real(Node):
cpdef set_value(self, object value)
@@ -59,5 +70,10 @@ cdef class Array(Node):
cpdef object from_xml(xml)
cpdef object from_bin(bytes bin)
+cpdef object load(fp, fmt=*, use_builtin_types=*, dict_type=*)
+cpdef object loads(data, fmt=*, use_builtin_types=*, dict_type=*)
+cpdef object dump(value, fp, fmt=*, sort_keys=*, skipkeys=*)
+cpdef object dumps(value, fmt=*, sort_keys=*, skipkeys=*)
+
cdef object plist_t_to_node(plist_t c_plist, bint managed=*)
cdef plist_t native_to_plist_t(object native)
diff --git a/cython/plist.pyx b/cython/plist.pyx
index 3716a9c..73912d8 100644
--- a/cython/plist.pyx
+++ b/cython/plist.pyx
@@ -1,21 +1,11 @@
-cdef extern from *:
- ctypedef unsigned char uint8_t
- ctypedef short int int16_t
- ctypedef unsigned short int uint16_t
- ctypedef unsigned int uint32_t
- ctypedef int int32_t
-IF UNAME_MACHINE == 'x86_64':
- ctypedef unsigned long int uint64_t
-ELSE:
- ctypedef unsigned long long int uint64_t
-
cimport cpython
cimport libc.stdlib
+from libc.stdint cimport *
cdef extern from *:
ctypedef enum plist_type:
PLIST_BOOLEAN,
- PLIST_UINT,
+ PLIST_INT,
PLIST_REAL,
PLIST_STRING,
PLIST_ARRAY,
@@ -23,6 +13,8 @@ cdef extern from *:
PLIST_DATE,
PLIST_DATA,
PLIST_KEY,
+ PLIST_UID,
+ PLIST_NULL,
PLIST_NONE
plist_t plist_new_bool(uint8_t val)
@@ -33,13 +25,24 @@ cdef extern from *:
void plist_get_uint_val(plist_t node, uint64_t *val)
void plist_set_uint_val(plist_t node, uint64_t val)
+ plist_t plist_new_int(int64_t val)
+ void plist_get_int_val(plist_t node, int64_t *val)
+ void plist_set_int_val(plist_t node, int64_t val)
+
plist_t plist_new_real(double val)
void plist_get_real_val(plist_t node, double *val)
void plist_set_real_val(plist_t node, double val)
- plist_t plist_new_date(int32_t sec, int32_t usec)
- void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
- void plist_set_date_val(plist_t node, int32_t sec, int32_t usec)
+ plist_t plist_new_unix_date(int64_t sec)
+ void plist_get_unix_date_val(plist_t node, int64_t *sec)
+ void plist_set_unix_date_val(plist_t node, int64_t sec)
+
+ void plist_get_key_val(plist_t node, char **val)
+ void plist_set_key_val(plist_t node, char *val)
+
+ plist_t plist_new_uid(uint64_t val)
+ void plist_get_uid_val(plist_t node, uint64_t *val)
+ void plist_set_uid_val(plist_t node, uint64_t val)
plist_t plist_new_string(char *val)
void plist_get_string_val(plist_t node, char **val)
@@ -49,6 +52,8 @@ cdef extern from *:
void plist_get_data_val(plist_t node, char **val, uint64_t * length)
void plist_set_data_val(plist_t node, char *val, uint64_t length)
+ plist_t plist_new_null();
+
plist_t plist_new_dict()
int plist_dict_get_size(plist_t node)
plist_t plist_dict_get_item(plist_t node, char* key)
@@ -76,11 +81,11 @@ cdef extern from *:
plist_t plist_get_parent(plist_t node)
plist_type plist_get_node_type(plist_t node)
- void plist_set_type(plist_t node, plist_type type)
-
void plist_from_xml(char *plist_xml, uint32_t length, plist_t * plist)
void plist_from_bin(char *plist_bin, uint32_t length, plist_t * plist)
+ int plist_int_val_is_negative(plist_t node);
+
cdef class Node:
def __init__(self, *args, **kwargs):
self._c_managed = True
@@ -116,7 +121,7 @@ cdef class Node:
plist_to_bin(self._c_node, &out, &length)
try:
- return cpython.PyString_FromStringAndSize(out, length)
+ return bytes(out[:length])
finally:
if out != NULL:
libc.stdlib.free(out)
@@ -181,19 +186,22 @@ cdef Bool Bool_factory(plist_t c_node, bint managed=True):
cdef class Integer(Node):
def __cinit__(self, object value=None, *args, **kwargs):
if value is None:
- self._c_node = plist_new_uint(0)
+ self._c_node = plist_new_int(0)
else:
- self._c_node = plist_new_uint(int(value))
+ if value < 0 or value <= INT64_MAX:
+ self._c_node = plist_new_int(int(value))
+ else:
+ self._c_node = plist_new_uint(int(value))
def __repr__(self):
- cdef int i = self.get_value()
- return '<Integer: %s>' % i
+ return '<Integer: %s>' % self.get_value()
def __int__(self):
return self.get_value()
def __float__(self):
- return float(self.get_value())
+ v = self.get_value()
+ return float(v)
def __richcmp__(self, other, op):
cdef int i = self.get_value()
@@ -213,10 +221,18 @@ cdef class Integer(Node):
cpdef set_value(self, object value):
plist_set_uint_val(self._c_node, int(value))
- cpdef int get_value(self):
- cdef uint64_t value
- plist_get_uint_val(self._c_node, &value)
- return value
+ cpdef get_value(self):
+ cdef int64_t ivalue
+ cdef uint64_t uvalue
+ if self.is_negative():
+ plist_get_int_val(self._c_node, &ivalue)
+ return int(ivalue)
+ else:
+ plist_get_uint_val(self._c_node, &uvalue)
+ return int(uvalue)
+
+ cpdef bint is_negative(self):
+ return plist_int_val_is_negative(self._c_node);
cdef Integer Integer_factory(plist_t c_node, bint managed=True):
cdef Integer instance = Integer.__new__(Integer)
@@ -270,8 +286,139 @@ cdef Real Real_factory(plist_t c_node, bint managed=True):
instance._c_node = c_node
return instance
+cdef class Uid(Node):
+ def __cinit__(self, object value=None, *args, **kwargs):
+ if value is None:
+ self._c_node = plist_new_uid(0)
+ else:
+ self._c_node = plist_new_uid(int(value))
+
+ def __repr__(self):
+ cdef uint64_t i = self.get_value()
+ return '<Uid: %s>' % i
+
+ def __int__(self):
+ return self.get_value()
+
+ def __float__(self):
+ v = self.get_value()
+ return float(v)
+
+ def __richcmp__(self, other, op):
+ cdef int i = self.get_value()
+ if op == 0:
+ return i < other
+ if op == 1:
+ return i <= other
+ if op == 2:
+ return i == other
+ if op == 3:
+ return i != other
+ if op == 4:
+ return i > other
+ if op == 5:
+ return i >= other
+
+ cpdef set_value(self, object value):
+ plist_set_uid_val(self._c_node, int(value))
+
+ cpdef uint64_t get_value(self):
+ cdef uint64_t value
+ plist_get_uid_val(self._c_node, &value)
+ return value
+
+cdef Uid Uid_factory(plist_t c_node, bint managed=True):
+ cdef Uid instance = Uid.__new__(Uid)
+ instance._c_managed = managed
+ instance._c_node = c_node
+ return instance
+
+cdef class Null(Node):
+ def __cinit__(self, object value=None, *args, **kwargs):
+ self._c_node = plist_new_null()
+
+ def __repr__(self):
+ cdef uint64_t i = self.get_value()
+ return '<Null>'
+
+cdef Null Null_factory(plist_t c_node, bint managed=True):
+ cdef Null instance = Null.__new__(Null)
+ instance._c_managed = managed
+ instance._c_node = c_node
+ return instance
+
from cpython cimport PY_MAJOR_VERSION
+cdef class Key(Node):
+ def __cinit__(self, object value=None, *args, **kwargs):
+ cdef:
+ char* c_utf8_data = NULL
+ bytes utf8_data
+ if value is None:
+ raise ValueError("Requires a value")
+ else:
+ if isinstance(value, unicode):
+ utf8_data = value.encode('utf-8')
+ elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
+ value.encode('ascii') # trial decode
+ utf8_data = value.encode('ascii')
+ else:
+ raise ValueError("Requires unicode input, got %s" % type(value))
+ c_utf8_data = utf8_data
+ self._c_node = plist_new_string("")
+ plist_set_key_val(self._c_node, c_utf8_data)
+
+ def __repr__(self):
+ s = self.get_value()
+ return '<Key: %s>' % s.encode('utf-8')
+
+ def __richcmp__(self, other, op):
+ cdef unicode s = self.get_value()
+ if op == 0:
+ return s < other
+ if op == 1:
+ return s <= other
+ if op == 2:
+ return s == other
+ if op == 3:
+ return s != other
+ if op == 4:
+ return s > other
+ if op == 5:
+ return s >= other
+
+ cpdef set_value(self, object value):
+ cdef:
+ char* c_utf8_data = NULL
+ bytes utf8_data
+ if value is None:
+ plist_set_key_val(self._c_node, c_utf8_data)
+ else:
+ if isinstance(value, unicode):
+ utf8_data = value.encode('utf-8')
+ elif (PY_MAJOR_VERSION < 3) and isinstance(value, str):
+ value.encode('ascii') # trial decode
+ utf8_data = value.encode('ascii')
+ else:
+ raise ValueError("Requires unicode input, got %s" % type(value))
+ c_utf8_data = utf8_data
+ plist_set_key_val(self._c_node, c_utf8_data)
+
+ cpdef unicode get_value(self):
+ cdef:
+ char* c_value = NULL
+ plist_get_key_val(self._c_node, &c_value)
+ try:
+ return cpython.PyUnicode_DecodeUTF8(c_value, len(c_value), 'strict')
+ finally:
+ libc.stdlib.free(c_value)
+
+cdef Key Key_factory(plist_t c_node, bint managed=True):
+ cdef Key instance = Key.__new__(Key)
+ instance._c_managed = managed
+ instance._c_node = c_node
+ return instance
+
cdef class String(Node):
def __cinit__(self, object value=None, *args, **kwargs):
cdef:
@@ -342,19 +489,20 @@ cdef String String_factory(plist_t c_node, bint managed=True):
return instance
cdef extern from "plist_util.h":
- void datetime_to_ints(object obj, int32_t* sec, int32_t* usec)
- object ints_to_datetime(int32_t sec, int32_t usec)
+ int64_t datetime_to_timestamp(object obj)
+ object timestamp_to_datetime(int64_t sec)
int check_datetime(object obj)
cdef plist_t create_date_plist(value=None):
cdef plist_t node = NULL
- cdef int32_t secs
- cdef int32_t usecs
if value is None:
- node = plist_new_date(0, 0)
+ node = plist_new_unix_date(0)
+ elif isinstance(value, int):
+ node = plist_new_unix_date(value)
+ elif isinstance(value, float):
+ node = plist_new_unix_date(int(value))
elif check_datetime(value):
- datetime_to_ints(value, &secs, &usecs)
- node = plist_new_date(secs, usecs)
+ node = plist_new_unix_date(datetime_to_timestamp(value))
return node
cdef class Date(Node):
@@ -381,19 +529,21 @@ cdef class Date(Node):
return d >= other
cpdef object get_value(self):
- cdef int32_t secs = 0
- cdef int32_t usecs = 0
- cdef object result
- plist_get_date_val(self._c_node, &secs, &usecs)
- return ints_to_datetime(secs, usecs)
+ cdef int64_t secs = 0
+ plist_get_unix_date_val(self._c_node, &secs)
+ return timestamp_to_datetime(secs)
cpdef set_value(self, object value):
- cdef int32_t secs
- cdef int32_t usecs
- if not check_datetime(value):
- raise ValueError("Expected a datetime")
- datetime_to_ints(value, &secs, &usecs)
- plist_set_date_val(self._c_node, secs, usecs)
+ cdef int64_t secs = 0
+ if isinstance(value, int):
+ secs = value
+ elif isinstance(value, float):
+ secs = int(value)
+ elif check_datetime(value):
+ secs = datetime_to_timestamp(value)
+ else:
+ raise ValueError("Expected int or datetime")
+ plist_set_unix_date_val(self._c_node, secs)
cdef Date Date_factory(plist_t c_node, bint managed=True):
cdef Date instance = Date.__new__(Date)
@@ -434,7 +584,7 @@ cdef class Data(Node):
plist_get_data_val(self._c_node, &val, &length)
try:
- return cpython.PyString_FromStringAndSize(val, length)
+ return bytes(val[:length])
finally:
libc.stdlib.free(val)
@@ -456,7 +606,7 @@ cdef plist_t create_dict_plist(object value=None):
if value is not None and isinstance(value, dict):
for key, item in value.items():
c_node = native_to_plist_t(item)
- plist_dict_insert_item(node, key, c_node)
+ plist_dict_set_item(node, key, c_node)
c_node = NULL
return node
@@ -480,7 +630,12 @@ cdef class Dict(Node):
plist_dict_next_item(self._c_node, it, &key, &subnode);
while subnode is not NULL:
- cpython.PyDict_SetItem(self._map, key, plist_t_to_node(subnode, False))
+ py_key = key
+
+ if PY_MAJOR_VERSION >= 3:
+ py_key = py_key.decode('utf-8')
+
+ cpython.PyDict_SetItem(self._map, py_key, plist_t_to_node(subnode, False))
subnode = NULL
libc.stdlib.free(key)
key = NULL
@@ -512,9 +667,7 @@ cdef class Dict(Node):
return '<Dict: %s>' % self._map
cpdef dict get_value(self):
- cdef dict result = cpython.PyDict_New()
return dict([(key, value.get_value()) for key, value in self.items()])
- return result
cpdef set_value(self, dict value):
plist_free(self._c_node)
@@ -546,7 +699,6 @@ cdef class Dict(Node):
cpdef list values(self):
return cpython.PyDict_Values(self._map)
- return self._map.values()
cpdef object itervalues(self):
return self._map.itervalues()
@@ -561,12 +713,12 @@ cdef class Dict(Node):
else:
n = plist_t_to_node(native_to_plist_t(value), False)
- plist_dict_insert_item(self._c_node, key, n._c_node)
+ plist_dict_set_item(self._c_node, key, n._c_node)
self._map[key] = n
def __delitem__(self, key):
cpython.PyDict_DelItem(self._map, key)
- plist_dict_remove_item(self._c_node, key)
+ plist_dict_remove_item(self._c_node, key.encode('utf-8'))
cdef Dict Dict_factory(plist_t c_node, bint managed=True):
cdef Dict instance = Dict.__new__(Dict)
@@ -598,7 +750,7 @@ cdef class Array(Node):
cdef uint32_t size = plist_array_get_size(self._c_node)
cdef plist_t subnode = NULL
- for i from 0 <= i < size:
+ for i in range(size):
subnode = plist_array_get_item(self._c_node, i)
self._array.append(plist_t_to_node(subnode, False))
@@ -637,7 +789,10 @@ cdef class Array(Node):
return self._array.__iter__()
def __getitem__(self, index):
- return self._array[index]
+ value = self._array[index]
+ if isinstance(value, list):
+ return [item.copy() for item in value]
+ return value.copy()
def __setitem__(self, index, value):
cdef Node n
@@ -699,7 +854,7 @@ cdef plist_t native_to_plist_t(object native):
return plist_new_string(native)
if isinstance(native, bool):
return plist_new_bool(<bint>native)
- if isinstance(native, int) or isinstance(native, long):
+ if isinstance(native, int):
return plist_new_uint(native)
if isinstance(native, float):
return plist_new_real(native)
@@ -714,8 +869,10 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True):
cdef plist_type t = plist_get_node_type(c_plist)
if t == PLIST_BOOLEAN:
return Bool_factory(c_plist, managed)
- if t == PLIST_UINT:
+ if t == PLIST_INT:
return Integer_factory(c_plist, managed)
+ if t == PLIST_KEY:
+ return Key_factory(c_plist, managed)
if t == PLIST_REAL:
return Real_factory(c_plist, managed)
if t == PLIST_STRING:
@@ -728,5 +885,105 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True):
return Date_factory(c_plist, managed)
if t == PLIST_DATA:
return Data_factory(c_plist, managed)
+ if t == PLIST_UID:
+ return Uid_factory(c_plist, managed)
+ if t == PLIST_NULL:
+ return Null_factory(c_plist, managed)
if t == PLIST_NONE:
return None
+
+# This is to match up with the new plistlib API
+# http://docs.python.org/dev/library/plistlib.html
+# dump() and dumps() are not yet implemented
+FMT_XML = 1
+FMT_BINARY = 2
+
+cpdef object load(fp, fmt=None, use_builtin_types=True, dict_type=dict):
+ is_binary = fp.read(6) == 'bplist'
+ fp.seek(0)
+
+ cdef object cb = None
+
+ if not fmt:
+ if is_binary:
+ if 'b' not in fp.mode:
+ raise IOError('File handle must be opened in binary (b) mode to read binary property lists')
+ cb = from_bin
+ else:
+ cb = from_xml
+ else:
+ if fmt not in (FMT_XML, FMT_BINARY):
+ raise ValueError('Format must be constant FMT_XML or FMT_BINARY')
+ if fmt == FMT_BINARY:
+ cb = from_bin
+ elif fmt == FMT_XML:
+ cb = from_xml
+
+ if is_binary and fmt == FMT_XML:
+ raise ValueError('Cannot parse binary property list as XML')
+ elif not is_binary and fmt == FMT_BINARY:
+ raise ValueError('Cannot parse XML property list as binary')
+
+ return cb(fp.read())
+
+cpdef object loads(data, fmt=None, use_builtin_types=True, dict_type=dict):
+ is_binary = data[0:6] == 'bplist'
+
+ cdef object cb = None
+
+ if fmt is not None:
+ if fmt not in (FMT_XML, FMT_BINARY):
+ raise ValueError('Format must be constant FMT_XML or FMT_BINARY')
+ if fmt == FMT_BINARY:
+ cb = from_bin
+ else:
+ cb = from_xml
+ else:
+ if is_binary:
+ cb = from_bin
+ else:
+ cb = from_xml
+
+ if is_binary and fmt == FMT_XML:
+ raise ValueError('Cannot parse binary property list as XML')
+ elif not is_binary and fmt == FMT_BINARY:
+ raise ValueError('Cannot parse XML property list as binary')
+
+ return cb(data)
+
+cpdef object dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False):
+ fp.write(dumps(value, fmt=fmt))
+
+cpdef object dumps(value, fmt=FMT_XML, sort_keys=True, skipkeys=False):
+ if fmt not in (FMT_XML, FMT_BINARY):
+ raise ValueError('Format must be constant FMT_XML or FMT_BINARY')
+
+ if check_datetime(value):
+ node = Date(value)
+ elif isinstance(value, unicode):
+ node = String(value)
+ elif PY_MAJOR_VERSION >= 3 and isinstance(value, bytes):
+ node = Data(value)
+ elif isinstance(value, str):
+ # See if this is binary
+ try:
+ node = String(value)
+ except ValueError:
+ node = Data(value)
+ elif isinstance(value, bool):
+ node = Bool(value)
+ elif isinstance(value, int):
+ node = Integer(value)
+ elif isinstance(value, float):
+ node = Real(value)
+ elif isinstance(value, dict):
+ node = Dict(value)
+ elif type(value) in (list, set, tuple):
+ node = Array(value)
+ else:
+ node = value
+
+ if fmt == FMT_XML:
+ return node.to_xml().encode('utf-8')
+
+ return node.to_bin()
diff --git a/cython/plist_util.c b/cython/plist_util.c
index 70c5be3..27084fa 100644
--- a/cython/plist_util.c
+++ b/cython/plist_util.c
@@ -3,39 +3,35 @@
#include <time.h>
#include <datetime.h>
-void datetime_to_ints(PyObject* obj, int32_t* sec, int32_t* usec) {
+int64_t datetime_to_timestamp(PyObject* obj) {
PyDateTime_IMPORT;
if (!PyDateTime_Check(obj)) {
- PyErr_SetString(PyExc_ValueError,"Expected a datetime");
- sec = NULL;
- usec = NULL;
- return;
+ PyErr_SetString(PyExc_ValueError,"Expected a datetime");
+ return 0;
}
- struct tm t = {
- PyDateTime_DATE_GET_SECOND(obj),
- PyDateTime_DATE_GET_MINUTE(obj),
- PyDateTime_DATE_GET_HOUR(obj),
- PyDateTime_GET_DAY(obj),
- PyDateTime_GET_MONTH(obj)-1,
- PyDateTime_GET_YEAR(obj)-1900,
- 0,0,0
- };
- *sec = (int32_t)mktime(&t);
- *usec = PyDateTime_DATE_GET_MICROSECOND(obj);
+ struct tm t;
+ memset(&t, 0, sizeof(t));
+ t.tm_sec = PyDateTime_DATE_GET_SECOND(obj);
+ t.tm_min = PyDateTime_DATE_GET_MINUTE(obj);
+ t.tm_hour = PyDateTime_DATE_GET_HOUR(obj);
+ t.tm_mday = PyDateTime_GET_DAY(obj);
+ t.tm_mon = PyDateTime_GET_MONTH(obj)-1;
+ t.tm_year = PyDateTime_GET_YEAR(obj)-1900;
+ return mktime(&t);
}
-PyObject* ints_to_datetime(int32_t sec, int32_t usec) {
- time_t sec_tt = sec;
+PyObject* timestamp_to_datetime(int64_t sec) {
+ time_t sec_tt = sec;
struct tm* t = gmtime(&sec_tt);
if(t){
- PyDateTime_IMPORT;
- return PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, usec);
+ PyDateTime_IMPORT;
+ return PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, 0);
}
- return NULL;
+ return NULL;
}
int check_datetime(PyObject* ob) {
- if(ob){
- PyDateTime_IMPORT;
- return PyDateTime_Check(ob);
- }
- return 0;
+ if(ob){
+ PyDateTime_IMPORT;
+ return PyDateTime_Check(ob);
+ }
+ return 0;
}
diff --git a/cython/plist_util.h b/cython/plist_util.h
index fbf56b6..e0a84b5 100644
--- a/cython/plist_util.h
+++ b/cython/plist_util.h
@@ -1,5 +1,5 @@
#include <Python.h>
-void datetime_to_ints(PyObject* obj, int32_t* sec, int32_t* usec);
-PyObject* ints_to_datetime(int32_t sec, int32_t usec);
+int64_t datetime_to_timestamp(PyObject* obj);
+PyObject* timestamp_to_datetime(int64_t sec);
int check_datetime(PyObject* obj);
diff --git a/docs/Makefile.am b/docs/Makefile.am
new file mode 100644
index 0000000..f025791
--- /dev/null
+++ b/docs/Makefile.am
@@ -0,0 +1,3 @@
+man_MANS = plistutil.1
+
+EXTRA_DIST = $(man_MANS)
diff --git a/docs/doxygen/custom.css b/docs/doxygen/custom.css
new file mode 100644
index 0000000..e9f1ea8
--- /dev/null
+++ b/docs/doxygen/custom.css
@@ -0,0 +1,1723 @@
+body, table, div, p, dl {
+ font: 14px -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
+ line-height: 1.5;
+}
+
+/* @group Heading Levels */
+
+h1.groupheader {
+ font-size: 28px;
+}
+
+.title {
+ font: inherit;
+ font-size: 2rem;
+ font-weight: bold;
+ margin: 0;
+ padding-bottom: 0.3rem;
+ border-bottom: 1px solid #eaecef;
+}
+
+h2.groupheader {
+ border-bottom: none;
+ color: rgb(36, 41, 46);
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin: 1.75rem 0px 1rem 0px;
+ padding: 0 0.3rem 0 0;
+ border-bottom: 1px solid #eaecef;
+ width: 100%;
+}
+
+h3.groupheader {
+ font-size: 100%;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ -webkit-transition: text-shadow 0.5s linear;
+ -moz-transition: text-shadow 0.5s linear;
+ -ms-transition: text-shadow 0.5s linear;
+ -o-transition: text-shadow 0.5s linear;
+ transition: text-shadow 0.5s linear;
+}
+
+.textblock h1,
+.textblock h2,
+.textblock h3,
+.textblock h4,
+.textblock h5,
+.textblock h6 {
+ margin: 1.75rem 0px 1rem 0px;
+ padding: 0 0.3rem 0 0;
+ border-bottom: 1px solid #eaecef;
+}
+
+h1 {
+ font-size: 1.75rem;
+}
+
+h2 {
+ color: rgb(36, 41, 46);
+ font-size: 1.5rem;
+ font-weight: bold;
+ margin: 1.75rem 0 1rem 0px;
+}
+
+h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow {
+ text-shadow: 0 0 15px #1fa4a9;
+}
+
+dt {
+ font-weight: bold;
+}
+
+div.multicol {
+ -moz-column-gap: 1em;
+ -webkit-column-gap: 1em;
+ -moz-column-count: 3;
+ -webkit-column-count: 3;
+}
+
+p.startli, p.startdd, p.starttd {
+ margin-top: 2px;
+}
+
+p.endli {
+ margin-bottom: 0px;
+}
+
+p.enddd {
+ margin-bottom: 4px;
+}
+
+p.endtd {
+ margin-bottom: 2px;
+}
+
+/* @end */
+
+caption {
+ font-weight: bold;
+}
+
+span.legend {
+ font-size: 70%;
+ text-align: center;
+}
+
+h3.version {
+ font-size: 90%;
+ text-align: center;
+}
+
+div.qindex, div.navtab {
+ background-color: #fff;
+ border: 1px solid #d1d5da;
+ padding: 0;
+ border-radius: 3px;
+ text-align: left;
+}
+
+div.qindex, div.navpath {
+ width: auto;
+ line-height: 140%;
+}
+
+div.navtab {
+ margin-right: 2rem;
+}
+
+@media (max-width: 576px) {
+ .contents td.top:not(.mempage),
+ div.navtab {
+ display: none;
+ }
+}
+
+div.navtab table {
+ border-spacing: 0;
+}
+
+div.navtab table td.navtab {
+ position: relative;
+ display: block;
+ padding: 8px 10px;
+ border-bottom: 1px solid #e1e4e8;
+}
+
+div.navtab table td.navtab:hover {
+ background-color: #f6f8fa;
+}
+
+/* @group Link Styling */
+
+a {
+ color: #005082;
+ font-weight: normal;
+ text-decoration: none;
+}
+
+.contents a:visited {
+ color: #005082;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a.qindex {
+ font-weight: bold;
+}
+
+a.qindexHL {
+ font-weight: bold;
+ background-color: #fff;
+ color: inherit;
+ border: none;
+}
+
+a.qindexHL:before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 2px;
+ content: "";
+ background-color: #f7ae1a;
+}
+
+.contents a.qindexHL:visited {
+ color: inherit;
+}
+
+a.el {
+ font-weight: normal;
+}
+
+a.elRef {
+}
+
+a.code, a.code:visited {
+ color: #3465a4;
+}
+
+a.codeRef, a.codeRef:visited {
+ color: #3465a4;
+}
+
+/* @end */
+
+dl.el {
+ margin-left: -1cm;
+}
+
+pre.fragment {
+ border: 1px solid rgb(221, 221, 221);
+ border-radius: 3px;
+ background-color: rgb(248, 248, 248);
+ padding: 1rem;
+ margin: 1rem 0px;
+ overflow: auto;
+ word-wrap: break-word;
+ font-size: inherit;
+ line-height: inherit;
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+}
+
+div.fragment {
+ padding: 1rem;
+ margin: 1rem 0px;
+ border: solid 1px rgb(221, 221, 221);
+ border-radius: 3px;
+ background-color: rgb(248, 248, 248);
+ color: rgb(0,0,0);
+}
+
+div.line {
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+ font-size: inherit;
+ min-height: 1rem;
+ line-height: inherit;
+ text-wrap: unrestricted;
+ white-space: -moz-pre-wrap; /* Moz */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ white-space: pre-wrap; /* CSS3 */
+ word-wrap: break-word; /* IE 5.5+ */
+ text-indent: initial;
+ padding-left: initial;
+ padding-bottom: 0px;
+ margin: 0px;
+ -webkit-transition-property: background-color, box-shadow;
+ -webkit-transition-duration: 0.5s;
+ -moz-transition-property: background-color, box-shadow;
+ -moz-transition-duration: 0.5s;
+ -ms-transition-property: background-color, box-shadow;
+ -ms-transition-duration: 0.5s;
+ -o-transition-property: background-color, box-shadow;
+ -o-transition-duration: 0.5s;
+ transition-property: background-color, box-shadow;
+ transition-duration: 0.5s;
+ display: inline-block;
+ min-width: 100%;
+}
+
+div.line.glow {
+ background-color: cyan;
+ box-shadow: 0 0 10px cyan;
+}
+
+
+span.lineno {
+ padding-right: 4px;
+ text-align: right;
+ border-right: 2px solid #0F0;
+ background-color: #E8E8E8;
+ white-space: pre;
+}
+span.lineno a {
+ background-color: #D8D8D8;
+}
+
+span.lineno a:hover {
+ background-color: #C8C8C8;
+}
+
+div.ah {
+ background-color: black;
+ font-weight: bold;
+ color: #ffffff;
+ margin-bottom: 3px;
+ margin-top: 3px;
+ padding: 0.2em;
+ border: solid thin #333;
+ border-radius: 0.5em;
+ -webkit-border-radius: .5em;
+ -moz-border-radius: .5em;
+ box-shadow: 2px 2px 3px #999;
+ -webkit-box-shadow: 2px 2px 3px #999;
+ -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444));
+ background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000);
+}
+
+div.groupHeader {
+ margin-left: 0px;
+ margin-top: 9px;
+ margin-bottom: 4.7px;
+
+ font-size: 19px;
+ font-weight: normal;
+}
+
+div.groupText {
+ margin-left: 16px;
+ font-style: italic;
+}
+
+body {
+ background-color: white;
+ color: rgb(36, 41, 46);
+ margin: 0;
+}
+
+div.contents {
+ padding: 1rem 2rem;
+ margin: 0;
+}
+
+td.indexkey {
+ background-color: #EBEFF6;
+ font-weight: bold;
+ border: 1px solid #C4CFE5;
+ margin: 2px 0px 2px 0;
+ padding: 2px 10px;
+ white-space: nowrap;
+ vertical-align: top;
+}
+
+td.indexvalue {
+ background-color: #EBEFF6;
+ border: 1px solid #C4CFE5;
+ padding: 2px 10px;
+ margin: 2px 0px;
+}
+
+tr.memlist {
+ background-color: #EEF1F7;
+}
+
+p.formulaDsp {
+ text-align: center;
+}
+
+img.formulaDsp {
+}
+
+img.formulaInl {
+ vertical-align: middle;
+}
+
+div.center {
+ text-align: center;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ padding: 0px;
+}
+
+div.center img {
+ border: 0px;
+}
+
+address.footer {
+ text-align: left;
+ margin-left: 2rem;
+ margin-right: 2rem;
+ margin-bottom: 1rem;
+}
+
+img.footer {
+ border: 0px;
+ vertical-align: middle;
+}
+
+/* @group Code Colorization */
+
+span.keyword {
+ color: #008000;
+}
+
+span.keywordtype {
+ color: #604020;
+}
+
+span.keywordflow {
+ color: #e08000;
+}
+
+span.comment {
+ color: #800000;
+}
+
+span.preprocessor {
+ color: #806020;
+}
+
+span.stringliteral {
+ color: #002080;
+}
+
+span.charliteral {
+ color: #008080;
+}
+
+span.vhdldigit {
+ color: #ff00ff;
+}
+
+span.vhdlchar {
+ color: #000000;
+}
+
+span.vhdlkeyword {
+ color: #700070;
+}
+
+span.vhdllogic {
+ color: #ff0000;
+}
+
+blockquote {
+ background-color: #F7F8FB;
+ border-left: 2px solid #9CAFD4;
+ margin: 0 24px 0 4px;
+ padding: 0 12px 0 16px;
+}
+
+/* @end */
+
+td.tiny {
+ font-size: 75%;
+}
+
+.dirtab {
+ padding: 4px;
+ border-collapse: collapse;
+ border: 1px solid #A3B4D7;
+}
+
+th.dirtab {
+ background: #EBEFF6;
+ font-weight: bold;
+}
+
+hr {
+ height: 0px;
+ border: none;
+ border-top: 1px solid #e1e4e8;
+}
+
+hr.footer {
+ height: 0px;
+ border-top: 1px solid #e1e4e8;
+ margin-left: 2rem;
+ margin-right: 2rem;
+}
+
+/* @group Member Descriptions */
+
+table.memberdecls {
+ border-spacing: inherit;
+ padding: 0px;
+ background-color: transparent;
+ border-color: #c8e1ff;
+}
+
+table.memberdecls tr:not(.heading) {
+ background-color: #f1f8ff;
+}
+
+table.memberdecls tr:not(.heading) td.ititle {
+ background-color: #fff;
+ border: none;
+}
+
+.memberdecls td, .fieldtable tr {
+ -webkit-transition-property: background-color, box-shadow;
+ -webkit-transition-duration: 0.5s;
+ -moz-transition-property: background-color, box-shadow;
+ -moz-transition-duration: 0.5s;
+ -ms-transition-property: background-color, box-shadow;
+ -ms-transition-duration: 0.5s;
+ -o-transition-property: background-color, box-shadow;
+ -o-transition-duration: 0.5s;
+ transition-property: background-color, box-shadow;
+ transition-duration: 0.5s;
+}
+
+.memberdecls td.glow, .fieldtable tr.glow {
+ background-color: #1fa4a9;
+ box-shadow: none;
+}
+
+.mdescLeft, .mdescRight,
+.memItemLeft, .memItemRight,
+.memTemplItemLeft, .memTemplItemRight, .memTemplParams {
+ background-color: transparent;
+ border: none;
+ margin: 0;
+ padding: 1rem 1rem 0.5rem 1rem;
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+}
+
+.mdescLeft,
+.memItemLeft,
+.memTemplItemLeft {
+ border-left: 1px solid #c8e1ff;
+}
+
+.mdescRight,
+.memItemRight,
+.memTemplItemRight {
+ border-right: 1px solid #c8e1ff;
+}
+
+.memberdecls tr:nth-child(2) td {
+ border-top: 1px solid #c8e1ff;
+ padding-bottom: 1rem;
+}
+
+.memberdecls tr:last-child td {
+ border-bottom: none;
+ padding-bottom: 1rem;
+}
+
+.memberdecls tr:nth-last-child(2) td {
+ padding-bottom: 1rem;
+}
+
+.mdescLeft, .mdescRight {
+ padding: 0 1rem 1rem 1rem;
+ color: black;
+
+ /* font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif; */
+ font-style: normal;
+}
+
+.memSeparator {
+ background-color: #fff;
+ border-top: 1px solid #c8e1ff;
+ border-bottom: 1px solid #c8e1ff;
+ line-height: 0.5rem;
+ margin: 0px;
+ padding: 0px;
+}
+
+tr:last-child .memSeparator {
+ border-bottom: none;
+}
+
+.memItemLeft, .memTemplItemLeft {
+ white-space: nowrap;
+}
+
+.memItemRight {
+ width: 100%;
+}
+
+.mdescRight a.el:first-child,
+.memItemRight a.el:first-child,
+.memTemplItemRight a.el:first-child {
+ font-weight: bold;
+}
+
+.memTemplParams {
+ color: #005082;
+ white-space: nowrap;
+ font-size: 80%;
+}
+
+/* @end */
+
+/* @group Member Details */
+
+/* Styles for detailed member documentation */
+
+.memtitle {
+ padding: 0 0.3rem 0 0;
+ border: none;
+ border-bottom: 1px solid #eaecef;
+ margin: 0 0 1.75rem 0;
+ line-height: inherit;
+ background: none;
+ font-weight: bold;
+ font-size: 2rem;
+ width: 100%;
+ float: left;
+}
+
+.permalink {
+ font-size: 65%;
+ display: inline-block;
+ vertical-align: middle;
+ display: none;
+}
+
+.permalink a:hover {
+ text-decoration: none;
+}
+
+.memtemplate {
+ font-size: 100%;
+ color: black;
+ font-weight: normal;
+ margin-left: 1px;
+}
+
+.memnav {
+ background-color: #EBEFF6;
+ border: 1px solid #A3B4D7;
+ text-align: center;
+ margin: 2px;
+ margin-right: 15px;
+ padding: 2px;
+}
+
+.mempage {
+ width: 100%;
+}
+
+.memitem {
+ padding: 0;
+ margin-bottom: 2rem;
+ margin-right: 0;
+ -webkit-transition: box-shadow 0.5s linear;
+ -moz-transition: box-shadow 0.5s linear;
+ -ms-transition: box-shadow 0.5s linear;
+ -o-transition: box-shadow 0.5s linear;
+ transition: box-shadow 0.5s linear;
+ display: table !important;
+ width: 100%;
+ background-color: #f1f8ff;
+ border-color: #c8e1ff;
+}
+
+.memitem.glow {
+ box-shadow: 0 0 15px #1fa4a9;
+}
+
+.memname {
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+ margin-left: 0px;
+ border-spacing: initial;
+}
+
+.memname td {
+ font-weight: bold;
+ vertical-align: bottom;
+}
+
+.memproto, dl.reflist dt {
+ border: none;
+ border: 1px solid #c8e1ff;
+ padding: 1rem;
+ color: black;
+ font-weight: bold;
+ text-shadow: none;
+ background-image: none;
+ background-color: #f1f8ff;
+ /* opera specific markup */
+ box-shadow: none;
+ border-top-right-radius: 0px;
+ border-top-left-radius: 0px;
+ /* firefox specific markup */
+ -moz-box-shadow: none;
+ -moz-border-radius-topright: 0px;
+ -moz-border-radius-topleft: 0px;
+ /* webkit specific markup */
+ -webkit-box-shadow: none;
+ -webkit-border-top-right-radius: 0px;
+ -webkit-border-top-left-radius: 0px;
+}
+
+.memdoc, dl.reflist dd {
+ border: none;
+ padding: 6px;
+ background-color: #FBFCFD;
+ border-top-width: 0;
+ background-image: none;
+ background-color: #FFFFFF;
+ /* opera specific markup */
+ border-bottom-left-radius: 0px;
+ border-bottom-right-radius: 0px;
+ box-shadow: none;
+ /* firefox specific markup */
+ -moz-border-radius-bottomleft: 0px;
+ -moz-border-radius-bottomright: 0px;
+ -moz-box-shadow: none;
+ /* webkit specific markup */
+ -webkit-border-bottom-left-radius: 0px;
+ -webkit-border-bottom-right-radius: 0px;
+ -webkit-box-shadow: none;
+}
+
+dl.reflist dt {
+ padding: 5px;
+}
+
+dl.reflist dd {
+ margin: 0px 0px 10px 0px;
+ padding: 5px;
+}
+
+.paramkey {
+ text-align: right;
+}
+
+.paramtype {
+ white-space: nowrap;
+}
+
+.paramname {
+ color: #01496d;
+ white-space: nowrap;
+}
+.paramname em {
+ font-style: italic;
+ font-weight: normal;
+}
+.paramname code {
+ line-height: 14px;
+}
+
+.params, .retval, .exception, .tparams {
+ margin-left: 0px;
+ padding-left: 0px;
+}
+
+.params .paramname, .retval .paramname {
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+ font-style: italic;
+ font-weight: normal;
+ text-shadow: none;
+ padding-right: 1rem;
+}
+
+.params .paramtype {
+ font-style: italic;
+ vertical-align: top;
+}
+
+.params .paramdir {
+ font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
+ vertical-align: top;
+}
+
+table.mlabels {
+ border-spacing: 0px;
+}
+
+td.mlabels-left {
+ width: 100%;
+ padding: 0px;
+}
+
+td.mlabels-right {
+ vertical-align: middle;
+ padding: 0px;
+ white-space: nowrap;
+}
+
+span.mlabels {
+ margin-left: 8px;
+}
+
+span.mlabel {
+ background-color: rgb(172, 172, 172);;
+ border: none;
+ text-shadow: none;
+ color: white;
+ margin-right: 4px;
+ padding: 2px 3px;
+ border-radius: 4px;
+ font-size: 9pt;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+/* @end */
+
+/* these are for tree view when not used as main index */
+
+div.directory {
+ border-top: 1px solid #eaecef;
+ border-left: 1px solid #eaecef;
+ border-right: 1px solid #eaecef;
+ border-bottom: 1px solid #eaecef;
+ margin: 10px 0px;
+ width: 100%;
+}
+
+.directory table {
+ border-collapse:collapse;
+}
+
+.directory td {
+ margin: 0px;
+ padding: 8px;
+ vertical-align: middle;
+ line-height: 1.5rem;
+}
+
+.directory tr {
+ border-top: 1px solid #eaecef;
+}
+
+.directory tr:first-child {
+ border-top: none;
+}
+
+.directory td.entry {
+ white-space: nowrap;
+ padding: 8px;
+}
+
+@media (max-width: 576px) {
+ .directory td.entry > span:first-child {
+ display: none !important;
+ }
+}
+
+.directory td.entry a {
+ outline: none;
+}
+
+.directory td.entry a img {
+ border: none;
+}
+
+.directory td.desc {
+ width: 100%;
+ padding: 8px;
+ border-left: 1px solid rgba(0,0,0,0.05);
+}
+
+.directory tr.even {
+ padding-left: 0;
+ background-color: inherit;
+}
+
+.directory tr:not(.even) {
+ background-color: #f6f8fa;
+}
+
+.directory img {
+ vertical-align: -30%;
+}
+
+.directory .levels {
+ display: none;
+ white-space: nowrap;
+ width: auto;
+ text-align: right;
+ font-size: 0.75rem;
+ font-weight: bold;
+ padding: 8px;
+ border-bottom: 1px solid #eaecef;
+}
+
+.directory .levels span {
+ cursor: pointer;
+ padding: 0 0.5rem;
+ color: #3d578c;
+}
+
+.arrow {
+ color: #6c757d;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ cursor: pointer;
+ font-size: inherit;
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+}
+
+.icon {
+ display: inline-block;
+ margin: 0;
+
+ font-family: Arial, Helvetica;
+ font-weight: bold;
+ font-size: 0.75rem;
+ text-align: center;
+
+ width: 16px;
+ height: 16px;
+ line-height: 16px;
+
+ border-radius: 4px;
+ background-color: #6194cb;
+ color: white;
+}
+
+.icona {
+ width: 16px;
+ height: 16px;
+ display: inline-block;
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+}
+
+.iconfopen {
+ width: 16px;
+ height: 16px;
+ margin-bottom: 0;
+ background-image:url('folder-open.png');
+ background-position: 0px -1px;
+ background-repeat: no-repeat;
+ vertical-align: middle;
+ display: inline-block;
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+}
+
+.iconfclosed {
+ width: 16px;
+ height: 16px;
+ margin-bottom: 0;
+ background-image:url('folder.png');
+ background-position: 0px 0px;
+ background-repeat: no-repeat;
+ vertical-align: middle;
+ display: inline-block;
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+}
+
+.icondoc {
+ width: 16px;
+ height: 16px;
+ margin-bottom: 0;
+ background-image:url('text-x-generic.png');
+ background-position: 0px 0px;
+ background-repeat: no-repeat;
+ vertical-align: middle;
+ display: inline-block;
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+}
+
+/* @end */
+
+div.dynheader {
+ margin-top: 8px;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+address {
+ font-style: normal;
+ color: #2A3D61;
+}
+
+table.doxtable {
+ border-collapse:collapse;
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+table.doxtable td, table.doxtable th {
+ border: 1px solid #2D4068;
+ padding: 3px 7px 2px;
+}
+
+table.doxtable th {
+ background-color: #374F7F;
+ color: #FFFFFF;
+ font-size: 110%;
+ padding-bottom: 4px;
+ padding-top: 5px;
+}
+
+table.fieldtable {
+ margin-bottom: 10px;
+ border: 1px solid #c6cbd1;
+ border-spacing: 0px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ -moz-box-shadow: none;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ font-size: 1rem;
+}
+
+.fieldtable td, .fieldtable th {
+ padding: 0.5rem 1rem;
+}
+
+.fieldtable td.fieldtype, .fieldtable td.fieldname {
+ white-space: nowrap;
+ border-right: 1px solid #c6cbd1;
+ border-bottom: 1px solid #c6cbd1;
+ vertical-align: middle;
+}
+
+.fieldtable td.fieldname {
+ padding-top: 0.5rem;
+ font-style: italic;
+}
+
+.fieldtable td.fielddoc {
+ border-bottom: 1px solid #c6cbd1;
+}
+
+.fieldtable td.fielddoc p {
+ font-size: 1rem;
+}
+
+.fieldtable td.fielddoc p:first-child {
+ margin-top: 0px;
+}
+
+.fieldtable td.fielddoc p:last-child {
+ margin-bottom: 2px;
+}
+
+.fieldtable tr:last-child td {
+ border-bottom: none;
+}
+
+.fieldtable tbody tr:nth-of-type(odd) {
+ background-color: #f6f8fa;
+}
+
+.fieldtable th {
+ background-image: none;
+ background-repeat:repeat-x;
+ background-color: #fff;
+ font-size: inherit;
+ font-weight: bold;
+ color: inherit;
+ padding: 0.5rem 1rem;
+ text-align: inherit;
+ -moz-border-radius-topleft: 3px;
+ -moz-border-radius-topright: 3px;
+ -webkit-border-top-left-radius: 3px;
+ -webkit-border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border-bottom: 1px solid #c6cbd1;
+}
+
+
+.tabsearch {
+ top: 0px;
+ left: 10px;
+ height: 36px;
+ background-image: none;
+ z-index: 101;
+ overflow: hidden;
+ font-size: 13px;
+}
+
+.navpath ul {
+ font-size: 1rem;
+ background-image: none;
+ background-repeat: repeat-x;
+ background-position: 0 -5px;
+ height:30px;
+ line-height:30px;
+ color: inherit;
+ border: none;
+ overflow:hidden;
+ margin:0px;
+ padding: 0px;
+}
+
+.navpath li {
+ list-style-type: none;
+ float: left;
+ background-image: none;
+ background-repeat: no-repeat;
+ background-position: right;
+ color: inherit;
+}
+
+#top .navpath li+li {
+ padding-left: .5rem;
+}
+
+#top .navpath li+li::before {
+ display: inline-block;
+ padding-right: .5rem;
+ color: #6c757d;
+ content: "/";
+}
+
+@media (min-width: 576px) {
+ address.footer .navpath li+li {
+ padding-left: .5rem;
+ }
+
+ address.footer .navpath li+li::before {
+ display: inline-block;
+ padding-right: .5rem;
+ color: #6c757d;
+ content: "/";
+ }
+}
+
+.navpath li.navelem a {
+ height:32px;
+ display: inline-block;
+ text-decoration: none;
+ outline: none;
+ color: inherit;
+ font-family: inherit;
+ text-shadow: none;
+ text-decoration: none;
+}
+
+.navpath li.navelem a:hover {
+ color: inherit;
+}
+
+address.footer {
+ text-align: left;
+ padding-right: 0;
+}
+
+address.footer ul.navpath {
+ display: inline-block;
+ padding: 0;
+}
+
+.navpath li.footer {
+ list-style-type: none;
+ float: left;
+ padding-left: 0.5rem;
+ padding-right: 0;
+ background-image: none;
+ background-repeat: no-repeat;
+ background-position: right;
+ color: inherit;
+ font-size: 0.75rem;
+}
+
+div.summary {
+ float: none;
+ font-size: 0.75rem;
+ padding-right: 5px;
+ width: 100%;
+ text-align: left;
+}
+
+div.summary a {
+ white-space: nowrap;
+}
+
+div.ingroups {
+ font-size: 0.75rem;
+ width: 50%;
+ text-align: left;
+}
+
+div.ingroups a {
+ white-space: nowrap;
+}
+
+div.header {
+ background-image: none;
+ background-color: white;
+ padding: 1rem 2rem 0 2rem;
+ margin: 0px;
+ border: none;
+}
+
+div.headertitle {
+ margin: 1rem 0px 0rem 0px;
+ padding: 0 0.3rem 0 0;
+}
+
+dl {
+ padding: 0 0 0 10px;
+}
+
+/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug */
+dl.section {
+ margin-left: 0px;
+ padding-left: 0px;
+}
+
+dl.note {
+ margin-left: 0px;
+ padding: 1rem;
+ border-left: 6px solid;
+ border: 1px solid rgba(27,31,35,.15);
+ background-color: #fffbdd;
+ color: #735c0f;
+}
+
+dl.section.note dd {
+ margin-left: 0;
+ margin-bottom: 0;
+}
+
+dl.warning, dl.attention {
+ margin-left: 0px;
+ padding: 1rem;
+
+ border-left: 6px solid;
+ border-color: #ab2333;
+}
+
+dl.pre, dl.post, dl.invariant {
+ margin-left:-7px;
+ padding-left: 3px;
+ border-left:4px solid;
+ border-color: #4e9a06;
+}
+
+dl.deprecated {
+ margin-left: 0px;
+ padding: 1rem;
+ border-left: 6px solid;
+ border-color: #505050;
+}
+
+dl.deprecated dt a.el {
+}
+
+dl.todo {
+ margin-left: 0px;
+ padding: 1rem;
+ border-left:4px solid;
+ border-color: #04898e;
+}
+
+dl.test {
+ margin-left:-7px;
+ padding-left: 3px;
+ border-left:4px solid;
+ border-color: #21376d;
+}
+
+dl.bug {
+ margin-left:-7px;
+ padding-left: 3px;
+ border-left:4px solid;
+ border-color: #8f5902;
+}
+
+dl.section dd {
+ margin-bottom: 0.5rem;
+}
+
+
+#projectlogo {
+ text-align: center;
+ vertical-align: bottom;
+ border-collapse: separate;
+ padding-right: 1rem;
+}
+
+#projectlogo img {
+ border: 0px none;
+ width: 8rem;
+ height: auto;
+}
+
+#projectname {
+ font: inherit;
+ font-size: 300%;
+ margin: 0px;
+ padding: 2px 0px;
+}
+
+#projectbrief {
+ font: inherit;
+ font-size: 120%;
+ margin: 0px;
+ padding: 0px;
+}
+
+#projectnumber {
+ font: inherit;
+ font-size: 50%;
+ margin: 0px;
+ padding: 0px;
+}
+
+#titlearea {
+ padding: 1rem;
+ margin: 0px;
+ width: auto;
+ border-bottom: none;
+ background-color: #fafbfc;
+}
+
+.image {
+ text-align: center;
+}
+
+.dotgraph {
+ text-align: center;
+}
+
+.mscgraph {
+ text-align: center;
+}
+
+.caption {
+ font-weight: bold;
+}
+
+div.zoom {
+ border: 1px solid #90A5CE;
+}
+
+dl.citelist {
+ margin-bottom:50px;
+}
+
+dl.citelist dt {
+ color:#334975;
+ float:left;
+ font-weight:bold;
+ margin-right:10px;
+ padding:5px;
+}
+
+dl.citelist dd {
+ margin:2px 0;
+ padding:5px 0;
+}
+
+div.toc {
+ padding: 14px 25px;
+ background-color: #F4F6FA;
+ border: 1px solid #D8DFEE;
+ border-radius: 7px 7px 7px 7px;
+ float: right;
+ height: auto;
+ margin: 0 20px 10px 10px;
+ width: 200px;
+}
+
+div.toc li {
+ background: url("bdwn.png") no-repeat scroll 0 5px transparent;
+ font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif;
+ margin-top: 5px;
+ padding-left: 10px;
+ padding-top: 2px;
+}
+
+div.toc h3 {
+ font: bold 12px/1.2 Arial,FreeSans,sans-serif;
+ color: #4665A2;
+ border-bottom: 0 none;
+ margin: 0;
+}
+
+div.toc ul {
+ list-style: none outside none;
+ border: medium none;
+ padding: 0px;
+}
+
+div.toc li.level1 {
+ margin-left: 0px;
+}
+
+div.toc li.level2 {
+ margin-left: 15px;
+}
+
+div.toc li.level3 {
+ margin-left: 30px;
+}
+
+div.toc li.level4 {
+ margin-left: 45px;
+}
+
+.inherit_header {
+ font-weight: bold;
+ color: gray;
+ cursor: pointer;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.inherit_header td {
+ padding: 6px 0px 2px 5px;
+}
+
+.inherit {
+ display: none;
+}
+
+tr.heading h2 {
+ padding-top: 1.75rem;
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+@media print
+{
+ #top { display: none; }
+ #side-nav { display: none; }
+ #nav-path { display: none; }
+ body { overflow:visible; }
+ h1, h2, h3, h4, h5, h6 { page-break-after: avoid; }
+ .summary { display: none; }
+ .memitem { page-break-inside: avoid; }
+ #doc-content {
+ margin-left:0 !important;
+ height:auto !important;
+ width:auto !important;
+ overflow:inherit;
+ display:inline;
+ }
+}
+
+
+.tabs, .tabs2, .tabs3 {
+ background-image: none;
+ background-color: #fafbfc;
+ color: #24292e;
+ padding: 0 1rem;
+ font-family: inherit;
+ font-size: inherit;
+ width: auto;
+}
+
+.tabs {
+ border-bottom: 1px solid #e1e4e8;
+}
+
+.tablist {
+ position: relative;
+ top: 1px;
+}
+
+.tabs2 {
+ margin-top: 1rem;
+ border-bottom: 1px solid #e1e4e8;
+ background-color: #fff;
+}
+
+.tabs3 {
+ margin-top: 1rem;
+ border-bottom: 1px solid #e1e4e8;
+ background-color: #fff;
+}
+
+.tablist li {
+ background-image: none;
+}
+
+.tablist a {
+ background-image: none;
+ text-shadow: none;
+ color: #586069;
+ border: 1px solid transparent;
+ border-top: 3px solid transparent;
+ border-radius: 3px 3px 0 0;
+ padding: 0 1rem;
+}
+
+.tablist a:hover {
+ background-image: none;
+ text-shadow: none;
+ color: #24292e;
+}
+
+.tablist li.current a {
+ text-shadow: none;
+ background-image: none;
+ background-color: #fff;
+ color: #24292e;
+}
+
+#navrow1 .tablist li.current a {
+ border-color: #f7ae1a #e1e4e8 transparent;
+}
+
+#navrow2 .tablist li.current a {
+ border-color: #e45e25 #e1e4e8 transparent;
+}
+
+#navrow3 .tablist li.current a {
+ border-color: #1fa4a9 #e1e4e8 transparent;
+}
+
+#navrow4 .tablist li.current a {
+ border-color: #01496d #e1e4e8 transparent;
+}
+
+.tabs li.current {
+ background-color: #fff;
+}
+
+.tabs2 li.current {
+ background-color: #fff;
+}
+
+.navpath {
+ border: none;
+ margin-top: 1rem;
+ padding: 0 2rem;
+}
+
+.navpath ul {
+ background-color: transparent;
+ border: none;
+}
+
+.navpath li {
+ padding: 0;
+}
+
+.navpath li.navelem a {
+ color: #005082;
+ text-shadow: none;
+ padding: 0 0.25rem;
+ font-size: 1rem;
+}
+
+.navpath li.navelem a:hover {
+ color: #005082;
+ text-shadow: none;
+ text-decoration: underline;
+}
+
+/* @group Markdown */
+
+table.markdownTable td,
+table.markdownTable th {
+ border: 1px solid #c6cbd1;
+ padding: 0.5rem 1rem;
+}
+
+.markdownTable tbody tr:nth-of-type(odd) {
+ background-color: #f6f8fa;
+}
+
+th.markdownTableHeadLeft,
+th.markdownTableHeadRight,
+th.markdownTableHeadCenter,
+th.markdownTableHeadNone {
+ background-color: #fff;
+ color: inherit;
+ font-size: inherit;
+ font-weight: bold;
+ padding: 0.5rem 1rem;
+ text-align: inherit;
+ -moz-border-radius-topleft: 3px;
+ -moz-border-radius-topright: 3px;
+ -webkit-border-top-left-radius: 3px;
+ -webkit-border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border-bottom: 1px solid #c6cbd1;
+}
+
+/* @end */
+
+@media (max-width: 576px) {
+ .navpath {
+ padding: 0 1rem;
+ }
+
+ .navpath li.navelem a {
+ padding: 0;
+ }
+
+ div.header {
+ padding: 1rem 1rem 0 1rem;
+ }
+
+ div.contents {
+ padding: 1rem 1rem 0 1rem;
+ }
+
+ hr.footer {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ address.footer {
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ .memtitle {
+ font-size: 1.5rem;
+ word-break: break-all;
+ }
+
+ .memname td {
+ float: left;
+ word-break: break-all;
+ }
+
+ div.memitem {
+ margin-bottom: 1rem;
+ }
+
+ .memdoc table.params {
+ word-break: break-word;
+ }
+
+ div.memproto {
+ display: table;
+ }
+
+ dl.section dt {
+ margin-bottom: 0.5rem;
+ }
+
+ dl.section dd {
+ margin-left: 0;
+ word-break: break-word;
+ }
+
+ .contents > table,
+ .contents > table thead,
+ .contents > table tbody,
+ .contents > table th,
+ .contents > table td,
+ .contents > table tr {
+ display: block;
+ }
+
+ .contents table.directory,
+ .contents table.directory thead,
+ .contents table.directory tbody,
+ .contents table.directory th,
+ .contents table.directory td {
+ display: block;
+ }
+
+ .contents table.directory tr {
+ }
+
+ div:not(.PageDoc) .contents table,
+ div:not(.PageDoc) .contents thead,
+ div:not(.PageDoc) .contents tbody,
+ div:not(.PageDoc) .contents th,
+ div:not(.PageDoc) .contents td,
+ div:not(.PageDoc) .contents tr {
+ display: block;
+ }
+
+ .mdescLeft,
+ .memItemLeft,
+ .memTemplItemLeft {
+ text-align: left;
+ border-left: 1px solid #c8e1ff;
+ border-right: 1px solid #c8e1ff;
+ word-break: break-word;
+ white-space: normal;
+ padding-bottom: 0;
+ }
+
+ table td.mdescLeft {
+ display: none;
+ }
+
+ table td.mdescRight,
+ table td.memItemRight,
+ table td.memTemplItemRight {
+ border-top: none !important;
+ border-left: 1px solid #c8e1ff;
+ border-right: 1px solid #c8e1ff;
+ display: table-cell;
+ word-break: break-word;
+ padding-top: 0;
+ }
+
+ .directory td.desc {
+ display: table-cell;
+ }
+
+ dl.params dd {
+ margin-left: 0;
+ }
+
+ table.params td.paramname {
+ margin-top: 0.5rem;
+ }
+
+ #projectlogo img {
+ border: 0px none;
+ width: 4rem;
+ height: auto;
+ }
+
+ #projectname {
+ font-size: 1rem;
+ }
+
+ #projectbrief {
+ font-size: 0.75rem;
+ }
+
+ #projectnumber {
+ font-size: 0.75rem;
+ }
+}
diff --git a/docs/doxygen/favicon.ico b/docs/doxygen/favicon.ico
new file mode 100644
index 0000000..b542c5b
--- /dev/null
+++ b/docs/doxygen/favicon.ico
Binary files differ
diff --git a/docs/doxygen/folder-open.png b/docs/doxygen/folder-open.png
new file mode 100644
index 0000000..b67403d
--- /dev/null
+++ b/docs/doxygen/folder-open.png
Binary files differ
diff --git a/docs/doxygen/folder.png b/docs/doxygen/folder.png
new file mode 100644
index 0000000..901edc9
--- /dev/null
+++ b/docs/doxygen/folder.png
Binary files differ
diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html
new file mode 100644
index 0000000..9eef78d
--- /dev/null
+++ b/docs/doxygen/footer.html
@@ -0,0 +1,9 @@
+<hr class="footer"/>
+<address class="footer">
+ <ul class="navpath">
+ <li class="footer">&copy; 2007-$year <a href="https://libimobiledevice.org">$projectname</a>&nbsp;&nbsp;All rights reserved</li>
+ <li class="footer">Generated on <em>$datetime</em> by <a href="https://www.doxygen.org">doxygen $doxygenversion</a></li>
+ <li class="footer">Icons by <a href="http://tango-project.org/">Tango</a></li>
+ <li class="footer">Design &copy; $year by <a href="https://mirell.com" target="_blank">Mirell</a></li>
+ </ui>
+</address> \ No newline at end of file
diff --git a/docs/doxygen/header.html b/docs/doxygen/header.html
new file mode 100644
index 0000000..bc41a11
--- /dev/null
+++ b/docs/doxygen/header.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
+<meta http-equiv="X-UA-Compatible" content="IE=9"/>
+<meta name="generator" content="doxygen $doxygenversion"/>
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
+<!--BEGIN PROJECT_NAME--><title>$title - $projectname $projectnumber</title><!--END PROJECT_NAME-->
+<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
+
+<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
+
+<meta property="og:locale" content="en_US" />
+<meta property="og:type" content="website" />
+<meta property="og:title" content="$projectbrief - $projectname" />
+<meta property="og:url" content="https://libimobiledevice.org/docs/$projectname/$projectnumber/" />
+<meta property="og:site_name" content="$projectname - $projectbrief" />
+<meta name="twitter:card" content="summary_large_image" />
+
+<script type="text/javascript" src="$relpath^jquery.js"></script>
+<script type="text/javascript" src="$relpath^dynsections.js"></script>
+$treeview
+$search
+$mathjax
+<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
+$extrastylesheet
+</head>
+<body>
+<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
+
+<!--BEGIN TITLEAREA-->
+<div id="titlearea">
+<table cellspacing="0" cellpadding="0">
+ <tbody>
+ <tr style="height: 56px;">
+ <!--BEGIN PROJECT_LOGO-->
+ <td id="projectlogo"><a href="$relpath^index.html"><img alt="Logo" src="$relpath^$projectlogo"/></a></td>
+ <!--END PROJECT_LOGO-->
+ <!--BEGIN PROJECT_NAME-->
+ <td id="projectalign" style="padding-left: 0.5em;">
+ <div id="projectname">$projectname
+ <!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
+ </div>
+ <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief - <a href="https://libimobiledevice.org"><small>Return to Homepage</small></a></div><!--END PROJECT_BRIEF-->
+ </td>
+ <!--END PROJECT_NAME-->
+ <!--BEGIN !PROJECT_NAME-->
+ <!--BEGIN PROJECT_BRIEF-->
+ <td style="padding-left: 0.5em;">
+ <div id="projectbrief">$projectbrief</div>
+ </td>
+ <!--END PROJECT_BRIEF-->
+ <!--END !PROJECT_NAME-->
+ <!--BEGIN DISABLE_INDEX-->
+ <!--BEGIN SEARCHENGINE-->
+ <td>$searchbox</td>
+ <!--END SEARCHENGINE-->
+ <!--END DISABLE_INDEX-->
+ </tr>
+ </tbody>
+</table>
+</div>
+<!--END TITLEAREA-->
+<!-- end header part --> \ No newline at end of file
diff --git a/docs/doxygen/layout.xml b/docs/doxygen/layout.xml
new file mode 100644
index 0000000..46f068d
--- /dev/null
+++ b/docs/doxygen/layout.xml
@@ -0,0 +1,226 @@
+<doxygenlayout version="1.0">
+ <!-- Generated by doxygen 1.8.16 -->
+ <!-- Navigation index tabs for HTML output -->
+ <navindex>
+ <tab type="mainpage" visible="yes" title="About"/>
+ <tab type="modules" visible="yes" title="" intro=""/>
+ <tab type="namespaces" visible="yes" title="">
+ <tab type="namespacelist" visible="yes" title="" intro=""/>
+ <tab type="namespacemembers" visible="yes" title="" intro=""/>
+ </tab>
+ <tab type="interfaces" visible="yes" title="">
+ <tab type="interfacelist" visible="yes" title="" intro=""/>
+ <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
+ <tab type="interfacehierarchy" visible="yes" title="" intro=""/>
+ </tab>
+ <tab type="classes" visible="yes" title="">
+ <tab type="classlist" visible="yes" title="" intro=""/>
+ <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
+ <tab type="hierarchy" visible="yes" title="" intro=""/>
+ <tab type="classmembers" visible="yes" title="" intro=""/>
+ </tab>
+ <tab type="structs" visible="yes" title="">
+ <tab type="structlist" visible="yes" title="" intro=""/>
+ <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
+ </tab>
+ <tab type="exceptions" visible="yes" title="">
+ <tab type="exceptionlist" visible="yes" title="" intro=""/>
+ <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
+ <tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
+ </tab>
+ <tab type="files" visible="yes" title="">
+ <tab type="filelist" visible="yes" title="" intro=""/>
+ <tab type="globals" visible="yes" title="" intro=""/>
+ </tab>
+ <tab type="examples" visible="yes" title="" intro=""/>
+ <tab type="pages" visible="yes" title="" intro=""/>
+ </navindex>
+
+ <!-- Layout definition for a class page -->
+ <class>
+ <briefdescription visible="no"/>
+ <detaileddescription title="Description"/>
+ <includes visible="$SHOW_INCLUDE_FILES"/>
+ <inheritancegraph visible="$CLASS_GRAPH"/>
+ <collaborationgraph visible="$COLLABORATION_GRAPH"/>
+ <memberdecl>
+ <nestedclasses visible="yes" title=""/>
+ <publictypes title=""/>
+ <services title=""/>
+ <interfaces title=""/>
+ <publicslots title=""/>
+ <signals title=""/>
+ <publicmethods title=""/>
+ <publicstaticmethods title=""/>
+ <publicattributes title=""/>
+ <publicstaticattributes title=""/>
+ <protectedtypes title=""/>
+ <protectedslots title=""/>
+ <protectedmethods title=""/>
+ <protectedstaticmethods title=""/>
+ <protectedattributes title=""/>
+ <protectedstaticattributes title=""/>
+ <packagetypes title=""/>
+ <packagemethods title=""/>
+ <packagestaticmethods title=""/>
+ <packageattributes title=""/>
+ <packagestaticattributes title=""/>
+ <properties title=""/>
+ <events title=""/>
+ <privatetypes title=""/>
+ <privateslots title=""/>
+ <privatemethods title=""/>
+ <privatestaticmethods title=""/>
+ <privateattributes title=""/>
+ <privatestaticattributes title=""/>
+ <friends title=""/>
+ <related title="" subtitle=""/>
+ <membergroups visible="yes"/>
+ </memberdecl>
+ <memberdef>
+ <inlineclasses title=""/>
+ <typedefs title=""/>
+ <enums title=""/>
+ <services title=""/>
+ <interfaces title=""/>
+ <constructors title=""/>
+ <functions title=""/>
+ <related title=""/>
+ <variables title=""/>
+ <properties title=""/>
+ <events title=""/>
+ </memberdef>
+ <allmemberslink visible="yes"/>
+ <usedfiles visible="$SHOW_USED_FILES"/>
+ <authorsection visible="yes"/>
+ </class>
+
+ <!-- Layout definition for a namespace page -->
+ <namespace>
+ <briefdescription visible="no"/>
+ <detaileddescription title="Description"/>
+ <memberdecl>
+ <nestednamespaces visible="yes" title=""/>
+ <constantgroups visible="yes" title=""/>
+ <interfaces visible="yes" title=""/>
+ <classes visible="yes" title=""/>
+ <structs visible="yes" title=""/>
+ <exceptions visible="yes" title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ <membergroups visible="yes"/>
+ </memberdecl>
+ <memberdef>
+ <inlineclasses title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ </memberdef>
+ <authorsection visible="yes"/>
+ </namespace>
+
+ <!-- Layout definition for a file page -->
+ <file>
+ <briefdescription visible="no"/>
+ <detaileddescription title="Description"/>
+ <includes visible="$SHOW_INCLUDE_FILES"/>
+ <includegraph visible="$INCLUDE_GRAPH"/>
+ <includedbygraph visible="$INCLUDED_BY_GRAPH"/>
+ <memberdecl>
+ <interfaces visible="yes" title=""/>
+ <classes visible="yes" title=""/>
+ <structs visible="yes" title=""/>
+ <exceptions visible="yes" title=""/>
+ <namespaces visible="yes" title=""/>
+ <constantgroups visible="yes" title=""/>
+ <defines title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ <membergroups visible="yes"/>
+ </memberdecl>
+ <memberdef>
+ <inlineclasses title=""/>
+ <defines title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ </memberdef>
+ <authorsection/>
+ <sourcelink visible="yes"/>
+ </file>
+
+ <!-- Layout definition for a group page -->
+ <group>
+ <briefdescription visible="no"/>
+ <detaileddescription title="Description"/>
+ <groupgraph visible="$GROUP_GRAPHS"/>
+ <memberdecl>
+ <nestedgroups visible="yes" title=""/>
+ <dirs visible="yes" title=""/>
+ <files visible="yes" title=""/>
+ <namespaces visible="yes" title=""/>
+ <classes visible="yes" title=""/>
+ <defines title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <enumvalues title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ <signals title=""/>
+ <publicslots title=""/>
+ <protectedslots title=""/>
+ <privateslots title=""/>
+ <events title=""/>
+ <properties title=""/>
+ <friends title=""/>
+ <membergroups visible="yes"/>
+ </memberdecl>
+ <memberdef>
+ <pagedocs/>
+ <inlineclasses title=""/>
+ <defines title=""/>
+ <typedefs title=""/>
+ <sequences title=""/>
+ <dictionaries title=""/>
+ <enums title=""/>
+ <enumvalues title=""/>
+ <functions title=""/>
+ <variables title=""/>
+ <signals title=""/>
+ <publicslots title=""/>
+ <protectedslots title=""/>
+ <privateslots title=""/>
+ <events title=""/>
+ <properties title=""/>
+ <friends title=""/>
+ </memberdef>
+ <authorsection visible="yes"/>
+ </group>
+
+ <!-- Layout definition for a directory page -->
+ <directory>
+ <briefdescription visible="no"/>
+ <detaileddescription title="Description"/>
+ <directorygraph visible="yes"/>
+ <memberdecl>
+ <dirs visible="yes"/>
+ <files visible="yes"/>
+ </memberdecl>
+ </directory>
+</doxygenlayout> \ No newline at end of file
diff --git a/docs/doxygen/logo-vector-clean.svg b/docs/doxygen/logo-vector-clean.svg
new file mode 100644
index 0000000..a953115
--- /dev/null
+++ b/docs/doxygen/logo-vector-clean.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 283.5 283.5" height="793.7" viewBox="0 0 793.7 793.7" width="793.7" xmlns="http://www.w3.org/2000/svg"><g stroke-width=".10005" transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><path d="m94.968 35.193v-10.71l9.2751 5.355z" fill="#f7ae1a"/><path d="m94.968 35.193 9.2751-5.355v10.71z" fill="#e45e25"/><path d="m94.968 42.414 9.2751 5.355-9.2751 5.355z" fill="#d2343e"/><path d="m94.968 53.124 9.2751-5.355v10.71z" fill="#bb2033"/><path d="m94.968 53.124 9.2751 5.355-9.2751 5.355z" fill="#ab2333"/></g><g fill="#2e3436" fill-opacity=".19964" transform="matrix(13.831 0 0 13.831 -1361.02498 -319.002)"><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(-.16726 .09657 -.09657 -.16726 121.04 71.121)"/><path d="m148.27 69.213h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.86603 .5 .5 .86603 211.62 -57.046)"/><path d="m142.92-59.938h-10.71l5.355-9.2751z" stroke-width=".10005" transform="matrix(-.8660254 .5 -.5 -.8660254 211.614597 -57.0459)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(0 .19314 -.19314 0 132.86 60.576)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" stroke-width=".518" transform="matrix(.16726 .09657 -.09657 .16726 129.64 55.775)"/><path d="m57.472 29.867h-55.452l27.726-48.023z" transform="matrix(0 .19314 -.19314 0 142.13 55.221)"/></g><g transform="matrix(13.831 0 0 13.831 -1173.2 -291.34)"><g stroke-width=".10005"><path d="m94.968 63.834 9.2751-5.355v10.71z" fill="#7b2f44"/><path d="m104.24 69.189 9.2751-5.355-9.2751-5.355z" fill="#21376d"/><path d="m104.24 58.479 9.2751-5.355v10.71z" fill="#005082"/><path d="m113.52 63.834v-10.71l9.2751 5.355z" fill="#023e49"/><path d="m122.79 58.479-9.2751-5.355 9.2751-5.355z" fill="#007e96"/></g><path d="m122.79 58.479v-10.71l9.2751 5.355z" fill="#0a9197"/></g><g fill="#ccc" transform="translate(2.808 194.51)"><polygon/><polygon/></g></svg> \ No newline at end of file
diff --git a/docs/doxygen/text-x-generic.png b/docs/doxygen/text-x-generic.png
new file mode 100644
index 0000000..2d7f2d6
--- /dev/null
+++ b/docs/doxygen/text-x-generic.png
Binary files differ
diff --git a/docs/plistutil.1 b/docs/plistutil.1
new file mode 100644
index 0000000..b2bf7bd
--- /dev/null
+++ b/docs/plistutil.1
@@ -0,0 +1,135 @@
+.TH "plistutil" 1
+.SH NAME
+plistutil \- Convert a plist FILE between binary, XML, JSON, and OpenStep formats
+.SH SYNOPSIS
+.B plistutil
+[OPTIONS]
+[-i FILE]
+[-o FILE]
+.SH DESCRIPTION
+plistutil allows converting a Property List file between binary, XML, JSON, and OpenStep formats.
+.SH OPTIONS
+.TP
+.B \-i, \-\-infile FILE
+Input FILE to convert from. If this argument is omitted or - is passed as
+filename, plistutil will read from stdin.
+.TP
+.B \-o, \-\-outfile FILE
+Output FILE to convert to. If this argument is omitted or - is passed as
+filename, plistutil will write to stdout.
+.TP
+.B \-f, \-\-format [bin|xml|json|openstep]
+Force output format, regardless of input type. This is useful if the input
+format is not known, but the output format should always be in a specific
+format (like xml or json).
+
+If omitted, XML plist data will be converted to binary and vice-versa. To
+convert to/from JSON or OpenStep the output format needs to specified.
+.TP
+.B \-p, \-\-print FILE
+Print PList in human-readable format.
+.TP
+.B \-n, \-\-nodepath PATH
+Restrict output to nodepath defined by
+.I PATH
+which selects a specific node within the plist document.
+A node path describes traversal through dictionaries and arrays using / to
+separate the components.
+Traversal begins at the root node and proceeds from left to right.
+Each component selects a child of the current node.
+If the current node is a dictionary, the component is interpreted as a
+dictionary key name.
+If the current node is an array, the component must be a non-negative decimal
+integer specifying the array index (0-based).
+
+Given the following structure:
+.PP
+.RS
+.nf
+<plist version="1.0">
+<dict>
+ <key>Users</key>
+ <array>
+ <dict>
+ <key>Name</key>
+ <string>Alice</string>
+ </dict>
+ <dict>
+ <key>Name</key>
+ <string>Bob</string>
+ </dict>
+ </array>
+</dict>
+</plist>
+.fi
+
+A nodepath of:
+.TP
+\f[B]Users\f[] resolves to the entire array.
+.TP
+\f[B]Users/0\f[] resolves to the first dictionary in the array.
+.TP
+\f[B]Users/0/Name\f[] resolves to the string value "Alice".
+.TP
+\f[B]Users/1/Name\f[] resolves to the string value "Bob".
+.RE
+
+.TP
+.B \-c, \-\-compact
+JSON and OpenStep only: Print output in compact form. By default, the output
+will be pretty-printed.
+.TP
+.B \-C, \-\-coerce
+JSON and OpenStep only: Coerce non-compatible plist types to JSON/OpenStep
+compatible representations.
+Date values become ISO 8601 strings,
+data values become Base64-encoded strings (JSON),
+UID values become integers,
+boolean becomes 1 or 0 (OpenStep),
+and NULL becomes a string 'NULL' (OpenStep)
+
+This options is implied when invoked as plist2json.
+.TP
+.B \-s, \-\-sort
+Sort all dictionary nodes lexicographically by key before converting to the output format.
+.TP
+.B \-h, \-\-help
+Prints usage information.
+.TP
+.B \-d, \-\-debug
+Enabled extended debug output.
+.TP
+.B \-v, \-\-version
+Print version information
+.SH EXAMPLES
+.TP
+.B plistutil -i test.plist -o out.plist
+Convert test.plist and write to out.plist. If test.plist is in XML format,
+out.plist will be in binary format. If test.plist is in binary format,
+out.plist will be in XML format.
+.TP
+.B plistutil -i test.plist -o out.plist -f bin
+Same as before, but the output will always be in binary format.
+.TP
+.B plistutil -i test.plist -f xml
+Print test.plist as XML plist, regardless of the input format.
+.TP
+.B plistutil -i test.plist -f xml -o -
+Same as before.
+.TP
+.B plistutil -i test.plist -f json
+Print test.plist as JSON plist, regardless of the input format.
+.TP
+.B cat test.plist |plistutil -f xml
+Take plist data from stdin - piped via cat - and write the output as XML
+to stdout.
+.SH AUTHORS
+Zach C.
+
+Martin Szulecki
+
+Nikias Bassen
+.SH ON THE WEB
+https://libimobiledevice.org
+
+https://github.com/libimobiledevice/libplist
diff --git a/doxygen.cfg b/doxygen.cfg
deleted file mode 100644
index a77c3cd..0000000
--- a/doxygen.cfg
+++ /dev/null
@@ -1,1418 +0,0 @@
-# Doxyfile 1.5.6
-
-# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
-#
-# All text after a hash (#) is considered a comment and will be ignored
-# The format is:
-# TAG = value [value, ...]
-# For lists items can also be appended using:
-# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
-
-PROJECT_NAME = libplist
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-PROJECT_NUMBER = 1.8
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = docs
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
-# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
-# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
-# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
-# and Ukrainian.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
-
-ABBREVIATE_BRIEF =
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES = YES
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip.
-
-STRIP_FROM_PATH = include
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
-# doesn't support long names like on DOS, Mac, or CD-ROM.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
-
-JAVADOC_AUTOBRIEF = YES
-
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the DETAILS_AT_TOP tag is set to YES then Doxygen
-# will output the detailed description near the top, like JavaDoc.
-# If set to NO, the detailed description appears after the member
-# documentation.
-
-DETAILS_AT_TOP = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE = 8
-
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
-
-ALIASES =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
-
-OPTIMIZE_OUTPUT_FOR_C = YES
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate getter
-# and setter methods for a property. Setting this option to YES (the default)
-# will make doxygen to replace the get and set methods by a property in the
-# documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
-
-SUBGROUPING = NO
-
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-
-TYPEDEF_HIDES_STRUCT = YES
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespace are hidden.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-
-CASE_SENSE_NAMES = YES
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES = NO
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
-
-SORT_BY_SCOPE_NAME = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if sectionname ... \endif.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
-
-SHOW_USED_FILES = YES
-
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page. This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
-
-FILE_VERSION_FILTER =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
-
-WARN_IF_DOC_ERROR = YES
-
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT = src \
- include/plist
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
-
-FILE_PATTERNS =
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE = NO
-
-# The EXCLUDE tag can be used to specify files and/or directories that should
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-
-EXCLUDE = dev
-
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
-# from the input.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
-
-EXCLUDE_PATTERNS =
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS =
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output. If FILTER_PATTERNS is specified, this tag will be
-# ignored.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
-
-FILTER_SOURCE_FILES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
-
-REFERENCED_BY_RELATION = YES
-
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
-
-REFERENCES_RELATION = YES
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code. Otherwise they will link to the documentstion.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX = NO
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header.
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
-
-HTML_STYLESHEET =
-
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP = NO
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-
-GENERATE_DOCSET = NO
-
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
-# written to the html output directory.
-
-CHM_FILE =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
-
-HHC_LOCATION =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
-
-GENERATE_CHI = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
-
-CHM_INDEX_ENCODING =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
-
-TOC_EXPAND = NO
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
-
-DISABLE_INDEX = NO
-
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
-
-ENUM_VALUES_PER_LINE = 4
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to FRAME, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
-# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
-# probably better off using the HTML help feature. Other possible values
-# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
-# and Class Hiererachy pages using a tree view instead of an ordered list;
-# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
-# disables this behavior completely. For backwards compatibility with previous
-# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
-# respectively.
-
-GENERATE_TREEVIEW = NO
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
-
-TREEVIEW_WIDTH = 250
-
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
-
-FORMULA_FONTSIZE = 10
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-GENERATE_LATEX = NO
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE = a4wide
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS = NO
-
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
-
-USE_PDFLATEX = NO
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE = NO
-
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
-
-LATEX_HIDE_INDICES = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS = NO
-
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader. This is useful
-# if you want to understand what is going on. On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
-
-SEARCH_INCLUDES = NO
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
-
-EXTERNAL_GROUPS = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
-# powerful graphs.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT = NO
-
-# By default doxygen will write a font called FreeSans.ttf to the output
-# directory and reference it in all dot files that doxygen generates. This
-# font does not include all possible unicode characters however, so when you need
-# these (or just want a differently looking font) you can specify the font name
-# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
-# which can be done by putting it in a standard location or by setting the
-# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
-# containing the font.
-
-DOT_FONTNAME = FreeSans
-
-# By default doxygen will tell dot to use the output directory to look for the
-# FreeSans.ttf font (which doxygen will put there itself). If you specify a
-# different font using DOT_FONTNAME you can set the path where dot
-# can find it using this tag.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-
-UML_LOOK = NO
-
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
-
-TEMPLATE_RELATIONS = NO
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
-
-INCLUDE_GRAPH = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
-
-DOT_IMAGE_FORMAT = png
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
-
-DOTFILE_DIRS =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is enabled by default, which results in a transparent
-# background. Warning: Depending on the platform used, enabling this option
-# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
-# become hard to read).
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
-
-DOT_MULTI_TARGETS = NO
-
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
-
-DOT_CLEANUP = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine
-#---------------------------------------------------------------------------
-
-# The SEARCHENGINE tag specifies whether or not a search engine should be
-# used. If set to NO the values of all tags below this one will be ignored.
-
-SEARCHENGINE = NO
diff --git a/doxygen.cfg.in b/doxygen.cfg.in
new file mode 100644
index 0000000..eb84c85
--- /dev/null
+++ b/doxygen.cfg.in
@@ -0,0 +1,2806 @@
+# Doxyfile 1.11.0
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = @PACKAGE@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "API Documentation"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO = docs/doxygen/logo-vector-clean.svg
+
+# With the PROJECT_ICON tag one can specify an icon that is included in the tabs
+# when the HTML document is shown. Doxygen will copy the logo to the output
+# directory.
+
+PROJECT_ICON =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = docs
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+CREATE_SUBDIRS_LEVEL = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH = include
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = YES
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE = YES
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 6.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 5
+
+# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
+# generate identifiers for the Markdown headings. Note: Every identifier is
+# unique.
+# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
+# sequence number starting at 0 and GITHUB use the lower case version of title
+# with any whitespace replaced by '-' and punctuation characters removed.
+# The default value is: DOXYGEN.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+MARKDOWN_ID_STYLE = DOXYGEN
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also makes the inheritance and
+# collaboration diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse
+# them like normal C++ but will assume all classes use public instead of private
+# inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = NO
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = YES
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS = 1
+
+# If the TIMESTAMP tag is set different from NO then each generated page will
+# contain the date or date and time when the page was generated. Setting this to
+# NO can help when comparing the output of multiple runs.
+# Possible values are: YES, NO, DATETIME and DATE.
+# The default value is: NO.
+
+TIMESTAMP = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# will also hide undocumented C++ concepts if enabled. This option has no effect
+# if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# Possible values are: SYSTEM, NO and YES.
+# The default value is: SYSTEM.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = NO
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE = docs/doxygen/layout.xml
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
+# undocumented enumeration values. If set to NO, doxygen will accept
+# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: NO.
+
+WARN_IF_UNDOC_ENUM_VAL = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
+# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
+# write the warning messages in between other messages but write them at the end
+# of a run, in case a WARN_LOGFILE is defined the warning messages will be
+# besides being in the defined file also be shown at the end of a run, unless
+# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
+# the behavior will remain as with the setting FAIL_ON_WARNINGS.
+# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = include/plist/plist.h \
+ docs/README.doxygen.md
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# See also: INPUT_FILE_ENCODING
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
+# character encoding on a per file pattern basis. Doxygen will compare the file
+# name with each pattern and apply the encoding instead of the default
+# INPUT_ENCODING) if there is a match. The character encodings are a list of the
+# form: pattern=encoding (like *.php=ISO-8859-1).
+# See also: INPUT_ENCODING for further information on supported encodings.
+
+INPUT_FILE_ENCODING =
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
+# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl,
+# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d,
+# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to
+# be provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# ANamespace::AClass, ANamespace::*Test
+
+EXCLUDE_SYMBOLS = PLIST_WARN_DEPRECATED PLIST_API PLIST_UINT PLIST_IS_UINT
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that doxygen will use the data processed and written to standard output
+# for further processing, therefore nothing else, like debug statements or used
+# commands (so in case of a Windows batch file always use @echo OFF), should be
+# written to standard output.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = docs/README.doxygen.md
+
+# The Fortran standard specifies that for fixed formatted Fortran code all
+# characters from position 72 are to be considered as comment. A common
+# extension is to allow longer lines before the automatic comment starts. The
+# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
+# be processed before the automatic comment starts.
+# Minimum value: 7, maximum value: 10000, default value: 72.
+
+FORTRAN_COMMENT_AFTER = 72
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# multi-line macros, enums or list initialized variables directly into the
+# documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = NO
+
+# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
+# that should be ignored while generating the index headers. The IGNORE_PREFIX
+# tag works for classes, function and member names. The entity will be placed in
+# the alphabetical list under the first letter of the entity name that remains
+# after removing the prefix.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER = docs/doxygen/header.html
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER = docs/doxygen/footer.html
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# Note: Since the styling of scrollbars can currently not be overruled in
+# Webkit/Chromium, the styling will be left out of the default doxygen.css if
+# one or more extra stylesheets have been specified. So if scrollbar
+# customization is desired it has to be added explicitly. For an example see the
+# documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET = docs/doxygen/custom.css
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES = docs/doxygen/favicon.ico \
+ docs/doxygen/folder.png \
+ docs/doxygen/folder-open.png \
+ docs/doxygen/text-x-generic.png
+
+# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
+# should be rendered with a dark or light theme.
+# Possible values are: LIGHT always generates light mode output, DARK always
+# generates dark mode output, AUTO_LIGHT automatically sets the mode according
+# to the user preference, uses light mode if no preference is set (the default),
+# AUTO_DARK automatically sets the mode according to the user preference, uses
+# dark mode if no preference is set and TOGGLE allows a user to switch between
+# light and dark mode via a button.
+# The default value is: AUTO_LIGHT.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE = AUTO_LIGHT
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
+# dynamically folded and expanded in the generated HTML source code.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_CODE_FOLDING = YES
+
+# If the HTML_COPY_CLIPBOARD tag is set to YES then doxygen will show an icon in
+# the top right corner of code and text fragments that allows the user to copy
+# its content to the clipboard. Note this only works if supported by the browser
+# and the web page is served via a secure context (see:
+# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file:
+# protocol.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COPY_CLIPBOARD = YES
+
+# Doxygen stores a couple of settings persistently in the browser (via e.g.
+# cookies). By default these settings apply to all HTML pages generated by
+# doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store
+# the settings under a project specific key, such that the user preferences will
+# be stored separately.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_PROJECT_COOKIE =
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# The SITEMAP_URL tag is used to specify the full URL of the place where the
+# generated documentation will be placed on the server by the user during the
+# deployment of the documentation. The generated sitemap is called sitemap.xml
+# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
+# is specified no sitemap is generated. For information about the sitemap
+# protocol see https://www.sitemaps.org
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SITEMAP_URL =
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NONE
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+OBFUSCATE_EMAILS = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = NO
+
+# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
+# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
+# mode nothing is printed on the terminal, errors are scrolled as if <return> is
+# hit at every error; missing files that TeX tries to input or request from
+# keyboard input (\read on a not open input stream) cause the job to abort,
+# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
+# but there is no possibility of user interaction just like in batch mode,
+# SCROLL In scroll mode, TeX will stop only for missing files to input or if
+# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
+# each error, asking for user intervention.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# The RTF_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the RTF_OUTPUT output directory.
+# Note that the files will be copied as-is; there are no commands or markers
+# available.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTRA_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to Sqlite3 output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
+# database with symbols found by doxygen stored in tables.
+# The default value is: NO.
+
+GENERATE_SQLITE3 = NO
+
+# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
+# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
+# in front of it.
+# The default directory is: sqlite3.
+# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
+
+SQLITE3_OUTPUT = sqlite3
+
+# The SQLITE3_RECREATE_DB tag is set to YES, the existing doxygen_sqlite3.db
+# database file will be recreated with each doxygen run. If set to NO, doxygen
+# will warn if a database file is already found and not modify it.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
+
+SQLITE3_RECREATE_DB = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = YES
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = NO
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED = LIBIMOBILEDEVICE_API
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
+# will be listed in the class and namespace index. If set to NO, only the
+# inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the topic index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to diagram generator tools
+#---------------------------------------------------------------------------
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
+# subgraphs. When you want a differently looking font in the dot files that
+# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
+# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
+# Edge and Graph Attributes specification</a> You need to make sure dot is able
+# to find the font, which can be done by putting it in a standard location or by
+# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font. Default graphviz fontsize is 14.
+# The default value is: fontname=Helvetica,fontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
+
+# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
+# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
+# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
+# arrows shapes.</a>
+# The default value is: labelfontname=Helvetica,labelfontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
+
+# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
+# around nodes set 'shape=plain' or 'shape=plaintext' <a
+# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
+# The default value is: shape=box,height=0.2,width=0.4.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
+
+# You can set the path where dot can find font specified with fontname in
+# DOT_COMMON_ATTR and others dot attributes.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
+# generate a graph for each documented class showing the direct and indirect
+# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
+# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
+# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
+# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
+# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
+# relations will be shown as texts / links. Explicit enabling an inheritance
+# graph or choosing a different representation for an inheritance graph of a
+# specific class, can be accomplished by means of the command \inheritancegraph.
+# Disabling an inheritance graph can be accomplished by means of the command
+# \hideinheritancegraph.
+# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
+# The default value is: YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes. Explicit enabling a collaboration graph,
+# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
+# command \collaborationgraph. Disabling a collaboration graph can be
+# accomplished by means of the command \hidecollaborationgraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. Explicit enabling a group
+# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
+# of the command \groupgraph. Disabling a directory graph can be accomplished by
+# means of the command \hidegroupgraph. See also the chapter Grouping in the
+# manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will be wrapped across multiple lines. Some heuristics are
+# applied to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
+# can be accomplished by means of the command \includegraph. Disabling an
+# include graph can be accomplished by means of the command \hideincludegraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
+# to NO, can be accomplished by means of the command \includedbygraph. Disabling
+# an included by graph can be accomplished by means of the command
+# \hideincludedbygraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories. Explicit enabling a directory graph, when
+# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
+# \directorygraph. Disabling a directory graph can be accomplished by means of
+# the command \hidedirectorygraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH = 1
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# https://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
+DOT_CLEANUP = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
+# use a built-in version of mscgen tool to produce the charts. Alternatively,
+# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
+# specifying prog as the value, doxygen will call the tool as prog -T
+# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
+# output file formats "png", "eps", "svg", and "ismap".
+
+MSCGEN_TOOL =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am
new file mode 100644
index 0000000..8ea3fb0
--- /dev/null
+++ b/fuzz/Makefile.am
@@ -0,0 +1,55 @@
+if BUILD_FUZZERS
+
+libFuzzer.a: Fuzzer/build.sh
+ @echo "Building $@"
+ @./Fuzzer/build.sh
+
+Fuzzer/build.sh: LIBFUZZER_SRC
+
+LIBFUZZER_SRC:
+ @if test -d Fuzzer ; then \
+ if test -d Fuzzer/.git ; then \
+ echo Making sure libFuzzer source tree is up-to-date... ; \
+ cd Fuzzer && git checkout . && git pull && cd .. ; \
+ fi \
+ else \
+ echo Checking out libFuzzer source code... ; \
+ git clone https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer ; \
+ fi
+
+CLEANFILES = libFuzzer.a
+
+noinst_PROGRAMS = \
+ xplist_fuzzer \
+ bplist_fuzzer \
+ jplist_fuzzer \
+ oplist_fuzzer
+
+xplist_fuzzer_SOURCES = xplist_fuzzer.cc
+xplist_fuzzer_LDFLAGS = -static
+xplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a
+
+bplist_fuzzer_SOURCES = bplist_fuzzer.cc
+bplist_fuzzer_LDFLAGS = -static
+bplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a
+
+jplist_fuzzer_SOURCES = jplist_fuzzer.cc
+jplist_fuzzer_LDFLAGS = -static
+jplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a
+
+oplist_fuzzer_SOURCES = oplist_fuzzer.cc
+oplist_fuzzer_LDFLAGS = -static
+oplist_fuzzer_LDADD = $(top_builddir)/src/libplist-2.0.la libFuzzer.a
+
+TESTS = fuzzers.test
+
+EXTRA_DIST = \
+ bplist.dict \
+ xplist.dict \
+ jplist.dict \
+ oplist.dict \
+ init-fuzzers.sh \
+ test-fuzzers.sh \
+ fuzzers.test
+
+endif
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704 b/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704
new file mode 100644
index 0000000..ad24aea
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-4766291097288704
Binary files differ
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344 b/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344
new file mode 100644
index 0000000..a8f654c
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-4930725262393344
Binary files differ
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816 b/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816
new file mode 100644
index 0000000..7b4972b
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-5421965105954816
Binary files differ
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696 b/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696
new file mode 100644
index 0000000..dac8e23
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-5556177727389696
Binary files differ
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792 b/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792
new file mode 100644
index 0000000..296e2f7
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-6557963011489792
Binary files differ
diff --git a/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504 b/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504
new file mode 100644
index 0000000..06c225a
--- /dev/null
+++ b/fuzz/bplist-crashes/clusterfuzz-testcase-6605859144597504
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840 b/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840
new file mode 100644
index 0000000..fb68699
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-0ce4dd483b645bc05ec45a97d285dcf1933ee840
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800 b/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800
new file mode 100644
index 0000000..ba30b5e
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-0d59cdcb5c3416c2790612b78a631ce9f5622800
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5 b/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5
new file mode 100644
index 0000000..31e2388
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-17d635ab460fef74df1ac7561f1253533a6359c5
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28 b/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28
new file mode 100644
index 0000000..060cf42
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-33a67313fa7864cd627ef92d12017a127d342f28
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b b/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b
new file mode 100644
index 0000000..02c60bc
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-39f1347115f8fe9ac25cdc9332e3fc5cd32c7f7b
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210 b/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210
new file mode 100644
index 0000000..739dabc
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-4bd78175fe01d07c5ee90104fbc68d95dd092210
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392 b/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392
new file mode 100644
index 0000000..28ac412
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-4c5404453bb7425aef9b953a5f6f3f0afcc48392
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032 b/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032
new file mode 100644
index 0000000..7a9af5f
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-52775e25f6af20af351cd616834e602fc988a032
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa b/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa
new file mode 100644
index 0000000..2846cbb
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-6d1399685b745d3d49fadc91072fabcdde8d16fa
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9 b/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9
new file mode 100644
index 0000000..9337149
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-94b3725900d63c6258448fd757559c81002de9c9
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af b/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af
new file mode 100644
index 0000000..ecc9f9a
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-a47195acbabc3f5b2cda9c6ebda42f2afc1cc5af
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e b/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e
new file mode 100644
index 0000000..97761e4
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-ce852bae6aeeffc4698a93660236f1b995ce966e
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131 b/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131
new file mode 100644
index 0000000..d278fd9
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-daddb6e82e3d1ed76e20e4131462f5247a6fb131
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21 b/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21
new file mode 100644
index 0000000..27a6356
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-f21be4ebce7f9c337bf96b93cec7e252f5c75a21
Binary files differ
diff --git a/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2 b/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2
new file mode 100644
index 0000000..01e3dca
--- /dev/null
+++ b/fuzz/bplist-crashes/crash-f779dafccc0164684d1a264976e56037188b40e2
Binary files differ
diff --git a/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac b/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac
new file mode 100644
index 0000000..d4a1ec3
--- /dev/null
+++ b/fuzz/bplist-leaks/leak-96fb453b3ba18d31e164831795a708183b5a0dac
Binary files differ
diff --git a/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1 b/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1
new file mode 100644
index 0000000..d99278b
--- /dev/null
+++ b/fuzz/bplist-leaks/leak-acb3c00a83d7635638a476a4e70fc1f1caa1a5b1
Binary files differ
diff --git a/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074 b/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074
new file mode 100644
index 0000000..b499279
--- /dev/null
+++ b/fuzz/bplist-leaks/leak-fe2cd4b1f67e0c9b21e6225a4fb4ca19eab80074
Binary files differ
diff --git a/fuzz/bplist.dict b/fuzz/bplist.dict
new file mode 100644
index 0000000..bb0ea5d
--- /dev/null
+++ b/fuzz/bplist.dict
@@ -0,0 +1 @@
+header_bplist = "bplist00"
diff --git a/libcnary/list.c b/fuzz/bplist_fuzzer.cc
index 2f05347..17d0649 100644
--- a/libcnary/list.c
+++ b/fuzz/bplist_fuzzer.cc
@@ -1,10 +1,8 @@
/*
- * list.c
+ * bplist_fuzzer.cc
+ * binary plist fuzz target for libFuzzer
*
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
+ * Copyright (c) 2017 Nikias Bassen All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,27 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <plist/plist.h>
#include <stdio.h>
-#include <stdlib.h>
-
-#include "list.h"
-
-void list_init(list_t* list) {
- list->next = NULL;
- list->prev = list;
-}
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
+{
+ plist_t root_node = NULL;
+ plist_from_bin(reinterpret_cast<const char*>(data), size, &root_node);
+ plist_free(root_node);
-void list_destroy(list_t* list) {
- if(list) {
- free(list);
- }
-}
-
-int list_add(list_t* list, object_t* object) {
- return -1;
-}
-
-int list_remove(list_t* list, object_t* object) {
- return -1;
+ return 0;
}
diff --git a/fuzz/bplist_fuzzer.options b/fuzz/bplist_fuzzer.options
new file mode 100644
index 0000000..c0689b2
--- /dev/null
+++ b/fuzz/bplist_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 4096
+dict = bplist.dict
diff --git a/fuzz/fuzzers.test b/fuzz/fuzzers.test
new file mode 100755
index 0000000..dd3fb08
--- /dev/null
+++ b/fuzz/fuzzers.test
@@ -0,0 +1,8 @@
+## -*- sh -*-
+
+set -e
+
+./init-fuzzers.sh
+
+./test-fuzzers.sh
+
diff --git a/fuzz/init-fuzzers.sh b/fuzz/init-fuzzers.sh
new file mode 100755
index 0000000..c9b1955
--- /dev/null
+++ b/fuzz/init-fuzzers.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+CURDIR=`pwd`
+FUZZDIR=`dirname $0`
+
+cd ${FUZZDIR}
+
+if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer || ! test -x jplist_fuzzer; then
+ echo "ERROR: you need to build the fuzzers first."
+ cd ${CURDIR}
+ exit 1
+fi
+
+mkdir -p xplist-input
+cp ../test/data/*.plist xplist-input/
+./xplist_fuzzer -merge=1 xplist-input xplist-crashes xplist-leaks -dict=xplist.dict
+
+mkdir -p bplist-input
+cp ../test/data/*.bplist bplist-input/
+./bplist_fuzzer -merge=1 bplist-input bplist-crashes bplist-leaks -dict=bplist.dict
+
+mkdir -p jplist-input
+mkdir -p jplist-crashes
+mkdir -p jplist-leaks
+cp ../test/data/j1.plist jplist-input/
+cp ../test/data/j2.plist jplist-input/
+./jplist_fuzzer -merge=1 jplist-input jplist-crashes jplist-leaks -dict=jplist.dict
+
+mkdir -p oplist-input
+mkdir -p oplist-crashes
+mkdir -p oplist-leaks
+cp ../test/data/*.ostep oplist-input/
+cp ../test/data/test.strings oplist-input/
+./oplist_fuzzer -merge=1 oplist-input oplist-crashes oplist-leaks -dict=oplist.dict
+
+cd ${CURDIR}
+exit 0
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272
new file mode 100644
index 0000000..e9982b8
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-4997614678966272
@@ -0,0 +1,65 @@
+{"Some AISCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","日I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,i,{}ng""e:sT.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Stri:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","日I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,i,{}ng""e:sT.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Stri{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,"""""""""",0""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk polÏski","\nàTest A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыothe""""""""""""""""""{""{,""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk p©Ã‹¸Ã§Ã¹","Test A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыotheyk polski","\nहà check this: falSs{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""…ÓÝ×""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,"""""""""",0""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk polÏski","\nàTest A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыothe""""""""""""""""""{""{,""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""SCI stri,{}ng""e:sTt ASCII Stri{"":tring""e:sTt ASCII Stri{"":5}ome UTF8 stringq":["àéËçù","æ—¥I Stri""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""0:""""""""""{""{,""""""""""""""""""{""{,""""""""""""""""""{""{,""""""""""""""(ZÝÙßtheyk Ѥ¿à¤¨à¥à¤¦à¥€"],"Keys & \"entiti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯÐ-0.2e+3yk p©Ã‹¸Ã§Ã¹","Test A''I{"":0}oee UT0ÑÑÐी"],"Keys & \"ti]s\"":"helloִבְרִ*f(ZÝÙßtheyk Ñ\nºÐ¸Ð¹ ѯзыotheyk polski","\nहà check this: falSse2 !!!"} \ No newline at end of file
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128
new file mode 100644
index 0000000..653c585
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5149455463088128
@@ -0,0 +1 @@
+{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\Xn‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!\\n‰!\n‰!"}{"\n\\\n‰!\n‰!n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"}{"\n\\\n‰!\n‰!"} \ No newline at end of file
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896
new file mode 100644
index 0000000..10c37ec
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5161359598288896
Binary files differ
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288
new file mode 100644
index 0000000..e440e5c
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5486807695884288
@@ -0,0 +1 @@
+3 \ No newline at end of file
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488
new file mode 100644
index 0000000..734562b
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5576833398079488
@@ -0,0 +1 @@
+{""f ""}{""[""""""]""}{""[""""""]""}{""[""""""]""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""""]""{""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""""]""}{""[""""""]""}{""[""""""{""}{""[""""""]""}{""[""""""]""}{""[""""""]""}{""[""""]""}{""[""""{""[""]""}{""[""""""]""}{""[""""""]""}{""}{""}{""}{""}{""[""""""]""}{""[""""""]}{""[""""]""}{""[""""]""}{""}{""[""""""]""}""]}{""[""""""]""}{""}{""}{""[""""""]""}{""[""""]}]}{""[""""""]""}{""[""""""]""}{""}{""[""""""]""}} \ No newline at end of file
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624
new file mode 100644
index 0000000..7853ee8
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-5704016686874624
@@ -0,0 +1 @@
+{"Some Ak this: \u-170141183460469231731687303715884040192 !!!"} \ No newline at end of file
diff --git a/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320 b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320
new file mode 100644
index 0000000..3cf20d5
--- /dev/null
+++ b/fuzz/jplist-crashes/clusterfuzz-testcase-minimized-jplist_fuzzer-6639076466360320
@@ -0,0 +1 @@
+- \ No newline at end of file
diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352
new file mode 100644
index 0000000..d8ae3d3
--- /dev/null
+++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-4881933237092352
@@ -0,0 +1 @@
+{"Some ASCII stringtring":"Test ASCII String","Some UTF8 strnngs":["àéèçù","日本語","汉语/漢語","한국어/ì¡°ì„ ë§","руÑÑкий Ñзык","الْعَرَبيّة","עִבְרִית","jÄ™zyk polski¹à¤¿à¤¨à¥à¤¦à¥€"],"Keys & \"entities\"":"hellow world & others <nodes> are \"fun!?'","Boolean":false,"Anothe\uD800\uDC00rue,"Some Int":32434543632,"Some String with Unicode entity":"Yeah check this: \u1234 !!!"} \ No newline at end of file
diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984
new file mode 100644
index 0000000..a1f85f7
--- /dev/null
+++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5069883912617984
@@ -0,0 +1 @@
+{""A} \ No newline at end of file
diff --git a/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656 b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656
new file mode 100644
index 0000000..f19d601
--- /dev/null
+++ b/fuzz/jplist-leaks/clusterfuzz-testcase-minimized-jplist_fuzzer-5816111696838656
@@ -0,0 +1 @@
+[[][[][][][][][]{"ÿ222ÀÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222ÀÀÀÀÀÀÀÀÀÀÀ\uDBFF\uDFFFÀÀÀÀeÀÀ2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[]\r[][][][]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2221Ø2222222222h che[][][][][][][][][][][][][][][][][][][][][][][[][][][][][][][][][][][][][][][][][][][][][][][][][]22222h che22#"}[][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]][]] \ No newline at end of file
diff --git a/fuzz/jplist.dict b/fuzz/jplist.dict
new file mode 100644
index 0000000..e08245a
--- /dev/null
+++ b/fuzz/jplist.dict
@@ -0,0 +1,52 @@
+#
+# AFL dictionary for JSON
+# -----------------------
+#
+# Just the very basics.
+#
+# Inspired by a dictionary by Jakub Wilk <jwilk@jwilk.net>
+#
+
+"0"
+",0"
+":0"
+"0:"
+"-1.2e+3"
+
+"true"
+"false"
+"null"
+
+"\"\""
+",\"\""
+":\"\""
+"\"\":"
+
+"{}"
+",{}"
+":{}"
+"{\"\":0}"
+"{{}}"
+
+"[]"
+",[]"
+":[]"
+"[0]"
+"[[]]"
+
+"''"
+"\\"
+"\\b"
+"\\f"
+"\\n"
+"\\r"
+"\\t"
+"\\u0000"
+"\\x00"
+"\\0"
+"\\uD800\\uDC00"
+"\\uDBFF\\uDFFF"
+
+"\"\":0"
+"//"
+"/**/"
diff --git a/libcnary/include/list.h b/fuzz/jplist_fuzzer.cc
index 6b18e6f..a10da59 100644
--- a/libcnary/include/list.h
+++ b/fuzz/jplist_fuzzer.cc
@@ -1,10 +1,8 @@
/*
- * list.h
+ * jplist_fuzzer.cc
+ * JSON plist fuzz target for libFuzzer
*
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
+ * Copyright (c) 2021 Nikias Bassen All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,20 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef LIST_H_
-#define LIST_H_
-
-#include "object.h"
-
-typedef struct list_t {
- void* next;
- void* prev;
-} list_t;
-
-void list_init(struct list_t* list);
-void list_destroy(struct list_t* list);
+#include <plist/plist.h>
+#include <stdio.h>
-int list_add(struct list_t* list, struct object_t* object);
-int list_remove(struct list_t* list, struct object_t* object);
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
+{
+ plist_t root_node = NULL;
+ plist_from_json(reinterpret_cast<const char*>(data), size, &root_node);
+ plist_free(root_node);
-#endif /* LIST_H_ */
+ return 0;
+}
diff --git a/fuzz/jplist_fuzzer.options b/fuzz/jplist_fuzzer.options
new file mode 100644
index 0000000..b22e679
--- /dev/null
+++ b/fuzz/jplist_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 4096
+dict = jplist.dict
diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144
new file mode 100644
index 0000000..11496c4
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4503815405830144
@@ -0,0 +1 @@
+"3ÿÿÿÿ"= /// hÐo/**5/*(*///6/*/#o/,{Å \ No newline at end of file
diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392
new file mode 100644
index 0000000..25d9ed9
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4683683569467392
Binary files differ
diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264
new file mode 100644
index 0000000..2fa08dc
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4716194114699264
@@ -0,0 +1 @@
+(< \ No newline at end of file
diff --git a/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976 b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976
new file mode 100644
index 0000000..a36e0a2
--- /dev/null
+++ b/fuzz/oplist-crashes/clusterfuzz-testcase-minimized-oplist_fuzzer-4789915626110976
Binary files differ
diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448
new file mode 100644
index 0000000..6598aac
--- /dev/null
+++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6043548602728448
@@ -0,0 +1,8 @@
+{JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJtrings" = (
+ "àéèçù",
+ "æi¥æœ¬èªž"Ä,
+ "汉语/漢語",
+ "한국어/ì¡!?'";
+ "Somš Int" = 32434543632;
+ "Some String with Unicode entity" = "Yea—ßœ—eck this: \U1234 !!!";
+}
diff --git a/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344 b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344
new file mode 100644
index 0000000..9d68933
--- /dev/null
+++ b/fuzz/oplist-leaks/clusterfuzz-testcase-minimized-oplist_fuzzer-6497436988473344
@@ -0,0 +1 @@
+" \ No newline at end of file
diff --git a/fuzz/oplist.dict b/fuzz/oplist.dict
new file mode 100644
index 0000000..1408c4a
--- /dev/null
+++ b/fuzz/oplist.dict
@@ -0,0 +1,51 @@
+#
+# AFL dictionary for OpenStep plist format
+# ----------------------------------------
+
+"0"
+",0"
+"=0"
+"0="
+
+"\"\""
+",\"\""
+"=\"\""
+"\"\"="
+
+"="
+";"
+
+"{}"
+",{}"
+"={}"
+"{\"\"=0}"
+"{{}}"
+
+"()"
+",()"
+"=()"
+"(0)"
+"(())"
+
+"''"
+"\\"
+"\\b"
+"\\f"
+"\\n"
+"\\r"
+"\\t"
+"\\U0000"
+"\\a"
+"\\b"
+"\\f"
+"\\n"
+"\\r"
+"\\t"
+"\\v"
+"\\0"
+"\\uD800\\uDC00"
+"\\uDBFF\\uDFFF"
+
+"\"\"=0"
+"//"
+"/**/"
diff --git a/libcnary/iterator.c b/fuzz/oplist_fuzzer.cc
index 492a8ae..0fabed8 100644
--- a/libcnary/iterator.c
+++ b/fuzz/oplist_fuzzer.cc
@@ -1,10 +1,8 @@
/*
- * iterator.c
+ * oplist_fuzzer.cc
+ * OpenStep plist fuzz target for libFuzzer
*
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
+ * Copyright (c) 2023 Nikias Bassen All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,41 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <plist/plist.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "list.h"
-#include "object.h"
-#include "iterator.h"
-
-void iterator_destroy(iterator_t* iterator) {
- if(iterator) {
- free(iterator);
- }
-}
-
-iterator_t* iterator_create(list_t* list) {
- iterator_t* iterator = (iterator_t*) malloc(sizeof(iterator_t));
- if(iterator == NULL) {
- return NULL;
- }
- memset(iterator, '\0', sizeof(iterator_t));
- if(list != NULL) {
- // Create and bind to list
-
- } else {
- // Empty Iterator
- }
-
- return iterator;
-}
-
-object_t* iterator_next(iterator_t* iterator) {
- return NULL;
-}
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
+{
+ plist_t root_node = NULL;
+ plist_from_openstep(reinterpret_cast<const char*>(data), size, &root_node);
+ plist_free(root_node);
-int iterator_bind(iterator_t* iterator, list_t* list) {
- return -1;
+ return 0;
}
diff --git a/fuzz/oplist_fuzzer.options b/fuzz/oplist_fuzzer.options
new file mode 100644
index 0000000..69a63d9
--- /dev/null
+++ b/fuzz/oplist_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 4096
+dict = oplist.dict
diff --git a/fuzz/test-fuzzers.sh b/fuzz/test-fuzzers.sh
new file mode 100755
index 0000000..4fdf82b
--- /dev/null
+++ b/fuzz/test-fuzzers.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+CURDIR=`pwd`
+FUZZDIR=`dirname $0`
+
+cd ${FUZZDIR}
+
+if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer || ! test -x jplist_fuzzer || ! test -x oplist_fuzzer; then
+ echo "ERROR: you need to build the fuzzers first."
+ cd ${CURDIR}
+ exit 1
+fi
+
+if ! test -d xplist-input || ! test -d bplist-input || ! test -d jplist-input || ! test -d oplist-input; then
+ echo "ERROR: fuzzer corpora directories are not present. Did you run init-fuzzers.sh ?"
+ cd ${CURDIR}
+ exit 1
+fi
+
+echo "### TESTING xplist_fuzzer ###"
+if ! ./xplist_fuzzer xplist-input -dict=xplist.dict -max_len=65536 -runs=10000; then
+ cd ${CURDIR}
+ exit 1
+fi
+
+echo "### TESTING bplist_fuzzer ###"
+if ! ./bplist_fuzzer bplist-input -dict=bplist.dict -max_len=4096 -runs=10000; then
+ cd ${CURDIR}
+ exit 1
+fi
+
+echo "### TESTING jplist_fuzzer ###"
+if ! ./jplist_fuzzer jplist-input -dict=jplist.dict -max_len=65536 -runs=10000; then
+ cd ${CURDIR}
+ exit 1
+fi
+
+echo "### TESTING oplist_fuzzer ###"
+if ! ./oplist_fuzzer oplist-input -dict=oplist.dict -max_len=65536 -runs=10000; then
+ cd ${CURDIR}
+ exit 1
+fi
+
+cd ${CURDIR}
+exit 0
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856 b/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856
new file mode 100644
index 0000000..e7b3493
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-4516959125241856
Binary files differ
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760 b/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760
new file mode 100644
index 0000000..ec8b44d
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-4663876528373760
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<plist>
+ <data>
+ /EN" "http://www.apple.com/DTDs========================/PropertyList-1.0.</data>
+</plist>
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048 b/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048
new file mode 100644
index 0000000..ff3100a
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-5799076113154048
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http<key>test<!test></key>
+://www.apple.com/D|Ds/Pr<false/>st-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>CF$UID</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <dict>
+ <key>Time1</key>
+ <date>2010-11-12T13:14:15Z</date>
+ <key>Time2</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time3</key>
+ <date>1869-01-03T08:16:32Z</date>
+ <key>Time4</key>
+ \ No newline at end of file
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496 b/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496
new file mode 100644
index 0000000..49efd9d
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-5922404220010496
Binary files differ
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168 b/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168
new file mode 100644
index 0000000..52c9d0a
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6015098361479168
Binary files differ
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936 b/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936
new file mode 100644
index 0000000..6c7ea6f
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6043836860071936
Binary files differ
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576 b/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576
new file mode 100644
index 0000000..96a889a
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6047509906456576
@@ -0,0 +1,10050 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ </array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>test</key>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <string>foo</string>
+ <array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ </array>
+ <array>
+ <array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ </array><dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ </array><dict>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ </array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <array>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>test</key>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <string>foo</string>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ </array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <key>foo</key>
+ <dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <dict>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <dict>
+ <key>test</key>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <key>foo</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <key>test</key>
+ <array>
+ </array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array>
+ <dict>
+ </dict>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <string>foo</string>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ </array>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <key>test</key>
+ <array>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <string>foo</string>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <string>foo</string>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <dict>
+ <key>test</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <string>foo</string>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array><dict>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ </array><dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <dict>
+ <array>
+ <array>
+ </array><dict>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ </dict>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <dict>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <key>test</key>
+ <string>foo</string>
+ <array>
+ <integer>1</integer>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <key>test</key>
+ <key>foo</key>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <key>foo</key>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <array>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ </array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ </array>
+ <dict>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <array>
+ <key>foo</key>
+ <array>
+ <dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array><dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <array>
+ <integer>1</integer>
+ <key>test</key>
+ <array>
+ <array>
+ <array>
+ <array>
+ <array>
+ <dict>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <array>
+ <key>test</key>
+ <array> \ No newline at end of file
diff --git a/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984 b/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984
new file mode 100644
index 0000000..54111bb
--- /dev/null
+++ b/fuzz/xplist-crashes/clusterfuzz-testcase-6482601374121984
Binary files differ
diff --git a/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17 b/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17
new file mode 100644
index 0000000..9038147
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-09788d7acb46b4b177422f9c4ee54556a3ad1f17
@@ -0,0 +1,53 @@
+<plist version="1.0">
+<dict>
+ <key>some test entitites</key>
+ <dict>
+ <key>copyright</key>
+ <string>&#169;</string>
+ <key>multiple entitites</key>
+ <string>&#x2F20;&#x2F21;&#x2F22;</string>
+ <key>multiple entitites with characters inbetween</key>
+ <string>a&#x2F23;b&#x2F24;c&#x2F25;d</string>
+ </dict>
+ <key>one byte entity</key>
+ <dict>
+ <key>one byte entity</key>
+ <string>&#64;</string>
+ <key>one byte entity with text after</key>
+ <string>&#x40;z</string>
+ <key>one byte entity with text before</key>
+ <string>a&#x40;</string>
+ <key>one byte entity with text before &amp; after</key>
+ <string>a&#64;z</string>
+ </dict>
+ <key>two-byte utf-8</key>
+ <dict>
+ <key>two-byte utf-8</key>
+ <string>&#xC4;</string>
+ <key>two-byte utf-8 with text after</key>
+ <string>&#xCB;yz</string>
+ <key>two-byte utf-8 with text before</key>
+ <string>ab&#xD6;</string>
+ <key>two-byte utf-8 with text before &amp; after</key>
+ <string>ab&#xDC;yz</string>
+ </dict>
+ <key>three byte utf-8</key>
+ <dict>
+ <key>three byte utf-8</key>
+ <string>&#xFFE0;</string>
+ <key>three byte utf-8 with text after</key>
+ <string>&#65505;xyz</string>
+ <key>three byte utf-8 with text bef&a;</key>
+ <string>abc&#xFFE5;</string>
+ <key>three byte utf-8 with text before &amp; after</key>
+ (<string>abc&#65510;xyz</string>
+ </dict>
+ <key>four byte utf-8</key>
+ <dict>
+ <key>four byte utf-8</key>
+ <string>&#x1D562;</string>
+ with text before &amp; after</key>
+ <string>abcd&#x1D565;wxyz</string>
+ </dict>
+</dict>
+</plist>
diff --git a/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1 b/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1
new file mode 100644
index 0000000..9954475
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-155c60d5c4b2778110fb4a633caa7d64eb9b99c1
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.aPropertyList-1.0.dtd">
+<plist version="1.0teger>
+ <integer>9223372036854775807</integer>
+ <integer>-9223372036854775808</integer>
+ <integer>9223372036854775808</036854775808</in6854775808</integer>
+ <integer>9223372036854775808</036854775808</integer>
+</array>
+</plist>
diff --git a/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883 b/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883
new file mode 100644
index 0000000..2942529
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-348c909a0b3532977185f551eb5d2ed04e694883
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+ <integer>-1</integer>
+ <integer>18446744073709551615</integer>
+ <integer>9223372036854775807</integer>
+ <integer>-9223372036854775808</integer>
+ <integer>9223372036854775808</integer>
+</array>
+</plist>
diff --git a/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a b/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a
new file mode 100644
index 0000000..cda6a0f
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-7bc172e27a2b1d4fcaf031f59d1f08fb83081e4a
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist versiog="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2010-11-12T13:14:15Z</date>
+ <key>Time2</key>
+ <date>4016-07-0-11<!--- \ No newline at end of file
diff --git a/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e b/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e
new file mode 100644
index 0000000..7a7d8f1
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-80c28b0e2a446665e68927dd4441c10c9a68e64e
Binary files differ
diff --git a/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6 b/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6
new file mode 100644
index 0000000..9a64518
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-8acf177da60ecf13c36d0da1b8e25de891980ea6
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"" http://www.apple.com/DTDs/PropertyList-1.0.dtd<integer>-1</integer>
+ <integnteger>
+</array>
+</plist>
diff --git a/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33 b/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33
new file mode 100644
index 0000000..1eed6ac
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-94276cf69a50e78dd6cdf079c208cb80f357cd33
Binary files differ
diff --git a/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9 b/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9
new file mode 100644
index 0000000..fb9a05f
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-9ddb891a0ec42401eb4629e6b99e01aef7909eb9
Binary files differ
diff --git a/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9 b/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9
new file mode 100644
index 0000000..f06d59b
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-a8b44e1f5be4f75f00a937016ec5405bce77a0f9
Binary files differ
diff --git a/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 b/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709
diff --git a/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d b/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d
new file mode 100644
index 0000000..278dbcc
--- /dev/null
+++ b/fuzz/xplist-crashes/crash-f03c680ee1c4d84c28e731752df77a61e213a24d
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Some ASCII string</key>
+ <string>Test ASCII String</string>
+ <key>Some UTF8 strings</key>
+ <array>
+ <string>àéèçù</string>
+ <string>日本語</string>
+ <string>汉语/漢語</string>
+ <string>한국어/ì¡°ì„ ë§</string>
+ <string>руÑÑкий Ñзык</string>
+ <string>الْعَرَبيّة</string>
+ <string>עִבְרִית</string>
+ <string>język polski</string>
+ <string>हिनà¥à¤¦à¥€</string>
+ </array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
+ <key>Boolean</key>
+ <false/>
+ <key>Another Boolean</key>
+ <true/>
+ <key>Some Int</key>
+ <integer>32434543632</integer>
+ <key>Some Real</key>
+ <real>58654.347656</real>
+ <key>Some Date</key>
+ <date>2009-02-12T22:23:00Z</date>
+ <key>Some Data</key>
+ <data>
+ MDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1
+ w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qm
+ w6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAow
+ MTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXD
+ uSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbD
+ qSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAx
+ MjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5
+ JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOp
+ IicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEy
+ MzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7kl
+ IcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6ki
+ Jygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIz
+ NDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUh
+ wqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSIn
+ KC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0
+ NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHC
+ pzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIico
+ LcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1
+ Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKn
+ Oi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygt
+ w6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2
+ Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6
+ LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3D
+ qF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3
+ ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzov
+ Oy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOo
+ X8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4
+ OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87
+ Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hf
+ w6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5
+ VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6Lzsu
+ LD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRFU1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/D
+ p8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlU
+ RVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fDoCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4s
+ Pz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVTVDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8On
+ w6ApPSvCsCTCoyRewqgqwrXDuSUhwqc6LzsuLD8+PH4je1t8YFxeQF19wqQKMDEyMzQ1Njc4OVRF
+ U1QwMTIzNDU2Nzg5dGVzdCbDqSInKC3DqF/Dp8OgKT0rwrAkwqMkXsKoKsK1w7klIcKnOi87Liw/
+ Pjx+I3tbfGBcXkBdfcKkCjAxMjM0NTY3ODlURVNUMDEyMzQ1Njc4OXRlc3Qmw6kiJygtw6hfw6fD
+ oCk9K8KwJMKjJF7CqCrCtcO5JSHCpzovOy4sPz48fiN7W3xgXF5AXX3CpAowMTIzNDU2Nzg5VEVT
+ VDAxMjM0NTY3ODl0ZXN0JsOpIicoLcOoX8Onw6ApPSvCsCTCoyRewqg \ No newline at end of file
diff --git a/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840 b/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840
new file mode 100644
index 0000000..aa1ca4e
--- /dev/null
+++ b/fuzz/xplist-leaks/clusterfuzz-testcase-4634751103139840
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE pló ©ist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict>
+</plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist><plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict>
+</plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist>
+</plist>
+</plist>
+</plist></plist>
+</plist><plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9rsion="1.0">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time4</key>
+ <date>1162-08-20T01:10:11Z</date>
+</dict><plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist version="1.0">
+<plist ve+/v9r \ No newline at end of file
diff --git a/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440 b/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440
new file mode 100644
index 0000000..be5f3f6
--- /dev/null
+++ b/fuzz/xplist-leaks/clusterfuzz-testcase-6056412121661440
@@ -0,0 +1,12 @@
+<plist version="9223372036854808575.1">
+<dict>
+ <key><![CDATA[cdata&key]]></key><key><![CDATA[cdata&key]]></key>
+ <plist version="32768.1">
+<dict>
+ <key><![CDATA[cdata&key]]></key><key><![CDATA[cdatadkey]]></key>
+ <plist version="32768.1">
+<dict>
+ <key><![CDATA[cdata&key]]></key><key><![CDATA[cdata&key]]></key>
+ <plist version="32768.1">
+<dict>
+ <key><![CDATA[cdata&key]]></key><key><![CDA
diff --git a/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07 b/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07
new file mode 100644
index 0000000..36e09f0
--- /dev/null
+++ b/fuzz/xplist-leaks/leak-47b0a9f21d3affab6cfc898e5140bb53265ada07
Binary files differ
diff --git a/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7 b/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7
new file mode 100644
index 0000000..4dec3dd
--- /dev/null
+++ b/fuzz/xplist-leaks/leak-4c81aae70b20d9b9408b78d40999a54205bdcfb7
Binary files differ
diff --git a/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf b/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf
new file mode 100644
index 0000000..ce9e7ac
--- /dev/null
+++ b/fuzz/xplist-leaks/leak-53bc466851eda790bd891733da5db0a7482886bf
@@ -0,0 +1,6 @@
+<plist version="1.0">
+<dict>
+ <key>test&amp</key>
+ <string>value</string>
+</dict>
+</plist>
diff --git a/fuzz/xplist.dict b/fuzz/xplist.dict
new file mode 100644
index 0000000..48b0367
--- /dev/null
+++ b/fuzz/xplist.dict
@@ -0,0 +1,51 @@
+################################################################################
+#
+# AFL dictionary for XML Property Lists
+# ----------------------
+#
+# Several basic syntax elements and attributes for libplist.
+#
+# Created by Nikias Bassen <nikias@gmx.li>
+# Adapted from libxml2's dict file (created by Michal Zalewski <lcamtuf@google.com>)
+#
+
+attr_encoding=" encoding=\"1\""
+attr_generic=" a=\"1\""
+attr_version=" version=\"1\""
+
+entity_builtin="&lt;"
+entity_decimal="&#1;"
+entity_external="&a;"
+entity_hex="&#x1;"
+
+string_cdata="CDATA"
+string_dashes="--"
+string_empty="EMPTY"
+string_empty_dblquotes="\"\""
+string_empty_quotes="''"
+string_parentheses="()"
+string_pcdata="#PCDATA"
+string_percent="%a"
+string_public="PUBLIC"
+string_utf8="UTF-8"
+
+tag_cdata="<![CDATA["
+tag_close="</plist>"
+tag_doctype="<!DOCTYPE"
+tag_open="<plist>"
+tag_open_close="<plist />"
+tag_open_exclamation="<!"
+tag_open_q="<?"
+tag_sq2_close="]]>"
+tag_xml_q="<?xml?>"
+tag_array="<array>"
+tag_data="<data>"
+tag_date="<date>"
+tag_dict="<dict>"
+tag_false="<false/>"
+tag_integer="<integer>"
+tag_key="<key>"
+tag_plist="<plist>"
+tag_real="<real>"
+tag_string="<string>"
+tag_true="<true/>"
diff --git a/plutil/plutil.h b/fuzz/xplist_fuzzer.cc
index 4be716e..c477c4d 100644
--- a/plutil/plutil.h
+++ b/fuzz/xplist_fuzzer.cc
@@ -1,8 +1,8 @@
/*
- * plutil.h
- * header for plist convertion tool
+ * xplist_fuzzer.cc
+ * XML plist fuzz target for libFuzzer
*
- * Copyright (c) 2008 Zach C. All Rights Reserved.
+ * Copyright (c) 2017 Nikias Bassen All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,11 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-typedef struct _options
+#include <plist/plist.h>
+#include <stdio.h>
+
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
{
- char *in_file, *out_file;
- uint8_t debug, in_fmt, out_fmt;
-} Options;
+ plist_t root_node = NULL;
+ plist_from_xml(reinterpret_cast<const char*>(data), size, &root_node);
+ plist_free(root_node);
-Options *parse_arguments(int argc, char *argv[]);
-void print_usage();
+ return 0;
+}
diff --git a/fuzz/xplist_fuzzer.options b/fuzz/xplist_fuzzer.options
new file mode 100644
index 0000000..bad5dac
--- /dev/null
+++ b/fuzz/xplist_fuzzer.options
@@ -0,0 +1,3 @@
+[libfuzzer]
+max_len = 4096
+dict = xplist.dict
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..d868952
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,20 @@
+#!/bin/sh
+SRCDIR=`dirname $0`
+if test -n "$1"; then
+ VER=$1
+else
+ if test -r "${SRCDIR}/.git" && test -x "`which git`" ; then
+ git update-index -q --refresh
+ if ! VER=`git describe --tags --dirty 2>/dev/null`; then
+ COMMIT=`git rev-parse --short HEAD`
+ DIRTY=`git diff --quiet HEAD || echo "-dirty"`
+ VER=`sed -n '1,/RE/s/Version \(.*\)/\1/p' ${SRCDIR}/NEWS`-git-${COMMIT}${DIRTY}
+ fi
+ else
+ if test -f "${SRCDIR}/.tarball-version"; then
+ VER=`cat "${SRCDIR}/.tarball-version"`
+ fi
+ fi
+fi
+VER=`printf %s "$VER" | head -n1`
+printf %s "$VER"
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
deleted file mode 100644
index ea77d45..0000000
--- a/include/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-SET( libplist_HDR
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/plist.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/plist++.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Array.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Boolean.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Data.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Date.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Dictionary.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Integer.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Node.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Real.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/String.h
- ${CMAKE_CURRENT_SOURCE_DIR}/plist/Structure.h
- )
-
-INSTALL( FILES ${libplist_HDR}
- DESTINATION include/plist COMPONENT dev)
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..2fa500e
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,15 @@
+nobase_include_HEADERS = \
+ plist/plist.h \
+ plist/plist++.h \
+ plist/Array.h \
+ plist/Boolean.h \
+ plist/Data.h \
+ plist/Date.h \
+ plist/Dictionary.h \
+ plist/Integer.h \
+ plist/Key.h \
+ plist/Node.h \
+ plist/Real.h \
+ plist/String.h \
+ plist/Structure.h \
+ plist/Uid.h
diff --git a/include/plist/Array.h b/include/plist/Array.h
index fd4dea8..f4360c5 100644
--- a/include/plist/Array.h
+++ b/include/plist/Array.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__ARRAY_H
-#define PLIST__ARRAY_H
+#ifndef PLIST_ARRAY_H
+#define PLIST_ARRAY_H
#include <plist/Structure.h>
#include <vector>
@@ -33,18 +33,43 @@ class Array : public Structure
public :
Array(Node* parent = NULL);
Array(plist_t node, Node* parent = NULL);
- Array(Array& a);
- Array& operator=(Array& a);
+ Array(const Array& a);
+ Array& operator=(const Array& a);
virtual ~Array();
- Node* Clone();
+ Node* Clone() const;
+
+ typedef std::vector<Node*>::iterator iterator;
+ typedef std::vector<Node*>::const_iterator const_iterator;
Node* operator[](unsigned int index);
- void Append(Node* node);
- void Insert(Node* node, unsigned int pos);
+ Node* Back();
+ Node* back();
+ Node* Front();
+ Node* front();
+ iterator Begin();
+ iterator begin();
+ iterator End();
+ iterator end();
+ const_iterator Begin() const;
+ const_iterator begin() const;
+ const_iterator End() const;
+ const_iterator end() const;
+ size_t size() const;
+ void Append(const Node& node);
+ void Append(const Node* node);
+ void Insert(const Node& node, unsigned int pos);
+ void Insert(const Node* node, unsigned int pos);
void Remove(Node* node);
void Remove(unsigned int pos);
- unsigned int GetNodeIndex(Node* node);
+ unsigned int GetNodeIndex(const Node& node) const;
+ unsigned int GetNodeIndex(const Node* node) const;
+ template <typename T> T* at(unsigned int index) {
+ return (T*)(_array.at(index));
+ }
+ template <typename T> T* At(unsigned int index) {
+ return (T*)(_array.at(index));
+ }
private :
std::vector<Node*> _array;
@@ -52,4 +77,4 @@ private :
};
-#endif // PLIST__ARRAY_H
+#endif // PLIST_ARRAY_H
diff --git a/include/plist/Boolean.h b/include/plist/Boolean.h
index 731eb9e..9ecbc48 100644
--- a/include/plist/Boolean.h
+++ b/include/plist/Boolean.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__BOOLEAN_H
-#define PLIST__BOOLEAN_H
+#ifndef PLIST_BOOLEAN_H
+#define PLIST_BOOLEAN_H
#include <plist/Node.h>
@@ -32,17 +32,17 @@ class Boolean : public Node
public :
Boolean(Node* parent = NULL);
Boolean(plist_t node, Node* parent = NULL);
- Boolean(Boolean& b);
- Boolean& operator=(Boolean& b);
+ Boolean(const Boolean& b);
+ Boolean& operator=(const Boolean& b);
Boolean(bool b);
virtual ~Boolean();
- Node* Clone();
+ Node* Clone() const;
void SetValue(bool b);
- bool GetValue();
+ bool GetValue() const;
};
};
-#endif // PLIST__BOOLEAN_H
+#endif // PLIST_BOOLEAN_H
diff --git a/include/plist/Data.h b/include/plist/Data.h
index f8ae715..1de88aa 100644
--- a/include/plist/Data.h
+++ b/include/plist/Data.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__DATA_H
-#define PLIST__DATA_H
+#ifndef PLIST_DATA_H
+#define PLIST_DATA_H
#include <plist/Node.h>
#include <vector>
@@ -33,17 +33,18 @@ class Data : public Node
public :
Data(Node* parent = NULL);
Data(plist_t node, Node* parent = NULL);
- Data(Data& d);
- Data& operator=(Data& d);
+ Data(const Data& d);
+ Data& operator=(const Data& b);
Data(const std::vector<char>& buff);
+ Data(const char* buff, uint64_t size);
virtual ~Data();
- Node* Clone();
+ Node* Clone() const;
void SetValue(const std::vector<char>& buff);
- std::vector<char> GetValue();
+ std::vector<char> GetValue() const;
};
};
-#endif // PLIST__DATA_H
+#endif // PLIST_DATA_H
diff --git a/include/plist/Date.h b/include/plist/Date.h
index 2ea830b..7026699 100644
--- a/include/plist/Date.h
+++ b/include/plist/Date.h
@@ -19,14 +19,15 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__DATE_H
-#define PLIST__DATE_H
+#ifndef PLIST_DATE_H
+#define PLIST_DATE_H
#include <plist/Node.h>
#include <ctime>
-
-#ifdef _WIN32
-#include <Winsock2.h>
+#ifdef _MSC_VER
+#include <winsock2.h>
+#else
+#include <sys/time.h>
#endif
namespace PList
@@ -37,17 +38,17 @@ class Date : public Node
public :
Date(Node* parent = NULL);
Date(plist_t node, Node* parent = NULL);
- Date(Date& d);
- Date& operator=(Date& d);
- Date(timeval t);
+ Date(const Date& d);
+ Date& operator=(const Date& d);
+ Date(int64_t t);
virtual ~Date();
- Node* Clone();
+ Node* Clone() const;
- void SetValue(timeval t);
- timeval GetValue();
+ void SetValue(int64_t t);
+ int64_t GetValue() const;
};
};
-#endif // PLIST__DATE_H
+#endif // PLIST_DATE_H
diff --git a/include/plist/Dictionary.h b/include/plist/Dictionary.h
index a937bde..37ff1c9 100644
--- a/include/plist/Dictionary.h
+++ b/include/plist/Dictionary.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__DICTIONARY_H
-#define PLIST__DICTIONARY_H
+#ifndef PLIST_DICTIONARY_H
+#define PLIST_DICTIONARY_H
#include <plist/Structure.h>
#include <map>
@@ -34,29 +34,40 @@ class Dictionary : public Structure
public :
Dictionary(Node* parent = NULL);
Dictionary(plist_t node, Node* parent = NULL);
- Dictionary(Dictionary& d);
- Dictionary& operator=(Dictionary& d);
+ Dictionary(const Dictionary& d);
+ Dictionary& operator=(const Dictionary& d);
virtual ~Dictionary();
- Node* Clone();
+ Node* Clone() const;
typedef std::map<std::string,Node*>::iterator iterator;
+ typedef std::map<std::string,Node*>::const_iterator const_iterator;
Node* operator[](const std::string& key);
iterator Begin();
+ iterator begin();
iterator End();
+ iterator end();
iterator Find(const std::string& key);
- iterator Insert(const std::string& key, Node* node);
+ const_iterator Begin() const;
+ const_iterator begin() const;
+ const_iterator End() const;
+ const_iterator end() const;
+ size_t size() const;
+ const_iterator Find(const std::string& key) const;
+ iterator Set(const std::string& key, const Node* node);
+ iterator Set(const std::string& key, const Node& node);
void Remove(Node* node);
void Remove(const std::string& key);
- std::string GetNodeKey(Node* key);
+ std::string GetNodeKey(Node* node);
+ template <typename T> T* Get(const std::string& key) {
+ return (T*)(_map[key]);
+ }
private :
std::map<std::string,Node*> _map;
-
-
};
};
-#endif // PLIST__DICTIONARY_H
+#endif // PLIST_DICTIONARY_H
diff --git a/include/plist/Integer.h b/include/plist/Integer.h
index 7cdf0df..1a4d980 100644
--- a/include/plist/Integer.h
+++ b/include/plist/Integer.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__INTEGER_H
-#define PLIST__INTEGER_H
+#ifndef PLIST_INTEGER_H
+#define PLIST_INTEGER_H
#include <plist/Node.h>
@@ -32,17 +32,23 @@ class Integer : public Node
public :
Integer(Node* parent = NULL);
Integer(plist_t node, Node* parent = NULL);
- Integer(Integer& i);
- Integer& operator=(Integer& i);
+ Integer(const Integer& i);
+ Integer& operator=(const Integer& i);
Integer(uint64_t i);
+ Integer(int64_t i);
virtual ~Integer();
- Node* Clone();
+ Node* Clone() const;
+ void SetValue(int64_t i);
void SetValue(uint64_t i);
- uint64_t GetValue();
+ void SetUnsignedValue(uint64_t i);
+ int64_t GetValue() const;
+ uint64_t GetUnsignedValue() const;
+
+ bool isNegative() const;
};
};
-#endif // PLIST__INTEGER_H
+#endif // PLIST_INTEGER_H
diff --git a/include/plist/Key.h b/include/plist/Key.h
new file mode 100644
index 0000000..bd5f7bc
--- /dev/null
+++ b/include/plist/Key.h
@@ -0,0 +1,49 @@
+/*
+ * Key.h
+ * Key node type for C++ binding
+ *
+ * Copyright (c) 2012 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PLIST_KEY_H
+#define PLIST_KEY_H
+
+#include <plist/Node.h>
+#include <string>
+
+namespace PList
+{
+
+class Key : public Node
+{
+public :
+ Key(Node* parent = NULL);
+ Key(plist_t node, Node* parent = NULL);
+ Key(const Key& k);
+ Key& operator=(const Key& k);
+ Key(const std::string& s);
+ virtual ~Key();
+
+ Node* Clone() const;
+
+ void SetValue(const std::string& s);
+ std::string GetValue() const;
+};
+
+};
+
+#endif // PLIST_KEY_H
diff --git a/include/plist/Node.h b/include/plist/Node.h
index 1da9ee1..6c9ae43 100644
--- a/include/plist/Node.h
+++ b/include/plist/Node.h
@@ -19,10 +19,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__NODE_H
-#define PLIST__NODE_H
+#ifndef PLIST_NODE_H
+#define PLIST_NODE_H
#include <plist/plist.h>
+#include <cstddef>
namespace PList
{
@@ -32,11 +33,11 @@ class Node
public :
virtual ~Node();
- virtual Node* Clone() = 0;
+ virtual Node* Clone() const = 0;
- Node * GetParent();
- plist_type GetType();
- plist_t GetPlist();
+ Node * GetParent() const;
+ plist_type GetType() const;
+ plist_t GetPlist() const;
static Node* FromPlist(plist_t node, Node* parent = NULL);
@@ -53,4 +54,4 @@ private:
};
-#endif // PLIST__NODE_H
+#endif // PLIST_NODE_H
diff --git a/include/plist/Real.h b/include/plist/Real.h
index 7d6f8e2..5afb0c0 100644
--- a/include/plist/Real.h
+++ b/include/plist/Real.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__REAL_H
-#define PLIST__REAL_H
+#ifndef PLIST_REAL_H
+#define PLIST_REAL_H
#include <plist/Node.h>
@@ -32,17 +32,17 @@ class Real : public Node
public :
Real(Node* parent = NULL);
Real(plist_t node, Node* parent = NULL);
- Real(Real& d);
- Real& operator=(Real& d);
+ Real(const Real& d);
+ Real& operator=(const Real& d);
Real(double d);
virtual ~Real();
- Node* Clone();
+ Node* Clone() const;
void SetValue(double d);
- double GetValue();
+ double GetValue() const;
};
};
-#endif // PLIST__REAL_H
+#endif // PLIST_REAL_H
diff --git a/include/plist/String.h b/include/plist/String.h
index bd8680d..95f8dd1 100644
--- a/include/plist/String.h
+++ b/include/plist/String.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__STRING_H
-#define PLIST__STRING_H
+#ifndef PLIST_STRING_H
+#define PLIST_STRING_H
#include <plist/Node.h>
#include <string>
@@ -33,17 +33,20 @@ class String : public Node
public :
String(Node* parent = NULL);
String(plist_t node, Node* parent = NULL);
- String(String& s);
- String& operator=(String& s);
+ String(const String& s);
+ String& operator=(const String& s);
+ String& operator=(const std::string& s);
+ String& operator=(const char* s);
String(const std::string& s);
+ String(const char *s);
virtual ~String();
- Node* Clone();
+ Node* Clone() const;
void SetValue(const std::string& s);
- std::string GetValue();
+ std::string GetValue() const;
};
};
-#endif // PLIST__STRING_H
+#endif // PLIST_STRING_H
diff --git a/include/plist/Structure.h b/include/plist/Structure.h
index ace8b5c..b7fbdb5 100644
--- a/include/plist/Structure.h
+++ b/include/plist/Structure.h
@@ -19,8 +19,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#ifndef PLIST__STRUCTURE_H
-#define PLIST__STRUCTURE_H
+#ifndef PLIST_STRUCTURE_H
+#define PLIST_STRUCTURE_H
#include <plist/Node.h>
#include <string>
@@ -34,15 +34,18 @@ class Structure : public Node
public :
virtual ~Structure();
- uint32_t GetSize();
+ uint32_t GetSize() const;
- std::string ToXml();
- std::vector<char> ToBin();
+ std::string ToXml() const;
+ std::vector<char> ToBin() const;
virtual void Remove(Node* node) = 0;
static Structure* FromXml(const std::string& xml);
static Structure* FromBin(const std::vector<char>& bin);
+ static Structure* FromBin(const char* bin, uint64_t size);
+ static Structure* FromMemory(const std::vector<char>& buf, plist_format_t* format = NULL);
+ static Structure* FromMemory(const char* buf, uint64_t size, plist_format_t* format = NULL);
protected:
Structure(Node* parent = NULL);
@@ -56,4 +59,4 @@ private:
};
-#endif // PLIST__STRUCTURE_H
+#endif // PLIST_STRUCTURE_H
diff --git a/include/plist/Uid.h b/include/plist/Uid.h
new file mode 100644
index 0000000..af6e51d
--- /dev/null
+++ b/include/plist/Uid.h
@@ -0,0 +1,48 @@
+/*
+ * Uid.h
+ * Uid node type for C++ binding
+ *
+ * Copyright (c) 2012 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PLIST_UID_H
+#define PLIST_UID_H
+
+#include <plist/Node.h>
+
+namespace PList
+{
+
+class Uid : public Node
+{
+public :
+ Uid(Node* parent = NULL);
+ Uid(plist_t node, Node* parent = NULL);
+ Uid(const Uid& i);
+ Uid& operator=(const Uid& i);
+ Uid(uint64_t i);
+ virtual ~Uid();
+
+ Node* Clone() const;
+
+ void SetValue(uint64_t i);
+ uint64_t GetValue() const;
+};
+
+};
+
+#endif // PLIST_UID_H
diff --git a/include/plist/plist++.h b/include/plist/plist++.h
index d5eb691..b29fc76 100644
--- a/include/plist/plist++.h
+++ b/include/plist/plist++.h
@@ -31,6 +31,8 @@
#include "Integer.h"
#include "Node.h"
#include "Real.h"
+#include "Key.h"
+#include "Uid.h"
#include "String.h"
#include "Structure.h"
diff --git a/include/plist/plist.h b/include/plist/plist.h
index 1b1ac52..e720aac 100644
--- a/include/plist/plist.h
+++ b/include/plist/plist.h
@@ -3,7 +3,8 @@
* @brief Main include of libplist
* \internal
*
- * Copyright (c) 2008 Jonathan Beck All Rights Reserved.
+ * Copyright (c) 2012-2023 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2008-2009 Jonathan Beck, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -28,7 +29,7 @@ extern "C"
{
#endif
-#ifdef _MSC_VER
+#if _MSC_VER && _MSC_VER < 1700
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
@@ -39,21 +40,57 @@ extern "C"
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
-#ifdef plist_EXPORTS
-#define PLIST_API __declspec( dllexport )
#else
-#define PLIST_API __declspec( dllimport )
+#include <stdint.h>
#endif
+
+/*{{{ deprecation macros */
+#ifdef __llvm__
+ #if defined(__has_extension)
+ #if (__has_extension(attribute_deprecated_with_message))
+ #ifndef PLIST_WARN_DEPRECATED
+ #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated(x)))
+ #endif
+ #else
+ #ifndef PLIST_WARN_DEPRECATED
+ #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated))
+ #endif
+ #endif
+ #else
+ #ifndef PLIST_WARN_DEPRECATED
+ #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated))
+ #endif
+ #endif
+#elif (__GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 5)))
+ #ifndef PLIST_WARN_DEPRECATED
+ #define PLIST_WARN_DEPRECATED(x) __attribute__((deprecated(x)))
+ #endif
+#elif defined(_MSC_VER)
+ #ifndef PLIST_WARN_DEPRECATED
+ #define PLIST_WARN_DEPRECATED(x) __declspec(deprecated(x))
+ #endif
#else
-#include <stdint.h>
-#define PLIST_API
+ #define PLIST_WARN_DEPRECATED(x)
+ #pragma message("WARNING: You need to implement DEPRECATED for this compiler")
+#endif
+/*}}}*/
+
+#ifndef PLIST_API
+ #ifdef LIBPLIST_STATIC
+ #define PLIST_API
+ #elif defined(_WIN32)
+ #define PLIST_API __declspec(dllimport)
+ #else
+ #define PLIST_API
+ #endif
#endif
#include <sys/types.h>
#include <stdarg.h>
+#include <stdio.h>
/**
- * \mainpage libplist : A library to handle Apple Property Lists
+ * libplist : A library to handle Apple Property Lists
* \defgroup PublicAPI Public libplist API
*/
/*@{*/
@@ -67,25 +104,87 @@ extern "C"
/**
* The plist dictionary iterator.
*/
- typedef void *plist_dict_iter;
+ typedef void* plist_dict_iter;
+
+ /**
+ * The plist array iterator.
+ */
+ typedef void* plist_array_iter;
/**
* The enumeration of plist node types.
*/
typedef enum
{
- PLIST_BOOLEAN, /**< Boolean, scalar type */
- PLIST_UINT, /**< Unsigned integer, scalar type */
- PLIST_REAL, /**< Real, scalar type */
- PLIST_STRING, /**< ASCII string, scalar type */
- PLIST_ARRAY, /**< Ordered array, structured type */
- PLIST_DICT, /**< Unordered dictionary (key/value pair), structured type */
- PLIST_DATE, /**< Date, scalar type */
- PLIST_DATA, /**< Binary data, scalar type */
- PLIST_KEY, /**< Key in dictionaries (ASCII String), scalar type */
- PLIST_NONE /**< No type */
+ PLIST_NONE =-1, /**< No type */
+ PLIST_BOOLEAN, /**< Boolean, scalar type */
+ PLIST_INT, /**< Integer, scalar type */
+ PLIST_REAL, /**< Real, scalar type */
+ PLIST_STRING, /**< ASCII string, scalar type */
+ PLIST_ARRAY, /**< Ordered array, structured type */
+ PLIST_DICT, /**< Unordered dictionary (key/value pair), structured type */
+ PLIST_DATE, /**< Date, scalar type */
+ PLIST_DATA, /**< Binary data, scalar type */
+ PLIST_KEY, /**< Key in dictionaries (ASCII String), scalar type */
+ PLIST_UID, /**< Special type used for 'keyed encoding' */
+ PLIST_NULL, /**< NULL type */
} plist_type;
+ /* for backwards compatibility */
+ #define PLIST_UINT PLIST_INT
+
+ /**
+ * libplist error values
+ */
+ typedef enum
+ {
+ PLIST_ERR_SUCCESS = 0, /**< operation successful */
+ PLIST_ERR_INVALID_ARG = -1, /**< one or more of the parameters are invalid */
+ PLIST_ERR_FORMAT = -2, /**< the plist contains nodes not compatible with the output format */
+ PLIST_ERR_PARSE = -3, /**< parsing of the input format failed */
+ PLIST_ERR_NO_MEM = -4, /**< not enough memory to handle the operation */
+ PLIST_ERR_IO = -5, /**< I/O error */
+ PLIST_ERR_CIRCULAR_REF = -6, /**< circular reference detected */
+ PLIST_ERR_MAX_NESTING = -7, /**< maximum nesting depth exceeded */
+ PLIST_ERR_UNKNOWN = -255 /**< an unspecified error occurred */
+ } plist_err_t;
+
+ /**
+ * libplist format types
+ */
+ typedef enum
+ {
+ PLIST_FORMAT_NONE = 0, /**< No format */
+ PLIST_FORMAT_XML = 1, /**< XML format */
+ PLIST_FORMAT_BINARY = 2, /**< bplist00 format */
+ PLIST_FORMAT_JSON = 3, /**< JSON format */
+ PLIST_FORMAT_OSTEP = 4, /**< OpenStep "old-style" plist format */
+ /* 5-9 are reserved for possible future use */
+ PLIST_FORMAT_PRINT = 10, /**< human-readable output-only format */
+ PLIST_FORMAT_LIMD = 11, /**< "libimobiledevice" output-only format (ideviceinfo) */
+ PLIST_FORMAT_PLUTIL = 12, /**< plutil-style output-only format */
+ } plist_format_t;
+
+ /**
+ * libplist write options
+ */
+ typedef enum
+ {
+ PLIST_OPT_NONE = 0, /**< Default value to use when none of the options is needed. */
+ PLIST_OPT_COMPACT = 1 << 0, /**< Use a compact representation (non-prettified). Only valid for #PLIST_FORMAT_JSON and #PLIST_FORMAT_OSTEP. */
+ PLIST_OPT_PARTIAL_DATA = 1 << 1, /**< Print 24 bytes maximum of #PLIST_DATA values. If the data is longer than 24 bytes, the first 16 and last 8 bytes will be written. Only valid for #PLIST_FORMAT_PRINT. */
+ PLIST_OPT_NO_NEWLINE = 1 << 2, /**< Do not print a final newline character. Only valid for #PLIST_FORMAT_PRINT, #PLIST_FORMAT_LIMD, and #PLIST_FORMAT_PLUTIL. */
+ PLIST_OPT_INDENT = 1 << 3, /**< Indent each line of output. Currently only #PLIST_FORMAT_PRINT and #PLIST_FORMAT_LIMD are supported. Use #PLIST_OPT_INDENT_BY() macro to specify the level of indentation. */
+ PLIST_OPT_COERCE = 1 << 4, /**< Coerce plist types that have no native JSON representation into JSON-compatible types.
+ #PLIST_DATE is converted to an ISO 8601 date string,
+ #PLIST_DATA is converted to a Base64-encoded string, and
+ #PLIST_UID is converted to an integer.
+ Only valid for #PLIST_FORMAT_JSON. Without this option, these types cause #PLIST_ERR_FORMAT. */
+ } plist_write_options_t;
+
+ /** To be used with #PLIST_OPT_INDENT - encodes the level of indentation for OR'ing it into the #plist_write_options_t bitfield. */
+ #define PLIST_OPT_INDENT_BY(x) ((x & 0xFF) << 24)
+
/********************************************
* *
@@ -128,15 +227,28 @@ extern "C"
PLIST_API plist_t plist_new_bool(uint8_t val);
/**
- * Create a new plist_t type #PLIST_UINT
+ * Create a new plist_t type #PLIST_INT with an unsigned integer value
*
* @param val the unsigned integer value
* @return the created item
* @sa #plist_type
+ * @note The value is always stored as uint64_t internally.
+ * Use #plist_get_uint_val or #plist_get_int_val to get the unsigned or signed value.
*/
PLIST_API plist_t plist_new_uint(uint64_t val);
/**
+ * Create a new plist_t type #PLIST_INT with a signed integer value
+ *
+ * @param val the signed integer value
+ * @return the created item
+ * @sa #plist_type
+ * @note The value is always stored as uint64_t internally.
+ * Use #plist_get_uint_val or #plist_get_int_val to get the unsigned or signed value.
+ */
+ PLIST_API plist_t plist_new_int(int64_t val);
+
+ /**
* Create a new plist_t type #PLIST_REAL
*
* @param val the real value
@@ -158,12 +270,29 @@ extern "C"
/**
* Create a new plist_t type #PLIST_DATE
*
- * @param sec the number of seconds since 01/01/2001
- * @param usec the number of microseconds
+ * @param sec The number of seconds since 01/01/1970 (UNIX timestamp)
* @return the created item
* @sa #plist_type
*/
- PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec);
+ PLIST_API plist_t plist_new_unix_date(int64_t sec);
+
+ /**
+ * Create a new plist_t type #PLIST_UID
+ *
+ * @param val the unsigned integer value
+ * @return the created item
+ * @sa #plist_type
+ */
+ PLIST_API plist_t plist_new_uid(uint64_t val);
+
+ /**
+ * Create a new plist_t type #PLIST_NULL
+ * @return the created item
+ * @sa #plist_type
+ * @note This type is not valid for all formats, e.g. the XML format
+ * does not support it.
+ */
+ PLIST_API plist_t plist_new_null(void);
/**
* Destruct a plist_t node and all its children recursively
@@ -208,7 +337,7 @@ extern "C"
* Get the index of an item. item must be a member of a #PLIST_ARRAY node.
*
* @param node the node
- * @return the node index
+ * @return the node index or UINT_MAX if node index can't be determined
*/
PLIST_API uint32_t plist_array_get_item_index(plist_t node);
@@ -217,36 +346,105 @@ extern "C"
* The previous item at index n will be freed using #plist_free
*
* @param node the node of type #PLIST_ARRAY
- * @param item the new item at index n
- * @param n the index of the item to get. Range is [0, array_size[. Assert if n is not in range.
+ * @param item the new item at index n. The array is responsible for freeing item when it is no longer needed.
+ * @param n the index of the item to get. Range is [0, array_size[.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, item is already
+ * attached to a parent, or n is outside the valid range.
+ * - PLIST_ERR_NO_MEM if replacing the item fails due to memory allocation failure
+ * and the original array element was restored successfully.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred, including failure
+ * to restore the original array element after insertion of the replacement failed.
*/
- PLIST_API void plist_array_set_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API plist_err_t plist_array_set_item(plist_t node, plist_t item, uint32_t n);
/**
* Append a new item at the end of a #PLIST_ARRAY node.
*
* @param node the node of type #PLIST_ARRAY
- * @param item the new item
+ * @param item the new item. The array is responsible for freeing item when it is no longer needed.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, or item is
+ * already attached to a parent.
+ * - PLIST_ERR_NO_MEM if memory allocation failed while appending the item.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred.
*/
- PLIST_API void plist_array_append_item(plist_t node, plist_t item);
+ PLIST_API plist_err_t plist_array_append_item(plist_t node, plist_t item);
/**
* Insert a new item at position n in a #PLIST_ARRAY node.
*
* @param node the node of type #PLIST_ARRAY
- * @param item the new item to insert
- * @param n The position at which the node will be stored. Range is [0, array_size[. Assert if n is not in range.
+ * @param item the new item to insert. The array is responsible for freeing item when it is no longer needed.
+ * @param n The position at which the node will be stored. Range is [0, array_size[.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not an array, item is NULL, item is already
+ * attached to a parent, or n is outside the valid insertion range.
+ * - PLIST_ERR_NO_MEM if memory allocation failed while inserting the item.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred.
*/
- PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API plist_err_t plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
/**
* Remove an existing position in a #PLIST_ARRAY node.
- * Removed position will be freed using #plist_free
+ * Removed position will be freed using #plist_free.
*
* @param node the node of type #PLIST_ARRAY
- * @param n The position to remove. Range is [0, array_size[. Assert if n is not in range.
+ * @param n The position to remove. Range is [0, array_size[.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not an array or n is outside the valid range.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred while removing
+ * the item.
+ */
+ PLIST_API plist_err_t plist_array_remove_item(plist_t node, uint32_t n);
+
+ /**
+ * Remove a node that is a child node of a #PLIST_ARRAY node.
+ * node will be freed using #plist_free.
+ *
+ * @param node The node to be removed from its #PLIST_ARRAY parent.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if item is NULL or not a child of a #PLIST_ARRAY
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred.
+ */
+ PLIST_API plist_err_t plist_array_item_remove(plist_t item);
+
+ /**
+ * Create an iterator of a #PLIST_ARRAY node.
+ * The allocated iterator should be freed with the standard free function.
+ *
+ * @param node The node of type #PLIST_ARRAY
+ * @param iter Location to store the iterator for the array.
+ */
+ PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter);
+
+ /**
+ * Increment iterator of a #PLIST_ARRAY node.
+ *
+ * @param node The node of type #PLIST_ARRAY.
+ * @param iter Iterator of the array
+ * @param item Location to store the item. The caller must *not* free the
+ * returned item. Will be set to NULL when no more items are left
+ * to iterate.
+ */
+ PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item);
+
+ /**
+ * Free #PLIST_ARRAY iterator.
+ *
+ * @param iter Iterator to free.
*/
- PLIST_API void plist_array_remove_item(plist_t node, uint32_t n);
+ PLIST_API void plist_array_free_iter(plist_array_iter iter);
/********************************************
* *
@@ -263,29 +461,39 @@ extern "C"
PLIST_API uint32_t plist_dict_get_size(plist_t node);
/**
- * Create iterator of a #PLIST_DICT node.
- * The allocated iterator shoult be freed with tandard free function
+ * Create an iterator of a #PLIST_DICT node.
+ * The allocated iterator should be freed with the standard free function.
*
- * @param node the node of type #PLIST_DICT
- * @param iter iterator of the #PLIST_DICT node
+ * @param node The node of type #PLIST_DICT.
+ * @param iter Location to store the iterator for the dictionary.
*/
PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter);
/**
* Increment iterator of a #PLIST_DICT node.
*
- * @param node the node of type #PLIST_DICT
- * @param iter iterator of the dictionary
- * @param key a location to store the key, or NULL.
- * @param val a location to store the value, or NULL.
+ * @param node The node of type #PLIST_DICT
+ * @param iter Iterator of the dictionary
+ * @param key Location to store the key, or NULL. The caller is responsible
+ * for freeing the the returned string.
+ * @param val Location to store the value, or NULL. The caller must *not*
+ * free the returned value. Will be set to NULL when no more
+ * key/value pairs are left to iterate.
*/
PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val);
/**
- * Get key associated to an item. Item must be member of a dictionary
+ * Free #PLIST_DICT iterator.
*
- * @param node the node
- * @param key a location to store the key.
+ * @param iter Iterator to free.
+ */
+ PLIST_API void plist_dict_free_iter(plist_dict_iter iter);
+
+ /**
+ * Get key associated key to an item. Item must be member of a dictionary.
+ *
+ * @param node the item
+ * @param key a location to store the key. The caller is responsible for freeing the returned string.
*/
PLIST_API void plist_dict_get_item_key(plist_t node, char **key);
@@ -294,38 +502,232 @@ extern "C"
*
* @param node the node of type #PLIST_DICT
* @param key the identifier of the item to get.
- * @return the item or NULL if node is not of type #PLIST_DICT
+ * @return the item or NULL if node is not of type #PLIST_DICT. The caller should not free
+ * the returned node.
*/
PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key);
/**
- * Set item identified by key in a #PLIST_DICT node.
- * The previous item at index n will be freed using #plist_free
+ * Get key node associated to an item. Item must be member of a dictionary.
*
- * @param node the node of type #PLIST_DICT
- * @param item the new item associated to key
- * @param key the identifier of the item to get. Assert if identifier is not present.
+ * @param node the item
+ * @return the key node of the given item, or NULL.
*/
- PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item);
+ PLIST_API plist_t plist_dict_item_get_key(plist_t node);
/**
- * Insert a new item at position n in a #PLIST_DICT node.
+ * Set item identified by key in a #PLIST_DICT node.
+ * The previous item identified by key will be freed using #plist_free.
+ * If there is no item for the given key a new item will be inserted.
*
* @param node the node of type #PLIST_DICT
- * @param item the new item to insert
- * @param key The identifier of the item to insert. Assert if identifier already present.
+ * @param item the new item associated to key
+ * @param key the identifier of the item to set.
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not a dictionary, key is NULL, item is NULL,
+ * or item is already attached to a parent.
+ * - PLIST_ERR_NO_MEM if memory allocation failed while creating or inserting the
+ * key/value pair.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred.
*/
- PLIST_API void plist_dict_insert_item(plist_t node, const char* key, plist_t item);
+ PLIST_API plist_err_t plist_dict_set_item(plist_t node, const char* key, plist_t item);
/**
* Remove an existing position in a #PLIST_DICT node.
* Removed position will be freed using #plist_free
*
* @param node the node of type #PLIST_DICT
- * @param key The identifier of the item to remove. Assert if identifier is not present.
+ * @param key The identifier of the item to remove
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if node is not a dictionary, key is NULL, or no item
+ * with the given key exists.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred while removing
+ * the item.
+ */
+ PLIST_API plist_err_t plist_dict_remove_item(plist_t node, const char* key);
+
+ /**
+ * Merge a dictionary into another. This will add all key/value pairs
+ * from the source dictionary to the target dictionary, overwriting
+ * any existing key/value pairs that are already present in target.
+ *
+ * @param target pointer to an existing node of type #PLIST_DICT
+ * @param source node of type #PLIST_DICT that should be merged into target
+ *
+ * @return
+ * - PLIST_ERR_SUCCESS on success.
+ * - PLIST_ERR_INVALID_ARG if target is NULL, source is NULL, source is not a
+ * dictionary, or *target is not NULL and does not point to a dictionary.
+ * - PLIST_ERR_NO_MEM if memory allocation failed while creating the target
+ * dictionary or copying items from source.
+ * - PLIST_ERR_UNKNOWN if an unexpected internal error occurred.
+ */
+ PLIST_API plist_err_t plist_dict_merge(plist_t *target, plist_t source);
+
+ /**
+ * Get a boolean value from a given #PLIST_DICT entry.
+ *
+ * The value node can be of type #PLIST_BOOLEAN, but also
+ * #PLIST_STRING (either 'true' or 'false'),
+ * #PLIST_INT with a numerical value of 0 or >= 1,
+ * or #PLIST_DATA with a single byte with a value of 0 or >= 1.
+ *
+ * @note This function returns 0 if the dictionary does not contain an
+ * entry for the given key, if the value node is of any other than
+ * the above mentioned type, or has any mismatching value.
+ *
+ * @param dict A node of type #PLIST_DICT
+ * @param key The key to look for in dict
+ * @return 0 or 1 depending on the value of the node.
+ */
+ PLIST_API uint8_t plist_dict_get_bool(plist_t dict, const char *key);
+
+ /**
+ * Get a signed integer value from a given #PLIST_DICT entry.
+ * The value node can be of type #PLIST_INT, but also
+ * #PLIST_STRING with a numerical value as string (decimal or hexadecimal),
+ * or #PLIST_DATA with a size of 1, 2, 4, or 8 bytes in little endian byte order.
+ *
+ * @note This function returns 0 if the dictionary does not contain an
+ * entry for the given key, if the value node is of any other than
+ * the above mentioned type, or has any mismatching value.
+ *
+ * @param dict A node of type #PLIST_DICT
+ * @param key The key to look for in dict
+ * @return Signed integer value depending on the value of the node.
+ */
+ PLIST_API int64_t plist_dict_get_int(plist_t dict, const char *key);
+
+ /**
+ * Get an unsigned integer value from a given #PLIST_DICT entry.
+ * The value node can be of type #PLIST_INT, but also
+ * #PLIST_STRING with a numerical value as string (decimal or hexadecimal),
+ * or #PLIST_DATA with a size of 1, 2, 4, or 8 bytes in little endian byte order.
+ *
+ * @note This function returns 0 if the dictionary does not contain an
+ * entry for the given key, if the value node is of any other than
+ * the above mentioned type, or has any mismatching value.
+ *
+ * @param dict A node of type #PLIST_DICT
+ * @param key The key to look for in dict
+ * @return Signed integer value depending on the value of the node.
+ */
+ PLIST_API uint64_t plist_dict_get_uint(plist_t dict, const char *key);
+
+ /**
+ * Copy a node from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key.
+ */
+ PLIST_API plist_err_t plist_dict_copy_item(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
+
+ /**
+ * Copy a boolean value from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @note The boolean value from *source_dict* is retrieved with #plist_dict_get_bool,
+ * but is **always** created as #PLIST_BOOLEAN in *target_dict*.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key.
+ */
+ PLIST_API plist_err_t plist_dict_copy_bool(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
+
+ /**
+ * Copy a signed integer value from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @note The signed integer value from *source_dict* is retrieved with #plist_dict_get_int,
+ * but is **always** created as #PLIST_INT.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node value to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key.
*/
- PLIST_API void plist_dict_remove_item(plist_t node, const char* key);
+ PLIST_API plist_err_t plist_dict_copy_int(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
+ /**
+ * Copy an unsigned integer value from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @note The unsigned integer value from *source_dict* is retrieved with #plist_dict_get_uint,
+ * but is **always** created as #PLIST_INT.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node value to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key.
+ */
+ PLIST_API plist_err_t plist_dict_copy_uint(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
+
+ /**
+ * Copy a #PLIST_DATA node from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @note This function is like #plist_dict_copy_item, except that it fails
+ * if the source node is not of type #PLIST_DATA.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node value to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key, or if it is not of type #PLIST_DATA.
+ */
+ PLIST_API plist_err_t plist_dict_copy_data(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
+
+ /**
+ * Copy a #PLIST_STRING node from *source_dict* to *target_dict*.
+ * The node is looked up in *source_dict* with given *key*, unless *alt_source_key*
+ * is non-NULL, in which case it is looked up with *alt_source_key*.
+ * The entry in *target_dict* is **always** created with *key*.
+ *
+ * @note This function is like #plist_dict_copy_item, except that it fails
+ * if the source node is not of type #PLIST_STRING.
+ *
+ * @param target_dict The target dictionary to copy to.
+ * @param source_dict The source dictionary to copy from.
+ * @param key The key for the node value to copy.
+ * @param alt_source_key The alternative source key for lookup in *source_dict* or NULL.
+ *
+ * @result PLIST_ERR_SUCCESS on success or PLIST_ERR_INVALID_ARG if the source dictionary does not contain
+ * any entry with given key or alt_source_key, or if it is not of type #PLIST_STRING.
+ */
+ PLIST_API plist_err_t plist_dict_copy_string(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key);
/********************************************
* *
@@ -355,6 +757,7 @@ extern "C"
* @param node the node
* @param val a pointer to a C-string. This function allocates the memory,
* caller is responsible for freeing it.
+ * @note Use plist_mem_free() to free the allocated memory.
*/
PLIST_API void plist_get_key_val(plist_t node, char **val);
@@ -365,10 +768,24 @@ extern "C"
* @param node the node
* @param val a pointer to a C-string. This function allocates the memory,
* caller is responsible for freeing it. Data is UTF-8 encoded.
+ * @note Use plist_mem_free() to free the allocated memory.
*/
PLIST_API void plist_get_string_val(plist_t node, char **val);
/**
+ * Get a pointer to the buffer of a #PLIST_STRING node.
+ *
+ * @note DO NOT MODIFY the buffer. Mind that the buffer is only available
+ * until the plist node gets freed. Make a copy if needed.
+ *
+ * @param node The node
+ * @param length If non-NULL, will be set to the length of the string
+ *
+ * @return Pointer to the NULL-terminated buffer.
+ */
+ PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length);
+
+ /**
* Get the value of a #PLIST_BOOLEAN node.
* This function does nothing if node is not of type #PLIST_BOOLEAN
*
@@ -378,8 +795,8 @@ extern "C"
PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val);
/**
- * Get the value of a #PLIST_UINT node.
- * This function does nothing if node is not of type #PLIST_UINT
+ * Get the unsigned integer value of a #PLIST_INT node.
+ * This function does nothing if node is not of type #PLIST_INT
*
* @param node the node
* @param val a pointer to a uint64_t variable.
@@ -387,6 +804,15 @@ extern "C"
PLIST_API void plist_get_uint_val(plist_t node, uint64_t * val);
/**
+ * Get the signed integer value of a #PLIST_INT node.
+ * This function does nothing if node is not of type #PLIST_INT
+ *
+ * @param node the node
+ * @param val a pointer to a int64_t variable.
+ */
+ PLIST_API void plist_get_int_val(plist_t node, int64_t * val);
+
+ /**
* Get the value of a #PLIST_REAL node.
* This function does nothing if node is not of type #PLIST_REAL
*
@@ -403,18 +829,40 @@ extern "C"
* @param val a pointer to an unallocated char buffer. This function allocates the memory,
* caller is responsible for freeing it.
* @param length the length of the buffer
+ * @note Use plist_mem_free() to free the allocated memory.
*/
PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length);
/**
+ * Get a pointer to the data buffer of a #PLIST_DATA node.
+ *
+ * @note DO NOT MODIFY the buffer. Mind that the buffer is only available
+ * until the plist node gets freed. Make a copy if needed.
+ *
+ * @param node The node
+ * @param length Pointer to a uint64_t that will be set to the length of the buffer
+ *
+ * @return Pointer to the buffer
+ */
+ PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length);
+
+ /**
* Get the value of a #PLIST_DATE node.
* This function does nothing if node is not of type #PLIST_DATE
*
* @param node the node
- * @param sec a pointer to an int32_t variable. Represents the number of seconds since 01/01/2001.
- * @param usec a pointer to an int32_t variable. Represents the number of microseconds
+ * @param sec a pointer to an int64_t variable. Represents the number of seconds since 01/01/1970 (UNIX timestamp).
*/
- PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec);
+ PLIST_API void plist_get_unix_date_val(plist_t node, int64_t *sec);
+
+ /**
+ * Get the value of a #PLIST_UID node.
+ * This function does nothing if node is not of type #PLIST_UID
+ *
+ * @param node the node
+ * @param val a pointer to a uint64_t variable.
+ */
+ PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val);
/********************************************
@@ -424,14 +872,6 @@ extern "C"
********************************************/
/**
- * Forces type of node. Changing type of structured nodes is only allowed if node is empty.
- * Reset value of node;
- * @param node the node
- * @param type the key value
- */
- PLIST_API void plist_set_type(plist_t node, plist_type type);
-
- /**
* Set the value of a node.
* Forces type of node to #PLIST_KEY
*
@@ -445,7 +885,8 @@ extern "C"
* Forces type of node to #PLIST_STRING
*
* @param node the node
- * @param val the string value
+ * @param val the string value. The string is copied when set and will be
+ * freed by the node.
*/
PLIST_API void plist_set_string_val(plist_t node, const char *val);
@@ -460,7 +901,7 @@ extern "C"
/**
* Set the value of a node.
- * Forces type of node to #PLIST_UINT
+ * Forces type of node to #PLIST_INT
*
* @param node the node
* @param val the unsigned integer value
@@ -469,6 +910,15 @@ extern "C"
/**
* Set the value of a node.
+ * Forces type of node to #PLIST_INT
+ *
+ * @param node the node
+ * @param val the signed integer value
+ */
+ PLIST_API void plist_set_int_val(plist_t node, int64_t val);
+
+ /**
+ * Set the value of a node.
* Forces type of node to #PLIST_REAL
*
* @param node the node
@@ -481,7 +931,8 @@ extern "C"
* Forces type of node to #PLIST_DATA
*
* @param node the node
- * @param val the binary buffer
+ * @param val the binary buffer. The buffer is copied when set and will
+ * be freed by the node.
* @param length the length of the buffer
*/
PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length);
@@ -491,10 +942,18 @@ extern "C"
* Forces type of node to #PLIST_DATE
*
* @param node the node
- * @param sec the number of seconds since 01/01/2001
- * @param usec the number of microseconds
+ * @param sec the number of seconds since 01/01/1970 (UNIX timestamp)
*/
- PLIST_API void plist_set_date_val(plist_t node, int32_t sec, int32_t usec);
+ PLIST_API void plist_set_unix_date_val(plist_t node, int64_t sec);
+
+ /**
+ * Set the value of a node.
+ * Forces type of node to #PLIST_UID
+ *
+ * @param node the node
+ * @param val the unsigned integer value
+ */
+ PLIST_API void plist_set_uid_val(plist_t node, uint64_t val);
/********************************************
@@ -510,8 +969,10 @@ extern "C"
* @param plist_xml a pointer to a C-string. This function allocates the memory,
* caller is responsible for freeing it. Data is UTF-8 encoded.
* @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
*/
- PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length);
+ PLIST_API plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length);
/**
* Export the #plist_t structure to binary format.
@@ -520,8 +981,84 @@ extern "C"
* @param plist_bin a pointer to a char* buffer. This function allocates the memory,
* caller is responsible for freeing it.
* @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
+ */
+ PLIST_API plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
+
+ /**
+ * Export the #plist_t structure to JSON format.
+ *
+ * @param plist the root node to export
+ * @param plist_json a pointer to a char* buffer. This function allocates the memory,
+ * caller is responsible for freeing it.
+ * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @param prettify pretty print the output if != 0
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
+ */
+ PLIST_API plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify);
+
+ /**
+ * Export the #plist_t structure to JSON format with extended options.
+ *
+ * When \a PLIST_OPT_COMPACT is set in \a options, the resulting JSON
+ * will be non-prettified.
+ *
+ * When \a PLIST_OPT_COERCE is set in \a options, plist types that have
+ * no native JSON representation are converted to JSON-compatible types
+ * instead of returning #PLIST_ERR_FORMAT:
+ * - #PLIST_DATE is serialized as an ISO 8601 date string
+ * - #PLIST_DATA is serialized as a Base64-encoded string
+ * - #PLIST_UID is serialized as an integer
+ *
+ * @param plist the root node to export
+ * @param plist_json a pointer to a char* buffer. This function allocates the memory,
+ * caller is responsible for freeing it.
+ * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @param options One or more bitwise ORed values of #plist_write_options_t.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
+ */
+ PLIST_API plist_err_t plist_to_json_with_options(plist_t plist, char **plist_json, uint32_t* length, plist_write_options_t options);
+
+ /**
+ * Export the #plist_t structure to OpenStep format.
+ *
+ * @param plist the root node to export
+ * @param plist_openstep a pointer to a char* buffer. This function allocates the memory,
+ * caller is responsible for freeing it.
+ * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @param prettify pretty print the output if != 0
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
+ */
+ PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify);
+
+ /**
+ * Export the #plist_t structure to OpenStep format with extended options.
+ *
+ * When \a PLIST_OPT_COMPACT is set in \a options, the resulting OpenStep output
+ * will be non-prettified.
+ *
+ * When \a PLIST_OPT_COERCE is set in \a options, plist types that have
+ * no native OpenStep representation are converted to OpenStep-compatible types
+ * instead of returning #PLIST_ERR_FORMAT:
+ * - #PLIST_BOOLEAN is serialized as a 1 or 0
+ * - #PLIST_DATE is serialized as an ISO 8601 date string
+ * - #PLIST_NULL is serialized as string 'NULL'
+ * - #PLIST_UID is serialized as an integer
+ *
+ * @param plist the root node to export
+ * @param plist_openstep a pointer to a char* buffer. This function allocates the memory,
+ * caller is responsible for freeing it.
+ * @param length a pointer to an uint32_t variable. Represents the length of the allocated buffer.
+ * @param options One or more bitwise ORed values of #plist_write_options_t.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ * @note Use plist_mem_free() to free the allocated memory.
*/
- PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
+ PLIST_API plist_err_t plist_to_openstep_with_options(plist_t plist, char **plist_openstep, uint32_t* length, plist_write_options_t options);
+
/**
* Import the #plist_t structure from XML format.
@@ -529,8 +1066,9 @@ extern "C"
* @param plist_xml a pointer to the xml buffer.
* @param length length of the buffer to read.
* @param plist a pointer to the imported plist.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
*/
- PLIST_API void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist);
+ PLIST_API plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist);
/**
* Import the #plist_t structure from binary format.
@@ -538,9 +1076,129 @@ extern "C"
* @param plist_bin a pointer to the xml buffer.
* @param length length of the buffer to read.
* @param plist a pointer to the imported plist.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ */
+ PLIST_API plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
+
+ /**
+ * Import the #plist_t structure from JSON format.
+ *
+ * @param json a pointer to the JSON buffer.
+ * @param length length of the buffer to read.
+ * @param plist a pointer to the imported plist.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ */
+ PLIST_API plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist);
+
+ /**
+ * Import the #plist_t structure from OpenStep plist format.
+ *
+ * @param openstep a pointer to the OpenStep plist buffer.
+ * @param length length of the buffer to read.
+ * @param plist a pointer to the imported plist.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ */
+ PLIST_API plist_err_t plist_from_openstep(const char *openstep, uint32_t length, plist_t * plist);
+
+ /**
+ * Import the #plist_t structure from memory data.
+ *
+ * This function will look at the first bytes of plist_data
+ * to determine if plist_data contains a binary, JSON, OpenStep, or XML plist
+ * and tries to parse the data in the appropriate format.
+ * @note This is just a convenience function and the format detection is
+ * very basic. It checks with plist_is_binary() if the data supposedly
+ * contains binary plist data, if not it checks if the first bytes have
+ * either '{' or '[' and assumes JSON format, and XML tags will result
+ * in parsing as XML, otherwise it will try to parse as OpenStep.
+ *
+ * @param plist_data A pointer to the memory buffer containing plist data.
+ * @param length Length of the buffer to read.
+ * @param plist A pointer to the imported plist.
+ * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
+ */
+ PLIST_API plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format);
+
+ /**
+ * Import the #plist_t structure directly from file.
+ *
+ * This function will look at the first bytes of the file data
+ * to determine if it contains a binary, JSON, OpenStep, or XML plist
+ * and tries to parse the data in the appropriate format.
+ * Uses plist_from_memory() internally.
+ *
+ * @param filename The name of the file to parse.
+ * @param plist A pointer to the imported plist.
+ * @param format If non-NULL, the #plist_format_t value pointed to will be set to the parsed format.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure
*/
- PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
+ PLIST_API plist_err_t plist_read_from_file(const char *filename, plist_t *plist, plist_format_t *format);
+ /**
+ * Write the #plist_t structure to a NULL-terminated string using the given format and options.
+ *
+ * @param plist The input plist structure
+ * @param output Pointer to a char* buffer. This function allocates the memory,
+ * caller is responsible for freeing it.
+ * @param length A pointer to a uint32_t value that will receive the lenght of the allocated buffer.
+ * @param format A #plist_format_t value that specifies the output format to use.
+ * @param options One or more bitwise ORed values of #plist_write_options_t.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure.
+ * @note Use plist_mem_free() to free the allocated memory.
+ * @note #PLIST_FORMAT_BINARY is not supported by this function.
+ */
+ PLIST_API plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length, plist_format_t format, plist_write_options_t options);
+
+ /**
+ * Write the #plist_t structure to a FILE* stream using the given format and options.
+ *
+ * @param plist The input plist structure
+ * @param stream A writeable FILE* stream that the data will be written to.
+ * @param format A #plist_format_t value that specifies the output format to use.
+ * @param options One or more bitwise ORed values of #plist_write_options_t.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure.
+ * @note While this function allows all formats to be written to the given stream,
+ * only the formats #PLIST_FORMAT_PRINT, #PLIST_FORMAT_LIMD, and #PLIST_FORMAT_PLUTIL
+ * (basically all output-only formats) are directly and efficiently written to the stream;
+ * the other formats are written to a memory buffer first.
+ */
+ PLIST_API plist_err_t plist_write_to_stream(plist_t plist, FILE* stream, plist_format_t format, plist_write_options_t options);
+
+ /**
+ * Write the #plist_t structure to a file at given path using the given format and options.
+ *
+ * @param plist The input plist structure
+ * @param filename The file name of the file to write to. Existing files will be overwritten.
+ * @param format A #plist_format_t value that specifies the output format to use.
+ * @param options One or more bitwise ORed values of #plist_write_options_t.
+ * @return PLIST_ERR_SUCCESS on success or a #plist_err_t on failure.
+ * @note Use plist_mem_free() to free the allocated memory.
+ */
+ PLIST_API plist_err_t plist_write_to_file(plist_t plist, const char *filename, plist_format_t format, plist_write_options_t options);
+
+ /**
+ * Print the given plist in human-readable format to standard output.
+ * This is equivalent to
+ * <code>plist_write_to_stream(plist, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_PARTIAL_DATA);</code>
+ * @param plist The #plist_t structure to print
+ * @note For #PLIST_DATA nodes, only a maximum of 24 bytes (first 16 and last 8) are written.
+ */
+ PLIST_API void plist_print(plist_t plist);
+
+ /**
+ * Test if in-memory plist data is in binary format.
+ * This function will look at the first bytes of plist_data to determine
+ * if it supposedly contains a binary plist.
+ * @note The function is not validating the whole memory buffer to check
+ * if the content is truly a plist, it is only using some heuristic on
+ * the first few bytes of plist_data.
+ *
+ * @param plist_data a pointer to the memory buffer containing plist data.
+ * @param length length of the buffer to read.
+ * @return 1 if the buffer is a binary plist, 0 otherwise.
+ */
+ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length);
/********************************************
* *
@@ -578,6 +1236,334 @@ extern "C"
*/
PLIST_API char plist_compare_node_value(plist_t node_l, plist_t node_r);
+ /** Helper macro used by PLIST_IS_* macros that will evaluate the type of a plist node. */
+ #define _PLIST_IS_TYPE(__plist, __plist_type) (__plist && (plist_get_node_type(__plist) == PLIST_##__plist_type))
+
+ /* Helper macros for the different plist types */
+ /** Evaluates to true if the given plist node is of type PLIST_BOOLEAN */
+ #define PLIST_IS_BOOLEAN(__plist) _PLIST_IS_TYPE(__plist, BOOLEAN)
+ /** Evaluates to true if the given plist node is of type PLIST_INT */
+ #define PLIST_IS_INT(__plist) _PLIST_IS_TYPE(__plist, INT)
+ /** Evaluates to true if the given plist node is of type PLIST_REAL */
+ #define PLIST_IS_REAL(__plist) _PLIST_IS_TYPE(__plist, REAL)
+ /** Evaluates to true if the given plist node is of type PLIST_STRING */
+ #define PLIST_IS_STRING(__plist) _PLIST_IS_TYPE(__plist, STRING)
+ /** Evaluates to true if the given plist node is of type PLIST_ARRAY */
+ #define PLIST_IS_ARRAY(__plist) _PLIST_IS_TYPE(__plist, ARRAY)
+ /** Evaluates to true if the given plist node is of type PLIST_DICT */
+ #define PLIST_IS_DICT(__plist) _PLIST_IS_TYPE(__plist, DICT)
+ /** Evaluates to true if the given plist node is of type PLIST_DATE */
+ #define PLIST_IS_DATE(__plist) _PLIST_IS_TYPE(__plist, DATE)
+ /** Evaluates to true if the given plist node is of type PLIST_DATA */
+ #define PLIST_IS_DATA(__plist) _PLIST_IS_TYPE(__plist, DATA)
+ /** Evaluates to true if the given plist node is of type PLIST_KEY */
+ #define PLIST_IS_KEY(__plist) _PLIST_IS_TYPE(__plist, KEY)
+ /** Evaluates to true if the given plist node is of type PLIST_UID */
+ #define PLIST_IS_UID(__plist) _PLIST_IS_TYPE(__plist, UID)
+ /* for backwards compatibility */
+ #define PLIST_IS_UINT PLIST_IS_INT
+
+ /**
+ * Helper function to check the value of a PLIST_BOOL node.
+ *
+ * @param boolnode node of type PLIST_BOOL
+ * @return 1 if the boolean node has a value of TRUE or 0 if FALSE.
+ */
+ PLIST_API int plist_bool_val_is_true(plist_t boolnode);
+
+ /**
+ * Helper function to test if a given #PLIST_INT node's value is negative
+ *
+ * @param intnode node of type PLIST_INT
+ * @return 1 if the node's value is negative, or 0 if positive.
+ */
+ PLIST_API int plist_int_val_is_negative(plist_t intnode);
+
+ /**
+ * Helper function to compare the value of a PLIST_INT node against
+ * a given signed integer value.
+ *
+ * @param uintnode node of type PLIST_INT
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are equal,
+ * 1 if the node's value is greater than cmpval,
+ * or -1 if the node's value is less than cmpval.
+ */
+ PLIST_API int plist_int_val_compare(plist_t uintnode, int64_t cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_INT node against
+ * a given unsigned integer value.
+ *
+ * @param uintnode node of type PLIST_INT
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are equal,
+ * 1 if the node's value is greater than cmpval,
+ * or -1 if the node's value is less than cmpval.
+ */
+ PLIST_API int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_UID node against
+ * a given value.
+ *
+ * @param uidnode node of type PLIST_UID
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are equal,
+ * 1 if the node's value is greater than cmpval,
+ * or -1 if the node's value is less than cmpval.
+ */
+ PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_REAL node against
+ * a given value.
+ *
+ * @note WARNING: Comparing floating point values can give inaccurate
+ * results because of the nature of floating point values on computer
+ * systems. While this function is designed to be as accurate as
+ * possible, please don't rely on it too much.
+ *
+ * @param realnode node of type PLIST_REAL
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are (almost) equal,
+ * 1 if the node's value is greater than cmpval,
+ * or -1 if the node's value is less than cmpval.
+ */
+ PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_DATE node against
+ * a given number of seconds since epoch (UNIX timestamp).
+ *
+ * @param datenode node of type PLIST_DATE
+ * @param cmpval Number of seconds to compare against (UNIX timestamp)
+ * @return 0 if the node's date is equal to the supplied values,
+ * 1 if the node's date is greater than the supplied values,
+ * or -1 if the node's date is less than the supplied values.
+ */
+ PLIST_API int plist_unix_date_val_compare(plist_t datenode, int64_t cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_STRING node against
+ * a given value.
+ * This function basically behaves like strcmp.
+ *
+ * @param strnode node of type PLIST_STRING
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_STRING node against
+ * a given value, while not comparing more than n characters.
+ * This function basically behaves like strncmp.
+ *
+ * @param strnode node of type PLIST_STRING
+ * @param cmpval value to compare against
+ * @param n maximum number of characters to compare
+ * @return 0 if the node's value and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n);
+
+ /**
+ * Helper function to match a given substring in the value of a
+ * PLIST_STRING node.
+ *
+ * @param strnode node of type PLIST_STRING
+ * @param substr value to match
+ * @return 1 if the node's value contains the given substring,
+ * or 0 if not.
+ */
+ PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr);
+
+ /**
+ * Helper function to compare the value of a PLIST_KEY node against
+ * a given value.
+ * This function basically behaves like strcmp.
+ *
+ * @param keynode node of type PLIST_KEY
+ * @param cmpval value to compare against
+ * @return 0 if the node's value and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval);
+
+ /**
+ * Helper function to compare the value of a PLIST_KEY node against
+ * a given value, while not comparing more than n characters.
+ * This function basically behaves like strncmp.
+ *
+ * @param keynode node of type PLIST_KEY
+ * @param cmpval value to compare against
+ * @param n maximum number of characters to compare
+ * @return 0 if the node's value and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n);
+
+ /**
+ * Helper function to match a given substring in the value of a
+ * PLIST_KEY node.
+ *
+ * @param keynode node of type PLIST_KEY
+ * @param substr value to match
+ * @return 1 if the node's value contains the given substring,
+ * or 0 if not.
+ */
+ PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr);
+
+ /**
+ * Helper function to compare the data of a PLIST_DATA node against
+ * a given blob and size.
+ * This function basically behaves like memcmp after making sure the
+ * size of the node's data value is equal to the size of cmpval (n),
+ * making this a "full match" comparison.
+ *
+ * @param datanode node of type PLIST_DATA
+ * @param cmpval data blob to compare against
+ * @param n size of data blob passed in cmpval
+ * @return 0 if the node's data blob and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n);
+
+ /**
+ * Helper function to compare the data of a PLIST_DATA node against
+ * a given blob and size, while no more than n bytes are compared.
+ * This function basically behaves like memcmp after making sure the
+ * size of the node's data value is at least n, making this a
+ * "starts with" comparison.
+ *
+ * @param datanode node of type PLIST_DATA
+ * @param cmpval data blob to compare against
+ * @param n size of data blob passed in cmpval
+ * @return 0 if the node's value and cmpval are equal,
+ * > 0 if the node's value is lexicographically greater than cmpval,
+ * or < 0 if the node's value is lexicographically less than cmpval.
+ */
+ PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n);
+
+ /**
+ * Helper function to match a given data blob within the value of a
+ * PLIST_DATA node.
+ *
+ * @param datanode node of type PLIST_KEY
+ * @param cmpval data blob to match
+ * @param n size of data blob passed in cmpval
+ * @return 1 if the node's value contains the given data blob
+ * or 0 if not.
+ */
+ PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n);
+
+ /**
+ * Sort all PLIST_DICT key/value pairs in a property list lexicographically
+ * by key. Recurses into the child nodes if necessary.
+ *
+ * @param plist The property list to perform the sorting operation on.
+ */
+ PLIST_API void plist_sort(plist_t plist);
+
+ /**
+ * Free memory allocated by relevant libplist API calls:
+ * - plist_to_xml()
+ * - plist_to_bin()
+ * - plist_get_key_val()
+ * - plist_get_string_val()
+ * - plist_get_data_val()
+ *
+ * @param ptr pointer to the memory to free
+ *
+ * @note Do not use this function to free plist_t nodes, use plist_free()
+ * instead.
+ */
+ PLIST_API void plist_mem_free(void* ptr);
+
+ /**
+ * Set debug level for the format parsers.
+ * @note This function does nothing if libplist was not configured with --enable-debug .
+ *
+ * @param debug Debug level. Currently, only 0 (off) and 1 (enabled) are supported.
+ */
+ PLIST_API void plist_set_debug(int debug);
+
+ /**
+ * Returns a static string of the libplist version.
+ *
+ * @return The libplist version as static ascii string
+ */
+ PLIST_API const char* libplist_version();
+
+
+ /********************************************
+ * *
+ * Deprecated API *
+ * *
+ ********************************************/
+
+ /**
+ * Create a new plist_t type #PLIST_DATE
+ *
+ * @deprecated Deprecated. Use plist_new_unix_date instead.
+ *
+ * @param sec the number of seconds since 01/01/2001
+ * @param usec the number of microseconds
+ * @return the created item
+ * @sa #plist_type
+ */
+ PLIST_WARN_DEPRECATED("use plist_new_unix_date instead")
+ PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec);
+
+ /**
+ * Get the value of a #PLIST_DATE node.
+ * This function does nothing if node is not of type #PLIST_DATE
+ *
+ * @deprecated Deprecated. Use plist_get_unix_date_val instead.
+ *
+ * @param node the node
+ * @param sec a pointer to an int32_t variable. Represents the number of seconds since 01/01/2001.
+ * @param usec a pointer to an int32_t variable. Represents the number of microseconds
+ */
+ PLIST_WARN_DEPRECATED("use plist_get_unix_date_val instead")
+ PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec);
+
+ /**
+ * Set the value of a node.
+ * Forces type of node to #PLIST_DATE
+ *
+ * @deprecated Deprecated. Use plist_set_unix_date_val instead.
+ *
+ * @param node the node
+ * @param sec the number of seconds since 01/01/2001
+ * @param usec the number of microseconds
+ */
+ PLIST_WARN_DEPRECATED("use plist_set_unix_date_val instead")
+ PLIST_API void plist_set_date_val(plist_t node, int32_t sec, int32_t usec);
+
+ /**
+ * Helper function to compare the value of a PLIST_DATE node against
+ * a given set of seconds and fraction of a second since epoch.
+ *
+ * @deprecated Deprecated. Use plist_unix_date_val_compare instead.
+ *
+ * @param datenode node of type PLIST_DATE
+ * @param cmpsec number of seconds since epoch to compare against
+ * @param cmpusec fraction of a second in microseconds to compare against
+ * @return 0 if the node's date is equal to the supplied values,
+ * 1 if the node's date is greater than the supplied values,
+ * or -1 if the node's date is less than the supplied values.
+ */
+ PLIST_WARN_DEPRECATED("use plist_unix_date_val_compare instead")
+ PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec);
+
/*@}*/
#ifdef __cplusplus
diff --git a/libcnary/CMakeLists.txt b/libcnary/CMakeLists.txt
deleted file mode 100644
index bbbe8ce..0000000
--- a/libcnary/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-cmake_minimum_required(VERSION 2.6)
-
-SET(libcnary_SRC
- iterator.c
- list.c
- node.c
- node_iterator.c
- node_list.c )
-
-INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
-
-SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
-ADD_LIBRARY(libcnary STATIC ${libcnary_SRC})
-
-SET_TARGET_PROPERTIES(libcnary PROPERTIES OUTPUT_NAME cnary)
-
diff --git a/libcnary/Makefile b/libcnary/Makefile
deleted file mode 100644
index bdd165d..0000000
--- a/libcnary/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-TARGET = cnary
-LIBRARY = libcnary.a
-OBJECTS = cnary.o libcnary.a
-LIBRARY_OBJECTS = node.o list.o iterator.o node_list.o node_iterator.o
-CFLAGS=-g -I./include -I/opt/local/include -mmacosx-version-min=10.5 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk
-LDFLAGS=-L/opt/local/lib -framework CoreFoundation -mmacosx-version-min=10.5 -arch i386 -isysroot /Developer/SDKs/MacOSX10.5.sdk -Wl,-no_compact_linkedit
-
-
-%.o: %.c
- $(CC) -o $(@) -c $(^) $(CFLAGS)
-
-$(LIBRARY): $(LIBRARY_OBJECTS)
- $(AR) rs $(@) $(^)
-
-$(TARGET): $(OBJECTS)
- $(CC) -o $(@) $(^) $(CFLAGS) $(LDFLAGS)
-
-all: $(TARGET)
-
-clean:
- rm -rf $(TARGET) $(LIBRARY) $(OBJECTS) $(LIBRARY_OBJECTS) \ No newline at end of file
diff --git a/libcnary/Makefile.am b/libcnary/Makefile.am
new file mode 100644
index 0000000..f5c7bc9
--- /dev/null
+++ b/libcnary/Makefile.am
@@ -0,0 +1,15 @@
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/libcnary/include
+
+AM_LDFLAGS =
+
+noinst_LTLIBRARIES = libcnary.la
+libcnary_la_LIBADD =
+libcnary_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined
+libcnary_la_SOURCES = \
+ node.c \
+ node_list.c \
+ include/node.h \
+ include/node_list.h \
+ include/object.h
diff --git a/libcnary/include/iterator.h b/libcnary/include/iterator.h
deleted file mode 100644
index a33c4ff..0000000
--- a/libcnary/include/iterator.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * iterator.h
- *
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef ITERATOR_H_
-#define ITERATOR_H_
-
-struct list_t;
-struct object_t;
-
-typedef struct iterator_t {
- struct object_t*(*next)(struct iterator_t* iterator);
- int(*bind)(struct iterator_t* iterator, struct list_t* list);
-
- unsigned int count;
- unsigned int position;
-
- struct list_t* list;
- struct object_t* end;
- struct object_t* begin;
- struct object_t* value;
-} iterator_t;
-
-void iterator_destroy(struct iterator_t* iterator);
-struct iterator_t* iterator_create(struct list_t* list);
-
-struct object_t* iterator_next(struct iterator_t* iterator);
-int iterator_bind(struct iterator_t* iterator, struct list_t* list);
-
-#endif /* ITERATOR_H_ */
diff --git a/libcnary/include/node.h b/libcnary/include/node.h
index f9afdd6..5b3ae37 100644
--- a/libcnary/include/node.h
+++ b/libcnary/include/node.h
@@ -24,52 +24,54 @@
#ifndef NODE_H_
#define NODE_H_
+#include "node_list.h"
#include "object.h"
#define NODE_TYPE 1;
-struct node_list_t;
+#ifndef NODE_MAX_DEPTH
+#define NODE_MAX_DEPTH 512
+#endif
+
+#define NODE_ERR_SUCCESS 0
+#define NODE_ERR_INVALID_ARG -1
+#define NODE_ERR_NO_MEM -2
+#define NODE_ERR_PARENT -3
+#define NODE_ERR_CIRCULAR_REF -4
+#define NODE_ERR_MAX_DEPTH -5
+#define NODE_ERR_NOT_FOUND -6
// This class implements the abstract iterator class
-typedef struct node_t {
+typedef struct node* node_t;
+struct node {
// Super class
- struct node_t* next;
- struct node_t* prev;
+ node_t next;
+ node_t prev;
unsigned int count;
- // Local Properties
- int isRoot;
- int isLeaf;
-
// Local Members
void *data;
- unsigned int depth;
- struct node_t* parent;
- struct node_list_t* children;
-
- // Virtual Functions
- int(*attach)(struct node_t* parent, struct node_t* child);
- int(*detach)(struct node_t* parent, struct node_t* child);
-
-} node_t;
+ node_t parent;
+ node_list_t children;
+};
-void node_destroy(struct node_t* node);
-struct node_t* node_create(struct node_t* parent, void* data);
+void node_destroy(node_t node);
+node_t node_create(node_t parent, void* data);
-int node_attach(struct node_t* parent, struct node_t* child);
-int node_detach(struct node_t* parent, struct node_t* child);
-int node_insert(struct node_t* parent, unsigned int index, struct node_t* child);
+int node_attach(node_t parent, node_t child);
+int node_detach(node_t parent, node_t child);
+int node_insert(node_t parent, unsigned int index, node_t child);
-unsigned int node_n_children(struct node_t* node);
-node_t* node_nth_child(struct node_t* node, unsigned int n);
-node_t* node_first_child(struct node_t* node);
-node_t* node_prev_sibling(struct node_t* node);
-node_t* node_next_sibling(struct node_t* node);
-int node_child_position(struct node_t* parent, node_t* child);
+unsigned int node_n_children(node_t node);
+node_t node_nth_child(node_t node, unsigned int n);
+node_t node_first_child(node_t node);
+node_t node_prev_sibling(node_t node);
+node_t node_next_sibling(node_t node);
+int node_child_position(node_t parent, node_t child);
typedef void* (*copy_func_t)(const void *src);
-node_t* node_copy_deep(node_t* node, copy_func_t copy_func);
+node_t node_copy_deep(node_t node, copy_func_t copy_func);
-void node_debug(struct node_t* node);
+void node_debug(node_t node);
#endif /* NODE_H_ */
diff --git a/libcnary/include/node_iterator.h b/libcnary/include/node_iterator.h
deleted file mode 100644
index d3bce3a..0000000
--- a/libcnary/include/node_iterator.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * node_iterator.h
- *
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef NODE_ITERATOR_H_
-#define NODE_ITERATOR_H_
-
-#include "iterator.h"
-#include "node_list.h"
-
-// This class implements the abstract iterator class
-typedef struct node_iterator_t {
- // Super class
- struct iterator_t super;
-
- // Local members
- struct node_t*(*next)(struct node_iterator_t* iterator);
- int(*bind)(struct node_iterator_t* iterator, struct node_list_t* list);
-
- unsigned int count;
- unsigned int position;
-
- struct node_list_t* list;
- struct node_t* end;
- struct node_t* begin;
- struct node_t* value;
-
-} node_iterator_t;
-
-void node_iterator_destroy(node_iterator_t* iterator);
-node_iterator_t* node_iterator_create(node_list_t* list);
-
-struct node_t* node_iterator_next(struct node_iterator_t* iterator);
-int node_iterator_bind(struct node_iterator_t* iterator, struct node_list_t* list);
-
-#endif /* NODE_ITERATOR_H_ */
diff --git a/libcnary/include/node_list.h b/libcnary/include/node_list.h
index 7b9e311..d566b00 100644
--- a/libcnary/include/node_list.h
+++ b/libcnary/include/node_list.h
@@ -24,24 +24,27 @@
#ifndef NODE_LIST_H_
#define NODE_LIST_H_
-struct node_t;
+#include "node.h"
+
+typedef struct node* node_t;
// This class implements the list_t abstract class
-typedef struct node_list_t {
+struct node_list {
// list_t members
- struct node_t* begin;
- struct node_t* end;
+ node_t begin;
+ node_t end;
// node_list_t members
unsigned int count;
-} node_list_t;
+};
+typedef struct node_list* node_list_t;
-void node_list_destroy(struct node_list_t* list);
-struct node_list_t* node_list_create(struct node_t* node);
+void node_list_destroy(node_list_t list);
+node_list_t node_list_create();
-int node_list_add(node_list_t* list, node_t* node);
-int node_list_insert(node_list_t* list, unsigned int index, node_t* node);
-int node_list_remove(node_list_t* list, node_t* node);
+int node_list_add(node_list_t list, node_t node);
+int node_list_insert(node_list_t list, unsigned int index, node_t node);
+int node_list_remove(node_list_t list, node_t node);
#endif /* NODE_LIST_H_ */
diff --git a/libcnary/node.c b/libcnary/node.c
index 0a8f414..63b449c 100644
--- a/libcnary/node.c
+++ b/libcnary/node.c
@@ -26,13 +26,13 @@
#include "node.h"
#include "node_list.h"
-#include "node_iterator.h"
-void node_destroy(node_t* node) {
+void node_destroy(node_t node)
+{
if(!node) return;
if (node->children && node->children->count > 0) {
- node_t* ch;
+ node_t ch;
while ((ch = node->children->begin)) {
node_list_remove(node->children, ch);
node_destroy(ch);
@@ -44,32 +44,28 @@ void node_destroy(node_t* node) {
free(node);
}
-node_t* node_create(node_t* parent, void* data) {
+node_t node_create(node_t parent, void* data)
+{
int error = 0;
- node_t* node = (node_t*) malloc(sizeof(node_t));
- if(node == NULL) {
+ node_t node = (node_t)calloc(1, sizeof(struct node));
+ if (node == NULL) {
return NULL;
}
- memset(node, '\0', sizeof(node_t));
node->data = data;
- node->depth = 0;
node->next = NULL;
node->prev = NULL;
node->count = 0;
- node->isLeaf = TRUE;
- node->isRoot = TRUE;
node->parent = NULL;
- node->children = node_list_create(node);
+ node->children = NULL;
// Pass NULL to create a root node
- if(parent != NULL) {
+ if (parent != NULL) {
// This is a child node so attach it to it's parent
error = node_attach(parent, node);
- if(error < 0) {
+ if (error < 0) {
// Unable to attach nodes
- printf("ERROR: %d \"Unable to attach nodes\"\n", error);
node_destroy(node);
return NULL;
}
@@ -78,88 +74,180 @@ node_t* node_create(node_t* parent, void* data) {
return node;
}
-int node_attach(node_t* parent, node_t* child) {
- if (!parent || !child) return -1;
- child->isLeaf = TRUE;
- child->isRoot = FALSE;
- child->parent = parent;
- child->depth = parent->depth + 1;
- if(parent->isLeaf == TRUE) {
- parent->isLeaf = FALSE;
+static int node_depth_from_root(node_t n)
+{
+ int d = 0;
+ while (n && n->parent) {
+ d++;
+ n = n->parent;
+ if (d > NODE_MAX_DEPTH) return d; // early out
+ }
+ return d;
+}
+
+static int node_subtree_max_depth(node_t root)
+{
+ if (!root) return 0;
+
+ typedef struct { node_t n; int depth; } frame_t;
+ size_t cap = 64, sp = 0;
+ frame_t *st = (frame_t*)malloc(cap * sizeof(*st));
+ if (!st) return NODE_MAX_DEPTH + 1;
+
+ st[sp++] = (frame_t){ root, 0 };
+ int maxd = 0;
+
+ while (sp) {
+ frame_t f = st[--sp];
+ if (f.depth > maxd) maxd = f.depth;
+ if (maxd > NODE_MAX_DEPTH) break;
+
+ if (!f.n->children) continue;
+
+ for (node_t ch = node_first_child(f.n); ch; ch = node_next_sibling(ch)) {
+ if (sp == cap) {
+ cap *= 2;
+ frame_t *tmp = (frame_t*)realloc(st, cap * sizeof(*st));
+ if (!tmp) { maxd = NODE_MAX_DEPTH + 1; goto out; }
+ st = tmp;
+ }
+ st[sp++] = (frame_t){ ch, f.depth + 1 };
+ }
+ }
+
+out:
+ free(st);
+ return maxd;
+}
+
+static int would_create_cycle(node_t parent, node_t child)
+{
+ // if parent is anywhere in child's ancestor chain => cycle
+ for (node_t p = parent; p; p = p->parent) {
+ if (p == child) return 1;
+ }
+ return 0;
+}
+
+int node_attach(node_t parent, node_t child)
+{
+ if (!parent || !child) return NODE_ERR_INVALID_ARG;
+
+ // already parented?
+ if (child->parent) return NODE_ERR_PARENT;
+
+ // self/cycle guard
+ if (parent == child) return NODE_ERR_CIRCULAR_REF;
+ if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF;
+
+ // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH
+ int pd = node_depth_from_root(parent);
+ int cd = node_subtree_max_depth(child);
+ if (pd + 1 + cd > NODE_MAX_DEPTH) {
+ return NODE_ERR_MAX_DEPTH;
+ }
+
+ if (!parent->children) {
+ parent->children = node_list_create();
+ if (!parent->children) return NODE_ERR_NO_MEM;
}
int res = node_list_add(parent->children, child);
if (res == 0) {
+ child->parent = parent;
parent->count++;
}
return res;
}
-int node_detach(node_t* parent, node_t* child) {
- if (!parent || !child) return -1;
- int index = node_list_remove(parent->children, child);
- if (index >= 0) {
- parent->count--;
+int node_detach(node_t parent, node_t child)
+{
+ if (!parent || !child) return NODE_ERR_INVALID_ARG;
+ if (!parent->children) return NODE_ERR_NOT_FOUND;
+ if (child->parent && child->parent != parent) return NODE_ERR_PARENT;
+
+ int node_index = node_list_remove(parent->children, child);
+ if (node_index >= 0) {
+ if (parent->count > 0) parent->count--;
+ child->parent = NULL;
+ child->prev = NULL;
+ child->next = NULL;
}
- return index;
+ return node_index;
}
-int node_insert(node_t* parent, unsigned int index, node_t* child)
+int node_insert(node_t parent, unsigned int node_index, node_t child)
{
- if (!parent || !child) return -1;
- child->isLeaf = TRUE;
- child->isRoot = FALSE;
- child->parent = parent;
- child->depth = parent->depth + 1;
- if(parent->isLeaf == TRUE) {
- parent->isLeaf = FALSE;
+ if (!parent || !child) return NODE_ERR_INVALID_ARG;
+
+ // already parented?
+ if (child->parent) return NODE_ERR_PARENT;
+
+ // self/cycle guard
+ if (parent == child) return NODE_ERR_CIRCULAR_REF;
+ if (would_create_cycle(parent, child)) return NODE_ERR_CIRCULAR_REF;
+
+ // depth guard: depth(parent)+1+max_depth(child_subtree) <= NODE_MAX_DEPTH
+ int pd = node_depth_from_root(parent);
+ int cd = node_subtree_max_depth(child);
+ if (pd + 1 + cd > NODE_MAX_DEPTH) {
+ return NODE_ERR_MAX_DEPTH;
}
- int res = node_list_insert(parent->children, index, child);
+
+ if (!parent->children) {
+ parent->children = node_list_create();
+ if (!parent->children) return NODE_ERR_NO_MEM;
+ }
+ int res = node_list_insert(parent->children, node_index, child);
if (res == 0) {
+ child->parent = parent;
parent->count++;
}
return res;
}
-void node_debug(node_t* node) {
- int i = 0;
- node_t* current = NULL;
- node_iterator_t* iter = NULL;
- for(i = 0; i < node->depth; i++) {
+static void _node_debug(node_t node, unsigned int depth)
+{
+ unsigned int i = 0;
+ node_t current = NULL;
+ for(i = 0; i < depth; i++) {
printf("\t");
}
- if(node->isRoot) {
+ if(!node->parent) {
printf("ROOT\n");
}
- if(node->isLeaf && !node->isRoot) {
+ if(!node->children && node->parent) {
printf("LEAF\n");
-
} else {
- if(!node->isRoot) {
+ if(node->parent) {
printf("NODE\n");
}
- iter = node_iterator_create(node->children);
- for(current = iter->begin; current != NULL; current = iter->next(iter)) {
- node_debug(current);
+ for (current = node_first_child(node); current; current = node_next_sibling(current)) {
+ _node_debug(current, depth+1);
}
}
}
-unsigned int node_n_children(struct node_t* node)
+void node_debug(node_t node)
+{
+ _node_debug(node, 0);
+}
+
+unsigned int node_n_children(node_t node)
{
if (!node) return 0;
return node->count;
}
-node_t* node_nth_child(struct node_t* node, unsigned int n)
+node_t node_nth_child(node_t node, unsigned int n)
{
if (!node || !node->children || !node->children->begin) return NULL;
- int index = 0;
+ unsigned int node_index = 0;
int found = 0;
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
- if (index++ == n) {
+ if (node_index++ == n) {
found = 1;
break;
}
@@ -170,55 +258,64 @@ node_t* node_nth_child(struct node_t* node, unsigned int n)
return ch;
}
-node_t* node_first_child(struct node_t* node)
+node_t node_first_child(node_t node)
{
if (!node || !node->children) return NULL;
return node->children->begin;
}
-node_t* node_prev_sibling(struct node_t* node)
+node_t node_prev_sibling(node_t node)
{
if (!node) return NULL;
return node->prev;
}
-node_t* node_next_sibling(struct node_t* node)
+node_t node_next_sibling(node_t node)
{
if (!node) return NULL;
return node->next;
}
-int node_child_position(struct node_t* parent, node_t* child)
+int node_child_position(node_t parent, node_t child)
{
- if (!parent || !parent->children || !parent->children->begin || !child) return -1;
- int index = 0;
+ if (!parent || !parent->children || !parent->children->begin || !child) return NODE_ERR_INVALID_ARG;
+ int node_index = 0;
int found = 0;
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(parent); ch; ch = node_next_sibling(ch)) {
if (ch == child) {
found = 1;
break;
}
- index++;
+ node_index++;
}
if (!found) {
- return -1;
+ return NODE_ERR_NOT_FOUND;
}
- return index;
+ return node_index;
}
-node_t* node_copy_deep(node_t* node, copy_func_t copy_func)
+node_t node_copy_deep(node_t node, copy_func_t copy_func)
{
if (!node) return NULL;
void *data = NULL;
if (copy_func) {
data = copy_func(node->data);
}
- node_t* copy = node_create(NULL, data);
- node_t* ch;
+ node_t copy = node_create(NULL, data);
+ if (!copy) return NULL;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
- node_t* cc = node_copy_deep(ch, copy_func);
- node_attach(copy, cc);
+ node_t cc = node_copy_deep(ch, copy_func);
+ if (!cc) {
+ node_destroy(copy);
+ return NULL;
+ }
+ if (node_attach(copy, cc) < 0) {
+ node_destroy(cc);
+ node_destroy(copy);
+ return NULL;
+ }
}
return copy;
}
diff --git a/libcnary/node_iterator.c b/libcnary/node_iterator.c
deleted file mode 100644
index dedf3b4..0000000
--- a/libcnary/node_iterator.c
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * node_iterator.c
- *
- * Created on: Mar 8, 2011
- * Author: posixninja
- *
- * Copyright (c) 2011 Joshua Hill. All Rights Reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "node.h"
-#include "node_list.h"
-#include "node_iterator.h"
-
-void node_iterator_destroy(node_iterator_t* iterator) {
- if(iterator) {
- free(iterator);
- }
-}
-
-node_iterator_t* node_iterator_create(node_list_t* list) {
- node_iterator_t* iterator = (node_iterator_t*) malloc(sizeof(node_iterator_t));
- if(iterator == NULL) {
- return NULL;
- }
- memset(iterator, '\0', sizeof(node_iterator_t));
-
- iterator->count = 0;
- iterator->position = 0;
-
- iterator->end = NULL;
- iterator->begin = NULL;
- iterator->value = list->begin;
-
- iterator->list = NULL;
- iterator->next = node_iterator_next;
- iterator->bind = node_iterator_bind;
-
-
- if(list != NULL) {
- iterator->bind(iterator, list);
- }
-
- return iterator;
-}
-
-node_t* node_iterator_next(node_iterator_t* iterator) {
- node_t* node = iterator->value;
- if (node) {
- iterator->value = node->next;
- }
- iterator->position++;
- return node;
-}
-
-int node_iterator_bind(node_iterator_t* iterator, node_list_t* list) {
- iterator->position = 0;
- iterator->end = list->end;
- iterator->count = list->count;
- iterator->begin = list->begin;
- iterator->value = list->begin;
- return 0;
-}
diff --git a/libcnary/node_list.c b/libcnary/node_list.c
index 5b291e7..218c81d 100644
--- a/libcnary/node_list.c
+++ b/libcnary/node_list.c
@@ -25,34 +25,34 @@
#include <stdlib.h>
#include <string.h>
-#include "list.h"
#include "node.h"
#include "node_list.h"
-void node_list_destroy(node_list_t* list) {
- if(list != NULL) {
- list_destroy((list_t*) list);
- }
+void node_list_destroy(node_list_t list)
+{
+ free(list);
}
-node_list_t* node_list_create(node_t* node) {
- node_list_t* list = (node_list_t*) malloc(sizeof(node_list_t));
- if(list == NULL) {
+node_list_t node_list_create()
+{
+ node_list_t list = (node_list_t)calloc(1, sizeof(struct node_list));
+ if (list == NULL) {
return NULL;
}
- memset(list, '\0', sizeof(node_list_t));
// Initialize structure
- list_init((list_t*) list);
+ list->begin = NULL;
+ list->end = NULL;
list->count = 0;
return list;
}
-int node_list_add(node_list_t* list, node_t* node) {
- if (!list || !node) return -1;
+int node_list_add(node_list_t list, node_t node)
+{
+ if (!list || !node) return NODE_ERR_INVALID_ARG;
// Find the last element in the list
- node_t* last = list->end;
+ node_t last = list->end;
// Setup our new node as the new last element
node->next = NULL;
@@ -62,6 +62,9 @@ int node_list_add(node_list_t* list, node_t* node) {
if (last) {
// but only if the node list is not empty
last->next = node;
+ } else {
+ // otherwise this is the start of the list
+ list->begin = node;
}
// Set the lists prev to the new last element
@@ -69,86 +72,80 @@ int node_list_add(node_list_t* list, node_t* node) {
// Increment our node count for this list
list->count++;
- return 0;
+ return NODE_ERR_SUCCESS;
}
-int node_list_insert(node_list_t* list, unsigned int index, node_t* node) {
- if (!list || !node) return -1;
- if (index >= list->count) {
+int node_list_insert(node_list_t list, unsigned int node_index, node_t node)
+{
+ if (!list || !node) return NODE_ERR_INVALID_ARG;
+ if (node_index > list->count) return NODE_ERR_INVALID_ARG;
+ if (node_index == list->count) {
return node_list_add(list, node);
}
// Get the first element in the list
- node_t* cur = list->begin;
-
- unsigned int pos = 0;
- node_t* prev = NULL;
+ node_t cur = list->begin;
+ node_t prev = NULL;
- if (index > 0) {
- while (pos < index) {
- prev = cur;
- cur = cur->next;
- pos++;
- }
+ for (unsigned int pos = 0; pos < node_index; pos++) {
+ if (!cur) return NODE_ERR_INVALID_ARG;
+ prev = cur;
+ cur = cur->next;
}
+ // insert node before cur
+ node->prev = prev;
+ node->next = cur;
+
if (prev) {
- // Set previous node
- node->prev = prev;
- // Set next node of our new node to next node of the previous node
- node->next = prev->next;
- // Set next node of previous node to our new node
prev->next = node;
} else {
- node->prev = NULL;
- // get old first element in list
- node->next = list->begin;
- // set new node as first element in list
list->begin = node;
}
- if (node->next == NULL) {
- // Set the lists prev to the new last element
- list->end = node;
+ if (cur) {
+ cur->prev = node;
} else {
- // set prev of the new next element to our node
- node->next->prev = node;
+ // should not happen with bounds above, but keeps things consistent
+ list->end = node;
}
// Increment our node count for this list
list->count++;
- return 0;
+ return NODE_ERR_SUCCESS;
}
-int node_list_remove(node_list_t* list, node_t* node) {
- if (!list || !node) return -1;
- if (list->count == 0) return -1;
-
- int index = 0;
- node_t* n;
- for (n = list->begin; n; n = n->next) {
- if (node == n) {
- node_t* newnode = node->next;
- if (node->prev) {
- node->prev->next = newnode;
- if (newnode) {
- newnode->prev = node->prev;
- } else {
- // last element in the list
- list->end = node->prev;
- }
- } else {
- // we just removed the first element
- if (newnode) {
- newnode->prev = NULL;
- }
- list->begin = newnode;
- }
- list->count--;
- return index;
+// Returns removed index (>=0) on success, or NODE_ERR_* (<0) on failure.
+int node_list_remove(node_list_t list, node_t node)
+{
+ if (!list || !node) return NODE_ERR_INVALID_ARG;
+ if (list->count == 0) return NODE_ERR_NOT_FOUND;
+
+ int node_index = 0;
+ for (node_t n = list->begin; n; n = n->next, node_index++) {
+ if (node != n) continue;
+
+ node_t newnode = node->next;
+ if (node->prev) {
+ node->prev->next = newnode;
+ } else {
+ // we just removed the first element
+ list->begin = newnode;
+ }
+
+ if (newnode) {
+ newnode->prev = node->prev;
+ } else {
+ // we removed the last element, set new end
+ list->end = node->prev;
}
- index++;
- }
- return -1;
-}
+ // fully detach node from list
+ node->prev = NULL;
+ node->next = NULL;
+
+ list->count--;
+ return node_index;
+ }
+ return NODE_ERR_NOT_FOUND;
+}
diff --git a/libplist++.pc.in b/libplist++.pc.in
deleted file mode 100644
index 89e40d7..0000000
--- a/libplist++.pc.in
+++ /dev/null
@@ -1,12 +0,0 @@
-prefix=${CMAKE_INSTALL_PREFIX}
-exec_prefix=${CMAKE_INSTALL_PREFIX}
-libdir=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}
-includedir=${CMAKE_INSTALL_PREFIX}/include
-
-Name: libplist++
-Description: C++ binding of libplist
-Version: ${LIBPLIST_VERSION}
-Requires: libplist >= ${LIBPLIST_VERSION}
-Libs: -L${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -lplist++
-Cflags: -I${CMAKE_INSTALL_PREFIX}/include
-
diff --git a/libplist.pc.in b/libplist.pc.in
deleted file mode 100644
index 971b8b5..0000000
--- a/libplist.pc.in
+++ /dev/null
@@ -1,12 +0,0 @@
-prefix=${CMAKE_INSTALL_PREFIX}
-exec_prefix=${CMAKE_INSTALL_PREFIX}
-libdir=${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}
-includedir=${CMAKE_INSTALL_PREFIX}/include
-
-Name: libplist
-Description: A library to handle Apple Property Lists whereas they are binary or XML
-Version: ${LIBPLIST_VERSION}
-Requires.private: libxml-2.0 >= 2.6.30
-Libs: -L${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} -lplist
-Cflags: -I${CMAKE_INSTALL_PREFIX}/include
-
diff --git a/m4/ac_pkg_cython.m4 b/m4/ac_pkg_cython.m4
new file mode 100644
index 0000000..e0af96a
--- /dev/null
+++ b/m4/ac_pkg_cython.m4
@@ -0,0 +1,67 @@
+
+AC_DEFUN([AC_PROG_CYTHON],[
+ AC_PATH_PROGS([CYTHON],[cython cython3])
+ if test -z "$CYTHON" ; then
+ AC_MSG_WARN([Unable to find 'cython' or 'cython3' program. You should look at https://cython.org or install your distribution specific cython package.])
+ CYTHON=false
+ elif test -n "$1" ; then
+ AC_MSG_CHECKING([for Cython version])
+ [cython_version=`$CYTHON --version 2>&1 | sed 's/Cython version \(.*\)$/\1/g'`]
+ AC_MSG_RESULT([$cython_version])
+
+ # Setup extra version string for parsing
+ [cython_version_stripped=`echo $cython_version | sed 's/\([0-9]\+\)\.\([0-9]\+\)[^\.]*\(\.\([0-9]\+\)\)\?.*/0\1.0\2.0\4/g'`]
+ if test -n "$cython_version" ; then
+ # Calculate the required version number components
+ [required=$1]
+ [required_major=`echo $required | sed 's/[^0-9].*//'`]
+ if test -z "$required_major" ; then
+ [required_major=0]
+ fi
+ [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+ [required_minor=`echo $required | sed 's/[^0-9].*//'`]
+ if test -z "$required_minor" ; then
+ [required_minor=0]
+ fi
+ [required=`echo $required | sed 's/[0-9]*[^0-9]//'`]
+ [required_patch=`echo $required | sed 's/[^0-9].*//'`]
+ if test -z "$required_patch" ; then
+ [required_patch=0]
+ fi
+
+ # Calculate the available version number components
+ [available=$cython_version_stripped]
+ [available_major=`echo $available | sed 's/[^0-9].*//'`]
+ if test -z "$available_major" ; then
+ [available_major=0]
+ fi
+ [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+ [available_minor=`echo $available | sed 's/[^0-9].*//'`]
+ if test -z "$available_minor" ; then
+ [available_minor=0]
+ fi
+ [available=`echo $available | sed 's/[0-9]*[^0-9]//'`]
+ [available_patch=`echo $available | sed 's/[^0-9].*//'`]
+ if test -z "$available_patch" ; then
+ [available_patch=0]
+ fi
+
+ if test $available_major -gt $required_major || \
+ ( test $available_major -eq $required_major && \
+ test $available_minor -gt $required_minor ) || \
+ ( test $available_major -eq $required_major && \
+ test $available_minor -eq $required_minor && \
+ test $available_patch -ge $required_patch ) ; then
+
+ AC_MSG_NOTICE([Cython executable is '$CYTHON'])
+ else
+ AC_MSG_WARN([Cython version >= $1 is required. You have $cython_version. You should look at http://www.cython.org])
+ CYTHON='echo "Error: Cython version >= $1 is required. You have '"$cython_version"'. You should look at http://www.cython.org" ; false'
+ fi
+ else
+ AC_MSG_WARN([Unable to determine Cython version])
+ CYTHON=false
+ fi
+ fi
+ AC_SUBST([CYTHON_LIB])
+])
diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4
new file mode 100644
index 0000000..baab5d9
--- /dev/null
+++ b/m4/as-compiler-flag.m4
@@ -0,0 +1,62 @@
+dnl as-compiler-flag.m4 0.1.0
+
+dnl autostars m4 macro for detection of compiler flags
+
+dnl David Schleef <ds@schleef.org>
+
+dnl $Id: as-compiler-flag.m4,v 1.1 2005/12/15 23:35:19 ds Exp $
+
+dnl AS_COMPILER_FLAG(CFLAGS, ACTION-IF-ACCEPTED, [ACTION-IF-NOT-ACCEPTED])
+dnl Tries to compile with the given CFLAGS.
+dnl Runs ACTION-IF-ACCEPTED if the compiler can compile with the flags,
+dnl and ACTION-IF-NOT-ACCEPTED otherwise.
+
+AC_DEFUN([AS_COMPILER_FLAG],
+[
+ AC_MSG_CHECKING([to see if compiler understands $1])
+
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $1"
+
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ m4_ifvaln([$2],[$2])
+ true
+ else
+ m4_ifvaln([$3],[$3])
+ true
+ fi
+ AC_MSG_RESULT([$flag_ok])
+])
+
+dnl AS_COMPILER_FLAGS(VAR, FLAGS)
+dnl Tries to compile with the given CFLAGS.
+
+AC_DEFUN([AS_COMPILER_FLAGS],
+[
+ list=$2
+ flags_supported=""
+ flags_unsupported=""
+ AC_MSG_CHECKING([for supported compiler flags])
+ for each in $list
+ do
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $each"
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
+ CFLAGS="$save_CFLAGS"
+
+ if test "X$flag_ok" = Xyes ; then
+ flags_supported="$flags_supported $each"
+ else
+ flags_unsupported="$flags_unsupported $each"
+ fi
+ done
+ AC_MSG_RESULT([$flags_supported])
+ if test "X$flags_unsupported" != X ; then
+ AC_MSG_WARN([unsupported compiler flags: $flags_unsupported])
+ fi
+ $1="$$1 $flags_supported"
+])
+
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 0000000..9f35d13
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,522 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC and PTHREAD_CXX to any special C compiler that is
+# needed for multi-threaded programs (defaults to the value of CC
+# respectively CXX otherwise). (This is necessary on e.g. AIX to use the
+# special cc_r/CC_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also to link with them as well. For example, you might link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+# $PTHREAD_CXX $CXXFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threaded programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+# CXX="$PTHREAD_CXX"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
+# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 31
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_SED])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on Tru64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
+ ax_pthread_save_CC="$CC"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
+ AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"])
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
+ AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = "xno"; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ CC="$ax_pthread_save_CC"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items with a "," contain both
+# C compiler flags (before ",") and linker flags (after ","). Other items
+# starting with a "-" are C compiler flags, and remaining items are
+# library names, except for "none" which indicates that we try without
+# any flags at all, and "pthread-config" which is a program returning
+# the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
+# (Note: HP C rejects this with "bad form for `-t' option")
+# -pthreads: Solaris/gcc (Note: HP C also rejects)
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads and
+# -D_REENTRANT too), HP C (must be checked before -lpthread, which
+# is present but should not be used directly; and before -mthreads,
+# because the compiler interprets this as "-mt" + "-hreads")
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case $host_os in
+
+ freebsd*)
+
+ # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+ # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+
+ ax_pthread_flags="-kthread lthread $ax_pthread_flags"
+ ;;
+
+ hpux*)
+
+ # From the cc(1) man page: "[-mt] Sets various -D flags to enable
+ # multi-threading and also sets -lpthread."
+
+ ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
+ ;;
+
+ openedition*)
+
+ # IBM z/OS requires a feature-test macro to be defined in order to
+ # enable POSIX threads at all, so give the user a hint if this is
+ # not set. (We don't define these ourselves, as they can affect
+ # other portions of the system API in unpredictable ways.)
+
+ AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
+ [
+# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
+ AX_PTHREAD_ZOS_MISSING
+# endif
+ ],
+ [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
+ ;;
+
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (N.B.: The stubs are missing
+ # pthread_cleanup_push, or rather a function called by this macro,
+ # so we could check for that, but who knows whether they'll stub
+ # that too in a future libc.) So we'll check first for the
+ # standard Solaris way of linking pthreads (-mt -lpthread).
+
+ ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags"
+ ;;
+esac
+
+# Are we compiling with Clang?
+
+AC_CACHE_CHECK([whether $CC is Clang],
+ [ax_cv_PTHREAD_CLANG],
+ [ax_cv_PTHREAD_CLANG=no
+ # Note that Autoconf sets GCC=yes for Clang as well as GCC
+ if test "x$GCC" = "xyes"; then
+ AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
+ [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
+# if defined(__clang__) && defined(__llvm__)
+ AX_PTHREAD_CC_IS_CLANG
+# endif
+ ],
+ [ax_cv_PTHREAD_CLANG=yes])
+ fi
+ ])
+ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
+
+
+# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
+
+# Note that for GCC and Clang -pthread generally implies -lpthread,
+# except when -nostdlib is passed.
+# This is problematic using libtool to build C++ shared libraries with pthread:
+# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460
+# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333
+# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555
+# To solve this, first try -pthread together with -lpthread for GCC
+
+AS_IF([test "x$GCC" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"])
+
+# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first
+
+AS_IF([test "x$ax_pthread_clang" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread"])
+
+
+# The presence of a feature test macro requesting re-entrant function
+# definitions is, on some systems, a strong hint that pthreads support is
+# correctly enabled
+
+case $host_os in
+ darwin* | hpux* | linux* | osf* | solaris*)
+ ax_pthread_check_macro="_REENTRANT"
+ ;;
+
+ aix*)
+ ax_pthread_check_macro="_THREAD_SAFE"
+ ;;
+
+ *)
+ ax_pthread_check_macro="--"
+ ;;
+esac
+AS_IF([test "x$ax_pthread_check_macro" = "x--"],
+ [ax_pthread_check_cond=0],
+ [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
+
+
+if test "x$ax_pthread_ok" = "xno"; then
+for ax_pthread_try_flag in $ax_pthread_flags; do
+
+ case $ax_pthread_try_flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ *,*)
+ PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"`
+ PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"`
+ AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
+ PTHREAD_CFLAGS="$ax_pthread_try_flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
+ PTHREAD_LIBS="-l$ax_pthread_try_flag"
+ ;;
+ esac
+
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+# if $ax_pthread_check_cond
+# error "$ax_pthread_check_macro must be defined"
+# endif
+ static void *some_global = NULL;
+ static void routine(void *a)
+ {
+ /* To avoid any unused-parameter or
+ unused-but-set-parameter warning. */
+ some_global = a;
+ }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+
+# Clang needs special handling, because older versions handle the -pthread
+# option in a rather... idiosyncratic way
+
+if test "x$ax_pthread_clang" = "xyes"; then
+
+ # Clang takes -pthread; it has never supported any other flag
+
+ # (Note 1: This will need to be revisited if a system that Clang
+ # supports has POSIX threads in a separate library. This tends not
+ # to be the way of modern systems, but it's conceivable.)
+
+ # (Note 2: On some systems, notably Darwin, -pthread is not needed
+ # to get POSIX threads support; the API is always present and
+ # active. We could reasonably leave PTHREAD_CFLAGS empty. But
+ # -pthread does define _REENTRANT, and while the Darwin headers
+ # ignore this macro, third-party headers might not.)
+
+ # However, older versions of Clang make a point of warning the user
+ # that, in an invocation where only linking and no compilation is
+ # taking place, the -pthread option has no effect ("argument unused
+ # during compilation"). They expect -pthread to be passed in only
+ # when source code is being compiled.
+ #
+ # Problem is, this is at odds with the way Automake and most other
+ # C build frameworks function, which is that the same flags used in
+ # compilation (CFLAGS) are also used in linking. Many systems
+ # supported by AX_PTHREAD require exactly this for POSIX threads
+ # support, and in fact it is often not straightforward to specify a
+ # flag that is used only in the compilation phase and not in
+ # linking. Such a scenario is extremely rare in practice.
+ #
+ # Even though use of the -pthread flag in linking would only print
+ # a warning, this can be a nuisance for well-run software projects
+ # that build with -Werror. So if the active version of Clang has
+ # this misfeature, we search for an option to squash it.
+
+ AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
+ # Create an alternate version of $ac_link that compiles and
+ # links in two steps (.c -> .o, .o -> exe) instead of one
+ # (.c -> exe), because the warning occurs only in the second
+ # step
+ ax_pthread_save_ac_link="$ac_link"
+ ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
+ ax_pthread_link_step=`AS_ECHO(["$ac_link"]) | sed "$ax_pthread_sed"`
+ ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
+ AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
+ CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
+ ac_link="$ax_pthread_save_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [ac_link="$ax_pthread_2step_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [break])
+ ])
+ done
+ ac_link="$ax_pthread_save_ac_link"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
+ ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
+ ])
+
+ case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
+ no | unknown) ;;
+ *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
+ esac
+
+fi # $ax_pthread_clang = yes
+
+
+
+# Various other checks:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_CACHE_CHECK([for joinable pthread attribute],
+ [ax_cv_PTHREAD_JOINABLE_ATTR],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=unknown
+ for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $ax_pthread_attr; return attr /* ; */])],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
+ [])
+ done
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
+ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
+ test "x$ax_pthread_joinable_attr_defined" != "xyes"],
+ [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
+ [$ax_cv_PTHREAD_JOINABLE_ATTR],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ ax_pthread_joinable_attr_defined=yes
+ ])
+
+ AC_CACHE_CHECK([whether more special flags are required for pthreads],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS=no
+ case $host_os in
+ solaris*)
+ ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
+ ;;
+ esac
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
+ test "x$ax_pthread_special_flags_added" != "xyes"],
+ [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
+ ax_pthread_special_flags_added=yes])
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
+ test "x$ax_pthread_prio_inherit_defined" != "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
+ ax_pthread_prio_inherit_defined=yes
+ ])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != "xyes"; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [
+ AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])
+ AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])])
+ ],
+ [
+ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])
+ AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])])
+ ]
+ )
+ ])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+AC_SUBST([PTHREAD_CXX])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4
new file mode 100644
index 0000000..935056c
--- /dev/null
+++ b/m4/ax_python_devel.m4
@@ -0,0 +1,468 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PYTHON_DEVEL([version[,optional]])
+#
+# DESCRIPTION
+#
+# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it
+# in your configure.ac.
+#
+# This macro checks for Python and tries to get the include path to
+# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output
+# variables. It also exports $(PYTHON_EXTRA_LIBS) and
+# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code.
+#
+# You can search for some particular version of Python by passing a
+# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please
+# note that you *have* to pass also an operator along with the version to
+# match, and pay special attention to the single quotes surrounding the
+# version number. Don't use "PYTHON_VERSION" for this: that environment
+# variable is declared as precious and thus reserved for the end-user.
+#
+# By default this will fail if it does not detect a development version of
+# python. If you want it to continue, set optional to true, like
+# AX_PYTHON_DEVEL([], [true]). The ax_python_devel_found variable will be
+# "no" if it fails.
+#
+# This macro should work for all versions of Python >= 2.1.0. As an end
+# user, you can disable the check for the python version by setting the
+# PYTHON_NOVERSIONCHECK environment variable to something else than the
+# empty string.
+#
+# If you need to use this macro for an older Python version, please
+# contact the authors. We're always open for feedback.
+#
+# LICENSE
+#
+# Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de>
+# Copyright (c) 2009 Alan W. Irwin
+# Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net>
+# Copyright (c) 2009 Andrew Collier
+# Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org>
+# Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org>
+# Copyright (c) 2013 Daniel Mullner <muellner@math.stanford.edu>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 37
+
+AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL])
+AC_DEFUN([AX_PYTHON_DEVEL],[
+ # Get whether it's optional
+ if test -z "$2"; then
+ ax_python_devel_optional=false
+ else
+ ax_python_devel_optional=$2
+ fi
+ ax_python_devel_found=yes
+
+ #
+ # Allow the use of a (user set) custom python version
+ #
+ AC_ARG_VAR([PYTHON_VERSION],[The installed Python
+ version to use, for example '2.3'. This string
+ will be appended to the Python interpreter
+ canonical name.])
+
+ AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]])
+ if test -z "$PYTHON"; then
+ AC_MSG_WARN([Cannot find python$PYTHON_VERSION in your system path])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up, python development not available])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
+
+ if test $ax_python_devel_found = yes; then
+ #
+ # Check for a version of Python >= 2.1.0
+ #
+ AC_MSG_CHECKING([for a version of Python >= '2.1.0'])
+ ac_supports_python_ver=`$PYTHON -c "import sys; \
+ ver = sys.version.split ()[[0]]; \
+ print (ver >= '2.1.0')"`
+ if test "$ac_supports_python_ver" != "True"; then
+ if test -z "$PYTHON_NOVERSIONCHECK"; then
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([
+This version of the AC@&t@_PYTHON_DEVEL macro
+doesn't work properly with versions of Python before
+2.1.0. You may need to re-run configure, setting the
+variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG,
+PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand.
+Moreover, to disable this check, set PYTHON_NOVERSIONCHECK
+to something else than an empty string.
+])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_FAILURE([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ else
+ AC_MSG_RESULT([skip at user request])
+ fi
+ else
+ AC_MSG_RESULT([yes])
+ fi
+ fi
+
+ if test $ax_python_devel_found = yes; then
+ #
+ # If the macro parameter ``version'' is set, honour it.
+ # A Python shim class, VPy, is used to implement correct version comparisons via
+ # string expressions, since e.g. a naive textual ">= 2.7.3" won't work for
+ # Python 2.7.10 (the ".1" being evaluated as less than ".3").
+ #
+ if test -n "$1"; then
+ AC_MSG_CHECKING([for a version of Python $1])
+ cat << EOF > ax_python_devel_vpy.py
+class VPy:
+ def vtup(self, s):
+ return tuple(map(int, s.strip().replace("rc", ".").split(".")))
+ def __init__(self):
+ import sys
+ self.vpy = tuple(sys.version_info)[[:3]]
+ def __eq__(self, s):
+ return self.vpy == self.vtup(s)
+ def __ne__(self, s):
+ return self.vpy != self.vtup(s)
+ def __lt__(self, s):
+ return self.vpy < self.vtup(s)
+ def __gt__(self, s):
+ return self.vpy > self.vtup(s)
+ def __le__(self, s):
+ return self.vpy <= self.vtup(s)
+ def __ge__(self, s):
+ return self.vpy >= self.vtup(s)
+EOF
+ ac_supports_python_ver=`$PYTHON -c "import ax_python_devel_vpy; \
+ ver = ax_python_devel_vpy.VPy(); \
+ print (ver $1)"`
+ rm -rf ax_python_devel_vpy*.py* __pycache__/ax_python_devel_vpy*.py*
+ if test "$ac_supports_python_ver" = "True"; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ AC_MSG_WARN([this package requires Python $1.
+If you have it installed, but it isn't the default Python
+interpreter in your system path, please pass the PYTHON_VERSION
+variable to configure. See ``configure --help'' for reference.
+])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
+ fi
+ fi
+
+ if test $ax_python_devel_found = yes; then
+ #
+ # Check if you have distutils, else fail
+ #
+ AC_MSG_CHECKING([for the sysconfig Python package])
+ ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`
+ if test $? -eq 0; then
+ AC_MSG_RESULT([yes])
+ IMPORT_SYSCONFIG="import sysconfig"
+ else
+ AC_MSG_RESULT([no])
+
+ AC_MSG_CHECKING([for the distutils Python package])
+ ac_sysconfig_result=`$PYTHON -c "from distutils import sysconfig" 2>&1`
+ if test $? -eq 0; then
+ AC_MSG_RESULT([yes])
+ IMPORT_SYSCONFIG="from distutils import sysconfig"
+ else
+ AC_MSG_WARN([cannot import Python module "distutils".
+Please check your Python installation. The error was:
+$ac_sysconfig_result])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
+ fi
+ fi
+
+ if test $ax_python_devel_found = yes; then
+ #
+ # Check for Python include path
+ #
+ AC_MSG_CHECKING([for Python include path])
+ if test -z "$PYTHON_CPPFLAGS"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ # sysconfig module has different functions
+ python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_path ('include'));"`
+ plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_path ('platinclude'));"`
+ else
+ # old distutils way
+ python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_inc ());"`
+ plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_inc (plat_specific=1));"`
+ fi
+ if test -n "${python_path}"; then
+ if test "${plat_python_path}" != "${python_path}"; then
+ python_path="-I$python_path -I$plat_python_path"
+ else
+ python_path="-I$python_path"
+ fi
+ fi
+ PYTHON_CPPFLAGS=$python_path
+ fi
+ AC_MSG_RESULT([$PYTHON_CPPFLAGS])
+ AC_SUBST([PYTHON_CPPFLAGS])
+
+ #
+ # Check for Python library path
+ #
+ AC_MSG_CHECKING([for Python library path])
+ if test -z "$PYTHON_LIBS"; then
+ # (makes two attempts to ensure we've got a version number
+ # from the interpreter)
+ ac_python_version=`cat<<EOD | $PYTHON -
+
+# join all versioning strings, on some systems
+# major/minor numbers could be in different list elements
+from sysconfig import *
+e = get_config_var('VERSION')
+if e is not None:
+ print(e)
+EOD`
+
+ if test -z "$ac_python_version"; then
+ if test -n "$PYTHON_VERSION"; then
+ ac_python_version=$PYTHON_VERSION
+ else
+ ac_python_version=`$PYTHON -c "import sys; \
+ print ("%d.%d" % sys.version_info[[:2]])"`
+ fi
+ fi
+
+ # Make the versioning information available to the compiler
+ AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"],
+ [If available, contains the Python version number currently in use.])
+
+ # First, the library directory:
+ ac_python_libdir=`cat<<EOD | $PYTHON -
+
+# There should be only one
+$IMPORT_SYSCONFIG
+e = sysconfig.get_config_var('LIBDIR')
+if e is not None:
+ print (e)
+EOD`
+
+ # Now, for the library:
+ ac_python_library=`cat<<EOD | $PYTHON -
+
+$IMPORT_SYSCONFIG
+c = sysconfig.get_config_vars()
+if 'LDVERSION' in c:
+ print ('python'+c[['LDVERSION']])
+else:
+ print ('python'+c[['VERSION']])
+EOD`
+
+ # This small piece shamelessly adapted from PostgreSQL python macro;
+ # credits goes to momjian, I think. I'd like to put the right name
+ # in the credits, if someone can point me in the right direction... ?
+ #
+ if test -n "$ac_python_libdir" -a -n "$ac_python_library"
+ then
+ # use the official shared library
+ ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"`
+ PYTHON_LIBS="-L$ac_python_libdir -l$ac_python_library"
+ else
+ # old way: use libpython from python_configdir
+ ac_python_libdir=`$PYTHON -c \
+ "from sysconfig import get_python_lib as f; \
+ import os; \
+ print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"`
+ PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version"
+ fi
+
+ if test -z "$PYTHON_LIBS"; then
+ AC_MSG_WARN([
+ Cannot determine location of your Python DSO. Please check it was installed with
+ dynamic libraries enabled, or try setting PYTHON_LIBS by hand.
+ ])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
+ fi
+ fi
+
+ if test $ax_python_devel_found = yes; then
+ AC_MSG_RESULT([$PYTHON_LIBS])
+ AC_SUBST([PYTHON_LIBS])
+
+ #
+ # Check for site packages
+ #
+ AC_MSG_CHECKING([for Python site-packages path])
+ if test -z "$PYTHON_SITE_PKG"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ PYTHON_SITE_PKG=`$PYTHON -c "
+$IMPORT_SYSCONFIG;
+if hasattr(sysconfig, 'get_default_scheme'):
+ scheme = sysconfig.get_default_scheme()
+else:
+ scheme = sysconfig._get_default_scheme()
+if scheme == 'posix_local':
+ # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/
+ scheme = 'posix_prefix'
+prefix = '$prefix'
+if prefix == 'NONE':
+ prefix = '$ac_default_prefix'
+sitedir = sysconfig.get_path('purelib', scheme, vars={'base': prefix})
+print(sitedir)"`
+ else
+ # distutils.sysconfig way
+ PYTHON_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_lib(0,0));"`
+ fi
+ fi
+ AC_MSG_RESULT([$PYTHON_SITE_PKG])
+ AC_SUBST([PYTHON_SITE_PKG])
+
+ #
+ # Check for platform-specific site packages
+ #
+ AC_MSG_CHECKING([for Python platform specific site-packages path])
+ if test -z "$PYTHON_PLATFORM_SITE_PKG"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "
+$IMPORT_SYSCONFIG;
+if hasattr(sysconfig, 'get_default_scheme'):
+ scheme = sysconfig.get_default_scheme()
+else:
+ scheme = sysconfig._get_default_scheme()
+if scheme == 'posix_local':
+ # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/
+ scheme = 'posix_prefix'
+prefix = '$prefix'
+if prefix == 'NONE':
+ prefix = '$ac_default_prefix'
+sitedir = sysconfig.get_path('platlib', scheme, vars={'platbase': prefix})
+print(sitedir)"`
+ else
+ # distutils.sysconfig way
+ PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_lib(1,0));"`
+ fi
+ fi
+ AC_MSG_RESULT([$PYTHON_PLATFORM_SITE_PKG])
+ AC_SUBST([PYTHON_PLATFORM_SITE_PKG])
+
+ #
+ # libraries which must be linked in when embedding
+ #
+ AC_MSG_CHECKING(python extra libraries)
+ if test -z "$PYTHON_EXTRA_LIBS"; then
+ PYTHON_EXTRA_LIBS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ conf = sysconfig.get_config_var; \
+ print (conf('LIBS') + ' ' + conf('SYSLIBS'))"`
+ fi
+ AC_MSG_RESULT([$PYTHON_EXTRA_LIBS])
+ AC_SUBST(PYTHON_EXTRA_LIBS)
+
+ #
+ # linking flags needed when embedding
+ #
+ AC_MSG_CHECKING(python extra linking flags)
+ if test -z "$PYTHON_EXTRA_LDFLAGS"; then
+ PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ conf = sysconfig.get_config_var; \
+ print (conf('LINKFORSHARED'))"`
+ # Hack for macos, it sticks this in here.
+ PYTHON_EXTRA_LDFLAGS=`echo $PYTHON_EXTRA_LDFLAGS | sed 's/CoreFoundation.*$/CoreFoundation/'`
+ fi
+ AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])
+ AC_SUBST(PYTHON_EXTRA_LDFLAGS)
+
+ #
+ # final check to see if everything compiles alright
+ #
+ AC_MSG_CHECKING([consistency of all components of python development environment])
+ # save current global flags
+ ac_save_LIBS="$LIBS"
+ ac_save_LDFLAGS="$LDFLAGS"
+ ac_save_CPPFLAGS="$CPPFLAGS"
+ LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS"
+ LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS"
+ CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
+ AC_LANG_PUSH([C])
+ AC_LINK_IFELSE([
+ AC_LANG_PROGRAM([[#include <Python.h>]],
+ [[Py_Initialize();]])
+ ],[pythonexists=yes],[pythonexists=no])
+ AC_LANG_POP([C])
+ # turn back to default flags
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIBS="$ac_save_LIBS"
+ LDFLAGS="$ac_save_LDFLAGS"
+
+ AC_MSG_RESULT([$pythonexists])
+
+ if test ! "x$pythonexists" = "xyes"; then
+ AC_MSG_WARN([
+ Could not link test program to Python. Maybe the main Python library has been
+ installed in some non-standard library path. If so, pass it to configure,
+ via the LIBS environment variable.
+ Example: ./configure LIBS="-L/usr/non-standard-path/python/lib"
+ ============================================================================
+ ERROR!
+ You probably have to install the development version of the Python package
+ for your distribution. The exact name of this package varies among them.
+ ============================================================================
+ ])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
+ fi
+
+ #
+ # all done!
+ #
+])
diff --git a/m4/cython_python.m4 b/m4/cython_python.m4
new file mode 100644
index 0000000..5b4a041
--- /dev/null
+++ b/m4/cython_python.m4
@@ -0,0 +1,7 @@
+AC_DEFUN([CYTHON_PYTHON],[
+ AC_REQUIRE([AC_PROG_CYTHON])
+ AC_REQUIRE([AX_PYTHON_DEVEL])
+ test "x$1" != "xno" || cython_shadow=" -noproxy"
+ AC_SUBST([CYTHON_PYTHON_OPT],[-python$cython_shadow])
+ AC_SUBST([CYTHON_PYTHON_CPPFLAGS],[$PYTHON_CPPFLAGS])
+])
diff --git a/plutil/CMakeLists.txt b/plutil/CMakeLists.txt
deleted file mode 100644
index 149d68f..0000000
--- a/plutil/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-
-SET(plutil_SRC
- plutil.c)
-
-ADD_EXECUTABLE(plutil ${plutil_SRC})
-TARGET_LINK_LIBRARIES(plutil plist)
-SET_TARGET_PROPERTIES( plutil PROPERTIES VERSION ${PLUTIL_VERSION} )
-
-INSTALL( TARGETS plutil RUNTIME DESTINATION bin COMPONENT plutil ) \ No newline at end of file
diff --git a/plutil/plutil.c b/plutil/plutil.c
deleted file mode 100644
index 88df080..0000000
--- a/plutil/plutil.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * plutil.C
- * source for plist convertion tool
- *
- * Copyright (c) 2008 Zach C. All Rights Reserved.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-
-#include "plist/plist.h"
-#include "plutil.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#ifdef _MSC_VER
-#pragma warning(disable:4996)
-#endif
-
-
-int main(int argc, char *argv[])
-{
- FILE *iplist = NULL;
- plist_t root_node = NULL;
- char *plist_out = NULL;
- uint32_t size = 0;
- int read_size = 0;
- char *plist_entire = NULL;
- struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
- Options *options = parse_arguments(argc, argv);
-
- if (!options)
- {
- print_usage();
- free(filestats);
- return 0;
- }
- //read input file
- iplist = fopen(options->in_file, "rb");
- if (!iplist)
- return 1;
- stat(options->in_file, filestats);
- plist_entire = (char *) malloc(sizeof(char) * (filestats->st_size + 1));
- read_size = fread(plist_entire, sizeof(char), filestats->st_size, iplist);
- fclose(iplist);
-
-
- //convert one format to another
-
-
- if (memcmp(plist_entire, "bplist00", 8) == 0)
- {
- plist_from_bin(plist_entire, read_size, &root_node);
- plist_to_xml(root_node, &plist_out, &size);
- }
- else
- {
- plist_from_xml(plist_entire, read_size, &root_node);
- plist_to_bin(root_node, &plist_out, &size);
- }
- plist_free(root_node);
- free(plist_entire);
- free(filestats);
-
- if (plist_out)
- {
- if (options->out_file != NULL)
- {
- FILE *oplist = fopen(options->out_file, "wb");
- if (!oplist)
- return 1;
- fwrite(plist_out, size, sizeof(char), oplist);
- fclose(oplist);
- }
- //if no output file specified, write to stdout
- else
- fwrite(plist_out, size, sizeof(char), stdout);
-
- free(plist_out);
- }
- else
- printf("ERROR\n");
-
- free(options);
- return 0;
-}
-
-Options *parse_arguments(int argc, char *argv[])
-{
- int i = 0;
-
- Options *options = (Options *) malloc(sizeof(Options));
- memset(options, 0, sizeof(Options));
-
- for (i = 1; i < argc; i++)
- {
- if (!strcmp(argv[i], "--infile") || !strcmp(argv[i], "-i"))
- {
- if ((i + 1) == argc)
- {
- free(options);
- return NULL;
- }
- options->in_file = argv[i + 1];
- i++;
- continue;
- }
-
- if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o"))
- {
- if ((i + 1) == argc)
- {
- free(options);
- return NULL;
- }
- options->out_file = argv[i + 1];
- i++;
- continue;
- }
-
- if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d") || !strcmp(argv[i], "-v"))
- {
- options->debug = 1;
- }
-
- if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
- {
- free(options);
- return NULL;
- }
- }
-
- if (!options->in_file /*|| !options->out_file */ )
- {
- free(options);
- return NULL;
- }
-
- return options;
-}
-
-void print_usage()
-{
- printf("Usage: plistutil -i|--infile in_file.plist -o|--outfile out_file.plist [--debug]\n");
- printf("\n");
- printf("\t-i or --infile: The file to read in.\n");
- printf("\t-o or --outfile: The file to convert to.\n");
- printf("\t-d, -v or --debug: Provide extended debug information.\n\n");
-}
diff --git a/src/Array.cpp b/src/Array.cpp
index 419249f..784df7c 100644
--- a/src/Array.cpp
+++ b/src/Array.cpp
@@ -18,10 +18,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
-#include <plist/Array.h>
-
+#include <cstdlib>
#include <algorithm>
+#include <climits>
+#include "plist.h"
+#include <plist/Array.h>
namespace PList
{
@@ -31,71 +32,131 @@ Array::Array(Node* parent) : Structure(PLIST_ARRAY, parent)
_array.clear();
}
+static void array_fill(Array *_this, std::vector<Node*> &array, plist_t node)
+{
+ plist_array_iter iter = NULL;
+ plist_array_new_iter(node, &iter);
+ plist_t subnode;
+ do {
+ subnode = NULL;
+ plist_array_next_item(node, iter, &subnode);
+ if (subnode) {
+ array.push_back( Node::FromPlist(subnode, _this) );
+ }
+ } while (subnode);
+ free(iter);
+}
+
Array::Array(plist_t node, Node* parent) : Structure(parent)
{
_node = node;
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
}
-Array::Array(PList::Array& a) : Structure()
+Array::Array(const PList::Array& a) : Structure(a.GetParent())
{
_array.clear();
_node = plist_copy(a.GetPlist());
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
}
-Array& Array::operator=(PList::Array& a)
+Array& Array::operator=(const PList::Array& a)
{
+ if (this == &a) return *this;
+
plist_free(_node);
- for (unsigned int it = 0; it < _array.size(); it++)
- {
+ for (size_t it = 0; it < _array.size(); it++) {
delete _array.at(it);
}
_array.clear();
-
_node = plist_copy(a.GetPlist());
- uint32_t size = plist_array_get_size(_node);
-
- for (uint32_t i = 0; i < size; i++)
- {
- plist_t subnode = plist_array_get_item(_node, i);
- _array.push_back( Node::FromPlist(subnode, this) );
- }
+ array_fill(this, _array, _node);
return *this;
}
Array::~Array()
{
- for (unsigned int it = 0; it < _array.size(); it++)
- {
+ for (size_t it = 0; it < _array.size(); it++) {
delete (_array.at(it));
}
_array.clear();
}
-Node* Array::Clone()
+Node* Array::Clone() const
{
return new Array(*this);
}
-Node* Array::operator[](unsigned int index)
+Node* Array::operator[](unsigned int array_index)
+{
+ return _array.at(array_index);
+}
+
+Node* Array::Back()
+{
+ return _array.back();
+}
+
+Node* Array::back()
+{
+ return _array.back();
+}
+
+Node* Array::Front()
+{
+ return _array.front();
+}
+
+Node* Array::front()
+{
+ return _array.front();
+}
+
+Array::iterator Array::Begin()
{
- return _array.at(index);
+ return _array.begin();
}
-void Array::Append(Node* node)
+Array::iterator Array::begin()
+{
+ return _array.begin();
+}
+
+Array::iterator Array::End()
+{
+ return _array.end();
+}
+
+Array::iterator Array::end()
+{
+ return _array.end();
+}
+
+Array::const_iterator Array::Begin() const
+{
+ return _array.begin();
+}
+
+Array::const_iterator Array::begin() const
+{
+ return _array.begin();
+}
+
+Array::const_iterator Array::End() const
+{
+ return _array.end();
+}
+
+Array::const_iterator Array::end() const
+{
+ return _array.end();
+}
+
+size_t Array::size() const {
+ return _array.size();
+}
+
+void Array::Append(const Node* node)
{
if (node)
{
@@ -106,7 +167,12 @@ void Array::Append(Node* node)
}
}
-void Array::Insert(Node* node, unsigned int pos)
+void Array::Append(const Node& node)
+{
+ Append(&node);
+}
+
+void Array::Insert(const Node* node, unsigned int pos)
{
if (node)
{
@@ -119,16 +185,24 @@ void Array::Insert(Node* node, unsigned int pos)
}
}
+void Array::Insert(const Node &node, unsigned int pos)
+{
+ Insert(&node, pos);
+}
+
void Array::Remove(Node* node)
{
if (node)
{
uint32_t pos = plist_array_get_item_index(node->GetPlist());
+ if (pos == UINT_MAX) {
+ return;
+ }
plist_array_remove_item(_node, pos);
std::vector<Node*>::iterator it = _array.begin();
it += pos;
_array.erase(it);
- delete node;
+ free(node);
}
}
@@ -141,10 +215,15 @@ void Array::Remove(unsigned int pos)
_array.erase(it);
}
-unsigned int Array::GetNodeIndex(Node* node)
+unsigned int Array::GetNodeIndex(const Node* node) const
{
- std::vector<Node*>::iterator it = std::find(_array.begin(), _array.end(), node);
+ std::vector<Node*>::const_iterator it = std::find(_array.begin(), _array.end(), node);
return std::distance (_array.begin(), it);
}
-};
+unsigned int Array::GetNodeIndex(const Node& node) const
+{
+ return GetNodeIndex(&node);
+}
+
+} // namespace PList
diff --git a/src/Boolean.cpp b/src/Boolean.cpp
index e58472f..2a5e303 100644
--- a/src/Boolean.cpp
+++ b/src/Boolean.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Boolean.h>
namespace PList
@@ -32,13 +33,15 @@ Boolean::Boolean(plist_t node, Node* parent) : Node(node, parent)
{
}
-Boolean::Boolean(PList::Boolean& b) : Node(PLIST_BOOLEAN)
+Boolean::Boolean(const PList::Boolean& b) : Node(PLIST_BOOLEAN)
{
plist_set_bool_val(_node, b.GetValue());
}
-Boolean& Boolean::operator=(PList::Boolean& b)
+Boolean& Boolean::operator=(const PList::Boolean& b)
{
+ if (this == &b) return *this;
+
plist_free(_node);
_node = plist_copy(b.GetPlist());
return *this;
@@ -53,7 +56,7 @@ Boolean::~Boolean()
{
}
-Node* Boolean::Clone()
+Node* Boolean::Clone() const
{
return new Boolean(*this);
}
@@ -63,11 +66,11 @@ void Boolean::SetValue(bool b)
plist_set_bool_val(_node, b);
}
-bool Boolean::GetValue()
+bool Boolean::GetValue() const
{
uint8_t b = 0;
plist_get_bool_val(_node, &b);
return b != 0 ;
}
-};
+} // namespace PList
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index 0de4c8c..0000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,57 +0,0 @@
-
-INCLUDE_DIRECTORIES( ${LIBXML2_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/libcnary/include )
-
-
-SET(libplist_SRC
- plist.c
- hashtable.c
- bytearray.c
- ptrarray.c
- bplist.c
- base64.c
- xplist.c )
-
-SET(libplist++_SRC
- Node.cpp
- Boolean.cpp
- Integer.cpp
- Real.cpp
- String.cpp
- Date.cpp
- Data.cpp
- Structure.cpp
- Array.cpp
- Dictionary.cpp
- )
-
-SET(libcnary_object_files
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/iterator.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/list.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node_iterator.c${CMAKE_C_OUTPUT_EXTENSION}
- ${CMAKE_BINARY_DIR}/libcnary/CMakeFiles/libcnary.dir/node_list.c${CMAKE_C_OUTPUT_EXTENSION}
-)
-
-SET_SOURCE_FILES_PROPERTIES(${libcnary_object_files} PROPERTIES EXTERNAL_OBJECT true GENERATED true)
-
-SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPLIST_BYTE_ORDER=${PLIST_BYTE_ORDER}")
-ADD_LIBRARY( plist SHARED ${libplist_SRC} ${libcnary_object_files} )
-TARGET_LINK_LIBRARIES( plist ${LIBXML2_LIBRARIES} )
-SET_TARGET_PROPERTIES( plist PROPERTIES VERSION ${LIBPLIST_LIBVERSION} )
-SET_TARGET_PROPERTIES( plist PROPERTIES SOVERSION ${LIBPLIST_SOVERSION} )
-if(APPLE)
- SET_TARGET_PROPERTIES( plist PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-endif()
-
-ADD_LIBRARY( plist++ SHARED ${libplist++_SRC} )
-TARGET_LINK_LIBRARIES( plist++ plist )
-SET_TARGET_PROPERTIES( plist++ PROPERTIES VERSION ${LIBPLIST_LIBVERSION} )
-SET_TARGET_PROPERTIES( plist++ PROPERTIES SOVERSION ${LIBPLIST_SOVERSION} )
-if(APPLE)
- SET_TARGET_PROPERTIES( plist++ PROPERTIES INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-endif()
-
-INSTALL(TARGETS plist plist++
- RUNTIME DESTINATION bin COMPONENT lib
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT dev)
diff --git a/src/Data.cpp b/src/Data.cpp
index df5c1c7..94767c9 100644
--- a/src/Data.cpp
+++ b/src/Data.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Data.h>
namespace PList
@@ -32,14 +32,16 @@ Data::Data(plist_t node, Node* parent) : Node(node, parent)
{
}
-Data::Data(PList::Data& d) : Node(PLIST_DATA)
+Data::Data(const PList::Data& d) : Node(PLIST_DATA)
{
std::vector<char> b = d.GetValue();
plist_set_data_val(_node, &b[0], b.size());
}
-Data& Data::operator=(PList::Data& b)
+Data& Data::operator=(const PList::Data& b)
{
+ if (this == &b) return *this;
+
plist_free(_node);
_node = plist_copy(b.GetPlist());
return *this;
@@ -50,11 +52,16 @@ Data::Data(const std::vector<char>& buff) : Node(PLIST_DATA)
plist_set_data_val(_node, &buff[0], buff.size());
}
+Data::Data(const char* buff, uint64_t size) : Node(PLIST_DATA)
+{
+ plist_set_data_val(_node, buff, size);
+}
+
Data::~Data()
{
}
-Node* Data::Clone()
+Node* Data::Clone() const
{
return new Data(*this);
}
@@ -64,16 +71,12 @@ void Data::SetValue(const std::vector<char>& buff)
plist_set_data_val(_node, &buff[0], buff.size());
}
-std::vector<char> Data::GetValue()
+std::vector<char> Data::GetValue() const
{
- char* buff = NULL;
uint64_t length = 0;
- plist_get_data_val(_node, &buff, &length);
+ const char* buff = plist_get_data_ptr(_node, &length);
std::vector<char> ret(buff, buff + length);
- free(buff);
return ret;
}
-
-
-};
+} // namespace PList
diff --git a/src/Date.cpp b/src/Date.cpp
index 2430184..214220b 100644
--- a/src/Date.cpp
+++ b/src/Date.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Date.h>
namespace PList
@@ -32,45 +32,45 @@ Date::Date(plist_t node, Node* parent) : Node(node, parent)
{
}
-Date::Date(PList::Date& d) : Node(PLIST_DATE)
+Date::Date(const PList::Date& d) : Node(PLIST_DATE)
{
- timeval t = d.GetValue();
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ int64_t t = d.GetValue();
+ plist_set_unix_date_val(_node, t);
}
-Date& Date::operator=(PList::Date& d)
+Date& Date::operator=(const PList::Date& d)
{
+ if (this == &d) return *this;
+
plist_free(_node);
_node = plist_copy(d.GetPlist());
return *this;
}
-Date::Date(timeval t) : Node(PLIST_DATE)
+Date::Date(int64_t t) : Node(PLIST_DATE)
{
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ plist_set_unix_date_val(_node, t);
}
Date::~Date()
{
}
-Node* Date::Clone()
+Node* Date::Clone() const
{
return new Date(*this);
}
-void Date::SetValue(timeval t)
+void Date::SetValue(int64_t t)
{
- plist_set_date_val(_node, t.tv_sec, t.tv_usec);
+ plist_set_unix_date_val(_node, t);
}
-timeval Date::GetValue()
+int64_t Date::GetValue() const
{
- int32_t tv_sec = 0;
- int32_t tv_usec = 0;
- plist_get_date_val(_node, &tv_sec, &tv_usec);
- timeval t = {tv_sec, tv_usec};
- return t;
+ int64_t sec = 0;
+ plist_get_unix_date_val(_node, &sec);
+ return sec;
}
-};
+} // namespace PList
diff --git a/src/Dictionary.cpp b/src/Dictionary.cpp
index 0030df6..b354945 100644
--- a/src/Dictionary.cpp
+++ b/src/Dictionary.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Dictionary.h>
namespace PList
@@ -28,28 +29,29 @@ Dictionary::Dictionary(Node* parent) : Structure(PLIST_DICT, parent)
{
}
-Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
+static void dictionary_fill(Dictionary *_this, std::map<std::string,Node*> &map, plist_t node)
{
- _node = node;
plist_dict_iter it = NULL;
-
- char* key = NULL;
+ plist_dict_new_iter(node, &it);
plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
+ do {
+ char *key = NULL;
subnode = NULL;
+ plist_dict_next_item(node, it, &key, &subnode);
+ if (key && subnode)
+ map[std::string(key)] = Node::FromPlist(subnode, _this);
free(key);
- key = NULL;
- plist_dict_next_item(_node, it, &key, &subnode);
- }
+ } while (subnode);
free(it);
}
-Dictionary::Dictionary(PList::Dictionary& d) : Structure()
+Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
+{
+ _node = node;
+ dictionary_fill(this, _map, _node);
+}
+
+Dictionary::Dictionary(const PList::Dictionary& d) : Structure(d.GetParent())
{
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
@@ -57,52 +59,22 @@ Dictionary::Dictionary(PList::Dictionary& d) : Structure()
delete it->second;
}
_map.clear();
-
_node = plist_copy(d.GetPlist());
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
- subnode = NULL;
- free(key);
- key = NULL;
- plist_dict_next_item(_node, it, &key, &subnode);
- }
- free(it);
+ dictionary_fill(this, _map, _node);
}
-Dictionary& Dictionary::operator=(PList::Dictionary& d)
+Dictionary& Dictionary::operator=(const PList::Dictionary& d)
{
+ if (this == &d) return *this;
+
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
plist_free(it->second->GetPlist());
delete it->second;
}
_map.clear();
-
_node = plist_copy(d.GetPlist());
- plist_dict_iter it = NULL;
-
- char* key = NULL;
- plist_t subnode = NULL;
- plist_dict_new_iter(_node, &it);
- plist_dict_next_item(_node, it, &key, &subnode);
- while (subnode)
- {
- _map[std::string(key)] = Node::FromPlist(subnode, this);
-
- subnode = NULL;
- free(key);
- key = NULL;
- plist_dict_next_item(_node, it, NULL, &subnode);
- }
- free(it);
+ dictionary_fill(this, _map, _node);
return *this;
}
@@ -115,7 +87,7 @@ Dictionary::~Dictionary()
_map.clear();
}
-Node* Dictionary::Clone()
+Node* Dictionary::Clone() const
{
return new Dictionary(*this);
}
@@ -130,28 +102,72 @@ Dictionary::iterator Dictionary::Begin()
return _map.begin();
}
+Dictionary::iterator Dictionary::begin()
+{
+ return _map.begin();
+}
+
Dictionary::iterator Dictionary::End()
{
return _map.end();
}
+Dictionary::iterator Dictionary::end()
+{
+ return _map.end();
+}
+
+Dictionary::const_iterator Dictionary::Begin() const
+{
+ return _map.begin();
+}
+
+Dictionary::const_iterator Dictionary::begin() const
+{
+ return _map.begin();
+}
+
+Dictionary::const_iterator Dictionary::End() const
+{
+ return _map.end();
+}
+
+Dictionary::const_iterator Dictionary::end() const
+{
+ return _map.end();
+}
+
+size_t Dictionary::size() const {
+ return _map.size();
+}
+
Dictionary::iterator Dictionary::Find(const std::string& key)
{
return _map.find(key);
}
-Dictionary::iterator Dictionary::Insert(const std::string& key, Node* node)
+Dictionary::const_iterator Dictionary::Find(const std::string& key) const
+{
+ return _map.find(key);
+}
+
+Dictionary::iterator Dictionary::Set(const std::string& key, const Node* node)
{
if (node)
{
Node* clone = node->Clone();
UpdateNodeParent(clone);
- plist_dict_insert_item(_node, key.c_str(), clone->GetPlist());
+ plist_dict_set_item(_node, key.c_str(), clone->GetPlist());
delete _map[key];
_map[key] = clone;
return _map.find(key);
}
- return iterator(NULL);
+ return iterator(this->_map.end());
+}
+
+Dictionary::iterator Dictionary::Set(const std::string& key, const Node& node)
+{
+ return Set(key, &node);
}
void Dictionary::Remove(Node* node)
@@ -164,7 +180,7 @@ void Dictionary::Remove(Node* node)
std::string skey = key;
free(key);
_map.erase(skey);
- delete node;
+ free(node);
}
}
@@ -185,4 +201,4 @@ std::string Dictionary::GetNodeKey(Node* node)
return "";
}
-};
+} // namespace PList
diff --git a/src/Integer.cpp b/src/Integer.cpp
index fed03f6..26e7ccf 100644
--- a/src/Integer.cpp
+++ b/src/Integer.cpp
@@ -18,13 +18,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Integer.h>
namespace PList
{
-Integer::Integer(Node* parent) : Node(PLIST_UINT, parent)
+Integer::Integer(Node* parent) : Node(PLIST_INT, parent)
{
}
@@ -32,42 +33,72 @@ Integer::Integer(plist_t node, Node* parent) : Node(node, parent)
{
}
-Integer::Integer(PList::Integer& i) : Node(PLIST_UINT)
+Integer::Integer(const PList::Integer& i) : Node(PLIST_INT)
{
- plist_set_uint_val(_node, i.GetValue());
+ plist_free(_node);
+ _node = plist_copy(i.GetPlist());
}
-Integer& Integer::operator=(PList::Integer& i)
+Integer& Integer::operator=(const PList::Integer& i)
{
+ if (this == &i) return *this;
+
plist_free(_node);
_node = plist_copy(i.GetPlist());
return *this;
}
-Integer::Integer(uint64_t i) : Node(PLIST_UINT)
+Integer::Integer(uint64_t i) : Node(PLIST_INT)
{
plist_set_uint_val(_node, i);
}
+Integer::Integer(int64_t i) : Node(PLIST_INT)
+{
+ plist_set_int_val(_node, i);
+}
+
Integer::~Integer()
{
}
-Node* Integer::Clone()
+Node* Integer::Clone() const
{
return new Integer(*this);
}
+void Integer::SetValue(int64_t i)
+{
+ plist_set_int_val(_node, i);
+}
+
void Integer::SetValue(uint64_t i)
{
plist_set_uint_val(_node, i);
}
-uint64_t Integer::GetValue()
+void Integer::SetUnsignedValue(uint64_t i)
+{
+ plist_set_uint_val(_node, i);
+}
+
+int64_t Integer::GetValue() const
+{
+ int64_t i = 0;
+ plist_get_int_val(_node, &i);
+ return i;
+}
+
+uint64_t Integer::GetUnsignedValue() const
{
uint64_t i = 0;
plist_get_uint_val(_node, &i);
return i;
}
-};
+bool Integer::isNegative() const
+{
+ return plist_int_val_is_negative(_node);
+}
+
+} // namespace PList
diff --git a/src/Key.cpp b/src/Key.cpp
new file mode 100644
index 0000000..459227a
--- /dev/null
+++ b/src/Key.cpp
@@ -0,0 +1,78 @@
+/*
+ * Key.cpp
+ *
+ * Copyright (c) 2012 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cstdlib>
+#include "plist.h"
+#include <plist/Key.h>
+
+namespace PList
+{
+
+Key::Key(Node* parent) : Node(PLIST_KEY, parent)
+{
+}
+
+Key::Key(plist_t node, Node* parent) : Node(node, parent)
+{
+}
+
+Key::Key(const PList::Key& k) : Node(PLIST_INT)
+{
+ plist_set_key_val(_node, k.GetValue().c_str());
+}
+
+Key& Key::operator=(const PList::Key& k)
+{
+ if (this == &k) return *this;
+
+ plist_free(_node);
+ _node = plist_copy(k.GetPlist());
+ return *this;
+}
+
+Key::Key(const std::string& s) : Node(PLIST_STRING)
+{
+ plist_set_key_val(_node, s.c_str());
+}
+
+Key::~Key()
+{
+}
+
+Node* Key::Clone() const
+{
+ return new Key(*this);
+}
+
+void Key::SetValue(const std::string& s)
+{
+ plist_set_key_val(_node, s.c_str());
+}
+
+std::string Key::GetValue() const
+{
+ char* s = NULL;
+ plist_get_key_val(_node, &s);
+ std::string ret = s ? s : "";
+ free(s);
+ return ret;
+}
+
+} // namespace PList
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..ce4f931
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,71 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/libcnary/include
+
+AM_CFLAGS = $(GLOBAL_CFLAGS)
+AM_CXXFLAGS = $(GLOBAL_CXXFLAGS)
+AM_LDFLAGS = $(GLOBAL_LDFLAGS)
+
+lib_LTLIBRARIES = \
+ libplist-2.0.la \
+ libplist++-2.0.la
+
+libplist_2_0_la_LIBADD = $(top_builddir)/libcnary/libcnary.la
+libplist_2_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
+libplist_2_0_la_SOURCES = \
+ base64.c base64.h \
+ bytearray.c bytearray.h \
+ strbuf.h \
+ hashtable.c hashtable.h \
+ ptrarray.c ptrarray.h \
+ time64.c time64.h \
+ time64_limits.h \
+ common.c common.h \
+ xplist.c \
+ bplist.c \
+ jsmn.c jsmn.h \
+ jplist.c \
+ oplist.c \
+ out-default.c \
+ out-plutil.c \
+ out-limd.c \
+ plist.c plist.h
+
+libplist___2_0_la_LIBADD = libplist-2.0.la
+libplist___2_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
+libplist___2_0_la_SOURCES = \
+ Node.cpp \
+ Structure.cpp \
+ Array.cpp \
+ Boolean.cpp \
+ Data.cpp \
+ Date.cpp \
+ Dictionary.cpp \
+ Integer.cpp \
+ Key.cpp \
+ Real.cpp \
+ String.cpp \
+ Uid.cpp \
+ $(top_srcdir)/include/plist/Node.h \
+ $(top_srcdir)/include/plist/Structure.h \
+ $(top_srcdir)/include/plist/Array.h \
+ $(top_srcdir)/include/plist/Boolean.h \
+ $(top_srcdir)/include/plist/Data.h \
+ $(top_srcdir)/include/plist/Date.h \
+ $(top_srcdir)/include/plist/Dictionary.h \
+ $(top_srcdir)/include/plist/Integer.h \
+ $(top_srcdir)/include/plist/Key.h \
+ $(top_srcdir)/include/plist/Real.h \
+ $(top_srcdir)/include/plist/String.h \
+ $(top_srcdir)/include/plist/Uid.h
+
+if WIN32
+libplist_2_0_la_LDFLAGS += -avoid-version -static-libgcc
+libplist___2_0_la_LDFLAGS += -avoid-version -static-libgcc
+endif
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = \
+ libplist-2.0.pc \
+ libplist++-2.0.pc
diff --git a/src/Node.cpp b/src/Node.cpp
index 9bf50ee..1043b31 100644
--- a/src/Node.cpp
+++ b/src/Node.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Node.h>
#include <plist/Structure.h>
#include <plist/Dictionary.h>
@@ -27,6 +28,8 @@
#include <plist/Integer.h>
#include <plist/Real.h>
#include <plist/String.h>
+#include <plist/Key.h>
+#include <plist/Uid.h>
#include <plist/Data.h>
#include <plist/Date.h>
@@ -50,7 +53,7 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_BOOLEAN:
_node = plist_new_bool(0);
break;
- case PLIST_UINT:
+ case PLIST_INT:
_node = plist_new_uint(0);
break;
case PLIST_REAL:
@@ -59,11 +62,18 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_STRING:
_node = plist_new_string("");
break;
+ case PLIST_KEY:
+ _node = plist_new_string("");
+ plist_set_key_val(_node, "");
+ break;
+ case PLIST_UID:
+ _node = plist_new_uid(0);
+ break;
case PLIST_DATA:
_node = plist_new_data(NULL,0);
break;
case PLIST_DATE:
- _node = plist_new_date(0,0);
+ _node = plist_new_unix_date(0);
break;
case PLIST_ARRAY:
_node = plist_new_array();
@@ -71,7 +81,6 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
case PLIST_DICT:
_node = plist_new_dict();
break;
- case PLIST_KEY:
case PLIST_NONE:
default:
break;
@@ -80,12 +89,17 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
Node::~Node()
{
- plist_free(_node);
+ /* If the Node is in a container, let _node be cleaned up by
+ * operations on the parent plist_t. Otherwise, duplicate frees
+ * occur when a Node is removed from or replaced in a Dictionary.
+ */
+ if (_parent == NULL)
+ plist_free(_node);
_node = NULL;
_parent = NULL;
}
-plist_type Node::GetType()
+plist_type Node::GetType() const
{
if (_node)
{
@@ -94,12 +108,12 @@ plist_type Node::GetType()
return PLIST_NONE;
}
-plist_t Node::GetPlist()
+plist_t Node::GetPlist() const
{
return _node;
}
-Node* Node::GetParent()
+Node* Node::GetParent() const
{
return _parent;
}
@@ -121,7 +135,7 @@ Node* Node::FromPlist(plist_t node, Node* parent)
case PLIST_BOOLEAN:
ret = new Boolean(node, parent);
break;
- case PLIST_UINT:
+ case PLIST_INT:
ret = new Integer(node, parent);
break;
case PLIST_REAL:
@@ -130,6 +144,12 @@ Node* Node::FromPlist(plist_t node, Node* parent)
case PLIST_STRING:
ret = new String(node, parent);
break;
+ case PLIST_KEY:
+ ret = new Key(node, parent);
+ break;
+ case PLIST_UID:
+ ret = new Uid(node, parent);
+ break;
case PLIST_DATE:
ret = new Date(node, parent);
break;
@@ -144,4 +164,4 @@ Node* Node::FromPlist(plist_t node, Node* parent)
return ret;
}
-};
+} // namespace PList
diff --git a/src/Real.cpp b/src/Real.cpp
index 768d07c..b743ab5 100644
--- a/src/Real.cpp
+++ b/src/Real.cpp
@@ -18,7 +18,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
#include <plist/Real.h>
namespace PList
@@ -32,13 +32,15 @@ Real::Real(plist_t node, Node* parent) : Node(node, parent)
{
}
-Real::Real(PList::Real& d) : Node(PLIST_UINT)
+Real::Real(const PList::Real& d) : Node(PLIST_INT)
{
plist_set_real_val(_node, d.GetValue());
}
-Real& Real::operator=(PList::Real& d)
+Real& Real::operator=(const PList::Real& d)
{
+ if (this == &d) return *this;
+
plist_free(_node);
_node = plist_copy(d.GetPlist());
return *this;
@@ -53,7 +55,7 @@ Real::~Real()
{
}
-Node* Real::Clone()
+Node* Real::Clone() const
{
return new Real(*this);
}
@@ -63,11 +65,11 @@ void Real::SetValue(double d)
plist_set_real_val(_node, d);
}
-double Real::GetValue()
+double Real::GetValue() const
{
double d = 0.;
plist_get_real_val(_node, &d);
return d;
}
-};
+} // namespace PList
diff --git a/src/String.cpp b/src/String.cpp
index bf65605..bcd5e2e 100644
--- a/src/String.cpp
+++ b/src/String.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/String.h>
namespace PList
@@ -32,28 +33,49 @@ String::String(plist_t node, Node* parent) : Node(node, parent)
{
}
-String::String(PList::String& s) : Node(PLIST_UINT)
+String::String(const PList::String& s) : Node(PLIST_INT)
{
plist_set_string_val(_node, s.GetValue().c_str());
}
-String& String::operator=(PList::String& s)
+String& String::operator=(const PList::String& s)
{
+ if (this == &s) return *this;
+
plist_free(_node);
_node = plist_copy(s.GetPlist());
return *this;
}
+String& String::operator=(const std::string& s)
+{
+ plist_free(_node);
+ _node = plist_new_string(s.c_str());
+ return *this;
+}
+
+String& String::operator=(const char* s)
+{
+ plist_free(_node);
+ _node = plist_new_string(s);
+ return *this;
+}
+
String::String(const std::string& s) : Node(PLIST_STRING)
{
plist_set_string_val(_node, s.c_str());
}
+String::String(const char *s) : Node(PLIST_STRING)
+{
+ plist_set_string_val(_node, s);
+}
+
String::~String()
{
}
-Node* String::Clone()
+Node* String::Clone() const
{
return new String(*this);
}
@@ -63,13 +85,11 @@ void String::SetValue(const std::string& s)
plist_set_string_val(_node, s.c_str());
}
-std::string String::GetValue()
+std::string String::GetValue() const
{
- char* s = NULL;
- plist_get_string_val(_node, &s);
- std::string ret = s;
- free(s);
+ const char* s = plist_get_string_ptr(_node, NULL);
+ std::string ret = s ? s : "";
return ret;
}
-};
+} // namespace PList
diff --git a/src/Structure.cpp b/src/Structure.cpp
index 18b19ef..65e5ca8 100644
--- a/src/Structure.cpp
+++ b/src/Structure.cpp
@@ -18,7 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include <stdlib.h>
+#include <cstdlib>
+#include "plist.h"
#include <plist/Structure.h>
namespace PList
@@ -35,7 +36,7 @@ Structure::~Structure()
{
}
-uint32_t Structure::GetSize()
+uint32_t Structure::GetSize() const
{
uint32_t size = 0;
plist_type type = plist_get_node_type(_node);
@@ -50,7 +51,7 @@ uint32_t Structure::GetSize()
return size;
}
-std::string Structure::ToXml()
+std::string Structure::ToXml() const
{
char* xml = NULL;
uint32_t length = 0;
@@ -60,7 +61,7 @@ std::string Structure::ToXml()
return ret;
}
-std::vector<char> Structure::ToBin()
+std::vector<char> Structure::ToBin() const
{
char* bin = NULL;
uint32_t length = 0;
@@ -76,7 +77,7 @@ void Structure::UpdateNodeParent(Node* node)
if ( NULL != node->_parent )
{
plist_type type = plist_get_node_type(node->_parent);
- if (PLIST_ARRAY ==type || PLIST_DICT == type )
+ if (PLIST_ARRAY == type || PLIST_DICT == type)
{
Structure* s = static_cast<Structure*>(node->_parent);
s->Remove(node);
@@ -116,8 +117,27 @@ Structure* Structure::FromBin(const std::vector<char>& bin)
plist_from_bin(&bin[0], bin.size(), &root);
return ImportStruct(root);
+}
+
+Structure* Structure::FromBin(const char* bin, uint64_t size)
+{
+ plist_t root = NULL;
+ plist_from_bin(bin, size, &root);
+
+ return ImportStruct(root);
+}
+Structure* Structure::FromMemory(const std::vector<char>& buf, plist_format_t *format)
+{
+ return Structure::FromMemory(&buf[0], buf.size(), format);
+}
+
+Structure* Structure::FromMemory(const char* buf, uint64_t size, plist_format_t *format)
+{
+ plist_t root = NULL;
+ plist_from_memory(buf, size, &root, format);
+ return ImportStruct(root);
}
-};
+} // namespace PList
diff --git a/src/Uid.cpp b/src/Uid.cpp
new file mode 100644
index 0000000..d73f777
--- /dev/null
+++ b/src/Uid.cpp
@@ -0,0 +1,76 @@
+/*
+ * Uid.cpp
+ *
+ * Copyright (c) 2012 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cstdlib>
+#include "plist.h"
+#include <plist/Uid.h>
+
+namespace PList
+{
+
+Uid::Uid(Node* parent) : Node(PLIST_UID, parent)
+{
+}
+
+Uid::Uid(plist_t node, Node* parent) : Node(node, parent)
+{
+}
+
+Uid::Uid(const PList::Uid& i) : Node(PLIST_UID)
+{
+ plist_set_uid_val(_node, i.GetValue());
+}
+
+Uid& Uid::operator=(const PList::Uid& i)
+{
+ if (this == &i) return *this;
+
+ plist_free(_node);
+ _node = plist_copy(i.GetPlist());
+ return *this;
+}
+
+Uid::Uid(uint64_t i) : Node(PLIST_UID)
+{
+ plist_set_uid_val(_node, i);
+}
+
+Uid::~Uid()
+{
+}
+
+Node* Uid::Clone() const
+{
+ return new Uid(*this);
+}
+
+void Uid::SetValue(uint64_t i)
+{
+ plist_set_uid_val(_node, i);
+}
+
+uint64_t Uid::GetValue() const
+{
+ uint64_t i = 0;
+ plist_get_uid_val(_node, &i);
+ return i;
+}
+
+} // namespace PList
diff --git a/src/base64.c b/src/base64.c
index e558d9e..603ab6d 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -28,7 +28,7 @@ static const signed char base64_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
@@ -43,82 +43,77 @@ static const signed char base64_table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
-char *base64encode(const unsigned char *buf, size_t *size)
+size_t base64encode(char *outbuf, const unsigned char *buf, size_t size)
{
- if (!buf || !size || !(*size > 0)) return NULL;
- int outlen = (*size / 3) * 4;
- char *outbuf = (char*)malloc(outlen+5); // 4 spare bytes + 1 for '\0'
+ if (!outbuf || !buf || (size <= 0)) {
+ return 0;
+ }
+
size_t n = 0;
size_t m = 0;
unsigned char input[3];
unsigned int output[4];
- while (n < *size) {
+ while (n < size) {
input[0] = buf[n];
- input[1] = (n+1 < *size) ? buf[n+1] : 0;
- input[2] = (n+2 < *size) ? buf[n+2] : 0;
+ input[1] = (n+1 < size) ? buf[n+1] : 0;
+ input[2] = (n+2 < size) ? buf[n+2] : 0;
output[0] = input[0] >> 2;
output[1] = ((input[0] & 3) << 4) + (input[1] >> 4);
output[2] = ((input[1] & 15) << 2) + (input[2] >> 6);
output[3] = input[2] & 63;
outbuf[m++] = base64_str[(int)output[0]];
outbuf[m++] = base64_str[(int)output[1]];
- outbuf[m++] = (n+1 < *size) ? base64_str[(int)output[2]] : base64_pad;
- outbuf[m++] = (n+2 < *size) ? base64_str[(int)output[3]] : base64_pad;
+ outbuf[m++] = (n+1 < size) ? base64_str[(int)output[2]] : base64_pad;
+ outbuf[m++] = (n+2 < size) ? base64_str[(int)output[3]] : base64_pad;
n+=3;
}
outbuf[m] = 0; // 0-termination!
- *size = m;
- return outbuf;
-}
-
-static int base64decode_block(unsigned char *target, const char *data, size_t data_size)
-{
- int w1,w2,w3,w4;
- int n,i;
-
- if (!data || (data_size <= 0)) {
- return 0;
- }
-
- n = 0;
- i = 0;
- while (n < data_size-3) {
- w1 = base64_table[(int)data[n]];
- w2 = base64_table[(int)data[n+1]];
- w3 = base64_table[(int)data[n+2]];
- w4 = base64_table[(int)data[n+3]];
-
- if (w2 >= 0) {
- target[i++] = (char)((w1*4 + (w2 >> 4)) & 255);
- }
- if (w3 >= 0) {
- target[i++] = (char)((w2*16 + (w3 >> 2)) & 255);
- }
- if (w4 >= 0) {
- target[i++] = (char)((w3*64 + w4) & 255);
- }
- n+=4;
- }
- return i;
+ return m;
}
unsigned char *base64decode(const char *buf, size_t *size)
{
- if (!buf) return NULL;
- size_t len = strlen(buf);
+ if (!buf || !size) return NULL;
+ size_t len = (*size > 0) ? *size : strlen(buf);
if (len <= 0) return NULL;
unsigned char *outbuf = (unsigned char*)malloc((len/4)*3+3);
+ if (!outbuf) return NULL;
+ const char *ptr = buf;
+ size_t p = 0;
+ int wv, w1, w2, w3, w4;
+ int tmpval[4];
+ int tmpcnt = 0;
- unsigned char *line;
- int p = 0;
+ do {
+ while (ptr < buf+len && (*ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r')) {
+ ptr++;
+ }
+ if (*ptr == '\0' || ptr >= buf+len) {
+ break;
+ }
+ if ((wv = base64_table[(int)(unsigned char)*ptr++]) == -1) {
+ continue;
+ }
+ tmpval[tmpcnt++] = wv;
+ if (tmpcnt == 4) {
+ tmpcnt = 0;
+ w1 = tmpval[0];
+ w2 = tmpval[1];
+ w3 = tmpval[2];
+ w4 = tmpval[3];
- line = (unsigned char*)strtok((char*)buf, "\r\n\t ");
- while (line) {
- p+=base64decode_block(outbuf+p, (const char*)line, strlen((char*)line));
+ if (w1 >= 0 && w2 >= 0) {
+ outbuf[p++] = (unsigned char)(((w1 << 2) + (w2 >> 4)) & 0xFF);
+ }
+ if (w2 >= 0 && w3 >= 0) {
+ outbuf[p++] = (unsigned char)(((w2 << 4) + (w3 >> 2)) & 0xFF);
+ }
+ if (w3 >= 0 && w4 >= 0) {
+ outbuf[p++] = (unsigned char)(((w3 << 6) + w4) & 0xFF);
+ }
+ }
+ } while (1);
- // get next line of base64 encoded block
- line = (unsigned char*)strtok(NULL, "\r\n\t ");
- }
outbuf[p] = 0;
*size = p;
return outbuf;
diff --git a/src/base64.h b/src/base64.h
index dbaf104..58b8396 100644
--- a/src/base64.h
+++ b/src/base64.h
@@ -21,9 +21,8 @@
#ifndef BASE64_H
#define BASE64_H
#include <stdlib.h>
-#include "common.h"
-_PLIST_INTERNAL char *base64encode(const unsigned char *buf, size_t *size);
-_PLIST_INTERNAL unsigned char *base64decode(const char *buf, size_t *size);
+size_t base64encode(char *outbuf, const unsigned char *buf, size_t size);
+unsigned char *base64decode(const char *buf, size_t *size);
#endif
diff --git a/src/bplist.c b/src/bplist.c
index eff44fc..7b08532 100644
--- a/src/bplist.c
+++ b/src/bplist.c
@@ -1,8 +1,9 @@
/*
- * plist.c
+ * bplist.c
* Binary plist implementation
*
- * Copyright (c) 2008 Jonathan Beck All Rights Reserved.
+ * Copyright (c) 2011-2017 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2008-2010 Jonathan Beck, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,37 +20,42 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
-#include <libxml/encoding.h>
#include <ctype.h>
+#include <inttypes.h>
-#include <plist/plist.h>
#include "plist.h"
#include "hashtable.h"
#include "bytearray.h"
#include "ptrarray.h"
+#include "plist/plist.h"
#include <node.h>
-#include <node_iterator.h>
/* Magic marker and size. */
-#define BPLIST_MAGIC ((uint8_t*)"bplist")
-#define BPLIST_MAGIC_SIZE 6
-
-#define BPLIST_VERSION ((uint8_t*)"00")
-#define BPLIST_VERSION_SIZE 2
-
-
-#define BPLIST_TRL_SIZE 26
-#define BPLIST_TRL_OFFSIZE_IDX 0
-#define BPLIST_TRL_PARMSIZE_IDX 1
-#define BPLIST_TRL_NUMOBJ_IDX 2
-#define BPLIST_TRL_ROOTOBJ_IDX 10
-#define BPLIST_TRL_OFFTAB_IDX 18
+#define BPLIST_MAGIC ((uint8_t*)"bplist")
+#define BPLIST_MAGIC_SIZE 6
+
+#define BPLIST_VERSION ((uint8_t*)"00")
+#define BPLIST_VERSION_SIZE 2
+
+#pragma pack(push,1)
+typedef struct {
+ uint8_t unused[6];
+ uint8_t offset_size;
+ uint8_t ref_size;
+ uint64_t num_objects;
+ uint64_t root_object_index;
+ uint64_t offset_table_offset;
+} bplist_trailer_t;
+#pragma pack(pop)
enum
{
@@ -57,44 +63,51 @@ enum
BPLIST_FALSE = 0x08,
BPLIST_TRUE = 0x09,
BPLIST_FILL = 0x0F, /* will be used for length grabbing */
- BPLIST_UINT = 0x10,
+ BPLIST_INT = 0x10,
BPLIST_REAL = 0x20,
BPLIST_DATE = 0x30,
BPLIST_DATA = 0x40,
BPLIST_STRING = 0x50,
BPLIST_UNICODE = 0x60,
- BPLIST_UID = 0x70,
+ BPLIST_UNK_0x70 = 0x70,
+ BPLIST_UID = 0x80,
BPLIST_ARRAY = 0xA0,
BPLIST_SET = 0xC0,
BPLIST_DICT = 0xD0,
BPLIST_MASK = 0xF0
};
-static void float_byte_convert(uint8_t * address, size_t size)
-{
-#if PLIST_BYTE_ORDER == PLIST_LITTLE_ENDIAN && !defined (__VFP_FP__)
- uint8_t i = 0, j = 0;
- uint8_t tmp = 0;
-
- for (i = 0; i < (size / 2); i++)
- {
- tmp = address[i];
- j = ((size - 1) + 0) - i;
- address[i] = address[j];
- address[j] = tmp;
- }
-#endif
-}
-
union plist_uint_ptr
{
- void *src;
+ const void *src;
uint8_t *u8ptr;
uint16_t *u16ptr;
uint32_t *u32ptr;
uint64_t *u64ptr;
};
+#ifdef _MSC_VER
+uint64_t get_unaligned_64(uint64_t *ptr)
+{
+ uint64_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+
+uint32_t get_unaligned_32(uint32_t *ptr)
+{
+ uint32_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+
+uint16_t get_unaligned_16(uint16_t *ptr)
+{
+ uint16_t temp;
+ memcpy(&temp, ptr, sizeof(temp));
+ return temp;
+}
+#else
#define get_unaligned(ptr) \
({ \
struct __attribute__((packed)) { \
@@ -102,102 +115,173 @@ union plist_uint_ptr
} *__p = (void *) (ptr); \
__p->__v; \
})
-
-
-static void byte_convert(uint8_t * address, size_t size)
-{
-#if PLIST_BYTE_ORDER == PLIST_LITTLE_ENDIAN
- uint8_t i = 0, j = 0;
- uint8_t tmp = 0;
-
- for (i = 0; i < (size / 2); i++)
- {
- tmp = address[i];
- j = ((size - 1) + 0) - i;
- address[i] = address[j];
- address[j] = tmp;
- }
#endif
-}
-static uint32_t uint24_from_be(union plist_uint_ptr buf)
-{
- union plist_uint_ptr tmp;
- uint32_t ret = 0;
- tmp.src = &ret;
+#ifndef bswap16
+#define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#endif
- memcpy(tmp.u8ptr + 1, buf.u8ptr, 3 * sizeof(char));
+#ifndef bswap32
+#define bswap32(x) ((((x) & 0xFF000000) >> 24) \
+ | (((x) & 0x00FF0000) >> 8) \
+ | (((x) & 0x0000FF00) << 8) \
+ | (((x) & 0x000000FF) << 24))
+#endif
- byte_convert(tmp.u8ptr, sizeof(uint32_t));
- return ret;
-}
+#ifndef bswap64
+#define bswap64(x) ((((x) & 0xFF00000000000000ull) >> 56) \
+ | (((x) & 0x00FF000000000000ull) >> 40) \
+ | (((x) & 0x0000FF0000000000ull) >> 24) \
+ | (((x) & 0x000000FF00000000ull) >> 8) \
+ | (((x) & 0x00000000FF000000ull) << 8) \
+ | (((x) & 0x0000000000FF0000ull) << 24) \
+ | (((x) & 0x000000000000FF00ull) << 40) \
+ | (((x) & 0x00000000000000FFull) << 56))
+#endif
#ifndef be16toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be16toh(x) (x)
#else
-#define be16toh(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#define be16toh(x) bswap16(x)
#endif
#endif
#ifndef be32toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be32toh(x) (x)
#else
-#define be32toh(x) ((((x) & 0xFF000000) >> 24) \
- | (((x) & 0x00FF0000) >> 8) \
- | (((x) & 0x0000FF00) << 8) \
- | (((x) & 0x000000FF) << 24))
+#define be32toh(x) bswap32(x)
#endif
#endif
#ifndef be64toh
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
+#ifdef __BIG_ENDIAN__
#define be64toh(x) (x)
#else
-#define be64toh(x) ((((x) & 0xFF00000000000000ull) >> 56) \
- | (((x) & 0x00FF000000000000ull) >> 40) \
- | (((x) & 0x0000FF0000000000ull) >> 24) \
- | (((x) & 0x000000FF00000000ull) >> 8) \
- | (((x) & 0x00000000FF000000ull) << 8) \
- | (((x) & 0x0000000000FF0000ull) << 24) \
- | (((x) & 0x000000000000FF00ull) << 40) \
- | (((x) & 0x00000000000000FFull) << 56))
+#define be64toh(x) bswap64(x)
#endif
#endif
-#define UINT_TO_HOST(x, n) \
- ({ \
- union plist_uint_ptr __up; \
- __up.src = x; \
- (n == 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \
- (n == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \
- (n == 3 ? uint24_from_be( __up ) : \
- (n == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \
- *__up.u8ptr )))); \
- })
+#ifdef __BIG_ENDIAN__
+#define beNtoh(x,n) (x >> ((8-n) << 3))
+#else
+#define beNtoh(x,n) be64toh((x) << ((8-(n)) << 3))
+#endif
-#define be64dec(x) \
+#ifdef _MSC_VER
+static uint64_t UINT_TO_HOST(const void* x, uint8_t n)
+{
+ union plist_uint_ptr __up;
+ __up.src = (n > 8) ? (const char*)x + (n - 8) : (const char*)x;
+ return (n >= 8 ? be64toh( get_unaligned_64(__up.u64ptr) ) :
+ (n == 4 ? be32toh( get_unaligned_32(__up.u32ptr) ) :
+ (n == 2 ? be16toh( get_unaligned_16(__up.u16ptr) ) :
+ (n == 1 ? *__up.u8ptr :
+ beNtoh( get_unaligned_64(__up.u64ptr), n)
+ ))));
+}
+#else
+#define UINT_TO_HOST(x, n) \
({ \
union plist_uint_ptr __up; \
- __up.src = x; \
- be64toh( get_unaligned(__up.u64ptr) ); \
+ __up.src = ((n) > 8) ? (const char*)(x) + ((n) - 8) : (const char*)(x); \
+ ((n) >= 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \
+ ((n) == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \
+ ((n) == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \
+ ((n) == 1 ? *__up.u8ptr : \
+ beNtoh( get_unaligned(__up.u64ptr), n) \
+ )))); \
})
+#endif
#define get_needed_bytes(x) \
- ( ((uint64_t)x) < (1ULL << 8) ? 1 : \
- ( ((uint64_t)x) < (1ULL << 16) ? 2 : \
- ( ((uint64_t)x) < (1ULL << 24) ? 3 : \
- ( ((uint64_t)x) < (1ULL << 32) ? 4 : 8))))
+ ( ((uint64_t)(x)) < (1ULL << 8) ? 1 : \
+ ( ((uint64_t)(x)) < (1ULL << 16) ? 2 : \
+ ( ((uint64_t)(x)) < (1ULL << 24) ? 3 : \
+ ( ((uint64_t)(x)) < (1ULL << 32) ? 4 : 8))))
-#define get_real_bytes(x) (x == (float) x ? 4 : 8)
+#define get_real_bytes(x) ((x) == (float) (x) ? sizeof(float) : sizeof(double))
-#define NODE_IS_ROOT(x) (((node_t*)x)->isRoot)
+#if (defined(__BIG_ENDIAN__) && !defined(__FLOAT_WORD_ORDER__)) \
+ || (defined(__FLOAT_WORD_ORDER__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define float_bswap64(x) (x)
+#define float_bswap32(x) (x)
+#else
+#define float_bswap64(x) bswap64(x)
+#define float_bswap32(x) bswap32(x)
+#endif
-static plist_t parse_uint_node(char *bnode, uint8_t size, char **next_object)
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
+#if __has_builtin(__builtin_umulll_overflow) || __GNUC__ >= 5
+#define uint64_mul_overflow(a, b, r) __builtin_umulll_overflow(a, b, (unsigned long long*)(r))
+#else
+static int uint64_mul_overflow(uint64_t a, uint64_t b, uint64_t *res)
+{
+ *res = a * b;
+ return (a > UINT64_MAX / b);
+}
+#endif
+
+#define NODE_IS_ROOT(x) (((node_t)(x))->isRoot)
+
+struct bplist_data {
+ const char* data;
+ uint64_t size;
+ uint64_t num_objects;
+ uint8_t ref_size;
+ uint8_t offset_size;
+ const char* offset_table;
+ uint32_t level;
+ ptrarray_t* used_indexes;
+ plist_err_t err;
+};
+
+#ifdef DEBUG
+static int plist_bin_debug = 0;
+#define PLIST_BIN_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binparser] ERROR: " __VA_ARGS__); }
+#define PLIST_BIN_WRITE_ERR(...) if (plist_bin_debug) { fprintf(stderr, "libplist[binwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_BIN_ERR(...)
+#define PLIST_BIN_WRITE_ERR(...)
+#endif
+
+void plist_bin_init(void)
+{
+ /* init binary plist stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_BIN_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_bin_debug = 1;
+ }
+#endif
+}
+
+void plist_bin_deinit(void)
+{
+ /* deinit binary plist stuff */
+}
+
+void plist_bin_set_debug(int debug)
+{
+#if DEBUG
+ plist_bin_debug = debug;
+#endif
+}
+
+static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index);
+
+static plist_t parse_int_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
size = 1 << size; // make length less misleading
switch (size)
@@ -206,91 +290,143 @@ static plist_t parse_uint_node(char *bnode, uint8_t size, char **next_object)
case sizeof(uint16_t):
case sizeof(uint32_t):
case sizeof(uint64_t):
- memcpy(&data->intval, bnode, size);
- data->intval = UINT_TO_HOST(&data->intval, size);
+ data->length = sizeof(uint64_t);
+ break;
+ case 16:
+ data->length = size;
break;
default:
free(data);
+ PLIST_BIN_ERR("%s: Invalid byte size for integer node\n", __func__);
return NULL;
};
- *next_object = bnode + size;
- data->type = PLIST_UINT;
- data->length = sizeof(uint64_t);
+ data->intval = UINT_TO_HOST(*bnode, size);
+
+ (*bnode) += size;
+ data->type = PLIST_INT;
return node_create(NULL, data);
}
-static plist_t parse_real_node(char *bnode, uint8_t size)
+static plist_t parse_real_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
- float floatval = 0.0;
- uint8_t* buf;
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
size = 1 << size; // make length less misleading
- buf = malloc (size);
- memcpy (buf, bnode, size);
switch (size)
{
- case sizeof(float):
- float_byte_convert(buf, size);
- floatval = *(float *) buf;
- data->realval = floatval;
- break;
- case sizeof(double):
- float_byte_convert(buf, size);
- data->realval = *(double *) buf;
+ case sizeof(uint32_t):
+ {
+ uint32_t ival;
+ memcpy(&ival, *bnode, sizeof(uint32_t));
+ ival = float_bswap32(ival);
+ float fval;
+ memcpy(&fval, &ival, sizeof(float));
+ data->realval = fval;
+ }
+ break;
+
+ case sizeof(uint64_t):
+ {
+ uint64_t ival;
+ memcpy(&ival, *bnode, sizeof(uint64_t));
+ ival = float_bswap64(ival);
+ memcpy(&data->realval, &ival, sizeof(double));
break;
+ }
+
default:
free(data);
+ PLIST_BIN_ERR("%s: Invalid byte size for real node\n", __func__);
return NULL;
}
- free (buf);
data->type = PLIST_REAL;
data->length = sizeof(double);
return node_create(NULL, data);
}
-static plist_t parse_date_node(char *bnode, uint8_t size)
+static plist_t parse_date_node(const char **bnode, uint8_t size)
{
plist_t node = parse_real_node(bnode, size);
plist_data_t data = plist_get_data(node);
- double time_real = data->realval;
- data->timeval.tv_sec = (long) time_real;
- data->timeval.tv_usec = (time_real - (long) time_real) * 1000000;
data->type = PLIST_DATE;
- data->length = sizeof(struct timeval);
return node;
}
-static plist_t parse_string_node(char *bnode, uint64_t size)
+static plist_t parse_string_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_STRING;
data->strval = (char *) malloc(sizeof(char) * (size + 1));
- memcpy(data->strval, bnode, size);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(char) * (size + 1));
+ return NULL;
+ }
+ memcpy(data->strval, *bnode, size);
data->strval[size] = '\0';
data->length = strlen(data->strval);
return node_create(NULL, data);
}
-static char *plist_utf16_to_utf8(uint16_t *unistr, long len, long *items_read, long *items_written)
+static char *plist_utf16be_to_utf8(uint16_t *unistr, size_t len, size_t *items_read, size_t *items_written)
{
if (!unistr || (len <= 0)) return NULL;
- char *outbuf = (char*)malloc(3*(len+1));
- int p = 0;
- int i = 0;
+ char* outbuf;
+ char* outbuf_new;
+ size_t p = 0;
+ size_t i = 0;
uint16_t wc;
+ uint32_t w;
+ int read_lead_surrogate = 0;
+
+ /* allocate with enough space */
+ outbuf = (char*)malloc(4*(len+1));
+ if (!outbuf) {
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)(4*(len+1)));
+ return NULL;
+ }
while (i < len) {
- wc = unistr[i++];
- if (wc >= 0x800) {
+ wc = UINT_TO_HOST(unistr + i, sizeof(wc));
+ i++;
+ if (wc >= 0xD800 && wc <= 0xDBFF) {
+ if (!read_lead_surrogate) {
+ read_lead_surrogate = 1;
+ w = 0x010000 + ((wc & 0x3FF) << 10);
+ } else {
+ // This is invalid, the next 16 bit char should be a trail surrogate.
+ // Handling error by skipping.
+ read_lead_surrogate = 0;
+ }
+ } else if (wc >= 0xDC00 && wc <= 0xDFFF) {
+ if (read_lead_surrogate) {
+ read_lead_surrogate = 0;
+ w = w | (wc & 0x3FF);
+ outbuf[p++] = (char)(0xF0 + ((w >> 18) & 0x7));
+ outbuf[p++] = (char)(0x80 + ((w >> 12) & 0x3F));
+ outbuf[p++] = (char)(0x80 + ((w >> 6) & 0x3F));
+ outbuf[p++] = (char)(0x80 + (w & 0x3F));
+ } else {
+ // This is invalid. A trail surrogate should always follow a lead surrogate.
+ // Handling error by skipping
+ }
+ } else if (wc >= 0x800) {
outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF));
outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F));
outbuf[p++] = (char)(0x80 + (wc & 0x3F));
@@ -309,85 +445,263 @@ static char *plist_utf16_to_utf8(uint16_t *unistr, long len, long *items_read, l
}
outbuf[p] = 0;
+ /* reduce the size to the actual size */
+ outbuf_new = (char*)realloc(outbuf, p+1);
+ if (outbuf_new) {
+ outbuf = outbuf_new;
+ }
+
return outbuf;
}
-static plist_t parse_unicode_node(char *bnode, uint64_t size)
+static plist_t parse_unicode_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
- uint64_t i = 0;
- uint16_t *unicodestr = NULL;
- char *tmpstr = NULL;
- long items_read = 0;
- long items_written = 0;
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ size_t items_read = 0;
+ size_t items_written = 0;
data->type = PLIST_STRING;
- unicodestr = (uint16_t*) malloc(sizeof(uint16_t) * size);
- memcpy(unicodestr, bnode, sizeof(uint16_t) * size);
- for (i = 0; i < size; i++)
- byte_convert((uint8_t *) (unicodestr + i), sizeof(uint16_t));
-
- tmpstr = plist_utf16_to_utf8(unicodestr, size, &items_read, &items_written);
- free(unicodestr);
+ data->strval = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
+ if (!data->strval) {
+ plist_free_data(data);
+ return NULL;
+ }
+ data->length = items_written;
- data->type = PLIST_STRING;
- data->strval = (char *) malloc(sizeof(char) * (items_written + 1));
- memcpy(data->strval, tmpstr, items_written);
- data->strval[items_written] = '\0';
- data->length = strlen(data->strval);
- free(tmpstr);
return node_create(NULL, data);
}
-static plist_t parse_data_node(char *bnode, uint64_t size)
+static plist_t parse_data_node(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
-
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATA;
data->length = size;
data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size);
+ if (!data->buff) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, sizeof(uint8_t) * size);
+ return NULL;
+ }
+ memcpy(data->buff, *bnode, sizeof(uint8_t) * size);
return node_create(NULL, data);
}
-static plist_t parse_dict_node(char *bnode, uint64_t size, uint32_t ref_size)
+static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
+ uint64_t j;
+ uint64_t str_i = 0, str_j = 0;
+ uint64_t index1, index2;
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ const char *index1_ptr = NULL;
+ const char *index2_ptr = NULL;
data->type = PLIST_DICT;
data->length = size;
- data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size * ref_size * 2);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size * ref_size * 2);
- return node_create(NULL, data);
+ plist_t node = node_create(NULL, data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ for (j = 0; j < data->length; j++) {
+ str_i = j * bplist->ref_size;
+ str_j = (j + size) * bplist->ref_size;
+ index1_ptr = (*bnode) + str_i;
+ index2_ptr = (*bnode) + str_j;
+
+ if ((index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) ||
+ (index2_ptr < bplist->data || index2_ptr + bplist->ref_size > bplist->offset_table)) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 " is outside of valid range\n", __func__, j);
+ return NULL;
+ }
+
+ index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);
+ index2 = UINT_TO_HOST(index2_ptr, bplist->ref_size);
+
+ if (index1 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+ if (index2 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": value index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+
+ /* process key node */
+ plist_t key = parse_bin_node_at_index(bplist, index1);
+ if (!key) {
+ plist_free(node);
+ return NULL;
+ }
+
+ if (plist_get_data(key)->type != PLIST_STRING) {
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": invalid node type for key\n", __func__, j);
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ /* enforce key type */
+ plist_get_data(key)->type = PLIST_KEY;
+ if (!plist_get_data(key)->strval) {
+ PLIST_BIN_ERR("%s: dict entry %" PRIu64 ": key must not be NULL\n", __func__, j);
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ /* process value node */
+ plist_t val = parse_bin_node_at_index(bplist, index2);
+ if (!val) {
+ plist_free(key);
+ plist_free(node);
+ return NULL;
+ }
+
+ node_attach((node_t)node, (node_t)key);
+ node_attach((node_t)node, (node_t)val);
+ }
+
+ return node;
}
-static plist_t parse_array_node(char *bnode, uint64_t size, uint32_t ref_size)
+static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode, uint64_t size)
{
+ uint64_t j;
+ uint64_t str_j = 0;
+ uint64_t index1;
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ const char *index1_ptr = NULL;
data->type = PLIST_ARRAY;
data->length = size;
- data->buff = (uint8_t *) malloc(sizeof(uint8_t) * size * ref_size);
- memcpy(data->buff, bnode, sizeof(uint8_t) * size * ref_size);
- return node_create(NULL, data);
+ plist_t node = node_create(NULL, data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_BIN_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ for (j = 0; j < data->length; j++) {
+ str_j = j * bplist->ref_size;
+ index1_ptr = (*bnode) + str_j;
+
+ if (index1_ptr < bplist->data || index1_ptr + bplist->ref_size > bplist->offset_table) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: array item %" PRIu64 " is outside of valid range\n", __func__, j);
+ return NULL;
+ }
+
+ index1 = UINT_TO_HOST(index1_ptr, bplist->ref_size);
+
+ if (index1 >= bplist->num_objects) {
+ plist_free(node);
+ PLIST_BIN_ERR("%s: array item %" PRIu64 " object index (%" PRIu64 ") must be smaller than the number of objects (%" PRIu64 ")\n", __func__, j, index1, bplist->num_objects);
+ return NULL;
+ }
+
+ /* process value node */
+ plist_t val = parse_bin_node_at_index(bplist, index1);
+ if (!val) {
+ plist_free(node);
+ return NULL;
+ }
+
+ node_attach((node_t)node, (node_t)val);
+ }
+
+ return node;
}
+static plist_t parse_uid_node(const char **bnode, uint8_t size)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ size = size + 1;
+ data->intval = UINT_TO_HOST(*bnode, size);
+ if (data->intval > UINT32_MAX) {
+ PLIST_BIN_ERR("%s: value %" PRIu64 " too large for UID node (must be <= %u)\n", __func__, (uint64_t)data->intval, UINT32_MAX);
+ free(data);
+ return NULL;
+ }
+
+ (*bnode) += size;
+ data->type = PLIST_UID;
+ data->length = sizeof(uint64_t);
+ return node_create(NULL, data);
+}
-static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_object)
+static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
{
uint16_t type = 0;
uint64_t size = 0;
+ uint64_t pobject = 0;
+ uint64_t poffset_table = (uint64_t)(uintptr_t)bplist->offset_table;
if (!object)
return NULL;
- type = (*object) & 0xF0;
- size = (*object) & 0x0F;
- object++;
+ type = (**object) & BPLIST_MASK;
+ size = (**object) & BPLIST_FILL;
+ (*object)++;
+
+ if (size == BPLIST_FILL) {
+ switch (type) {
+ case BPLIST_DATA:
+ case BPLIST_STRING:
+ case BPLIST_UNICODE:
+ case BPLIST_ARRAY:
+ case BPLIST_SET:
+ case BPLIST_DICT:
+ {
+ uint16_t next_size = **object & BPLIST_FILL;
+ if ((**object & BPLIST_MASK) != BPLIST_INT) {
+ PLIST_BIN_ERR("%s: invalid size node type for node type 0x%02x: found 0x%02x, expected 0x%02x\n", __func__, type, **object & BPLIST_MASK, BPLIST_INT);
+ return NULL;
+ }
+ (*object)++;
+ next_size = 1 << next_size;
+ if (*object + next_size > bplist->offset_table) {
+ PLIST_BIN_ERR("%s: size node data bytes for node type 0x%02x point outside of valid range\n", __func__, type);
+ return NULL;
+ }
+ size = UINT_TO_HOST(*object, next_size);
+ (*object) += next_size;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ pobject = (uint64_t)(uintptr_t)*object;
switch (type)
{
@@ -399,6 +713,10 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
case BPLIST_TRUE:
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = TRUE;
data->length = 1;
@@ -408,6 +726,10 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
case BPLIST_FALSE:
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = FALSE;
data->length = 1;
@@ -415,246 +737,286 @@ static plist_t parse_bin_node(char *object, uint8_t dict_size, char **next_objec
}
case BPLIST_NULL:
+ {
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_BIN_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ data->length = 0;
+ return node_create(NULL, data);
+ }
+
default:
return NULL;
}
- case BPLIST_UINT:
- return parse_uint_node(object, size, next_object);
+ case BPLIST_INT:
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_int_node(object, size);
case BPLIST_REAL:
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_REAL data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
return parse_real_node(object, size);
case BPLIST_DATE:
- if (3 != size)
+ if (3 != size) {
+ PLIST_BIN_ERR("%s: invalid data size for BPLIST_DATE node\n", __func__);
return NULL;
- else
- return parse_date_node(object, size);
+ }
+ if (pobject + (uint64_t)(1 << size) > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DATE data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_date_node(object, size);
case BPLIST_DATA:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DATA data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_data_node(object, size);
case BPLIST_STRING:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_STRING data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_string_node(object, size);
case BPLIST_UNICODE:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (size*2 < size) {
+ PLIST_BIN_ERR("%s: Integer overflow when calculating BPLIST_UNICODE data size.\n", __func__);
+ return NULL;
+ }
+ if (pobject + size*2 < pobject || pobject + size*2 > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_UNICODE data bytes point outside of valid range\n", __func__);
+ return NULL;
}
return parse_unicode_node(object, size);
- case BPLIST_UID:
+ case BPLIST_SET:
case BPLIST_ARRAY:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_ARRAY data bytes point outside of valid range\n", __func__);
+ return NULL;
}
- return parse_array_node(object, size, dict_size);
+ return parse_array_node(bplist, object, size);
+
+ case BPLIST_UID:
+ if (pobject + size+1 > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_UID data bytes point outside of valid range\n", __func__);
+ return NULL;
+ }
+ return parse_uid_node(object, size);
- case BPLIST_SET:
case BPLIST_DICT:
- if (0x0F == size)
- {
- plist_t size_node = parse_bin_node(object, dict_size, &object);
- if (plist_get_node_type(size_node) != PLIST_UINT)
- return NULL;
- plist_get_uint_val(size_node, &size);
- plist_free(size_node);
+ if (pobject + size < pobject || pobject + size > poffset_table) {
+ PLIST_BIN_ERR("%s: BPLIST_DICT data bytes point outside of valid range\n", __func__);
+ return NULL;
}
- return parse_dict_node(object, size, dict_size);
+ return parse_dict_node(bplist, object, size);
+
default:
+ PLIST_BIN_ERR("%s: unexpected node type 0x%02x\n", __func__, type);
return NULL;
}
return NULL;
}
-static void* copy_plist_data(const void* src)
+static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node_index)
{
- plist_data_t srcdata = (plist_data_t) src;
- plist_data_t dstdata = plist_new_plist_data();
+ int i = 0;
+ const char* ptr = NULL;
+ plist_t plist = NULL;
+ const char* idx_ptr = NULL;
+
+ if (node_index >= bplist->num_objects) {
+ PLIST_BIN_ERR("node index (%u) must be smaller than the number of objects (%" PRIu64 ")\n", node_index, bplist->num_objects);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
- dstdata->type = srcdata->type;
- dstdata->length = srcdata->length;
- switch (dstdata->type)
- {
- case PLIST_BOOLEAN:
- dstdata->boolval = srcdata->boolval;
- break;
- case PLIST_UINT:
- dstdata->intval = srcdata->intval;
- break;
- case PLIST_DATE:
- dstdata->timeval.tv_sec = srcdata->timeval.tv_sec;
- dstdata->timeval.tv_usec = srcdata->timeval.tv_usec;
- break;
- case PLIST_REAL:
- dstdata->realval = srcdata->realval;
- break;
- case PLIST_KEY:
- case PLIST_STRING:
- dstdata->strval = strdup(srcdata->strval);
- break;
- case PLIST_DATA:
- case PLIST_ARRAY:
- dstdata->buff = (uint8_t*) malloc(sizeof(uint8_t) * srcdata->length);
- memcpy(dstdata->buff, srcdata->buff, sizeof(uint8_t) * srcdata->length);
- break;
- case PLIST_DICT:
- dstdata->buff = (uint8_t*) malloc(sizeof(uint8_t) * srcdata->length * 2);
- memcpy(dstdata->buff, srcdata->buff, sizeof(uint8_t) * srcdata->length * 2);
- break;
- default:
- break;
+ idx_ptr = bplist->offset_table + node_index * bplist->offset_size;
+ if (idx_ptr < bplist->offset_table ||
+ idx_ptr >= bplist->offset_table + bplist->num_objects * bplist->offset_size) {
+ PLIST_BIN_ERR("node index %u points outside of valid range\n", node_index);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+
+ uint64_t node_offset = UINT_TO_HOST(idx_ptr, bplist->offset_size);
+ if (node_offset > (uint64_t)bplist->size) {
+ PLIST_BIN_ERR("node offset overflow (%" PRIu64 ")\n", node_offset);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ptr = bplist->data + node_offset;
+ /* make sure the node offset is in a sane range */
+ if ((ptr < bplist->data+BPLIST_MAGIC_SIZE+BPLIST_VERSION_SIZE) || (ptr >= bplist->offset_table)) {
+ PLIST_BIN_ERR("offset for node index %u points outside of valid range\n", node_index);
+ bplist->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+
+ /* check nesting depth */
+ if (bplist->level > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_BIN_ERR("maximum nesting depth (%u) exceeded\n",(unsigned)PLIST_MAX_NESTING_DEPTH);
+ bplist->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+
+ /* store node_index for current recursion level */
+ if ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) {
+ while ((uint32_t)ptr_array_size(bplist->used_indexes) < bplist->level+1) {
+ ptr_array_add(bplist->used_indexes, (void*)(uintptr_t)node_index);
+ }
+ } else {
+ ptr_array_set(bplist->used_indexes, (void*)(uintptr_t)node_index, bplist->level);
+ }
+
+ /* recursion check */
+ if (bplist->level > 0) {
+ for (i = bplist->level-1; i >= 0; i--) {
+ void *node_i = ptr_array_index(bplist->used_indexes, i);
+ void *node_level = ptr_array_index(bplist->used_indexes, bplist->level);
+ if (node_i == node_level) {
+ PLIST_BIN_ERR("recursion detected in binary plist\n");
+ bplist->err = PLIST_ERR_CIRCULAR_REF;
+ return NULL;
+ }
+ }
}
- return dstdata;
+ /* finally parse node */
+ bplist->level++;
+ plist = parse_bin_node(bplist, &ptr);
+ bplist->level--;
+ return plist;
}
-void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist)
+plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist)
{
- char *trailer = NULL;
-
+ bplist_trailer_t *trailer = NULL;
uint8_t offset_size = 0;
- uint8_t dict_param_size = 0;
+ uint8_t ref_size = 0;
uint64_t num_objects = 0;
uint64_t root_object = 0;
- uint64_t offset_table_index = 0;
-
- plist_t *nodeslist = NULL;
- uint64_t i = 0;
- uint64_t current_offset = 0;
- char *offset_table = NULL;
- uint32_t j = 0, str_i = 0, str_j = 0;
- uint32_t index1 = 0, index2 = 0;
+ const char *offset_table = NULL;
+ uint64_t offset_table_size = 0;
+ const char *start_data = NULL;
+ const char *end_data = NULL;
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_bin || length == 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
//first check we have enough data
- if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + BPLIST_TRL_SIZE))
- return;
+ if (!(length >= BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE + sizeof(bplist_trailer_t))) {
+ PLIST_BIN_ERR("plist data is to small to hold a binary plist\n");
+ return PLIST_ERR_PARSE;
+ }
//check that plist_bin in actually a plist
- if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0)
- return;
+ if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0) {
+ PLIST_BIN_ERR("bplist magic mismatch\n");
+ return PLIST_ERR_PARSE;
+ }
//check for known version
- if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0)
- return;
+ if (memcmp(plist_bin + BPLIST_MAGIC_SIZE, BPLIST_VERSION, BPLIST_VERSION_SIZE) != 0) {
+ PLIST_BIN_ERR("unsupported binary plist version '%.2s\n", plist_bin+BPLIST_MAGIC_SIZE);
+ return PLIST_ERR_PARSE;
+ }
+
+ start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE;
+ end_data = plist_bin + length - sizeof(bplist_trailer_t);
//now parse trailer
- trailer = (char *) (plist_bin + (length - BPLIST_TRL_SIZE));
+ trailer = (bplist_trailer_t*)end_data;
+
+ offset_size = trailer->offset_size;
+ ref_size = trailer->ref_size;
+ num_objects = be64toh(trailer->num_objects);
+ root_object = be64toh(trailer->root_object_index);
+
+ uint64_t offset_table_offset = be64toh(trailer->offset_table_offset);
+ uint64_t max_valid_offset = (uint64_t)length - sizeof(bplist_trailer_t);
+ if (offset_table_offset > max_valid_offset) {
+ PLIST_BIN_ERR("offset table offset outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
+ offset_table = (char *)(plist_bin + offset_table_offset);
- offset_size = trailer[BPLIST_TRL_OFFSIZE_IDX];
- dict_param_size = trailer[BPLIST_TRL_PARMSIZE_IDX];
- num_objects = be64dec(trailer + BPLIST_TRL_NUMOBJ_IDX);
- root_object = be64dec(trailer + BPLIST_TRL_ROOTOBJ_IDX);
- offset_table_index = be64dec(trailer + BPLIST_TRL_OFFTAB_IDX);
+ if (num_objects == 0) {
+ PLIST_BIN_ERR("number of objects must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- if (num_objects == 0)
- return;
+ if (offset_size == 0) {
+ PLIST_BIN_ERR("offset size in trailer must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- //allocate serialized array of nodes
- nodeslist = (plist_t *) malloc(sizeof(plist_t) * num_objects);
+ if (ref_size == 0) {
+ PLIST_BIN_ERR("object reference size in trailer must be larger than 0\n");
+ return PLIST_ERR_PARSE;
+ }
- if (!nodeslist)
- return;
+ if (root_object >= num_objects) {
+ PLIST_BIN_ERR("root object index (%" PRIu64 ") must be smaller than number of objects (%" PRIu64 ")\n", root_object, num_objects);
+ return PLIST_ERR_PARSE;
+ }
- //parse serialized nodes
- offset_table = (char *) (plist_bin + offset_table_index);
- for (i = 0; i < num_objects; i++)
- {
- char *obj = NULL;
- current_offset = UINT_TO_HOST(offset_table + i * offset_size, offset_size);
+ if (offset_table < start_data || offset_table >= end_data) {
+ PLIST_BIN_ERR("offset table offset points outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
- obj = (char *) (plist_bin + current_offset);
- nodeslist[i] = parse_bin_node(obj, dict_param_size, &obj);
+ if (uint64_mul_overflow(num_objects, offset_size, &offset_table_size)) {
+ PLIST_BIN_ERR("integer overflow when calculating offset table size\n");
+ return PLIST_ERR_PARSE;
}
- //setup children for structured types
- for (i = 0; i < num_objects; i++)
- {
+ if (offset_table_size > (uint64_t)(end_data - offset_table)) {
+ PLIST_BIN_ERR("offset table points outside of valid range\n");
+ return PLIST_ERR_PARSE;
+ }
- plist_data_t data = plist_get_data(nodeslist[i]);
+ struct bplist_data bplist;
+ bplist.data = plist_bin;
+ bplist.size = length;
+ bplist.num_objects = num_objects;
+ bplist.ref_size = ref_size;
+ bplist.offset_size = offset_size;
+ bplist.offset_table = offset_table;
+ bplist.level = 0;
+ bplist.used_indexes = ptr_array_new(16);
+ bplist.err = PLIST_ERR_SUCCESS;
+
+ if (!bplist.used_indexes) {
+ PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n");
+ return PLIST_ERR_NO_MEM;
+ }
- switch (data->type)
- {
- case PLIST_DICT:
- for (j = 0; j < data->length; j++)
- {
- str_i = j * dict_param_size;
- str_j = (j + data->length) * dict_param_size;
-
- index1 = UINT_TO_HOST(data->buff + str_i, dict_param_size);
- index2 = UINT_TO_HOST(data->buff + str_j, dict_param_size);
-
- //first one is actually a key
- plist_get_data(nodeslist[index1])->type = PLIST_KEY;
-
- if (index1 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index1]))
- node_attach(nodeslist[i], nodeslist[index1]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index1], copy_plist_data));
- }
-
- if (index2 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index2]))
- node_attach(nodeslist[i], nodeslist[index2]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index2], copy_plist_data));
- }
- }
+ *plist = parse_bin_node_at_index(&bplist, root_object);
- free(data->buff);
- break;
+ ptr_array_free(bplist.used_indexes);
- case PLIST_ARRAY:
- for (j = 0; j < data->length; j++)
- {
- str_j = j * dict_param_size;
- index1 = UINT_TO_HOST(data->buff + str_j, dict_param_size);
-
- if (index1 < num_objects)
- {
- if (NODE_IS_ROOT(nodeslist[index1]))
- node_attach(nodeslist[i], nodeslist[index1]);
- else
- node_attach(nodeslist[i], node_copy_deep(nodeslist[index1], copy_plist_data));
- }
- }
- free(data->buff);
- break;
- default:
- break;
- }
+ if (!*plist) {
+ return (bplist.err != PLIST_ERR_SUCCESS) ? bplist.err : PLIST_ERR_PARSE;
}
- *plist = nodeslist[root_object];
- free(nodeslist);
+ return PLIST_ERR_SUCCESS;
}
static unsigned int plist_data_hash(const void* key)
@@ -670,15 +1032,18 @@ static unsigned int plist_data_hash(const void* key)
switch (data->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
+ case PLIST_DATE:
+ case PLIST_UID:
buff = (char *) &data->intval; //works also for real as we use an union
size = 8;
break;
case PLIST_KEY:
case PLIST_STRING:
buff = data->strval;
- size = strlen(buff);
+ size = data->length;
break;
case PLIST_DATA:
case PLIST_ARRAY:
@@ -687,17 +1052,16 @@ static unsigned int plist_data_hash(const void* key)
buff = (char *) &key;
size = sizeof(const void*);
break;
- case PLIST_DATE:
- buff = (char *) &(data->timeval);
- size = data->length;
- break;
default:
break;
}
- //now perform hash
- for (i = 0; i < size; buff++, i++)
- hash = hash << 7 ^ (*buff);
+ // now perform hash using djb2 hashing algorithm
+ // see: http://www.cse.yorku.ca/~oz/hash.html
+ hash += 5381;
+ for (i = 0; i < size; buff++, i++) {
+ hash = ((hash << 5) + hash) + *buff;
+ }
return hash;
}
@@ -706,110 +1070,124 @@ struct serialize_s
{
ptrarray_t* objects;
hashtable_t* ref_table;
+ hashtable_t* in_stack;
};
-static void serialize_plist(node_t* node, void* data)
+static plist_err_t serialize_plist(node_t node, void* data, uint32_t depth)
{
uint64_t *index_val = NULL;
struct serialize_s *ser = (struct serialize_s *) data;
- uint64_t current_index = ser->objects->len;
- //first check that node is not yet in objects
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_BIN_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ // circular reference check: is node on current recursion stack?
+ if (hash_table_lookup(ser->in_stack, node)) {
+ PLIST_BIN_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // first check that node is not yet in objects
void* val = hash_table_lookup(ser->ref_table, node);
- if (val)
- {
- //data is already in table
- return;
+ if (val) {
+ // data is already in table
+ return PLIST_ERR_SUCCESS;
}
- //insert new ref
+
+ // mark as active
+ hash_table_insert(ser->in_stack, node, (void*)1);
+
+ // insert new ref
index_val = (uint64_t *) malloc(sizeof(uint64_t));
- *index_val = current_index;
+ if (!index_val) return PLIST_ERR_NO_MEM;
+ *index_val = ser->objects->len;
hash_table_insert(ser->ref_table, node, index_val);
- //now append current node to object array
+ // now append current node to object array
ptr_array_add(ser->objects, node);
- //now recurse on children
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- serialize_plist(ch, data);
+ // now recurse on children
+ node_t ch;
+ plist_err_t err = PLIST_ERR_SUCCESS;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ err = serialize_plist(ch, data, depth+1);
+ if (err != PLIST_ERR_SUCCESS) {
+ break;
+ }
}
- node_iterator_destroy(ni);
- return;
+ // leave recursion stack
+ hash_table_remove(ser->in_stack, node);
+
+ return err;
}
-#define Log2(x) (x == 8 ? 3 : (x == 4 ? 2 : (x == 2 ? 1 : 0)))
+#define Log2(x) ((x) == 8 ? 3 : ((x) == 4 ? 2 : ((x) == 2 ? 1 : 0)))
static void write_int(bytearray_t * bplist, uint64_t val)
{
- uint64_t size = get_needed_bytes(val);
- uint8_t *buff = NULL;
+ int size = get_needed_bytes(val);
+ uint8_t sz;
//do not write 3bytes int node
if (size == 3)
size++;
+ sz = BPLIST_INT | Log2(size);
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- val = val << ((sizeof(uint64_t) - size) * 8);
-#endif
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
+}
- buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_UINT | Log2(size);
- memcpy(buff + 1, &val, size);
- byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+static void write_uint(bytearray_t * bplist, uint64_t val)
+{
+ uint8_t sz = BPLIST_INT | 4;
+ uint64_t zero = 0;
+
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, &zero, sizeof(uint64_t));
+ byte_array_append(bplist, &val, sizeof(uint64_t));
}
static void write_real(bytearray_t * bplist, double val)
{
- uint64_t size = get_real_bytes(val); //cheat to know used space
- uint8_t *buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_REAL | Log2(size);
- if (size == sizeof(double))
- {
- memcpy(buff + 1, &val, size);
- }
- else if (size == sizeof(float))
- {
- float tmpval = (float) val;
- memcpy(buff + 1, &tmpval, size);
+ int size = get_real_bytes(val); //cheat to know used space
+ uint8_t buff[16];
+ buff[7] = BPLIST_REAL | Log2(size);
+ if (size == sizeof(float)) {
+ float floatval = (float)val;
+ uint32_t intval;
+ memcpy(&intval, &floatval, sizeof(float));
+ *(uint32_t*)(buff+8) = float_bswap32(intval);
+ } else {
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
}
- float_byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+ byte_array_append(bplist, buff+7, size+1);
}
static void write_date(bytearray_t * bplist, double val)
{
- uint64_t size = 8; //dates always use 8 bytes
- uint8_t *buff = (uint8_t *) malloc(sizeof(uint8_t) + size);
- buff[0] = BPLIST_DATE | Log2(size);
- memcpy(buff + 1, &val, size);
- float_byte_convert(buff + 1, size);
- byte_array_append(bplist, buff, sizeof(uint8_t) + size);
- free(buff);
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ uint8_t buff[16];
+ buff[7] = BPLIST_DATE | 3;
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
+ byte_array_append(bplist, buff+7, 9);
}
static void write_raw_data(bytearray_t * bplist, uint8_t mark, uint8_t * val, uint64_t size)
{
- uint8_t *buff = NULL;
uint8_t marker = mark | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
- }
- //stupid unicode buffer length
- if (BPLIST_UNICODE==mark) size *= 2;
- buff = (uint8_t *) malloc(size);
- memcpy(buff, val, size);
- byte_array_append(bplist, buff, size);
- free(buff);
+ if (size >= 15) {
+ write_int(bplist, size);
+ }
+ if (BPLIST_UNICODE==mark) size <<= 1;
+ byte_array_append(bplist, val, size);
}
static void write_data(bytearray_t * bplist, uint8_t * val, uint64_t size)
@@ -817,208 +1195,323 @@ static void write_data(bytearray_t * bplist, uint8_t * val, uint64_t size)
write_raw_data(bplist, BPLIST_DATA, val, size);
}
-static void write_string(bytearray_t * bplist, char *val)
+static void write_string(bytearray_t * bplist, char *val, uint64_t size)
{
- uint64_t size = strlen(val);
write_raw_data(bplist, BPLIST_STRING, (uint8_t *) val, size);
}
-static void write_unicode(bytearray_t * bplist, uint16_t * val, uint64_t size)
+static uint16_t *plist_utf8_to_utf16be(const unsigned char *unistr, size_t size, size_t *items_read, size_t *items_written)
{
- uint64_t i = 0;
- uint64_t size2 = size * sizeof(uint16_t);
- uint8_t *buff = (uint8_t *) malloc(size2);
- memcpy(buff, val, size2);
- for (i = 0; i < size; i++)
- byte_convert(buff + i * sizeof(uint16_t), sizeof(uint16_t));
- write_raw_data(bplist, BPLIST_UNICODE, buff, size);
- free(buff);
+ uint16_t *outbuf;
+ size_t p = 0;
+ size_t i = 0;
+
+ unsigned char c0;
+ unsigned char c1;
+ unsigned char c2;
+ unsigned char c3;
+
+ outbuf = (uint16_t*)malloc(((size*2)+1)*sizeof(uint16_t));
+ if (!outbuf) {
+ PLIST_BIN_ERR("%s: Could not allocate %" PRIu64 " bytes\n", __func__, (uint64_t)((size*2)+1)*sizeof(uint16_t));
+ return NULL;
+ }
+
+ while (i < size) {
+ c0 = unistr[i];
+ c1 = (i+1 < size) ? unistr[i+1] : 0;
+ c2 = (i+2 < size) ? unistr[i+2] : 0;
+ c3 = (i+3 < size) ? unistr[i+3] : 0;
+ if ((c0 >= 0xF0 && c0 <= 0xF4) && (i+3 < size) && ((c1 & 0xC0) == 0x80) && ((c2 & 0xC0) == 0x80) && ((c3 & 0xC0) == 0x80)) {
+ // 4 byte sequence. Need to generate UTF-16 surrogate pair
+ /* lead-specific second-byte constraints */
+ if ((c0 == 0xF0 && c1 < 0x90) || /* overlong (< U+10000) */
+ (c0 == 0xF4 && c1 > 0x8F)) /* > U+10FFFF */
+ {
+ break;
+ }
+ uint32_t w = ((uint32_t)(c3 & 0x3F)) | ((uint32_t)(c2 & 0x3F) << 6) | ((uint32_t)(c1 & 0x3F) << 12) | ((uint32_t)(c0 & 0x07) << 18);
+ if (w < 0x10000 || w > 0x10FFFF) break;
+ w -= 0x10000;
+ outbuf[p++] = be16toh((uint16_t)(0xD800 + (w >> 10)));
+ outbuf[p++] = be16toh((uint16_t)(0xDC00 + (w & 0x3FF)));
+ i+=4;
+ } else if (((c0 & 0xF0) == 0xE0) && (i+2 < size) && ((c1 & 0xC0) == 0x80) && ((c2 & 0xC0) == 0x80)) {
+ // 3 byte sequence
+ if ((c0 == 0xE0 && c1 < 0xA0) || /* overlong (< U+0800) */
+ (c0 == 0xED && c1 > 0x9F)) /* UTF-16 surrogate range */
+ {
+ break;
+ }
+ uint32_t w = ((uint32_t)(c2 & 0x3F)) | ((uint32_t)(c1 & 0x3F) << 6) | ((uint32_t)(c0 & 0x0F) << 12);
+ if (w < 0x800) break;
+ if (w >= 0xD800 && w <= 0xDFFF) break; // invalid Unicode scalar values
+ outbuf[p++] = be16toh((uint16_t)w);
+ i+=3;
+ } else if ((c0 >= 0xC2 && c0 <= 0xDF) && (i+1 < size) && ((c1 & 0xC0) == 0x80)) {
+ // 2 byte sequence
+ uint32_t w = ((uint32_t)(c1 & 0x3F)) | ((uint32_t)(c0 & 0x1F) << 6);
+ outbuf[p++] = be16toh((uint16_t)w);
+ i+=2;
+ } else if (c0 < 0x80) {
+ // 1 byte sequence
+ outbuf[p++] = be16toh((uint16_t)c0);
+ i+=1;
+ } else {
+ // invalid character
+ PLIST_BIN_ERR("%s: invalid utf8 sequence in string at index %zu\n", __func__, i);
+ break;
+ }
+ }
+ if (items_read) {
+ *items_read = i;
+ }
+ if (items_written) {
+ *items_written = p;
+ }
+ outbuf[p] = 0;
+
+ return outbuf;
}
-static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t dict_param_size)
+static void write_unicode(bytearray_t * bplist, char *val, size_t size)
{
- uint64_t idx = 0;
- uint8_t *buff = NULL;
+ size_t items_read = 0;
+ size_t items_written = 0;
+ uint16_t *unicodestr = NULL;
+
+ unicodestr = plist_utf8_to_utf16be((const unsigned char *)val, size, &items_read, &items_written);
+ write_raw_data(bplist, BPLIST_UNICODE, (uint8_t*)unicodestr, items_written);
+ free(unicodestr);
+}
- node_t* cur = NULL;
+static void write_array(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
+{
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node);
uint8_t marker = BPLIST_ARRAY | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
+ if (size >= 15) {
+ write_int(bplist, size);
}
- buff = (uint8_t *) malloc(size * dict_param_size);
-
- for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(cur), i++)
- {
- idx = *(uint64_t *) (hash_table_lookup(ref_table, cur));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx = idx << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + i * dict_param_size, &idx, dict_param_size);
- byte_convert(buff + i * dict_param_size, dict_param_size);
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(cur), i++) {
+ uint64_t idx = *(uint64_t *) (hash_table_lookup(ref_table, cur));
+ idx = be64toh(idx);
+ byte_array_append(bplist, (uint8_t*)&idx + (sizeof(uint64_t) - ref_size), ref_size);
}
-
- //now append to bplist
- byte_array_append(bplist, buff, size * dict_param_size);
- free(buff);
-
}
-static void write_dict(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t dict_param_size)
+static void write_dict(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- uint64_t idx1 = 0;
- uint64_t idx2 = 0;
- uint8_t *buff = NULL;
-
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node) / 2;
uint8_t marker = BPLIST_DICT | (size < 15 ? size : 0xf);
byte_array_append(bplist, &marker, sizeof(uint8_t));
- if (size >= 15)
- {
- bytearray_t *int_buff = byte_array_new();
- write_int(int_buff, size);
- byte_array_append(bplist, int_buff->data, int_buff->len);
- byte_array_free(int_buff);
+ if (size >= 15) {
+ write_int(bplist, size);
}
- buff = (uint8_t *) malloc(size * 2 * dict_param_size);
- for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++)
- {
- idx1 = *(uint64_t *) (hash_table_lookup(ref_table, cur));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx1 = idx1 << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + i * dict_param_size, &idx1, dict_param_size);
- byte_convert(buff + i * dict_param_size, dict_param_size);
-
- idx2 = *(uint64_t *) (hash_table_lookup(ref_table, cur->next));
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- idx2 = idx2 << ((sizeof(uint64_t) - dict_param_size) * 8);
-#endif
- memcpy(buff + (i + size) * dict_param_size, &idx2, dict_param_size);
- byte_convert(buff + (i + size) * dict_param_size, dict_param_size);
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) {
+ uint64_t idx1 = *(uint64_t *) (hash_table_lookup(ref_table, cur));
+ idx1 = be64toh(idx1);
+ byte_array_append(bplist, (uint8_t*)&idx1 + (sizeof(uint64_t) - ref_size), ref_size);
}
- //now append to bplist
- byte_array_append(bplist, buff, size * 2 * dict_param_size);
- free(buff);
-
+ for (i = 0, cur = node_first_child(node); cur && i < size; cur = node_next_sibling(node_next_sibling(cur)), i++) {
+ uint64_t idx2 = *(uint64_t *) (hash_table_lookup(ref_table, cur->next));
+ idx2 = be64toh(idx2);
+ byte_array_append(bplist, (uint8_t*)&idx2 + (sizeof(uint64_t) - ref_size), ref_size);
+ }
}
-static int is_ascii_string(char* s, int len)
+static void write_uid(bytearray_t * bplist, uint64_t val)
{
- int ret = 1, i = 0;
- for(i = 0; i < len; i++)
- {
- if ( !isascii( s[i] ) )
- {
- ret = 0;
- break;
- }
- }
- return ret;
+ val = (uint32_t)val;
+ int size = get_needed_bytes(val);
+ uint8_t sz;
+ //do not write 3bytes int node
+ if (size == 3)
+ size++;
+ sz = BPLIST_UID | (size-1); // yes, this is what Apple does...
+
+ val = be64toh(val);
+ byte_array_append(bplist, &sz, 1);
+ byte_array_append(bplist, (uint8_t*)&val + (8-size), size);
}
-uint16_t *plist_utf8_to_utf16(char *unistr, long size, long *items_read, long *items_written)
+static int is_ascii_string(const char* s, size_t len)
{
- uint16_t *outbuf = (uint16_t*)malloc((size+1)*sizeof(uint16_t));
- int p = 0;
- int i = 0;
-
- unsigned char c0;
- unsigned char c1;
- unsigned char c2;
-
- while (i < size) {
- c0 = unistr[i];
- c1 = (i < size-1) ? unistr[i+1] : 0;
- c2 = (i < size-2) ? unistr[i+2] : 0;
- if ((c0 >= 0xE0) && (i < size-2) && (c1 >= 0x80) && (c2 >= 0x80)) {
- // 3 byte sequence
- outbuf[p++] = ((c2 & 0x3F) + ((c1 & 3) << 6)) + (((c1 >> 2) & 15) << 8) + ((c0 & 15) << 12);
- i+=3;
- } else if ((c0 >= 0xC0) && (i < size-1) && (c1 >= 0x80)) {
- // 2 byte sequence
- outbuf[p++] = ((c1 & 0x3F) + ((c0 & 3) << 6)) + (((c0 >> 2) & 7) << 8);
- i+=2;
- } else if (c0 < 0x80) {
- // 1 byte sequence
- outbuf[p++] = c0;
- i+=1;
- } else {
- // invalid character
- fprintf(stderr, "invalid utf8 sequence in string at index %d\n", i);
- break;
- }
- }
- if (items_read) {
- *items_read = i;
- }
- if (items_written) {
- *items_written = p;
- }
- outbuf[p] = 0;
-
- return outbuf;
-
+ int ret = 1;
+ size_t i = 0;
+ for (i = 0; i < len; i++) {
+ if ( !isascii( s[i] ) ) {
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
}
-void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
+plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
{
ptrarray_t* objects = NULL;
hashtable_t* ref_table = NULL;
+ hashtable_t* in_stack = NULL;
struct serialize_s ser_s;
uint8_t offset_size = 0;
- uint8_t dict_param_size = 0;
+ uint8_t ref_size = 0;
uint64_t num_objects = 0;
uint64_t root_object = 0;
uint64_t offset_table_index = 0;
bytearray_t *bplist_buff = NULL;
uint64_t i = 0;
- uint8_t *buff = NULL;
+ uint64_t buff_len = 0;
uint64_t *offsets = NULL;
- uint8_t pad[6] = { 0, 0, 0, 0, 0, 0 };
- uint8_t trailer[BPLIST_TRL_SIZE];
- //for string
- long len = 0;
- long items_read = 0;
- long items_written = 0;
- uint16_t *unicodestr = NULL;
+ bplist_trailer_t trailer;
+ uint64_t objects_len = 0;
//check for valid input
- if (!plist || !plist_bin || *plist_bin || !length)
- return;
+ if (!plist || !plist_bin || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
//list of objects
- objects = ptr_array_new(256);
+ objects = ptr_array_new(4096);
+ if (!objects) {
+ return PLIST_ERR_NO_MEM;
+ }
//hashtable to write only once same nodes
- ref_table = hash_table_new(plist_data_hash, plist_data_compare);
+ ref_table = hash_table_new(plist_data_hash, plist_data_compare, free);
+ if (!ref_table) {
+ ptr_array_free(objects);
+ return PLIST_ERR_NO_MEM;
+ }
+ //hashtable for circular reference detection
+ in_stack = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!in_stack) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ return PLIST_ERR_NO_MEM;
+ }
//serialize plist
ser_s.objects = objects;
ser_s.ref_table = ref_table;
- serialize_plist(plist, &ser_s);
+ ser_s.in_stack = in_stack;
+ plist_err_t err = serialize_plist((node_t)plist, &ser_s, 0);
+ if (err != PLIST_ERR_SUCCESS) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ hash_table_destroy(in_stack);
+ return err;
+ }
+ //no longer needed
+ hash_table_destroy(in_stack);
+ ser_s.in_stack = NULL;
//now stream to output buffer
offset_size = 0; //unknown yet
- dict_param_size = get_needed_bytes(objects->len);
+ objects_len = objects->len;
+ ref_size = get_needed_bytes(objects_len);
num_objects = objects->len;
root_object = 0; //root is first in list
offset_table_index = 0; //unknown yet
+ //figure out the storage size required
+ uint64_t req = 0;
+ for (i = 0; i < num_objects; i++)
+ {
+ node_t node = (node_t)ptr_array_index(objects, i);
+ plist_data_t data = plist_get_data(node);
+ uint64_t size;
+ uint8_t bsize;
+ switch (data->type)
+ {
+ case PLIST_NULL:
+ case PLIST_BOOLEAN:
+ req += 1;
+ break;
+ case PLIST_KEY:
+ case PLIST_STRING:
+ req += 1;
+ if (data->length >= 15) {
+ bsize = get_needed_bytes(data->length);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ if ( is_ascii_string(data->strval, data->length) )
+ {
+ req += data->length;
+ }
+ else
+ {
+ req += data->length * 2;
+ }
+ break;
+ case PLIST_REAL:
+ size = get_real_bytes(data->realval);
+ req += 1;
+ req += size;
+ break;
+ case PLIST_DATE:
+ req += 9;
+ break;
+ case PLIST_ARRAY:
+ size = node_n_children(node);
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += size * ref_size;
+ break;
+ case PLIST_DICT:
+ size = node_n_children(node) / 2;
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += size * 2 * ref_size;
+ break;
+ default:
+ size = data->length;
+ req += 1;
+ if (size >= 15) {
+ bsize = get_needed_bytes(size);
+ if (bsize == 3) bsize = 4;
+ req += 1;
+ req += bsize;
+ }
+ req += data->length;
+ break;
+ }
+ }
+ // add size of magic
+ req += BPLIST_MAGIC_SIZE;
+ req += BPLIST_VERSION_SIZE;
+ // add size of offset table
+ req += get_needed_bytes(req) * num_objects;
+ // add size of trailer
+ req += sizeof(bplist_trailer_t);
+
//setup a dynamic bytes array to store bplist in
- bplist_buff = byte_array_new();
+ bplist_buff = byte_array_new(req);
+ if (!bplist_buff) {
+ ptr_array_free(objects);
+ hash_table_destroy(ref_table);
+ return PLIST_ERR_NO_MEM;
+ }
//set magic number and version
byte_array_append(bplist_buff, BPLIST_MAGIC, BPLIST_MAGIC_SIZE);
@@ -1026,6 +1519,9 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
//write objects and table
offsets = (uint64_t *) malloc(num_objects * sizeof(uint64_t));
+ if (!offsets) {
+ return PLIST_ERR_NO_MEM;
+ }
for (i = 0; i < num_objects; i++)
{
@@ -1034,15 +1530,22 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
switch (data->type)
{
- case PLIST_BOOLEAN:
- buff = (uint8_t *) malloc(sizeof(uint8_t));
- buff[0] = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
- byte_array_append(bplist_buff, buff, sizeof(uint8_t));
- free(buff);
+ case PLIST_NULL: {
+ uint8_t b = 0;
+ byte_array_append(bplist_buff, &b, 1);
break;
-
- case PLIST_UINT:
- write_int(bplist_buff, data->intval);
+ }
+ case PLIST_BOOLEAN: {
+ uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
+ byte_array_append(bplist_buff, &b, 1);
+ break;
+ }
+ case PLIST_INT:
+ if (data->length == 16) {
+ write_uint(bplist_buff, data->intval);
+ } else {
+ write_int(bplist_buff, data->intval);
+ }
break;
case PLIST_REAL:
@@ -1051,28 +1554,29 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
case PLIST_KEY:
case PLIST_STRING:
- len = strlen(data->strval);
- if ( is_ascii_string(data->strval, len) )
+ if ( is_ascii_string(data->strval, data->length) )
{
- write_string(bplist_buff, data->strval);
+ write_string(bplist_buff, data->strval, data->length);
}
else
{
- unicodestr = plist_utf8_to_utf16(data->strval, len, &items_read, &items_written);
- write_unicode(bplist_buff, unicodestr, items_written);
- free(unicodestr);
+ write_unicode(bplist_buff, data->strval, data->length);
}
break;
case PLIST_DATA:
write_data(bplist_buff, data->buff, data->length);
+ break;
case PLIST_ARRAY:
- write_array(bplist_buff, ptr_array_index(objects, i), ref_table, dict_param_size);
+ write_array(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size);
break;
case PLIST_DICT:
- write_dict(bplist_buff, ptr_array_index(objects, i), ref_table, dict_param_size);
+ write_dict(bplist_buff, (node_t)ptr_array_index(objects, i), ref_table, ref_size);
break;
case PLIST_DATE:
- write_date(bplist_buff, data->timeval.tv_sec + (double) data->timeval.tv_usec / 1000000);
+ write_date(bplist_buff, data->realval);
+ break;
+ case PLIST_UID:
+ write_uid(bplist_buff, data->intval);
break;
default:
break;
@@ -1084,43 +1588,31 @@ void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
hash_table_destroy(ref_table);
//write offsets
- offset_size = get_needed_bytes(bplist_buff->len);
+ buff_len = bplist_buff->len;
+ offset_size = get_needed_bytes(buff_len);
offset_table_index = bplist_buff->len;
- for (i = 0; i < num_objects; i++)
- {
- uint8_t *offsetbuff = (uint8_t *) malloc(offset_size);
-
-#if PLIST_BYTE_ORDER == PLIST_BIG_ENDIAN
- offsets[i] = offsets[i] << ((sizeof(uint64_t) - offset_size) * 8);
-#endif
-
- memcpy(offsetbuff, &offsets[i], offset_size);
- byte_convert(offsetbuff, offset_size);
- byte_array_append(bplist_buff, offsetbuff, offset_size);
- free(offsetbuff);
+ for (i = 0; i < num_objects; i++) {
+ uint64_t offset = be64toh(offsets[i]);
+ byte_array_append(bplist_buff, (uint8_t*)&offset + (sizeof(uint64_t) - offset_size), offset_size);
}
-
- //experimental pad to reflect apple's files
- byte_array_append(bplist_buff, pad, 6);
+ free(offsets);
//setup trailer
- num_objects = be64toh(num_objects);
- root_object = be64toh(root_object);
- offset_table_index = be64toh(offset_table_index);
+ memset(trailer.unused, '\0', sizeof(trailer.unused));
+ trailer.offset_size = offset_size;
+ trailer.ref_size = ref_size;
+ trailer.num_objects = be64toh(num_objects);
+ trailer.root_object_index = be64toh(root_object);
+ trailer.offset_table_offset = be64toh(offset_table_index);
- memcpy(trailer + BPLIST_TRL_OFFSIZE_IDX, &offset_size, sizeof(uint8_t));
- memcpy(trailer + BPLIST_TRL_PARMSIZE_IDX, &dict_param_size, sizeof(uint8_t));
- memcpy(trailer + BPLIST_TRL_NUMOBJ_IDX, &num_objects, sizeof(uint64_t));
- memcpy(trailer + BPLIST_TRL_ROOTOBJ_IDX, &root_object, sizeof(uint64_t));
- memcpy(trailer + BPLIST_TRL_OFFTAB_IDX, &offset_table_index, sizeof(uint64_t));
+ byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t));
- byte_array_append(bplist_buff, trailer, BPLIST_TRL_SIZE);
-
- //duplicate buffer
- *plist_bin = (char *) malloc(bplist_buff->len);
- memcpy(*plist_bin, bplist_buff->data, bplist_buff->len);
+ //set output buffer and size
+ *plist_bin = (char*)bplist_buff->data;
*length = bplist_buff->len;
+ bplist_buff->data = NULL; // make sure we don't free the output buffer
byte_array_free(bplist_buff);
- free(offsets);
+
+ return PLIST_ERR_SUCCESS;
}
diff --git a/src/bytearray.c b/src/bytearray.c
index 0abfe49..39fad5f 100644
--- a/src/bytearray.c
+++ b/src/bytearray.c
@@ -21,12 +21,25 @@
#include <string.h>
#include "bytearray.h"
-bytearray_t *byte_array_new()
+#define PAGE_SIZE 4096
+
+bytearray_t *byte_array_new(size_t initial)
{
bytearray_t *a = (bytearray_t*)malloc(sizeof(bytearray_t));
- a->data = malloc(256);
+ a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
+ a->data = malloc(a->capacity);
a->len = 0;
- a->capacity = 256;
+ a->stream = NULL;
+ return a;
+}
+
+bytearray_t *byte_array_new_for_stream(FILE *stream)
+{
+ bytearray_t *a = (bytearray_t*)malloc(sizeof(bytearray_t));
+ a->capacity = (size_t)-1;
+ a->data = NULL;
+ a->len = 0;
+ a->stream = stream;
return a;
}
@@ -39,14 +52,32 @@ void byte_array_free(bytearray_t *ba)
free(ba);
}
+void byte_array_grow(bytearray_t *ba, size_t amount)
+{
+ if (ba->stream) {
+ return;
+ }
+ size_t increase = (amount > PAGE_SIZE) ? (amount+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
+ ba->data = realloc(ba->data, ba->capacity + increase);
+ ba->capacity += increase;
+}
+
void byte_array_append(bytearray_t *ba, void *buf, size_t len)
{
- if (!ba || !ba->data || (len <= 0)) return;
- size_t remaining = ba->capacity-ba->len;
- if (len > remaining) {
- ba->data = realloc(ba->data, ba->capacity + (len - remaining));
- ba->capacity += (len - remaining);
+ if (!ba || (!ba->stream && !ba->data) || (len <= 0)) return;
+ if (ba->stream) {
+ if (fwrite(buf, 1, len, ba->stream) < len) {
+#if DEBUG
+ fprintf(stderr, "ERROR: Failed to write to stream.\n");
+#endif
+ }
+ } else {
+ size_t remaining = ba->capacity-ba->len;
+ if (len > remaining) {
+ size_t needed = len - remaining;
+ byte_array_grow(ba, needed);
+ }
+ memcpy(((char*)ba->data) + ba->len, buf, len);
}
- memcpy(ba->data+ba->len, buf, len);
ba->len += len;
}
diff --git a/src/bytearray.h b/src/bytearray.h
index 3e67d7e..b53e006 100644
--- a/src/bytearray.h
+++ b/src/bytearray.h
@@ -21,16 +21,19 @@
#ifndef BYTEARRAY_H
#define BYTEARRAY_H
#include <stdlib.h>
-#include "common.h"
+#include <stdio.h>
typedef struct bytearray_t {
void *data;
size_t len;
size_t capacity;
+ FILE *stream;
} bytearray_t;
-_PLIST_INTERNAL bytearray_t *byte_array_new();
-_PLIST_INTERNAL void byte_array_free(bytearray_t *ba);
-_PLIST_INTERNAL void byte_array_append(bytearray_t *ba, void *buf, size_t len);
+bytearray_t *byte_array_new(size_t initial);
+bytearray_t *byte_array_new_for_stream(FILE *stream);
+void byte_array_free(bytearray_t *ba);
+void byte_array_grow(bytearray_t *ba, size_t amount);
+void byte_array_append(bytearray_t *ba, void *buf, size_t len);
#endif
diff --git a/src/common.c b/src/common.c
new file mode 100644
index 0000000..810c2e0
--- /dev/null
+++ b/src/common.c
@@ -0,0 +1,115 @@
+/*
+ * common.c
+ * contains some common functions
+ *
+ * Copyright (c) 2026 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <math.h>
+#include <stdio.h>
+#include <stdint.h>
+#include "common.h"
+
+size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ int slen = 0;
+ if (isnan(realval)) {
+ slen = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ slen = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ slen = snprintf(buf, bufsize, "0.0");
+ } else {
+ slen = snprintf(buf, bufsize, "%.*g", 17, realval);
+ if (slen < 0) {
+ return 0;
+ }
+ if (!buf || bufsize == 0) {
+ return (size_t)slen;
+ }
+ size_t len = (size_t)slen;
+ if (len >= bufsize) {
+ len = bufsize - 1;
+ }
+ size_t i = 0;
+ for (i = 0; i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ return len;
+ }
+ if (slen < 0) {
+ return 0;
+ }
+ return (size_t)slen;
+}
+
+/* based on https://stackoverflow.com/a/4143288 */
+#define PO10i_LIMIT (INT64_MAX/10)
+int num_digits_i(int64_t i)
+{
+ int n;
+ int64_t po10;
+ n=1;
+ if (i < 0) {
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
+ n++;
+ }
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10i_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+#undef PO10i_LIMIT
+
+/* based on https://stackoverflow.com/a/4143288 */
+#define PO10u_LIMIT (UINT64_MAX/10)
+int num_digits_u(uint64_t i)
+{
+ int n;
+ uint64_t po10;
+ n=1;
+ po10=10;
+ while (i>=po10) {
+ n++;
+ if (po10 > PO10u_LIMIT) break;
+ po10*=10;
+ }
+ return n;
+}
+#undef PO10u_LIMIT
+
+int plist_real_to_time64(double realval, Time64_T *timev)
+{
+ if (!timev || !isfinite(realval)) {
+ return -1;
+ }
+
+ if (realval < (double)TIME64_MIN - (double)MAC_EPOCH ||
+ realval > (double)TIME64_MAX - (double)MAC_EPOCH) {
+ return -1;
+ }
+
+ *timev = (Time64_T)realval + MAC_EPOCH;
+ return 0;
+}
diff --git a/src/common.h b/src/common.h
index beb4a2b..ffaa78c 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,24 +1,34 @@
+/*
+ * common.h
+ * contains some common functions
+ *
+ * Copyright (c) 2026 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
#ifndef COMMON_H
#define COMMON_H
-#define PLIST_LITTLE_ENDIAN 0
-#define PLIST_BIG_ENDIAN 1
+#include <stddef.h>
+#include "time64.h"
-#ifndef PLIST_BYTE_ORDER
-#if __BIG_ENDIAN__ == 1
-#define PLIST_BYTE_ORDER PLIST_BIG_ENDIAN
-#endif
-#if __LITTLE_ENDIAN__ == 1
-#define PLIST_BYTE_ORDER PLIST_LITTLE_ENDIAN
-#endif
-#endif
+#define MAC_EPOCH 978307200
-#if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__CYGWIN__) && !defined(WIN32)
-# define _PLIST_INTERNAL __attribute__((visibility("hidden")))
-#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
-# define _PLIST_INTERNAL __hidden
-#else /* not gcc >= 4 and not Sun Studio >= 8 */
-# define _PLIST_INTERNAL
-#endif /* GNUC >= 4 */
+size_t dtostr(char *buf, size_t bufsize, double realval);
+int num_digits_i(int64_t i);
+int num_digits_u(uint64_t i);
+int plist_real_to_time64(double realval, Time64_T *timev);
#endif
diff --git a/src/hashtable.c b/src/hashtable.c
index 08ff934..dd6dbfc 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -2,7 +2,7 @@
* hashtable.c
* really simple hash table implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2016 Nikias Bassen, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -20,16 +20,17 @@
*/
#include "hashtable.h"
-hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func)
+hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func, free_func_t free_func)
{
hashtable_t* ht = (hashtable_t*)malloc(sizeof(hashtable_t));
int i;
- for (i = 0; i < 256; i++) {
+ for (i = 0; i < 4096; i++) {
ht->entries[i] = NULL;
}
ht->count = 0;
ht->hash_func = hash_func;
ht->compare_func = compare_func;
+ ht->free_func = free_func;
return ht;
}
@@ -38,11 +39,13 @@ void hash_table_destroy(hashtable_t *ht)
if (!ht) return;
int i = 0;
- for (i = 0; i < 256; i++) {
+ for (i = 0; i < 4096; i++) {
if (ht->entries[i]) {
hashentry_t* e = ht->entries[i];
while (e) {
- free(e->value);
+ if (ht->free_func) {
+ ht->free_func(e->value);
+ }
hashentry_t* old = e;
e = e->next;
free(old);
@@ -58,7 +61,7 @@ void hash_table_insert(hashtable_t* ht, void *key, void *value)
unsigned int hash = ht->hash_func(key);
- int idx0 = hash & 0xFF;
+ int idx0 = hash & 0xFFF;
// get the idx0 list
hashentry_t* e = ht->entries[idx0];
@@ -93,7 +96,7 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
if (!ht || !key) return NULL;
unsigned int hash = ht->hash_func(key);
- int idx0 = hash & 0xFF;
+ int idx0 = hash & 0xFFF;
hashentry_t* e = ht->entries[idx0];
while (e) {
@@ -104,3 +107,34 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
}
return NULL;
}
+
+void hash_table_remove(hashtable_t* ht, void *key)
+{
+ if (!ht || !key) return;
+
+ unsigned int hash = ht->hash_func(key);
+
+ int idx0 = hash & 0xFFF;
+
+ // get the idx0 list
+ hashentry_t* e = ht->entries[idx0];
+ hashentry_t* last = e;
+ while (e) {
+ if (ht->compare_func(e->key, key)) {
+ // found element, remove it from the list
+ hashentry_t* old = e;
+ if (e == ht->entries[idx0]) {
+ ht->entries[idx0] = e->next;
+ } else {
+ last->next = e->next;
+ }
+ if (ht->free_func) {
+ ht->free_func(old->value);
+ }
+ free(old);
+ return;
+ }
+ last = e;
+ e = e->next;
+ }
+}
diff --git a/src/hashtable.h b/src/hashtable.h
index 8c72c82..514cfec 100644
--- a/src/hashtable.h
+++ b/src/hashtable.h
@@ -2,7 +2,7 @@
* hashtable.h
* header file for really simple hash table implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2016 Nikias Bassen, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,28 +21,30 @@
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include <stdlib.h>
-#include "common.h"
typedef struct hashentry_t {
void *key;
void *value;
- void *next;
+ struct hashentry_t *next;
} hashentry_t;
typedef unsigned int(*hash_func_t)(const void* key);
typedef int (*compare_func_t)(const void *a, const void *b);
+typedef void (*free_func_t)(void *ptr);
typedef struct hashtable_t {
- hashentry_t *entries[256];
+ hashentry_t *entries[4096];
size_t count;
hash_func_t hash_func;
compare_func_t compare_func;
+ free_func_t free_func;
} hashtable_t;
-_PLIST_INTERNAL hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func);
-_PLIST_INTERNAL void hash_table_destroy(hashtable_t *ht);
+hashtable_t* hash_table_new(hash_func_t hash_func, compare_func_t compare_func, free_func_t free_func);
+void hash_table_destroy(hashtable_t *ht);
-_PLIST_INTERNAL void hash_table_insert(hashtable_t* ht, void *key, void *value);
-_PLIST_INTERNAL void* hash_table_lookup(hashtable_t* ht, void *key);
+void hash_table_insert(hashtable_t* ht, void *key, void *value);
+void* hash_table_lookup(hashtable_t* ht, void *key);
+void hash_table_remove(hashtable_t* ht, void *key);
#endif
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..c29f760
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,981 @@
+/*
+ * jplist.c
+ * JSON plist implementation
+ *
+ * Copyright (c) 2019-2021 Nikias Bassen All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "jsmn.h"
+#include "hashtable.h"
+#include "base64.h"
+#include "time64.h"
+#include "common.h"
+
+#ifdef DEBUG
+static int plist_json_debug = 0;
+#define PLIST_JSON_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonparser] ERROR: " __VA_ARGS__); }
+#define PLIST_JSON_WRITE_ERR(...) if (plist_json_debug) { fprintf(stderr, "libplist[jsonwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_JSON_ERR(...)
+#define PLIST_JSON_WRITE_ERR(...)
+#endif
+
+void plist_json_init(void)
+{
+ /* init JSON stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_JSON_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_json_debug = 1;
+ }
+#endif
+}
+
+void plist_json_deinit(void)
+{
+ /* deinit JSON stuff */
+}
+
+void plist_json_set_debug(int debug)
+{
+#ifdef DEBUG
+ plist_json_debug = debug;
+#endif
+}
+
+#ifndef HAVE_STRNDUP
+#ifndef _MSC_VER
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+static char* strndup(const char* str, size_t len)
+{
+ char *newstr = (char *)malloc(len+1);
+ if (newstr) {
+ strncpy(newstr, str, len);
+ newstr[len]= '\0';
+ }
+ return newstr;
+}
+#ifndef _MSC_VER
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify, int coerce)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node)
+ return PLIST_ERR_INVALID_ARG;
+
+ node_data = plist_get_data(node);
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ break;
+
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (node_data->length == 16) {
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ const char *charmap[32] = {
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+ "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f",
+ "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
+ };
+ size_t j = 0;
+ size_t len = 0;
+ off_t start = 0;
+ off_t cur = 0;
+
+ str_buf_append(*outbuf, "\"", 1);
+
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ } else if (ch == '"') {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "\\\"", 2);
+ start = cur+1;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+
+ str_buf_append(*outbuf, "\"", 1);
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "[", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "]", 1);
+ } break;
+ case PLIST_DICT: {
+ str_buf_append(*outbuf, "{", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (cnt % 2 == 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_json(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, ":", 1);
+ if (prettify) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "}", 1);
+ } break;
+ case PLIST_DATA:
+ if (coerce) {
+ size_t b64_len = ((node_data->length + 2) / 3) * 4;
+ char *b64_buf = (char*)malloc(b64_len + 1);
+ if (!b64_buf) {
+ return PLIST_ERR_NO_MEM;
+ }
+ size_t actual_len = base64encode(b64_buf, node_data->buff, node_data->length);
+ str_buf_append(*outbuf, "\"", 1);
+ str_buf_append(*outbuf, b64_buf, actual_len);
+ str_buf_append(*outbuf, "\"", 1);
+ free(b64_buf);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_DATE:
+ if (coerce) {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_JSON_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ char datebuf[32];
+ size_t datelen = 0;
+ if (btime) {
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ datelen = strftime(datebuf, sizeof(datebuf), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ }
+ if (datelen <= 0) {
+ datelen = snprintf(datebuf, sizeof(datebuf), "1970-01-01T00:00:00Z");
+ }
+ str_buf_append(*outbuf, "\"", 1);
+ str_buf_append(*outbuf, datebuf, datelen);
+ str_buf_append(*outbuf, "\"", 1);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_UID:
+ if (coerce) {
+ val = (char*)malloc(64);
+ if (node_data->length == 16) {
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+ PLIST_JSON_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, prettify, coerce, visited);
+ if (res < 0) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ if (prettify) {
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ }
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ if (prettify) {
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ }
+ break;
+ default:
+ break;
+ }
+ if (prettify)
+ *size += (depth << 1); // indent for {} and []
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ *size += 2;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ if (coerce) {
+ // base64 encoded string: 2 quotes + ((len+2)/3)*4 base64 chars
+ *size += 2 + ((data->length + 2) / 3) * 4;
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_DATE:
+ if (coerce) {
+ // ISO 8601 string: "YYYY-MM-DDTHH:MM:SSZ" = 22 chars max
+ *size += 24;
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_UID:
+ if (coerce) {
+ // integer representation
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ } else {
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ default:
+ PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, prettify, coerce, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify)
+{
+ plist_write_options_t opts = prettify ? PLIST_OPT_NONE : PLIST_OPT_COMPACT;
+ return plist_to_json_with_options(plist, plist_json, length, opts);
+}
+
+plist_err_t plist_to_json_with_options(plist_t plist, char **plist_json, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !plist_json || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (!PLIST_IS_DICT(plist) && !PLIST_IS_ARRAY(plist)) {
+ PLIST_JSON_WRITE_ERR("plist data is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ }
+
+ int prettify = !(options & PLIST_OPT_COMPACT);
+ int coerce = options & PLIST_OPT_COERCE;
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_JSON_WRITE_ERR("Could not allocate output buffer\n");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = node_to_json((node_t)plist, &outbuf, 0, prettify, coerce);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *plist_json = NULL;
+ *length = 0;
+ return res;
+ }
+ if (prettify) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+
+ str_buf_append(outbuf, "\0", 1);
+
+ *plist_json = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+typedef struct {
+ jsmntok_t* tokens;
+ int count;
+ plist_err_t err;
+} jsmntok_info_t;
+
+static int64_t parse_decimal(const char* str, const char* str_end, char** endp)
+{
+ const uint64_t po10i_limit = INT64_MAX / 10;
+ uint64_t MAX = INT64_MAX;
+ uint64_t x = 0;
+ int is_neg = 0;
+ *endp = (char*)str;
+
+ if (str[0] == '-') {
+ is_neg = 1;
+ (*endp)++;
+ } else if (str[0] == '+') {
+ (*endp)++;
+ }
+ if (is_neg) {
+ MAX++;
+ }
+ while (*endp < str_end && isdigit(**endp)) {
+ if (x > po10i_limit) {
+ x = MAX;
+ break;
+ }
+ x = x * 10;
+ unsigned int add = (**endp - '0');
+ if (x + add > MAX) {
+ x = MAX;
+ break;
+ }
+ x += add;
+ (*endp)++;
+ }
+
+ // swallow the rest of the digits in case we dropped out early
+ while (*endp < str_end && isdigit(**endp)) (*endp)++;
+
+ int64_t result = x;
+ if (is_neg) {
+ if (x == MAX) {
+ result = INT64_MIN;
+ } else {
+ result = -(int64_t)x;
+ }
+ }
+ return result;
+}
+
+static plist_t parse_primitive(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_PRIMITIVE) {
+ PLIST_JSON_ERR("%s: token type != JSMN_PRIMITIVE\n", __func__);
+ return NULL;
+ }
+ plist_t val = NULL;
+ const char* str_val = js + ti->tokens[*index].start;
+ const char* str_end = js + ti->tokens[*index].end;
+ size_t str_len = ti->tokens[*index].end - ti->tokens[*index].start;
+ if (!strncmp("false", str_val, str_len)) {
+ val = plist_new_bool(0);
+ } else if (!strncmp("true", str_val, str_len)) {
+ val = plist_new_bool(1);
+ } else if (!strncmp("null", str_val, str_len)) {
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_JSON_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ val = plist_new_node(data);
+ } else if (isdigit(str_val[0]) || (str_val[0] == '-' && str_val+1 < str_end && isdigit(str_val[1]))) {
+ char* endp = (char*)str_val;
+ int is_neg = (str_val[0] == '-');
+ int64_t intpart = parse_decimal(str_val, str_end, &endp);
+ if (endp >= str_end) {
+ /* integer */
+ if (is_neg || intpart <= INT64_MAX) {
+ val = plist_new_int(intpart);
+ } else {
+ val = plist_new_uint((uint64_t)intpart);
+ }
+ } else if ((*endp == '.' && endp+1 < str_end && isdigit(*(endp+1))) || ((*endp == 'e' || *endp == 'E') && endp+1 < str_end && (isdigit(*(endp+1)) || (((*(endp+1) == '-') || (*(endp+1) == '+')) && endp+2 < str_end && isdigit(*(endp+2)))))) {
+ /* floating point */
+ double dval = (double)intpart;
+ char* fendp = endp;
+ int err = 0;
+ do {
+ if (*endp == '.') {
+ fendp++;
+ double frac = 0;
+ double p = 0.1;
+ while (fendp < str_end && isdigit(*fendp)) {
+ frac = frac + (*fendp - '0') * p;
+ p *= 0.1;
+ fendp++;
+ }
+ if (is_neg) {
+ dval -= frac;
+ } else {
+ dval += frac;
+ }
+ }
+ if (fendp >= str_end) {
+ break;
+ }
+ if (fendp+1 < str_end && (*fendp == 'e' || *fendp == 'E') && (isdigit(*(fendp+1)) || (((*(fendp+1) == '-') || (*(fendp+1) == '+')) && fendp+2 < str_end && isdigit(*(fendp+2))))) {
+ int64_t exp = parse_decimal(fendp+1, str_end, &fendp);
+ dval = dval * pow(10, (double)exp);
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing floating point value\n", __func__, (int)(fendp - js));
+ err++;
+ }
+ } while (0);
+ if (!err) {
+ if (isinf(dval) || isnan(dval)) {
+ PLIST_JSON_ERR("%s: unrepresentable floating point value at offset %d when parsing numerical value\n", __func__, (int)(str_val - js));
+ } else {
+ val = plist_new_real(dval);
+ }
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid character at offset %d when parsing numerical value\n", __func__, (int)(endp - js));
+ }
+ } else {
+ PLIST_JSON_ERR("%s: invalid primitive value '%.*s' encountered\n", __func__, (int)str_len, str_val);
+ }
+ if (!val) {
+ PLIST_JSON_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+ (*index)++;
+ return val;
+}
+
+static char* unescape_string(const char* str_val, size_t str_len, size_t *new_len)
+{
+ char* strval = strndup(str_val, str_len);
+ if (!strval) return NULL;
+ size_t i = 0;
+ while (i < str_len) {
+ if (strval[i] == '\\' && i < str_len-1) {
+ switch (strval[i+1]) {
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ memmove(strval+i, strval+i+1, str_len - (i+1));
+ str_len--;
+ switch (strval[i]) {
+ case 'b':
+ strval[i] = '\b';
+ break;
+ case 'f':
+ strval[i] = '\f';
+ break;
+ case 'r':
+ strval[i] = '\r';
+ break;
+ case 'n':
+ strval[i] = '\n';
+ break;
+ case 't':
+ strval[i] = '\t';
+ break;
+ default:
+ break;
+ }
+ break;
+ case 'u': {
+ unsigned int val = 0;
+ if (str_len-(i+2) < 4) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%s' (too short)\n", __func__, strval+i);
+ free(strval);
+ return NULL;
+ }
+ if (!(isxdigit(strval[i+2]) && isxdigit(strval[i+3]) && isxdigit(strval[i+4]) && isxdigit(strval[i+5])) || sscanf(strval+i+2, "%04x", &val) != 1) {
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 6, strval+i);
+ free(strval);
+ return NULL;
+ }
+ int bytelen = 0;
+ if (val >= 0x800) {
+ /* three bytes */
+ strval[i] = (char)(0xE0 + ((val >> 12) & 0xF));
+ strval[i+1] = (char)(0x80 + ((val >> 6) & 0x3F));
+ strval[i+2] = (char)(0x80 + (val & 0x3F));
+ bytelen = 3;
+ } else if (val >= 0x80) {
+ /* two bytes */
+ strval[i] = (char)(0xC0 + ((val >> 6) & 0x1F));
+ strval[i+1] = (char)(0x80 + (val & 0x3F));
+ bytelen = 2;
+ } else {
+ /* one byte */
+ strval[i] = (char)(val & 0x7F);
+ bytelen = 1;
+ }
+ memmove(strval+i+bytelen, strval+i+6, str_len - (i+5));
+ str_len -= (6-bytelen);
+ } break;
+ default:
+ PLIST_JSON_ERR("%s: invalid escape sequence '%.*s'\n", __func__, 2, strval+i);
+ free(strval);
+ return NULL;
+ }
+ }
+ i++;
+ }
+ strval[str_len] = '\0';
+ if (new_len) {
+ *new_len = str_len;
+ }
+ return strval;
+}
+
+static plist_t parse_string(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_STRING) {
+ PLIST_JSON_ERR("%s: token type != JSMN_STRING\n", __func__);
+ return NULL;
+ }
+
+ size_t str_len = 0; ;
+ char* strval = unescape_string(js + ti->tokens[*index].start, ti->tokens[*index].end - ti->tokens[*index].start, &str_len);
+ if (!strval) {
+ return NULL;
+ }
+ plist_t node;
+
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ free(strval);
+ PLIST_JSON_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_STRING;
+ data->strval = strval;
+ data->length = str_len;
+ node = plist_new_node(data);
+ if (!node) {
+ plist_free_data(data);
+ PLIST_JSON_ERR("%s: failed to create node\n", __func__);
+ return NULL;
+ }
+
+ (*index)++;
+ return node;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth);
+
+static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth)
+{
+ if (ti->tokens[*index].type != JSMN_ARRAY) {
+ PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ti->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+ plist_t arr = plist_new_array();
+ if (!arr) {
+ PLIST_JSON_ERR("%s: failed to create array node\n", __func__);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ size_t num_tokens = ti->tokens[*index].size;
+ size_t num;
+ int j = (*index)+1;
+ for (num = 0; num < num_tokens; num++) {
+ if (j >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(arr);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t val = NULL;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j, depth+1);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j, depth+1);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_array_append_item(arr, val);
+ // if append failed, val still has no parent, free it and abort
+ if (((node_t)val)->parent == NULL) {
+ plist_free(val);
+ plist_free(arr);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ } else {
+ plist_free(arr);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ }
+ *(index) = j;
+ return arr;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index, uint32_t depth)
+{
+ if (ti->tokens[*index].type != JSMN_OBJECT) {
+ PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_JSON_ERR("%s: maximum nesting depth (%u) exceeded\n", __func__, (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ti->err = PLIST_ERR_MAX_NESTING;
+ return NULL;
+ }
+ size_t num_tokens = ti->tokens[*index].size;
+ size_t num;
+ int j = (*index)+1;
+ if (num_tokens % 2 != 0) {
+ PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t obj = plist_new_dict();
+ if (!obj) {
+ PLIST_JSON_ERR("%s: failed to create dict node\n", __func__);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ for (num = 0; num < num_tokens; num++) {
+ if (j+1 >= ti->count) {
+ PLIST_JSON_ERR("%s: token index out of valid range\n", __func__);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (ti->tokens[j].type == JSMN_STRING) {
+ char* key = unescape_string(js + ti->tokens[j].start, ti->tokens[j].end - ti->tokens[j].start, NULL);
+ if (!key) {
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ plist_t val = NULL;
+ j++;
+ num++;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j, depth+1);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j, depth+1);
+ break;
+ case JSMN_STRING:
+ val = parse_string(js, ti, &j);
+ break;
+ case JSMN_PRIMITIVE:
+ val = parse_primitive(js, ti, &j);
+ break;
+ default:
+ break;
+ }
+ if (val) {
+ plist_dict_set_item(obj, key, val);
+ // if set failed, val still has no parent, free it and abort
+ if (((node_t)val)->parent == NULL) {
+ plist_free(val);
+ free(key);
+ plist_free(obj);
+ ti->err = PLIST_ERR_NO_MEM;
+ return NULL;
+ }
+ } else {
+ free(key);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ free(key);
+ } else {
+ PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
+ plist_free(obj);
+ ti->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ }
+ (*index) = j;
+ return obj;
+}
+
+plist_err_t plist_from_json(const char *json, uint32_t length, plist_t * plist)
+{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!json || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ jsmn_parser parser;
+ jsmn_init(&parser);
+ unsigned int maxtoks = 256;
+ unsigned int curtoks = 0;
+ int r = 0;
+ jsmntok_t *tokens = NULL;
+
+ do {
+ jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
+ if (!newtokens) {
+ free(tokens);
+ PLIST_JSON_ERR("%s: Out of memory\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
+ memset((unsigned char*)newtokens + sizeof(jsmntok_t)*curtoks, '\0', sizeof(jsmntok_t)*(maxtoks-curtoks));
+ tokens = newtokens;
+ curtoks = maxtoks;
+
+ r = jsmn_parse(&parser, json, length, tokens, maxtoks);
+ if (r == JSMN_ERROR_NOMEM) {
+ if (maxtoks > (unsigned int)INT_MAX - 16) {
+ free(tokens);
+ return PLIST_ERR_NO_MEM;
+ }
+ maxtoks+=16;
+ continue;
+ } else if (r < 0) {
+ break;
+ }
+ } while (r == JSMN_ERROR_NOMEM);
+
+ switch(r) {
+ case JSMN_ERROR_NOMEM:
+ PLIST_JSON_ERR("%s: Out of memory...\n", __func__);
+ free(tokens);
+ return PLIST_ERR_NO_MEM;
+ case JSMN_ERROR_INVAL:
+ PLIST_JSON_ERR("%s: Invalid character inside JSON string\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ case JSMN_ERROR_PART:
+ PLIST_JSON_ERR("%s: Incomplete JSON, more bytes expected\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ case JSMN_ERROR_LIMIT:
+ PLIST_JSON_ERR("%s: Input data too large\n", __func__);
+ free(tokens);
+ return PLIST_ERR_PARSE;
+ default:
+ break;
+ }
+
+ int startindex = 0;
+ jsmntok_info_t ti = { tokens, parser.toknext, PLIST_ERR_SUCCESS };
+ switch (tokens[startindex].type) {
+ case JSMN_PRIMITIVE:
+ *plist = parse_primitive(json, &ti, &startindex);
+ break;
+ case JSMN_STRING:
+ *plist = parse_string(json, &ti, &startindex);
+ break;
+ case JSMN_ARRAY:
+ *plist = parse_array(json, &ti, &startindex, 0);
+ break;
+ case JSMN_OBJECT:
+ *plist = parse_object(json, &ti, &startindex, 0);
+ break;
+ default:
+ break;
+ }
+ free(tokens);
+ if (!*plist) {
+ return (ti.err != PLIST_ERR_SUCCESS) ? ti.err : PLIST_ERR_PARSE;
+ }
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/jsmn.c b/src/jsmn.c
new file mode 100644
index 0000000..889b8d7
--- /dev/null
+++ b/src/jsmn.c
@@ -0,0 +1,306 @@
+/*
+ * jsmn.c
+ * Simple JSON parser
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ * Updated to use size_t for token offsets and harden against overflows.
+ * (Nikias Bassen, January 2026)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "jsmn.h"
+
+#define JSMN_POS_INVALID ((size_t)SIZE_MAX)
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *tok;
+ if ((unsigned int)parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = JSMN_POS_INVALID;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ size_t start, size_t end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *token;
+ size_t start;
+
+ start = parser->pos;
+
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ default:
+ break;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return JSMN_SUCCESS;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ jsmntok_t *token;
+
+ size_t start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return JSMN_SUCCESS;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\') {
+ parser->pos++;
+ if (parser->end > 0 && parser->pos >= parser->end) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ /* TODO */
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length, jsmntok_t *tokens,
+ unsigned int num_tokens) {
+ jsmnerr_t r;
+ int i;
+ jsmntok_t *token;
+
+ parser->end = length;
+
+ if (num_tokens >= INT_MAX) {
+ return JSMN_ERROR_LIMIT;
+ }
+ if (length > SIZE_MAX / 2) {
+ return JSMN_ERROR_LIMIT;
+ }
+
+ for (; (parser->end > 0 && parser->pos < parser->end) && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ if (parser->pos == SIZE_MAX) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != JSMN_POS_INVALID && token->end == JSMN_POS_INVALID) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, tokens, num_tokens);
+ if (r < 0) return r;
+ if (parser->toksuper != -1)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ':
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, tokens, num_tokens);
+ if (r < 0) return r;
+ if (parser->toksuper != -1)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+
+ }
+ }
+
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != JSMN_POS_INVALID && tokens[i].end == JSMN_POS_INVALID) {
+ return JSMN_ERROR_PART;
+ }
+ }
+
+ return JSMN_SUCCESS;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->end = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
diff --git a/src/jsmn.h b/src/jsmn.h
new file mode 100644
index 0000000..629a0dd
--- /dev/null
+++ b/src/jsmn.h
@@ -0,0 +1,98 @@
+/*
+ * jsmn.h
+ * Simple JSON parser (header file)
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ * Updated to use size_t for token offsets and harden against overflows.
+ * (Nikias Bassen, January 2026)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef __JSMN_H_
+#define __JSMN_H_
+
+#include <stddef.h>
+
+/**
+ * JSON type identifier. Basic types are:
+ * o Object
+ * o Array
+ * o String
+ * o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+ JSMN_PRIMITIVE = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3
+} jsmntype_t;
+
+typedef enum {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3,
+ /* Input exceeds implementation-defined limits */
+ JSMN_ERROR_LIMIT = -4,
+ /* Everything was fine */
+ JSMN_SUCCESS = 0
+} jsmnerr_t;
+
+/**
+ * JSON token description.
+ * @param type type (object, array, string etc.)
+ * @param start start position in JSON data string
+ * @param end end position in JSON data string
+ */
+typedef struct {
+ jsmntype_t type;
+ size_t start;
+ size_t end;
+ size_t size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string
+ */
+typedef struct {
+ size_t pos; /* offset in the JSON string */
+ size_t end; /* offset after last character of JSON string */
+ int toknext; /* next token to allocate */
+ int toksuper; /* superior token node, e.g parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
+ * a single JSON object.
+ */
+jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t length,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#endif /* __JSMN_H_ */
diff --git a/src/libplist++-2.0.pc.in b/src/libplist++-2.0.pc.in
new file mode 100644
index 0000000..79dc315
--- /dev/null
+++ b/src/libplist++-2.0.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE_NAME@++
+Description: C++ binding for @PACKAGE_NAME@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lplist++-2.0
+Cflags: -I${includedir}
+Requires.private: libplist-2.0 >= @PACKAGE_VERSION@
diff --git a/src/libplist-2.0.pc.in b/src/libplist-2.0.pc.in
new file mode 100644
index 0000000..43d9f57
--- /dev/null
+++ b/src/libplist-2.0.pc.in
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE_NAME@
+Description: A library to handle Apple Property Lists whereas they are binary or XML
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lplist-2.0
+Cflags: -I${includedir}
diff --git a/src/oplist.c b/src/oplist.c
new file mode 100644
index 0000000..77a99cd
--- /dev/null
+++ b/src/oplist.c
@@ -0,0 +1,989 @@
+/*
+ * oplist.c
+ * OpenStep plist implementation
+ *
+ * Copyright (c) 2021-2022 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "time64.h"
+#include "hashtable.h"
+#include "common.h"
+
+#ifdef DEBUG
+static int plist_ostep_debug = 0;
+#define PLIST_OSTEP_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepparser] ERROR: " __VA_ARGS__); }
+#define PLIST_OSTEP_WRITE_ERR(...) if (plist_ostep_debug) { fprintf(stderr, "libplist[ostepwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_OSTEP_ERR(...)
+#define PLIST_OSTEP_WRITE_ERR(...)
+#endif
+
+void plist_ostep_init(void)
+{
+ /* init OpenStep stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_OSTEP_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_ostep_debug = 1;
+ }
+#endif
+}
+
+void plist_ostep_deinit(void)
+{
+ /* deinit OpenStep plist stuff */
+}
+
+void plist_ostep_set_debug(int debug)
+{
+#if DEBUG
+ plist_ostep_debug = debug;
+#endif
+}
+
+#ifndef HAVE_STRNDUP
+#ifndef _MSC_VER
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+static char* strndup(const char* str, size_t len)
+{
+ char *newstr = (char *)malloc(len+1);
+ if (newstr) {
+ strncpy(newstr, str, len);
+ newstr[len]= '\0';
+ }
+ return newstr;
+}
+#ifndef _MSC_VER
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+static const char allowed_unquoted_chars[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static int str_needs_quotes(const char* str, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++) {
+ if (!allowed_unquoted_chars[(unsigned char)str[i]]) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static plist_err_t node_to_openstep(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify, int coerce)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node)
+ return PLIST_ERR_INVALID_ARG;
+
+ node_data = plist_get_data(node);
+
+ switch (node_data->type)
+ {
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (node_data->length == 16) {
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ const char *charmap[32] = {
+ "\\U0000", "\\U0001", "\\U0002", "\\U0003", "\\U0004", "\\U0005", "\\U0006", "\\U0007",
+ "\\b", "\\t", "\\n", "\\U000b", "\\f", "\\r", "\\U000e", "\\U000f",
+ "\\U0010", "\\U0011", "\\U0012", "\\U0013", "\\U0014", "\\U0015", "\\U0016", "\\U0017",
+ "\\U0018", "\\U0019", "\\U001a", "\\U001b", "\\U001c", "\\U001d", "\\U001e", "\\U001f",
+ };
+ size_t j = 0;
+ size_t len = 0;
+ off_t start = 0;
+ off_t cur = 0;
+ int needs_quotes;
+
+ len = node_data->length;
+
+ needs_quotes = str_needs_quotes(node_data->strval, len);
+
+ if (needs_quotes) {
+ str_buf_append(*outbuf, "\"", 1);
+ }
+
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ } else if (ch == '"') {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "\\\"", 2);
+ start = cur+1;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+
+ if (needs_quotes) {
+ str_buf_append(*outbuf, "\"", 1);
+ }
+
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "(", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, ")", 1);
+ } break;
+ case PLIST_DICT: {
+ str_buf_append(*outbuf, "{", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ";", 1);
+ }
+ if (cnt % 2 == 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_openstep(ch, outbuf, depth+1, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ if (prettify) {
+ str_buf_append(*outbuf, " = ", 3);
+ } else {
+ str_buf_append(*outbuf, "=", 1);
+ }
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ";", 1);
+ }
+ if (cnt > 0 && prettify) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "}", 1);
+ } break;
+ case PLIST_DATA: {
+ size_t j = 0;
+ size_t len = 0;
+ str_buf_append(*outbuf, "<", 1);
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ char charb[4];
+ if (prettify && j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, ">", 1);
+ } break;
+ case PLIST_BOOLEAN:
+ if (coerce) {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "1", 1);
+ } else {
+ str_buf_append(*outbuf, "0", 1);
+ }
+ } else {
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_NULL:
+ if (coerce) {
+ str_buf_append(*outbuf, "NULL", 4);
+ } else {
+ PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_DATE:
+ if (coerce) {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_OSTEP_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ char datebuf[32];
+ size_t datelen = 0;
+ if (btime) {
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ datelen = strftime(datebuf, sizeof(datebuf), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ }
+ if (datelen <= 0) {
+ datelen = snprintf(datebuf, sizeof(datebuf), "1970-01-01T00:00:00Z");
+ }
+ str_buf_append(*outbuf, "\"", 1);
+ str_buf_append(*outbuf, datebuf, datelen);
+ str_buf_append(*outbuf, "\"", 1);
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_UID:
+ if (coerce) {
+ val = (char*)malloc(64);
+ if (node_data->length == 16) {
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_OSTEP_WRITE_ERR("node tree is nested too deeply\n");
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+ PLIST_OSTEP_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, prettify, coerce, visited);
+ if (res < 0) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children; // number of '=' and ';'
+ if (prettify) {
+ *size += n_children*2; // number of '\n' and extra spaces
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ }
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '(' and ')'
+ *size += n_children-1; // number of ','
+ if (prettify) {
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ }
+ break;
+ default:
+ break;
+ }
+ if (prettify)
+ *size += (depth << 1); // indent for {} and ()
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ *size += 2;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ *size += 2; // < and >
+ *size += data->length*2;
+ if (prettify)
+ *size += data->length/4;
+ break;
+ case PLIST_BOOLEAN:
+ if (coerce) {
+ *size += 1;
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_DATE:
+ if (coerce) {
+ // ISO 8601 string: "YYYY-MM-DDTHH:MM:SSZ" = 22 chars max
+ *size += 24;
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_UID:
+ if (coerce) {
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ case PLIST_NULL:
+ if (coerce) {
+ *size += 4;
+ } else {
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ }
+ break;
+ default:
+ PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify, int coerce)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, prettify, coerce, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify)
+{
+ plist_write_options_t opts = prettify ? PLIST_OPT_NONE : PLIST_OPT_COMPACT;
+ return plist_to_openstep_with_options(plist, openstep, length, opts);
+}
+
+plist_err_t plist_to_openstep_with_options(plist_t plist, char **openstep, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !openstep || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ int prettify = !(options & PLIST_OPT_COMPACT);
+ int coerce = options & PLIST_OPT_COERCE;
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify, coerce);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_OSTEP_WRITE_ERR("Could not allocate output buffer");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = node_to_openstep((node_t)plist, &outbuf, 0, prettify, coerce);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *openstep = NULL;
+ *length = 0;
+ return res;
+ }
+ if (prettify) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+
+ str_buf_append(outbuf, "\0", 1);
+
+ *openstep = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+struct _parse_ctx {
+ const char *start;
+ const char *pos;
+ const char *end;
+ plist_err_t err;
+ uint32_t depth;
+};
+typedef struct _parse_ctx* parse_ctx;
+
+static void parse_skip_ws(parse_ctx ctx)
+{
+ while (ctx->pos < ctx->end) {
+ // skip comments
+ if (*ctx->pos == '/' && (ctx->end - ctx->pos > 1)) {
+ if (*(ctx->pos+1) == '/') {
+ ctx->pos++;
+ while (ctx->pos < ctx->end) {
+ if ((*ctx->pos == '\n') || (*ctx->pos == '\r')) {
+ break;
+ }
+ ctx->pos++;
+ }
+ } else if (*(ctx->pos+1) == '*') {
+ ctx->pos++;
+ while (ctx->pos < ctx->end) {
+ if (*ctx->pos == '*' && (ctx->end - ctx->pos > 1)) {
+ if (*(ctx->pos+1) == '/') {
+ ctx->pos+=2;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ }
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ }
+ // break on any char that's not white space
+ if (!(((*(ctx->pos) == ' ') || (*(ctx->pos) == '\t') || (*(ctx->pos) == '\r') || (*(ctx->pos) == '\n')))) {
+ break;
+ }
+ ctx->pos++;
+ }
+}
+
+#define HEX_DIGIT(x) ((x <= '9') ? (x - '0') : ((x <= 'F') ? (x - 'A' + 10) : (x - 'a' + 10)))
+
+static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist);
+
+static void parse_dict_data(parse_ctx ctx, plist_t dict)
+{
+ plist_t key = NULL;
+ plist_t val = NULL;
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end || *ctx->pos == '}') {
+ break;
+ }
+ key = NULL;
+ ctx->err = node_from_openstep(ctx, &key);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!PLIST_IS_STRING(key)) {
+ PLIST_OSTEP_ERR("Invalid type for dictionary key at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary '=' delimiter at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != '=') {
+ PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ val = NULL;
+ ctx->err = node_from_openstep(ctx, &val);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!val) {
+ PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary item terminator ';' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ';') {
+ PLIST_OSTEP_ERR("Missing terminating ';' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+
+ plist_dict_set_item(dict, plist_get_string_ptr(key, NULL), val);
+ plist_free(key);
+ key = NULL;
+ val = NULL;
+
+ ctx->pos++;
+ }
+ plist_free(key);
+ plist_free(val);
+}
+
+static plist_err_t node_from_openstep(parse_ctx ctx, plist_t *plist)
+{
+ plist_t subnode = NULL;
+ const char *p = NULL;
+ ctx->depth++;
+ if (ctx->depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_OSTEP_ERR("Too many levels of recursion (%u) at offset %ld\n", ctx->depth, (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_MAX_NESTING;
+ return ctx->err;
+ }
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ plist_data_t data = plist_new_plist_data();
+ if (*ctx->pos == '{') {
+ data->type = PLIST_DICT;
+ subnode = plist_new_node(data);
+ ctx->pos++;
+ parse_dict_data(ctx, subnode);
+ if (ctx->err) {
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing dictionary terminator '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != '}') {
+ PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ *plist = subnode;
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '(') {
+ data->type = PLIST_ARRAY;
+ subnode = plist_new_node(data);
+ ctx->pos++;
+ plist_t tmp = NULL;
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end || *ctx->pos == ')') {
+ break;
+ }
+ ctx->err = node_from_openstep(ctx, &tmp);
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ break;
+ }
+ if (!tmp) {
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ plist_array_append_item(subnode, tmp);
+ tmp = NULL;
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing array item delimiter ',' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ',') {
+ break;
+ }
+ ctx->pos++;
+ }
+ plist_free(tmp);
+ tmp = NULL;
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing array terminator ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos != ')') {
+ PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ *plist = subnode;
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '<') {
+ data->type = PLIST_DATA;
+ ctx->pos++;
+ bytearray_t *bytes = byte_array_new(256);
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (*ctx->pos == '>') {
+ break;
+ }
+ if (!isxdigit(*ctx->pos)) {
+ PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ uint8_t b = HEX_DIGIT(*ctx->pos);
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_OSTEP_ERR("Unexpected end of data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ if (!isxdigit(*ctx->pos)) {
+ PLIST_OSTEP_ERR("Invalid byte group in data at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ b = (b << 4) + HEX_DIGIT(*ctx->pos);
+ byte_array_append(bytes, &b, 1);
+ ctx->pos++;
+ }
+ if (ctx->err) {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ goto err_out;
+ }
+ if (ctx->pos >= ctx->end) {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("EOF while parsing data terminator '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != '>') {
+ byte_array_free(bytes);
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Missing terminating '>' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ data->buff = (uint8_t*)bytes->data;
+ data->length = bytes->len;
+ bytes->data = NULL;
+ byte_array_free(bytes);
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else if (*ctx->pos == '"' || *ctx->pos == '\'') {
+ char c = *ctx->pos;
+ ctx->pos++;
+ p = ctx->pos;
+ size_t num_escapes = 0;
+ while (ctx->pos < ctx->end) {
+ if (*ctx->pos == '\\') {
+ num_escapes++;
+ }
+ if ((*ctx->pos == c) && (*(ctx->pos-1) != '\\')) {
+ break;
+ }
+ ctx->pos++;
+ }
+ if (ctx->pos >= ctx->end) {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("EOF while parsing quoted string at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != c) {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Missing closing quote (%c) at offset %ld\n", c, (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t slen = ctx->pos - p;
+ ctx->pos++; // skip the closing quote
+ char* strbuf = (char*)malloc(slen+1);
+ if (num_escapes > 0) {
+ size_t i = 0;
+ size_t o = 0;
+ while (i < slen) {
+ if (p[i] == '\\') {
+ /* handle escape sequence */
+ i++;
+ switch (p[i]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ // max 3 digits octal
+ unsigned char chr = 0;
+ int maxd = 3;
+ while ((i < slen) && (p[i] >= '0' && p[i] <= '7') && --maxd) {
+ chr = (chr << 3) + p[i] - '0';
+ i++;
+ }
+ strbuf[o++] = (char)chr;
+ } break;
+ case 'U': {
+ i++;
+ // max 4 digits hex
+ uint16_t wchr = 0;
+ int maxd = 4;
+ while ((i < slen) && isxdigit(p[i]) && maxd--) {
+ wchr = (wchr << 4) + ((p[i] <= '9') ? (p[i] - '0') : ((p[i] <= 'F') ? (p[i] - 'A' + 10) : (p[i] - 'a' + 10)));
+ i++;
+ }
+ if (wchr >= 0x800) {
+ strbuf[o++] = (char)(0xE0 + ((wchr >> 12) & 0xF));
+ strbuf[o++] = (char)(0x80 + ((wchr >> 6) & 0x3F));
+ strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
+ } else if (wchr >= 0x80) {
+ strbuf[o++] = (char)(0xC0 + ((wchr >> 6) & 0x1F));
+ strbuf[o++] = (char)(0x80 + (wchr & 0x3F));
+ } else {
+ strbuf[o++] = (char)(wchr & 0x7F);
+ }
+ } break;
+ case 'a': strbuf[o++] = '\a'; i++; break;
+ case 'b': strbuf[o++] = '\b'; i++; break;
+ case 'f': strbuf[o++] = '\f'; i++; break;
+ case 'n': strbuf[o++] = '\n'; i++; break;
+ case 'r': strbuf[o++] = '\r'; i++; break;
+ case 't': strbuf[o++] = '\t'; i++; break;
+ case 'v': strbuf[o++] = '\v'; i++; break;
+ case '"': strbuf[o++] = '"'; i++; break;
+ case '\'': strbuf[o++] = '\''; i++; break;
+ default:
+ break;
+ }
+ } else {
+ strbuf[o++] = p[i++];
+ }
+ }
+ strbuf[o] = '\0';
+ slen = o;
+ } else {
+ strncpy(strbuf, p, slen);
+ strbuf[slen] = '\0';
+ }
+ data->type = PLIST_STRING;
+ data->strval = strbuf;
+ data->length = slen;
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else {
+ // unquoted string
+ size_t slen = 0;
+ parse_skip_ws(ctx);
+ p = ctx->pos;
+ while (ctx->pos < ctx->end) {
+ if (!allowed_unquoted_chars[(uint8_t)*ctx->pos]) {
+ break;
+ }
+ ctx->pos++;
+ }
+ slen = ctx->pos-p;
+ if (slen > 0) {
+ data->type = PLIST_STRING;
+ data->strval = strndup(p, slen);
+ data->length = slen;
+ *plist = plist_new_node(data);
+ parse_skip_ws(ctx);
+ break;
+ } else {
+ plist_free_data(data);
+ PLIST_OSTEP_ERR("Unexpected character when parsing unquoted string at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err = PLIST_ERR_PARSE;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ ctx->depth--;
+
+err_out:
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ plist_free(subnode);
+ plist_free(*plist);
+ *plist = NULL;
+ return ctx->err;
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_from_openstep(const char *plist_ostep, uint32_t length, plist_t * plist)
+{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_ostep || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ struct _parse_ctx ctx = { plist_ostep, plist_ostep, plist_ostep + length, 0 , 0 };
+
+ plist_err_t err = node_from_openstep(&ctx, plist);
+ if (err == 0) {
+ if (!*plist) {
+ /* whitespace only file is considered an empty dictionary */
+ *plist = plist_new_dict();
+ } else if (ctx.pos < ctx.end && *ctx.pos == '=') {
+ /* attempt to parse this as 'strings' data */
+ plist_free(*plist);
+ *plist = NULL;
+ plist_t pl = plist_new_dict();
+ ctx.pos = plist_ostep;
+ parse_dict_data(&ctx, pl);
+ if (ctx.err > 0) {
+ plist_free(pl);
+ PLIST_OSTEP_ERR("Failed to parse strings data\n");
+ err = PLIST_ERR_PARSE;
+ } else {
+ *plist = pl;
+ }
+ }
+ }
+
+ return err;
+}
diff --git a/src/out-default.c b/src/out-default.c
new file mode 100644
index 0000000..13b9d9c
--- /dev/null
+++ b/src/out-default.c
@@ -0,0 +1,488 @@
+/*
+ * out-default.c
+ * libplist default *output-only* format - NOT for machine parsing
+ *
+ * Copyright (c) 2022-2023 Nikias Bassen All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "time64.h"
+#include "hashtable.h"
+#include "common.h"
+
+static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent, int partial_data)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+
+ uint32_t i = 0;
+
+ if (!node || !outbuf || !*outbuf) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ node_data = plist_get_data(node);
+ if (!node_data) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ break;
+
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (!val) return PLIST_ERR_NO_MEM;
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ if (!val) return PLIST_ERR_NO_MEM;
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ if (!node_data->strval && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ const char *charmap[32] = {
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+ "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f",
+ "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
+ };
+ size_t j = 0;
+ size_t len = 0;
+ size_t start = 0;
+ size_t cur = 0;
+
+ str_buf_append(*outbuf, "\"", 1);
+
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ } else if (ch == '"') {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "\\\"", 2);
+ start = cur+1;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+
+ str_buf_append(*outbuf, "\"", 1);
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "[", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "]", 1);
+ } break;
+ case PLIST_DICT: {
+ str_buf_append(*outbuf, "{", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, ",", 1);
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent, partial_data);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "}", 1);
+ } break;
+ case PLIST_DATA:
+ {
+ if (!node_data->buff && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ str_buf_append(*outbuf, "<", 1);
+ size_t len = node_data->length;
+ char charb[4];
+ size_t j;
+ if (!partial_data || len <= 24) {
+ for (j = 0; j < len; j++) {
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ } else {
+ for (j = 0; j < 16; j++) {
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (j = len - 8; j < len; j++) {
+ snprintf(charb, sizeof(charb), "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, charb, 2);
+ if (j > 0 && j < len-1 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ str_buf_append(*outbuf, ">", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: Encountered invalid date value %f\n", node_data->realval);
+#endif
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ if (!val) return PLIST_ERR_NO_MEM;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy);
+ if (val_len > 0) {
+ str_buf_append(*outbuf, val, val_len);
+ }
+ free(val);
+ val = NULL;
+ }
+ }
+ break;
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ val = (char*)malloc(64);
+ if (!val) return PLIST_ERR_NO_MEM;
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+#endif
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: circular reference detected\n");
+#endif
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, indent, partial_data, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child
+ *size += indent+1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child
+ *size += indent+1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ *size += ((depth+indent) << 1); // indent for {} and []
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ *size += 2;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ *size += 2; // < and >
+ if (partial_data) {
+ *size += 58;
+ } else {
+ *size += data->length * 2;
+ *size += data->length / 4; // space between 4 byte groups
+ }
+ break;
+ case PLIST_DATE:
+ *size += 25;
+ break;
+ case PLIST_UID:
+ *size += 7; // "CF$UID:"
+ *size += num_digits_u(data->intval);
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "%s: invalid node type encountered\n", __func__);
+#endif
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ if (depth == 0) {
+ *size += 1; // final newline
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, indent, partial_data, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
+{
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+ uint8_t i;
+ for (i = 0; i < indent; i++) {
+ str_buf_append(outbuf, " ", 2);
+ }
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
+ if (res < 0) {
+ return res;
+ }
+ if (!(options & PLIST_OPT_NO_NEWLINE)) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+ return res;
+}
+
+plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !output || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, indent, options & PLIST_OPT_PARTIAL_DATA);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *output = NULL;
+ *length = 0;
+ return res;
+ }
+ str_buf_append(outbuf, "\0", 1);
+
+ *output = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options)
+{
+ if (!plist || !stream) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ strbuf_t *outbuf = str_buf_new_for_stream(stream);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ plist_err_t res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ return res;
+ }
+
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/out-limd.c b/src/out-limd.c
new file mode 100644
index 0000000..83a5e26
--- /dev/null
+++ b/src/out-limd.c
@@ -0,0 +1,469 @@
+/*
+ * out-limd.c
+ * libplist *output-only* format introduced by libimobiledevice/ideviceinfo
+ * - NOT for machine parsing
+ *
+ * Copyright (c) 2022-2023 Nikias Bassen All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "time64.h"
+#include "base64.h"
+#include "hashtable.h"
+#include "common.h"
+
+static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth, uint32_t indent)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+ char buf[16];
+
+ uint32_t i = 0;
+
+ if (!node || !outbuf || !*outbuf) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ node_data = plist_get_data(node);
+ if (!node_data) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "true", 4);
+ } else {
+ str_buf_append(*outbuf, "false", 5);
+ }
+ break;
+ }
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "null", 4);
+ break;
+
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ if (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ if (!node_data->strval && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ const char *charmap[32] = {
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+ "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f",
+ "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
+ };
+
+ size_t j = 0;
+ size_t len = node_data->length;
+ size_t start = 0;
+ size_t cur = 0;
+
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ }
+
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ break;
+ }
+ case PLIST_ARRAY: {
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 || (cnt == 0 && node->parent != NULL)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ slen = snprintf(buf, sizeof(buf), "%u: ", cnt);
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DICT: {
+ node_t ch;
+ uint32_t cnt = 0;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt > 0 && cnt % 2 == 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth+indent; i++) {
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1, indent);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ plist_t valnode = (plist_t)node_next_sibling(ch);
+ if (PLIST_IS_ARRAY(valnode)) {
+ slen = snprintf(buf, sizeof(buf), "[%u]:", plist_array_get_size(valnode));
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, buf, (size_t)slen);
+ } else {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ }
+ cnt++;
+ }
+ break;
+ }
+ case PLIST_DATA:
+ {
+ if (!node_data->buff && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ if (node_data->length == 0) {
+ break;
+ }
+#define BASE64_CHUNK_SIZE 3072
+#define BASE64_BUF_SIZE (4 * ((BASE64_CHUNK_SIZE + 2) / 3) + 4)
+ val = (char*)malloc(BASE64_BUF_SIZE);
+ if (!val) return PLIST_ERR_NO_MEM;
+ size_t done = 0;
+ while (done < node_data->length) {
+ size_t amount = node_data->length - done;
+ if (amount > BASE64_CHUNK_SIZE) {
+ amount = BASE64_CHUNK_SIZE;
+ }
+ size_t bsize = base64encode(val, node_data->buff + done, amount);
+ str_buf_append(*outbuf, val, bsize);
+ done += amount;
+ }
+ free(val);
+#undef BASE64_CHUNK_SIZE
+#undef BASE64_BUF_SIZE
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: Encountered invalid date value %f\n", node_data->realval);
+#endif
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 24);
+ if (!val) return PLIST_ERR_NO_MEM;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ if (val_len > 0) {
+ str_buf_append(*outbuf, val, val_len);
+ }
+ free(val);
+ }
+ }
+ break;
+
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ val = (char*)malloc(64);
+ if (!val) {
+ return PLIST_ERR_NO_MEM;
+ }
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+#endif
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: circular reference detected\n");
+#endif
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, indent, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += n_children-1; // number of ':' and ' '
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+indent+1); // indent for every 2nd child
+ *size += indent+1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+indent+1)<<1); // indent for every child
+ *size += indent+1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? 4 : 5);
+ break;
+ case PLIST_NULL:
+ *size += 4;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 3;
+ break;
+ case PLIST_DATA:
+ *size += (data->length / 3) * 4 + 4;
+ break;
+ case PLIST_DATE:
+ *size += 23;
+ break;
+ case PLIST_UID:
+ *size += 7; // "CF$UID:"
+ *size += num_digits_u(data->intval);
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "%s: invalid node type encountered\n", __func__);
+#endif
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ if (depth == 0) {
+ *size += 1; // final newline
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, indent, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
+{
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+ uint8_t i;
+ for (i = 0; i < indent; i++) {
+ str_buf_append(outbuf, " ", 1);
+ }
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0, indent);
+ if (res < 0) {
+ return res;
+ }
+ if (!(options & PLIST_OPT_NO_NEWLINE)) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+ return res;
+}
+
+plist_err_t plist_write_to_string_limd(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !output || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ uint8_t indent = 0;
+ if (options & PLIST_OPT_INDENT) {
+ indent = (options >> 24) & 0xFF;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, indent);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *output = NULL;
+ *length = 0;
+ return res;
+ }
+ str_buf_append(outbuf, "\0", 1);
+
+ *output = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_write_to_stream_limd(plist_t plist, FILE *stream, plist_write_options_t options)
+{
+ if (!plist || !stream) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ strbuf_t *outbuf = str_buf_new_for_stream(stream);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ plist_err_t res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ return res;
+ }
+
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/out-plutil.c b/src/out-plutil.c
new file mode 100644
index 0000000..e603f31
--- /dev/null
+++ b/src/out-plutil.c
@@ -0,0 +1,477 @@
+/*
+ * out-plutil.c
+ * plutil-like *output-only* format - NOT for machine parsing
+ *
+ * Copyright (c) 2023 Nikias Bassen All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <ctype.h>
+#include <math.h>
+#include <limits.h>
+
+#include <node.h>
+
+#include "plist.h"
+#include "strbuf.h"
+#include "time64.h"
+#include "hashtable.h"
+#include "common.h"
+
+static plist_err_t node_to_string(node_t node, bytearray_t **outbuf, uint32_t depth)
+{
+ plist_data_t node_data = NULL;
+
+ char *val = NULL;
+ int slen = 0;
+ size_t val_len = 0;
+
+ if (!node || !outbuf || !*outbuf) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ node_data = plist_get_data(node);
+ if (!node_data) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ switch (node_data->type)
+ {
+ case PLIST_BOOLEAN:
+ {
+ if (node_data->boolval) {
+ str_buf_append(*outbuf, "1", 1);
+ } else {
+ str_buf_append(*outbuf, "0", 1);
+ }
+ }
+ break;
+
+ case PLIST_NULL:
+ str_buf_append(*outbuf, "<null>", 6);
+ break;
+
+ case PLIST_INT:
+ val = (char*)malloc(64);
+ if (!val) return PLIST_ERR_NO_MEM;
+ if (node_data->length == 16) {
+ slen = snprintf(val, 64, "%" PRIu64, node_data->intval);
+ } else {
+ slen = snprintf(val, 64, "%" PRIi64, node_data->intval);
+ }
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_REAL:
+ val = (char*)malloc(64);
+ if (!val) return PLIST_ERR_NO_MEM;
+ val_len = dtostr(val, 64, node_data->realval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ break;
+
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ if (!node_data->strval && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ const char *charmap[32] = {
+ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
+ "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f",
+ "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
+ "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
+ };
+ size_t j = 0;
+ size_t len = 0;
+ size_t start = 0;
+ size_t cur = 0;
+
+ str_buf_append(*outbuf, "\"", 1);
+
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ unsigned char ch = (unsigned char)node_data->strval[j];
+ if (ch < 0x20) {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, charmap[ch], (charmap[ch][1] == 'u') ? 6 : 2);
+ start = cur+1;
+ } else if (ch == '"') {
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "\\\"", 2);
+ start = cur+1;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+
+ str_buf_append(*outbuf, "\"", 1);
+ } break;
+
+ case PLIST_ARRAY: {
+ str_buf_append(*outbuf, "[", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ uint32_t i;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ char indexbuf[16];
+ slen = snprintf(indexbuf, sizeof(indexbuf), "%u => ", cnt);
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, indexbuf, (size_t)slen);
+ plist_err_t res = node_to_string(ch, outbuf, depth+1);
+ if (res < 0) {
+ return res;
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "]", 1);
+ } break;
+ case PLIST_DICT: {
+ str_buf_append(*outbuf, "{", 1);
+ node_t ch;
+ uint32_t cnt = 0;
+ uint32_t i;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ plist_err_t res = node_to_string(ch, outbuf, depth+1);
+ if (res < 0) {
+ return res;
+ }
+ if (cnt % 2 == 0) {
+ str_buf_append(*outbuf, " => ", 4);
+ }
+ cnt++;
+ }
+ if (cnt > 0) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ }
+ str_buf_append(*outbuf, "}", 1);
+ } break;
+ case PLIST_DATA:
+ {
+ if (!node_data->buff && node_data->length > 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ val = (char*)calloc(1, 48);
+ if (!val) return PLIST_ERR_NO_MEM;
+ size_t len = node_data->length;
+ slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
+ if (slen < 0) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ str_buf_append(*outbuf, val, (size_t)slen);
+ size_t j;
+ if (len <= 24) {
+ for (j = 0; j < len; j++) {
+ snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ } else {
+ for (j = 0; j < 16; j++) {
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (j = len - 8; j < len; j++) {
+ snprintf(val, 4, "%02x", (unsigned char)node_data->buff[j]);
+ str_buf_append(*outbuf, val, 2);
+ if (j > 0 && (j % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ free(val);
+ val = NULL;
+ str_buf_append(*outbuf, "}", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: Encountered invalid date value %f\n", node_data->realval);
+#endif
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ if (!val) return PLIST_ERR_NO_MEM;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, 26, "%Y-%m-%d %H:%M:%S +0000", &_tmcopy);
+ if (val_len > 0) {
+ str_buf_append(*outbuf, val, val_len);
+ }
+ free(val);
+ val = NULL;
+ }
+ }
+ break;
+ case PLIST_UID:
+ {
+#define UID_FMT "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}"
+ slen = snprintf(NULL, 0, UID_FMT, node, node_data, node_data->intval);
+ if (slen < 0) {
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ val = (char*)malloc(val_len + 1);
+ if (!val) return PLIST_ERR_NO_MEM;
+ slen = snprintf(val, val_len+1, UID_FMT, node, node_data, node_data->intval);
+ if (slen < 0 || (size_t)slen > val_len) {
+ free(val);
+ return PLIST_ERR_UNKNOWN;
+ }
+ val_len = (size_t)slen;
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ val = NULL;
+#undef UID_FMT
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+#endif
+ return PLIST_ERR_MAX_NESTING;
+ }
+
+ if (hash_table_lookup(visited, node)) {
+#if DEBUG
+ fprintf(stderr, "libplist: ERROR: circular reference detected\n");
+#endif
+ return PLIST_ERR_CIRCULAR_REF;
+ }
+
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
+
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ unsigned int n_children = node_n_children(node);
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = _node_estimate_size(ch, size, depth + 1, visited);
+ if (res != PLIST_ERR_SUCCESS) {
+ return res;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += 2; // '{' and '}'
+ *size += n_children-1; // number of ':' and ','
+ *size += n_children; // number of '\n' and extra space
+ *size += (uint64_t)n_children * (depth+1); // indent for every 2nd child
+ *size += 1; // additional '\n'
+ break;
+ case PLIST_ARRAY:
+ *size += 2; // '[' and ']'
+ *size += n_children-1; // number of ','
+ *size += n_children; // number of '\n'
+ *size += (uint64_t)n_children * ((depth+1)<<1); // indent for every child
+ *size += 1; // additional '\n'
+ break;
+ default:
+ break;
+ }
+ *size += (depth << 1); // indent for {} and []
+ } else {
+ switch (data->type) {
+ case PLIST_STRING:
+ case PLIST_KEY:
+ *size += data->length;
+ *size += 2;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ break;
+ case PLIST_BOOLEAN:
+ *size += 1;
+ break;
+ case PLIST_NULL:
+ *size += 6;
+ break;
+ case PLIST_DICT:
+ case PLIST_ARRAY:
+ *size += 2;
+ break;
+ case PLIST_DATA:
+ *size = (data->length <= 24) ? 73 : 100;
+ break;
+ case PLIST_DATE:
+ *size += 25;
+ break;
+ case PLIST_UID:
+ *size += 88;
+ break;
+ default:
+#ifdef DEBUG
+ fprintf(stderr, "invalid node type encountered\n");
+#endif
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ if (depth == 0) {
+ *size += 1; // final newline
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, visited);
+ hash_table_destroy(visited);
+ return err;
+}
+
+static plist_err_t _plist_write_to_strbuf(plist_t plist, strbuf_t *outbuf, plist_write_options_t options)
+{
+ plist_err_t res = node_to_string((node_t)plist, &outbuf, 0);
+ if (res < 0) {
+ return res;
+ }
+ if (!(options & PLIST_OPT_NO_NEWLINE)) {
+ str_buf_append(outbuf, "\n", 1);
+ }
+ return res;
+}
+
+plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !output || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0);
+ if (res < 0) {
+ return res;
+ }
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *output = NULL;
+ *length = 0;
+ return res;
+ }
+ str_buf_append(outbuf, "\0", 1);
+
+ *output = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options)
+{
+ if (!plist || !stream) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ strbuf_t *outbuf = str_buf_new_for_stream(stream);
+ if (!outbuf) {
+#if DEBUG
+ fprintf(stderr, "%s: Could not allocate output buffer\n", __func__);
+#endif
+ return PLIST_ERR_NO_MEM;
+ }
+
+ plist_err_t res = _plist_write_to_strbuf(plist, outbuf, options);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ return res;
+ }
+
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/plist.c b/src/plist.c
index 2b31fdd..05af457 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -1,8 +1,10 @@
/*
* plist.c
- * Builds plist XML structures.
+ * Builds plist XML structures
*
- * Copyright (c) 2008 Zach C. All Rights Reserved.
+ * Copyright (c) 2009-2023 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2010-2015 Martin Szulecki, All Rights Reserved.
+ * Copyright (c) 2008 Zach C., All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,77 +21,516 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#define _GNU_SOURCE 1
#include <string.h>
-#include <assert.h>
#include "plist.h"
#include <stdlib.h>
#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+#include <limits.h>
+#include <float.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
#include <node.h>
-#include <node_iterator.h>
+#include <node_list.h>
+#include <hashtable.h>
+#include <ptrarray.h>
+
+#include "common.h"
+
+#ifdef _MSC_VER
+typedef SSIZE_T ssize_t;
+#endif
+
+#ifdef DEBUG
+static int plist_debug = 0;
+#define PLIST_ERR(...) if (plist_debug > 0) { fprintf(stderr, "libplist ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_ERR(...)
+#endif
+
+#ifndef bswap16
+#define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#endif
+
+#ifndef bswap32
+#define bswap32(x) ((((x) & 0xFF000000) >> 24) \
+ | (((x) & 0x00FF0000) >> 8) \
+ | (((x) & 0x0000FF00) << 8) \
+ | (((x) & 0x000000FF) << 24))
+#endif
+
+#ifndef bswap64
+#define bswap64(x) ((((x) & 0xFF00000000000000ull) >> 56) \
+ | (((x) & 0x00FF000000000000ull) >> 40) \
+ | (((x) & 0x0000FF0000000000ull) >> 24) \
+ | (((x) & 0x000000FF00000000ull) >> 8) \
+ | (((x) & 0x00000000FF000000ull) << 8) \
+ | (((x) & 0x0000000000FF0000ull) << 24) \
+ | (((x) & 0x000000000000FF00ull) << 40) \
+ | (((x) & 0x00000000000000FFull) << 56))
+#endif
+
+#ifndef le16toh
+#ifdef __BIG_ENDIAN__
+#define le16toh(x) bswap16(x)
+#else
+#define le16toh(x) (x)
+#endif
+#endif
+
+#ifndef le32toh
+#ifdef __BIG_ENDIAN__
+#define le32toh(x) bswap32(x)
+#else
+#define le32toh(x) (x)
+#endif
+#endif
+
+#ifndef le64toh
+#ifdef __BIG_ENDIAN__
+#define le64toh(x) bswap64(x)
+#else
+#define le64toh(x) (x)
+#endif
+#endif
+
+// Reference: https://stackoverflow.com/a/2390626/1806760
+// Initializer/finalizer sample for MSVC and GCC/Clang.
+// 2010-2016 Joe Lowe. Released into the public domain.
+
+#ifdef __cplusplus
+ #define INITIALIZER(f) \
+ static void f(void); \
+ struct f##_t_ { f##_t_(void) { f(); } }; static f##_t_ f##_; \
+ static void f(void)
+#elif defined(_MSC_VER)
+ #pragma section(".CRT$XCU",read)
+ #define INITIALIZER2_(f,p) \
+ static void f(void); \
+ __declspec(allocate(".CRT$XCU")) void (*f##_)(void) = f; \
+ __pragma(comment(linker,"/include:" p #f "_")) \
+ static void f(void)
+ #ifdef _WIN64
+ #define INITIALIZER(f) INITIALIZER2_(f,"")
+ #else
+ #define INITIALIZER(f) INITIALIZER2_(f,"_")
+ #endif
+#else
+ #define INITIALIZER(f) \
+ static void f(void) __attribute__((__constructor__)); \
+ static void f(void)
+#endif
+
+extern void plist_xml_init(void);
+extern void plist_xml_deinit(void);
+extern void plist_bin_init(void);
+extern void plist_bin_deinit(void);
+extern void plist_json_init(void);
+extern void plist_json_deinit(void);
+extern void plist_ostep_init(void);
+extern void plist_ostep_deinit(void);
+
+static void internal_plist_deinit(void)
+{
+ plist_bin_deinit();
+ plist_xml_deinit();
+ plist_json_deinit();
+ plist_ostep_deinit();
+}
+
+INITIALIZER(internal_plist_init)
+{
+ plist_bin_init();
+ plist_xml_init();
+ plist_json_init();
+ plist_ostep_init();
+ atexit(internal_plist_deinit);
+}
+
+#ifndef HAVE_MEMMEM
+// see https://sourceware.org/legacy-ml/libc-alpha/2007-12/msg00000.html
+
+#ifndef _LIBC
+# define __builtin_expect(expr, val) (expr)
+#endif
+
+#undef memmem
+
+/* Return the first occurrence of NEEDLE in HAYSTACK. */
+void* memmem(const void* haystack, size_t haystack_len, const void* needle, size_t needle_len)
+{
+ /* not really Rabin-Karp, just using additive hashing */
+ char* haystack_ = (char*)haystack;
+ char* needle_ = (char*)needle;
+ int hash = 0; /* this is the static hash value of the needle */
+ int hay_hash = 0; /* rolling hash over the haystack */
+ char* last;
+ size_t i;
+
+ if (haystack_len < needle_len)
+ return NULL;
+
+ if (!needle_len)
+ return haystack_;
+
+ /* initialize hashes */
+ for (i = needle_len; i; --i) {
+ hash += *needle_++;
+ hay_hash += *haystack_++;
+ }
+
+ /* iterate over the haystack */
+ haystack_ = (char*)haystack;
+ needle_ = (char*)needle;
+ last = haystack_+(haystack_len - needle_len + 1);
+ for (; haystack_ < last; ++haystack_) {
+ if (__builtin_expect(hash == hay_hash, 0)
+ && *haystack_ == *needle_ /* prevent calling memcmp, was a optimization from existing glibc */
+ && !memcmp (haystack_, needle_, needle_len)) {
+ return haystack_;
+ }
+ /* roll the hash */
+ hay_hash -= *haystack_;
+ hay_hash += *(haystack_+needle_len);
+ }
+ return NULL;
+}
+#endif
+
+int plist_is_binary(const char *plist_data, uint32_t length)
+{
+ if (plist_data == NULL || length < 8) {
+ return 0;
+ }
+
+ return (memcmp(plist_data, "bplist00", 8) == 0);
+}
+
+#define SKIP_WS(blob, pos, len) \
+ while (pos < len && ((blob[pos] == ' ') || (blob[pos] == '\t') || (blob[pos] == '\r') || (blob[pos] == '\n'))) pos++;
+#define FIND_NEXT(blob, pos, len, chr) \
+ while (pos < len && (blob[pos] != chr)) pos++;
+
+plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format)
+{
+ plist_err_t res = PLIST_ERR_UNKNOWN;
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_data || length == 0) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_format_t fmt = PLIST_FORMAT_NONE;
+ if (format) *format = PLIST_FORMAT_NONE;
+ if (plist_is_binary(plist_data, length)) {
+ res = plist_from_bin(plist_data, length, plist);
+ fmt = PLIST_FORMAT_BINARY;
+ } else {
+ uint32_t pos = 0;
+ int is_json = 0;
+ int is_xml = 0;
+ /* skip whitespace */
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '<' && (length-pos > 3) && !isxdigit(plist_data[pos+1]) && !isxdigit(plist_data[pos+2]) && !isxdigit(plist_data[pos+3])) {
+ is_xml = 1;
+ } else if (plist_data[pos] == '[') {
+ /* only valid for json */
+ is_json = 1;
+ } else if (plist_data[pos] == '(') {
+ /* only valid for openstep */
+ } else if (plist_data[pos] == '{') {
+ /* this could be json or openstep */
+ pos++;
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '"') {
+ /* still could be both */
+ pos++;
+ while (pos < length) {
+ FIND_NEXT(plist_data, pos, length, '"');
+ if (plist_data[pos-1] != '\\') {
+ break;
+ }
+ pos++;
+ }
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == '"') {
+ pos++;
+ SKIP_WS(plist_data, pos, length);
+ if (pos >= length) {
+ return PLIST_ERR_PARSE;
+ }
+ if (plist_data[pos] == ':') {
+ /* this is definitely json */
+ is_json = 1;
+ }
+ }
+ }
+ }
+ if (is_xml) {
+ res = plist_from_xml(plist_data, length, plist);
+ fmt = PLIST_FORMAT_XML;
+ } else if (is_json) {
+ res = plist_from_json(plist_data, length, plist);
+ fmt = PLIST_FORMAT_JSON;
+ } else {
+ res = plist_from_openstep(plist_data, length, plist);
+ fmt = PLIST_FORMAT_OSTEP;
+ }
+ }
+ if (format && res == PLIST_ERR_SUCCESS) {
+ *format = fmt;
+ }
+ return res;
+}
+
+plist_err_t plist_read_from_file(const char *filename, plist_t *plist, plist_format_t *format)
+{
+ if (!filename || !plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ FILE *f = fopen(filename, "rb");
+ if (!f) {
+ return PLIST_ERR_IO;
+ }
+ struct stat fst;
+ fstat(fileno(f), &fst);
+ if ((uint64_t)fst.st_size > UINT32_MAX) {
+ return PLIST_ERR_NO_MEM;
+ }
+ uint32_t total = (uint32_t)fst.st_size;
+ if (total == 0) {
+ return PLIST_ERR_PARSE;
+ }
+ char *buf = (char*)malloc(total);
+ if (!buf) {
+ fclose(f);
+ return PLIST_ERR_NO_MEM;
+ }
+ uint32_t done = 0;
+ while (done < total) {
+ ssize_t r = fread(buf + done, 1, total - done, f);
+ if (r <= 0) {
+ break;
+ }
+ done += r;
+ }
+ fclose(f);
+ if (done < total) {
+ free(buf);
+ return PLIST_ERR_IO;
+ }
+ plist_err_t res = plist_from_memory(buf, total, plist, format);
+ free(buf);
+ return res;
+}
plist_t plist_new_node(plist_data_t data)
{
return (plist_t) node_create(NULL, data);
}
-plist_data_t plist_get_data(const plist_t node)
+plist_data_t plist_get_data(plist_t node)
{
if (!node)
return NULL;
- return ((node_t*)node)->data;
+ return (plist_data_t)((node_t)node)->data;
}
plist_data_t plist_new_plist_data(void)
{
- plist_data_t data = (plist_data_t) calloc(sizeof(struct plist_data_s), 1);
- return data;
+ return (plist_data_t) calloc(1, sizeof(struct plist_data_s));
}
-static void plist_free_data(plist_data_t data)
+static unsigned int dict_key_hash(const void *data)
{
- if (data)
- {
- switch (data->type)
- {
+ plist_data_t keydata = (plist_data_t)data;
+ unsigned int hash = 5381;
+ size_t i;
+ char *str = keydata->strval;
+ for (i = 0; i < keydata->length; str++, i++) {
+ hash = ((hash << 5) + hash) + *str;
+ }
+ return hash;
+}
+
+static int dict_key_compare(const void* a, const void* b)
+{
+ plist_data_t data_a = (plist_data_t)a;
+ plist_data_t data_b = (plist_data_t)b;
+ if (data_a->strval == NULL || data_b->strval == NULL) {
+ return FALSE;
+ }
+ if (data_a->length != data_b->length) {
+ return FALSE;
+ }
+ return (strcmp(data_a->strval, data_b->strval) == 0) ? TRUE : FALSE;
+}
+
+static void _plist_free_data(plist_data_t data)
+{
+ if (!data) return;
+ switch (data->type) {
case PLIST_KEY:
case PLIST_STRING:
free(data->strval);
+ data->strval = NULL;
break;
case PLIST_DATA:
free(data->buff);
+ data->buff = NULL;
+ break;
+ case PLIST_ARRAY:
+ ptr_array_free((ptrarray_t*)data->hashtable);
+ data->hashtable = NULL;
+ break;
+ case PLIST_DICT: {
+ hashtable_t *ht = (hashtable_t*)data->hashtable;
+ // PLIST_DICT hashtables must not own/free values; values are freed via node tree.
+ assert(!ht || ht->free_func == NULL);
+ if (ht) ht->free_func = NULL;
+ hash_table_destroy(ht);
+ data->hashtable = NULL;
break;
+ }
default:
break;
+ }
+}
+
+void plist_free_data(plist_data_t data)
+{
+ if (!data) return;
+ _plist_free_data(data);
+ free(data);
+}
+
+static int plist_free_children(node_t root)
+{
+ if (!root) return NODE_ERR_INVALID_ARG;
+
+ if (!node_first_child(root)) {
+ return NODE_ERR_SUCCESS;
+ }
+
+ size_t cap = 64, sp = 0;
+ node_t *stack = (node_t*)malloc(cap * sizeof(*stack));
+ if (!stack) return NODE_ERR_NO_MEM;
+
+ // Push *direct* children onto the stack, detached from root.
+ for (;;) {
+ node_t ch = node_first_child(root);
+ if (!ch) break;
+
+ int di = node_detach(root, ch);
+ if (di < 0) {
+ free(stack);
+ return di;
}
- free(data);
+
+ if (sp == cap) {
+ cap += 64;
+ node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack));
+ if (!tmp) {
+ free(stack);
+ return NODE_ERR_NO_MEM;
+ }
+ stack = tmp;
+ }
+ stack[sp++] = ch;
}
+
+ // Now free the detached subtree nodes (and their descendants).
+ while (sp) {
+ node_t node = stack[sp - 1];
+ node_t ch = node_first_child(node);
+ if (ch) {
+ int di = node_detach(node, ch);
+ if (di < 0) {
+ free(stack);
+ return di;
+ }
+
+ if (sp == cap) {
+ cap += 64;
+ node_t *tmp = (node_t*)realloc(stack, cap * sizeof(*stack));
+ if (!tmp) {
+ free(stack);
+ return NODE_ERR_NO_MEM;
+ }
+ stack = tmp;
+ }
+ stack[sp++] = ch;
+ continue;
+ }
+
+ plist_data_t data = plist_get_data(node);
+ plist_free_data(data);
+ node->data = NULL;
+
+ node_destroy(node);
+
+ sp--;
+ }
+
+ free(stack);
+ return NODE_ERR_SUCCESS;
}
-static int plist_free_node(node_t* node)
+static int plist_free_node(node_t root)
{
- plist_data_t data = NULL;
- int index = node_detach(node->parent, node);
- data = plist_get_data(node);
- plist_free_data(data);
- node->data = NULL;
+ if (!root) return NODE_ERR_INVALID_ARG;
+
+ int root_index = -1;
+
+ if (root->parent) {
+ root_index = node_detach(root->parent, root);
+ if (root_index < 0) {
+ return root_index;
+ }
+ }
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- plist_free_node(ch);
+ int r = plist_free_children(root);
+ if (r < 0) {
+ // root is already detached; caller should treat as error.
+ return r;
}
- node_iterator_destroy(ni);
- node_destroy(node);
+ plist_data_t data = plist_get_data(root);
+ plist_free_data(data);
+ root->data = NULL;
+
+ node_destroy(root);
- return index;
+ return root_index;
}
plist_t plist_new_dict(void)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DICT;
return plist_new_node(data);
}
@@ -97,6 +538,10 @@ plist_t plist_new_dict(void)
plist_t plist_new_array(void)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_ARRAY;
return plist_new_node(data);
}
@@ -105,24 +550,48 @@ plist_t plist_new_array(void)
static plist_t plist_new_key(const char *val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_KEY;
data->strval = strdup(val);
- data->length = strlen(val);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return NULL;
+ } else {
+ data->length = strlen(val);
+ }
return plist_new_node(data);
}
plist_t plist_new_string(const char *val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_STRING;
data->strval = strdup(val);
- data->length = strlen(val);
+ if (!data->strval) {
+ plist_free_data(data);
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return NULL;
+ } else {
+ data->length = strlen(val);
+ }
return plist_new_node(data);
}
plist_t plist_new_bool(uint8_t val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_BOOLEAN;
data->boolval = val;
data->length = sizeof(uint8_t);
@@ -132,7 +601,37 @@ plist_t plist_new_bool(uint8_t val)
plist_t plist_new_uint(uint64_t val)
{
plist_data_t data = plist_new_plist_data();
- data->type = PLIST_UINT;
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_INT;
+ data->intval = val;
+ data->length = (val > INT_MAX) ? sizeof(uint64_t)*2 : sizeof(uint64_t);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_int(int64_t val)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_INT;
+ data->intval = val;
+ data->length = sizeof(uint64_t);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_uid(uint64_t val)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_UID;
data->intval = val;
data->length = sizeof(uint64_t);
return plist_new_node(data);
@@ -141,6 +640,10 @@ plist_t plist_new_uint(uint64_t val)
plist_t plist_new_real(double val)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_REAL;
data->realval = val;
data->length = sizeof(double);
@@ -150,9 +653,19 @@ plist_t plist_new_real(double val)
plist_t plist_new_data(const char *val, uint64_t length)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATA;
- data->buff = (uint8_t *) malloc(length);
- memcpy(data->buff, val, length);
+ if (val && length) {
+ data->buff = (uint8_t *) malloc(length);
+ if (!data->buff) {
+ PLIST_ERR("%s: failed to allocate %" PRIu64 " bytes\n", __func__, length);
+ return NULL;
+ }
+ memcpy(data->buff, val, length);
+ }
data->length = length;
return plist_new_node(data);
}
@@ -160,10 +673,39 @@ plist_t plist_new_data(const char *val, uint64_t length)
plist_t plist_new_date(int32_t sec, int32_t usec)
{
plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
data->type = PLIST_DATE;
- data->timeval.tv_sec = sec;
- data->timeval.tv_usec = usec;
- data->length = sizeof(struct timeval);
+ data->realval = (double)sec + (double)usec / 1000000;
+ data->length = sizeof(double);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_unix_date(int64_t sec)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_DATE;
+ data->realval = (double)sec - MAC_EPOCH;
+ data->length = sizeof(double);
+ return plist_new_node(data);
+}
+
+plist_t plist_new_null(void)
+{
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_ERR("%s: failed to allocate plist data\n", __func__);
+ return NULL;
+ }
+ data->type = PLIST_NULL;
+ data->intval = 0;
+ data->length = 0;
return plist_new_node(data);
}
@@ -171,70 +713,248 @@ void plist_free(plist_t plist)
{
if (plist)
{
- plist_free_node(plist);
+ plist_free_node((node_t)plist);
}
}
-static void plist_copy_node(node_t *node, void *parent_node_ptr)
+void plist_mem_free(void* ptr)
{
- plist_type node_type = PLIST_NONE;
- plist_t newnode = NULL;
+ if (ptr)
+ {
+ free(ptr);
+ }
+}
+
+static int plist_copy_node_shallow(node_t node, plist_t *out_newnode, plist_data_t *out_newdata, plist_type *out_type)
+{
+ if (!node || !out_newnode || !out_newdata || !out_type) return NODE_ERR_INVALID_ARG;
+
plist_data_t data = plist_get_data(node);
- plist_data_t newdata = plist_new_plist_data();
+ if (!data) return NODE_ERR_INVALID_ARG;
- assert(data); // plist should always have data
+ plist_data_t newdata = plist_new_plist_data();
+ if (!newdata) return NODE_ERR_NO_MEM;
memcpy(newdata, data, sizeof(struct plist_data_s));
- node_type = plist_get_node_type(node);
- if (node_type == PLIST_DATA || node_type == PLIST_STRING || node_type == PLIST_KEY)
- {
- switch (node_type)
- {
+ plist_type node_type = plist_get_node_type(node);
+ switch (node_type) {
case PLIST_DATA:
- newdata->buff = (uint8_t *) malloc(data->length);
- memcpy(newdata->buff, data->buff, data->length);
+ if (data->buff) {
+ newdata->buff = (uint8_t*)malloc(data->length);
+ if (!newdata->buff) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ memcpy(newdata->buff, data->buff, data->length);
+ } else {
+ newdata->buff = NULL;
+ newdata->length = 0;
+ }
break;
+
case PLIST_KEY:
case PLIST_STRING:
- newdata->strval = strdup((char *) data->strval);
+ if (data->strval) {
+ size_t n = strlen(data->strval);
+ newdata->strval = (char*)malloc(n+1);
+ if (!newdata->strval) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ memcpy(newdata->strval, data->strval, n+1);
+ newdata->length = (uint64_t)n;
+ } else {
+ newdata->strval = NULL;
+ newdata->length = 0;
+ }
break;
+
+ case PLIST_ARRAY:
+ if (data->hashtable) {
+ ptrarray_t* pa = ptr_array_new(((ptrarray_t*)data->hashtable)->capacity);
+ if (!pa) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ newdata->hashtable = pa;
+ }
+ break;
+
+ case PLIST_DICT:
+ if (data->hashtable) {
+ hashtable_t* ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
+ if (!ht) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
+ }
+ newdata->hashtable = ht;
+ }
+ break;
+
default:
break;
- }
}
- newnode = plist_new_node(newdata);
- if (*(plist_t*)parent_node_ptr)
- {
- node_attach(*(plist_t*)parent_node_ptr, newnode);
+ plist_t newnode = plist_new_node(newdata);
+ if (!newnode) {
+ plist_free_data(newdata);
+ return NODE_ERR_NO_MEM;
}
- else
- {
- *(plist_t*)parent_node_ptr = newnode;
+
+ *out_newnode = newnode;
+ *out_newdata = newdata;
+ *out_type = node_type;
+ return NODE_ERR_SUCCESS;
+}
+
+static plist_t plist_copy_node(node_t root)
+{
+ typedef struct copy_frame {
+ node_t orig; // original node
+ plist_t copy; // copied node
+ plist_data_t copydata; // copied node's data (for cache updates)
+ plist_type type; // copied node type
+ node_t next_child; // next child of orig to process
+ unsigned int node_index; // child index (for dict key/value odd/even)
+ int depth; // optional depth tracking
+ } copy_frame_t;
+
+ if (!root) return NULL;
+
+ // shallow-copy root first
+ plist_t newroot = NULL;
+ plist_data_t newroot_data = NULL;
+ plist_type newroot_type = PLIST_NONE;
+
+ int r = plist_copy_node_shallow(root, &newroot, &newroot_data, &newroot_type);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // stack of frames
+ size_t cap = 64, sp = 0;
+ copy_frame_t *st = (copy_frame_t*)malloc(cap * sizeof(*st));
+ if (!st) {
+ plist_free_node((node_t)newroot);
+ return NULL;
}
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- plist_copy_node(ch, &newnode);
+ copy_frame_t cf;
+ cf.orig = root;
+ cf.copy = newroot;
+ cf.copydata = newroot_data;
+ cf.type = newroot_type;
+ cf.next_child = node_first_child(root);
+ cf.node_index = 0;
+ cf.depth = 0;
+ st[sp++] = cf;
+
+ while (sp) {
+ copy_frame_t *f = &st[sp - 1];
+
+ if (f->depth > NODE_MAX_DEPTH) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: maximum nesting depth exceeded\n", __func__);
+ return NULL;
+ }
+
+ // done with this node?
+ if (!f->next_child) {
+ sp--;
+ continue;
+ }
+
+ // take next child and advance iterator
+ node_t ch = f->next_child;
+ f->next_child = node_next_sibling(ch);
+
+ // shallow copy child
+ plist_t newch = NULL;
+ plist_data_t newch_data = NULL;
+ plist_type newch_type = PLIST_NONE;
+
+ r = plist_copy_node_shallow(ch, &newch, &newch_data, &newch_type);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: shallow node copy failed (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // attach child to copied parent
+ r = node_attach((node_t)f->copy, (node_t)newch);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)newch);
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: failed to attach child to copied parent (%d)\n", __func__, r);
+ return NULL;
+ }
+
+ // update lookup cache on the *parent* copy
+ switch (f->type) {
+ case PLIST_ARRAY:
+ if (f->copydata->hashtable) {
+ ptr_array_add((ptrarray_t*)f->copydata->hashtable, newch);
+ }
+ break;
+
+ case PLIST_DICT:
+ if (f->copydata->hashtable && (f->node_index % 2 != 0)) {
+ node_t new_key = node_prev_sibling((node_t)newch);
+ if (new_key) {
+ hash_table_insert((hashtable_t*)f->copydata->hashtable, new_key->data, newch);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ f->node_index++;
+
+ // push child frame to process its children
+ if (sp == cap) {
+ cap += 64;
+ copy_frame_t *tmp = (copy_frame_t*)realloc(st, cap * sizeof(*st));
+ if (!tmp) {
+ plist_free_node((node_t)newroot);
+ free(st);
+ PLIST_ERR("%s: out of memory when reallocating\n", __func__);
+ return NULL;
+ }
+ st = tmp;
+ }
+
+ copy_frame_t nf;
+ nf.orig = ch;
+ nf.copy = newch;
+ nf.copydata = newch_data;
+ nf.type = newch_type;
+ nf.next_child = node_first_child(ch);
+ nf.node_index = 0;
+ nf.depth = f->depth + 1;
+ st[sp++] = nf;
}
- node_iterator_destroy(ni);
+
+ free(st);
+ return newroot;
}
plist_t plist_copy(plist_t node)
{
- plist_t copied = NULL;
- plist_copy_node(node, &copied);
- return copied;
+ return node ? plist_copy_node((node_t)node) : NULL;
}
uint32_t plist_array_get_size(plist_t node)
{
uint32_t ret = 0;
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- ret = node_n_children(node);
+ if (PLIST_IS_ARRAY(node)) {
+ ret = node_n_children((node_t)node);
}
return ret;
}
@@ -242,199 +962,695 @@ uint32_t plist_array_get_size(plist_t node)
plist_t plist_array_get_item(plist_t node, uint32_t n)
{
plist_t ret = NULL;
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- ret = (plist_t)node_nth_child(node, n);
+ if (PLIST_IS_ARRAY(node) && n < INT_MAX) {
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ret = (plist_t)ptr_array_index(pa, n);
+ } else {
+ ret = (plist_t)node_nth_child((node_t)node, n);
+ }
}
return ret;
}
uint32_t plist_array_get_item_index(plist_t node)
{
- plist_t father = plist_get_parent(node);
- if (PLIST_ARRAY == plist_get_node_type(father))
- {
- return node_child_position(father, node);
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_ARRAY(parent)) {
+ return node_child_position((node_t)parent, (node_t)node);
}
- return 0;
+ return UINT_MAX;
}
-void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
+static void _plist_array_post_insert(plist_t node, plist_t item, long n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- plist_t old_item = plist_array_get_item(node, n);
- if (old_item)
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ /* store pointer to item in array */
+ ptr_array_insert(pa, item, n);
+ return;
+ }
+
+ if (((node_t)node)->count > 100) {
+ /* make new lookup array */
+ pa = ptr_array_new(128);
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ pa && current;
+ current = (plist_t)node_next_sibling((node_t)current))
+ {
+ ptr_array_add(pa, current);
+ }
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
+ }
+}
+
+static void _plist_array_post_set(plist_t node, plist_t item, long n)
+{
+ ptrarray_t *pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+
+ if (pa) {
+ if (n < 0 || n >= pa->len) {
+ PLIST_ERR("%s: cache index out of range (n=%ld len=%ld)\n", __func__, n, pa->len);
+ return;
+ }
+ ptr_array_set(pa, item, n);
+ return;
+ }
+
+ if (((node_t)node)->count > 100) {
+ pa = ptr_array_new(128);
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ pa && current;
+ current = (plist_t)node_next_sibling((node_t)current))
{
- int idx = plist_free_node(old_item);
- if (idx < 0) {
- node_attach(node, item);
- } else {
- node_insert(node, idx, item);
- }
+ ptr_array_add(pa, current);
+ }
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
+
+ // Now that it exists (and is filled), apply the set (will no-op if out of range)
+ if (pa) {
+ ptr_array_set(pa, item, n);
}
}
- return;
}
-void plist_array_append_item(plist_t node, plist_t item)
+plist_err_t plist_array_set_item(plist_t node, plist_t item, uint32_t n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- node_attach(node, item);
+ if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_t old_item = plist_array_get_item(node, n);
+ if (!old_item) return PLIST_ERR_INVALID_ARG;
+
+ int idx = node_detach((node_t)node, (node_t)old_item);
+ if (idx < 0) {
+ PLIST_ERR("%s: Failed to detach old item (err=%d)\n", __func__, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ int r = node_insert((node_t)node, (unsigned)idx, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ int rb = node_insert((node_t)node, (unsigned)idx, (node_t)old_item);
+ if (rb == NODE_ERR_SUCCESS) {
+ _plist_array_post_set(node, old_item, idx); // restore cache correctly
+ PLIST_ERR("%s: failed to insert replacement (idx=%d err=%d); rollback succeeded\n", __func__, idx, r);
+ return (r == NODE_ERR_NO_MEM) ? PLIST_ERR_NO_MEM : PLIST_ERR_UNKNOWN;
+ } else {
+ PLIST_ERR("%s: insert failed (err=%d) and rollback failed (err=%d); array now missing element at idx=%d\n", __func__, r, rb, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
}
- return;
+
+ _plist_array_post_set(node, item, idx); // update cache
+ plist_free_node((node_t)old_item);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
+plist_err_t plist_array_append_item(plist_t node, plist_t item)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- node_insert(node, n, item);
+ if (!PLIST_IS_ARRAY(node) || !item) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p)\n", __func__, node, item);
+ return PLIST_ERR_INVALID_ARG;
}
- return;
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ int r = node_attach((node_t)node, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: failed to append item (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ _plist_array_post_insert(node, item, -1);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_array_remove_item(plist_t node, uint32_t n)
+plist_err_t plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
{
- if (node && PLIST_ARRAY == plist_get_node_type(node))
- {
- plist_t old_item = plist_array_get_item(node, n);
- if (old_item)
- {
- plist_free(old_item);
+ if (!PLIST_IS_ARRAY(node) || !item || n >= INT_MAX) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, item=%p, n=%u)\n", __func__, node, item, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent; use plist_copy() or detach first");
+ PLIST_ERR("%s: item already has a parent; use plist_copy() or detach first\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ int r = node_insert((node_t)node, n, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ PLIST_ERR("%s: Failed to insert item at index %u (err=%d)\n", __func__, n, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ _plist_array_post_insert(node, item, (long)n);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_array_remove_item(plist_t node, uint32_t n)
+{
+ if (!PLIST_IS_ARRAY(node) || n >= INT_MAX || n > plist_array_get_size(node)) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, n=%u)\n", __func__, node, n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t old_item = plist_array_get_item(node, n);
+ if (!old_item) {
+ PLIST_ERR("item not found at index %u\n", n);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ptr_array_remove(pa, n);
+ }
+ plist_free(old_item);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_array_item_remove(plist_t item)
+{
+ plist_t parent = plist_get_parent(item);
+ if (PLIST_IS_ARRAY(parent)) {
+ int n = node_child_position((node_t)parent, (node_t)item);
+ if (n < 0) return PLIST_ERR_INVALID_ARG;
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)parent)->data)->hashtable;
+ if (pa) {
+ ptr_array_remove(pa, n);
}
+ plist_free(item);
}
- return;
+ return PLIST_ERR_SUCCESS;
+}
+
+typedef struct {
+ node_t cur;
+} plist_array_iter_private;
+
+void plist_array_new_iter(plist_t node, plist_array_iter *iter)
+{
+ if (!iter) return;
+ *iter = NULL;
+ if (!PLIST_IS_ARRAY(node)) return;
+
+ plist_array_iter_private* it = (plist_array_iter_private*)malloc(sizeof(*it));
+ if (!it) return;
+ it->cur = node_first_child((node_t)node);
+ *iter = (plist_array_iter)it;
+}
+
+void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
+{
+ if (item) *item = NULL;
+ if (!iter) return;
+ if (!PLIST_IS_ARRAY(node)) return;
+
+ plist_array_iter_private* it = (plist_array_iter_private*)iter;
+ node_t cur = it->cur;
+ if (!cur) return;
+
+ if (item) {
+ *item = (plist_t)cur;
+ }
+ it->cur = node_next_sibling(cur);
+}
+
+void plist_array_free_iter(plist_array_iter iter)
+{
+ free(iter);
}
uint32_t plist_dict_get_size(plist_t node)
{
uint32_t ret = 0;
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- ret = node_n_children(node) / 2;
+ if (PLIST_IS_DICT(node)) {
+ ret = node_n_children((node_t)node) / 2;
}
return ret;
}
+typedef struct {
+ node_t cur;
+} plist_dict_iter_private;
+
void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
{
- if (iter && *iter == NULL)
- {
- *iter = malloc(sizeof(uint32_t));
- *((uint32_t*)(*iter)) = 0;
- }
- return;
+ if (!iter) return;
+ *iter = NULL;
+ if (!PLIST_IS_DICT(node)) return;
+
+ plist_dict_iter_private* it = (plist_dict_iter_private*)malloc(sizeof(*it));
+ if (!it) return;
+ it->cur = node_first_child((node_t)node);
+ *iter = (plist_dict_iter)it;
}
void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
{
- uint32_t* iter_int = (uint32_t*) iter;
+ if (key) *key = NULL;
+ if (val) *val = NULL;
+ if (!iter) return;
+ if (!PLIST_IS_DICT(node)) return;
- if (key)
- {
- *key = NULL;
- }
- if (val)
- {
- *val = NULL;
- }
+ plist_dict_iter_private* it = (plist_dict_iter_private*)iter;
- if (node && PLIST_DICT == plist_get_node_type(node) && *iter_int < node_n_children(node))
- {
+ node_t k = it->cur;
+ if (!k) return;
- if (key)
- {
- plist_get_key_val((plist_t)node_nth_child(node, *iter_int), key);
- }
+ if (!PLIST_IS_KEY((plist_t)k)) {
+ // malformed dict, terminate iteration
+ it->cur = NULL;
+ return;
+ }
- if (val)
- {
- *val = (plist_t) node_nth_child(node, *iter_int + 1);
- }
+ node_t v = node_next_sibling(k);
+ if (!v) {
+ // key without value, terminate iteration
+ it->cur = NULL;
+ return;
+ }
- *iter_int += 2;
+ if (key) {
+ plist_get_key_val((plist_t)k, key);
}
- return;
+ if (val) {
+ *val = (plist_t)v;
+ }
+ it->cur = node_next_sibling(v);
+}
+
+void plist_dict_free_iter(plist_dict_iter iter)
+{
+ free(iter);
}
void plist_dict_get_item_key(plist_t node, char **key)
{
- plist_t father = plist_get_parent(node);
- if (PLIST_DICT == plist_get_node_type(father))
- {
- plist_get_key_val( (plist_t) node_prev_sibling(node), key);
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_DICT(parent)) {
+ plist_get_key_val( (plist_t) node_prev_sibling((node_t)node), key);
}
}
+plist_t plist_dict_item_get_key(plist_t node)
+{
+ plist_t ret = NULL;
+ plist_t parent = plist_get_parent(node);
+ if (PLIST_IS_DICT(parent)) {
+ ret = (plist_t)node_prev_sibling((node_t)node);
+ }
+ return ret;
+}
+
plist_t plist_dict_get_item(plist_t node, const char* key)
{
plist_t ret = NULL;
+ if (!PLIST_IS_DICT(node) || !key) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p)\n", __func__, node, key);
+ return NULL;
+ }
+ plist_data_t data = plist_get_data(node);
+ if (!data) {
+ PLIST_ERR("%s: invalid node\n", __func__);
+ return NULL;
+ }
+ size_t keylen = strlen(key);
+ hashtable_t *ht = (hashtable_t*)data->hashtable;
+ if (ht) {
+ struct plist_data_s sdata = { 0 };
+ sdata.strval = (char*)key;
+ sdata.length = keylen;
+ return (plist_t)hash_table_lookup(ht, &sdata);
+ } else {
+ plist_t k = NULL;
+ for (k = (plist_t)node_first_child((node_t)node); k; ) {
+ plist_t v = (plist_t)node_next_sibling(k);
+ if (!v) break;
+ data = plist_get_data(k);
+ assert(PLIST_IS_KEY(k));
+ if (!PLIST_IS_KEY(k) || !data || !data->strval) {
+ PLIST_ERR("invalid key node at %p\n", k);
+ break;
+ }
+ if (data->length == keylen && !memcmp(key, data->strval, keylen+1)) {
+ ret = v;
+ break;
+ }
+ k = node_next_sibling(v);
+ }
+ }
+ return ret;
+}
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
+plist_err_t plist_dict_set_item(plist_t node, const char* key, plist_t item)
+{
+ if (!PLIST_IS_DICT(node) || !key || !item) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p, item=%p)\n", __func__, node, key, item);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ node_t it = (node_t)item;
+ if (it->parent != NULL) {
+ assert(it->parent == NULL && "item already has a parent");
+ PLIST_ERR("%s: item already has a parent\n", __func__);
+ return PLIST_ERR_INVALID_ARG;
+ }
- plist_t current = NULL;
- for (current = (plist_t)node_first_child(node);
- current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
- {
+ hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+
+ plist_t old_item = plist_dict_get_item(node, key);
+ plist_t key_node = NULL;
+
+ if (old_item) {
+ // --- REPLACE EXISTING VALUE ---
+ node_t old_val = (node_t)old_item;
+ node_t old_key = node_prev_sibling(old_val);
+ if (!old_key) {
+ PLIST_ERR("%s: corrupt dict (value without key)\n", __func__);
+ return PLIST_ERR_UNKNOWN;
+ }
+ if (!PLIST_IS_KEY((plist_t)old_key)) {
+ PLIST_ERR("%s: corrupt dict ('key' node is not PLIST_KEY\n", __func__);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ // detach old value (do NOT free yet)
+ int idx = node_detach((node_t)node, old_val);
+ if (idx < 0) {
+ PLIST_ERR("%s: failed to detach old value (err=%d)\n", __func__, idx);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ // insert new value at same position
+ int r = node_insert((node_t)node, (unsigned)idx, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ // rollback: reinsert old value
+ int rb = node_insert((node_t)node, (unsigned)idx, old_val);
+ if (rb == NODE_ERR_SUCCESS && ht) {
+ hash_table_insert(ht, ((node_t)old_key)->data, old_item);
+ }
+ PLIST_ERR("%s: failed to replace dict value (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ key_node = old_key;
- plist_data_t data = plist_get_data(current);
- assert( PLIST_KEY == plist_get_node_type(current) );
+ // update hash table
+ if (ht) {
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
+ }
- if (data && !strcmp(key, data->strval))
+ // now it’s safe to free old value
+ plist_free_node(old_val);
+ } else {
+ // --- INSERT NEW KEY/VALUE PAIR ---
+ key_node = plist_new_key(key);
+ if (!key_node) return PLIST_ERR_NO_MEM;
+
+ int r = node_attach((node_t)node, (node_t)key_node);
+ if (r != NODE_ERR_SUCCESS) {
+ plist_free_node((node_t)key_node);
+ PLIST_ERR("%s: failed to attach dict key (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+ r = node_attach((node_t)node, (node_t)item);
+ if (r != NODE_ERR_SUCCESS) {
+ // rollback key insertion
+ node_detach((node_t)node, (node_t)key_node);
+ plist_free_node((node_t)key_node);
+ PLIST_ERR("%s: failed to attach dict value (err=%d)\n", __func__, r);
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ if (ht) {
+ // store pointer to item in hash table
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
+ } else if (((node_t)node)->count > 500) {
+ // make new hash table
+ ht = hash_table_new(dict_key_hash, dict_key_compare, NULL);
+ // calculate the hashes for all entries we have so far
+ plist_t current = NULL;
+ for (current = (plist_t)node_first_child((node_t)node);
+ ht && current;
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
- ret = (plist_t)node_next_sibling(current);
- break;
+ hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current));
}
+ ((plist_data_t)((node_t)node)->data)->hashtable = ht;
}
}
- return ret;
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_set_item(plist_t node, const char* key, plist_t item)
+plist_err_t plist_dict_remove_item(plist_t node, const char* key)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- node_t* old_item = plist_dict_get_item(node, key);
- if (old_item)
- {
- int idx = plist_free_node(old_item);
- if (idx < 0) {
- node_attach(node, item);
- } else {
- node_insert(node, idx, item);
- }
- }
+ if (!PLIST_IS_DICT(node) || !key) {
+ PLIST_ERR("invalid argument passed to %s (node=%p, key=%p)\n", __func__, node, key);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t old_item = plist_dict_get_item(node, key);
+ if (!old_item) {
+ PLIST_ERR("item not found for key '%s'\n", key);
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ plist_t key_node = node_prev_sibling((node_t)old_item);
+ hashtable_t* ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (ht) {
+ hash_table_remove(ht, ((node_t)key_node)->data);
}
- return;
+ plist_free(key_node);
+ plist_free(old_item);
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_insert_item(plist_t node, const char* key, plist_t item)
+plist_err_t plist_dict_merge(plist_t *target, plist_t source)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- node_attach(node, plist_new_key(key));
- node_attach(node, item);
- }
- return;
+ if (!target || !PLIST_IS_DICT(*target) || !PLIST_IS_DICT(source))
+ return PLIST_ERR_INVALID_ARG;
+
+ char* key = NULL;
+ plist_dict_iter it = NULL;
+ plist_t subnode = NULL;
+ plist_dict_new_iter(source, &it);
+ if (!it)
+ return PLIST_ERR_NO_MEM;
+
+ do {
+ plist_dict_next_item(source, it, &key, &subnode);
+ if (!key)
+ break;
+
+ plist_dict_set_item(*target, key, plist_copy(subnode));
+ free(key);
+ key = NULL;
+ } while (1);
+ free(it);
+ return PLIST_ERR_SUCCESS;
}
-void plist_dict_remove_item(plist_t node, const char* key)
+uint8_t plist_dict_get_bool(plist_t dict, const char *key)
{
- if (node && PLIST_DICT == plist_get_node_type(node))
- {
- plist_t old_item = plist_dict_get_item(node, key);
- if (old_item)
- {
- plist_t key_node = node_prev_sibling(old_item);
- plist_free(key_node);
- plist_free(old_item);
- }
- }
- return;
+ uint8_t bval = 0;
+ uint64_t uintval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return 0;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_BOOLEAN:
+ plist_get_bool_val(node, &bval);
+ break;
+ case PLIST_INT:
+ plist_get_uint_val(node, &uintval);
+ bval = (uintval) ? 1 : 0;
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ if (strcmp(strval, "true")) {
+ bval = 1;
+ } else if (strcmp(strval, "false")) {
+ bval = 0;
+ } else {
+ PLIST_ERR("%s: invalid string '%s' for string to boolean conversion\n", __func__, strval);
+ }
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 1) {
+ bval = (strval[0]) ? 1 : 0;
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to boolean conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return bval;
+}
+
+int64_t plist_dict_get_int(plist_t dict, const char *key)
+{
+ int64_t intval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return intval;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_INT:
+ plist_get_int_val(node, &intval);
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ intval = strtoll(strval, NULL, 0);
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 8) {
+ intval = le64toh(*(int64_t*)strval);
+ } else if (strsz == 4) {
+ intval = le32toh(*(int32_t*)strval);
+ } else if (strsz == 2) {
+ intval = le16toh(*(int16_t*)strval);
+ } else if (strsz == 1) {
+ intval = strval[0];
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return intval;
+}
+
+
+uint64_t plist_dict_get_uint(plist_t dict, const char *key)
+{
+ uint64_t uintval = 0;
+ const char *strval = NULL;
+ uint64_t strsz = 0;
+ plist_t node = plist_dict_get_item(dict, key);
+ if (!node) {
+ return uintval;
+ }
+ switch (plist_get_node_type(node)) {
+ case PLIST_INT:
+ plist_get_uint_val(node, &uintval);
+ break;
+ case PLIST_STRING:
+ strval = plist_get_string_ptr(node, NULL);
+ if (strval) {
+ uintval = strtoull(strval, NULL, 0);
+ }
+ break;
+ case PLIST_DATA:
+ strval = (const char*)plist_get_data_ptr(node, &strsz);
+ if (strval) {
+ if (strsz == 8) {
+ uintval = le64toh(*(uint64_t*)strval);
+ } else if (strsz == 4) {
+ uintval = le32toh(*(uint32_t*)strval);
+ } else if (strsz == 2) {
+ uintval = le16toh(*(uint16_t*)strval);
+ } else if (strsz == 1) {
+ uintval = strval[0];
+ } else {
+ PLIST_ERR("%s: invalid size %" PRIu64 " for data to integer conversion\n", __func__, strsz);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return uintval;
+}
+
+plist_err_t plist_dict_copy_item(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_bool(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ uint8_t bval = plist_dict_get_bool(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_bool(bval));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_int(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ int64_t i64val = plist_dict_get_int(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_int(i64val));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_uint(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ if (plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key) == NULL) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ uint64_t u64val = plist_dict_get_uint(source_dict, (alt_source_key) ? alt_source_key : key);
+ plist_dict_set_item(target_dict, key, plist_new_uint(u64val));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_data(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!PLIST_IS_DATA(node)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_dict_copy_string(plist_t target_dict, plist_t source_dict, const char *key, const char *alt_source_key)
+{
+ plist_t node = plist_dict_get_item(source_dict, (alt_source_key) ? alt_source_key : key);
+ if (!PLIST_IS_STRING(node)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_dict_set_item(target_dict, key, plist_copy(node));
+ return PLIST_ERR_SUCCESS;
}
plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
@@ -476,10 +1692,11 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
{
plist_data_t data = NULL;
- if (!node)
+ if (!node || !type || !value || !length)
return;
data = plist_get_data(node);
+ if (!data) return;
*type = data->type;
*length = data->length;
@@ -489,25 +1706,30 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
case PLIST_BOOLEAN:
*((char *) value) = data->boolval;
break;
- case PLIST_UINT:
+ case PLIST_INT:
+ case PLIST_UID:
*((uint64_t *) value) = data->intval;
break;
case PLIST_REAL:
+ case PLIST_DATE:
*((double *) value) = data->realval;
break;
case PLIST_KEY:
case PLIST_STRING:
*((char **) value) = strdup(data->strval);
+ if (!*((char **) value)) {
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return;
+ }
break;
case PLIST_DATA:
*((uint8_t **) value) = (uint8_t *) malloc(*length * sizeof(uint8_t));
+ if (!*((uint8_t **) value)) {
+ PLIST_ERR("%s: malloc failed\n", __func__);
+ return;
+ }
memcpy(*((uint8_t **) value), data->buff, *length * sizeof(uint8_t));
break;
- case PLIST_DATE:
- //exception : here we use memory on the stack since it is just a temporary buffer
- ((struct timeval*) value)->tv_sec = data->timeval.tv_sec;
- ((struct timeval*) value)->tv_usec = data->timeval.tv_usec;
- break;
case PLIST_ARRAY:
case PLIST_DICT:
default:
@@ -517,7 +1739,7 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
plist_t plist_get_parent(plist_t node)
{
- return node ? (plist_t) ((node_t*) node)->parent : NULL;
+ return node ? (plist_t) ((node_t) node)->parent : NULL;
}
plist_type plist_get_node_type(plist_t node)
@@ -533,66 +1755,152 @@ plist_type plist_get_node_type(plist_t node)
void plist_get_key_val(plist_t node, char **val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_KEY == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_KEY != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (!*val)
+ return;
assert(length == strlen(*val));
}
void plist_get_string_val(plist_t node, char **val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_STRING == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_STRING != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (!*val)
+ return;
assert(length == strlen(*val));
}
+const char* plist_get_string_ptr(plist_t node, uint64_t* length)
+{
+ if (!node)
+ return NULL;
+ plist_type type = plist_get_node_type(node);
+ if (PLIST_STRING != type)
+ return NULL;
+ plist_data_t data = plist_get_data(node);
+ if (length)
+ *length = data->length;
+ return (const char*)data->strval;
+}
+
void plist_get_bool_val(plist_t node, uint8_t * val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_BOOLEAN == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_BOOLEAN != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint8_t));
}
void plist_get_uint_val(plist_t node, uint64_t * val)
{
+ if (!node || !val)
+ return;
+ plist_type type = plist_get_node_type(node);
+ uint64_t length = 0;
+ if (PLIST_INT != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
+ assert(length == sizeof(uint64_t) || length == 16);
+}
+
+void plist_get_int_val(plist_t node, int64_t * val)
+{
+ plist_get_uint_val(node, (uint64_t*)val);
+}
+
+void plist_get_uid_val(plist_t node, uint64_t * val)
+{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_UINT == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_UID != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint64_t));
}
void plist_get_real_val(plist_t node, double *val)
{
+ if (!node || !val)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- if (PLIST_REAL == type)
- plist_get_type_and_value(node, &type, (void *) val, &length);
+ if (PLIST_REAL != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(double));
}
void plist_get_data_val(plist_t node, char **val, uint64_t * length)
{
+ if (!node || !val || !length)
+ return;
plist_type type = plist_get_node_type(node);
- if (PLIST_DATA == type)
- plist_get_type_and_value(node, &type, (void *) val, length);
+ if (PLIST_DATA != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) val, length);
+}
+
+const char* plist_get_data_ptr(plist_t node, uint64_t* length)
+{
+ if (!node || !length)
+ return NULL;
+ plist_type type = plist_get_node_type(node);
+ if (PLIST_DATA != type)
+ return NULL;
+ plist_data_t data = plist_get_data(node);
+ *length = data->length;
+ return (const char*)data->buff;
}
void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
{
+ if (!node)
+ return;
plist_type type = plist_get_node_type(node);
uint64_t length = 0;
- struct timeval val = { 0, 0 };
- if (PLIST_DATE == type)
- plist_get_type_and_value(node, &type, (void *) &val, &length);
- assert(length == sizeof(struct timeval));
- *sec = val.tv_sec;
- *usec = val.tv_usec;
+ double val = 0;
+ if (PLIST_DATE != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) &val, &length);
+ assert(length == sizeof(double));
+ if (sec)
+ *sec = (int32_t)val;
+ if (usec)
+ {
+ val = fabs((val - (int64_t)val) * 1000000);
+ *usec = (int32_t)val;
+ }
+}
+
+void plist_get_unix_date_val(plist_t node, int64_t *sec)
+{
+ if (!node || !sec)
+ return;
+ plist_type type = plist_get_node_type(node);
+ uint64_t length = 0;
+ double val = 0;
+ if (PLIST_DATE != type)
+ return;
+ plist_get_type_and_value(node, &type, (void *) &val, &length);
+ assert(length == sizeof(double));
+ *sec = (int64_t)val + MAC_EPOCH;
}
int plist_data_compare(const void *a, const void *b)
@@ -600,57 +1908,56 @@ int plist_data_compare(const void *a, const void *b)
plist_data_t val_a = NULL;
plist_data_t val_b = NULL;
- if (!a || !b)
- return FALSE;
+ if (a == b)
+ return TRUE;
- if (!((node_t*) a)->data || !((node_t*) b)->data)
+ if (!a || !b)
return FALSE;
val_a = plist_get_data((plist_t) a);
val_b = plist_get_data((plist_t) b);
+ if (val_a == NULL && val_b == NULL)
+ return TRUE;
+
+ if (val_a == NULL || val_b == NULL)
+ return FALSE;
+
if (val_a->type != val_b->type)
return FALSE;
switch (val_a->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
- if (val_a->intval == val_b->intval) //it is an union so this is sufficient
- return TRUE;
- else
- return FALSE;
+ case PLIST_DATE:
+ case PLIST_UID:
+ return val_a->length == val_b->length
+ && val_a->intval == val_b->intval; // it is a union so this is sufficient
case PLIST_KEY:
case PLIST_STRING:
- if (!strcmp(val_a->strval, val_b->strval))
- return TRUE;
- else
- return FALSE;
+ if (!val_a->strval || !val_b->strval)
+ return val_a->strval == val_b->strval;
+ return strcmp(val_a->strval, val_b->strval) == 0;
- case PLIST_DATA:
- if (!memcmp(val_a->buff, val_b->buff, val_a->length))
- return TRUE;
- else
+ case PLIST_DATA: {
+ if (val_a->length != val_b->length)
return FALSE;
+ if (val_a->length == 0)
+ return TRUE;
+ return memcmp(val_a->buff, val_b->buff, val_a->length) == 0;
+ }
case PLIST_ARRAY:
case PLIST_DICT:
//compare pointer
- if (a == b)
- return TRUE;
- else
- return FALSE;
- break;
- case PLIST_DATE:
- if (!memcmp(&(val_a->timeval), &(val_b->timeval), sizeof(struct timeval)))
- return TRUE;
- else
- return FALSE;
+ return a == b;
+
default:
- break;
+ return FALSE;
}
- return FALSE;
}
char plist_compare_node_value(plist_t node_l, plist_t node_r)
@@ -658,26 +1965,20 @@ char plist_compare_node_value(plist_t node_l, plist_t node_r)
return plist_data_compare(node_l, node_r);
}
-static void plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length)
+static plist_err_t plist_set_element_val(plist_t node, plist_type type, const void *value, uint64_t length)
{
- //free previous allocated buffer
+ //free previous allocated data
plist_data_t data = plist_get_data(node);
- assert(data); // a node should always have data attached
+ if (!data) { // a node should always have data attached
+ PLIST_ERR("%s: Failed to allocate plist data\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
- switch (data->type)
- {
- case PLIST_KEY:
- case PLIST_STRING:
- free(data->strval);
- data->strval = NULL;
- break;
- case PLIST_DATA:
- free(data->buff);
- data->buff = NULL;
- break;
- default:
- break;
+ if (node_first_child((node_t)node)) {
+ int r = plist_free_children((node_t)node);
+ if (r < 0) return PLIST_ERR_UNKNOWN;
}
+ _plist_free_data(data);
//now handle value
@@ -689,62 +1990,45 @@ static void plist_set_element_val(plist_t node, plist_type type, const void *val
case PLIST_BOOLEAN:
data->boolval = *((char *) value);
break;
- case PLIST_UINT:
+ case PLIST_INT:
+ case PLIST_UID:
data->intval = *((uint64_t *) value);
break;
case PLIST_REAL:
+ case PLIST_DATE:
data->realval = *((double *) value);
break;
case PLIST_KEY:
case PLIST_STRING:
data->strval = strdup((char *) value);
+ if (!data->strval) {
+ PLIST_ERR("%s: strdup failed\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
break;
case PLIST_DATA:
data->buff = (uint8_t *) malloc(length);
+ if (!data->buff) {
+ PLIST_ERR("%s: malloc failed\n", __func__);
+ return PLIST_ERR_NO_MEM;
+ }
memcpy(data->buff, value, length);
break;
- case PLIST_DATE:
- data->timeval.tv_sec = ((struct timeval*) value)->tv_sec;
- data->timeval.tv_usec = ((struct timeval*) value)->tv_usec;
- break;
case PLIST_ARRAY:
case PLIST_DICT:
default:
break;
}
-}
-
-void plist_set_type(plist_t node, plist_type type)
-{
- if ( node_n_children(node) == 0 )
- {
- plist_data_t data = plist_get_data(node);
- plist_free_data( data );
- data = plist_new_plist_data();
- data->type = type;
- switch (type)
- {
- case PLIST_BOOLEAN:
- data->length = sizeof(uint8_t);
- break;
- case PLIST_UINT:
- data->length = sizeof(uint64_t);
- break;
- case PLIST_REAL:
- data->length = sizeof(double);
- break;
- case PLIST_DATE:
- data->length = sizeof(struct timeval);
- break;
- default:
- data->length = 0;
- break;
- }
- }
+ return PLIST_ERR_SUCCESS;
}
void plist_set_key_val(plist_t node, const char *val)
{
+ plist_t parent = plist_get_parent(node);
+ plist_t item = plist_dict_get_item(parent, val);
+ if (item) {
+ return;
+ }
plist_set_element_val(node, PLIST_KEY, val, strlen(val));
}
@@ -760,7 +2044,17 @@ void plist_set_bool_val(plist_t node, uint8_t val)
void plist_set_uint_val(plist_t node, uint64_t val)
{
- plist_set_element_val(node, PLIST_UINT, &val, sizeof(uint64_t));
+ plist_set_element_val(node, PLIST_INT, &val, (val > INT64_MAX) ? sizeof(uint64_t)*2 : sizeof(uint64_t));
+}
+
+void plist_set_int_val(plist_t node, int64_t val)
+{
+ plist_set_element_val(node, PLIST_INT, &val, sizeof(uint64_t));
+}
+
+void plist_set_uid_val(plist_t node, uint64_t val)
+{
+ plist_set_element_val(node, PLIST_UID, &val, sizeof(uint64_t));
}
void plist_set_real_val(plist_t node, double val)
@@ -775,7 +2069,444 @@ void plist_set_data_val(plist_t node, const char *val, uint64_t length)
void plist_set_date_val(plist_t node, int32_t sec, int32_t usec)
{
- struct timeval val = { sec, usec };
- plist_set_element_val(node, PLIST_DATE, &val, sizeof(struct timeval));
+ double val = (double)sec + (double)usec / 1000000;
+ plist_set_element_val(node, PLIST_DATE, &val, sizeof(double));
+}
+
+void plist_set_unix_date_val(plist_t node, int64_t sec)
+{
+ double val = (double)(sec - MAC_EPOCH);
+ plist_set_element_val(node, PLIST_DATE, &val, sizeof(double));
+}
+
+int plist_bool_val_is_true(plist_t boolnode)
+{
+ if (!PLIST_IS_BOOLEAN(boolnode)) {
+ return 0;
+ }
+ uint8_t bv = 0;
+ plist_get_bool_val(boolnode, &bv);
+ return (bv == 1);
+}
+
+int plist_int_val_is_negative(plist_t intnode)
+{
+ if (!PLIST_IS_INT(intnode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(intnode);
+ if (data->length == 16) {
+ return 0;
+ }
+ if ((int64_t)data->intval < 0) {
+ return 1;
+ }
+ return 0;
+}
+
+int plist_int_val_compare(plist_t uintnode, int64_t cmpval)
+{
+ if (!PLIST_IS_INT(uintnode)) {
+ return -1;
+ }
+ int64_t uintval = 0;
+ plist_get_int_val(uintnode, &uintval);
+ if (uintval == cmpval) {
+ return 0;
+ }
+
+ if (uintval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval)
+{
+ if (!PLIST_IS_INT(uintnode)) {
+ return -1;
+ }
+ uint64_t uintval = 0;
+ plist_get_uint_val(uintnode, &uintval);
+ if (uintval == cmpval) {
+ return 0;
+ }
+
+ if (uintval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
+{
+ if (!PLIST_IS_UID(uidnode)) {
+ return -1;
+ }
+ uint64_t uidval = 0;
+ plist_get_uid_val(uidnode, &uidval);
+ if (uidval == cmpval) {
+ return 0;
+ }
+
+ if (uidval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_real_val_compare(plist_t realnode, double cmpval)
+{
+ if (!PLIST_IS_REAL(realnode)) {
+ return -1;
+ }
+ double a = 0;
+ double b = cmpval;
+ plist_get_real_val(realnode, &a);
+ double abs_a = fabs(a);
+ double abs_b = fabs(b);
+ double diff = fabs(a - b);
+ if (a == b) {
+ return 0;
+ }
+
+ if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
+ if (diff < (DBL_EPSILON * DBL_MIN)) {
+ return 0;
+ }
+
+ if (a < b) {
+ return -1;
+ }
+ } else {
+ if ((diff / fmin(abs_a + abs_b, DBL_MAX)) < DBL_EPSILON) {
+ return 0;
+ }
+
+ if (a < b) {
+ return -1;
+ }
+ }
+ return 1;
+}
+
+int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
+{
+ if (!PLIST_IS_DATE(datenode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datenode);
+ assert(data->length == sizeof(double));
+ double val = data->realval;
+ int32_t sec = (int32_t)val;
+ val = fabs((val - (int64_t)val) * 1000000);
+ int32_t usec = (int32_t)val;
+ uint64_t dateval = ((int64_t)sec << 32) | usec;
+ uint64_t cmpval = ((int64_t)cmpsec << 32) | cmpusec;
+ if (dateval == cmpval) {
+ return 0;
+ }
+
+ if (dateval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_unix_date_val_compare(plist_t datenode, int64_t cmpval)
+{
+ if (!PLIST_IS_DATE(datenode)) {
+ return -1;
+ }
+ int64_t dateval = 0;
+ plist_get_unix_date_val(datenode, &dateval);
+ if (dateval == cmpval) {
+ return 0;
+ }
+
+ if (dateval < cmpval) {
+ return -1;
+ }
+
+ return 1;
+}
+
+int plist_string_val_compare(plist_t strnode, const char* cmpval)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return strcmp(data->strval, cmpval);
+}
+
+int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return strncmp(data->strval, cmpval, n);
+}
+
+int plist_string_val_contains(plist_t strnode, const char* substr)
+{
+ if (!PLIST_IS_STRING(strnode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(strnode);
+ return (strstr(data->strval, substr) != NULL);
+}
+
+int plist_key_val_compare(plist_t keynode, const char* cmpval)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return strcmp(data->strval, cmpval);
+}
+
+int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return strncmp(data->strval, cmpval, n);
+}
+
+int plist_key_val_contains(plist_t keynode, const char* substr)
+{
+ if (!PLIST_IS_KEY(keynode)) {
+ return 0;
+ }
+ plist_data_t data = plist_get_data(keynode);
+ return (strstr(data->strval, substr) != NULL);
+}
+
+int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ if (data->length < n) {
+ return -1;
+ }
+
+ if (data->length > n) {
+ return 1;
+ }
+
+ return memcmp(data->buff, cmpval, n);
+}
+
+int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ if (data->length < n) {
+ return -1;
+ }
+ return memcmp(data->buff, cmpval, n);
+}
+
+int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
+{
+ if (!PLIST_IS_DATA(datanode)) {
+ return -1;
+ }
+ plist_data_t data = plist_get_data(datanode);
+ return (memmem(data->buff, data->length, cmpval, n) != NULL);
+}
+
+extern void plist_xml_set_debug(int debug);
+extern void plist_bin_set_debug(int debug);
+extern void plist_json_set_debug(int debug);
+extern void plist_ostep_set_debug(int debug);
+
+void plist_set_debug(int debug)
+{
+#if DEBUG
+ plist_debug = debug;
+#endif
+ plist_xml_set_debug(debug);
+ plist_bin_set_debug(debug);
+ plist_json_set_debug(debug);
+ plist_ostep_set_debug(debug);
+}
+
+void plist_sort(plist_t plist)
+{
+ if (!plist) {
+ return;
+ }
+ if (PLIST_IS_ARRAY(plist)) {
+ uint32_t n = plist_array_get_size(plist);
+ uint32_t i = 0;
+ for (i = 0; i < n; i++) {
+ plist_sort(plist_array_get_item(plist, i));
+ }
+ } else if (PLIST_IS_DICT(plist)) {
+ node_t node = (node_t)plist;
+ node_t ch;
+ if (!node_first_child(node)) {
+ return;
+ }
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ ch = node_next_sibling(ch);
+ plist_sort((plist_t)ch);
+ }
+ #define KEY_DATA(x) (x->data)
+ #define NEXT_KEY(x) (x->next->next)
+ #define KEY_STRVAL(x) ((plist_data_t)(KEY_DATA(x)))->strval
+ int swapped = 0;
+ do {
+ swapped = 0;
+ node_t lptr = NULL;
+ node_t cur_key = node_first_child((node_t)plist);
+
+ while (NEXT_KEY(cur_key) != lptr) {
+ node_t next_key = NEXT_KEY(cur_key);
+ if (strcmp(KEY_STRVAL(cur_key), KEY_STRVAL(next_key)) > 0) {
+ node_t cur_val = cur_key->next;
+ node_t next_val = next_key->next;
+ // we need to swap 2 consecutive nodes with the 2 after them
+ // a -> b -> [c] -> [d] -> [e] -> [f] -> g -> h
+ // cur next
+ // swapped:
+ // a -> b -> [e] -> [f] -> [c] -> [d] -> g -> h
+ // next cur
+ node_t tmp_prev = cur_key->prev;
+ node_t tmp_next = next_val->next;
+ cur_key->prev = next_val;
+ cur_val->next = tmp_next;
+ next_val->next = cur_key;
+ next_key->prev = tmp_prev;
+ if (tmp_prev) {
+ tmp_prev->next = next_key;
+ } else {
+ ((node_t)plist)->children->begin = next_key;
+ }
+ if (tmp_next) {
+ tmp_next->prev = cur_val;
+ } else {
+ ((node_t)plist)->children->end = cur_val;
+ }
+ cur_key = next_key;
+ swapped = 1;
+ }
+ cur_key = NEXT_KEY(cur_key);
+ }
+ lptr = cur_key;
+ } while (swapped);
+ }
}
+plist_err_t plist_write_to_string(plist_t plist, char **output, uint32_t* length, plist_format_t format, plist_write_options_t options)
+{
+ plist_err_t err = PLIST_ERR_UNKNOWN;
+ switch (format) {
+ case PLIST_FORMAT_XML:
+ err = plist_to_xml(plist, output, length);
+ break;
+ case PLIST_FORMAT_JSON:
+ err = plist_to_json_with_options(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep_with_options(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_PRINT:
+ err = plist_write_to_string_default(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_LIMD:
+ err = plist_write_to_string_limd(plist, output, length, options);
+ break;
+ case PLIST_FORMAT_PLUTIL:
+ err = plist_write_to_string_plutil(plist, output, length, options);
+ break;
+ default:
+ // unsupported output format
+ err = PLIST_ERR_FORMAT;
+ break;
+ }
+ return err;
+}
+
+plist_err_t plist_write_to_stream(plist_t plist, FILE *stream, plist_format_t format, plist_write_options_t options)
+{
+ if (!plist || !stream) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ plist_err_t err = PLIST_ERR_UNKNOWN;
+ char *output = NULL;
+ uint32_t length = 0;
+ switch (format) {
+ case PLIST_FORMAT_BINARY:
+ err = plist_to_bin(plist, &output, &length);
+ break;
+ case PLIST_FORMAT_XML:
+ err = plist_to_xml(plist, &output, &length);
+ break;
+ case PLIST_FORMAT_JSON:
+ err = plist_to_json_with_options(plist, &output, &length, options);
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep_with_options(plist, &output, &length, options);
+ break;
+ case PLIST_FORMAT_PRINT:
+ err = plist_write_to_stream_default(plist, stream, options);
+ break;
+ case PLIST_FORMAT_LIMD:
+ err = plist_write_to_stream_limd(plist, stream, options);
+ break;
+ case PLIST_FORMAT_PLUTIL:
+ err = plist_write_to_stream_plutil(plist, stream, options);
+ break;
+ default:
+ // unsupported output format
+ err = PLIST_ERR_FORMAT;
+ break;
+ }
+ if (output && err == PLIST_ERR_SUCCESS) {
+ if (fwrite(output, 1, length, stream) < length) {
+ err = PLIST_ERR_IO;
+ }
+ free(output);
+ }
+ return err;
+}
+
+plist_err_t plist_write_to_file(plist_t plist, const char* filename, plist_format_t format, plist_write_options_t options)
+{
+ if (!plist || !filename) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ FILE* f = fopen(filename, "wb");
+ if (!f) {
+ return PLIST_ERR_IO;
+ }
+ plist_err_t err = plist_write_to_stream(plist, f, format, options);
+ fclose(f);
+ return err;
+}
+
+void plist_print(plist_t plist)
+{
+ plist_write_to_stream(plist, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_PARTIAL_DATA);
+}
+
+const char* libplist_version()
+{
+#ifndef PACKAGE_VERSION
+#error PACKAGE_VERSION is not defined!
+#endif
+ return PACKAGE_VERSION;
+}
diff --git a/src/plist.h b/src/plist.h
index 2a9a3b5..7228696 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -22,18 +22,44 @@
#ifndef PLIST_H
#define PLIST_H
-#include "plist/plist.h"
-#include "common.h"
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include <sys/types.h>
#include <sys/stat.h>
-#include <sys/time.h>
#ifdef _MSC_VER
#pragma warning(disable:4996)
#pragma warning(disable:4244)
+#include <winsock2.h>
+#else
+#include <sys/time.h>
#endif
+#ifdef LIBPLIST_STATIC
+ #define PLIST_API
+#elif defined(_WIN32)
+ #define PLIST_API __declspec( dllexport )
+#else
+ #if __GNUC__ >= 4
+ #define PLIST_API __attribute__((visibility("default")))
+ #else
+ #define PLIST_API
+ #endif
+#endif
+
+#include "node.h"
+
+#ifndef PLIST_MAX_NESTING_DEPTH
+#ifdef NODE_MAX_DEPTH
+#define PLIST_MAX_NESTING_DEPTH NODE_MAX_DEPTH
+#else
+#define PLIST_MAX_NESTING_DEPTH 512
+#endif
+#endif
+
+#include "plist/plist.h"
struct plist_data_s
{
@@ -44,7 +70,7 @@ struct plist_data_s
double realval;
char *strval;
uint8_t *buff;
- struct timeval timeval;
+ void *hashtable;
};
uint64_t length;
plist_type type;
@@ -52,10 +78,30 @@ struct plist_data_s
typedef struct plist_data_s *plist_data_t;
-_PLIST_INTERNAL plist_t plist_new_node(plist_data_t data);
-_PLIST_INTERNAL plist_data_t plist_get_data(const plist_t node);
-_PLIST_INTERNAL plist_data_t plist_new_plist_data(void);
-_PLIST_INTERNAL int plist_data_compare(const void *a, const void *b);
+plist_t plist_new_node(plist_data_t data);
+plist_data_t plist_get_data(plist_t node);
+plist_data_t plist_new_plist_data(void);
+void plist_free_data(plist_data_t data);
+int plist_data_compare(const void *a, const void *b);
+
+extern plist_err_t plist_write_to_string_default(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_string_limd(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_string_plutil(plist_t plist, char **output, uint32_t* length, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_default(plist_t plist, FILE *stream, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_limd(plist_t plist, FILE *stream, plist_write_options_t options);
+extern plist_err_t plist_write_to_stream_plutil(plist_t plist, FILE *stream, plist_write_options_t options);
+static inline unsigned int plist_node_ptr_hash(const void *ptr)
+{
+ uintptr_t h = (uintptr_t)ptr;
+ h ^= (h >> 16);
+ h *= 0x85ebca6b;
+ return (unsigned int)h;
+}
+
+static inline int plist_node_ptr_compare(const void *a, const void *b)
+{
+ return a == b;
+}
#endif
diff --git a/src/ptrarray.c b/src/ptrarray.c
index 8567752..3a11031 100644
--- a/src/ptrarray.c
+++ b/src/ptrarray.c
@@ -2,7 +2,7 @@
* ptrarray.c
* simple pointer array implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2019 Nikias Bassen, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,13 +19,14 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ptrarray.h"
+#include <string.h>
ptrarray_t *ptr_array_new(int capacity)
{
ptrarray_t *pa = (ptrarray_t*)malloc(sizeof(ptrarray_t));
pa->pdata = (void**)malloc(sizeof(void*) * capacity);
pa->capacity = capacity;
- pa->capacity_step = (capacity > 64) ? 64 : capacity;
+ pa->capacity_step = (capacity > 4096) ? 4096 : capacity;
pa->len = 0;
return pa;
}
@@ -39,23 +40,57 @@ void ptr_array_free(ptrarray_t *pa)
free(pa);
}
-void ptr_array_add(ptrarray_t *pa, void *data)
+void ptr_array_insert(ptrarray_t *pa, void *data, long array_index)
{
- if (!pa || !pa->pdata || !data) return;
- size_t remaining = pa->capacity-pa->len;
+ if (!pa || !pa->pdata) return;
+ long remaining = pa->capacity-pa->len;
if (remaining == 0) {
- pa->pdata = realloc(pa->pdata, sizeof(void*) * (pa->capacity + pa->capacity_step));
+ pa->pdata = (void**)realloc(pa->pdata, sizeof(void*) * (pa->capacity + pa->capacity_step));
pa->capacity += pa->capacity_step;
}
- pa->pdata[pa->len] = data;
+ if (array_index < 0 || array_index >= pa->len) {
+ pa->pdata[pa->len] = data;
+ } else {
+ memmove(&pa->pdata[array_index+1], &pa->pdata[array_index], (pa->len-array_index) * sizeof(void*));
+ pa->pdata[array_index] = data;
+ }
pa->len++;
}
-void* ptr_array_index(ptrarray_t *pa, size_t index)
+void ptr_array_add(ptrarray_t *pa, void *data)
+{
+ ptr_array_insert(pa, data, -1);
+}
+
+void ptr_array_remove(ptrarray_t *pa, long array_index)
+{
+ if (!pa || !pa->pdata || array_index < 0) return;
+ if (pa->len == 0 || array_index >= pa->len) return;
+ if (pa->len == 1) {
+ pa->pdata[0] = NULL;
+ } else {
+ memmove(&pa->pdata[array_index], &pa->pdata[array_index+1], (pa->len-array_index-1) * sizeof(void*));
+ }
+ pa->len--;
+}
+
+void ptr_array_set(ptrarray_t *pa, void *data, long array_index)
+{
+ if (!pa || !pa->pdata || array_index < 0) return;
+ if (pa->len == 0 || array_index >= pa->len) return;
+ pa->pdata[array_index] = data;
+}
+
+void* ptr_array_index(ptrarray_t *pa, long array_index)
{
if (!pa) return NULL;
- if (index >= pa->len) {
+ if (array_index < 0 || array_index >= pa->len) {
return NULL;
}
- return pa->pdata[index];
+ return pa->pdata[array_index];
+}
+
+long ptr_array_size(ptrarray_t *pa)
+{
+ return pa->len;
}
diff --git a/src/ptrarray.h b/src/ptrarray.h
index 84f9ef0..ed67351 100644
--- a/src/ptrarray.h
+++ b/src/ptrarray.h
@@ -2,7 +2,7 @@
* ptrarray.h
* header file for simple pointer array implementation
*
- * Copyright (c) 2011 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2011-2019 Nikias Bassen, All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,17 +21,20 @@
#ifndef PTRARRAY_H
#define PTRARRAY_H
#include <stdlib.h>
-#include "common.h"
typedef struct ptrarray_t {
void **pdata;
- size_t len;
- size_t capacity;
- size_t capacity_step;
+ long len;
+ long capacity;
+ long capacity_step;
} ptrarray_t;
-_PLIST_INTERNAL ptrarray_t *ptr_array_new(int capacity);
-_PLIST_INTERNAL void ptr_array_free(ptrarray_t *pa);
-_PLIST_INTERNAL void ptr_array_add(ptrarray_t *pa, void *data);
-_PLIST_INTERNAL void* ptr_array_index(ptrarray_t *pa, size_t index);
+ptrarray_t *ptr_array_new(int capacity);
+void ptr_array_free(ptrarray_t *pa);
+void ptr_array_add(ptrarray_t *pa, void *data);
+void ptr_array_insert(ptrarray_t *pa, void *data, long index);
+void ptr_array_remove(ptrarray_t *pa, long index);
+void ptr_array_set(ptrarray_t *pa, void *data, long index);
+void* ptr_array_index(ptrarray_t *pa, long index);
+long ptr_array_size(ptrarray_t *pa);
#endif
diff --git a/src/strbuf.h b/src/strbuf.h
new file mode 100644
index 0000000..2fbfe93
--- /dev/null
+++ b/src/strbuf.h
@@ -0,0 +1,35 @@
+/*
+ * strbuf.h
+ * header file for simple string buffer, using the bytearray as underlying
+ * structure.
+ *
+ * Copyright (c) 2016 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef STRBUF_H
+#define STRBUF_H
+#include <stdlib.h>
+#include "bytearray.h"
+
+typedef struct bytearray_t strbuf_t;
+
+#define str_buf_new(__sz) byte_array_new(__sz)
+#define str_buf_new_for_stream(__stream) byte_array_new_for_stream(__stream)
+#define str_buf_free(__ba) byte_array_free(__ba)
+#define str_buf_grow(__ba, __am) byte_array_grow(__ba, __am)
+#define str_buf_append(__ba, __str, __len) byte_array_append(__ba, (void*)(__str), __len)
+
+#endif
diff --git a/src/time64.c b/src/time64.c
new file mode 100644
index 0000000..218088e
--- /dev/null
+++ b/src/time64.c
@@ -0,0 +1,804 @@
+/*
+
+Copyright (c) 2007-2010 Michael G Schwern
+
+This software originally derived from Paul Sheer's pivotal_gmtime_r.c.
+
+The MIT License:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+/*
+
+Programmers who have available to them 64-bit time values as a 'long
+long' type can use localtime64_r() and gmtime64_r() which correctly
+converts the time even on 32-bit systems. Whether you have 64-bit time
+values will depend on the operating system.
+
+localtime64_r() is a 64-bit equivalent of localtime_r().
+
+gmtime64_r() is a 64-bit equivalent of gmtime_r().
+
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include "time64.h"
+#include "time64_limits.h"
+
+
+static const char days_in_month[2][12] = {
+ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+};
+
+static const short julian_days_by_month[2][12] = {
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
+};
+
+static const char wday_name[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char mon_name[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static const short length_of_year[2] = { 365, 366 };
+
+/* Some numbers relating to the gregorian cycle */
+static const Year years_in_gregorian_cycle = 400;
+#define days_in_gregorian_cycle ((365 * 400) + 100 - 4 + 1)
+static const Time64_T seconds_in_gregorian_cycle = days_in_gregorian_cycle * 60LL * 60LL * 24LL;
+
+/* Year range we can trust the time funcitons with */
+#define MAX_SAFE_YEAR 2037
+#define MIN_SAFE_YEAR 1971
+
+/* 28 year Julian calendar cycle */
+#define SOLAR_CYCLE_LENGTH 28
+
+/* Year cycle from MAX_SAFE_YEAR down. */
+static const short safe_years_high[SOLAR_CYCLE_LENGTH] = {
+ 2016, 2017, 2018, 2019,
+ 2020, 2021, 2022, 2023,
+ 2024, 2025, 2026, 2027,
+ 2028, 2029, 2030, 2031,
+ 2032, 2033, 2034, 2035,
+ 2036, 2037, 2010, 2011,
+ 2012, 2013, 2014, 2015
+};
+
+/* Year cycle from MIN_SAFE_YEAR up */
+static const int safe_years_low[SOLAR_CYCLE_LENGTH] = {
+ 1996, 1997, 1998, 1971,
+ 1972, 1973, 1974, 1975,
+ 1976, 1977, 1978, 1979,
+ 1980, 1981, 1982, 1983,
+ 1984, 1985, 1986, 1987,
+ 1988, 1989, 1990, 1991,
+ 1992, 1993, 1994, 1995,
+};
+
+/* This isn't used, but it's handy to look at */
+#if 0
+static const char dow_year_start[SOLAR_CYCLE_LENGTH] = {
+ 5, 0, 1, 2, /* 0 2016 - 2019 */
+ 3, 5, 6, 0, /* 4 */
+ 1, 3, 4, 5, /* 8 1996 - 1998, 1971*/
+ 6, 1, 2, 3, /* 12 1972 - 1975 */
+ 4, 6, 0, 1, /* 16 */
+ 2, 4, 5, 6, /* 20 2036, 2037, 2010, 2011 */
+ 0, 2, 3, 4 /* 24 2012, 2013, 2014, 2015 */
+};
+#endif
+
+/* Let's assume people are going to be looking for dates in the future.
+ Let's provide some cheats so you can skip ahead.
+ This has a 4x speed boost when near 2008.
+*/
+/* Number of days since epoch on Jan 1st, 2008 GMT */
+#define CHEAT_DAYS (1199145600 / 24 / 60 / 60)
+#define CHEAT_YEARS 108
+
+#define IS_LEAP(n) ((!(((n) + 1900) % 400) || (!(((n) + 1900) % 4) && (((n) + 1900) % 100))) != 0)
+#define WRAP(a,b,m) ((a) = ((a) < 0 ) ? ((b)--, (a) + (m)) : (a))
+
+#ifdef USE_SYSTEM_LOCALTIME
+# define SHOULD_USE_SYSTEM_LOCALTIME(a) ( \
+ (a) <= SYSTEM_LOCALTIME_MAX && \
+ (a) >= SYSTEM_LOCALTIME_MIN \
+)
+#else
+# define SHOULD_USE_SYSTEM_LOCALTIME(a) (0)
+#endif
+
+#ifdef USE_SYSTEM_GMTIME
+# define SHOULD_USE_SYSTEM_GMTIME(a) ( \
+ (a) <= SYSTEM_GMTIME_MAX && \
+ (a) >= SYSTEM_GMTIME_MIN \
+)
+#else
+# define SHOULD_USE_SYSTEM_GMTIME(a) (0)
+#endif
+
+/* Multi varadic macros are a C99 thing, alas */
+#ifdef TIME_64_DEBUG
+# define TIME64_TRACE(format) (fprintf(stderr, format))
+# define TIME64_TRACE1(format, var1) (fprintf(stderr, format, var1))
+# define TIME64_TRACE2(format, var1, var2) (fprintf(stderr, format, var1, var2))
+# define TIME64_TRACE3(format, var1, var2, var3) (fprintf(stderr, format, var1, var2, var3))
+#else
+# define TIME64_TRACE(format) ((void)0)
+# define TIME64_TRACE1(format, var1) ((void)0)
+# define TIME64_TRACE2(format, var1, var2) ((void)0)
+# define TIME64_TRACE3(format, var1, var2, var3) ((void)0)
+#endif
+
+
+static int is_exception_century(Year year)
+{
+ int is_exception = ((year % 100 == 0) && !(year % 400 == 0));
+ TIME64_TRACE1("# is_exception_century: %s\n", is_exception ? "yes" : "no");
+
+ return(is_exception);
+}
+
+
+/* Compare two dates.
+ The result is like cmp.
+ Ignores things like gmtoffset and dst
+*/
+static int cmp_date( const struct TM* left, const struct tm* right ) {
+ if( left->tm_year > right->tm_year )
+ return 1;
+ if( left->tm_year < right->tm_year )
+ return -1;
+ if( left->tm_mon > right->tm_mon )
+ return 1;
+ if( left->tm_mon < right->tm_mon )
+ return -1;
+ if( left->tm_mday > right->tm_mday )
+ return 1;
+ if( left->tm_mday < right->tm_mday )
+ return -1;
+ if( left->tm_hour > right->tm_hour )
+ return 1;
+ if( left->tm_hour < right->tm_hour )
+ return -1;
+ if( left->tm_min > right->tm_min )
+ return 1;
+ if( left->tm_min < right->tm_min )
+ return -1;
+ if( left->tm_sec > right->tm_sec )
+ return 1;
+ if( left->tm_sec < right->tm_sec )
+ return -1;
+ return 0;
+}
+
+
+/* Check if a date is safely inside a range.
+ The intention is to check if its a few days inside.
+*/
+static int date_in_safe_range( const struct TM* date, const struct tm* min, const struct tm* max ) {
+ if( cmp_date(date, min) == -1 )
+ return 0;
+
+ if( cmp_date(date, max) == 1 )
+ return 0;
+
+ return 1;
+}
+
+
+/* timegm() is not in the C or POSIX spec, but it is such a useful
+ extension I would be remiss in leaving it out. Also I need it
+ for localtime64()
+*/
+Time64_T timegm64(const struct TM *date) {
+ Time64_T days = 0;
+ Time64_T seconds = 0;
+ Year year;
+ assert(date != NULL);
+ Year orig_year = (Year)date->tm_year;
+ int cycles = 0;
+
+ if( (orig_year > 100) || (orig_year < -300) ) {
+ cycles = (orig_year - 100) / 400;
+ orig_year -= cycles * 400;
+ days += (Time64_T)cycles * days_in_gregorian_cycle;
+ }
+ TIME64_TRACE3("# timegm/ cycles: %d, days: %lld, orig_year: %lld\n", cycles, days, orig_year);
+
+ if( orig_year > 70 ) {
+ year = 70;
+ while( year < orig_year ) {
+ days += length_of_year[IS_LEAP(year)];
+ year++;
+ }
+ }
+ else if ( orig_year < 70 ) {
+ year = 69;
+ do {
+ days -= length_of_year[IS_LEAP(year)];
+ year--;
+ } while( year >= orig_year );
+ }
+
+ days += julian_days_by_month[IS_LEAP(orig_year)][date->tm_mon];
+ days += date->tm_mday - 1;
+
+ seconds = days * 60 * 60 * 24;
+
+ seconds += date->tm_hour * 60 * 60;
+ seconds += date->tm_min * 60;
+ seconds += date->tm_sec;
+
+ return(seconds);
+}
+
+
+static int check_tm(struct TM *tm)
+{
+ /* Don't forget leap seconds */
+ assert(tm->tm_sec >= 0);
+ assert(tm->tm_sec <= 61);
+
+ assert(tm->tm_min >= 0);
+ assert(tm->tm_min <= 59);
+
+ assert(tm->tm_hour >= 0);
+ assert(tm->tm_hour <= 23);
+
+ assert(tm->tm_mday >= 1);
+ assert(tm->tm_mday <= days_in_month[IS_LEAP(tm->tm_year)][tm->tm_mon]);
+
+ assert(tm->tm_mon >= 0);
+ assert(tm->tm_mon <= 11);
+
+ assert(tm->tm_wday >= 0);
+ assert(tm->tm_wday <= 6);
+
+ assert(tm->tm_yday >= 0);
+ assert(tm->tm_yday <= length_of_year[IS_LEAP(tm->tm_year)]);
+
+#ifdef HAVE_TM_TM_GMTOFF
+ assert(tm->tm_gmtoff >= -24 * 60 * 60);
+ assert(tm->tm_gmtoff <= 24 * 60 * 60);
+#endif
+
+ return 1;
+}
+
+
+/* The exceptional centuries without leap years cause the cycle to
+ shift by 16
+*/
+static Year cycle_offset(Year year)
+{
+ const Year start_year = 2000;
+ Year year_diff = year - start_year;
+ Year exceptions;
+
+ if( year > start_year )
+ year_diff--;
+
+ exceptions = year_diff / 100;
+ exceptions -= year_diff / 400;
+
+ TIME64_TRACE3("# year: %lld, exceptions: %lld, year_diff: %lld\n",
+ year, exceptions, year_diff);
+
+ return exceptions * 16;
+}
+
+/* For a given year after 2038, pick the latest possible matching
+ year in the 28 year calendar cycle.
+
+ A matching year...
+ 1) Starts on the same day of the week.
+ 2) Has the same leap year status.
+
+ This is so the calendars match up.
+
+ Also the previous year must match. When doing Jan 1st you might
+ wind up on Dec 31st the previous year when doing a -UTC time zone.
+
+ Finally, the next year must have the same start day of week. This
+ is for Dec 31st with a +UTC time zone.
+ It doesn't need the same leap year status since we only care about
+ January 1st.
+*/
+static int safe_year(const Year year)
+{
+ int _safe_year = (int)year;
+ Year year_cycle;
+
+ if( year >= MIN_SAFE_YEAR && year <= MAX_SAFE_YEAR ) {
+ return _safe_year;
+ }
+
+ year_cycle = year + cycle_offset(year);
+
+ /* safe_years_low is off from safe_years_high by 8 years */
+ if( year < MIN_SAFE_YEAR )
+ year_cycle -= 8;
+
+ /* Change non-leap xx00 years to an equivalent */
+ if( is_exception_century(year) )
+ year_cycle += 11;
+
+ /* Also xx01 years, since the previous year will be wrong */
+ if( is_exception_century(year - 1) )
+ year_cycle += 17;
+
+ year_cycle %= SOLAR_CYCLE_LENGTH;
+ if( year_cycle < 0 )
+ year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
+
+ assert( year_cycle >= 0 );
+ assert( year_cycle < SOLAR_CYCLE_LENGTH );
+ if( year < MIN_SAFE_YEAR )
+ _safe_year = safe_years_low[year_cycle];
+ else if( year > MAX_SAFE_YEAR )
+ _safe_year = safe_years_high[year_cycle];
+ else
+ assert(0);
+
+ TIME64_TRACE3("# year: %lld, year_cycle: %lld, safe_year: %d\n",
+ year, year_cycle, _safe_year);
+
+ assert(_safe_year <= MAX_SAFE_YEAR && _safe_year >= MIN_SAFE_YEAR);
+
+ return _safe_year;
+}
+
+
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest) {
+ if( src == NULL ) {
+ memset(dest, 0, sizeof(*dest));
+ }
+ else {
+# ifdef USE_TM64
+ dest->tm_sec = src->tm_sec;
+ dest->tm_min = src->tm_min;
+ dest->tm_hour = src->tm_hour;
+ dest->tm_mday = src->tm_mday;
+ dest->tm_mon = src->tm_mon;
+ dest->tm_year = (Year)src->tm_year;
+ dest->tm_wday = src->tm_wday;
+ dest->tm_yday = src->tm_yday;
+ dest->tm_isdst = src->tm_isdst;
+
+# ifdef HAVE_TM_TM_GMTOFF
+ dest->tm_gmtoff = src->tm_gmtoff;
+# endif
+
+# ifdef HAVE_TM_TM_ZONE
+ dest->tm_zone = src->tm_zone;
+# endif
+
+# else
+ /* They're the same type */
+ memcpy(dest, src, sizeof(*dest));
+# endif
+ }
+}
+
+
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest) {
+ if( src == NULL ) {
+ memset(dest, 0, sizeof(*dest));
+ }
+ else {
+# ifdef USE_TM64
+ dest->tm_sec = src->tm_sec;
+ dest->tm_min = src->tm_min;
+ dest->tm_hour = src->tm_hour;
+ dest->tm_mday = src->tm_mday;
+ dest->tm_mon = src->tm_mon;
+ dest->tm_year = (int)src->tm_year;
+ dest->tm_wday = src->tm_wday;
+ dest->tm_yday = src->tm_yday;
+ dest->tm_isdst = src->tm_isdst;
+
+# ifdef HAVE_TM_TM_GMTOFF
+ dest->tm_gmtoff = src->tm_gmtoff;
+# endif
+
+# ifdef HAVE_TM_TM_ZONE
+ dest->tm_zone = src->tm_zone;
+# endif
+
+# else
+ /* They're the same type */
+ memcpy(dest, src, sizeof(*dest));
+# endif
+ }
+}
+
+
+#if !defined(HAVE_LOCALTIME_R) && !defined(_WIN32)
+/* Simulate localtime_r() to the best of our ability */
+static struct tm * fake_localtime_r(const time_t *time, struct tm *result) {
+ const struct tm *static_result = localtime(time);
+
+ assert(result != NULL);
+
+ if( static_result == NULL ) {
+ memset(result, 0, sizeof(*result));
+ return NULL;
+ }
+ else {
+ memcpy(result, static_result, sizeof(*result));
+ return result;
+ }
+}
+#endif
+
+
+#if !defined(HAVE_GMTIME_R) && !defined(_WIN32)
+/* Simulate gmtime_r() to the best of our ability */
+static struct tm * fake_gmtime_r(const time_t *time, struct tm *result) {
+ const struct tm *static_result = gmtime(time);
+
+ assert(result != NULL);
+
+ if( static_result == NULL ) {
+ memset(result, 0, sizeof(*result));
+ return NULL;
+ }
+ else {
+ memcpy(result, static_result, sizeof(*result));
+ return result;
+ }
+}
+#endif
+
+
+static Time64_T seconds_between_years(Year left_year, Year right_year) {
+ int increment = (left_year > right_year) ? 1 : -1;
+ Time64_T seconds = 0;
+ int cycles;
+
+ if( left_year > 2400 ) {
+ cycles = (left_year - 2400) / 400;
+ left_year -= cycles * 400;
+ seconds += cycles * seconds_in_gregorian_cycle;
+ }
+ else if( left_year < 1600 ) {
+ cycles = (left_year - 1600) / 400;
+ left_year += cycles * 400;
+ seconds += cycles * seconds_in_gregorian_cycle;
+ }
+
+ while( left_year != right_year ) {
+ seconds += length_of_year[IS_LEAP(right_year - 1900)] * 60 * 60 * 24;
+ right_year += increment;
+ }
+
+ return seconds * increment;
+}
+
+
+Time64_T mktime64(struct TM *input_date) {
+ struct tm safe_date;
+ struct TM date;
+ Time64_T timev;
+ Year year = input_date->tm_year + 1900;
+
+ if( date_in_safe_range(input_date, &SYSTEM_MKTIME_MIN, &SYSTEM_MKTIME_MAX) )
+ {
+ copy_TM64_to_tm(input_date, &safe_date);
+ timev = (Time64_T)mktime(&safe_date);
+
+ /* Correct the possibly out of bound input date */
+ copy_tm_to_TM64(&safe_date, input_date);
+ return timev;
+ }
+
+ /* Have to make the year safe in date else it won't fit in safe_date */
+ date = *input_date;
+ date.tm_year = safe_year(year) - 1900;
+ copy_TM64_to_tm(&date, &safe_date);
+
+ timev = (Time64_T)mktime(&safe_date);
+
+ /* Correct the user's possibly out of bound input date */
+ copy_tm_to_TM64(&safe_date, input_date);
+
+ timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900);
+
+ return timev;
+}
+
+
+/* Because I think mktime() is a crappy name */
+Time64_T timelocal64(struct TM *date) {
+ return mktime64(date);
+}
+
+
+struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
+{
+ int v_tm_sec, v_tm_min, v_tm_hour, v_tm_mon, v_tm_wday;
+ Time64_T v_tm_tday;
+ int leap;
+ Time64_T m;
+ Time64_T timev = *in_time;
+ Year year = 70;
+ int cycles = 0;
+
+ assert(p != NULL);
+
+ /* Use the system gmtime() if time_t is small enough */
+ if( SHOULD_USE_SYSTEM_GMTIME(*in_time) ) {
+ time_t safe_time = (time_t)*in_time;
+ struct tm safe_date;
+ GMTIME_R(&safe_time, &safe_date);
+
+ copy_tm_to_TM64(&safe_date, p);
+ assert(check_tm(p));
+
+ return p;
+ }
+
+#ifdef HAVE_TM_TM_GMTOFF
+ p->tm_gmtoff = 0;
+#endif
+ p->tm_isdst = 0;
+
+#ifdef HAVE_TM_TM_ZONE
+ p->tm_zone = (char*)"UTC";
+#endif
+
+ v_tm_sec = (int)(timev % 60);
+ timev /= 60;
+ v_tm_min = (int)(timev % 60);
+ timev /= 60;
+ v_tm_hour = (int)(timev % 24);
+ timev /= 24;
+ v_tm_tday = timev;
+
+ WRAP (v_tm_sec, v_tm_min, 60);
+ WRAP (v_tm_min, v_tm_hour, 60);
+ WRAP (v_tm_hour, v_tm_tday, 24);
+
+ v_tm_wday = (int)((v_tm_tday + 4) % 7);
+ if (v_tm_wday < 0)
+ v_tm_wday += 7;
+ m = v_tm_tday;
+
+ if (m >= CHEAT_DAYS) {
+ year = CHEAT_YEARS;
+ m -= CHEAT_DAYS;
+ }
+
+ if (m >= 0) {
+ /* Gregorian cycles, this is huge optimization for distant times */
+ cycles = (int)(m / (Time64_T) days_in_gregorian_cycle);
+ if( cycles ) {
+ m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+ year += (cycles * years_in_gregorian_cycle);
+ }
+
+ /* Years */
+ leap = IS_LEAP (year);
+ while (m >= (Time64_T) length_of_year[leap]) {
+ m -= (Time64_T) length_of_year[leap];
+ year++;
+ leap = IS_LEAP (year);
+ }
+
+ /* Months */
+ v_tm_mon = 0;
+ while (m >= (Time64_T) days_in_month[leap][v_tm_mon]) {
+ m -= (Time64_T) days_in_month[leap][v_tm_mon];
+ v_tm_mon++;
+ }
+ } else {
+ year--;
+
+ /* Gregorian cycles */
+ cycles = (int)((m / (Time64_T) days_in_gregorian_cycle) + 1);
+ if( cycles ) {
+ m -= (cycles * (Time64_T) days_in_gregorian_cycle);
+ year += (cycles * years_in_gregorian_cycle);
+ }
+
+ /* Years */
+ leap = IS_LEAP (year);
+ while (m < (Time64_T) -length_of_year[leap]) {
+ m += (Time64_T) length_of_year[leap];
+ year--;
+ leap = IS_LEAP (year);
+ }
+
+ /* Months */
+ v_tm_mon = 11;
+ while (m < (Time64_T) -days_in_month[leap][v_tm_mon]) {
+ m += (Time64_T) days_in_month[leap][v_tm_mon];
+ v_tm_mon--;
+ }
+ m += (Time64_T) days_in_month[leap][v_tm_mon];
+ }
+
+ p->tm_year = year;
+ if( p->tm_year != year ) {
+#ifdef EOVERFLOW
+ errno = EOVERFLOW;
+#endif
+ return NULL;
+ }
+
+ /* At this point m is less than a year so casting to an int is safe */
+ p->tm_mday = (int) m + 1;
+ p->tm_yday = julian_days_by_month[leap][v_tm_mon] + (int)m;
+ p->tm_sec = v_tm_sec;
+ p->tm_min = v_tm_min;
+ p->tm_hour = v_tm_hour;
+ p->tm_mon = v_tm_mon;
+ p->tm_wday = v_tm_wday;
+
+ assert(check_tm(p));
+
+ return p;
+}
+
+
+struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
+{
+ time_t safe_time;
+ struct tm safe_date;
+ struct TM gm_tm;
+ Year orig_year;
+ int month_diff;
+
+ assert(local_tm != NULL);
+
+ /* Use the system localtime() if time_t is small enough */
+ if( SHOULD_USE_SYSTEM_LOCALTIME(*timev) ) {
+ safe_time = (time_t)*timev;
+
+ TIME64_TRACE1("Using system localtime for %lld\n", *timev);
+
+ LOCALTIME_R(&safe_time, &safe_date);
+
+ copy_tm_to_TM64(&safe_date, local_tm);
+ assert(check_tm(local_tm));
+
+ return local_tm;
+ }
+
+ if( gmtime64_r(timev, &gm_tm) == NULL ) {
+ TIME64_TRACE1("gmtime64_r returned null for %lld\n", *timev);
+ return NULL;
+ }
+
+ orig_year = gm_tm.tm_year;
+
+ if (gm_tm.tm_year > (2037 - 1900) ||
+ gm_tm.tm_year < (1970 - 1900)
+ )
+ {
+ TIME64_TRACE1("Mapping tm_year %lld to safe_year\n", (Year)gm_tm.tm_year);
+ gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year) + 1900) - 1900;
+ }
+
+ safe_time = (time_t)timegm64(&gm_tm);
+ if( LOCALTIME_R(&safe_time, &safe_date) == NULL ) {
+ TIME64_TRACE1("localtime_r(%d) returned NULL\n", (int)safe_time);
+ return NULL;
+ }
+
+ copy_tm_to_TM64(&safe_date, local_tm);
+
+ local_tm->tm_year = orig_year;
+ if( local_tm->tm_year != orig_year ) {
+ TIME64_TRACE2("tm_year overflow: tm_year %lld, orig_year %lld\n",
+ (Year)local_tm->tm_year, (Year)orig_year);
+
+#ifdef EOVERFLOW
+ errno = EOVERFLOW;
+#endif
+ return NULL;
+ }
+
+
+ month_diff = local_tm->tm_mon - gm_tm.tm_mon;
+
+ /* When localtime is Dec 31st previous year and
+ gmtime is Jan 1st next year.
+ */
+ if( month_diff == 11 ) {
+ local_tm->tm_year--;
+ }
+
+ /* When localtime is Jan 1st, next year and
+ gmtime is Dec 31st, previous year.
+ */
+ if( month_diff == -11 ) {
+ local_tm->tm_year++;
+ }
+
+ /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
+ in a non-leap xx00. There is one point in the cycle
+ we can't account for which the safe xx00 year is a leap
+ year. So we need to correct for Dec 31st comming out as
+ the 366th day of the year.
+ */
+ if( !IS_LEAP(local_tm->tm_year) && local_tm->tm_yday == 365 )
+ local_tm->tm_yday--;
+
+ assert(check_tm(local_tm));
+
+ return local_tm;
+}
+
+
+static int valid_tm_wday( const struct TM* date ) {
+ if( 0 <= date->tm_wday && date->tm_wday <= 6 )
+ return 1;
+
+ return 0;
+}
+
+static int valid_tm_mon( const struct TM* date ) {
+ if( 0 <= date->tm_mon && date->tm_mon <= 11 )
+ return 1;
+
+ return 0;
+}
+
+
+char *asctime64_r( const struct TM* date, char *result ) {
+ /* I figure everything else can be displayed, even hour 25, but if
+ these are out of range we walk off the name arrays */
+ if( !valid_tm_wday(date) || !valid_tm_mon(date) )
+ return NULL;
+
+ sprintf(result, TM64_ASCTIME_FORMAT,
+ wday_name[date->tm_wday],
+ mon_name[date->tm_mon],
+ date->tm_mday, date->tm_hour,
+ date->tm_min, date->tm_sec,
+ 1900 + date->tm_year);
+
+ return result;
+}
+
+
+char *ctime64_r( const Time64_T* timev, char* result ) {
+ struct TM date;
+
+ if (!localtime64_r( timev, &date ))
+ return NULL;
+
+ return asctime64_r( &date, result );
+}
+
diff --git a/src/time64.h b/src/time64.h
new file mode 100644
index 0000000..2c20ffe
--- /dev/null
+++ b/src/time64.h
@@ -0,0 +1,92 @@
+#ifndef TIME64_H
+# define TIME64_H
+
+#include <time.h>
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* Set our custom types */
+typedef long long Int64;
+typedef Int64 Time64_T;
+typedef Int64 Year;
+
+#ifndef TIME64_MIN
+#define TIME64_MIN ((Time64_T)INT64_MIN)
+#endif
+
+#ifndef TIME64_MAX
+#define TIME64_MAX ((Time64_T)INT64_MAX)
+#endif
+
+/* A copy of the tm struct but with a 64 bit year */
+struct TM64 {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ Year tm_year;
+ int tm_wday;
+ int tm_yday;
+ int tm_isdst;
+
+#ifdef HAVE_TM_TM_GMTOFF
+ long tm_gmtoff;
+#endif
+
+#ifdef HAVE_TM_TM_ZONE
+ char *tm_zone;
+#endif
+};
+
+
+/* Decide which tm struct to use */
+#ifdef USE_TM64
+#define TM TM64
+#else
+#define TM tm
+#endif
+
+
+/* Declare public functions */
+struct TM *gmtime64_r (const Time64_T *, struct TM *);
+struct TM *localtime64_r (const Time64_T *, struct TM *);
+
+char *asctime64_r (const struct TM *, char *);
+
+char *ctime64_r (const Time64_T*, char*);
+
+Time64_T timegm64 (const struct TM *);
+Time64_T mktime64 (struct TM *);
+Time64_T timelocal64 (struct TM *);
+
+
+/* Not everyone has gm/localtime_r(), provide a replacement */
+#ifdef HAVE_LOCALTIME_R
+# define LOCALTIME_R(clock, result) localtime_r(clock, result)
+#elif defined(_WIN32)
+# define LOCALTIME_R(clock, result) (localtime_s(result, clock) ? NULL : result)
+#else
+# define LOCALTIME_R(clock, result) fake_localtime_r(clock, result)
+#endif
+#ifdef HAVE_GMTIME_R
+# define GMTIME_R(clock, result) gmtime_r(clock, result)
+#elif defined (_WIN32)
+# define GMTIME_R(clock, result) (gmtime_s(result, clock) ? NULL : result)
+#else
+# define GMTIME_R(clock, result) fake_gmtime_r(clock, result)
+#endif
+
+
+/* Use a different asctime format depending on how big the year is */
+#ifdef USE_TM64
+ #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %lld\n"
+#else
+ #define TM64_ASCTIME_FORMAT "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
+#endif
+
+void copy_tm_to_TM64(const struct tm *src, struct TM *dest);
+void copy_TM64_to_tm(const struct TM *src, struct tm *dest);
+
+#endif
diff --git a/src/time64_limits.h b/src/time64_limits.h
new file mode 100644
index 0000000..1151cf2
--- /dev/null
+++ b/src/time64_limits.h
@@ -0,0 +1,97 @@
+/*
+ Maximum and minimum inputs your system's respective time functions
+ can correctly handle. time64.h will use your system functions if
+ the input falls inside these ranges and corresponding USE_SYSTEM_*
+ constant is defined.
+*/
+
+#ifndef TIME64_LIMITS_H
+#define TIME64_LIMITS_H
+
+#include <time.h>
+
+/* Max/min for localtime() */
+#define SYSTEM_LOCALTIME_MAX 2147483647
+#define SYSTEM_LOCALTIME_MIN -2147483647-1
+
+/* Max/min for gmtime() */
+#define SYSTEM_GMTIME_MAX 2147483647
+#define SYSTEM_GMTIME_MIN -2147483647-1
+
+/* Max/min for mktime() */
+static const struct tm SYSTEM_MKTIME_MAX = {
+ 7,
+ 14,
+ 19,
+ 18,
+ 0,
+ 138,
+ 1,
+ 17,
+ 0
+#ifdef HAVE_TM_TM_GMTOFF
+ ,-28800
+#endif
+#ifdef HAVE_TM_TM_ZONE
+ ,(char*)"PST"
+#endif
+};
+
+static const struct tm SYSTEM_MKTIME_MIN = {
+ 52,
+ 45,
+ 12,
+ 13,
+ 11,
+ 1,
+ 5,
+ 346,
+ 0
+#ifdef HAVE_TM_TM_GMTOFF
+ ,-28800
+#endif
+#ifdef HAVE_TM_TM_ZONE
+ ,(char*)"PST"
+#endif
+};
+
+/* Max/min for timegm() */
+#ifdef HAVE_TIMEGM
+static const struct tm SYSTEM_TIMEGM_MAX = {
+ 7,
+ 14,
+ 3,
+ 19,
+ 0,
+ 138,
+ 2,
+ 18,
+ 0
+ #ifdef HAVE_TM_TM_GMTOFF
+ ,0
+ #endif
+ #ifdef HAVE_TM_TM_ZONE
+ ,(char*)"UTC"
+ #endif
+};
+
+static const struct tm SYSTEM_TIMEGM_MIN = {
+ 52,
+ 45,
+ 20,
+ 13,
+ 11,
+ 1,
+ 5,
+ 346,
+ 0
+ #ifdef HAVE_TM_TM_GMTOFF
+ ,0
+ #endif
+ #ifdef HAVE_TM_TM_ZONE
+ ,(char*)"UTC"
+ #endif
+};
+#endif /* HAVE_TIMEGM */
+
+#endif /* TIME64_LIMITS_H */
diff --git a/src/xplist.c b/src/xplist.c
index ba312a1..b2c134e 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -1,7 +1,9 @@
/*
- * plist.c
+ * xplist.c
* XML plist implementation
*
+ * Copyright (c) 2010-2017 Nikias Bassen All Rights Reserved.
+ * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved.
* Copyright (c) 2008 Jonathan Beck All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
@@ -19,6 +21,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE 600
+#endif
#include <string.h>
#include <assert.h>
@@ -27,437 +36,1556 @@
#include <time.h>
#include <inttypes.h>
-#include <locale.h>
-
-#include <libxml/parser.h>
-#include <libxml/tree.h>
+#include <float.h>
+#include <math.h>
+#include <limits.h>
+#include <errno.h>
#include <node.h>
-#include <node_list.h>
-#include <node_iterator.h>
#include "plist.h"
#include "base64.h"
-
-#define XPLIST_TEXT BAD_CAST("text")
-#define XPLIST_KEY BAD_CAST("key")
-#define XPLIST_FALSE BAD_CAST("false")
-#define XPLIST_TRUE BAD_CAST("true")
-#define XPLIST_INT BAD_CAST("integer")
-#define XPLIST_REAL BAD_CAST("real")
-#define XPLIST_DATE BAD_CAST("date")
-#define XPLIST_DATA BAD_CAST("data")
-#define XPLIST_STRING BAD_CAST("string")
-#define XPLIST_ARRAY BAD_CAST("array")
-#define XPLIST_DICT BAD_CAST("dict")
-
-static const char *plist_base = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
+#include "strbuf.h"
+#include "time64.h"
+#include "hashtable.h"
+#include "common.h"
+
+#define XPLIST_KEY "key"
+#define XPLIST_KEY_LEN 3
+#define XPLIST_FALSE "false"
+#define XPLIST_FALSE_LEN 5
+#define XPLIST_TRUE "true"
+#define XPLIST_TRUE_LEN 4
+#define XPLIST_INT "integer"
+#define XPLIST_INT_LEN 7
+#define XPLIST_REAL "real"
+#define XPLIST_REAL_LEN 4
+#define XPLIST_DATE "date"
+#define XPLIST_DATE_LEN 4
+#define XPLIST_DATA "data"
+#define XPLIST_DATA_LEN 4
+#define XPLIST_STRING "string"
+#define XPLIST_STRING_LEN 6
+#define XPLIST_ARRAY "array"
+#define XPLIST_ARRAY_LEN 5
+#define XPLIST_DICT "dict"
+#define XPLIST_DICT_LEN 4
+
+#define MAX_DATA_BYTES_PER_LINE(__i) (((76 - ((__i) << 3)) >> 2) * 3)
+
+static const char XML_PLIST_PROLOG[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
-<plist version=\"1.0\">\n\
-</plist>\0";
+<plist version=\"1.0\">\n";
+static const char XML_PLIST_EPILOG[] = "</plist>\n";
+#ifdef DEBUG
+static int plist_xml_debug = 0;
+#define PLIST_XML_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlparser] ERROR: " __VA_ARGS__); }
+#define PLIST_XML_WRITE_ERR(...) if (plist_xml_debug) { fprintf(stderr, "libplist[xmlwriter] ERROR: " __VA_ARGS__); }
+#else
+#define PLIST_XML_ERR(...)
+#define PLIST_XML_WRITE_ERR(...)
+#endif
-/** Formats a block of text to be a given indentation and width.
- *
- * The total width of the return string will be depth + cols.
- *
- * @param buf The string to format.
- * @param cols The number of text columns for returned block of text.
- * @param depth The number of tabs to indent the returned block of text.
- *
- * @return The formatted string.
- */
-static char *format_string(const char *buf, size_t len, int cols, int depth)
+void plist_xml_init(void)
{
- if (!buf || !(len > 0)) return NULL;
- int colw = depth + cols + 1;
- int nlines = len / cols + 1;
- char *new_buf = NULL;
- int i = 0;
- int j = 0;
-
- assert(cols >= 0);
- assert(depth >= 0);
-
- new_buf = (char*) malloc(nlines * colw + depth + 1);
- assert(new_buf != 0);
- memset(new_buf, 0, nlines * colw + depth + 1);
-
- // Inserts new lines and tabs at appropriate locations
- for (i = 0; i < nlines; i++)
- {
- new_buf[i * colw] = '\n';
- for (j = 0; j < depth; j++)
- new_buf[i * colw + 1 + j] = '\t';
- memcpy(new_buf + i * colw + 1 + depth, buf + i * cols, (i + 1) * cols <= len ? cols : len - i * cols);
+ /* init XML stuff */
+#ifdef DEBUG
+ char *env_debug = getenv("PLIST_XML_DEBUG");
+ if (env_debug && !strcmp(env_debug, "1")) {
+ plist_xml_debug = 1;
}
- new_buf[len + (1 + depth) * nlines] = '\n';
-
- // Inserts final row of indentation and termination character
- for (j = 0; j < depth; j++)
- new_buf[len + (1 + depth) * nlines + 1 + j] = '\t';
- new_buf[len + (1 + depth) * nlines + depth + 1] = '\0';
-
- return new_buf;
+#endif
}
-
-
-struct xml_node
+void plist_xml_deinit(void)
{
- xmlNodePtr xml;
- uint32_t depth;
-};
+ /* deinit XML stuff */
+}
-/** Creates a new plist XML document.
- *
- * @return The plist XML document.
- */
-static xmlDocPtr new_xml_plist(void)
+void plist_xml_set_debug(int debug)
{
- char *plist = strdup(plist_base);
- xmlDocPtr plist_xml = xmlParseMemory(plist, strlen(plist));
-
- if (!plist_xml)
- return NULL;
-
- free(plist);
-
- return plist_xml;
+#if DEBUG
+ plist_xml_debug = debug;
+#endif
}
-static void node_to_xml(node_t* node, void *xml_struct)
+static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
{
- struct xml_node *xstruct = NULL;
plist_data_t node_data = NULL;
- xmlNodePtr child_node = NULL;
char isStruct = FALSE;
+ char tagOpen = FALSE;
- const xmlChar *tag = NULL;
- char *val = NULL;
-
- //for base64
- char *valtmp = NULL;
+ const char *tag = NULL;
+ size_t tag_len = 0;
+ char val[64] = { 0 };
+ size_t val_len = 0;
uint32_t i = 0;
- if (!node)
- return;
+ if (!node) {
+ PLIST_XML_WRITE_ERR("Encountered invalid empty node in property list\n");
+ return PLIST_ERR_INVALID_ARG;
+ }
- xstruct = (struct xml_node *) xml_struct;
node_data = plist_get_data(node);
switch (node_data->type)
{
case PLIST_BOOLEAN:
{
- if (node_data->boolval)
+ if (node_data->boolval) {
tag = XPLIST_TRUE;
- else
+ tag_len = XPLIST_TRUE_LEN;
+ } else {
tag = XPLIST_FALSE;
+ tag_len = XPLIST_FALSE_LEN;
+ }
}
break;
- case PLIST_UINT:
+ case PLIST_INT:
tag = XPLIST_INT;
- val = (char*)malloc(64);
- (void)snprintf(val, 64, "%"PRIu64, node_data->intval);
+ tag_len = XPLIST_INT_LEN;
+ if (node_data->length == 16) {
+ val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval);
+ }
break;
case PLIST_REAL:
tag = XPLIST_REAL;
- val = (char*)malloc(64);
- (void)snprintf(val, 64, "%f", node_data->realval);
+ tag_len = XPLIST_REAL_LEN;
+ val_len = dtostr(val, sizeof(val), node_data->realval);
break;
case PLIST_STRING:
tag = XPLIST_STRING;
- val = strdup(node_data->strval);
+ tag_len = XPLIST_STRING_LEN;
+ /* contents processed directly below */
break;
case PLIST_KEY:
tag = XPLIST_KEY;
- val = strdup((char*) node_data->strval);
+ tag_len = XPLIST_KEY_LEN;
+ /* contents processed directly below */
break;
case PLIST_DATA:
tag = XPLIST_DATA;
- if (node_data->length)
- {
- size_t len = node_data->length;
- valtmp = base64encode(node_data->buff, &len);
- val = format_string(valtmp, len, 68, xstruct->depth);
- free(valtmp);
- }
+ tag_len = XPLIST_DATA_LEN;
+ /* contents processed directly below */
break;
case PLIST_ARRAY:
tag = XPLIST_ARRAY;
- isStruct = TRUE;
+ tag_len = XPLIST_ARRAY_LEN;
+ isStruct = (node->children) ? TRUE : FALSE;
break;
case PLIST_DICT:
tag = XPLIST_DICT;
- isStruct = TRUE;
+ tag_len = XPLIST_DICT_LEN;
+ isStruct = (node->children) ? TRUE : FALSE;
break;
case PLIST_DATE:
tag = XPLIST_DATE;
+ tag_len = XPLIST_DATE_LEN;
{
- time_t time = (time_t)node_data->timeval.tv_sec;
- struct tm *btime = localtime(&time);
+ Time64_T timev;
+ if (plist_real_to_time64(node_data->realval, &timev) < 0) {
+ PLIST_XML_WRITE_ERR("Encountered invalid date value %f\n", node_data->realval);
+ return PLIST_ERR_INVALID_ARG;
+ }
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
if (btime) {
- val = (char*)malloc(24);
- memset(val, 0, 24);
- if (strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", btime) <= 0) {
- free (val);
- val = NULL;
+ struct tm _tmcopy;
+ copy_TM64_to_tm(btime, &_tmcopy);
+ val_len = strftime(val, sizeof(val), "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
+ if (val_len <= 0) {
+ snprintf(val, sizeof(val), "1970-01-01T00:00:00Z");
}
}
}
break;
- default:
+ case PLIST_UID:
+ tag = XPLIST_DICT;
+ tag_len = XPLIST_DICT_LEN;
+ if (node_data->length == 16) {
+ val_len = snprintf(val, sizeof(val), "%" PRIu64, node_data->intval);
+ } else {
+ val_len = snprintf(val, sizeof(val), "%" PRIi64, node_data->intval);
+ }
break;
+ case PLIST_NULL:
+ PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
}
- for (i = 0; i < xstruct->depth; i++)
- {
- xmlNodeAddContent(xstruct->xml, BAD_CAST("\t"));
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
- if (node_data->type == PLIST_STRING) {
+
+ /* append tag */
+ str_buf_append(*outbuf, "<", 1);
+ str_buf_append(*outbuf, tag, tag_len);
+ if ((node_data->type == PLIST_STRING || node_data->type == PLIST_KEY) && node_data->length > 0) {
+ size_t j;
+ size_t len;
+ off_t start = 0;
+ off_t cur = 0;
+
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+
/* make sure we convert the following predefined xml entities */
- /* < = &lt; > = &gt; ' = &apos; " = &quot; & = &amp; */
- child_node = xmlNewTextChild(xstruct->xml, NULL, tag, BAD_CAST(val));
- } else
- child_node = xmlNewChild(xstruct->xml, NULL, tag, BAD_CAST(val));
- xmlNodeAddContent(xstruct->xml, BAD_CAST("\n"));
- if (val) {
- free(val);
- }
+ /* < = &lt; > = &gt; & = &amp; */
+ len = node_data->length;
+ for (j = 0; j < len; j++) {
+ switch (node_data->strval[j]) {
+ case '<':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&lt;", 4);
+ start = cur+1;
+ break;
+ case '>':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&gt;", 4);
+ start = cur+1;
+ break;
+ case '&':
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ str_buf_append(*outbuf, "&amp;", 5);
+ start = cur+1;
+ break;
+ default:
+ break;
+ }
+ cur++;
+ }
+ str_buf_append(*outbuf, node_data->strval + start, cur - start);
+ } else if (node_data->type == PLIST_DATA) {
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, "\n", 1);
+ if (node_data->length > 0) {
+ uint32_t j = 0;
+ uint32_t indent = (depth > 8) ? 8 : depth;
+ uint32_t maxread = MAX_DATA_BYTES_PER_LINE(indent);
+ size_t count = 0;
+ size_t amount = (node_data->length / 3 * 4) + 4 + (((node_data->length / maxread) + 1) * (indent+1));
+ if ((*outbuf)->len + amount > (*outbuf)->capacity) {
+ str_buf_grow(*outbuf, amount);
+ }
+ while (j < node_data->length) {
+ for (i = 0; i < indent; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ count = (node_data->length-j < maxread) ? node_data->length-j : maxread;
+ assert((*outbuf)->len + count < (*outbuf)->capacity);
+ (*outbuf)->len += base64encode((char*)(*outbuf)->data + (*outbuf)->len, node_data->buff + j, count);
+ str_buf_append(*outbuf, "\n", 1);
+ j+=count;
+ }
+ }
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ } else if (node_data->type == PLIST_UID) {
+ /* special case for UID nodes: create a DICT */
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, "\n", 1);
+
+ /* add CF$UID key */
+ for (i = 0; i < depth+1; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ str_buf_append(*outbuf, "<key>CF$UID</key>", 17);
+ str_buf_append(*outbuf, "\n", 1);
- //add return for structured types
- if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
- xmlNodeAddContent(child_node, BAD_CAST("\n"));
+ /* add UID value */
+ for (i = 0; i < depth+1; i++) {
+ str_buf_append(*outbuf, "\t", 1);
+ }
+ str_buf_append(*outbuf, "<integer>", 9);
+ str_buf_append(*outbuf, val, val_len);
+ str_buf_append(*outbuf, "</integer>", 10);
+ str_buf_append(*outbuf, "\n", 1);
- if (isStruct)
- {
- struct xml_node child = { child_node, xstruct->depth + 1 };
- node_iterator_t *ni = node_iterator_create(node->children);
- node_t *ch;
- while ((ch = node_iterator_next(ni))) {
- node_to_xml(ch, &child);
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
- node_iterator_destroy(ni);
+ } else if (val_len > 0) {
+ str_buf_append(*outbuf, ">", 1);
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, val, val_len);
+ } else if (isStruct) {
+ tagOpen = TRUE;
+ str_buf_append(*outbuf, ">", 1);
+ } else {
+ tagOpen = FALSE;
+ str_buf_append(*outbuf, "/>", 2);
}
- //fix indent for structured types
- if (node_data->type == PLIST_ARRAY || node_data->type == PLIST_DICT)
- {
- for (i = 0; i < xstruct->depth; i++)
- {
- xmlNodeAddContent(child_node, BAD_CAST("\t"));
+ if (isStruct) {
+ /* add newline for structured types */
+ str_buf_append(*outbuf, "\n", 1);
+
+ /* add child nodes */
+ if (node_data->type == PLIST_DICT && node->children) {
+ assert((node->children->count % 2) == 0);
+ }
+ node_t ch;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t res = node_to_xml(ch, outbuf, depth+1);
+ if (res < 0) return res;
+ }
+
+ /* fix indent for structured types */
+ for (i = 0; i < depth; i++) {
+ str_buf_append(*outbuf, "\t", 1);
}
}
- return;
+ if (tagOpen) {
+ /* add closing tag */
+ str_buf_append(*outbuf, "</", 2);
+ str_buf_append(*outbuf, tag, tag_len);
+ str_buf_append(*outbuf, ">", 1);
+ }
+ str_buf_append(*outbuf, "\n", 1);
+ return PLIST_ERR_SUCCESS;
}
-static void parse_date(const char *strval, struct tm *btime)
+static int parse_date(const char *strval, struct TM *btime)
{
- if (!btime) return;
- memset(btime, 0, sizeof(struct tm));
- if (!strval) return;
-#ifdef strptime
- strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime);
+ if (!btime) return -1;
+ memset(btime, 0, sizeof(*btime));
+ if (!strval) return -1;
+#ifdef HAVE_STRPTIME
+#ifdef USE_TM64
+ struct tm t = { 0 };
+ char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", &t);
+ if (!r || *r != '\0') {
+ return -1;
+ }
+ copy_tm_to_TM64(&t, btime);
+#else
+ char* r = strptime((char*)strval, "%Y-%m-%dT%H:%M:%SZ", btime);
+ if (!r || *r != '\0') {
+ return -1;
+ }
+#endif
#else
- sscanf(strval, "%d-%d-%dT%d:%d:%dZ", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec);
+#ifdef USE_TM64
+ #define PLIST_SSCANF_FORMAT "%lld-%d-%dT%d:%d:%dZ"
+#else
+ #define PLIST_SSCANF_FORMAT "%d-%d-%dT%d:%d:%dZ"
+#endif
+ int n = 0;
+ if (sscanf(strval, PLIST_SSCANF_FORMAT "%n", &btime->tm_year, &btime->tm_mon, &btime->tm_mday, &btime->tm_hour, &btime->tm_min, &btime->tm_sec, &n) != 6) return -1;
+ if (strval[n] != '\0') return -1;
+ if (btime->tm_mon < 1 || btime->tm_mon > 12) return -1;
+ if (btime->tm_mday < 1 || btime->tm_mday > 31) return -1;
+ if (btime->tm_hour < 0 || btime->tm_hour > 23) return -1;
+ if (btime->tm_min < 0 || btime->tm_min > 59) return -1;
+ if (btime->tm_sec < 0 || btime->tm_sec > 59) return -1;
btime->tm_year-=1900;
btime->tm_mon--;
#endif
+ btime->tm_isdst=0;
+ return 0;
}
-static void xml_to_node(xmlNodePtr xml_node, plist_t * plist_node)
+static plist_err_t _node_estimate_size(node_t node, uint64_t *size, uint32_t depth, hashtable_t *visited)
{
- xmlNodePtr node = NULL;
- plist_data_t data = NULL;
- plist_t subnode = NULL;
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
- //for string
- long len = 0;
- int type = 0;
+ if (depth > PLIST_MAX_NESTING_DEPTH) {
+ PLIST_XML_WRITE_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ return PLIST_ERR_MAX_NESTING;
+ }
- if (!xml_node)
- return;
+ if (hash_table_lookup(visited, node)) {
+ PLIST_XML_WRITE_ERR("circular reference detected\n");
+ return PLIST_ERR_CIRCULAR_REF;
+ }
- for (node = xml_node->children; node; node = node->next)
- {
+ // mark as visited
+ hash_table_insert(visited, node, (void*)1);
- while (node && !xmlStrcmp(node->name, XPLIST_TEXT))
- node = node->next;
- if (!node)
+ data = plist_get_data(node);
+ if (node->children) {
+ node_t ch;
+ for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
+ plist_err_t err = _node_estimate_size(ch, size, depth + 1, visited);
+ if (err != PLIST_ERR_SUCCESS) {
+ return err;
+ }
+ }
+ switch (data->type) {
+ case PLIST_DICT:
+ *size += (XPLIST_DICT_LEN << 1) + 7;
+ break;
+ case PLIST_ARRAY:
+ *size += (XPLIST_ARRAY_LEN << 1) + 7;
+ break;
+ default:
+ break;
+ }
+ *size += (depth << 1);
+ } else {
+ uint32_t indent = (depth > 8) ? 8 : depth;
+ switch (data->type) {
+ case PLIST_DATA: {
+ uint32_t req_lines = (data->length / MAX_DATA_BYTES_PER_LINE(indent)) + 1;
+ uint32_t b64len = data->length + (data->length / 3);
+ b64len += b64len % 4;
+ *size += b64len;
+ *size += (XPLIST_DATA_LEN << 1) + 5 + (indent+1) * (req_lines+1) + 1;
+ } break;
+ case PLIST_STRING:
+ *size += data->length;
+ *size += (XPLIST_STRING_LEN << 1) + 6;
+ break;
+ case PLIST_KEY:
+ *size += data->length;
+ *size += (XPLIST_KEY_LEN << 1) + 6;
+ break;
+ case PLIST_INT:
+ if (data->length == 16) {
+ *size += num_digits_u(data->intval);
+ } else {
+ *size += num_digits_i((int64_t)data->intval);
+ }
+ *size += (XPLIST_INT_LEN << 1) + 6;
+ break;
+ case PLIST_REAL:
+ *size += dtostr(NULL, 0, data->realval);
+ *size += (XPLIST_REAL_LEN << 1) + 6;
+ break;
+ case PLIST_DATE:
+ *size += 20; /* YYYY-MM-DDThh:mm:ssZ */
+ *size += (XPLIST_DATE_LEN << 1) + 6;
+ break;
+ case PLIST_BOOLEAN:
+ *size += ((data->boolval) ? XPLIST_TRUE_LEN : XPLIST_FALSE_LEN) + 4;
+ break;
+ case PLIST_DICT:
+ *size += XPLIST_DICT_LEN + 4; /* <dict/> */
break;
+ case PLIST_ARRAY:
+ *size += XPLIST_ARRAY_LEN + 4; /* <array/> */
+ break;
+ case PLIST_UID:
+ *size += num_digits_i((int64_t)data->intval);
+ *size += (XPLIST_DICT_LEN << 1) + 7;
+ *size += indent + ((indent+1) << 1);
+ *size += 18; /* <key>CF$UID</key> */
+ *size += (XPLIST_INT_LEN << 1) + 6;
+ break;
+ case PLIST_NULL:
+ PLIST_XML_WRITE_ERR("PLIST_NULL type is not valid for XML format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_XML_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ *size += indent;
+ }
+ return PLIST_ERR_SUCCESS;
+}
- data = plist_new_plist_data();
- subnode = plist_new_node(data);
- if (*plist_node)
- node_attach(*plist_node, subnode);
- else
- *plist_node = subnode;
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
+{
+ hashtable_t *visited = hash_table_new(plist_node_ptr_hash, plist_node_ptr_compare, NULL);
+ if (!visited) return PLIST_ERR_NO_MEM;
+ plist_err_t err = _node_estimate_size(node, size, depth, visited);
+ hash_table_destroy(visited);
+ return err;
+}
- if (!xmlStrcmp(node->name, XPLIST_TRUE))
- {
- data->boolval = TRUE;
- data->type = PLIST_BOOLEAN;
- data->length = 1;
- continue;
- }
+plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
+{
+ uint64_t size = 0;
+ plist_err_t res;
- if (!xmlStrcmp(node->name, XPLIST_FALSE))
- {
- data->boolval = FALSE;
- data->type = PLIST_BOOLEAN;
- data->length = 1;
- continue;
- }
+ if (!plist || !plist_xml || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
- if (!xmlStrcmp(node->name, XPLIST_INT))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->intval = strtoull((char*)strval, NULL, 0);
- data->type = PLIST_UINT;
- data->length = 8;
- xmlFree(strval);
- continue;
+ res = node_estimate_size((node_t)plist, &size, 0);
+ if (res < 0) {
+ return res;
+ }
+ size += sizeof(XML_PLIST_PROLOG) + sizeof(XML_PLIST_EPILOG) - 1;
+
+ strbuf_t *outbuf = str_buf_new(size);
+ if (!outbuf) {
+ PLIST_XML_WRITE_ERR("Could not allocate output buffer\n");
+ return PLIST_ERR_NO_MEM;
+ }
+
+ str_buf_append(outbuf, XML_PLIST_PROLOG, sizeof(XML_PLIST_PROLOG)-1);
+
+ res = node_to_xml((node_t)plist, &outbuf, 0);
+ if (res < 0) {
+ str_buf_free(outbuf);
+ *plist_xml = NULL;
+ *length = 0;
+ return res;
+ }
+
+ str_buf_append(outbuf, XML_PLIST_EPILOG, sizeof(XML_PLIST_EPILOG));
+
+ *plist_xml = (char*)outbuf->data;
+ *length = outbuf->len - 1;
+
+ outbuf->data = NULL;
+ str_buf_free(outbuf);
+
+ return PLIST_ERR_SUCCESS;
+}
+
+struct _parse_ctx {
+ const char *pos;
+ const char *end;
+ plist_err_t err;
+};
+typedef struct _parse_ctx* parse_ctx;
+
+static inline int is_xml_ws(unsigned char c)
+{
+ return (c == ' ' || c == '\t' || c == '\r' || c == '\n');
+}
+
+static void parse_skip_ws(parse_ctx ctx)
+{
+ while (ctx->pos < ctx->end && is_xml_ws(*(ctx->pos))) {
+ ctx->pos++;
+ }
+}
+
+static void find_char(parse_ctx ctx, char c, int skip_quotes)
+{
+ while (ctx->pos < ctx->end && (*(ctx->pos) != c)) {
+ if (skip_quotes && (c != '"') && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
}
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_REAL))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->realval = atof((char *) strval);
- data->type = PLIST_REAL;
- data->length = 8;
- xmlFree(strval);
- continue;
+static void find_str(parse_ctx ctx, const char *str, size_t len, int skip_quotes)
+{
+ while (ctx->pos < (ctx->end - len)) {
+ if (!strncmp(ctx->pos, str, len)) {
+ break;
}
+ if (skip_quotes && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
+ }
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_DATE))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- time_t time = 0;
- if (strlen((const char*)strval) >= 11) {
- struct tm btime;
- parse_date((const char*)strval, &btime);
- time = mktime(&btime);
- }
- data->timeval.tv_sec = (long)time;
- data->timeval.tv_usec = 0;
- data->type = PLIST_DATE;
- data->length = sizeof(struct timeval);
- xmlFree(strval);
- continue;
+static void find_next(parse_ctx ctx, const char *nextchars, int numchars, int skip_quotes)
+{
+ int i = 0;
+ while (ctx->pos < ctx->end) {
+ if (skip_quotes && (*(ctx->pos) == '"')) {
+ ctx->pos++;
+ find_char(ctx, '"', 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while looking for matching double quote\n");
+ return;
+ }
+ if (*(ctx->pos) != '"') {
+ PLIST_XML_ERR("Unmatched double quote\n");
+ return;
+ }
+ }
+ for (i = 0; i < numchars; i++) {
+ if (*(ctx->pos) == nextchars[i]) {
+ return;
+ }
}
+ ctx->pos++;
+ }
+}
- if (!xmlStrcmp(node->name, XPLIST_STRING))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- len = strlen((char *) strval);
- type = xmlDetectCharEncoding(strval, len);
+typedef struct {
+ const char *begin;
+ size_t length;
+ int is_cdata;
+ void *next;
+} text_part_t;
- if (XML_CHAR_ENCODING_UTF8 == type || XML_CHAR_ENCODING_ASCII == type || XML_CHAR_ENCODING_NONE == type)
- {
- data->strval = strdup((char *) strval);
- data->type = PLIST_STRING;
- data->length = strlen(data->strval);
+static text_part_t* text_part_init(text_part_t* part, const char *begin, size_t length, int is_cdata)
+{
+ part->begin = begin;
+ part->length = length;
+ part->is_cdata = is_cdata;
+ part->next = NULL;
+ return part;
+}
+
+static void text_parts_free(text_part_t *tp)
+{
+ while (tp) {
+ text_part_t *tmp = tp;
+ tp = (text_part_t*)tp->next;
+ free(tmp);
+ }
+}
+
+static text_part_t* text_part_append(text_part_t* parts, const char *begin, size_t length, int is_cdata)
+{
+ text_part_t* newpart = (text_part_t*)malloc(sizeof(text_part_t));
+ assert(newpart);
+ parts->next = text_part_init(newpart, begin, length, is_cdata);
+ return newpart;
+}
+
+static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_len, int skip_ws, text_part_t *parts)
+{
+ const char *p = NULL;
+ const char *q = NULL;
+ text_part_t *last = NULL;
+
+ if (skip_ws) {
+ parse_skip_ws(ctx);
+ }
+ do {
+ p = ctx->pos;
+ find_char(ctx, '<', 0);
+ if (ctx->pos >= ctx->end || *ctx->pos != '<') {
+ PLIST_XML_ERR("EOF while looking for closing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ q = ctx->pos;
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing '%s'\n", p);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (*ctx->pos == '!') {
+ ctx->pos++;
+ if (ctx->pos >= ctx->end-1) {
+ PLIST_XML_ERR("EOF while parsing <! special tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
}
- xmlFree(strval);
- continue;
+ if (*ctx->pos == '-' && *(ctx->pos+1) == '-') {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ ctx->pos += 2;
+ find_str(ctx, "-->", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
+ PLIST_XML_ERR("EOF while looking for end of comment\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos += 3;
+ } else if (*ctx->pos == '[') {
+ ctx->pos++;
+ if (ctx->pos >= ctx->end - 8) {
+ PLIST_XML_ERR("EOF while parsing <[ tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ if (strncmp(ctx->pos, "CDATA[", 6) == 0) {
+ if (q-p > 0) {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ }
+ ctx->pos+=6;
+ p = ctx->pos;
+ find_str(ctx, "]]>", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "]]>", 3) != 0) {
+ PLIST_XML_ERR("EOF while looking for end of CDATA block\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ q = ctx->pos;
+ if (last) {
+ last = text_part_append(last, p, q-p, 1);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 1);
+ }
+ ctx->pos += 3;
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid special tag <[%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid special tag <!%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ } else if (*ctx->pos == '/') {
+ break;
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid tag <%.*s> encountered inside <%s> tag\n", (int)(ctx->pos - p), p, tag);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
}
+ } while (1);
+ ctx->pos++;
+ if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len) != 0) {
+ PLIST_XML_ERR("EOF or end tag mismatch\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos+=tag_len;
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing closing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ } else if (*ctx->pos != '>') {
+ PLIST_XML_ERR("Invalid closing tag; expected '>', found '%c'\n", *ctx->pos);
+ ctx->err = PLIST_ERR_PARSE;
+ return NULL;
+ }
+ ctx->pos++;
+ if (q > p) {
+ if (last) {
+ last = text_part_append(last, p, q-p, 0);
+ } else if (parts) {
+ last = text_part_init(parts, p, q-p, 0);
+ }
+ }
+ return parts;
+}
- if (!xmlStrcmp(node->name, XPLIST_KEY))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- data->strval = strdup((char *) strval);
- data->type = PLIST_KEY;
- data->length = strlen(data->strval);
- xmlFree(strval);
- continue;
+static int unescape_entities(char *str, size_t *length)
+{
+ size_t i = 0;
+ size_t len = *length;
+ while (len > 0 && i < len-1) {
+ if (str[i] == '&') {
+ char *entp = str + i + 1;
+ while (i < len && str[i] != ';') {
+ i++;
+ }
+ if (i >= len) {
+ PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n");
+ return -1;
+ }
+ size_t entlen = (size_t)(str+i - entp);
+ if (entlen > 0) {
+ if (entlen > 256) {
+ PLIST_XML_ERR("Rejecting absurdly large entity at &%.*s...\n", 8, entp);
+ return -1;
+ }
+ size_t bytelen = 1;
+ if (entlen == 3 && memcmp(entp, "amp", 3) == 0) {
+ /* the '&' is already there */
+ } else if (entlen == 4 && memcmp(entp, "apos", 4) == 0) {
+ *(entp-1) = '\'';
+ } else if (entlen == 4 && memcmp(entp, "quot", 4) == 0) {
+ *(entp-1) = '"';
+ } else if (entlen == 2 && memcmp(entp, "lt", 2) == 0) {
+ *(entp-1) = '<';
+ } else if (entlen == 2 && memcmp(entp, "gt", 2) == 0) {
+ *(entp-1) = '>';
+ } else if (*entp == '#') {
+ /* numerical character reference */
+ uint64_t val = 0;
+ char* ep = NULL;
+ if (entlen > 8) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ if (entlen >= 2 && (entp[1] == 'x' || entp[1] == 'X')) {
+ if (entlen < 3) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ val = strtoull(entp+2, &ep, 16);
+ } else {
+ if (entlen < 2) {
+ PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ val = strtoull(entp+1, &ep, 10);
+ }
+ if (val == 0 || val > 0x10FFFF || (size_t)(ep-entp) != entlen) {
+ PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ if (val >= 0xD800 && val <= 0xDFFF) {
+ PLIST_XML_ERR("Invalid numerical character reference found (surrogate): &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ /* convert to UTF8 */
+ if (val >= 0x10000) {
+ /* four bytes */
+ *(entp-1) = (char)(0xF0 + ((val >> 18) & 0x7));
+ *(entp+0) = (char)(0x80 + ((val >> 12) & 0x3F));
+ *(entp+1) = (char)(0x80 + ((val >> 6) & 0x3F));
+ *(entp+2) = (char)(0x80 + (val & 0x3F));
+ entp+=3;
+ bytelen = 4;
+ } else if (val >= 0x800) {
+ /* three bytes */
+ *(entp-1) = (char)(0xE0 + ((val >> 12) & 0xF));
+ *(entp+0) = (char)(0x80 + ((val >> 6) & 0x3F));
+ *(entp+1) = (char)(0x80 + (val & 0x3F));
+ entp+=2;
+ bytelen = 3;
+ } else if (val >= 0x80) {
+ /* two bytes */
+ *(entp-1) = (char)(0xC0 + ((val >> 6) & 0x1F));
+ *(entp+0) = (char)(0x80 + (val & 0x3F));
+ entp++;
+ bytelen = 2;
+ } else {
+ /* one byte */
+ *(entp-1) = (char)(val & 0x7F);
+ }
+ } else {
+ PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", (int)entlen, entp);
+ return -1;
+ }
+ memmove(entp, str+i+1, len - i); /* include '\0' */
+ size_t dec = entlen + 1 - bytelen;
+ size_t shrink = entlen + 2 - bytelen;
+ if (i < dec || len < shrink) {
+ PLIST_XML_ERR("Internal error: length underflow?!\n");
+ return -1;
+ }
+ i -= dec;
+ len -= shrink;
+ continue;
+ } else {
+ PLIST_XML_ERR("Invalid empty entity sequence &;\n");
+ return -1;
+ }
}
+ i++;
+ }
+ *length = len;
+ return 0;
+}
- if (!xmlStrcmp(node->name, XPLIST_DATA))
- {
- xmlChar *strval = xmlNodeGetContent(node);
- size_t size = 0;
- unsigned char *dec = base64decode((char*)strval, &size);
- data->buff = (uint8_t *) malloc(size * sizeof(uint8_t));
- memcpy(data->buff, dec, size * sizeof(uint8_t));
- free(dec);
- data->length = size;
- data->type = PLIST_DATA;
- xmlFree(strval);
- continue;
+static char* text_parts_get_content(text_part_t *tp, int unesc_entities, int trim_ws, size_t *length, int *requires_free)
+{
+ char *str = NULL;
+ size_t total_length = 0;
+
+ if (!tp) {
+ return NULL;
+ }
+ char *p;
+ if (requires_free && !tp->next && !unesc_entities && !trim_ws) {
+ *requires_free = 0;
+ if (length) {
+ *length = tp->length;
}
+ return (char*)tp->begin;
+ }
+ text_part_t *tmp = tp;
+ while (tp && tp->begin) {
+ total_length += tp->length;
+ tp = (text_part_t*)tp->next;
+ }
+ str = (char*)malloc(total_length + 1);
+ assert(str);
+ p = str;
+ tp = tmp;
+ while (tp && tp->begin) {
+ size_t len = tp->length;
+ memcpy(p, tp->begin, len);
+ p[len] = '\0';
+ if (!tp->is_cdata && unesc_entities) {
+ if (unescape_entities(p, &len) < 0) {
+ free(str);
+ return NULL;
+ }
+ }
+ p += len;
+ tp = (text_part_t*)tp->next;
+ }
+ *p = '\0';
+ if (trim_ws) {
+ char* start = str;
+ char* end = p;
+ while (start < end && is_xml_ws((unsigned char)start[0])) start++;
+ while (end > start && is_xml_ws((unsigned char)end[-1])) end--;
+ if (start != str) {
+ size_t newlen = (size_t)(end - start);
+ memmove(str, start, newlen);
+ str[newlen] = '\0';
+ p = str + newlen;
+ } else {
+ *end = '\0';
+ p = end;
+ }
+ }
+ if (length) {
+ *length = p - str;
+ }
+ if (requires_free) {
+ *requires_free = 1;
+ }
+ return str;
+}
- if (!xmlStrcmp(node->name, XPLIST_ARRAY))
- {
- data->type = PLIST_ARRAY;
- xml_to_node(node, &subnode);
- continue;
+static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist)
+{
+ char tag[16] = { 0 };
+ char *keyname = NULL;
+ plist_t subnode = NULL;
+ const char *p = NULL;
+ plist_t parent = NULL;
+
+ struct node_path_item {
+ const char *type;
+ struct node_path_item *prev;
+ };
+ struct node_path_item* node_path = NULL;
+ int depth = 0;
+
+ while (ctx->pos < ctx->end && !ctx->err) {
+ parse_skip_ws(ctx);
+ if (ctx->pos >= ctx->end) {
+ break;
+ }
+ if (*ctx->pos != '<') {
+ p = ctx->pos;
+ find_next(ctx, " \t\r\n", 4, 0);
+ PLIST_XML_ERR("Expected: opening tag, found: %.*s\n", (int)(ctx->pos - p), p);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos++;
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
}
- if (!xmlStrcmp(node->name, XPLIST_DICT))
- {
- data->type = PLIST_DICT;
- xml_to_node(node, &subnode);
+ if (*(ctx->pos) == '?') {
+ find_str(ctx, "?>", 2, 1);
+ if (ctx->pos > ctx->end-2) {
+ PLIST_XML_ERR("EOF while looking for <? tag closing marker\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strncmp(ctx->pos, "?>", 2) != 0) {
+ PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos += 2;
+ continue;
+ } else if (*(ctx->pos) == '!') {
+ /* comment or DTD */
+ if (((ctx->end - ctx->pos) > 3) && !strncmp(ctx->pos, "!--", 3)) {
+ ctx->pos += 3;
+ find_str(ctx, "-->", 3, 0);
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
+ PLIST_XML_ERR("Couldn't find end of comment\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos+=3;
+ } else if (((ctx->end - ctx->pos) > 8) && !strncmp(ctx->pos, "!DOCTYPE", 8)) {
+ int embedded_dtd = 0;
+ ctx->pos+=8;
+ while (ctx->pos < ctx->end) {
+ find_next(ctx, " \t\r\n[>", 6, 1);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("EOF while parsing !DOCTYPE\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos == '[') {
+ embedded_dtd = 1;
+ break;
+ } else if (*ctx->pos == '>') {
+ /* end of DOCTYPE found already */
+ ctx->pos++;
+ break;
+ } else {
+ parse_skip_ws(ctx);
+ }
+ }
+ if (embedded_dtd) {
+ find_str(ctx, "]>", 2, 1);
+ if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) {
+ PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ ctx->pos += 2;
+ }
+ } else {
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t>", 5, 1);
+ PLIST_XML_ERR("Invalid or incomplete special tag <%.*s> encountered\n", (int)(ctx->pos - p), p);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
continue;
+ } else {
+ int is_empty = 0;
+ int closing_tag = 0;
+ p = ctx->pos;
+ find_next(ctx, " \r\n\t<>", 6, 0);
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t taglen = ctx->pos - p;
+ if (taglen >= sizeof(tag)) {
+ PLIST_XML_ERR("Unexpected tag <%.*s> encountered\n", (int)taglen, p);
+ ctx->pos = ctx->end;
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ memcpy(tag, p, taglen);
+ tag[taglen] = '\0';
+ if (*ctx->pos != '>') {
+ find_next(ctx, "<>", 2, 1);
+ }
+ if (ctx->pos >= ctx->end) {
+ PLIST_XML_ERR("Unexpected EOF while parsing XML\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*ctx->pos != '>') {
+ PLIST_XML_ERR("Missing '>' for tag <%s\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (*(ctx->pos-1) == '/') {
+ size_t idx = ctx->pos - p - 1;
+ if (idx < taglen)
+ tag[idx] = '\0';
+ is_empty = 1;
+ }
+ ctx->pos++;
+ if (!strcmp(tag, "plist")) {
+ if (!node_path && *plist) {
+ PLIST_XML_ERR("Multiple top-level <plist> elements encountered\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Empty plist tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+
+ struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
+ if (!path_item) {
+ PLIST_XML_ERR("out of memory when allocating node path item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ path_item->type = "plist";
+ path_item->prev = node_path;
+ node_path = path_item;
+
+ continue;
+ } else if (!strcmp(tag, "/plist")) {
+ if (!*plist) {
+ PLIST_XML_ERR("encountered empty plist tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!node_path) {
+ PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strcmp(node_path->type, tag+1) != 0) {
+ PLIST_XML_ERR("mismatching closing tag <%s> found for opening tag <%s>\n", tag, node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)node_path->prev;
+ free(path_item);
+ continue;
+ }
+ if (tag[0] == '/') {
+ closing_tag = 1;
+ goto handle_closing;
+ }
+ plist_data_t data = plist_new_plist_data();
+ if (!data) {
+ PLIST_XML_ERR("failed to allocate plist data\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+ subnode = plist_new_node(data);
+ if (!subnode) {
+ PLIST_XML_ERR("failed to create node\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+
+ if (!strcmp(tag, XPLIST_DICT)) {
+ data->type = PLIST_DICT;
+ } else if (!strcmp(tag, XPLIST_ARRAY)) {
+ data->type = PLIST_ARRAY;
+ } else if (!strcmp(tag, XPLIST_INT)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ char *str = str_content;
+ int is_negative = 0;
+ if ((str[0] == '-') || (str[0] == '+')) {
+ if (str[0] == '-') {
+ is_negative = 1;
+ }
+ str++;
+ }
+ errno = 0;
+ char* endp = NULL;
+ data->intval = strtoull(str, &endp, 0);
+ if (errno == ERANGE) {
+ PLIST_XML_ERR("Integer overflow detected while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (endp == str || *endp != '\0') {
+ PLIST_XML_ERR("Invalid characters while parsing integer value '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (is_negative && data->intval > ((uint64_t)INT64_MAX + 1)) {
+ PLIST_XML_ERR("Signed integer value out of range while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (is_negative || (data->intval <= INT64_MAX)) {
+ uint64_t v = data->intval;
+ if (is_negative) {
+ v = -v;
+ }
+ data->intval = v;
+ data->length = 8;
+ } else {
+ data->length = 16;
+ }
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_INT " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->type = PLIST_INT;
+ } else if (!strcmp(tag, XPLIST_REAL)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ errno = 0;
+ char *endp = NULL;
+ data->realval = strtod(str_content, &endp);
+ if (errno == ERANGE) {
+ PLIST_XML_ERR("Invalid range while parsing value for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ if (endp == str_content || *endp != '\0') {
+ PLIST_XML_ERR("Could not parse value for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+
+ }
+ if (!isfinite(data->realval)) {
+ PLIST_XML_ERR("Invalid real value while parsing '%.20s'\n", str_content);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_REAL " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->type = PLIST_REAL;
+ data->length = 8;
+ } else if (!strcmp(tag, XPLIST_TRUE)) {
+ if (!is_empty) {
+ get_text_parts(ctx, tag, taglen, 1, NULL);
+ }
+ data->type = PLIST_BOOLEAN;
+ data->boolval = 1;
+ data->length = 1;
+ } else if (!strcmp(tag, XPLIST_FALSE)) {
+ if (!is_empty) {
+ get_text_parts(ctx, tag, taglen, 1, NULL);
+ }
+ data->type = PLIST_BOOLEAN;
+ data->boolval = 0;
+ data->length = 1;
+ } else if (!strcmp(tag, XPLIST_STRING) || !strcmp(tag, XPLIST_KEY)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 0, &first_part);
+ char *str = NULL;
+ size_t length = 0;
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ str = text_parts_get_content(tp, 1, 0, &length, NULL);
+ text_parts_free((text_part_t*)first_part.next);
+ if (!str) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) {
+ keyname = str;
+ plist_free(subnode);
+ subnode = NULL;
+ continue;
+ } else {
+ data->strval = str;
+ data->length = length;
+ }
+ } else {
+ data->strval = strdup("");
+ data->length = 0;
+ }
+ data->type = PLIST_STRING;
+ } else if (!strcmp(tag, XPLIST_DATA)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (tp->begin) {
+ int requires_free = 0;
+ char *str_content = text_parts_get_content(tp, 0, 0, NULL, &requires_free);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ size_t size = tp->length;
+ if (size > 0) {
+ data->buff = base64decode(str_content, &size);
+ if (!data->buff) {
+ text_parts_free((text_part_t*)first_part.next);
+ PLIST_XML_ERR("failed to decode base64 stream\n");
+ ctx->err = PLIST_ERR_NO_MEM;
+ goto err_out;
+ }
+ data->length = size;
+ }
+
+ if (requires_free) {
+ free(str_content);
+ }
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ }
+ data->type = PLIST_DATA;
+ } else if (!strcmp(tag, XPLIST_DATE)) {
+ if (!is_empty) {
+ text_part_t first_part = { NULL, 0, 0, NULL };
+ text_part_t *tp = get_text_parts(ctx, tag, taglen, 1, &first_part);
+ if (!tp) {
+ PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ Time64_T timev = 0;
+ if (tp->begin) {
+ char *str_content = text_parts_get_content(tp, 0, 1, NULL, NULL);
+ if (!str_content) {
+ PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ struct TM btime;
+ if (parse_date(str_content, &btime) < 0) {
+ PLIST_XML_ERR("Failed to parse date node\n");
+ text_parts_free((text_part_t*)first_part.next);
+ ctx->err = PLIST_ERR_PARSE;
+ free(str_content);
+ goto err_out;
+ }
+ timev = timegm64(&btime);
+ free(str_content);
+ } else {
+ is_empty = 1;
+ }
+ text_parts_free((text_part_t*)first_part.next);
+ data->realval = (double)(timev - MAC_EPOCH);
+ }
+ if (is_empty) {
+ PLIST_XML_ERR("Encountered empty " XPLIST_DATE " tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ data->length = sizeof(double);
+ data->type = PLIST_DATE;
+ } else {
+ PLIST_XML_ERR("Unexpected tag <%s%s> encountered\n", tag, (is_empty) ? "/" : "");
+ ctx->pos = ctx->end;
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (subnode && !closing_tag) {
+ if (!*plist) {
+ /* first value node inside <plist> */
+ *plist = subnode;
+
+ if (data->type == PLIST_DICT || data->type == PLIST_ARRAY) {
+ parent = subnode;
+ } else {
+ /* scalar root: keep parsing until </plist> */
+ parent = NULL;
+ }
+ } else if (parent) {
+ switch (plist_get_node_type(parent)) {
+ case PLIST_DICT:
+ if (!keyname) {
+ PLIST_XML_ERR("missing key name while adding dict item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ plist_dict_set_item(parent, keyname, subnode);
+ break;
+ case PLIST_ARRAY:
+ plist_array_append_item(parent, subnode);
+ break;
+ default:
+ /* should not happen */
+ PLIST_XML_ERR("parent is not a structured node\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ } else {
+ /* We already produced root, and we're not inside a container */
+ PLIST_XML_ERR("Unexpected tag <%s> found while </plist> is expected\n", tag);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) {
+ if (depth >= PLIST_MAX_NESTING_DEPTH) {
+ PLIST_XML_ERR("maximum nesting depth (%u) exceeded\n", (unsigned)PLIST_MAX_NESTING_DEPTH);
+ ctx->err = PLIST_ERR_MAX_NESTING;
+ goto err_out;
+ }
+ struct node_path_item *path_item = (struct node_path_item*)malloc(sizeof(struct node_path_item));
+ if (!path_item) {
+ PLIST_XML_ERR("out of memory when allocating node path item\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ path_item->type = (data->type == PLIST_DICT) ? XPLIST_DICT : XPLIST_ARRAY;
+ path_item->prev = node_path;
+ node_path = path_item;
+
+ depth++;
+ parent = subnode;
+ } else {
+ /* If we inserted a child scalar into a container, nothing to push. */
+ }
+ subnode = NULL;
+ }
+handle_closing:
+ if (closing_tag) {
+ if (!node_path) {
+ PLIST_XML_ERR("node path is empty while trying to match closing tag with opening tag\n");
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+ if (strcmp(node_path->type, tag+1) != 0) {
+ PLIST_XML_ERR("unexpected %s found (for opening %s)\n", tag, node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
+ goto err_out;
+ }
+
+ /* When closing a dictionary, convert a single-entry
+ { "CF$UID" : <integer> } dictionary into a PLIST_UID node.
+ Perform the conversion before moving to the parent node. */
+ if (!strcmp(node_path->type, XPLIST_DICT) && parent && plist_get_node_type(parent) == PLIST_DICT) {
+ if (plist_dict_get_size(parent) == 1) {
+ plist_t uid = plist_dict_get_item(parent, "CF$UID");
+ if (uid) {
+ uint64_t val = 0;
+ if (plist_get_node_type(uid) != PLIST_INT) {
+ ctx->err = PLIST_ERR_PARSE;
+ PLIST_XML_ERR("Invalid node type for CF$UID dict entry (must be PLIST_INT)\n");
+ goto err_out;
+ }
+ plist_get_uint_val(uid, &val);
+ plist_set_uid_val(parent, val);
+ }
+ }
+ }
+
+ if (depth > 0) depth--;
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)node_path->prev;
+ free(path_item);
+ parent = (parent) ? ((node_t)parent)->parent : NULL;
+ }
+ free(keyname);
+ keyname = NULL;
+ plist_free(subnode);
+ subnode = NULL;
}
}
-}
-void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
-{
- xmlDocPtr plist_doc = NULL;
- xmlNodePtr root_node = NULL;
- struct xml_node root = { NULL, 0 };
- int size = 0;
-
- if (!plist || !plist_xml || *plist_xml)
- return;
- plist_doc = new_xml_plist();
- root_node = xmlDocGetRootElement(plist_doc);
- root.xml = root_node;
-
- char *current_locale = setlocale(LC_NUMERIC, NULL);
- char *saved_locale = NULL;
- if (current_locale) {
- saved_locale = strdup(current_locale);
- }
- if (saved_locale) {
- setlocale(LC_NUMERIC, "POSIX");
- }
- node_to_xml(plist, &root);
-
- xmlChar* tmp = NULL;
- xmlDocDumpMemory(plist_doc, &tmp, &size);
- if (size >= 0 && tmp)
- {
- /* make sure to copy the terminating 0-byte */
- *plist_xml = (char*)malloc((size+1) * sizeof(char));
- memcpy(*plist_xml, tmp, size+1);
- *length = size;
- xmlFree(tmp);
- tmp = NULL;
+ if (node_path) {
+ PLIST_XML_ERR("EOF encountered while </%s> was expected\n", node_path->type);
+ ctx->err = PLIST_ERR_PARSE;
}
- xmlFreeDoc(plist_doc);
- if (saved_locale) {
- setlocale(LC_NUMERIC, saved_locale);
- free(saved_locale);
+err_out:
+ free(keyname);
+ plist_free(subnode);
+
+ /* clean up node_path if required */
+ while (node_path) {
+ struct node_path_item *path_item = node_path;
+ node_path = (struct node_path_item*)path_item->prev;
+ free(path_item);
}
+
+ if (ctx->err != PLIST_ERR_SUCCESS) {
+ plist_free(*plist);
+ *plist = NULL;
+ return ctx->err;
+ }
+
+ /* check if we have a UID "dict" so we can replace it with a proper UID node */
+ if (PLIST_IS_DICT(*plist) && plist_dict_get_size(*plist) == 1) {
+ plist_t value = plist_dict_get_item(*plist, "CF$UID");
+ if (PLIST_IS_INT(value)) {
+ uint64_t u64val = 0;
+ plist_get_uint_val(value, &u64val);
+ plist_free(*plist);
+ *plist = plist_new_uid(u64val);
+ }
+ }
+
+ return PLIST_ERR_SUCCESS;
}
-void plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
+plist_err_t plist_from_xml(const char *plist_xml, uint32_t length, plist_t * plist)
{
- xmlDocPtr plist_doc = xmlParseMemory(plist_xml, length);
- xmlNodePtr root_node = xmlDocGetRootElement(plist_doc);
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
+ if (!plist_xml || (length == 0)) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ struct _parse_ctx ctx = { plist_xml, plist_xml + length, PLIST_ERR_SUCCESS };
- xml_to_node(root_node, plist);
- xmlFreeDoc(plist_doc);
+ return node_from_xml(&ctx, plist);
}
diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt
deleted file mode 100644
index 379cb95..0000000
--- a/swig/CMakeLists.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-INCLUDE( ${SWIG_USE_FILE} )
-
-SET(CMAKE_SWIG_FLAGS -Werror -Wall -modern)
-
-INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_PATH} )
-
-SET_SOURCE_FILES_PROPERTIES(plist.i PROPERTIES CPLUSPLUS ON)
-SWIG_ADD_MODULE( plist python plist.i )
-SWIG_LINK_LIBRARIES( plist plist plist++ ${PYTHON_LIBRARIES} )
-
-EXEC_PROGRAM("${PYTHON_EXECUTABLE}"
- ARGS "-c 'try:\n import distutils.sysconfig; print distutils.sysconfig.get_python_lib(plat_specific=1)\nexcept: pass\n'"
- OUTPUT_VARIABLE DISTUTILS_PYTHON_ILIBRARY_PATH
- )
-
-INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/_plist${CMAKE_SHARED_MODULE_SUFFIX}
- DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ )
-INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/plist.py
- DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ )
-INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
- DESTINATION ${DISTUTILS_PYTHON_ILIBRARY_PATH}/plist/ )
-INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/plist.i
- DESTINATION include/plist/swig COMPONENT dev)
-
diff --git a/swig/__init__.py b/swig/__init__.py
deleted file mode 100644
index f4c0dbb..0000000
--- a/swig/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from plist import *
diff --git a/swig/plist.i b/swig/plist.i
deleted file mode 100644
index 7755cc5..0000000
--- a/swig/plist.i
+++ /dev/null
@@ -1,273 +0,0 @@
- /* swig.i */
- %module plist
- %feature("autodoc", "1");
- %{
- /* Includes the header in the wrapper code */
- #include <plist/plist++.h>
- #include <cstddef>
- %}
-
-%include "std_string.i"
-%include "stdint.i"
-
-%typemap(out) std::vector<char> {
- $result = SWIG_FromCharPtrAndSize((const char*)&($1[0]),(size_t)($1.size()));
-}
-
-%typemap(in) (const std::vector<char>&)
-{
- char* buffer = NULL;
- size_t length = 0;
- SWIG_AsCharPtrAndSize($input, &buffer, &length, NULL);
- $1 = new std::vector<char>(buffer, buffer + length - 1);
-}
-
-#if SWIGPYTHON
-//for datetime in python
-%{
-#include <ctime>
-#include <datetime.h>
-%}
-
-%typemap(typecheck,precedence=SWIG_TYPECHECK_POINTER) timeval {
- PyDateTime_IMPORT;
- $1 = PyDateTime_Check($input) ? 1 : 0;
-}
-
-%typemap(out) timeval {
- struct tm* t = gmtime ( &$1.tv_sec );
- if (t)
- {
- PyDateTime_IMPORT;
- $result = PyDateTime_FromDateAndTime(t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, $1.tv_usec);
- }
-}
-
-%typemap(in) (timeval t)
-{
- PyDateTime_IMPORT;
- if (!PyDateTime_Check($input)) {
- PyErr_SetString(PyExc_ValueError,"Expected a datetime");
- return NULL;
- }
- struct tm t = {
- PyDateTime_DATE_GET_SECOND($input),
- PyDateTime_DATE_GET_MINUTE($input),
- PyDateTime_DATE_GET_HOUR($input),
- PyDateTime_GET_DAY($input),
- PyDateTime_GET_MONTH($input)-1,
- PyDateTime_GET_YEAR($input)-1900,
- 0,0,0
- };
- timeval ret = {(int)mktime(&t), PyDateTime_DATE_GET_MICROSECOND($input)};
- $1 = ret;
-}
-#endif
-
-%apply SWIGTYPE *DYNAMIC { PList::Node* };
-%apply SWIGTYPE *DYNAMIC { PList::Structure* };
-
-%{
-static swig_type_info *Node_dynamic(void **ptr)
-{
- PList::Node* node = dynamic_cast<PList::Node *>((PList::Node *) *ptr);
- if (node)
- {
- plist_type type = node->GetType();
- switch(type)
- {
- case PLIST_DICT:
- *ptr = dynamic_cast<PList::Dictionary *>(node);
- return SWIGTYPE_p_PList__Dictionary;
- case PLIST_ARRAY:
- *ptr = dynamic_cast<PList::Array *>(node);
- return SWIGTYPE_p_PList__Array;
- case PLIST_BOOLEAN:
- *ptr = dynamic_cast<PList::Boolean *>(node);
- return SWIGTYPE_p_PList__Boolean;
- case PLIST_UINT:
- *ptr = dynamic_cast<PList::Integer *>(node);
- return SWIGTYPE_p_PList__Integer;
- case PLIST_REAL:
- *ptr = dynamic_cast<PList::Real *>(node);
- return SWIGTYPE_p_PList__Real;
- case PLIST_STRING:
- *ptr = dynamic_cast<PList::String *>(node);
- return SWIGTYPE_p_PList__String;
- case PLIST_DATE:
- *ptr = dynamic_cast<PList::Date *>(node);
- return SWIGTYPE_p_PList__Date;
- case PLIST_DATA:
- *ptr = dynamic_cast<PList::Data *>(node);
- return SWIGTYPE_p_PList__Data;
- default:
- break;
- }
- }
- return 0;
-}
-%}
-
-// Register the above casting function
-DYNAMIC_CAST(SWIGTYPE_p_PList__Node, Node_dynamic);
-DYNAMIC_CAST(SWIGTYPE_p_PList__Structure, Node_dynamic);
-
-%include "std_map.i"
-// Instantiate templates used by example
-namespace std {
- %template(PairStringNodePtr) std::pair<string, PList::Node*>;
- %template(MapStringNodePtr) map<string,PList::Node*>;
-}
-
-#if SWIGPYTHON
-%rename(__assign__) *::operator=;
-%rename(__getitem__) *::operator[];
-%rename(__delitem__) *::Remove;
-%rename(__setitem__) PList::Dictionary::Insert;
-%rename(__deepcopy__) *::Clone;
-%rename(__len__) *::GetSize;
-%rename(get_type) *::GetType;
-%rename(set_value) *::SetValue;
-%rename(get_value) *::GetValue;
-%rename(to_xml) *::ToXml;
-%rename(to_bin) *::ToBin;
-%rename(from_xml) *::FromXml;
-%rename(from_bin) *::FromBin;
-%rename(append) *::Append;
-%rename(insert) PList::Array::Insert;
-#endif
-
-%ignore GetPlist();
-%ignore Boolean(plist_t);
-%ignore Integer(plist_t);
-%ignore Real(plist_t);
-%ignore String(plist_t);
-%ignore Data(plist_t);
-%ignore Date(plist_t);
-%ignore Array(plist_t);
-%ignore Dictionary(plist_t);
-%ignore Begin();
-%ignore End();
-%ignore Find();
-
-%include <plist/Node.h>
-%include <plist/Boolean.h>
-%include <plist/Integer.h>
-%include <plist/Real.h>
-%include <plist/String.h>
-%include <plist/Data.h>
-%include <plist/Date.h>
-%include <plist/Structure.h>
-%include <plist/Array.h>
-%include <plist/Dictionary.h>
-
-typedef enum {
- PLIST_BOOLEAN,
- PLIST_UINT,
- PLIST_REAL,
- PLIST_STRING,
- PLIST_ARRAY,
- PLIST_DICT,
- PLIST_DATE,
- PLIST_DATA,
- PLIST_KEY,
- PLIST_NONE
-} plist_type;
-
-#if SWIGPYTHON
-
-#if SWIG_VERSION <= 0x010336
-#define SwigPyIterator PySwigIterator
-#endif
-
-%extend PList::Dictionary {
-
- %newobject key_iterator(PyObject **PYTHON_SELF);
- swig::SwigPyIterator* key_iterator(PyObject **PYTHON_SELF) {
- return swig::make_output_key_iterator(self->Begin(), self->Begin(), self->End(), *PYTHON_SELF);
- }
-
- %newobject value_iterator(PyObject **PYTHON_SELF);
- swig::SwigPyIterator* value_iterator(PyObject **PYTHON_SELF) {
- return swig::make_output_value_iterator(self->Begin(), self->Begin(), self->End(), *PYTHON_SELF);
- }
-
- iterator iteritems()
- {
- return self->Begin();
- }
-
- bool has_key(const std::string& key) const {
- PList::Dictionary* dict = const_cast<PList::Dictionary*>(self);
- PList::Dictionary::iterator i = dict->Find(key);
- return i != dict->End();
- }
-
- PyObject* keys() {
- uint32_t size = self->GetSize();
- int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1;
- if (pysize < 0) {
- SWIG_PYTHON_THREAD_BEGIN_BLOCK;
- PyErr_SetString(PyExc_OverflowError,
- "map size not valid in python");
- SWIG_PYTHON_THREAD_END_BLOCK;
- return NULL;
- }
- PyObject* keyList = PyList_New(pysize);
- PList::Dictionary::iterator i = self->Begin();
- for (int j = 0; j < pysize; ++i, ++j) {
- PyList_SET_ITEM(keyList, j, swig::from(i->first));
- }
- return keyList;
- }
-
- PyObject* values() {
- uint32_t size = self->GetSize();
- int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1;
- if (pysize < 0) {
- SWIG_PYTHON_THREAD_BEGIN_BLOCK;
- PyErr_SetString(PyExc_OverflowError,
- "map size not valid in python");
- SWIG_PYTHON_THREAD_END_BLOCK;
- return NULL;
- }
- PyObject* valList = PyList_New(pysize);
- PList::Dictionary::iterator i = self->Begin();
- for (int j = 0; j < pysize; ++i, ++j) {
- PList::Node *second = i->second;
- PyObject *down = SWIG_NewPointerObj(SWIG_as_voidptr(second), SWIG_TypeDynamicCast(SWIGTYPE_p_PList__Node, SWIG_as_voidptrptr(&second)), 0 | 0 );
- PyList_SET_ITEM(valList, j, down);
- }
- return valList;
- }
-
- PyObject* items() {
- uint32_t size = self->GetSize();
- int pysize = (size <= (uint32_t) INT_MAX) ? (int) size : -1;
- if (pysize < 0) {
- SWIG_PYTHON_THREAD_BEGIN_BLOCK;
- PyErr_SetString(PyExc_OverflowError,
- "map size not valid in python");
- SWIG_PYTHON_THREAD_END_BLOCK;
- return NULL;
- }
- PyObject* itemList = PyList_New(pysize);
- PList::Dictionary::iterator i = self->Begin();
- for (int j = 0; j < pysize; ++i, ++j) {
- PyObject *item = PyTuple_New(2);
- PList::Node *second = i->second;
- PyObject *down = SWIG_NewPointerObj(SWIG_as_voidptr(second), SWIG_TypeDynamicCast(SWIGTYPE_p_PList__Node, SWIG_as_voidptrptr(&second)), 0 | 0 );
- PyTuple_SetItem(item, 0, swig::from(i->first));
- PyTuple_SetItem(item, 1, down);
- PyList_SET_ITEM(itemList, j, item);
- }
- return itemList;
- }
-
- %pythoncode {def __iter__(self): return self.key_iterator()}
- %pythoncode {def iterkeys(self): return self.key_iterator()}
- %pythoncode {def itervalues(self): return self.value_iterator()}
-}
-
-#undef SwigPyIterator
-#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
deleted file mode 100644
index 1b3d2f7..0000000
--- a/test/CMakeLists.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-
-#build the test executable
-INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/libcnary/include )
-
-SET(plist_test_SRC
- plist_test.c)
-SET(plist_cmp_SRC
- plist_cmp.c)
-ADD_EXECUTABLE(plist_test ${plist_test_SRC})
-TARGET_LINK_LIBRARIES(plist_test plist)
-ADD_EXECUTABLE(plist_cmp ${plist_cmp_SRC})
-TARGET_LINK_LIBRARIES(plist_cmp plist)
-
-INCLUDE(CTest)
-
-SET(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR})
-ADD_TEST(Empty plist_test ${TEST_DATA_DIR}/data/1.plist)
-ADD_TEST(Small plist_test ${TEST_DATA_DIR}/data/2.plist)
-ADD_TEST(Medium plist_test ${TEST_DATA_DIR}/data/3.plist)
-ADD_TEST(Large plist_test ${TEST_DATA_DIR}/data/4.plist)
-ADD_TEST(Huge plist_test ${TEST_DATA_DIR}/data/5.plist)
-ADD_TEST(Big_Array plist_test ${TEST_DATA_DIR}/data/6.plist)
-
-ADD_TEST(EmptyCmp plist_cmp ${TEST_DATA_DIR}/data/1.plist ${TEST_DATA_DIR}/data/1.plist.out)
-ADD_TEST(SmallCmp plist_cmp ${TEST_DATA_DIR}/data/2.plist ${TEST_DATA_DIR}/data/2.plist.out)
-ADD_TEST(MediumCmp plist_cmp ${TEST_DATA_DIR}/data/3.plist ${TEST_DATA_DIR}/data/3.plist.out)
-ADD_TEST(LargeCmp plist_cmp ${TEST_DATA_DIR}/data/4.plist ${TEST_DATA_DIR}/data/4.plist.out)
-ADD_TEST(HugeCmp plist_cmp ${TEST_DATA_DIR}/data/5.plist ${TEST_DATA_DIR}/data/5.plist.out)
-ADD_TEST(Big_ArrayCmp plist_cmp ${TEST_DATA_DIR}/data/6.plist ${TEST_DATA_DIR}/data/6.plist.out)
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..f9f21e4
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,146 @@
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/libcnary/include
+AM_CPPFLAGS = $(AM_CFLAGS)
+
+AM_LDFLAGS =
+
+noinst_PROGRAMS = \
+ plist_cmp \
+ plist_test \
+ plist_test++ \
+ integer_set_test \
+ plist_btest \
+ plist_jtest \
+ plist_otest \
+ xml_behavior_test
+
+plist_cmp_SOURCES = plist_cmp.c
+plist_cmp_LDADD = \
+ $(top_builddir)/src/libplist-2.0.la \
+ $(top_builddir)/libcnary/libcnary.la
+
+plist_test___SOURCES = plist_test++.cpp
+plist_test___LDADD = $(top_builddir)/src/libplist++-2.0.la
+
+plist_test_SOURCES = plist_test.c
+plist_test_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+integer_set_test_SOURCES = integer_set.c
+integer_set_test_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+plist_btest_SOURCES = plist_btest.c
+plist_btest_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+plist_jtest_SOURCES = plist_jtest.c
+plist_jtest_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+plist_otest_SOURCES = plist_otest.c
+plist_otest_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+xml_behavior_test_SOURCES = xml_behavior_test.c
+xml_behavior_test_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+TESTS = \
+ empty.test \
+ small.test \
+ medium.test \
+ large.test \
+ huge.test \
+ bigarray.test \
+ empty++.test \
+ small++.test \
+ medium++.test \
+ large++.test \
+ huge++.test \
+ bigarray++.test \
+ dates.test \
+ timezone1.test \
+ timezone2.test \
+ signedunsigned1.test \
+ signedunsigned2.test \
+ signedunsigned3.test \
+ hex.test \
+ order.test \
+ recursion.test \
+ entities.test \
+ empty_keys.test \
+ amp.test \
+ invalid_tag.test \
+ cdata.test \
+ offsetsize.test \
+ refsize.test \
+ malformed_dict.test \
+ uid.test \
+ integer_set.test \
+ json1.test \
+ json2.test \
+ json3.test \
+ json-invalid-types.test \
+ json-int64-min-max.test \
+ ostep1.test \
+ ostep2.test \
+ ostep-strings.test \
+ ostep-comments.test \
+ ostep-invalid-types.test \
+ xml_behavior.test
+
+EXTRA_DIST = \
+ $(TESTS) \
+ data/1.plist \
+ data/2.plist \
+ data/3.plist \
+ data/4.plist \
+ data/5.plist \
+ data/6.plist \
+ data/7.plist \
+ data/amp.plist \
+ data/cdata.plist \
+ data/dictref1byte.bplist \
+ data/dictref2bytes.bplist \
+ data/dictref3bytes.bplist \
+ data/dictref4bytes.bplist \
+ data/dictref5bytes.bplist \
+ data/dictref6bytes.bplist \
+ data/dictref7bytes.bplist \
+ data/dictref8bytes.bplist \
+ data/empty_keys.plist \
+ data/entities.plist \
+ data/hex.plist \
+ data/invalid_tag.plist \
+ data/malformed_dict.bplist \
+ data/off1byte.bplist \
+ data/off2bytes.bplist \
+ data/off3bytes.bplist \
+ data/off4bytes.bplist \
+ data/off5bytes.bplist \
+ data/off6bytes.bplist \
+ data/off7bytes.bplist \
+ data/off8bytes.bplist \
+ data/offxml.plist \
+ data/order.bplist \
+ data/order.plist \
+ data/recursion.bplist \
+ data/signed.bplist \
+ data/signed.plist \
+ data/signedunsigned.bplist \
+ data/signedunsigned.plist \
+ data/unsigned.bplist \
+ data/unsigned.plist \
+ data/uid.bplist \
+ data/data.bplist \
+ data/j1.json \
+ data/j2.json \
+ data/int64_min_max.json \
+ data/o1.ostep \
+ data/o2.ostep \
+ data/o3.ostep \
+ data/test.strings
+
+TESTS_ENVIRONMENT = \
+ top_srcdir=$(top_srcdir) \
+ top_builddir=$(top_builddir)
+
+clean-local:
+ if test -d $(top_builddir)/test/data; then cd $(top_builddir)/test/data && rm -f *.out *.bin *.xml; fi
diff --git a/test/amp.test b/test/amp.test
new file mode 100755
index 0000000..3678f27
--- /dev/null
+++ b/test/amp.test
@@ -0,0 +1,16 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=amp.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/amp.test.out
+
+rm -rf $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+# test succeeds if plistutil fails
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/test/bigarray++.test b/test/bigarray++.test
new file mode 100755
index 0000000..78f38ef
--- /dev/null
+++ b/test/bigarray++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=6.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/bigarray++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray++.test.out
diff --git a/test/bigarray.test b/test/bigarray.test
new file mode 100755
index 0000000..1b88d43
--- /dev/null
+++ b/test/bigarray.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=6.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
diff --git a/test/cdata.test b/test/cdata.test
new file mode 100755
index 0000000..b4f3ed2
--- /dev/null
+++ b/test/cdata.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=cdata.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/cdata.test.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0
diff --git a/test/data/1.plist b/test/data/1.plist
index c7135e9..82a112b 100644
--- a/test/data/1.plist
+++ b/test/data/1.plist
@@ -16,20 +16,20 @@
<string></string>
<string></string>
</array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string/>
<key>Boolean</key>
<false/>
<key>Another Boolean</key>
<true/>
<key>Some Int</key>
- <integer></integer>
+ <integer>0</integer>
<key>Some Real</key>
- <real></real>
+ <real>1e4</real>
<key>Some Date</key>
- <date></date>
+ <date>1970-01-01T00:00:00Z</date>
<key>Some Data</key>
<data>
</data>
- <key>Some Entities</key>
- <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
</dict>
</plist>
diff --git a/test/data/2.plist b/test/data/2.plist
index 169b822..7c62067 100644
--- a/test/data/2.plist
+++ b/test/data/2.plist
@@ -16,6 +16,8 @@
<string>język polski</string>
<string>हिनà¥à¤¦à¥€</string>
</array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
<key>Boolean</key>
<false/>
<key>Another Boolean</key>
diff --git a/test/data/3.plist b/test/data/3.plist
index 6d16191..249e7ce 100644
--- a/test/data/3.plist
+++ b/test/data/3.plist
@@ -16,6 +16,8 @@
<string>język polski</string>
<string>हिनà¥à¤¦à¥€</string>
</array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
<key>Boolean</key>
<false/>
<key>Another Boolean</key>
diff --git a/test/data/4.plist b/test/data/4.plist
index de7c488..a04a292 100644
--- a/test/data/4.plist
+++ b/test/data/4.plist
@@ -16,6 +16,8 @@
<string>język polski</string>
<string>हिनà¥à¤¦à¥€</string>
</array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
<key>Boolean</key>
<false/>
<key>Another Boolean</key>
diff --git a/test/data/5.plist b/test/data/5.plist
index 647c648..9dc9109 100644
--- a/test/data/5.plist
+++ b/test/data/5.plist
@@ -16,6 +16,8 @@
<string>język polski</string>
<string>हिनà¥à¤¦à¥€</string>
</array>
+ <key>Keys &amp; &quot;entities&quot;</key>
+ <string>hellow world &amp; others &lt;nodes&gt; are &quot;fun!?&apos;</string>
<key>Boolean</key>
<false/>
<key>Another Boolean</key>
diff --git a/test/data/7.plist b/test/data/7.plist
new file mode 100644
index 0000000..d8b441d
--- /dev/null
+++ b/test/data/7.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Time1</key>
+ <date>2010-11-12T13:14:15Z</date>
+ <key>Time2</key>
+ <date>2008-07-06T05:04:03Z</date>
+ <key>Time3</key>
+ <date>1869-01-03T08:16:32Z</date>
+ <key>Time4</key>
+ <date>2199-08-20T01:10:11Z</date>
+</dict>
+</plist>
diff --git a/test/data/amp.plist b/test/data/amp.plist
new file mode 100644
index 0000000..ce9e7ac
--- /dev/null
+++ b/test/data/amp.plist
@@ -0,0 +1,6 @@
+<plist version="1.0">
+<dict>
+ <key>test&amp</key>
+ <string>value</string>
+</dict>
+</plist>
diff --git a/test/data/cdata.plist b/test/data/cdata.plist
new file mode 100644
index 0000000..dcd015f
--- /dev/null
+++ b/test/data/cdata.plist
@@ -0,0 +1,6 @@
+<plist version="1.0">
+<dict>
+ <key><![CDATA[cdata&key]]></key>
+ <string>string with cdata content: <![CDATA[ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~]]> !!!</string>
+</dict>
+</plist>
diff --git a/test/data/data.bplist b/test/data/data.bplist
new file mode 100644
index 0000000..955993f
--- /dev/null
+++ b/test/data/data.bplist
Binary files differ
diff --git a/test/data/dictref1byte.bplist b/test/data/dictref1byte.bplist
new file mode 100644
index 0000000..c33d70b
--- /dev/null
+++ b/test/data/dictref1byte.bplist
Binary files differ
diff --git a/test/data/dictref2bytes.bplist b/test/data/dictref2bytes.bplist
new file mode 100644
index 0000000..2a5225f
--- /dev/null
+++ b/test/data/dictref2bytes.bplist
Binary files differ
diff --git a/test/data/dictref3bytes.bplist b/test/data/dictref3bytes.bplist
new file mode 100644
index 0000000..fb34258
--- /dev/null
+++ b/test/data/dictref3bytes.bplist
Binary files differ
diff --git a/test/data/dictref4bytes.bplist b/test/data/dictref4bytes.bplist
new file mode 100644
index 0000000..5845107
--- /dev/null
+++ b/test/data/dictref4bytes.bplist
Binary files differ
diff --git a/test/data/dictref5bytes.bplist b/test/data/dictref5bytes.bplist
new file mode 100644
index 0000000..6ced9de
--- /dev/null
+++ b/test/data/dictref5bytes.bplist
Binary files differ
diff --git a/test/data/dictref6bytes.bplist b/test/data/dictref6bytes.bplist
new file mode 100644
index 0000000..2ed5f34
--- /dev/null
+++ b/test/data/dictref6bytes.bplist
Binary files differ
diff --git a/test/data/dictref7bytes.bplist b/test/data/dictref7bytes.bplist
new file mode 100644
index 0000000..cbb9368
--- /dev/null
+++ b/test/data/dictref7bytes.bplist
Binary files differ
diff --git a/test/data/dictref8bytes.bplist b/test/data/dictref8bytes.bplist
new file mode 100644
index 0000000..ffb4292
--- /dev/null
+++ b/test/data/dictref8bytes.bplist
Binary files differ
diff --git a/test/data/empty_keys.plist b/test/data/empty_keys.plist
new file mode 100644
index 0000000..a0107b4
--- /dev/null
+++ b/test/data/empty_keys.plist
@@ -0,0 +1,8 @@
+<plist version="1.0">
+<dict>
+ <key></key>
+ <string>empty key</string>
+ <key><!-- comment --></key>
+ <string>empty key with comment</string>
+</dict>
+</plist>
diff --git a/test/data/entities.plist b/test/data/entities.plist
new file mode 100644
index 0000000..d7655d6
--- /dev/null
+++ b/test/data/entities.plist
@@ -0,0 +1,57 @@
+<plist version="1.0">
+<dict>
+ <key>some test entitites</key>
+ <dict>
+ <key>copyright</key>
+ <string>&#169;</string>
+ <key>multiple entitites</key>
+ <string>&#x2F20;&#x2F21;&#x2F22;</string>
+ <key>multiple entitites with characters inbetween</key>
+ <string>a&#x2F23;b&#x2F24;c&#x2F25;d</string>
+ </dict>
+ <key>one byte entity</key>
+ <dict>
+ <key>one byte entity</key>
+ <string>&#64;</string>
+ <key>one byte entity with text after</key>
+ <string>&#x40;z</string>
+ <key>one byte entity with text before</key>
+ <string>a&#x40;</string>
+ <key>one byte entity with text before &amp; after</key>
+ <string>a&#64;z</string>
+ </dict>
+ <key>two-byte utf-8</key>
+ <dict>
+ <key>two-byte utf-8</key>
+ <string>&#xC4;</string>
+ <key>two-byte utf-8 with text after</key>
+ <string>&#xCB;yz</string>
+ <key>two-byte utf-8 with text before</key>
+ <string>ab&#xD6;</string>
+ <key>two-byte utf-8 with text before &amp; after</key>
+ <string>ab&#xDC;yz</string>
+ </dict>
+ <key>three byte utf-8</key>
+ <dict>
+ <key>three byte utf-8</key>
+ <string>&#xFFE0;</string>
+ <key>three byte utf-8 with text after</key>
+ <string>&#65505;xyz</string>
+ <key>three byte utf-8 with text before</key>
+ <string>abc&#xFFE5;</string>
+ <key>three byte utf-8 with text before &amp; after</key>
+ <string>abc&#65510;xyz</string>
+ </dict>
+ <key>four byte utf-8</key>
+ <dict>
+ <key>four byte utf-8</key>
+ <string>&#x1D562;</string>
+ <key>four byte utf-8 with text after</key>
+ <string>&#x1D563;wxyz</string>
+ <key>four byte utf-8 with text before</key>
+ <string>abcd&#x1D564;</string>
+ <key>four byte utf-8 with text before &amp; after</key>
+ <string>abcd&#x1D565;wxyz</string>
+ </dict>
+</dict>
+</plist>
diff --git a/test/data/hex.plist b/test/data/hex.plist
new file mode 100644
index 0000000..ae2d2b0
--- /dev/null
+++ b/test/data/hex.plist
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<integer>0x73709551615</integer>
+</plist>
diff --git a/test/data/int64_min_max.json b/test/data/int64_min_max.json
new file mode 100644
index 0000000..7707090
--- /dev/null
+++ b/test/data/int64_min_max.json
@@ -0,0 +1 @@
+{"INT64_MIN":-9223372036854775808,"INT64_MAX":9223372036854775807} \ No newline at end of file
diff --git a/test/data/invalid_tag.plist b/test/data/invalid_tag.plist
new file mode 100644
index 0000000..feb770f
--- /dev/null
+++ b/test/data/invalid_tag.plist
@@ -0,0 +1,6 @@
+<plist version="1.0">
+<dict>
+ <key>test<!test></key>
+ <string>value</string>
+</dict>
+</plist>
diff --git a/test/data/j1.json b/test/data/j1.json
new file mode 100644
index 0000000..2fe564d
--- /dev/null
+++ b/test/data/j1.json
@@ -0,0 +1 @@
+{"test":[1,1],"foo":[[-1337],[1],[1],[1],[[1],[1],[1],[1],[[1],[1],[1],[1]]]],"more":{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":0.25},{"a":"yo","b":[{"c":-0.25}]}]}]}}
diff --git a/test/data/j2.json b/test/data/j2.json
new file mode 100644
index 0000000..9d1210e
--- /dev/null
+++ b/test/data/j2.json
@@ -0,0 +1 @@
+{"Some ASCII string":"Test ASCII String","Some UTF8 strings":["àéèçù","日本語","汉语/漢語","한국어/ì¡°ì„ ë§","руÑÑкий Ñзык","الْعَرَبيّة","עִבְרִית","jÄ™zyk polski","हिनà¥à¤¦à¥€"],"Keys & \"entities\"":"hellow world & others <nodes> are \"fun!?'","Boolean":false,"Another Boolean":true,"Some Int":32434543632,"Some String with Unicode entity":"Yeah check this: \u1234 !!!"} \ No newline at end of file
diff --git a/test/data/malformed_dict.bplist b/test/data/malformed_dict.bplist
new file mode 100644
index 0000000..0de4cd7
--- /dev/null
+++ b/test/data/malformed_dict.bplist
Binary files differ
diff --git a/test/data/o1.ostep b/test/data/o1.ostep
new file mode 100644
index 0000000..074406a
--- /dev/null
+++ b/test/data/o1.ostep
@@ -0,0 +1,45 @@
+{
+ "test" = (1,1);
+ foo = (
+ (-1337),
+ (1),
+ (1),
+ (1),
+ (
+ (1),
+ (1),
+ (1),
+ (1),
+ (
+ (1),
+ (1),
+ (1),
+ (1)
+ )
+ )
+ );
+ more = {
+ "a" = "yo";
+ "b" = (
+ {
+ "c" = 0.25;
+ },
+ {
+ "a" = "yo";
+ "b" = (
+ {
+ "c" = 0.25;
+ },
+ {
+ "a" = "yo";
+ "b" = (
+ {
+ "cd" = -0.25;
+ }
+ );
+ }
+ );
+ }
+ );
+ };
+}
diff --git a/test/data/o2.ostep b/test/data/o2.ostep
new file mode 100644
index 0000000..5f5f3c2
--- /dev/null
+++ b/test/data/o2.ostep
@@ -0,0 +1,17 @@
+{
+ "Some ASCII string" = "Test ASCII String";
+ "Some UTF8 strings" = (
+ "àéèçù",
+ "日本語",
+ "汉语/漢語",
+ "한국어/ì¡°ì„ ë§",
+ "руÑÑкий Ñзык",
+ "الْعَرَبيّة",
+ "עִבְרִית",
+ "język polski",
+ "हिनà¥à¤¦à¥€",
+ );
+ "Keys & \"entities\"" = "hello world & others <nodes> are fun!?'";
+ "Some Int" = 32434543632;
+ "Some String with Unicode entity" = "Yeah check this: \U1234 !!!";
+}
diff --git a/test/data/o3.ostep b/test/data/o3.ostep
new file mode 100644
index 0000000..b80444d
--- /dev/null
+++ b/test/data/o3.ostep
@@ -0,0 +1,16 @@
+(
+ {
+ AFirstKey = "A First Value";
+ ASecondKey = "A Second Value";
+ // this is the last entry
+ },
+ /*{
+ BFirstKey = "B First Value";
+ BSecondKey = "B Second Value";
+ },*/
+ {
+ CFirstKey = "C First Value"; // "C First Unused Value";
+ // now here is another comment
+ CSecondKey = /* "C Second Value";*/ "C Second Corrected Value";
+ }
+)
diff --git a/test/data/off1byte.bplist b/test/data/off1byte.bplist
new file mode 100644
index 0000000..c33d70b
--- /dev/null
+++ b/test/data/off1byte.bplist
Binary files differ
diff --git a/test/data/off2bytes.bplist b/test/data/off2bytes.bplist
new file mode 100644
index 0000000..f080738
--- /dev/null
+++ b/test/data/off2bytes.bplist
Binary files differ
diff --git a/test/data/off3bytes.bplist b/test/data/off3bytes.bplist
new file mode 100644
index 0000000..a2f926b
--- /dev/null
+++ b/test/data/off3bytes.bplist
Binary files differ
diff --git a/test/data/off4bytes.bplist b/test/data/off4bytes.bplist
new file mode 100644
index 0000000..35897be
--- /dev/null
+++ b/test/data/off4bytes.bplist
Binary files differ
diff --git a/test/data/off5bytes.bplist b/test/data/off5bytes.bplist
new file mode 100644
index 0000000..9df03cd
--- /dev/null
+++ b/test/data/off5bytes.bplist
Binary files differ
diff --git a/test/data/off6bytes.bplist b/test/data/off6bytes.bplist
new file mode 100644
index 0000000..3d78a47
--- /dev/null
+++ b/test/data/off6bytes.bplist
Binary files differ
diff --git a/test/data/off7bytes.bplist b/test/data/off7bytes.bplist
new file mode 100644
index 0000000..150c277
--- /dev/null
+++ b/test/data/off7bytes.bplist
Binary files differ
diff --git a/test/data/off8bytes.bplist b/test/data/off8bytes.bplist
new file mode 100644
index 0000000..f01114e
--- /dev/null
+++ b/test/data/off8bytes.bplist
Binary files differ
diff --git a/test/data/offxml.plist b/test/data/offxml.plist
new file mode 100644
index 0000000..c3c1e10
--- /dev/null
+++ b/test/data/offxml.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>A</key>
+ <string>B</string>
+</dict>
+</plist>
diff --git a/test/data/order.bplist b/test/data/order.bplist
new file mode 100644
index 0000000..5ea86f8
--- /dev/null
+++ b/test/data/order.bplist
Binary files differ
diff --git a/test/data/order.plist b/test/data/order.plist
new file mode 100644
index 0000000..b2cbdcb
--- /dev/null
+++ b/test/data/order.plist
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>test</key>
+ <array>
+ <integer>1</integer>
+ <integer>1</integer>
+ </array>
+ <key>foo</key>
+ <array>
+ <array>
+ <integer>1</integer>
+ </array>
+ <dict>
+ <key>test</key>
+ <string>foo</string>
+ </dict>
+ <array>
+ <integer>1</integer>
+ </array>
+ <array>
+ <integer>1</integer>
+ </array>
+ </array>
+</dict>
+</plist>
diff --git a/test/data/recursion.bplist b/test/data/recursion.bplist
new file mode 100644
index 0000000..3f4b42a
--- /dev/null
+++ b/test/data/recursion.bplist
Binary files differ
diff --git a/test/data/signed.bplist b/test/data/signed.bplist
new file mode 100644
index 0000000..89bf362
--- /dev/null
+++ b/test/data/signed.bplist
Binary files differ
diff --git a/test/data/signed.plist b/test/data/signed.plist
new file mode 100644
index 0000000..c7d2991
--- /dev/null
+++ b/test/data/signed.plist
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<integer>-1</integer>
+</plist>
diff --git a/test/data/signedunsigned.bplist b/test/data/signedunsigned.bplist
new file mode 100644
index 0000000..f361063
--- /dev/null
+++ b/test/data/signedunsigned.bplist
Binary files differ
diff --git a/test/data/signedunsigned.plist b/test/data/signedunsigned.plist
new file mode 100644
index 0000000..2942529
--- /dev/null
+++ b/test/data/signedunsigned.plist
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+ <integer>-1</integer>
+ <integer>18446744073709551615</integer>
+ <integer>9223372036854775807</integer>
+ <integer>-9223372036854775808</integer>
+ <integer>9223372036854775808</integer>
+</array>
+</plist>
diff --git a/test/data/test.strings b/test/data/test.strings
new file mode 100644
index 0000000..6d6ee43
--- /dev/null
+++ b/test/data/test.strings
@@ -0,0 +1,12 @@
+STRINGS_ENTRY = "Whatever";
+FOO = "BAR";
+BAR = Foo;
+ENTRY0 = "àéèçù";
+ENTRY1 = "日本語";
+ENTRY2 = "汉语/漢語";
+ENTRY3 = "한국어/ì¡°ì„ ë§";
+ENTRY4 = "руÑÑкий Ñзык";
+ENTRY5 = "الْعَرَبيّة";
+ENTRY6 = "עִבְרִית";
+ENTRY7 = "język polski";
+ENTRY8 = "हिनà¥à¤¦à¥€";
diff --git a/test/data/uid.bplist b/test/data/uid.bplist
new file mode 100644
index 0000000..e1fc6f8
--- /dev/null
+++ b/test/data/uid.bplist
Binary files differ
diff --git a/test/data/unsigned.bplist b/test/data/unsigned.bplist
new file mode 100644
index 0000000..2b0a807
--- /dev/null
+++ b/test/data/unsigned.bplist
Binary files differ
diff --git a/test/data/unsigned.plist b/test/data/unsigned.plist
new file mode 100644
index 0000000..f08d1b7
--- /dev/null
+++ b/test/data/unsigned.plist
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<integer>18446744073709551615</integer>
+</plist>
diff --git a/test/dates.test b/test/dates.test
new file mode 100755
index 0000000..06bf034
--- /dev/null
+++ b/test/dates.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=7.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/dates.test.bin
+DATAOUT1=$top_builddir/test/data/dates.test.xml
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT1
+
+$top_builddir/test/plist_cmp $DATAOUT0 $DATAIN0
+$top_builddir/test/plist_cmp $DATAOUT1 $DATAIN0
diff --git a/test/empty++.test b/test/empty++.test
new file mode 100755
index 0000000..7e3695b
--- /dev/null
+++ b/test/empty++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=1.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/empty++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty++.test.out
diff --git a/test/empty.test b/test/empty.test
new file mode 100755
index 0000000..bc71562
--- /dev/null
+++ b/test/empty.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=1.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/empty.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty.test.out
diff --git a/test/empty_keys.test b/test/empty_keys.test
new file mode 100755
index 0000000..19bbb08
--- /dev/null
+++ b/test/empty_keys.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=empty_keys.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/empty_keys.test.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0
diff --git a/test/entities.test b/test/entities.test
new file mode 100755
index 0000000..a828e15
--- /dev/null
+++ b/test/entities.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=entities.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/entities.test.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0
diff --git a/test/hex.test b/test/hex.test
new file mode 100755
index 0000000..414cdbc
--- /dev/null
+++ b/test/hex.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=hex.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/hex.test.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT0
diff --git a/test/huge++.test b/test/huge++.test
new file mode 100755
index 0000000..90d3503
--- /dev/null
+++ b/test/huge++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=5.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/huge++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/huge++.test.out
diff --git a/test/huge.test b/test/huge.test
new file mode 100755
index 0000000..560c21f
--- /dev/null
+++ b/test/huge.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=5.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/huge.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/huge.test.out
diff --git a/test/integer_set.c b/test/integer_set.c
new file mode 100644
index 0000000..e25648f
--- /dev/null
+++ b/test/integer_set.c
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <plist/plist.h>
+
+void print_plist(plist_t pl)
+{
+ char *xml = NULL;
+ uint32_t xlen = 0;
+ plist_to_xml(pl, &xml, &xlen);
+ if (xml) {
+ printf("%s\n", xml);
+ }
+ free(xml);
+}
+
+int main(int argc, char** argv)
+{
+ int err = 0;
+ char *xml = NULL;
+ uint32_t xlen = 0;
+ plist_t iii = plist_new_int(0);
+
+ /* test 1 */
+ plist_set_uint_val(iii, 0x8000000000000000LL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>9223372036854775808</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match1) != 0) {
+ printf("ERROR: plist_set_uint_val with 0x8000000000000000LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_uint_val with 0x8000000000000000LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ /* test 2 */
+ plist_set_int_val(iii, 0x8000000000000000LL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match2 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>-9223372036854775808</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match2) != 0) {
+ printf("ERROR: plist_set_int_val with 0x8000000000000000LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_int_val with 0x8000000000000000LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ /* test 3 */
+ plist_set_uint_val(iii, (uint64_t)-1LL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match3 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>18446744073709551615</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match3) != 0) {
+ printf("ERROR: plist_set_uint_val with (uint64_t)-1LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_uint_val with (uint64_t)-1LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ /* test 4 */
+ plist_set_int_val(iii, -1LL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match4 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>-1</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match4) != 0) {
+ printf("ERROR: plist_set_int_val with -1LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_int_val with -1LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ /* test 5 */
+ plist_set_uint_val(iii, 0x8000000000000001LL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match5 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>9223372036854775809</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match5) != 0) {
+ printf("ERROR: plist_set_uint_val with 0x8000000000000001LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_uint_val with 0x8000000000000001LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ /* test 6 */
+ plist_set_uint_val(iii, 18446744073709551615uLL);
+ plist_to_xml(iii, &xml, &xlen);
+ const char* match6 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">\n"
+ "<integer>18446744073709551615</integer>\n"
+ "</plist>\n";
+ if (strcmp(xml, match6) != 0) {
+ printf("ERROR: plist_set_uint_val with 0x8000000000000001LL failed\n");
+ err++;
+ } else {
+ printf("SUCCESS: plist_set_uint_val with 0x8000000000000001LL\n");
+ }
+ free(xml);
+ xml = NULL;
+
+ return (err > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/test/integer_set.test b/test/integer_set.test
new file mode 100755
index 0000000..b917663
--- /dev/null
+++ b/test/integer_set.test
@@ -0,0 +1,5 @@
+## -*- sh -*-
+
+set -e
+
+$top_builddir/test/integer_set_test
diff --git a/test/invalid_tag.test b/test/invalid_tag.test
new file mode 100755
index 0000000..5d4da93
--- /dev/null
+++ b/test/invalid_tag.test
@@ -0,0 +1,16 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=invalid_tag.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/invalid_tag.test.out
+
+rm -rf $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+# test succeeds if plistutil fails
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/test/json-int64-min-max.test b/test/json-int64-min-max.test
new file mode 100755
index 0000000..f3fe61c
--- /dev/null
+++ b/test/json-int64-min-max.test
@@ -0,0 +1,19 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=int64_min_max.json
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_JSON_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json-int64-min-max.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json-int64-min-max.test.out
diff --git a/test/json-invalid-types.test b/test/json-invalid-types.test
new file mode 100755
index 0000000..a21fcd9
--- /dev/null
+++ b/test/json-invalid-types.test
@@ -0,0 +1,33 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE0=data.bplist
+TESTFILE1=7.plist
+TESTFILE2=uid.bplist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_JSON_DEBUG=1
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE0 -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 1
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE1 -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 2
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE2 -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 3
+fi
+
+exit 0
diff --git a/test/json1.test b/test/json1.test
new file mode 100755
index 0000000..cc7440f
--- /dev/null
+++ b/test/json1.test
@@ -0,0 +1,19 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=j1.json
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_JSON_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json1.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json1.test.out
diff --git a/test/json2.test b/test/json2.test
new file mode 100755
index 0000000..199643c
--- /dev/null
+++ b/test/json2.test
@@ -0,0 +1,19 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=j2.json
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_JSON_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_jtest $DATASRC/$TESTFILE $DATAOUT/json2.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json2.test.out
diff --git a/test/json3.test b/test/json3.test
new file mode 100755
index 0000000..6189e89
--- /dev/null
+++ b/test/json3.test
@@ -0,0 +1,24 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=entities.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_JSON_DEBUG=1
+
+echo "Converting input file to JSON"
+$top_builddir/tools/plistutil -f json -i $DATASRC/$TESTFILE -o $DATAOUT/json3.test.json
+
+echo "Converting to binary and back to JSON"
+$top_builddir/test/plist_jtest $DATAOUT/json3.test.json $DATAOUT/json3.test.json.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/json3.test.json.out
+
+rm -f $DATAOUT/json3.test.json
diff --git a/test/large++.test b/test/large++.test
new file mode 100755
index 0000000..f3f4c23
--- /dev/null
+++ b/test/large++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=4.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/large++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large++.test.out
diff --git a/test/large.test b/test/large.test
new file mode 100755
index 0000000..d3cce7a
--- /dev/null
+++ b/test/large.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=4.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/large.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large.test.out
diff --git a/test/malformed_dict.test b/test/malformed_dict.test
new file mode 100755
index 0000000..1d3ba2b
--- /dev/null
+++ b/test/malformed_dict.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=malformed_dict.bplist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/malformed_dict.test.out
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+# test succeeds if plistutil fails
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/test/medium++.test b/test/medium++.test
new file mode 100755
index 0000000..6eb453f
--- /dev/null
+++ b/test/medium++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=3.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/medium++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium++.test.out
diff --git a/test/medium.test b/test/medium.test
new file mode 100755
index 0000000..300c016
--- /dev/null
+++ b/test/medium.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=3.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/medium.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium.test.out
diff --git a/test/offsetsize.test b/test/offsetsize.test
new file mode 100755
index 0000000..d548d88
--- /dev/null
+++ b/test/offsetsize.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILES="off1byte.bplist off2bytes.bplist off3bytes.bplist off4bytes.bplist off5bytes.bplist off6bytes.bplist off7bytes.bplist off8bytes.bplist"
+CMPFILE=offxml.plist
+
+for I in $TESTFILES; do
+ echo "* checking $I"
+ $top_builddir/test/plist_cmp $DATASRC/$I $DATASRC/$CMPFILE
+done
diff --git a/test/order.test b/test/order.test
new file mode 100755
index 0000000..bd89b9d
--- /dev/null
+++ b/test/order.test
@@ -0,0 +1,13 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=order.bplist
+DATAIN0=$DATASRC/$TESTFILE
+DATAIN1=$DATASRC/order.plist
+DATAOUT0=$top_builddir/test/data/order.test.out
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+$top_builddir/test/plist_cmp $DATAIN1 $DATAOUT0
diff --git a/test/ostep-comments.test b/test/ostep-comments.test
new file mode 100755
index 0000000..68f5242
--- /dev/null
+++ b/test/ostep-comments.test
@@ -0,0 +1,20 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=o3.ostep
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_OSTEP_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep-comments.test.out
+
+echo "Comparing"
+export PLIST_OSTEP_DEBUG=1
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep-comments.test.out
diff --git a/test/ostep-invalid-types.test b/test/ostep-invalid-types.test
new file mode 100755
index 0000000..3ae376c
--- /dev/null
+++ b/test/ostep-invalid-types.test
@@ -0,0 +1,32 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE1=7.plist
+TESTFILE2=uid.bplist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_OSTEP_DEBUG=1
+
+echo "Converting (failure expected)"
+echo '[true]' |$top_builddir/tools/plistutil -f openstep -i - -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 1
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f openstepn -i $DATASRC/$TESTFILE1 -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 2
+fi
+
+echo "Converting (failure expected)"
+$top_builddir/tools/plistutil -f openstep -i $DATASRC/$TESTFILE2 -o /dev/null
+if [ $? -ne 2 ]; then
+ exit 3
+fi
+
+exit 0
diff --git a/test/ostep-strings.test b/test/ostep-strings.test
new file mode 100755
index 0000000..e3441a3
--- /dev/null
+++ b/test/ostep-strings.test
@@ -0,0 +1,20 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=test.strings
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_OSTEP_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep-strings.test.out
+
+echo "Comparing"
+export PLIST_OSTEP_DEBUG=1
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep-strings.test.out
diff --git a/test/ostep1.test b/test/ostep1.test
new file mode 100755
index 0000000..998fc54
--- /dev/null
+++ b/test/ostep1.test
@@ -0,0 +1,20 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=o1.ostep
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_OSTEP_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep1.test.out
+
+echo "Comparing"
+export PLIST_OSTEP_DEBUG=1
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep1.test.out
diff --git a/test/ostep2.test b/test/ostep2.test
new file mode 100755
index 0000000..a5485f8
--- /dev/null
+++ b/test/ostep2.test
@@ -0,0 +1,19 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=o2.ostep
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+export PLIST_OTEST_DEBUG=1
+
+echo "Converting"
+$top_builddir/test/plist_otest $DATASRC/$TESTFILE $DATAOUT/ostep2.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/ostep2.test.out
diff --git a/test/plist_btest.c b/test/plist_btest.c
new file mode 100644
index 0000000..32ba364
--- /dev/null
+++ b/test/plist_btest.c
@@ -0,0 +1,137 @@
+/*
+ * backup_test.c
+ * source libplist regression test
+ *
+ * Copyright (c) 2009 Jonathan Beck All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include "plist/plist.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#pragma warning(disable:4996)
+#endif
+
+
+int main(int argc, char *argv[])
+{
+ FILE *iplist = NULL;
+ plist_t root_node1 = NULL;
+ plist_t root_node2 = NULL;
+ char *plist_bin = NULL;
+ char *plist_bin2 = NULL;
+ char *plist_xml = NULL;
+ int size_in = 0;
+ uint32_t size_out = 0;
+ uint32_t size_out2 = 0;
+ char *file_in = NULL;
+ char *file_out = NULL;
+ struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
+ if (argc != 3)
+ {
+ printf("Wrong input\n");
+ return 1;
+ }
+
+ file_in = argv[1];
+ file_out = argv[2];
+ //read input file
+ iplist = fopen(file_in, "rb");
+
+ if (!iplist)
+ {
+ printf("File does not exists\n");
+ return 2;
+ }
+ printf("File %s is open\n", file_in);
+ stat(file_in, filestats);
+ size_in = filestats->st_size;
+ plist_bin = (char *) malloc(sizeof(char) * (size_in + 1));
+ if (fread(plist_bin, sizeof(char), size_in, iplist) != (size_t)size_in) {
+ printf("Failed to read input file\n");
+ fclose(iplist);
+ free(plist_bin);
+ free(filestats);
+ return 3;
+ }
+ fclose(iplist);
+
+
+ //convert one format to another
+ plist_from_bin(plist_bin, size_in, &root_node1);
+ if (!root_node1)
+ {
+ printf("PList BIN parsing failed\n");
+ return 3;
+ }
+
+ printf("PList BIN parsing succeeded\n");
+ plist_to_xml(root_node1, &plist_xml, &size_out);
+ if (!plist_xml)
+ {
+ printf("PList XML writing failed\n");
+ return 4;
+ }
+
+ printf("PList XML writing succeeded\n");
+ plist_from_xml(plist_xml, size_out, &root_node2);
+ if (!root_node2)
+ {
+ printf("PList XML parsing failed\n");
+ return 5;
+ }
+
+ printf("PList XML parsing succeeded\n");
+ plist_to_bin(root_node2, &plist_bin2, &size_out2);
+ if (!plist_bin2)
+ {
+ printf("PList BIN writing failed\n");
+ return 8;
+ }
+
+ printf("PList BIN writing succeeded\n");
+ if (plist_bin2)
+ {
+ FILE *oplist = NULL;
+ oplist = fopen(file_out, "wb");
+ fwrite(plist_bin2, size_out2, sizeof(char), oplist);
+ fclose(oplist);
+ }
+
+ plist_free(root_node1);
+ plist_free(root_node2);
+ free(plist_xml);
+ free(plist_bin);
+ free(plist_bin2);
+ free(filestats);
+
+ if ((uint32_t)size_in != size_out2)
+ {
+ printf("Size of input and output is different\n");
+ printf("Input size : %i\n", size_in);
+ printf("Output size : %i\n", size_out2);
+ }
+
+ //success
+ return 0;
+}
+
diff --git a/test/plist_cmp.c b/test/plist_cmp.c
index a07452b..c452032 100644
--- a/test/plist_cmp.c
+++ b/test/plist_cmp.c
@@ -35,12 +35,12 @@
static plist_t plist_get_first_child(plist_t node)
{
- return (plist_t) node_first_child((node_t*) node);
+ return (plist_t) node_first_child((node_t) node);
}
static plist_t plist_get_next_sibling(plist_t node)
{
- return (plist_t) node_next_sibling((node_t*) node);
+ return (plist_t) node_next_sibling((node_t) node);
}
static char compare_plist(plist_t node_l, plist_t node_r)
@@ -75,21 +75,12 @@ static char compare_plist(plist_t node_l, plist_t node_r)
int main(int argc, char *argv[])
{
- FILE *iplist1 = NULL;
- FILE *iplist2 = NULL;
plist_t root_node1 = NULL;
plist_t root_node2 = NULL;
- char *plist_1 = NULL;
- char *plist_2 = NULL;
- int size_in1 = 0;
- int size_in2 = 0;
char *file_in1 = NULL;
char *file_in2 = NULL;
int res = 0;
- struct stat *filestats1 = (struct stat *) malloc(sizeof(struct stat));
- struct stat *filestats2 = (struct stat *) malloc(sizeof(struct stat));
-
if (argc!= 3)
{
printf("Wrong input\n");
@@ -99,60 +90,21 @@ int main(int argc, char *argv[])
file_in1 = argv[1];
file_in2 = argv[2];
- //read input file
- iplist1 = fopen(file_in1, "rb");
- iplist2 = fopen(file_in2, "rb");
-
- if (!iplist1 || !iplist2)
- {
- printf("File does not exists\n");
- return 2;
- }
-
- stat(file_in1, filestats1);
- stat(file_in2, filestats2);
-
- size_in1 = filestats1->st_size;
- size_in2 = filestats2->st_size;
-
- plist_1 = (char *) malloc(sizeof(char) * (size_in1 + 1));
- plist_2 = (char *) malloc(sizeof(char) * (size_in2 + 1));
-
- fread(plist_1, sizeof(char), size_in1, iplist1);
- fread(plist_2, sizeof(char), size_in2, iplist2);
-
- fclose(iplist1);
- fclose(iplist2);
-
- if (memcmp(plist_1, "bplist00", 8) == 0)
- plist_from_bin(plist_1, size_in1, &root_node1);
- else
- plist_from_xml(plist_1, size_in1, &root_node1);
-
- if (memcmp(plist_2, "bplist00", 8) == 0)
- plist_from_bin(plist_2, size_in2, &root_node2);
- else
- plist_from_xml(plist_2, size_in2, &root_node2);
+ plist_read_from_file(file_in1, &root_node1, NULL);
+ plist_read_from_file(file_in2, &root_node2, NULL);
if (!root_node1 || !root_node2)
{
printf("PList parsing failed\n");
return 3;
}
- else
- printf("PList parsing succeeded\n");
+ printf("PList parsing succeeded\n");
res = compare_plist(root_node1, root_node2);
-
plist_free(root_node1);
plist_free(root_node2);
- free(plist_1);
- free(plist_2);
- free(filestats1);
- free(filestats2);
-
return !res;
}
diff --git a/test/plist_jtest.c b/test/plist_jtest.c
new file mode 100644
index 0000000..570886d
--- /dev/null
+++ b/test/plist_jtest.c
@@ -0,0 +1,137 @@
+/*
+ * backup_test.c
+ * source libplist regression test
+ *
+ * Copyright (c) 2009 Jonathan Beck All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include "plist/plist.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#pragma warning(disable:4996)
+#endif
+
+
+int main(int argc, char *argv[])
+{
+ FILE *iplist = NULL;
+ plist_t root_node1 = NULL;
+ plist_t root_node2 = NULL;
+ char *plist_json = NULL;
+ char *plist_json2 = NULL;
+ char *plist_bin = NULL;
+ int size_in = 0;
+ uint32_t size_out = 0;
+ uint32_t size_out2 = 0;
+ char *file_in = NULL;
+ char *file_out = NULL;
+ struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
+ if (argc != 3)
+ {
+ printf("Wrong input\n");
+ return 1;
+ }
+
+ file_in = argv[1];
+ file_out = argv[2];
+ //read input file
+ iplist = fopen(file_in, "rb");
+
+ if (!iplist)
+ {
+ printf("File does not exists\n");
+ return 2;
+ }
+ printf("File %s is open\n", file_in);
+ stat(file_in, filestats);
+ size_in = filestats->st_size;
+ plist_json = (char *) malloc(sizeof(char) * (size_in + 1));
+ if (fread(plist_json, sizeof(char), size_in, iplist) != (size_t)size_in) {
+ printf("Failed to read input file\n");
+ fclose(iplist);
+ free(plist_json);
+ free(filestats);
+ return 3;
+ }
+ fclose(iplist);
+ plist_json[size_in] = 0;
+
+ //convert one format to another
+ plist_from_json(plist_json, size_in, &root_node1);
+ if (!root_node1)
+ {
+ printf("PList JSON parsing failed\n");
+ return 3;
+ }
+
+ printf("PList JSON parsing succeeded\n");
+ plist_to_bin(root_node1, &plist_bin, &size_out);
+ if (!plist_bin)
+ {
+ printf("PList BIN writing failed\n");
+ return 4;
+ }
+
+ printf("PList BIN writing succeeded\n");
+ plist_from_bin(plist_bin, size_out, &root_node2);
+ if (!root_node2)
+ {
+ printf("PList BIN parsing failed\n");
+ return 5;
+ }
+
+ printf("PList BIN parsing succeeded\n");
+ plist_to_json(root_node2, &plist_json2, &size_out2, 0);
+ if (!plist_json2)
+ {
+ printf("PList JSON writing failed\n");
+ return 8;
+ }
+
+ printf("PList JSON writing succeeded\n");
+ if (plist_json2)
+ {
+ FILE *oplist = NULL;
+ oplist = fopen(file_out, "wb");
+ fwrite(plist_json2, size_out2, sizeof(char), oplist);
+ fclose(oplist);
+ }
+
+ plist_free(root_node1);
+ plist_free(root_node2);
+ free(plist_bin);
+ free(plist_json);
+ free(plist_json2);
+ free(filestats);
+
+ if ((uint32_t)size_in != size_out2)
+ {
+ printf("Size of input and output is different\n");
+ printf("Input size : %i\n", size_in);
+ printf("Output size : %i\n", size_out2);
+ }
+
+ //success
+ return 0;
+}
+
diff --git a/test/plist_otest.c b/test/plist_otest.c
new file mode 100644
index 0000000..b2d1c94
--- /dev/null
+++ b/test/plist_otest.c
@@ -0,0 +1,135 @@
+/*
+ * plist_otest.c
+ * source libplist regression test
+ *
+ * Copyright (c) 2022 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include "plist/plist.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#pragma warning(disable:4996)
+#endif
+
+
+int main(int argc, char *argv[])
+{
+ FILE *iplist = NULL;
+ plist_t root_node1 = NULL;
+ plist_t root_node2 = NULL;
+ char *plist_ostep = NULL;
+ char *plist_ostep2 = NULL;
+ char *plist_bin = NULL;
+ int size_in = 0;
+ uint32_t size_out = 0;
+ uint32_t size_out2 = 0;
+ char *file_in = NULL;
+ char *file_out = NULL;
+ struct stat filestats;
+ if (argc != 3)
+ {
+ printf("Wrong input\n");
+ return 1;
+ }
+
+ file_in = argv[1];
+ file_out = argv[2];
+ //read input file
+ iplist = fopen(file_in, "rb");
+
+ if (!iplist)
+ {
+ printf("File does not exists\n");
+ return 2;
+ }
+ printf("File %s is open\n", file_in);
+ stat(file_in, &filestats);
+ size_in = filestats.st_size;
+ plist_ostep = (char *) malloc(sizeof(char) * (size_in + 1));
+ if (fread(plist_ostep, sizeof(char), size_in, iplist) != (size_t)size_in) {
+ printf("Failed to read input file\n");
+ fclose(iplist);
+ free(plist_ostep);
+ return 3;
+ }
+ fclose(iplist);
+ plist_ostep[size_in] = 0;
+
+ //convert one format to another
+ plist_from_openstep(plist_ostep, size_in, &root_node1);
+ if (!root_node1)
+ {
+ printf("OpenStep PList parsing failed\n");
+ return 3;
+ }
+
+ printf("OpenStep PList parsing succeeded\n");
+ plist_to_bin(root_node1, &plist_bin, &size_out);
+ if (!plist_bin)
+ {
+ printf("PList BIN writing failed\n");
+ return 4;
+ }
+
+ printf("PList BIN writing succeeded\n");
+ plist_from_bin(plist_bin, size_out, &root_node2);
+ if (!root_node2)
+ {
+ printf("PList BIN parsing failed\n");
+ return 5;
+ }
+
+ printf("PList BIN parsing succeeded\n");
+ plist_to_openstep(root_node2, &plist_ostep2, &size_out2, 0);
+ if (!plist_ostep2)
+ {
+ printf("OpenStep PList writing failed\n");
+ return 8;
+ }
+
+ printf("OpenStep PList writing succeeded\n");
+ if (plist_ostep2)
+ {
+ FILE *oplist = NULL;
+ oplist = fopen(file_out, "wb");
+ fwrite(plist_ostep2, size_out2, sizeof(char), oplist);
+ fclose(oplist);
+ }
+
+ plist_free(root_node1);
+ plist_free(root_node2);
+ free(plist_bin);
+ free(plist_ostep);
+ free(plist_ostep2);
+
+ if ((uint32_t)size_in != size_out2)
+ {
+ printf("Size of input and output is different\n");
+ printf("Input size : %i\n", size_in);
+ printf("Output size : %i\n", size_out2);
+ }
+
+ //success
+ return 0;
+}
+
diff --git a/test/plist_test++.cpp b/test/plist_test++.cpp
new file mode 100644
index 0000000..33205c1
--- /dev/null
+++ b/test/plist_test++.cpp
@@ -0,0 +1,103 @@
+/*
+ * source libplist++ regression test
+ *
+ * Copyright (c) 2021 Sebastien Gonzalve All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include "plist/plist++.h"
+#include <fstream>
+#include <sstream>
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ printf("Wrong input\n");
+ return 1;
+ }
+
+ const char* file_in = argv[1];
+ const char* file_out = argv[2];
+
+ //read input file
+ std::ifstream iplist;
+ iplist.open(file_in);
+
+ if (!iplist)
+ {
+ printf("File does not exists\n");
+ return 2;
+ }
+
+ std::cout << "File " << file_in << " is open\n";
+
+ std::string plist_xml;
+ {
+ std::stringstream buffer;
+ buffer << iplist.rdbuf();
+ plist_xml = buffer.str();
+ }
+
+ iplist.close();
+
+ //convert one format to another
+ PList::Structure* root_node1 = PList::Structure::FromXml(plist_xml);
+ if (!root_node1)
+ {
+ std::cout << "PList XML parsing failed\n";
+ return 3;
+ }
+
+ std::cout << "PList XML parsing succeeded\n";
+ std::vector<char> plist_bin = root_node1->ToBin();
+ // FIXME There is no way to test for success of ToBin for now.
+
+ std::cout << "PList BIN writing succeeded\n";
+ PList::Structure* root_node2 = PList::Structure::FromBin(plist_bin);
+ if (!root_node2)
+ {
+ std::cout << "PList BIN parsing failed\n";
+ return 5;
+ }
+
+ std::cout << "PList BIN parsing succeeded\n";
+ std::string plist_xml2 = root_node2->ToXml();
+ if (plist_xml2.empty())
+ {
+ std::cout << "PList XML writing failed\n";
+ return 8;
+ }
+
+ std::cout << "PList XML writing succeeded\n";
+ {
+ std::ofstream oplist;
+ oplist.open(file_out);
+ oplist << plist_xml2;
+ }
+
+ if (plist_xml.size() != plist_xml2.size())
+ {
+ std::cout << "Size of input and output is different\n"
+ << "Input size : " << plist_xml.size()
+ << "\nOutput size : " << plist_xml2.size() << '\n';
+ }
+
+ return 0;
+}
+
diff --git a/test/plist_test.c b/test/plist_test.c
index 17be11a..6e94c57 100644
--- a/test/plist_test.c
+++ b/test/plist_test.c
@@ -44,14 +44,16 @@ int main(int argc, char *argv[])
uint32_t size_out = 0;
uint32_t size_out2 = 0;
char *file_in = NULL;
+ char *file_out = NULL;
struct stat *filestats = (struct stat *) malloc(sizeof(struct stat));
- if (argc!= 2)
+ if (argc != 3)
{
printf("Wrong input\n");
return 1;
}
file_in = argv[1];
+ file_out = argv[2];
//read input file
iplist = fopen(file_in, "rb");
@@ -64,7 +66,13 @@ int main(int argc, char *argv[])
stat(file_in, filestats);
size_in = filestats->st_size;
plist_xml = (char *) malloc(sizeof(char) * (size_in + 1));
- fread(plist_xml, sizeof(char), size_in, iplist);
+ if (fread(plist_xml, sizeof(char), size_in, iplist) != (size_t)size_in) {
+ printf("Failed to read input file\n");
+ fclose(iplist);
+ free(plist_xml);
+ free(filestats);
+ return 3;
+ }
fclose(iplist);
@@ -75,41 +83,35 @@ int main(int argc, char *argv[])
printf("PList XML parsing failed\n");
return 3;
}
- else
- printf("PList XML parsing succeeded\n");
+ printf("PList XML parsing succeeded\n");
plist_to_bin(root_node1, &plist_bin, &size_out);
if (!plist_bin)
{
printf("PList BIN writing failed\n");
return 4;
}
- else
- printf("PList BIN writing succeeded\n");
+ printf("PList BIN writing succeeded\n");
plist_from_bin(plist_bin, size_out, &root_node2);
if (!root_node2)
{
printf("PList BIN parsing failed\n");
return 5;
}
- else
- printf("PList BIN parsing succeeded\n");
+ printf("PList BIN parsing succeeded\n");
plist_to_xml(root_node2, &plist_xml2, &size_out2);
if (!plist_xml2)
{
printf("PList XML writing failed\n");
return 8;
}
- else
- printf("PList XML writing succeeded\n");
+ printf("PList XML writing succeeded\n");
if (plist_xml2)
{
FILE *oplist = NULL;
- char file_out[512];
- sprintf(file_out, "%s.out", file_in);
oplist = fopen(file_out, "wb");
fwrite(plist_xml2, size_out2, sizeof(char), oplist);
fclose(oplist);
diff --git a/test/recursion.test b/test/recursion.test
new file mode 100755
index 0000000..97bfb45
--- /dev/null
+++ b/test/recursion.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=recursion.bplist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/recursion.test.out
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+# test succeeds if plistutil fails
+if [ $? -eq 0 ]; then
+ exit 1
+else
+ exit 0
+fi
diff --git a/test/refsize.test b/test/refsize.test
new file mode 100755
index 0000000..379b7fb
--- /dev/null
+++ b/test/refsize.test
@@ -0,0 +1,12 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILES="dictref1byte.bplist dictref2bytes.bplist dictref3bytes.bplist dictref4bytes.bplist dictref5bytes.bplist dictref6bytes.bplist dictref7bytes.bplist dictref8bytes.bplist"
+CMPFILE=offxml.plist
+
+for I in $TESTFILES; do
+ echo "* checking $I"
+ $top_builddir/test/plist_cmp $DATASRC/$I $DATASRC/$CMPFILE
+done
diff --git a/test/signedunsigned1.test b/test/signedunsigned1.test
new file mode 100755
index 0000000..52f7080
--- /dev/null
+++ b/test/signedunsigned1.test
@@ -0,0 +1,23 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE0=signed.plist
+TESTFILE1=unsigned.plist
+DATAIN0=$DATASRC/$TESTFILE0
+DATAIN1=$DATASRC/$TESTFILE1
+
+CMPFILE0=signed.bplist
+CMPFILE1=unsigned.bplist
+DATACMP0=$DATASRC/$CMPFILE0
+DATACMP1=$DATASRC/$CMPFILE1
+
+DATAOUT0=$top_builddir/test/data/signedunsigned1.test.signed.bin
+DATAOUT1=$top_builddir/test/data/signedunsigned1.test.unsigned.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1
+
+diff --strip-trailing-cr $DATACMP0 $DATAOUT0
+diff --strip-trailing-cr $DATACMP1 $DATAOUT1
diff --git a/test/signedunsigned2.test b/test/signedunsigned2.test
new file mode 100755
index 0000000..192f1a6
--- /dev/null
+++ b/test/signedunsigned2.test
@@ -0,0 +1,23 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE0=signed.bplist
+TESTFILE1=unsigned.bplist
+DATAIN0=$DATASRC/$TESTFILE0
+DATAIN1=$DATASRC/$TESTFILE1
+
+CMPFILE0=signed.plist
+CMPFILE1=unsigned.plist
+DATACMP0=$DATASRC/$CMPFILE0
+DATACMP1=$DATASRC/$CMPFILE1
+
+DATAOUT0=$top_builddir/test/data/signedunsigned2.test.signed.bin
+DATAOUT1=$top_builddir/test/data/signedunsigned2.test.unsigned.bin
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1
+
+diff --strip-trailing-cr $DATACMP0 $DATAOUT0
+diff --strip-trailing-cr $DATACMP1 $DATAOUT1
diff --git a/test/signedunsigned3.test b/test/signedunsigned3.test
new file mode 100755
index 0000000..87bdcbc
--- /dev/null
+++ b/test/signedunsigned3.test
@@ -0,0 +1,23 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE0=signedunsigned.plist
+TESTFILE1=signedunsigned.bplist
+DATAIN0=$DATASRC/$TESTFILE0
+DATAIN1=$DATASRC/$TESTFILE1
+
+CMPFILE0=signedunsigned.bplist
+CMPFILE1=signedunsigned.plist
+DATACMP0=$DATASRC/$CMPFILE0
+DATACMP1=$DATASRC/$CMPFILE1
+
+DATAOUT0=$top_builddir/test/data/signedunsigned3.test.signed.bin
+DATAOUT1=$top_builddir/test/data/signedunsigned3.test.unsigned.xml
+
+$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+$top_builddir/tools/plistutil -i $DATAIN1 -o $DATAOUT1
+
+diff --strip-trailing-cr $DATACMP0 $DATAOUT0
+diff --strip-trailing-cr $DATACMP1 $DATAOUT1
diff --git a/test/small++.test b/test/small++.test
new file mode 100755
index 0000000..2ceace7
--- /dev/null
+++ b/test/small++.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=2.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test++ $DATASRC/$TESTFILE $DATAOUT/small++.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small++.test.out
diff --git a/test/small.test b/test/small.test
new file mode 100755
index 0000000..dd2faf8
--- /dev/null
+++ b/test/small.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=2.plist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/small.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small.test.out
diff --git a/test/timezone1.test b/test/timezone1.test
new file mode 100755
index 0000000..5de7cea
--- /dev/null
+++ b/test/timezone1.test
@@ -0,0 +1,17 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=7.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/timezone1.test.tz0.bin
+DATAOUT1=$top_builddir/test/data/timezone1.test.tz1.bin
+DATAOUT2=$top_builddir/test/data/timezone1.test.tz2.bin
+
+TZ=UTC $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+TZ=Asia/Singapore $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT1
+TZ=US/Pacific $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT2
+
+$top_builddir/test/plist_cmp $DATAOUT0 $DATAOUT1
+$top_builddir/test/plist_cmp $DATAOUT0 $DATAOUT2
diff --git a/test/timezone2.test b/test/timezone2.test
new file mode 100755
index 0000000..79db523
--- /dev/null
+++ b/test/timezone2.test
@@ -0,0 +1,21 @@
+## -*- sh -*-
+
+set -e
+
+DATASRC=$top_srcdir/test/data
+TESTFILE=7.plist
+DATAIN0=$DATASRC/$TESTFILE
+DATAOUT0=$top_builddir/test/data/timezone2.test.bin
+DATAOUT1=$top_builddir/test/data/timezone2.test.tz0.xml
+DATAOUT2=$top_builddir/test/data/timezone2.test.tz1.xml
+DATAOUT3=$top_builddir/test/data/timezone2.test.tz2.xml
+
+TZ=UTC $top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
+
+TZ=UTC $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT1
+TZ=Asia/Singapore $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT2
+TZ=US/Pacific $top_builddir/tools/plistutil -i $DATAOUT0 -o $DATAOUT3
+
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT1
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT2
+$top_builddir/test/plist_cmp $DATAIN0 $DATAOUT3
diff --git a/test/uid.test b/test/uid.test
new file mode 100755
index 0000000..a62756f
--- /dev/null
+++ b/test/uid.test
@@ -0,0 +1,15 @@
+## -*- sh -*-
+
+DATASRC=$top_srcdir/test/data
+DATAOUT=$top_builddir/test/data
+TESTFILE=uid.bplist
+
+if ! test -d "$DATAOUT"; then
+ mkdir -p $DATAOUT
+fi
+
+echo "Converting"
+$top_builddir/test/plist_btest $DATASRC/$TESTFILE $DATAOUT/uid.test.out
+
+echo "Comparing"
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/uid.test.out
diff --git a/test/xml_behavior.test b/test/xml_behavior.test
new file mode 100755
index 0000000..81d8dd0
--- /dev/null
+++ b/test/xml_behavior.test
@@ -0,0 +1,2 @@
+## -*- sh -*-
+$top_builddir/test/xml_behavior_test
diff --git a/test/xml_behavior_test.c b/test/xml_behavior_test.c
new file mode 100644
index 0000000..94d8a7f
--- /dev/null
+++ b/test/xml_behavior_test.c
@@ -0,0 +1,167 @@
+/*
+ * xml_behavior_test.c
+ *
+ * Tests XML parser behavior for correctness and specification compliance:
+ *
+ * 1) A <plist> element must contain exactly one root value node.
+ * Any additional value nodes after the first root object must
+ * cause parsing to fail.
+ *
+ * 2) Dictionaries of the form:
+ * <dict>
+ * <key>CF$UID</key>
+ * <integer>...</integer>
+ * </dict>
+ * must be converted to PLIST_UID nodes during XML parsing,
+ * including when they appear nested inside other containers.
+ *
+ * These tests ensure proper root handling and UID node conversion
+ * when parsing XML property lists.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "plist/plist.h"
+
+static int test_nested_cfuid_converts_to_uid(void)
+{
+ const char *xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+ "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+ "<plist version=\"1.0\">"
+ " <dict>"
+ " <key>obj</key>"
+ " <dict>"
+ " <key>CF$UID</key>"
+ " <integer>7</integer>"
+ " </dict>"
+ " </dict>"
+ "</plist>";
+
+ plist_t root = NULL;
+ plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root);
+ if (err != PLIST_ERR_SUCCESS || !root) {
+ fprintf(stderr, "nested CF$UID: plist_from_xml failed (err=%d)\n", err);
+ plist_free(root);
+ return 0;
+ }
+
+ if (plist_get_node_type(root) != PLIST_DICT) {
+ fprintf(stderr, "nested CF$UID: root is not dict\n");
+ plist_free(root);
+ return 0;
+ }
+
+ plist_t obj = plist_dict_get_item(root, "obj");
+ if (!obj) {
+ fprintf(stderr, "nested CF$UID: missing key 'obj'\n");
+ plist_free(root);
+ return 0;
+ }
+
+ if (plist_get_node_type(obj) != PLIST_UID) {
+ fprintf(stderr, "nested CF$UID: expected PLIST_UID, got %d\n",
+ plist_get_node_type(obj));
+ plist_free(root);
+ return 0;
+ }
+
+ uint64_t uid = 0;
+ plist_get_uid_val(obj, &uid);
+ if (uid != 7) {
+ fprintf(stderr, "nested CF$UID: expected uid=7, got %" PRIu64 "\n", uid);
+ plist_free(root);
+ return 0;
+ }
+
+ plist_free(root);
+ return 1;
+}
+
+static int test_extra_root_value_is_rejected(void)
+{
+ /* Two root values inside <plist> must be rejected */
+ const char *xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<plist version=\"1.0\">"
+ " <string>one</string>"
+ " <string>two</string>"
+ "</plist>";
+
+ plist_t root = NULL;
+ plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root);
+
+ /* Must fail, and root must be NULL (consistent with other parsers) */
+ if (err == PLIST_ERR_SUCCESS || root != NULL) {
+ fprintf(stderr, "extra root value: expected failure, got err=%d root=%p\n",
+ err, (void*)root);
+ plist_free(root);
+ return 0;
+ }
+ return 1;
+}
+
+static int test_scalar_then_extra_node_is_rejected(void)
+{
+ /* Scalar root followed by another node must be rejected */
+ const char *xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<plist version=\"1.0\">"
+ " <true/>"
+ " <dict><key>A</key><string>x</string></dict>"
+ "</plist>";
+
+ plist_t root = NULL;
+ plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root);
+
+ if (err == PLIST_ERR_SUCCESS || root != NULL) {
+ fprintf(stderr, "scalar then extra node: expected failure, got err=%d root=%p\n",
+ err, (void*)root);
+ plist_free(root);
+ return 0;
+ }
+ return 1;
+}
+
+static int test_scalar_with_comment_is_ok(void)
+{
+ /* Comment after the single root value is not an extra value node */
+ const char *xml =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<plist version=\"1.0\">"
+ " <string>ok</string>"
+ " <!-- trailing comment -->"
+ "</plist>";
+
+ plist_t root = NULL;
+ plist_err_t err = plist_from_xml(xml, (uint32_t)strlen(xml), &root);
+ if (err != PLIST_ERR_SUCCESS || !root) {
+ fprintf(stderr, "scalar + comment: expected success, got err=%d\n", err);
+ plist_free(root);
+ return 0;
+ }
+ if (plist_get_node_type(root) != PLIST_STRING) {
+ fprintf(stderr, "scalar + comment: expected root string, got %d\n",
+ plist_get_node_type(root));
+ plist_free(root);
+ return 0;
+ }
+ plist_free(root);
+ return 1;
+}
+
+int main(void)
+{
+ int ok = 1;
+
+ ok &= test_nested_cfuid_converts_to_uid();
+ ok &= test_extra_root_value_is_rejected();
+ ok &= test_scalar_then_extra_node_is_rejected();
+ ok &= test_scalar_with_comment_is_ok();
+
+ return ok ? 0 : 1;
+}
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 0000000..93b5b9f
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,20 @@
+if BUILD_TOOLS
+
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/include
+
+AM_LDFLAGS =
+
+bin_PROGRAMS = plistutil
+
+plistutil_SOURCES = plistutil.c
+plistutil_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+install-exec-hook:
+ cd $(DESTDIR)$(bindir) && ln -sf plistutil plist2json
+
+uninstall-hook:
+ rm -f $(DESTDIR)$(bindir)/plist2json
+
+endif
diff --git a/tools/plistutil.c b/tools/plistutil.c
new file mode 100644
index 0000000..37bb044
--- /dev/null
+++ b/tools/plistutil.c
@@ -0,0 +1,529 @@
+/*
+ * plistutil.c
+ * Simple tool to convert a plist into different formats
+ *
+ * Copyright (c) 2009-2020 Martin Szulecki All Rights Reserved.
+ * Copyright (c) 2013-2020 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2008 Zach C., All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "plist/plist.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <errno.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(disable:4996)
+#define STDIN_FILENO _fileno(stdin)
+#define strtok_r strtok_s
+#endif
+
+typedef struct _options
+{
+ char *in_file, *out_file, *nodepath;
+ uint8_t in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json, 4 = openstep
+ uint8_t flags;
+} options_t;
+#define OPT_DEBUG (1 << 0)
+#define OPT_COMPACT (1 << 1)
+#define OPT_SORT (1 << 2)
+#define OPT_COERCE (1 << 3)
+
+static void print_usage(int argc, char *argv[])
+{
+ char *name = NULL;
+ name = strrchr(argv[0], '/');
+ printf("Usage: %s [OPTIONS] [-i FILE] [-o FILE]\n", (name ? name + 1: argv[0]));
+ printf("\n");
+ printf("Convert a plist FILE between binary, XML, JSON, and OpenStep format.\n");
+ printf("If -f is omitted, XML plist data will be converted to binary and vice-versa.\n");
+ printf("To convert to/from JSON or OpenStep the output format needs to be specified.\n");
+ printf("\n");
+ printf("OPTIONS:\n");
+ printf(" -i, --infile FILE Optional FILE to convert from or stdin if - or not used\n");
+ printf(" -o, --outfile FILE Optional FILE to convert to or stdout if - or not used\n");
+ printf(" -f, --format FORMAT Force output format, regardless of input type\n");
+ printf(" FORMAT is one of xml, bin, json, or openstep\n");
+ printf(" If omitted, XML will be converted to binary,\n");
+ printf(" and binary to XML.\n");
+ printf(" -p, --print FILE Print the PList in human-readable format.\n");
+ printf(" -n, --nodepath PATH Restrict output to nodepath defined by PATH.\n");
+ printf(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
+ printf(" By default, the output will be pretty-printed.\n");
+ printf(" -C, --coerce JSON + OpenStep only: Coerce non-compatible plist types\n");
+ printf(" to JSON/OpenStep compatible representations.\n");
+ printf(" Date values become ISO 8601 strings,\n");
+ printf(" data values become Base64-encoded strings (JSON),\n");
+ printf(" UID values become integers,\n");
+ printf(" boolean becomes 1 or 0 (OpenStep),\n");
+ printf(" and NULL becomes a string 'NULL' (OpenStep)\n");
+ printf(" This options is implied when invoked as plist2json.\n");
+ printf(" -s, --sort Sort all dictionary nodes lexicographically by key\n");
+ printf(" before converting to the output format.\n");
+ printf(" -d, --debug Enable extended debug output\n");
+ printf(" -v, --version Print version information\n");
+ printf("\n");
+ printf("Homepage: <" PACKAGE_URL ">\n");
+ printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n");
+}
+
+static options_t *parse_arguments(int argc, char *argv[])
+{
+ options_t *options = calloc(1, sizeof(options_t));
+ if (!options)
+ return NULL;
+
+ options->out_fmt = 0;
+
+ static struct option long_options[] = {
+ { "infile", required_argument, 0, 'i' },
+ { "outfile", required_argument, 0, 'o' },
+ { "format", required_argument, 0, 'f' },
+ { "compact", no_argument, 0, 'c' },
+ { "coerce", no_argument, 0, 'C' },
+ { "sort", no_argument, 0, 's' },
+ { "print", required_argument, 0, 'p' },
+ { "nodepath", required_argument, 0, 'n' },
+ { "debug", no_argument, 0, 'd' },
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'v' },
+ { 0, 0, 0, 0 }
+ };
+
+ int c;
+ while ((c = getopt_long(argc, argv, "i:o:f:cCsp:n:dhv", long_options, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case 'i':
+ if (!optarg || optarg[0] == '\0') {
+ fprintf(stderr, "ERROR: --infile requires a filename or '-' for stdin\n");
+ free(options);
+ return NULL;
+ }
+ options->in_file = optarg;
+ break;
+
+ case 'o':
+ if (!optarg || optarg[0] == '\0') {
+ fprintf(stderr, "ERROR: --outfile requires a filename or '-' for stdout\n");
+ free(options);
+ return NULL;
+ }
+ options->out_file = optarg;
+ break;
+
+ case 'f':
+ if (!optarg || optarg[0] == '\0') {
+ fprintf(stderr, "ERROR: --format requires a format (bin|xml|json|openstep)\n");
+ free(options);
+ return NULL;
+ }
+ if (!strncmp(optarg, "bin", 3)) {
+ options->out_fmt = PLIST_FORMAT_BINARY;
+ } else if (!strncmp(optarg, "xml", 3)) {
+ options->out_fmt = PLIST_FORMAT_XML;
+ } else if (!strncmp(optarg, "json", 4)) {
+ options->out_fmt = PLIST_FORMAT_JSON;
+ } else if (!strncmp(optarg, "openstep", 8) ||
+ !strncmp(optarg, "ostep", 5)) {
+ options->out_fmt = PLIST_FORMAT_OSTEP;
+ } else {
+ fprintf(stderr, "ERROR: Unsupported output format\n");
+ free(options);
+ return NULL;
+ }
+ break;
+
+ case 'c':
+ options->flags |= OPT_COMPACT;
+ break;
+
+ case 'C':
+ options->flags |= OPT_COERCE;
+ break;
+
+ case 's':
+ options->flags |= OPT_SORT;
+ break;
+
+ case 'p': {
+ if (!optarg || optarg[0] == '\0') {
+ fprintf(stderr, "ERROR: --print requires a filename or '-' for stdin\n");
+ free(options);
+ return NULL;
+ }
+ options->in_file = optarg;
+ options->out_fmt = PLIST_FORMAT_PRINT;
+
+ char *env_fmt = getenv("PLIST_OUTPUT_FORMAT");
+ if (env_fmt) {
+ if (!strcmp(env_fmt, "plutil")) {
+ options->out_fmt = PLIST_FORMAT_PLUTIL;
+ } else if (!strcmp(env_fmt, "limd")) {
+ options->out_fmt = PLIST_FORMAT_LIMD;
+ }
+ }
+ break;
+ }
+
+ case 'n':
+ if (!optarg || optarg[0] == '\0') {
+ fprintf(stderr, "ERROR: --extract needs a node path\n");
+ free(options);
+ return NULL;
+ }
+ options->nodepath = optarg;
+ break;
+
+ case 'd':
+ options->flags |= OPT_DEBUG;
+ break;
+
+ case 'h':
+ free(options);
+ return NULL;
+
+ case 'v':
+ printf("plistutil %s\n", libplist_version());
+ exit(EXIT_SUCCESS);
+
+ default:
+ fprintf(stderr, "ERROR: Invalid option\n");
+ free(options);
+ return NULL;
+ }
+ }
+
+ return options;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ int input_res = PLIST_ERR_UNKNOWN;
+ int output_res = PLIST_ERR_UNKNOWN;
+ FILE *iplist = NULL;
+ plist_t root_node = NULL;
+ char *plist_out = NULL;
+ uint32_t size = 0;
+ size_t read_size = 0;
+ size_t read_capacity = 4096;
+ char *plist_entire = NULL;
+ struct stat filestats;
+ options_t *options = parse_arguments(argc, argv);
+
+ if (!options)
+ {
+ print_usage(argc, argv);
+ return 0;
+ }
+
+ // detect invocation as plist2json symlink
+ {
+ char *progname = strrchr(argv[0], '/');
+ progname = progname ? progname + 1 : argv[0];
+ if (!strcmp(progname, "plist2json")) {
+ if (options->out_fmt == 0) {
+ options->out_fmt = PLIST_FORMAT_JSON;
+ }
+ options->flags |= OPT_COERCE;
+ }
+ }
+
+ if (options->flags & OPT_DEBUG)
+ {
+ plist_set_debug(1);
+ }
+
+ if (!options->in_file || !strcmp(options->in_file, "-"))
+ {
+ read_size = 0;
+ plist_entire = malloc(sizeof(char) * read_capacity);
+ if(plist_entire == NULL)
+ {
+ fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin\n");
+ free(options);
+ return 1;
+ }
+ plist_entire[read_size] = '\0';
+ char buf[4096];
+ ssize_t n;
+
+ while (1) {
+ n = read(STDIN_FILENO, buf, sizeof(buf));
+ if (n > 0) {
+ size_t needed = read_size + (size_t)n + 1;
+ if (needed > read_capacity) {
+ size_t newcap = read_capacity ? read_capacity : 4096;
+ while (newcap < needed) {
+ newcap *= 2;
+ }
+
+ char *tmp = realloc(plist_entire, newcap);
+ if (!tmp) {
+ fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
+ free(plist_entire);
+ free(options);
+ return 1;
+ }
+ plist_entire = tmp;
+ read_capacity = newcap;
+ }
+
+ memcpy(plist_entire + read_size, buf, (size_t)n);
+ read_size += (size_t)n;
+ continue;
+ }
+
+ if (n == 0) { // EOF
+ break;
+ }
+
+ // n < 0: error
+ if (errno == EINTR)
+ continue;
+
+ fprintf(stderr, "ERROR: Failed to read from stdin\n");
+ free(plist_entire);
+ free(options);
+ return 1;
+ }
+ plist_entire[read_size] = '\0';
+ }
+ else
+ {
+ // read input file
+ iplist = fopen(options->in_file, "rb");
+ if (!iplist) {
+ fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
+ free(options);
+ return 1;
+ }
+
+ memset(&filestats, '\0', sizeof(struct stat));
+ fstat(fileno(iplist), &filestats);
+
+ plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1));
+ if(plist_entire == NULL)
+ {
+ fprintf(stderr, "ERROR: Failed to allocate buffer to read from file\n");
+ free(options);
+ return 1;
+ }
+ read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist);
+ if (read_size != (size_t)filestats.st_size) {
+ fprintf(stderr, "ERROR: Could not read from input file '%s'\n", options->in_file);
+ fclose(iplist);
+ free(plist_entire);
+ free(options);
+ return 1;
+ }
+ plist_entire[read_size] = '\0';
+ fclose(iplist);
+ }
+
+ if (options->out_fmt == 0) {
+ // convert from binary to xml or vice-versa
+ if (plist_is_binary(plist_entire, read_size))
+ {
+ input_res = plist_from_bin(plist_entire, read_size, &root_node);
+ if (input_res == PLIST_ERR_SUCCESS) {
+ if (options->flags & OPT_SORT) {
+ plist_sort(root_node);
+ }
+ output_res = plist_to_xml(root_node, &plist_out, &size);
+ }
+ }
+ else
+ {
+ input_res = plist_from_xml(plist_entire, read_size, &root_node);
+ if (input_res == PLIST_ERR_SUCCESS) {
+ if (options->flags & OPT_SORT) {
+ plist_sort(root_node);
+ }
+ output_res = plist_to_bin(root_node, &plist_out, &size);
+ }
+ }
+ }
+ else
+ {
+ input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL);
+ if (input_res == PLIST_ERR_SUCCESS) {
+
+ if (options->nodepath) {
+ char *copy = strdup(options->nodepath);
+ char *tok, *saveptr = NULL;
+ if (!copy) {
+ plist_free(root_node);
+ free(plist_entire);
+ free(options);
+ return 1;
+ }
+
+ plist_t current = root_node;
+ for (tok = strtok_r(copy, "/", &saveptr); tok; tok = strtok_r(NULL, "/", &saveptr)) {
+ if (*tok == '\0') continue;
+ switch (plist_get_node_type(current)) {
+ case PLIST_DICT:
+ current = plist_dict_get_item(current, tok);
+ break;
+ case PLIST_ARRAY: {
+ char* endp = NULL;
+ uint32_t idx = strtoul(tok, &endp, 10);
+ if (endp == tok || *endp != '\0') {
+ current = NULL;
+ break;
+ }
+ if (idx >= plist_array_get_size(current)) {
+ current = NULL;
+ break;
+ }
+ current = plist_array_get_item(current, idx);
+ break;
+ }
+ default:
+ current = NULL;
+ break;
+ }
+ if (!current) {
+ break;
+ }
+ }
+ free(copy);
+ if (current) {
+ plist_t destnode = plist_copy(current);
+ plist_free(root_node);
+ root_node = destnode;
+ } else {
+ fprintf(stderr, "ERROR: nodepath '%s' is invalid\n", options->nodepath);
+ plist_free(root_node);
+ free(plist_entire);
+ free(options);
+ return 1;
+ }
+ }
+
+ if (options->flags & OPT_SORT) {
+ plist_sort(root_node);
+ }
+ if (options->out_fmt == PLIST_FORMAT_BINARY) {
+ output_res = plist_to_bin(root_node, &plist_out, &size);
+ } else if (options->out_fmt == PLIST_FORMAT_XML) {
+ output_res = plist_to_xml(root_node, &plist_out, &size);
+ } else if (options->out_fmt == PLIST_FORMAT_JSON) {
+ plist_write_options_t wropts = PLIST_OPT_NONE;
+ if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT;
+ if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE;
+ output_res = plist_to_json_with_options(root_node, &plist_out, &size, wropts);
+ } else if (options->out_fmt == PLIST_FORMAT_OSTEP) {
+ plist_write_options_t wropts = PLIST_OPT_NONE;
+ if (options->flags & OPT_COMPACT) wropts |= PLIST_OPT_COMPACT;
+ if (options->flags & OPT_COERCE) wropts |= PLIST_OPT_COERCE;
+ output_res = plist_to_openstep_with_options(root_node, &plist_out, &size, wropts);
+ } else {
+ plist_write_to_stream(root_node, stdout, options->out_fmt, PLIST_OPT_PARTIAL_DATA);
+ plist_free(root_node);
+ free(plist_entire);
+ free(options);
+ return 0;
+ }
+ }
+ }
+ plist_free(root_node);
+ free(plist_entire);
+
+ if (plist_out)
+ {
+ if (options->out_file != NULL && strcmp(options->out_file, "-") != 0)
+ {
+ FILE *oplist = fopen(options->out_file, "wb");
+ if (!oplist) {
+ fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
+ free(options);
+ return 1;
+ }
+ fwrite(plist_out, size, sizeof(char), oplist);
+ fclose(oplist);
+ }
+ // if no output file specified, write to stdout
+ else
+ fwrite(plist_out, size, sizeof(char), stdout);
+
+ free(plist_out);
+ }
+
+ if (input_res == PLIST_ERR_SUCCESS) {
+ switch (output_res) {
+ case PLIST_ERR_SUCCESS:
+ break;
+ case PLIST_ERR_CIRCULAR_REF:
+ fprintf(stderr, "ERROR: Circular reference detected.\n");
+ ret = 5;
+ break;
+ case PLIST_ERR_MAX_NESTING:
+ fprintf(stderr, "ERROR: Output plist data exceeds maximum nesting depth.\n");
+ ret = 4;
+ break;
+ case PLIST_ERR_FORMAT:
+ fprintf(stderr, "ERROR: Input plist data is not compatible with output format.\n");
+ ret = 2;
+ break;
+ default:
+ fprintf(stderr, "ERROR: Failed to convert plist data (%d)\n", output_res);
+ ret = 1;
+ break;
+ }
+ } else {
+ switch (input_res) {
+ case PLIST_ERR_PARSE:
+ if (options->out_fmt == 0) {
+ fprintf(stderr, "ERROR: Could not parse plist data, expected XML or binary plist\n");
+ } else {
+ fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
+ }
+ ret = 3;
+ break;
+ case PLIST_ERR_CIRCULAR_REF:
+ fprintf(stderr, "ERROR: Circular reference detected in input plist data.\n");
+ ret = 5;
+ break;
+ case PLIST_ERR_MAX_NESTING:
+ fprintf(stderr, "ERROR: Input plist data exceeds maximum nesting depth.\n");
+ ret = 4;
+ break;
+ default:
+ fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
+ ret = 1;
+ break;
+ }
+ }
+
+ free(options);
+ return ret;
+}