summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.github/FUNDING.yml3
-rw-r--r--.github/workflows/build.yml136
-rw-r--r--.github/workflows/cifuzz.yml26
-rw-r--r--.github/workflows/codeql-analysis.yml53
-rw-r--r--.gitignore6
-rw-r--r--Makefile.am24
-rw-r--r--NEWS98
-rw-r--r--README.md296
-rwxr-xr-xautogen.sh28
-rw-r--r--configure.ac145
-rw-r--r--cython/Makefile.am44
-rw-r--r--cython/plist.pxd3
-rw-r--r--cython/plist.pyx117
-rw-r--r--cython/plist_util.c31
-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.178
-rw-r--r--doxygen.cfg.in3119
-rw-r--r--fuzz/Makefile.am27
-rwxr-xr-xfuzz/init-fuzzers.sh16
-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.cc32
-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.cc32
-rw-r--r--fuzz/oplist_fuzzer.options3
-rwxr-xr-xfuzz/test-fuzzers.sh16
-rwxr-xr-xgit-version-gen20
-rw-r--r--include/Makefile.am29
-rw-r--r--include/plist/Array.h33
-rw-r--r--include/plist/Boolean.h2
-rw-r--r--include/plist/Data.h3
-rw-r--r--include/plist/Date.h12
-rw-r--r--include/plist/Dictionary.h15
-rw-r--r--include/plist/Integer.h10
-rw-r--r--include/plist/Key.h4
-rw-r--r--include/plist/Real.h2
-rw-r--r--include/plist/String.h5
-rw-r--r--include/plist/Structure.h3
-rw-r--r--include/plist/Uid.h2
-rw-r--r--include/plist/plist.h765
-rw-r--r--libcnary/Makefile.am17
-rw-r--r--libcnary/include/node.h42
-rw-r--r--libcnary/include/node_list.h23
-rw-r--r--libcnary/node.c52
-rw-r--r--libcnary/node_list.c33
-rw-r--r--m4/ac_pkg_cython.m46
-rw-r--r--m4/as-compiler-flag.m44
-rw-r--r--m4/ax_pthread.m4251
-rw-r--r--m4/ax_python_devel.m4361
-rw-r--r--m4/cython_python.m42
-rw-r--r--src/Array.cpp115
-rw-r--r--src/Boolean.cpp9
-rw-r--r--src/Data.cpp19
-rw-r--r--src/Date.cpp30
-rw-r--r--src/Dictionary.cpp42
-rw-r--r--src/Integer.cpp47
-rw-r--r--src/Key.cpp20
-rw-r--r--src/Makefile.am102
-rw-r--r--src/Node.cpp11
-rw-r--r--src/Real.cpp10
-rw-r--r--src/String.cpp41
-rw-r--r--src/Structure.cpp26
-rw-r--r--src/Uid.cpp9
-rw-r--r--src/base64.c2
-rw-r--r--src/bplist.c321
-rw-r--r--src/bytearray.c34
-rw-r--r--src/bytearray.h3
-rw-r--r--src/hashtable.c10
-rw-r--r--src/jplist.c862
-rw-r--r--src/jsmn.c289
-rw-r--r--src/jsmn.h92
-rw-r--r--src/libplist++-2.0.pc.in (renamed from src/libplist++.pc.in)4
-rw-r--r--src/libplist-2.0.pc.in (renamed from src/libplist.pc.in)2
-rw-r--r--src/oplist.c937
-rw-r--r--src/out-default.c491
-rw-r--r--src/out-limd.c449
-rw-r--r--src/out-plutil.c465
-rw-r--r--src/plist.c1167
-rw-r--r--src/plist.h22
-rw-r--r--src/ptrarray.c2
-rw-r--r--src/strbuf.h1
-rw-r--r--src/time64.c56
-rw-r--r--src/time64.h2
-rw-r--r--src/xplist.c204
-rw-r--r--test/Makefile.am72
-rwxr-xr-xtest/amp.test10
-rwxr-xr-xtest/bigarray++.test15
-rwxr-xr-xtest/bigarray.test4
-rwxr-xr-xtest/cdata.test2
-rw-r--r--test/data/data.bplistbin0 -> 3718 bytes
-rw-r--r--test/data/int64_min_max.json1
-rw-r--r--test/data/j1.json1
-rw-r--r--test/data/j2.json1
-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/test.strings12
-rw-r--r--test/data/uid.bplistbin0 -> 43 bytes
-rwxr-xr-xtest/dates.test4
-rwxr-xr-xtest/empty++.test15
-rwxr-xr-xtest/empty.test4
-rwxr-xr-xtest/empty_keys.test2
-rwxr-xr-xtest/entities.test2
-rwxr-xr-xtest/hex.test2
-rwxr-xr-xtest/huge++.test15
-rwxr-xr-xtest/huge.test4
-rw-r--r--test/integer_set.c130
-rwxr-xr-xtest/integer_set.test5
-rwxr-xr-xtest/invalid_tag.test10
-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.test4
-rwxr-xr-xtest/malformed_dict.test10
-rwxr-xr-xtest/medium++.test15
-rwxr-xr-xtest/medium.test4
-rwxr-xr-xtest/order.test2
-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.c131
-rw-r--r--test/plist_cmp.c58
-rw-r--r--test/plist_jtest.c131
-rw-r--r--test/plist_otest.c130
-rw-r--r--test/plist_test++.cpp103
-rw-r--r--test/plist_test.c12
-rwxr-xr-xtest/recursion.test10
-rwxr-xr-xtest/signedunsigned1.test4
-rwxr-xr-xtest/signedunsigned2.test4
-rwxr-xr-xtest/signedunsigned3.test4
-rwxr-xr-xtest/small++.test15
-rwxr-xr-xtest/small.test4
-rwxr-xr-xtest/timezone1.test6
-rwxr-xr-xtest/timezone2.test8
-rwxr-xr-xtest/uid.test15
-rw-r--r--tools/Makefile.am11
-rw-r--r--tools/plistutil.c269
169 files changed, 13351 insertions, 2374 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
index 2036484..233a584 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,22 +1,46 @@
name: build
-on: [push]
+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 install cython
- - uses: actions/checkout@v1
+ run: |
+ sudo apt-get update
+ pip install cython
+ - name: prepare environment
+ run: |
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: ./autogen.sh
+ 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 distcheck
- run: make distcheck
+ - 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@v4
+ with:
+ name: libplist-latest_${{env.target_triplet}}
+ path: libplist.tar
build-macOS:
runs-on: macOS-latest
steps:
@@ -27,28 +51,95 @@ jobs:
else
brew install libtool autoconf automake
fi
- pip install cython
+ pip3 install --break-system-packages cython
shell: bash
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: ./autogen.sh
+ 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@v4
+ 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: numworks/setup-msys2@v1
- - name: install dependencies
- run: msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc make libtool autoconf automake-wrapper
- - uses: actions/checkout@v1
+ - 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@v4
+ with:
+ fetch-depth: 0
- name: autogen
- run: msys2do ./autogen.sh --without-cython
+ run: ./autogen.sh CC=gcc CXX=g++ --enable-debug
- name: make
- run: msys2do make
+ run: make
- name: make check
- run: msys2do make check
+ run: make check
- name: print test logs
run: |
for I in test/*.trs; do
@@ -61,4 +152,15 @@ jobs:
fi
done
shell: bash
- if: always()
+ - 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@v4
+ 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 74ad8ba..e63b0b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,7 +49,13 @@ 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/data/*.out
+test/*.trs
cython/Makefile
cython/Makefile.in
cython/.deps
diff --git a/Makefile.am b/Makefile.am
index 5f36fd7..e6923a4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,18 +1,32 @@
AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = libcnary src include tools test
+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
+ rm -rf docs/html
doxygen doxygen.cfg
-docs: doxygen.cfg docs/html
+docs/README.doxygen.md: README.md
+ awk '/\#\# Table of Contents/{while(getline && $$0 != ""){}{print "[TOC]"}}1' README.md > $@
-clean-local:
- rm -rf docs
+docs: doxygen.cfg docs/README.doxygen.md docs/html
diff --git a/NEWS b/NEWS
index 0238e7e..2627893 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,101 @@
+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
~~~~~~~~~~~~~
diff --git a/README.md b/README.md
index 7a4e0d1..ad36634 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,283 @@
# libplist
-## About
+*A small portable C library to handle Apple Property List files in binary, XML,
+JSON, or OpenStep format.*
-A small portable C library to handle Apple Property List files in binary or XML.
+![](https://github.com/libimobiledevice/libplist/workflows/build/badge.svg)
+![](https://github.com/libimobiledevice/libplist/workflows/CodeQL/badge.svg)
-## Requirements
+## 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)
-Software:
-* make
-* autoheader
-* automake
-* autoconf
-* libtool
-* pkg-config
-* gcc or clang
+## Features
-Optional:
-* cython (Python bindings)
-* doxygen (Documentation)
+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`.
-## Installation
+Some key features are:
-To compile run:
-```bash
-./autogen.sh
-make
-sudo make install
+- **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 require a custom prefix or other option being passed to `./configure`
-you can pass them directly to `./autogen.sh` like this:
-```bash
-./autogen.sh --prefix=/opt/local --without-cython
+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
```
-## Who/What/Where?
+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.
-* Home: https://www.libimobiledevice.org/
-* Code: `git clone https://git.libimobiledevice.org/libplist.git`
-* Code (Mirror): `git clone https://github.com/libimobiledevice/libplist.git`
-* Tickets: https://github.com/libimobiledevice/libplist/issues
+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
-* IRC: irc://irc.freenode.net#libimobiledevice
* 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, iPod, and iPod Touch are trademarks of Apple Inc.
-libimobiledevice is an independent software library and has not been
-authorized, sponsored, or otherwise approved by Apple Inc.
+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
-README Updated on: 2019-05-16
diff --git a/autogen.sh b/autogen.sh
index 9164d13..5a0ec43 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,22 +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 "$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" "$@"
+ $srcdir/configure "$@"
fi
diff --git a/configure.ac b/configure.ac
index a2edbc4..6f574d9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,8 +1,8 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.64)
-AC_INIT([libplist], [2.2.0], [https://github.com/libimobiledevice/libplist/issues],, [https://libimobiledevice.org])
+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/])
@@ -15,14 +15,13 @@ dnl libtool versioning
# changes to the signature and the semantic)
# ? :+1 : ? == just internal changes
# CURRENT : REVISION : AGE
-LIBPLIST_SO_VERSION=6:0:3
+LIBPLIST_SO_VERSION=11:0:7
AC_SUBST(LIBPLIST_SO_VERSION)
-# prefer clang if it is available and CC is not set
-if test -z "$CC" && test -z "$CXX" && test -x "`which clang`"; then
- CC=clang
- CXX=clang++
+# 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.
@@ -39,10 +38,9 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
AC_LANG_POP
AM_PROG_CC_C_O
-AC_PROG_LIBTOOL
+LT_INIT
# Checks for header files.
-AC_HEADER_STDC
AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
@@ -54,7 +52,7 @@ AC_TYPE_UINT32_T
AC_TYPE_UINT8_T
# Checks for library functions.
-AC_CHECK_FUNCS([asprintf strcasecmp strdup strerror strndup stpcpy vasprintf gmtime_r localtime_r timegm strptime memmem])
+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])],
@@ -62,58 +60,52 @@ AC_C_BIGENDIAN([AC_DEFINE([__BIG_ENDIAN__], [1], [big endian])],
# Check for operating system
-AC_MSG_CHECKING([wether we need platform-specific build settings for ${host_os}])
+AC_MSG_CHECKING([for platform-specific build settings])
case ${host_os} in
*mingw32*|*cygwin*)
- AC_MSG_RESULT([yes])
+ AC_MSG_RESULT([${host_os}])
win32=true
;;
darwin*|*android*)
- AC_MSG_RESULT([no])
+ AC_MSG_RESULT([${host_os}])
;;
*)
- AC_MSG_RESULT([yes])
- AX_PTHREAD([], [AC_MSG_ERROR([pthread is required to build libplist])])
- AC_CHECK_LIB(pthread, [pthread_once], [], [AC_MSG_ERROR([pthread with pthread_once required to build libplist])])
+ AC_MSG_RESULT([${host_os}])
;;
esac
AM_CONDITIONAL(WIN32, test x$win32 = xtrue)
-# Check if we need libm for fmin
-CACHED_CFLAGS="$CFLAGS"
-CFLAGS="-O0"
-AC_CACHE_CHECK(if fmin is a builtin function, ac_cv_fmin_builtin,
- AC_TRY_LINK([
- #include <math.h>
- #include <float.h>
- ], [
- double val = 3.1415f * 0.55555f;
- double diff = fmin(val, DBL_MAX);
- if (diff > 0) return 1;
- ], ac_cv_fmin_builtin=yes, ac_cv_fmin_builtin=no))
-CFLAGS="$CACHED_CFLAGS"
+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_TRY_COMPILE([
- #include <time.h>
- ], [
- struct tm tm;
- tm.tm_gmtoff = 1;
- ], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no))
+ 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])
+ 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_TRY_COMPILE([
- #include <time.h>
- ], [
- struct tm tm;
- tm.tm_zone = 1;
- ], ac_cv_struct_tm_zone=yes, ac_cv_struct_tm_zone=no))
+ 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])
@@ -126,16 +118,19 @@ AC_ARG_WITH([cython],
[build_cython=false],
[build_cython=true])
if test "$build_cython" = "true"; then
- AM_PATH_PYTHON(2.3)
- AC_PROG_CYTHON(0.17.0)
- CYTHON_PYTHON
+ 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])
- CYTHON_PLIST_INCLUDE_DIR=$($PKG_CONFIG --variable=includedir libplist)/plist/cython
+ 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
@@ -150,16 +145,20 @@ else
fi
AM_CONDITIONAL([HAVE_CYTHON],[test "x$cython_python_bindings" = "xyes"])
-AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Wno-strict-aliasing -fvisibility=hidden $PTHREAD_CFLAGS")
-GLOBAL_LDFLAGS="$PTHREAD_LIBS"
+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")
-if test "x$ac_cv_fmin_builtin" != "xyes"; then
- GLOBAL_LDFLAGS+=" -lm"
-fi
+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],
- [enable debugging, default: no]),
+ [build debug message output code (default is no)]),
[case "${enableval}" in
yes) debug=yes ;;
no) debug=no ;;
@@ -168,11 +167,21 @@ esac],
[debug=no])
if (test "x$debug" = "xyes"); then
- AC_DEFINE(DEBUG, 1, [Define if debug code should be enabled.])
+ 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
@@ -192,6 +201,16 @@ AC_ARG_WITH([fuzzers],
[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.])
@@ -262,9 +281,14 @@ if test "x$build_sanitizers" = "xyes"; then
fi
if test "x$build_fuzzers" = "xyes"; then
- if test "$CXX" != "clang++"; 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)])
- fi
+ ;;
+ esac
CFLAGS+=" -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION"
@@ -273,6 +297,7 @@ if test "x$build_fuzzers" = "xyes"; then
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])
@@ -280,19 +305,21 @@ fi
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
-AC_OUTPUT([
+AC_CONFIG_FILES([
Makefile
libcnary/Makefile
src/Makefile
-src/libplist.pc
-src/libplist++.pc
+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:
@@ -301,6 +328,8 @@ 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/Makefile.am b/cython/Makefile.am
index bce8121..bbc51c9 100644
--- a/cython/Makefile.am
+++ b/cython/Makefile.am
@@ -3,24 +3,48 @@ AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CFLAGS = $(GLOBAL_CFLAGS)
AM_LDFLAGS = $(GLOBAL_LDFLAGS)
-EXTRA_DIST = plist.pyx plist.pxd
+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
+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)
-plist_la_LIBADD = $(top_builddir)/src/libplist.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)
diff --git a/cython/plist.pxd b/cython/plist.pxd
index b11d80d..5a41bf8 100644
--- a/cython/plist.pxd
+++ b/cython/plist.pxd
@@ -19,7 +19,8 @@ cdef class Bool(Node):
cdef class Integer(Node):
cpdef set_value(self, object value)
- cpdef uint64_t get_value(self)
+ cpdef get_value(self)
+ cpdef bint is_negative(self)
cdef class Uid(Node):
cpdef set_value(self, object value)
diff --git a/cython/plist.pyx b/cython/plist.pyx
index bfecf85..29c1181 100644
--- a/cython/plist.pyx
+++ b/cython/plist.pyx
@@ -5,7 +5,7 @@ from libc.stdint cimport *
cdef extern from *:
ctypedef enum plist_type:
PLIST_BOOLEAN,
- PLIST_UINT,
+ PLIST_INT,
PLIST_REAL,
PLIST_STRING,
PLIST_ARRAY,
@@ -14,6 +14,7 @@ cdef extern from *:
PLIST_DATA,
PLIST_KEY,
PLIST_UID,
+ PLIST_NULL,
PLIST_NONE
plist_t plist_new_bool(uint8_t val)
@@ -24,13 +25,17 @@ 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)
@@ -47,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)
@@ -77,6 +84,8 @@ cdef extern from *:
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
@@ -177,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 uint64_t 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()
@@ -209,10 +221,18 @@ cdef class Integer(Node):
cpdef set_value(self, object value):
plist_set_uint_val(self._c_node, int(value))
- cpdef uint64_t 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)
@@ -281,7 +301,8 @@ cdef class Uid(Node):
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()
@@ -312,6 +333,20 @@ cdef Uid Uid_factory(plist_t c_node, bint managed=True):
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):
@@ -453,23 +488,21 @@ cdef String String_factory(plist_t c_node, bint managed=True):
instance._c_node = c_node
return instance
-MAC_EPOCH = 978307200
-
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)
- secs -= MAC_EPOCH
- node = plist_new_date(secs, usecs)
+ node = plist_new_unix_date(datetime_to_timestamp(value))
return node
cdef class Date(Node):
@@ -496,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
- plist_get_date_val(self._c_node, &secs, &usecs)
- secs += MAC_EPOCH
- 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)
@@ -683,7 +718,7 @@ cdef class Dict(Node):
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)
@@ -816,7 +851,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)
@@ -831,7 +866,7 @@ 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)
@@ -849,6 +884,8 @@ cdef object plist_t_to_node(plist_t c_plist, bint managed=True):
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
@@ -940,8 +977,10 @@ cpdef object dumps(value, fmt=FMT_XML, sort_keys=True, skipkeys=False):
node = Dict(value)
elif type(value) in (list, set, tuple):
node = Array(value)
+ else:
+ node = value
if fmt == FMT_XML:
- return node.to_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 4f922e5..27084fa 100644
--- a/cython/plist_util.c
+++ b/cython/plist_util.c
@@ -3,13 +3,11 @@
#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;
memset(&t, 0, sizeof(t));
@@ -19,22 +17,21 @@ void datetime_to_ints(PyObject* obj, int32_t* sec, int32_t* usec) {
t.tm_mday = PyDateTime_GET_DAY(obj);
t.tm_mon = PyDateTime_GET_MONTH(obj)-1;
t.tm_year = PyDateTime_GET_YEAR(obj)-1900;
- *sec = (int32_t)mktime(&t);
- *usec = PyDateTime_DATE_GET_MICROSECOND(obj);
+ 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..b1f7773
--- /dev/null
+++ b/docs/plistutil.1
@@ -0,0 +1,78 @@
+.TH "plistutil" 1
+.SH NAME
+plistutil \- Convert a plist FILE between binary, XML, and JSON format
+.SH SYNOPSIS
+.B plistutil
+[OPTIONS]
+[-i FILE]
+[-o FILE]
+.SH DESCRIPTION
+plistutil allows converting a Property List file between binary, XML, and JSON format.
+.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 \-c, \-\-compact
+JSON and OpenStep only: Print output in compact form. By default, the output
+will be pretty-printed.
+.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.in b/doxygen.cfg.in
index 9ed1b14..eb84c85 100644
--- a/doxygen.cfg.in
+++ b/doxygen.cfg.in
@@ -1,1417 +1,2806 @@
-# Doxyfile 1.5.6
+# Doxyfile 1.11.0
# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
+# doxygen (www.doxygen.org) for a project.
#
-# All text after a hash (#) is considered a comment and will be ignored
+# 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 (" ")
+# 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 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.
+# 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 quotes) that should identify the project.
+# 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.
+# 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@
-# 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.
+# 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
-# 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.
+# 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
-# 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.
+# 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 (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.
+# 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 (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
+# 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" "the"
+# 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 =
+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
+# 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
+# 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 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.
+# 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
-# 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.
+# 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 include paths that
-# are normally passed to the compiler using the -I flag.
+# 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 =
+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.
+# 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
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
+# 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 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.)
+# 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 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.
+# 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
-# 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.
+# 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.
-DETAILS_AT_TOP = NO
+PYTHON_DOCSTRING = YES
-# 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.
+# 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.
+# 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 = 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.
+# 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 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.
+# 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
-# 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.
+# 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 only. Doxygen will then generate output that is more tailored for
-# Fortran.
+# 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.
+# 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
-# 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.
+# 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 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.
+# 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 (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.
+# 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
+# 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
-# 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.
+# 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 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
+# 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 and EXTRACT_STATIC tags are set to YES
+# 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.
+# 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_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
+# 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.
+# 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. 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.
+# 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.
+# 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 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.
+# 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 (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
+# 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 (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
+# 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 (the default) these blocks will be appended to the
-# function's detailed documentation block.
+# 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 (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
+# 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
-# 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.
+# 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.
-CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = NO
-# 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.
+# 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_SCOPE_NAMES = 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 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.
+# 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.
-SHOW_INCLUDE_FILES = YES
+FORCE_LOCAL_INCLUDES = NO
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
+# 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 (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.
+# 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 documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
+# 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_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.
+# 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 default), the class list will be sorted only by class name,
-# not including the namespace part.
+# 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.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
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.
+# 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 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 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 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 sectionname ... \endif.
+# 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 =
+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.
+# 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
+# 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 = 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
+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 is YES.
+# 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 is 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. See the manual for examples.
+# 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.
-FILE_VERSION_FILTER =
+CITE_BIB_FILES =
#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
+# 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.
+# 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 by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
+# 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 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.
+# 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 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.
+# 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
-# 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.
+# 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
-# 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)
+# 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"
-# 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.
+# 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"
-WARN_LOGFILE =
+# 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
+# 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.
+# 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
+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, 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.
+# 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
-# 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
+# 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 =
-FILE_PATTERNS =
+# 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.
-# 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.
+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
-# excluded from the INPUT source files. This way you can easily exclude a
+# 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 = dev
+EXCLUDE =
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
+# 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/*
+# 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 =
+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
+# 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 =
+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).
+# 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 =
+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.
+# 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 =
+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.
+# 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 image that are included in the documentation (see
-# the \image command).
+# 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 =
+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.
+# 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.
-INPUT_FILTER =
+FILTER_PATTERNS =
-# 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.
+# 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_PATTERNS =
+FILTER_SOURCE_FILES = NO
-# 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).
+# 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_FILES = NO
+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
+# 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.
+# 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 and classes directly in the documentation.
+# 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 (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.
+# 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 function all documented
-# functions referencing it will be listed.
+# 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.
+# 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 (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.
+# 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 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.
+# 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 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.
+# 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
+# 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.
+# 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
-# 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
+# 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.
-# 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 =
+IGNORE_PREFIX =
#---------------------------------------------------------------------------
-# configuration options related to the HTML output
+# Configuration options related to the HTML output
#---------------------------------------------------------------------------
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate 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. If left blank `html' will be used as the default path.
+# 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). If it is left blank
-# doxygen will generate files with .html extension.
+# 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 personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
+# 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_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
+HTML_DYNAMIC_SECTIONS = 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.
+# 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
-# 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.
+# 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"
-# 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.
+# 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
-# 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).
+# 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.
-HTML_DYNAMIC_SECTIONS = NO
+GENERATE_HTMLHELP = 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
+# 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 =
+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.
+# 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 =
+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).
+# 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
-# 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.
+# 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 =
+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.
+# 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 contents of the HTML help documentation and to the tree view.
+# 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 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.
+# 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
-# 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.
+# 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
-# 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.
+# 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
-# 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.
+# 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
+# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate 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. If left blank `latex' will be used as the default path.
+# 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. If left blank `latex' will be used as the default command name.
+# 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. If left blank `makeindex' will be used as the
-# default command name.
+# 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
-# 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 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.
-# 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.
+LATEX_MAKEINDEX_CMD = makeindex
-PAPER_TYPE = a4wide
+# 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.
-# 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 =
+COMPACT_LATEX = NO
-# 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.
+# 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, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
+# 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
-# 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.
+# 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 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.
+# 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
+# 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.
+# 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. If left blank `rtf' will be used as the default path.
+# 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.
+# 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 other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
+# 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
-# config file, i.e. a series of assignments. You only have to provide
+# 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 =
+RTF_STYLESHEET_FILE =
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config 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 =
+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
+# Configuration options related to the man page output
#---------------------------------------------------------------------------
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
+# 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. If left blank `man' will be used as the default path.
+# 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 (default is the subroutine's section .3)
+# 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
-# 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.
+# 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
+# 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.
+# 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. If left blank `xml' will be used as the default path.
+# 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
-# 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.
+# 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
-XML_SCHEMA =
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
-# 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.
+# 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.
-XML_DTD =
+GENERATE_DOCBOOK = NO
-# 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 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.
-XML_PROGRAMLISTING = YES
+DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
+# 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.
+# 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 the Perl module output
+# Configuration options related to Sqlite3 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.
+# 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.
-PERLMOD_LATEX = NO
+GENERATE_SQLITE3 = 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 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.
-PERLMOD_PRETTY = YES
+SQLITE3_OUTPUT = sqlite3
-# 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.
+# 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.
-PERLMOD_MAKEVAR_PREFIX =
+SQLITE3_RECREATE_DB = YES
#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
+# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
-# 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.
+# 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.
-ENABLE_PREPROCESSING = YES
+GENERATE_PERLMOD = NO
-# 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.
+# 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.
-MACRO_EXPANSION = NO
+PERLMOD_LATEX = 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.
+# 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.
-EXPAND_ONLY_PREDEF = NO
+PERLMOD_PRETTY = YES
-# 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.
+# 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.
-SEARCH_INCLUDES = NO
+PERLMOD_MAKEVAR_PREFIX =
-# 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.
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
-INCLUDE_PATH =
+# 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.
-# 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.
+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.
-INCLUDE_FILE_PATTERNS =
+MACRO_EXPANSION = YES
-# 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.
+# 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.
-PREDEFINED =
+EXPAND_ONLY_PREDEF = YES
-# 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.
+# 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.
-EXPAND_AS_DEFINED =
+SEARCH_INCLUDES = NO
-# 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.
+# 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::additions related to external references
+# Configuration options 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.
+# 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 =
+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.
+# 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 =
+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.
+# 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 modules index. If set to NO, only the current project's groups will
-# be listed.
+# 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
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
+# 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.
-PERL_PATH = /usr/bin/perl
+EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
+# Configuration options related to diagram generator tools
#---------------------------------------------------------------------------
-# 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.
+# 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, 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)
+# 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
-# 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.
+# 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 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.
+# 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 and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
+# 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
+# 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 set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
+# 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 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.
+# 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 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.
+# 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 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.
+# 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 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.
+# 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 and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
+# 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, 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.
+# 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 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.
+# 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
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# 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).
-DOT_PATH =
+DIAFILE_DIRS =
-# 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).
+# 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.
-DOTFILE_DIRS =
+PLANTUML_JAR_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 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.
+# 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
+# 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_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.
+# 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 (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
+# 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 (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
+# 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
-#---------------------------------------------------------------------------
-# Configuration::additions related to the search engine
-#---------------------------------------------------------------------------
+# 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".
-# 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.
+MSCGEN_TOOL =
-SEARCHENGINE = NO
+# 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
index b9798f9..8ea3fb0 100644
--- a/fuzz/Makefile.am
+++ b/fuzz/Makefile.am
@@ -19,18 +19,37 @@ LIBFUZZER_SRC:
CLEANFILES = libFuzzer.a
-noinst_PROGRAMS = xplist_fuzzer bplist_fuzzer
+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.la libFuzzer.a
+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.la libFuzzer.a
+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 init-fuzzers.sh test-fuzzers.sh 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/init-fuzzers.sh b/fuzz/init-fuzzers.sh
index 4d28016..c9b1955 100755
--- a/fuzz/init-fuzzers.sh
+++ b/fuzz/init-fuzzers.sh
@@ -5,7 +5,7 @@ FUZZDIR=`dirname $0`
cd ${FUZZDIR}
-if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer; then
+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
@@ -19,5 +19,19 @@ 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/fuzz/jplist_fuzzer.cc b/fuzz/jplist_fuzzer.cc
new file mode 100644
index 0000000..a10da59
--- /dev/null
+++ b/fuzz/jplist_fuzzer.cc
@@ -0,0 +1,32 @@
+/*
+ * jplist_fuzzer.cc
+ * JSON plist fuzz target for libFuzzer
+ *
+ * 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
+ * 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>
+
+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);
+
+ 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/fuzz/oplist_fuzzer.cc b/fuzz/oplist_fuzzer.cc
new file mode 100644
index 0000000..0fabed8
--- /dev/null
+++ b/fuzz/oplist_fuzzer.cc
@@ -0,0 +1,32 @@
+/*
+ * oplist_fuzzer.cc
+ * OpenStep plist fuzz target for libFuzzer
+ *
+ * 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
+ */
+
+#include <plist/plist.h>
+#include <stdio.h>
+
+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);
+
+ 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
index b0a8367..4fdf82b 100755
--- a/fuzz/test-fuzzers.sh
+++ b/fuzz/test-fuzzers.sh
@@ -5,13 +5,13 @@ FUZZDIR=`dirname $0`
cd ${FUZZDIR}
-if ! test -x xplist_fuzzer || ! test -x bplist_fuzzer; then
+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; then
+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
@@ -29,5 +29,17 @@ if ! ./bplist_fuzzer bplist-input -dict=bplist.dict -max_len=4096 -runs=10000; t
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/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/Makefile.am b/include/Makefile.am
index 4e4db2e..2fa500e 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,14 +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
+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 745b750..f4360c5 100644
--- a/include/plist/Array.h
+++ b/include/plist/Array.h
@@ -34,17 +34,42 @@ public :
Array(Node* parent = NULL);
Array(plist_t node, Node* parent = NULL);
Array(const Array& a);
- Array& operator=(Array& a);
+ Array& operator=(const Array& a);
virtual ~Array();
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) const;
+ 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;
diff --git a/include/plist/Boolean.h b/include/plist/Boolean.h
index 307a1ff..9ecbc48 100644
--- a/include/plist/Boolean.h
+++ b/include/plist/Boolean.h
@@ -33,7 +33,7 @@ public :
Boolean(Node* parent = NULL);
Boolean(plist_t node, Node* parent = NULL);
Boolean(const Boolean& b);
- Boolean& operator=(Boolean& b);
+ Boolean& operator=(const Boolean& b);
Boolean(bool b);
virtual ~Boolean();
diff --git a/include/plist/Data.h b/include/plist/Data.h
index c9c089b..1de88aa 100644
--- a/include/plist/Data.h
+++ b/include/plist/Data.h
@@ -34,8 +34,9 @@ public :
Data(Node* parent = NULL);
Data(plist_t node, Node* parent = NULL);
Data(const Data& d);
- Data& operator=(Data& d);
+ Data& operator=(const Data& b);
Data(const std::vector<char>& buff);
+ Data(const char* buff, uint64_t size);
virtual ~Data();
Node* Clone() const;
diff --git a/include/plist/Date.h b/include/plist/Date.h
index 510a349..7026699 100644
--- a/include/plist/Date.h
+++ b/include/plist/Date.h
@@ -24,7 +24,11 @@
#include <plist/Node.h>
#include <ctime>
+#ifdef _MSC_VER
+#include <winsock2.h>
+#else
#include <sys/time.h>
+#endif
namespace PList
{
@@ -35,14 +39,14 @@ public :
Date(Node* parent = NULL);
Date(plist_t node, Node* parent = NULL);
Date(const Date& d);
- Date& operator=(Date& d);
- Date(timeval t);
+ Date& operator=(const Date& d);
+ Date(int64_t t);
virtual ~Date();
Node* Clone() const;
- void SetValue(timeval t);
- timeval GetValue() const;
+ void SetValue(int64_t t);
+ int64_t GetValue() const;
};
};
diff --git a/include/plist/Dictionary.h b/include/plist/Dictionary.h
index 71d40ac..37ff1c9 100644
--- a/include/plist/Dictionary.h
+++ b/include/plist/Dictionary.h
@@ -35,7 +35,7 @@ public :
Dictionary(Node* parent = NULL);
Dictionary(plist_t node, Node* parent = NULL);
Dictionary(const Dictionary& d);
- Dictionary& operator=(Dictionary& d);
+ Dictionary& operator=(const Dictionary& d);
virtual ~Dictionary();
Node* Clone() const;
@@ -45,22 +45,27 @@ public :
Node* operator[](const std::string& key);
iterator Begin();
+ iterator begin();
iterator End();
+ iterator end();
iterator Find(const std::string& key);
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);
- iterator Insert(const std::string& key, Node* node) PLIST_WARN_DEPRECATED("use Set() instead");
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;
-
-
};
};
diff --git a/include/plist/Integer.h b/include/plist/Integer.h
index adbc39a..1a4d980 100644
--- a/include/plist/Integer.h
+++ b/include/plist/Integer.h
@@ -33,14 +33,20 @@ public :
Integer(Node* parent = NULL);
Integer(plist_t node, Node* parent = NULL);
Integer(const Integer& i);
- Integer& operator=(Integer& i);
+ Integer& operator=(const Integer& i);
Integer(uint64_t i);
+ Integer(int64_t i);
virtual ~Integer();
Node* Clone() const;
+ void SetValue(int64_t i);
void SetValue(uint64_t i);
- uint64_t GetValue() const;
+ void SetUnsignedValue(uint64_t i);
+ int64_t GetValue() const;
+ uint64_t GetUnsignedValue() const;
+
+ bool isNegative() const;
};
};
diff --git a/include/plist/Key.h b/include/plist/Key.h
index c680f1c..bd5f7bc 100644
--- a/include/plist/Key.h
+++ b/include/plist/Key.h
@@ -33,8 +33,8 @@ class Key : public Node
public :
Key(Node* parent = NULL);
Key(plist_t node, Node* parent = NULL);
- Key(const Key& s);
- Key& operator=(Key& s);
+ Key(const Key& k);
+ Key& operator=(const Key& k);
Key(const std::string& s);
virtual ~Key();
diff --git a/include/plist/Real.h b/include/plist/Real.h
index c2d55f8..5afb0c0 100644
--- a/include/plist/Real.h
+++ b/include/plist/Real.h
@@ -33,7 +33,7 @@ public :
Real(Node* parent = NULL);
Real(plist_t node, Node* parent = NULL);
Real(const Real& d);
- Real& operator=(Real& d);
+ Real& operator=(const Real& d);
Real(double d);
virtual ~Real();
diff --git a/include/plist/String.h b/include/plist/String.h
index 80290b3..95f8dd1 100644
--- a/include/plist/String.h
+++ b/include/plist/String.h
@@ -34,8 +34,11 @@ public :
String(Node* parent = NULL);
String(plist_t node, Node* parent = NULL);
String(const String& s);
- String& operator=(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() const;
diff --git a/include/plist/Structure.h b/include/plist/Structure.h
index eded8b2..b7fbdb5 100644
--- a/include/plist/Structure.h
+++ b/include/plist/Structure.h
@@ -43,6 +43,9 @@ public :
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);
diff --git a/include/plist/Uid.h b/include/plist/Uid.h
index 2d8375b..af6e51d 100644
--- a/include/plist/Uid.h
+++ b/include/plist/Uid.h
@@ -33,7 +33,7 @@ public :
Uid(Node* parent = NULL);
Uid(plist_t node, Node* parent = NULL);
Uid(const Uid& i);
- Uid& operator=(Uid& i);
+ Uid& operator=(const Uid& i);
Uid(uint64_t i);
virtual ~Uid();
diff --git a/include/plist/plist.h b/include/plist/plist.h
index ab91612..41af8ce 100644
--- a/include/plist/plist.h
+++ b/include/plist/plist.h
@@ -3,7 +3,7 @@
* @brief Main include of libplist
* \internal
*
- * Copyright (c) 2012-2019 Nikias Bassen, 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
@@ -44,6 +44,7 @@ extern "C"
#include <stdint.h>
#endif
+/*{{{ deprecation macros */
#ifdef __llvm__
#if defined(__has_extension)
#if (__has_extension(attribute_deprecated_with_message))
@@ -72,12 +73,24 @@ extern "C"
#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
*/
/*@{*/
@@ -103,19 +116,68 @@ extern "C"
*/
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 =-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_NONE /**< No type */
+ 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_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_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)
+
/********************************************
* *
@@ -129,7 +191,7 @@ extern "C"
* @return the created plist
* @sa #plist_type
*/
- plist_t plist_new_dict(void);
+ PLIST_API plist_t plist_new_dict(void);
/**
* Create a new root plist_t type #PLIST_ARRAY
@@ -137,7 +199,7 @@ extern "C"
* @return the created plist
* @sa #plist_type
*/
- plist_t plist_new_array(void);
+ PLIST_API plist_t plist_new_array(void);
/**
* Create a new plist_t type #PLIST_STRING
@@ -146,7 +208,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_string(const char *val);
+ PLIST_API plist_t plist_new_string(const char *val);
/**
* Create a new plist_t type #PLIST_BOOLEAN
@@ -155,16 +217,29 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_bool(uint8_t val);
+ 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_t plist_new_uint(uint64_t val);
+ PLIST_API plist_t plist_new_int(int64_t val);
/**
* Create a new plist_t type #PLIST_REAL
@@ -173,7 +248,7 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_real(double val);
+ PLIST_API plist_t plist_new_real(double val);
/**
* Create a new plist_t type #PLIST_DATA
@@ -183,17 +258,16 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_data(const char *val, uint64_t length);
+ PLIST_API plist_t plist_new_data(const char *val, uint64_t length);
/**
* 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_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
@@ -202,14 +276,23 @@ extern "C"
* @return the created item
* @sa #plist_type
*/
- plist_t plist_new_uid(uint64_t val);
+ 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
*
* @param plist the plist to free
*/
- void plist_free(plist_t plist);
+ PLIST_API void plist_free(plist_t plist);
/**
* Return a copy of passed node and it's children
@@ -217,7 +300,7 @@ extern "C"
* @param node the plist to copy
* @return copied plist
*/
- plist_t plist_copy(plist_t node);
+ PLIST_API plist_t plist_copy(plist_t node);
/********************************************
@@ -232,7 +315,7 @@ extern "C"
* @param node the node of type #PLIST_ARRAY
* @return size of the #PLIST_ARRAY node
*/
- uint32_t plist_array_get_size(plist_t node);
+ PLIST_API uint32_t plist_array_get_size(plist_t node);
/**
* Get the nth item in a #PLIST_ARRAY node.
@@ -241,7 +324,7 @@ extern "C"
* @param n the index of the item to get. Range is [0, array_size[
* @return the nth item or NULL if node is not of type #PLIST_ARRAY
*/
- plist_t plist_array_get_item(plist_t node, uint32_t n);
+ PLIST_API plist_t plist_array_get_item(plist_t node, uint32_t n);
/**
* Get the index of an item. item must be a member of a #PLIST_ARRAY node.
@@ -249,7 +332,7 @@ extern "C"
* @param node the node
* @return the node index or UINT_MAX if node index can't be determined
*/
- uint32_t plist_array_get_item_index(plist_t node);
+ PLIST_API uint32_t plist_array_get_item_index(plist_t node);
/**
* Set the nth item in a #PLIST_ARRAY node.
@@ -259,7 +342,7 @@ extern "C"
* @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[. Assert if n is not in range.
*/
- void plist_array_set_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API void 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.
@@ -267,7 +350,7 @@ extern "C"
* @param node the node of type #PLIST_ARRAY
* @param item the new item. The array is responsible for freeing item when it is no longer needed.
*/
- void plist_array_append_item(plist_t node, plist_t item);
+ PLIST_API void plist_array_append_item(plist_t node, plist_t item);
/**
* Insert a new item at position n in a #PLIST_ARRAY node.
@@ -276,7 +359,7 @@ extern "C"
* @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[. Assert if n is not in range.
*/
- void plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
+ PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n);
/**
* Remove an existing position in a #PLIST_ARRAY node.
@@ -285,7 +368,7 @@ extern "C"
* @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.
*/
- void plist_array_remove_item(plist_t node, uint32_t n);
+ PLIST_API void plist_array_remove_item(plist_t node, uint32_t n);
/**
* Remove a node that is a child node of a #PLIST_ARRAY node.
@@ -293,7 +376,7 @@ extern "C"
*
* @param node The node to be removed from its #PLIST_ARRAY parent.
*/
- void plist_array_item_remove(plist_t node);
+ PLIST_API void plist_array_item_remove(plist_t node);
/**
* Create an iterator of a #PLIST_ARRAY node.
@@ -302,7 +385,7 @@ extern "C"
* @param node The node of type #PLIST_ARRAY
* @param iter Location to store the iterator for the array.
*/
- void plist_array_new_iter(plist_t node, plist_array_iter *iter);
+ PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter);
/**
* Increment iterator of a #PLIST_ARRAY node.
@@ -313,7 +396,7 @@ extern "C"
* returned item. Will be set to NULL when no more items are left
* to iterate.
*/
- void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item);
+ PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item);
/********************************************
@@ -328,7 +411,7 @@ extern "C"
* @param node the node of type #PLIST_DICT
* @return size of the #PLIST_DICT node
*/
- uint32_t plist_dict_get_size(plist_t node);
+ PLIST_API uint32_t plist_dict_get_size(plist_t node);
/**
* Create an iterator of a #PLIST_DICT node.
@@ -337,7 +420,7 @@ extern "C"
* @param node The node of type #PLIST_DICT.
* @param iter Location to store the iterator for the dictionary.
*/
- void plist_dict_new_iter(plist_t node, plist_dict_iter *iter);
+ PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter);
/**
* Increment iterator of a #PLIST_DICT node.
@@ -350,7 +433,7 @@ extern "C"
* free the returned value. Will be set to NULL when no more
* key/value pairs are left to iterate.
*/
- void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val);
+ PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val);
/**
* Get key associated key to an item. Item must be member of a dictionary.
@@ -358,7 +441,7 @@ extern "C"
* @param node the item
* @param key a location to store the key. The caller is responsible for freeing the returned string.
*/
- void plist_dict_get_item_key(plist_t node, char **key);
+ PLIST_API void plist_dict_get_item_key(plist_t node, char **key);
/**
* Get the nth item in a #PLIST_DICT node.
@@ -368,7 +451,7 @@ extern "C"
* @return the item or NULL if node is not of type #PLIST_DICT. The caller should not free
* the returned node.
*/
- plist_t plist_dict_get_item(plist_t node, const char* key);
+ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key);
/**
* Get key node associated to an item. Item must be member of a dictionary.
@@ -376,7 +459,7 @@ extern "C"
* @param node the item
* @return the key node of the given item, or NULL.
*/
- plist_t plist_dict_item_get_key(plist_t node);
+ PLIST_API plist_t plist_dict_item_get_key(plist_t node);
/**
* Set item identified by key in a #PLIST_DICT node.
@@ -387,19 +470,7 @@ extern "C"
* @param item the new item associated to key
* @param key the identifier of the item to set.
*/
- void plist_dict_set_item(plist_t node, const char* key, plist_t item);
-
- /**
- * Insert a new item into a #PLIST_DICT node.
- *
- * @deprecated Deprecated. Use plist_dict_set_item instead.
- *
- * @param node the node of type #PLIST_DICT
- * @param item the new item to insert
- * @param key The identifier of the item to insert.
- */
- PLIST_WARN_DEPRECATED("use plist_dict_set_item instead")
- void plist_dict_insert_item(plist_t node, const char* key, plist_t item);
+ PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item);
/**
* Remove an existing position in a #PLIST_DICT node.
@@ -408,7 +479,7 @@ extern "C"
* @param node the node of type #PLIST_DICT
* @param key The identifier of the item to remove. Assert if identifier is not present.
*/
- void plist_dict_remove_item(plist_t node, const char* key);
+ PLIST_API void plist_dict_remove_item(plist_t node, const char* key);
/**
* Merge a dictionary into another. This will add all key/value pairs
@@ -418,8 +489,168 @@ extern "C"
* @param target pointer to an existing node of type #PLIST_DICT
* @param source node of type #PLIST_DICT that should be merged into target
*/
- void plist_dict_merge(plist_t *target, plist_t source);
+ PLIST_API void 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 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);
/********************************************
* *
@@ -432,7 +663,7 @@ extern "C"
*
* @param node the parent (NULL if node is root)
*/
- plist_t plist_get_parent(plist_t node);
+ PLIST_API plist_t plist_get_parent(plist_t node);
/**
* Get the #plist_type of a node.
@@ -440,7 +671,7 @@ extern "C"
* @param node the node
* @return the type of the node
*/
- plist_type plist_get_node_type(plist_t node);
+ PLIST_API plist_type plist_get_node_type(plist_t node);
/**
* Get the value of a #PLIST_KEY node.
@@ -449,8 +680,9 @@ 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.
*/
- void plist_get_key_val(plist_t node, char **val);
+ PLIST_API void plist_get_key_val(plist_t node, char **val);
/**
* Get the value of a #PLIST_STRING node.
@@ -459,8 +691,9 @@ 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.
*/
- void plist_get_string_val(plist_t node, char **val);
+ PLIST_API void plist_get_string_val(plist_t node, char **val);
/**
* Get a pointer to the buffer of a #PLIST_STRING node.
@@ -473,7 +706,7 @@ extern "C"
*
* @return Pointer to the NULL-terminated buffer.
*/
- const char* plist_get_string_ptr(plist_t node, uint64_t* length);
+ PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length);
/**
* Get the value of a #PLIST_BOOLEAN node.
@@ -482,16 +715,25 @@ extern "C"
* @param node the node
* @param val a pointer to a uint8_t variable.
*/
- void plist_get_bool_val(plist_t node, uint8_t * val);
+ 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.
*/
- void plist_get_uint_val(plist_t node, uint64_t * val);
+ 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.
@@ -500,7 +742,7 @@ extern "C"
* @param node the node
* @param val a pointer to a double variable.
*/
- void plist_get_real_val(plist_t node, double *val);
+ PLIST_API void plist_get_real_val(plist_t node, double *val);
/**
* Get the value of a #PLIST_DATA node.
@@ -510,8 +752,9 @@ 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.
*/
- void plist_get_data_val(plist_t node, char **val, uint64_t * length);
+ 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.
@@ -524,17 +767,16 @@ extern "C"
*
* @return Pointer to the buffer
*/
- const char* plist_get_data_ptr(plist_t node, uint64_t* length);
+ 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).
*/
- 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.
@@ -543,7 +785,7 @@ extern "C"
* @param node the node
* @param val a pointer to a uint64_t variable.
*/
- void plist_get_uid_val(plist_t node, uint64_t * val);
+ PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val);
/********************************************
@@ -559,7 +801,7 @@ extern "C"
* @param node the node
* @param val the key value
*/
- void plist_set_key_val(plist_t node, const char *val);
+ PLIST_API void plist_set_key_val(plist_t node, const char *val);
/**
* Set the value of a node.
@@ -569,7 +811,7 @@ extern "C"
* @param val the string value. The string is copied when set and will be
* freed by the node.
*/
- void plist_set_string_val(plist_t node, const char *val);
+ PLIST_API void plist_set_string_val(plist_t node, const char *val);
/**
* Set the value of a node.
@@ -578,16 +820,25 @@ extern "C"
* @param node the node
* @param val the boolean value
*/
- void plist_set_bool_val(plist_t node, uint8_t val);
+ PLIST_API void plist_set_bool_val(plist_t node, uint8_t val);
/**
* 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
*/
- void plist_set_uint_val(plist_t node, uint64_t val);
+ PLIST_API void plist_set_uint_val(plist_t node, uint64_t val);
+
+ /**
+ * 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.
@@ -596,7 +847,7 @@ extern "C"
* @param node the node
* @param val the real value
*/
- void plist_set_real_val(plist_t node, double val);
+ PLIST_API void plist_set_real_val(plist_t node, double val);
/**
* Set the value of a node.
@@ -607,17 +858,16 @@ extern "C"
* be freed by the node.
* @param length the length of the buffer
*/
- void plist_set_data_val(plist_t node, const char *val, uint64_t length);
+ PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length);
/**
* Set the value of a node.
* 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)
*/
- 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.
@@ -626,7 +876,7 @@ extern "C"
* @param node the node
* @param val the unsigned integer value
*/
- void plist_set_uid_val(plist_t node, uint64_t val);
+ PLIST_API void plist_set_uid_val(plist_t node, uint64_t val);
/********************************************
@@ -642,32 +892,49 @@ 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.
*/
- 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);
/**
- * Frees the memory allocated by plist_to_xml().
+ * Export the #plist_t structure to binary format.
*
- * @param plist_xml The buffer allocated by plist_to_xml().
+ * @param plist the root node to export
+ * @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.
*/
- void plist_to_xml_free(char *plist_xml);
+ PLIST_API plist_err_t plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
/**
- * Export the #plist_t structure to binary format.
+ * Export the #plist_t structure to JSON format.
*
* @param plist the root node to export
- * @param plist_bin a pointer to a char* buffer. This function allocates the memory,
- * caller is responsible for freeing it.
+ * @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.
*/
- void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length);
+ PLIST_API plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify);
/**
- * Frees the memory allocated by plist_to_bin().
+ * Export the #plist_t structure to OpenStep format.
*
- * @param plist_bin The buffer allocated by plist_to_bin().
+ * @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.
*/
- void plist_to_bin_free(char *plist_bin);
+ PLIST_API plist_err_t plist_to_openstep(plist_t plist, char **plist_openstep, uint32_t* length, int prettify);
+
/**
* Import the #plist_t structure from XML format.
@@ -675,8 +942,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
*/
- 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.
@@ -684,33 +952,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
*/
- void plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
+ PLIST_API plist_err_t plist_from_bin(const char *plist_bin, uint32_t length, plist_t * plist);
/**
- * Import the #plist_t structure from memory data.
- * This method will look at the first bytes of plist_data
- * to determine if plist_data contains a binary or XML plist.
+ * Import the #plist_t structure from JSON format.
*
- * @param plist_data a pointer to the memory buffer containing plist data.
+ * @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 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.
*/
- void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist);
+ PLIST_API void plist_print(plist_t plist);
/**
- * Test if in-memory plist data is binary or XML
- * This method will look at the first bytes of plist_data
- * to determine if plist_data contains a binary or XML plist.
- * This method is not validating the whole memory buffer to check if the
- * content is truly a plist, it's only using some heuristic on the first few
- * bytes of plist_data.
+ * 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.
*/
- int plist_is_binary(const char *plist_data, uint32_t length);
+ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length);
/********************************************
* *
@@ -727,7 +1091,7 @@ extern "C"
* @param length length of the path to access
* @return the value to access.
*/
- plist_t plist_access_path(plist_t plist, uint32_t length, ...);
+ PLIST_API plist_t plist_access_path(plist_t plist, uint32_t length, ...);
/**
* Variadic version of #plist_access_path.
@@ -737,7 +1101,7 @@ extern "C"
* @param v list of array's index and dic'st key
* @return the value to access.
*/
- plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v);
+ PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v);
/**
* Compare two node values
@@ -746,42 +1110,74 @@ extern "C"
* @param node_r rigth node to compare
* @return TRUE is type and value match, FALSE otherwise.
*/
- char plist_compare_node_value(plist_t node_l, plist_t node_r);
+ 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)
- #define PLIST_IS_UINT(__plist) _PLIST_IS_TYPE(__plist, UINT)
+ /** 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, 0 if FALSE,
- * or -1 if the node is not of type PLIST_BOOL
+ * @return 1 if the boolean node has a value of TRUE or 0 if FALSE.
*/
- int plist_bool_val_is_true(plist_t boolnode);
+ PLIST_API int plist_bool_val_is_true(plist_t boolnode);
/**
- * Helper function to compare the value of a PLIST_UINT node against
- * a given value.
+ * Helper function to test if a given #PLIST_INT node's value is negative
*
- * @param uintnode node of type PLIST_UINT
+ * @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.
*/
- int plist_uint_val_compare(plist_t uintnode, uint64_t 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
@@ -793,7 +1189,7 @@ extern "C"
* 1 if the node's value is greater than cmpval,
* or -1 if the node's value is less than cmpval.
*/
- int plist_uid_val_compare(plist_t uidnode, uint64_t 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
@@ -810,20 +1206,19 @@ extern "C"
* 1 if the node's value is greater than cmpval,
* or -1 if the node's value is less than cmpval.
*/
- int plist_real_val_compare(plist_t realnode, double 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 set of seconds and fraction of a second since epoch.
+ * a given number of seconds since epoch (UNIX timestamp).
*
* @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
+ * @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.
*/
- int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec);
+ 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
@@ -836,7 +1231,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_string_val_compare(plist_t strnode, const char* 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
@@ -850,7 +1245,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n);
+ 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
@@ -861,7 +1256,7 @@ extern "C"
* @return 1 if the node's value contains the given substring,
* or 0 if not.
*/
- int plist_string_val_contains(plist_t strnode, const char* substr);
+ 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
@@ -874,7 +1269,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_key_val_compare(plist_t keynode, const char* 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
@@ -888,7 +1283,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n);
+ 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
@@ -899,7 +1294,7 @@ extern "C"
* @return 1 if the node's value contains the given substring,
* or 0 if not.
*/
- int plist_key_val_contains(plist_t keynode, const char* substr);
+ 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
@@ -915,7 +1310,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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
@@ -931,7 +1326,7 @@ extern "C"
* > 0 if the node's value is lexicographically greater than cmpval,
* or < 0 if the node's value is lexicographically less than cmpval.
*/
- int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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
@@ -943,7 +1338,107 @@ extern "C"
* @return 1 if the node's value contains the given data blob
* or 0 if not.
*/
- int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n);
+ 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);
/*@}*/
diff --git a/libcnary/Makefile.am b/libcnary/Makefile.am
index e2187ec..f5c7bc9 100644
--- a/libcnary/Makefile.am
+++ b/libcnary/Makefile.am
@@ -1,12 +1,15 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/libcnary/include
+AM_CFLAGS = \
+ $(GLOBAL_CFLAGS) \
+ -I$(top_srcdir)/libcnary/include
+
AM_LDFLAGS =
noinst_LTLIBRARIES = libcnary.la
-libcnary_la_LIBADD =
+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
+ node.c \
+ node_list.c \
+ include/node.h \
+ include/node_list.h \
+ include/object.h
diff --git a/libcnary/include/node.h b/libcnary/include/node.h
index 7e9da50..123241a 100644
--- a/libcnary/include/node.h
+++ b/libcnary/include/node.h
@@ -24,42 +24,42 @@
#ifndef NODE_H_
#define NODE_H_
+#include "node_list.h"
#include "object.h"
#define NODE_TYPE 1;
-struct node_list_t;
-
// 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 Members
void *data;
- struct node_t* parent;
- struct node_list_t* children;
-} 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_list.h b/libcnary/include/node_list.h
index 380916e..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();
+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 c24ca7a..8d3708b 100644
--- a/libcnary/node.c
+++ b/libcnary/node.c
@@ -27,11 +27,12 @@
#include "node.h"
#include "node_list.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);
@@ -43,14 +44,14 @@ 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->next = NULL;
@@ -74,7 +75,8 @@ node_t* node_create(node_t* parent, void* data) {
return node;
}
-int node_attach(node_t* parent, node_t* child) {
+int node_attach(node_t parent, node_t child)
+{
if (!parent || !child) return -1;
child->parent = parent;
if(!parent->children) {
@@ -87,7 +89,8 @@ int node_attach(node_t* parent, node_t* child) {
return res;
}
-int node_detach(node_t* parent, node_t* child) {
+int node_detach(node_t parent, node_t child)
+{
if (!parent || !child) return -1;
int node_index = node_list_remove(parent->children, child);
if (node_index >= 0) {
@@ -96,7 +99,7 @@ int node_detach(node_t* parent, node_t* child) {
return node_index;
}
-int node_insert(node_t* parent, unsigned int node_index, node_t* child)
+int node_insert(node_t parent, unsigned int node_index, node_t child)
{
if (!parent || !child) return -1;
child->parent = parent;
@@ -110,9 +113,10 @@ int node_insert(node_t* parent, unsigned int node_index, node_t* child)
return res;
}
-static void _node_debug(node_t* node, unsigned int depth) {
+static void _node_debug(node_t node, unsigned int depth)
+{
unsigned int i = 0;
- node_t* current = NULL;
+ node_t current = NULL;
for(i = 0; i < depth; i++) {
printf("\t");
}
@@ -133,23 +137,23 @@ static void _node_debug(node_t* node, unsigned int depth) {
}
-void node_debug(node_t* node)
+void node_debug(node_t node)
{
_node_debug(node, 0);
}
-unsigned int node_n_children(struct node_t* node)
+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;
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 (node_index++ == n) {
found = 1;
@@ -162,30 +166,30 @@ 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 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;
@@ -199,17 +203,17 @@ int node_child_position(struct node_t* parent, node_t* child)
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);
+ 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_t cc = node_copy_deep(ch, copy_func);
node_attach(copy, cc);
}
return copy;
diff --git a/libcnary/node_list.c b/libcnary/node_list.c
index b0dca0a..f6c2c70 100644
--- a/libcnary/node_list.c
+++ b/libcnary/node_list.c
@@ -28,16 +28,17 @@
#include "node.h"
#include "node_list.h"
-void node_list_destroy(node_list_t* list) {
+void node_list_destroy(node_list_t list)
+{
free(list);
}
-node_list_t* node_list_create() {
- 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->begin = NULL;
@@ -46,11 +47,12 @@ node_list_t* node_list_create() {
return list;
}
-int node_list_add(node_list_t* list, node_t* node) {
+int node_list_add(node_list_t list, node_t node)
+{
if (!list || !node) return -1;
// 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;
@@ -73,17 +75,18 @@ int node_list_add(node_list_t* list, node_t* node) {
return 0;
}
-int node_list_insert(node_list_t* list, unsigned int node_index, node_t* node) {
+int node_list_insert(node_list_t list, unsigned int node_index, node_t node)
+{
if (!list || !node) return -1;
if (node_index >= list->count) {
return node_list_add(list, node);
}
// Get the first element in the list
- node_t* cur = list->begin;
+ node_t cur = list->begin;
unsigned int pos = 0;
- node_t* prev = NULL;
+ node_t prev = NULL;
if (node_index > 0) {
while (pos < node_index) {
@@ -121,15 +124,16 @@ int node_list_insert(node_list_t* list, unsigned int node_index, node_t* node) {
return 0;
}
-int node_list_remove(node_list_t* list, node_t* node) {
+int node_list_remove(node_list_t list, node_t node)
+{
if (!list || !node) return -1;
if (list->count == 0) return -1;
int node_index = 0;
- node_t* n;
+ node_t n;
for (n = list->begin; n; n = n->next) {
if (node == n) {
- node_t* newnode = node->next;
+ node_t newnode = node->next;
if (node->prev) {
node->prev->next = newnode;
if (newnode) {
@@ -151,7 +155,6 @@ int node_list_remove(node_list_t* list, node_t* node) {
return node_index;
}
node_index++;
- }
+ }
return -1;
}
-
diff --git a/m4/ac_pkg_cython.m4 b/m4/ac_pkg_cython.m4
index 3b4c9a7..e0af96a 100644
--- a/m4/ac_pkg_cython.m4
+++ b/m4/ac_pkg_cython.m4
@@ -1,8 +1,8 @@
AC_DEFUN([AC_PROG_CYTHON],[
- AC_PATH_PROG([CYTHON],[cython])
+ AC_PATH_PROGS([CYTHON],[cython cython3])
if test -z "$CYTHON" ; then
- AC_MSG_WARN([cannot find 'cython' program. You should look at http://www.cython.org] or install your distribution specific cython package.)
+ 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])
@@ -59,7 +59,7 @@ AC_DEFUN([AC_PROG_CYTHON],[
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([cannot determine Cython version])
+ AC_MSG_WARN([Unable to determine Cython version])
CYTHON=false
fi
fi
diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4
index 0f660cf..baab5d9 100644
--- a/m4/as-compiler-flag.m4
+++ b/m4/as-compiler-flag.m4
@@ -18,7 +18,7 @@ AC_DEFUN([AS_COMPILER_FLAG],
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $1"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
@@ -44,7 +44,7 @@ AC_DEFUN([AS_COMPILER_FLAGS],
do
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $each"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
index 5fbf9fe..9f35d13 100644
--- a/m4/ax_pthread.m4
+++ b/m4/ax_pthread.m4
@@ -14,20 +14,24 @@
# 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 to any special C compiler that is needed for
-# multi-threaded programs (defaults to the value of CC otherwise). (This
-# is necessary on AIX to use the special cc_r compiler alias.)
+# 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
@@ -55,6 +59,7 @@
#
# 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
@@ -82,7 +87,7 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 24
+#serial 31
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
AC_DEFUN([AX_PTHREAD], [
@@ -104,6 +109,7 @@ if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
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])
@@ -123,10 +129,12 @@ fi
# (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 starting with a "-" are
-# C compiler flags, and other 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.
+# 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"
@@ -194,14 +202,47 @@ case $host_os in
# 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,pthread pthread $ax_pthread_flags"
+ 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 -pthreads $ax_pthread_flags"])
+ [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
@@ -224,25 +265,86 @@ AS_IF([test "x$ax_pthread_check_macro" = "x--"],
[ax_pthread_check_cond=0],
[ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
-# 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"
+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
-ax_pthread_clang_warning=no
# Clang needs special handling, because older versions handle the -pthread
# option in a rather... idiosyncratic way
@@ -261,11 +363,6 @@ if test "x$ax_pthread_clang" = "xyes"; then
# -pthread does define _REENTRANT, and while the Darwin headers
# ignore this macro, third-party headers might not.)
- PTHREAD_CFLAGS="-pthread"
- PTHREAD_LIBS=
-
- ax_pthread_ok=yes
-
# 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
@@ -294,7 +391,7 @@ if test "x$ax_pthread_clang" = "xyes"; then
# 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_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
@@ -320,78 +417,7 @@ if test "x$ax_pthread_clang" = "xyes"; then
fi # $ax_pthread_clang = yes
-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])
- ;;
-
- -mt,pthread)
- AC_MSG_CHECKING([whether pthreads work with -mt -lpthread])
- PTHREAD_CFLAGS="-mt"
- PTHREAD_LIBS="-lpthread"
- ;;
-
- -*)
- 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 routine(void *a) { a = 0; }
- 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
# Various other checks:
if test "x$ax_pthread_ok" = "xyes"; then
@@ -438,7 +464,8 @@ if test "x$ax_pthread_ok" = "xyes"; then
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;]])],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
[ax_cv_PTHREAD_PRIO_INHERIT=yes],
[ax_cv_PTHREAD_PRIO_INHERIT=no])
])
@@ -460,18 +487,28 @@ if test "x$ax_pthread_ok" = "xyes"; then
[#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"])],
- [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ [
+ 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
diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4
index 44dbd83..935056c 100644
--- a/m4/ax_python_devel.m4
+++ b/m4/ax_python_devel.m4
@@ -4,7 +4,7 @@
#
# SYNOPSIS
#
-# AX_PYTHON_DEVEL([version])
+# AX_PYTHON_DEVEL([version[,optional]])
#
# DESCRIPTION
#
@@ -23,6 +23,11 @@
# 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
@@ -67,10 +72,18 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 21
+#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
#
@@ -81,21 +94,26 @@ AC_DEFUN([AX_PYTHON_DEVEL],[
AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]])
if test -z "$PYTHON"; then
- AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path])
+ 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
- #
- # 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; \
+ 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 "$ac_supports_python_ver" != "True"; then
if test -z "$PYTHON_NOVERSIONCHECK"; then
AC_MSG_RESULT([no])
- AC_MSG_FAILURE([
+ 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
@@ -104,58 +122,119 @@ 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
+ else
AC_MSG_RESULT([yes])
+ fi
fi
- #
- # if the macro parameter ``version'' is set, honour it
- #
- if test -n "$1"; then
+ 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])
- ac_supports_python_ver=`$PYTHON -c "import sys; \
- ver = sys.version.split ()[[0]]; \
+ 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])
+ AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
- AC_MSG_ERROR([this package requires Python $1.
+ 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
- #
- # Check if you have distutils, else fail
- #
- AC_MSG_CHECKING([for the distutils Python package])
- ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`
- if test $? -eq 0; then
+ 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])
- else
+ IMPORT_SYSCONFIG="import sysconfig"
+ else
AC_MSG_RESULT([no])
- AC_MSG_ERROR([cannot import Python module "distutils".
+
+ 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_distutils_result])
- PYTHON_VERSION=""
+$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
- #
- # Check for Python include path
- #
- AC_MSG_CHECKING([for Python include path])
- if test -z "$PYTHON_CPPFLAGS"; then
- python_path=`$PYTHON -c "import distutils.sysconfig; \
- print (distutils.sysconfig.get_python_inc ());"`
- plat_python_path=`$PYTHON -c "import distutils.sysconfig; \
- print (distutils.sysconfig.get_python_inc (plat_specific=1));"`
+ 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"
@@ -164,22 +243,22 @@ $ac_distutils_result])
fi
fi
PYTHON_CPPFLAGS=$python_path
- fi
- AC_MSG_RESULT([$PYTHON_CPPFLAGS])
- AC_SUBST([PYTHON_CPPFLAGS])
+ 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
+ #
+ # 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 distutils.sysconfig import *
+from sysconfig import *
e = get_config_var('VERSION')
if e is not None:
print(e)
@@ -190,7 +269,7 @@ EOD`
ac_python_version=$PYTHON_VERSION
else
ac_python_version=`$PYTHON -c "import sys; \
- print (sys.version[[:3]])"`
+ print ("%d.%d" % sys.version_info[[:2]])"`
fi
fi
@@ -202,8 +281,8 @@ EOD`
ac_python_libdir=`cat<<EOD | $PYTHON -
# There should be only one
-import distutils.sysconfig
-e = distutils.sysconfig.get_config_var('LIBDIR')
+$IMPORT_SYSCONFIG
+e = sysconfig.get_config_var('LIBDIR')
if e is not None:
print (e)
EOD`
@@ -211,8 +290,8 @@ EOD`
# Now, for the library:
ac_python_library=`cat<<EOD | $PYTHON -
-import distutils.sysconfig
-c = distutils.sysconfig.get_config_vars()
+$IMPORT_SYSCONFIG
+c = sysconfig.get_config_vars()
if 'LDVERSION' in c:
print ('python'+c[['LDVERSION']])
else:
@@ -231,83 +310,140 @@ EOD`
else
# old way: use libpython from python_configdir
ac_python_libdir=`$PYTHON -c \
- "from distutils.sysconfig import get_python_lib as f; \
+ "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_ERROR([
+ 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
- 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
- PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \
- print (distutils.sysconfig.get_python_lib(0,0));"`
- fi
- AC_MSG_RESULT([$PYTHON_SITE_PKG])
- AC_SUBST([PYTHON_SITE_PKG])
+ if test $ax_python_devel_found = yes; then
+ AC_MSG_RESULT([$PYTHON_LIBS])
+ AC_SUBST([PYTHON_LIBS])
- #
- # 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 distutils.sysconfig; \
- conf = distutils.sysconfig.get_config_var; \
+ #
+ # 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)
+ 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 distutils.sysconfig; \
- conf = distutils.sysconfig.get_config_var; \
+ #
+ # 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'))"`
- fi
- AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])
- AC_SUBST(PYTHON_EXTRA_LDFLAGS)
+ # 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 $PYTHON_EXTRA_LIBS"
- LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS"
- CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
- AC_LANG_PUSH([C])
- AC_LINK_IFELSE([
+ #
+ # 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_LANG_POP([C])
+ # turn back to default flags
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIBS="$ac_save_LIBS"
+ LDFLAGS="$ac_save_LDFLAGS"
- AC_MSG_RESULT([$pythonexists])
+ AC_MSG_RESULT([$pythonexists])
- if test ! "x$pythonexists" = "xyes"; then
- AC_MSG_FAILURE([
+ 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.
@@ -317,8 +453,13 @@ EOD`
You probably have to install the development version of the Python package
for your distribution. The exact name of this package varies among them.
============================================================================
- ])
- PYTHON_VERSION=""
+ ])
+ if ! $ax_python_devel_optional; then
+ AC_MSG_ERROR([Giving up])
+ fi
+ ax_python_devel_found=no
+ PYTHON_VERSION=""
+ fi
fi
#
diff --git a/m4/cython_python.m4 b/m4/cython_python.m4
index ba730d5..5b4a041 100644
--- a/m4/cython_python.m4
+++ b/m4/cython_python.m4
@@ -1,6 +1,6 @@
AC_DEFUN([CYTHON_PYTHON],[
AC_REQUIRE([AC_PROG_CYTHON])
- AC_REQUIRE([AC_PYTHON_DEVEL])
+ 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/src/Array.cpp b/src/Array.cpp
index d5d9d7c..784df7c 100644
--- a/src/Array.cpp
+++ b/src/Array.cpp
@@ -18,11 +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 <limits.h>
+#include <climits>
+#include "plist.h"
+#include <plist/Array.h>
namespace PList
{
@@ -32,7 +32,7 @@ Array::Array(Node* parent) : Structure(PLIST_ARRAY, parent)
_array.clear();
}
-static void array_fill(Array *_this, std::vector<Node*> array, plist_t node)
+static void array_fill(Array *_this, std::vector<Node*> &array, plist_t node)
{
plist_array_iter iter = NULL;
plist_array_new_iter(node, &iter);
@@ -40,7 +40,9 @@ static void array_fill(Array *_this, std::vector<Node*> array, plist_t node)
do {
subnode = NULL;
plist_array_next_item(node, iter, &subnode);
- array.push_back( Node::FromPlist(subnode, _this) );
+ if (subnode) {
+ array.push_back( Node::FromPlist(subnode, _this) );
+ }
} while (subnode);
free(iter);
}
@@ -51,18 +53,19 @@ Array::Array(plist_t node, Node* parent) : Structure(parent)
array_fill(this, _array, _node);
}
-Array::Array(const PList::Array& a) : Structure()
+Array::Array(const PList::Array& a) : Structure(a.GetParent())
{
_array.clear();
_node = plist_copy(a.GetPlist());
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();
@@ -73,8 +76,7 @@ Array& Array::operator=(PList::Array& a)
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();
@@ -90,7 +92,71 @@ Node* Array::operator[](unsigned int array_index)
return _array.at(array_index);
}
-void Array::Append(Node* node)
+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.begin();
+}
+
+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)
{
@@ -101,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)
{
@@ -114,6 +185,11 @@ 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)
@@ -126,7 +202,7 @@ void Array::Remove(Node* node)
std::vector<Node*>::iterator it = _array.begin();
it += pos;
_array.erase(it);
- delete node;
+ free(node);
}
}
@@ -139,10 +215,15 @@ void Array::Remove(unsigned int pos)
_array.erase(it);
}
-unsigned int Array::GetNodeIndex(Node* node) const
+unsigned int Array::GetNodeIndex(const Node* node) const
{
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 4608eaf..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
@@ -37,8 +38,10 @@ 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;
@@ -70,4 +73,4 @@ bool Boolean::GetValue() const
return b != 0 ;
}
-};
+} // namespace PList
diff --git a/src/Data.cpp b/src/Data.cpp
index 2e93007..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
@@ -38,8 +38,10 @@ Data::Data(const PList::Data& d) : Node(PLIST_DATA)
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,6 +52,11 @@ 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()
{
}
@@ -66,14 +73,10 @@ void Data::SetValue(const std::vector<char>& buff)
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 4b5e0a1..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
@@ -34,20 +34,22 @@ Date::Date(plist_t node, Node* parent) : Node(node, parent)
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()
@@ -59,18 +61,16 @@ 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() const
+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 59908c6..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
@@ -50,7 +51,7 @@ Dictionary::Dictionary(plist_t node, Node* parent) : Structure(parent)
dictionary_fill(this, _map, _node);
}
-Dictionary::Dictionary(const PList::Dictionary& d) : Structure()
+Dictionary::Dictionary(const PList::Dictionary& d) : Structure(d.GetParent())
{
for (Dictionary::iterator it = _map.begin(); it != _map.end(); it++)
{
@@ -62,8 +63,10 @@ Dictionary::Dictionary(const PList::Dictionary& d) : Structure()
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());
@@ -99,21 +102,45 @@ 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);
@@ -143,11 +170,6 @@ Dictionary::iterator Dictionary::Set(const std::string& key, const Node& node)
return Set(key, &node);
}
-Dictionary::iterator Dictionary::Insert(const std::string& key, Node* node)
-{
- return this->Set(key, node);
-}
-
void Dictionary::Remove(Node* node)
{
if (node)
@@ -158,7 +180,7 @@ void Dictionary::Remove(Node* node)
std::string skey = key;
free(key);
_map.erase(skey);
- delete node;
+ free(node);
}
}
@@ -179,4 +201,4 @@ std::string Dictionary::GetNodeKey(Node* node)
return "";
}
-};
+} // namespace PList
diff --git a/src/Integer.cpp b/src/Integer.cpp
index 04315d7..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,23 +33,31 @@ Integer::Integer(plist_t node, Node* parent) : Node(node, parent)
{
}
-Integer::Integer(const 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()
{
}
@@ -58,16 +67,38 @@ 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() const
+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
index ed0c0c6..459227a 100644
--- a/src/Key.cpp
+++ b/src/Key.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/Key.h>
namespace PList
@@ -32,13 +33,15 @@ Key::Key(plist_t node, Node* parent) : Node(node, parent)
{
}
-Key::Key(const PList::Key& k) : Node(PLIST_UINT)
+Key::Key(const PList::Key& k) : Node(PLIST_INT)
{
plist_set_key_val(_node, k.GetValue().c_str());
}
-Key& Key::operator=(PList::Key& k)
+Key& Key::operator=(const PList::Key& k)
{
+ if (this == &k) return *this;
+
plist_free(_node);
_node = plist_copy(k.GetPlist());
return *this;
@@ -67,14 +70,9 @@ std::string Key::GetValue() const
{
char* s = NULL;
plist_get_key_val(_node, &s);
- std::string ret;
- if (s) {
- ret = s;
- free(s);
- } else {
- ret = "";
- }
+ std::string ret = s ? s : "";
+ free(s);
return ret;
}
-};
+} // namespace PList
diff --git a/src/Makefile.am b/src/Makefile.am
index 995bddd..1a416ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,52 +1,70 @@
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir) -I$(top_srcdir)/libcnary/include
+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.la libplist++.la
-libplist_la_LIBADD = $(top_builddir)/libcnary/libcnary.la
-libplist_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
-libplist_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 \
- xplist.c \
- bplist.c \
- plist.c plist.h
+lib_LTLIBRARIES = \
+ libplist-2.0.la \
+ libplist++-2.0.la
-libplist___la_LIBADD = libplist.la
-libplist___la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBPLIST_SO_VERSION) -no-undefined
-libplist___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
+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 \
+ 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_la_LDFLAGS += -avoid-version -static-libgcc
-libplist___la_LDFLAGS += -avoid-version -static-libgcc
+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.pc libplist++.pc
+pkgconfig_DATA = \
+ libplist-2.0.pc \
+ libplist++-2.0.pc
diff --git a/src/Node.cpp b/src/Node.cpp
index 3da401e..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>
@@ -52,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:
@@ -72,7 +73,7 @@ Node::Node(plist_type type, Node* parent) : _parent(parent)
_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();
@@ -134,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:
@@ -163,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 ec300c9..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(const 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;
@@ -70,4 +72,4 @@ double Real::GetValue() const
return d;
}
-};
+} // namespace PList
diff --git a/src/String.cpp b/src/String.cpp
index 0965349..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,23 +33,44 @@ String::String(plist_t node, Node* parent) : Node(node, parent)
{
}
-String::String(const 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()
{
}
@@ -65,16 +87,9 @@ void String::SetValue(const std::string& s)
std::string String::GetValue() const
{
- char* s = NULL;
- plist_get_string_val(_node, &s);
- std::string ret;
- if (s) {
- ret = s;
- free(s);
- } else {
- ret = "";
- }
+ 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 70150c2..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
@@ -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
index 440dec4..d73f777 100644
--- a/src/Uid.cpp
+++ b/src/Uid.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/Uid.h>
namespace PList
@@ -37,8 +38,10 @@ Uid::Uid(const PList::Uid& i) : Node(PLIST_UID)
plist_set_uid_val(_node, i.GetValue());
}
-Uid& Uid::operator=(PList::Uid& i)
+Uid& Uid::operator=(const PList::Uid& i)
{
+ if (this == &i) return *this;
+
plist_free(_node);
_node = plist_copy(i.GetPlist());
return *this;
@@ -70,4 +73,4 @@ uint64_t Uid::GetValue() const
return i;
}
-};
+} // namespace PList
diff --git a/src/base64.c b/src/base64.c
index ee02356..76990b9 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -78,7 +78,7 @@ unsigned char *base64decode(const char *buf, size_t *size)
if (len <= 0) return NULL;
unsigned char *outbuf = (unsigned char*)malloc((len/4)*3+3);
const char *ptr = buf;
- int p = 0;
+ size_t p = 0;
int wv, w1, w2, w3, w4;
int tmpval[4];
int tmpcnt = 0;
diff --git a/src/bplist.c b/src/bplist.c
index 14db755..b2d0e7c 100644
--- a/src/bplist.c
+++ b/src/bplist.c
@@ -32,11 +32,11 @@
#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>
@@ -47,7 +47,8 @@
#define BPLIST_VERSION ((uint8_t*)"00")
#define BPLIST_VERSION_SIZE 2
-typedef struct __attribute__((packed)) {
+#pragma pack(push,1)
+typedef struct {
uint8_t unused[6];
uint8_t offset_size;
uint8_t ref_size;
@@ -55,6 +56,7 @@ typedef struct __attribute__((packed)) {
uint64_t root_object_index;
uint64_t offset_table_offset;
} bplist_trailer_t;
+#pragma pack(pop)
enum
{
@@ -62,7 +64,7 @@ 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,
@@ -85,6 +87,28 @@ union plist_uint_ptr
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)) { \
@@ -92,6 +116,7 @@ union plist_uint_ptr
} *__p = (void *) (ptr); \
__p->__v; \
})
+#endif
#ifndef bswap16
@@ -143,38 +168,50 @@ union plist_uint_ptr
#ifdef __BIG_ENDIAN__
#define beNtoh(x,n) (x >> ((8-n) << 3))
#else
-#define beNtoh(x,n) be64toh(x << ((8-n) << 3))
+#define beNtoh(x,n) be64toh((x) << ((8-(n)) << 3))
#endif
+#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 = (n > 8) ? x + (n - 8) : 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 : \
+ __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 ? sizeof(float) : sizeof(double))
+#define get_real_bytes(x) ((x) == (float) (x) ? sizeof(float) : sizeof(double))
-#if (defined(__LITTLE_ENDIAN__) \
- && !defined(__FLOAT_WORD_ORDER__)) \
- || (defined(__FLOAT_WORD_ORDER__) \
- && __FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__)
-#define float_bswap64(x) bswap64(x)
-#define float_bswap32(x) bswap32(x)
-#else
+#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
#ifndef __has_builtin
@@ -182,7 +219,7 @@ union plist_uint_ptr
#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)
+#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)
{
@@ -191,7 +228,7 @@ static int uint64_mul_overflow(uint64_t a, uint64_t b, uint64_t *res)
}
#endif
-#define NODE_IS_ROOT(x) (((node_t*)x)->isRoot)
+#define NODE_IS_ROOT(x) (((node_t)(x))->isRoot)
struct bplist_data {
const char* data;
@@ -227,9 +264,16 @@ 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_uint_node(const char **bnode, uint8_t size)
+static plist_t parse_int_node(const char **bnode, uint8_t size)
{
plist_data_t data = plist_new_plist_data();
@@ -254,7 +298,7 @@ static plist_t parse_uint_node(const char **bnode, uint8_t size)
data->intval = UINT_TO_HOST(*bnode, size);
(*bnode) += size;
- data->type = PLIST_UINT;
+ data->type = PLIST_INT;
return node_create(NULL, data);
}
@@ -262,19 +306,30 @@ static plist_t parse_uint_node(const 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();
- uint8_t buf[8];
size = 1 << size; // make length less misleading
switch (size)
{
case sizeof(uint32_t):
- *(uint32_t*)buf = float_bswap32(get_unaligned((uint32_t*)*bnode));
- data->realval = *(float *) buf;
- break;
+ {
+ 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*)buf = float_bswap64(get_unaligned((uint64_t*)*bnode));
- data->realval = *(double *) buf;
+ {
+ 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__);
@@ -314,17 +369,19 @@ static plist_t parse_string_node(const char **bnode, uint64_t size)
return node_create(NULL, data);
}
-static char *plist_utf16be_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;
- int p = 0;
- long 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)));
@@ -332,14 +389,14 @@ static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read,
}
while (i < len) {
- wc = be16toh(get_unaligned(unistr + i));
+ 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.
+ // This is invalid, the next 16 bit char should be a trail surrogate.
// Handling error by skipping.
read_lead_surrogate = 0;
}
@@ -374,30 +431,29 @@ static char *plist_utf16be_to_utf8(uint16_t *unistr, long len, long *items_read,
}
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(const char **bnode, uint64_t size)
{
plist_data_t data = plist_new_plist_data();
- char *tmpstr = NULL;
- long items_read = 0;
- long items_written = 0;
+ size_t items_read = 0;
+ size_t items_written = 0;
data->type = PLIST_STRING;
-
- tmpstr = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
- if (!tmpstr) {
+ data->strval = plist_utf16be_to_utf8((uint16_t*)(*bnode), size, &items_read, &items_written);
+ if (!data->strval) {
plist_free_data(data);
return NULL;
}
- tmpstr[items_written] = '\0';
-
- data->type = PLIST_STRING;
- data->strval = realloc(tmpstr, items_written+1);
- if (!data->strval)
- data->strval = tmpstr;
data->length = items_written;
+
return node_create(NULL, data);
}
@@ -490,8 +546,8 @@ static plist_t parse_dict_node(struct bplist_data *bplist, const char** bnode, u
return NULL;
}
- node_attach(node, key);
- node_attach(node, val);
+ node_attach((node_t)node, (node_t)key);
+ node_attach((node_t)node, (node_t)val);
}
return node;
@@ -535,7 +591,7 @@ static plist_t parse_array_node(struct bplist_data *bplist, const char** bnode,
return NULL;
}
- node_attach(node, val);
+ node_attach((node_t)node, (node_t)val);
}
return node;
@@ -583,8 +639,8 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
case BPLIST_DICT:
{
uint16_t next_size = **object & BPLIST_FILL;
- if ((**object & BPLIST_MASK) != BPLIST_UINT) {
- 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_UINT);
+ 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)++;
@@ -630,16 +686,23 @@ static plist_t parse_bin_node(struct bplist_data *bplist, const char** object)
}
case BPLIST_NULL:
+ {
+ plist_data_t data = plist_new_plist_data();
+ data->type = PLIST_NULL;
+ data->length = 0;
+ return node_create(NULL, data);
+ }
+
default:
return NULL;
}
- case BPLIST_UINT:
+ case BPLIST_INT:
if (pobject + (uint64_t)(1 << size) > poffset_table) {
- PLIST_BIN_ERR("%s: BPLIST_UINT data bytes point outside of valid range\n", __func__);
+ PLIST_BIN_ERR("%s: BPLIST_INT data bytes point outside of valid range\n", __func__);
return NULL;
}
- return parse_uint_node(object, size);
+ return parse_int_node(object, size);
case BPLIST_REAL:
if (pobject + (uint64_t)(1 << size) > poffset_table) {
@@ -734,7 +797,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
ptr = bplist->data + UINT_TO_HOST(idx_ptr, bplist->offset_size);
/* make sure the node offset is in a sane range */
- if ((ptr < bplist->data) || (ptr >= bplist->offset_table)) {
+ 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);
return NULL;
}
@@ -751,8 +814,8 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
/* recursion check */
if (bplist->level > 0) {
for (i = bplist->level-1; i >= 0; i--) {
- uint32_t node_i = (uint32_t)(uintptr_t)ptr_array_index(bplist->used_indexes, i);
- uint32_t node_level = (uint32_t)(uintptr_t)ptr_array_index(bplist->used_indexes, bplist->level);
+ 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");
return NULL;
@@ -767,7 +830,7 @@ static plist_t parse_bin_node_at_index(struct bplist_data *bplist, uint32_t node
return plist;
}
-PLIST_API 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)
{
bplist_trailer_t *trailer = NULL;
uint8_t offset_size = 0;
@@ -779,20 +842,28 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
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 + sizeof(bplist_trailer_t))) {
PLIST_BIN_ERR("plist data is to small to hold a binary plist\n");
- return;
+ return PLIST_ERR_PARSE;
}
//check that plist_bin in actually a plist
if (memcmp(plist_bin, BPLIST_MAGIC, BPLIST_MAGIC_SIZE) != 0) {
PLIST_BIN_ERR("bplist magic mismatch\n");
- return;
+ return PLIST_ERR_PARSE;
}
//check for known version
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;
+ return PLIST_ERR_PARSE;
}
start_data = plist_bin + BPLIST_MAGIC_SIZE + BPLIST_VERSION_SIZE;
@@ -805,41 +876,48 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
ref_size = trailer->ref_size;
num_objects = be64toh(trailer->num_objects);
root_object = be64toh(trailer->root_object_index);
- offset_table = (char *)(plist_bin + be64toh(trailer->offset_table_offset));
+
+ 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);
if (num_objects == 0) {
PLIST_BIN_ERR("number of objects must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (offset_size == 0) {
PLIST_BIN_ERR("offset size in trailer must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (ref_size == 0) {
PLIST_BIN_ERR("object reference size in trailer must be larger than 0\n");
- return;
+ return PLIST_ERR_PARSE;
}
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;
+ return PLIST_ERR_PARSE;
}
if (offset_table < start_data || offset_table >= end_data) {
PLIST_BIN_ERR("offset table offset points outside of valid range\n");
- return;
+ return PLIST_ERR_PARSE;
}
if (uint64_mul_overflow(num_objects, offset_size, &offset_table_size)) {
PLIST_BIN_ERR("integer overflow when calculating offset table size\n");
- return;
+ return PLIST_ERR_PARSE;
}
- if ((offset_table + offset_table_size < offset_table) || (offset_table + offset_table_size > end_data)) {
+ if (offset_table_size > (uint64_t)(end_data - offset_table)) {
PLIST_BIN_ERR("offset table points outside of valid range\n");
- return;
+ return PLIST_ERR_PARSE;
}
struct bplist_data bplist;
@@ -854,12 +932,18 @@ PLIST_API void plist_from_bin(const char *plist_bin, uint32_t length, plist_t *
if (!bplist.used_indexes) {
PLIST_BIN_ERR("failed to create array to hold used node indexes. Out of memory?\n");
- return;
+ return PLIST_ERR_NO_MEM;
}
*plist = parse_bin_node_at_index(&bplist, root_object);
ptr_array_free(bplist.used_indexes);
+
+ if (!*plist) {
+ return PLIST_ERR_PARSE;
+ }
+
+ return PLIST_ERR_SUCCESS;
}
static unsigned int plist_data_hash(const void* key)
@@ -875,7 +959,8 @@ 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:
@@ -914,7 +999,7 @@ struct serialize_s
hashtable_t* ref_table;
};
-static void serialize_plist(node_t* node, void* data)
+static void serialize_plist(node_t node, void* data)
{
uint64_t *index_val = NULL;
struct serialize_s *ser = (struct serialize_s *) data;
@@ -937,15 +1022,13 @@ static void serialize_plist(node_t* node, void* data)
ptr_array_add(ser->objects, node);
//now recurse on children
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
serialize_plist(ch, data);
}
-
- return;
}
-#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)
{
@@ -954,7 +1037,7 @@ static void write_int(bytearray_t * bplist, uint64_t val)
//do not write 3bytes int node
if (size == 3)
size++;
- sz = BPLIST_UINT | Log2(size);
+ sz = BPLIST_INT | Log2(size);
val = be64toh(val);
byte_array_append(bplist, &sz, 1);
@@ -963,7 +1046,7 @@ static void write_int(bytearray_t * bplist, uint64_t val)
static void write_uint(bytearray_t * bplist, uint64_t val)
{
- uint8_t sz = BPLIST_UINT | 4;
+ uint8_t sz = BPLIST_INT | 4;
uint64_t zero = 0;
val = be64toh(val);
@@ -979,18 +1062,24 @@ static void write_real(bytearray_t * bplist, double val)
buff[7] = BPLIST_REAL | Log2(size);
if (size == sizeof(float)) {
float floatval = (float)val;
- *(uint32_t*)(buff+8) = float_bswap32(*(uint32_t*)&floatval);
+ uint32_t intval;
+ memcpy(&intval, &floatval, sizeof(float));
+ *(uint32_t*)(buff+8) = float_bswap32(intval);
} else {
- *(uint64_t*)(buff+8) = float_bswap64(*(uint64_t*)&val);
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
}
byte_array_append(bplist, buff+7, size+1);
}
static void write_date(bytearray_t * bplist, double val)
{
+ uint64_t intval;
+ memcpy(&intval, &val, sizeof(double));
uint8_t buff[16];
buff[7] = BPLIST_DATE | 3;
- *(uint64_t*)(buff+8) = float_bswap64(*(uint64_t*)&val);
+ *(uint64_t*)(buff+8) = float_bswap64(intval);
byte_array_append(bplist, buff+7, 9);
}
@@ -1015,11 +1104,11 @@ static void write_string(bytearray_t * bplist, char *val, uint64_t size)
write_raw_data(bplist, BPLIST_STRING, (uint8_t *) val, size);
}
-static uint16_t *plist_utf8_to_utf16be(char *unistr, long size, long *items_read, long *items_written)
+static uint16_t *plist_utf8_to_utf16be(char *unistr, size_t size, size_t *items_read, size_t *items_written)
{
uint16_t *outbuf;
- int p = 0;
- long i = 0;
+ size_t p = 0;
+ size_t i = 0;
unsigned char c0;
unsigned char c1;
@@ -1074,10 +1163,10 @@ static uint16_t *plist_utf8_to_utf16be(char *unistr, long size, long *items_read
return outbuf;
}
-static void write_unicode(bytearray_t * bplist, char *val, uint64_t size)
+static void write_unicode(bytearray_t * bplist, char *val, size_t size)
{
- long items_read = 0;
- long items_written = 0;
+ size_t items_read = 0;
+ size_t items_written = 0;
uint16_t *unicodestr = NULL;
unicodestr = plist_utf8_to_utf16be(val, size, &items_read, &items_written);
@@ -1085,9 +1174,9 @@ static void write_unicode(bytearray_t * bplist, char *val, uint64_t size)
free(unicodestr);
}
-static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t ref_size)
+static void write_array(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node);
@@ -1104,9 +1193,9 @@ static void write_array(bytearray_t * bplist, node_t* node, hashtable_t* ref_tab
}
}
-static void write_dict(bytearray_t * bplist, node_t* node, hashtable_t* ref_table, uint8_t ref_size)
+static void write_dict(bytearray_t * bplist, node_t node, hashtable_t* ref_table, uint8_t ref_size)
{
- node_t* cur = NULL;
+ node_t cur = NULL;
uint64_t i = 0;
uint64_t size = node_n_children(node) / 2;
@@ -1158,7 +1247,7 @@ static int is_ascii_string(char* s, int len)
return ret;
}
-PLIST_API 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;
@@ -1176,18 +1265,26 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
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(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, free);
+ if (!ref_table) {
+ ptr_array_free(objects);
+ return PLIST_ERR_NO_MEM;
+ }
//serialize plist
ser_s.objects = objects;
ser_s.ref_table = ref_table;
- serialize_plist(plist, &ser_s);
+ serialize_plist((node_t)plist, &ser_s);
//now stream to output buffer
offset_size = 0; //unknown yet
@@ -1201,12 +1298,13 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
uint64_t req = 0;
for (i = 0; i < num_objects; i++)
{
- node_t* node = ptr_array_index(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;
@@ -1281,6 +1379,11 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
//setup a dynamic bytes array to store bplist in
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);
@@ -1297,12 +1400,17 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
switch (data->type)
{
+ case PLIST_NULL: {
+ uint8_t b = 0;
+ byte_array_append(bplist_buff, &b, 1);
+ break;
+ }
case PLIST_BOOLEAN: {
uint8_t b = data->boolval ? BPLIST_TRUE : BPLIST_FALSE;
byte_array_append(bplist_buff, &b, 1);
break;
}
- case PLIST_UINT:
+ case PLIST_INT:
if (data->length == 16) {
write_uint(bplist_buff, data->intval);
} else {
@@ -1329,10 +1437,10 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
write_data(bplist_buff, data->buff, data->length);
break;
case PLIST_ARRAY:
- write_array(bplist_buff, ptr_array_index(objects, i), ref_table, ref_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, ref_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->realval);
@@ -1370,14 +1478,11 @@ PLIST_API void plist_to_bin(plist_t plist, char **plist_bin, uint32_t * length)
byte_array_append(bplist_buff, &trailer, sizeof(bplist_trailer_t));
//set output buffer and size
- *plist_bin = bplist_buff->data;
+ *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);
-}
-PLIST_API void plist_to_bin_free(char *plist_bin)
-{
- free(plist_bin);
+ return PLIST_ERR_SUCCESS;
}
diff --git a/src/bytearray.c b/src/bytearray.c
index 7d0549b..39fad5f 100644
--- a/src/bytearray.c
+++ b/src/bytearray.c
@@ -29,6 +29,17 @@ bytearray_t *byte_array_new(size_t initial)
a->capacity = (initial > PAGE_SIZE) ? (initial+(PAGE_SIZE-1)) & (~(PAGE_SIZE-1)) : PAGE_SIZE;
a->data = malloc(a->capacity);
a->len = 0;
+ 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;
}
@@ -43,6 +54,9 @@ void byte_array_free(bytearray_t *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;
@@ -50,12 +64,20 @@ void byte_array_grow(bytearray_t *ba, size_t amount)
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) {
- size_t needed = len - remaining;
- byte_array_grow(ba, needed);
+ 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(((char*)ba->data) + ba->len, buf, len);
ba->len += len;
}
diff --git a/src/bytearray.h b/src/bytearray.h
index 312e2aa..b53e006 100644
--- a/src/bytearray.h
+++ b/src/bytearray.h
@@ -21,14 +21,17 @@
#ifndef BYTEARRAY_H
#define BYTEARRAY_H
#include <stdlib.h>
+#include <stdio.h>
typedef struct bytearray_t {
void *data;
size_t len;
size_t capacity;
+ FILE *stream;
} bytearray_t;
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);
diff --git a/src/hashtable.c b/src/hashtable.c
index dd6dbfc..86dae82 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -47,7 +47,7 @@ void hash_table_destroy(hashtable_t *ht)
ht->free_func(e->value);
}
hashentry_t* old = e;
- e = e->next;
+ e = (hashentry_t*)e->next;
free(old);
}
}
@@ -71,7 +71,7 @@ void hash_table_insert(hashtable_t* ht, void *key, void *value)
e->value = value;
return;
}
- e = e->next;
+ e = (hashentry_t*)e->next;
}
// if we get here, the element is not yet in the list.
@@ -103,7 +103,7 @@ void* hash_table_lookup(hashtable_t* ht, void *key)
if (ht->compare_func(e->key, key)) {
return e->value;
}
- e = e->next;
+ e = (hashentry_t*)e->next;
}
return NULL;
}
@@ -124,7 +124,7 @@ void hash_table_remove(hashtable_t* ht, void *key)
// found element, remove it from the list
hashentry_t* old = e;
if (e == ht->entries[idx0]) {
- ht->entries[idx0] = e->next;
+ ht->entries[idx0] = (hashentry_t*)e->next;
} else {
last->next = e->next;
}
@@ -135,6 +135,6 @@ void hash_table_remove(hashtable_t* ht, void *key)
return;
}
last = e;
- e = e->next;
+ e = (hashentry_t*)e->next;
}
}
diff --git a/src/jplist.c b/src/jplist.c
new file mode 100644
index 0000000..1c7a932
--- /dev/null
+++ b/src/jplist.c
@@ -0,0 +1,862 @@
+/*
+ * 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"
+
+#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 size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+static plist_err_t node_to_json(node_t node, bytearray_t **outbuf, uint32_t depth, int prettify)
+{
+ 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);
+ 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);
+ 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:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ 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:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATA type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_DATE type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR JSON
+ PLIST_JSON_WRITE_ERR("PLIST_UID type is not valid for JSON format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_JSON_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_to_json(plist_t plist, char **plist_json, uint32_t* length, int prettify)
+{
+ 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;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ 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);
+ 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;
+} jsmntok_info_t;
+
+static int64_t parse_decimal(const char* str, const char* str_end, char** endp)
+{
+ 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();
+ 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);
+ }
+ (*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);
+ 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();
+ data->type = PLIST_STRING;
+ data->strval = strval;
+ data->length = str_len;
+ node = plist_new_node(data);
+
+ (*index)++;
+ return node;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index);
+
+static plist_t parse_array(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_ARRAY) {
+ PLIST_JSON_ERR("%s: token type != JSMN_ARRAY\n", __func__);
+ return NULL;
+ }
+ plist_t arr = plist_new_array();
+ int num_tokens = ti->tokens[*index].size;
+ int 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);
+ return NULL;
+ }
+ plist_t val = NULL;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ 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);
+ } else {
+ plist_free(arr);
+ return NULL;
+ }
+ }
+ *(index) = j;
+ return arr;
+}
+
+static plist_t parse_object(const char* js, jsmntok_info_t* ti, int* index)
+{
+ if (ti->tokens[*index].type != JSMN_OBJECT) {
+ PLIST_JSON_ERR("%s: token type != JSMN_OBJECT\n", __func__);
+ return NULL;
+ }
+ int num_tokens = ti->tokens[*index].size;
+ int num;
+ int j = (*index)+1;
+ if (num_tokens % 2 != 0) {
+ PLIST_JSON_ERR("%s: number of children must be even\n", __func__);
+ return NULL;
+ }
+ plist_t obj = plist_new_dict();
+ 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);
+ 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);
+ return NULL;
+ }
+ plist_t val = NULL;
+ j++;
+ num++;
+ switch (ti->tokens[j].type) {
+ case JSMN_OBJECT:
+ val = parse_object(js, ti, &j);
+ break;
+ case JSMN_ARRAY:
+ val = parse_array(js, ti, &j);
+ 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);
+ } else {
+ free(key);
+ plist_free(obj);
+ return NULL;
+ }
+ free(key);
+ } else {
+ PLIST_JSON_ERR("%s: keys must be of type STRING\n", __func__);
+ plist_free(obj);
+ 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);
+ int maxtoks = 256;
+ int curtoks = 0;
+ int r = 0;
+ jsmntok_t *tokens = NULL;
+
+ do {
+ jsmntok_t* newtokens = (jsmntok_t*)realloc(tokens, sizeof(jsmntok_t)*maxtoks);
+ if (!newtokens) {
+ 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) {
+ maxtoks+=16;
+ continue;
+ }
+ } 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;
+ default:
+ break;
+ }
+
+ int startindex = 0;
+ jsmntok_info_t ti = { tokens, parser.toknext };
+ 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);
+ break;
+ case JSMN_OBJECT:
+ *plist = parse_object(json, &ti, &startindex);
+ break;
+ default:
+ break;
+ }
+ free(tokens);
+ return PLIST_ERR_SUCCESS;
+}
diff --git a/src/jsmn.c b/src/jsmn.c
new file mode 100644
index 0000000..f190312
--- /dev/null
+++ b/src/jsmn.c
@@ -0,0 +1,289 @@
+/*
+ * jsmn.c
+ * Simple JSON parser
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ *
+ * 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 "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, int num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ 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,
+ int start, int 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, int num_tokens) {
+ jsmntok_t *token;
+ int 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, int num_tokens) {
+ jsmntok_t *token;
+
+ int 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, unsigned int length, jsmntok_t *tokens,
+ unsigned int num_tokens) {
+ jsmnerr_t r;
+ int i;
+ jsmntok_t *token;
+
+ parser->end = length;
+
+ 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 != -1 && token->end == -1) {
+ if (token->type != type) {
+ 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 != -1 && token->end == -1) {
+ 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 != -1 && token->end == -1) {
+ 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 != -1 && tokens[i].end == -1) {
+ 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..380744d
--- /dev/null
+++ b/src/jsmn.h
@@ -0,0 +1,92 @@
+/*
+ * jsmn.h
+ * Simple JSON parser (header file)
+ *
+ * Copyright (c) 2010 Serge A. Zaitsev
+ *
+ * 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_
+
+/**
+ * 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,
+ /* 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;
+ int start;
+ int end;
+ int 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 {
+ unsigned int pos; /* offset in the JSON string */
+ unsigned int 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, unsigned int length,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#endif /* __JSMN_H_ */
diff --git a/src/libplist++.pc.in b/src/libplist++-2.0.pc.in
index 0fce9d1..79dc315 100644
--- a/src/libplist++.pc.in
+++ b/src/libplist++-2.0.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: @PACKAGE_NAME@++
Description: C++ binding for @PACKAGE_NAME@
Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -lplist++
+Libs: -L${libdir} -lplist++-2.0
Cflags: -I${includedir}
-Requires.private: libplist >= @PACKAGE_VERSION@
+Requires.private: libplist-2.0 >= @PACKAGE_VERSION@
diff --git a/src/libplist.pc.in b/src/libplist-2.0.pc.in
index 0bd6932..43d9f57 100644
--- a/src/libplist.pc.in
+++ b/src/libplist-2.0.pc.in
@@ -6,5 +6,5 @@ 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
+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..277693f
--- /dev/null
+++ b/src/oplist.c
@@ -0,0 +1,937 @@
+/*
+ * 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"
+
+#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 size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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)
+{
+ 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);
+ 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);
+ 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:
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_NULL:
+ PLIST_OSTEP_WRITE_ERR("PLIST_NULL type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, int prettify)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ 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:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_BOOLEAN type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_DATE:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_DATE type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ case PLIST_UID:
+ // NOT VALID FOR OPENSTEP
+ PLIST_OSTEP_WRITE_ERR("PLIST_UID type is not valid for OpenStep format\n");
+ return PLIST_ERR_FORMAT;
+ default:
+ PLIST_OSTEP_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
+ }
+ }
+ return PLIST_ERR_SUCCESS;
+}
+
+plist_err_t plist_to_openstep(plist_t plist, char **openstep, uint32_t* length, int prettify)
+{
+ uint64_t size = 0;
+ plist_err_t res;
+
+ if (!plist || !openstep || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ res = node_estimate_size((node_t)plist, &size, 0, prettify);
+ 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);
+ 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;
+ int 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 != 0) {
+ 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++;
+ 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++;
+ break;
+ }
+ if (*ctx->pos != '=') {
+ PLIST_OSTEP_ERR("Missing '=' while parsing dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ break;
+ }
+ val = NULL;
+ ctx->err = node_from_openstep(ctx, &val);
+ if (ctx->err != 0) {
+ break;
+ }
+ if (!val) {
+ PLIST_OSTEP_ERR("Missing value for dictionary item at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ 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++;
+ 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 > 1000) {
+ PLIST_OSTEP_ERR("Too many levels of recursion (%u) at offset %ld\n", ctx->depth, (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ return PLIST_ERR_PARSE;
+ }
+ 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++;
+ break;
+ }
+ if (*ctx->pos != '}') {
+ PLIST_OSTEP_ERR("Missing terminating '}' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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 != 0) {
+ break;
+ }
+ if (!tmp) {
+ ctx->err++;
+ 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++;
+ break;
+ }
+ if (*ctx->pos != ',') {
+ break;
+ }
+ ctx->pos++;
+ }
+ plist_free(tmp);
+ tmp = NULL;
+ if (ctx->err) {
+ 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++;
+ break;
+ }
+ if (*ctx->pos != ')') {
+ PLIST_OSTEP_ERR("Missing terminating ')' at offset %ld\n", (long int)(ctx->pos - ctx->start));
+ ctx->err++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ 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++;
+ break;
+ }
+ }
+ ctx->pos++;
+ }
+ ctx->depth--;
+
+err_out:
+ if (ctx->err) {
+ plist_free(subnode);
+ plist_free(*plist);
+ *plist = NULL;
+ return PLIST_ERR_PARSE;
+ }
+ 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..266070b
--- /dev/null
+++ b/src/out-default.c
@@ -0,0 +1,491 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ 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);
+ }
+ 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:
+ {
+ str_buf_append(*outbuf, "<", 1);
+ size_t len = node_data->length;
+ char charb[4];
+ if (!partial_data || len <= 24) {
+ for (i = 0; i < len; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ } else {
+ for (i = 0; i < 16; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (i = len - 8; i < len; i++) {
+ sprintf(charb, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, charb, 2);
+ if (i > 0 && i < len-1 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ str_buf_append(*outbuf, ">", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ 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 (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;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent, int partial_data)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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..7d861f8
--- /dev/null
+++ b/src/out-limd.c
@@ -0,0 +1,449 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ size_t val_len = 0;
+ char buf[16];
+
+ 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;
+
+ 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;
+ }
+ 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);
+ }
+ }
+ size_t sl = sprintf(buf, "%u: ", cnt);
+ str_buf_append(*outbuf, buf, sl);
+ 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)) {
+ size_t sl = sprintf(buf, "[%u]:", plist_array_get_size(valnode));
+ str_buf_append(*outbuf, buf, sl);
+ } else {
+ str_buf_append(*outbuf, ": ", 2);
+ }
+ }
+ cnt++;
+ }
+ } break;
+ case PLIST_DATA:
+ {
+ val = (char*)malloc(4096);
+ size_t done = 0;
+ while (done < node_data->length) {
+ size_t amount = node_data->length - done;
+ if (amount > 3072) {
+ amount = 3072;
+ }
+ size_t bsize = base64encode(val, node_data->buff + done, amount);
+ str_buf_append(*outbuf, val, bsize);
+ done += amount;
+ }
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 24);
+ 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);
+ val = NULL;
+ }
+ }
+ break;
+ case PLIST_UID:
+ {
+ str_buf_append(*outbuf, "CF$UID:", 7);
+ 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;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth, uint32_t indent)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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..d85f22c
--- /dev/null
+++ b/src/out-plutil.c
@@ -0,0 +1,465 @@
+/*
+ * 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"
+
+#define MAC_EPOCH 978307200
+
+static size_t dtostr(char *buf, size_t bufsize, double realval)
+{
+ size_t len = 0;
+ if (isnan(realval)) {
+ len = snprintf(buf, bufsize, "nan");
+ } else if (isinf(realval)) {
+ len = snprintf(buf, bufsize, "%cinfinity", (realval > 0.0) ? '+' : '-');
+ } else if (realval == 0.0f) {
+ len = snprintf(buf, bufsize, "0.0");
+ } else {
+ size_t i = 0;
+ len = snprintf(buf, bufsize, "%.*g", 17, realval);
+ for (i = 0; buf && i < len; i++) {
+ if (buf[i] == ',') {
+ buf[i] = '.';
+ break;
+ } else if (buf[i] == '.') {
+ break;
+ }
+ }
+ }
+ return len;
+}
+
+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;
+ 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, "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 (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)) {
+ str_buf_append(*outbuf, "\n", 1);
+ for (i = 0; i <= depth; i++) {
+ str_buf_append(*outbuf, " ", 2);
+ }
+ char indexbuf[16];
+ int l = sprintf(indexbuf, "%u => ", cnt);
+ str_buf_append(*outbuf, indexbuf, l);
+ 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;
+ 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:
+ {
+ val = (char*)calloc(1, 48);
+ size_t len = node_data->length;
+ size_t slen = snprintf(val, 48, "{length = %" PRIu64 ", bytes = 0x", (uint64_t)len);
+ str_buf_append(*outbuf, val, slen);
+ if (len <= 24) {
+ for (i = 0; i < len; i++) {
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ } else {
+ for (i = 0; i < 16; i++) {
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ }
+ str_buf_append(*outbuf, " ... ", 5);
+ for (i = len - 8; i < len; i++) {
+ sprintf(val, "%02x", (unsigned char)node_data->buff[i]);
+ str_buf_append(*outbuf, val, 2);
+ if (i > 0 && (i % 4 == 0))
+ str_buf_append(*outbuf, " ", 1);
+ }
+ }
+ free(val);
+ val = NULL;
+ str_buf_append(*outbuf, "}", 1);
+ }
+ break;
+ case PLIST_DATE:
+ {
+ Time64_T timev = (Time64_T)node_data->realval + MAC_EPOCH;
+ struct TM _btime;
+ struct TM *btime = gmtime64_r(&timev, &_btime);
+ if (btime) {
+ val = (char*)calloc(1, 26);
+ 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:
+ {
+ val = (char*)malloc(88);
+ val_len = sprintf(val, "<CFKeyedArchiverUID %p [%p]>{value = %" PRIu64 "}", node, node_data, node_data->intval);
+ str_buf_append(*outbuf, val, val_len);
+ free(val);
+ val = NULL;
+ }
+ break;
+ default:
+ return PLIST_ERR_UNKNOWN;
+ }
+
+ return PLIST_ERR_SUCCESS;
+}
+
+#define PO10i_LIMIT (INT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+#define PO10u_LIMIT (UINT64_MAX/10)
+
+/* based on https://stackoverflow.com/a/4143288 */
+static 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;
+}
+
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
+{
+ plist_data_t data;
+ if (!node) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ 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);
+ if (res < 0) {
+ 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 _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 27f90b1..fe98f34 100644
--- a/src/plist.c
+++ b/src/plist.c
@@ -2,7 +2,7 @@
* plist.c
* Builds plist XML structures
*
- * Copyright (c) 2009-2019 Nikias Bassen, 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.
*
@@ -34,88 +34,130 @@
#include <assert.h>
#include <limits.h>
#include <float.h>
+#include <ctype.h>
+#include <inttypes.h>
#ifdef WIN32
#include <windows.h>
-#else
-#include <pthread.h>
#endif
#include <node.h>
+#include <node_list.h>
#include <hashtable.h>
#include <ptrarray.h>
-extern void plist_xml_init(void);
-extern void plist_xml_deinit(void);
-extern void plist_bin_init(void);
-extern void plist_bin_deinit(void);
+#define MAC_EPOCH 978307200
-static void internal_plist_init(void)
-{
- plist_bin_init();
- plist_xml_init();
-}
+#ifdef _MSC_VER
+typedef SSIZE_T ssize_t;
+#endif
-static void internal_plist_deinit(void)
-{
- plist_bin_deinit();
- plist_xml_deinit();
-}
+#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
-#ifdef WIN32
+#ifndef bswap16
+#define bswap16(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#endif
-typedef volatile struct {
- LONG lock;
- int state;
-} thread_once_t;
+#ifndef bswap32
+#define bswap32(x) ((((x) & 0xFF000000) >> 24) \
+ | (((x) & 0x00FF0000) >> 8) \
+ | (((x) & 0x0000FF00) << 8) \
+ | (((x) & 0x000000FF) << 24))
+#endif
-static thread_once_t init_once = {0, 0};
-static thread_once_t deinit_once = {0, 0};
+#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
-void thread_once(thread_once_t *once_control, void (*init_routine)(void))
-{
- while (InterlockedExchange(&(once_control->lock), 1) != 0) {
- Sleep(1);
- }
- if (!once_control->state) {
- once_control->state = 1;
- init_routine();
- }
- InterlockedExchange(&(once_control->lock), 0);
-}
+#ifndef le16toh
+#ifdef __BIG_ENDIAN__
+#define le16toh(x) bswap16(x)
+#else
+#define le16toh(x) (x)
+#endif
+#endif
-BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved)
-{
- switch (dwReason) {
- case DLL_PROCESS_ATTACH:
- thread_once(&init_once, internal_plist_init);
- break;
- case DLL_PROCESS_DETACH:
- thread_once(&deinit_once, internal_plist_deinit);
- break;
- default:
- break;
- }
- return 1;
-}
+#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
-static pthread_once_t init_once = PTHREAD_ONCE_INIT;
-static pthread_once_t deinit_once = PTHREAD_ONCE_INIT;
+// 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
-static void __attribute__((constructor)) libplist_initialize(void)
+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)
{
- pthread_once(&init_once, internal_plist_init);
+ plist_bin_deinit();
+ plist_xml_deinit();
+ plist_json_deinit();
+ plist_ostep_deinit();
}
-static void __attribute__((destructor)) libplist_deinitialize(void)
+INITIALIZER(internal_plist_init)
{
- pthread_once(&deinit_once, internal_plist_deinit);
+ plist_bin_init();
+ plist_xml_init();
+ plist_json_init();
+ plist_ostep_init();
+ atexit(internal_plist_deinit);
}
-#endif
-
#ifndef HAVE_MEMMEM
// see https://sourceware.org/legacy-ml/libc-alpha/2007-12/msg00000.html
@@ -166,7 +208,7 @@ void* memmem(const void* haystack, size_t haystack_len, const void* needle, size
}
#endif
-PLIST_API int plist_is_binary(const char *plist_data, uint32_t length)
+int plist_is_binary(const char *plist_data, uint32_t length)
{
if (length < 8) {
return 0;
@@ -175,19 +217,131 @@ PLIST_API int plist_is_binary(const char *plist_data, uint32_t length)
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_API void plist_from_memory(const char *plist_data, uint32_t length, plist_t * plist)
+plist_err_t plist_from_memory(const char *plist_data, uint32_t length, plist_t *plist, plist_format_t *format)
{
- if (length < 8) {
- *plist = NULL;
- return;
+ 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)) {
- plist_from_bin(plist_data, length, plist);
+ res = plist_from_bin(plist_data, length, plist);
+ fmt = PLIST_FORMAT_BINARY;
} else {
- plist_from_xml(plist_data, length, plist);
+ 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)
@@ -195,16 +349,16 @@ 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);
+ plist_data_t data = (plist_data_t) calloc(1, sizeof(struct plist_data_s));
return data;
}
@@ -247,10 +401,10 @@ void plist_free_data(plist_data_t data)
free(data->buff);
break;
case PLIST_ARRAY:
- ptr_array_free(data->hashtable);
+ ptr_array_free((ptrarray_t*)data->hashtable);
break;
case PLIST_DICT:
- hash_table_destroy(data->hashtable);
+ hash_table_destroy((hashtable_t*)data->hashtable);
break;
default:
break;
@@ -259,7 +413,7 @@ void plist_free_data(plist_data_t data)
}
}
-static int plist_free_node(node_t* node)
+static int plist_free_node(node_t node)
{
plist_data_t data = NULL;
int node_index = node_detach(node->parent, node);
@@ -267,9 +421,9 @@ static int plist_free_node(node_t* node)
plist_free_data(data);
node->data = NULL;
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ) {
- node_t *next = node_next_sibling(ch);
+ node_t next = node_next_sibling(ch);
plist_free_node(ch);
ch = next;
}
@@ -279,14 +433,14 @@ static int plist_free_node(node_t* node)
return node_index;
}
-PLIST_API plist_t plist_new_dict(void)
+plist_t plist_new_dict(void)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DICT;
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_array(void)
+plist_t plist_new_array(void)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_ARRAY;
@@ -303,7 +457,7 @@ static plist_t plist_new_key(const char *val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_string(const char *val)
+plist_t plist_new_string(const char *val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_STRING;
@@ -312,7 +466,7 @@ PLIST_API plist_t plist_new_string(const char *val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_bool(uint8_t val)
+plist_t plist_new_bool(uint8_t val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_BOOLEAN;
@@ -321,16 +475,25 @@ PLIST_API plist_t plist_new_bool(uint8_t val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_uint(uint64_t val)
+plist_t plist_new_uint(uint64_t val)
+{
+ plist_data_t data = plist_new_plist_data();
+ 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();
- data->type = PLIST_UINT;
+ data->type = PLIST_INT;
data->intval = val;
data->length = sizeof(uint64_t);
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_uid(uint64_t val)
+plist_t plist_new_uid(uint64_t val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_UID;
@@ -339,7 +502,7 @@ PLIST_API plist_t plist_new_uid(uint64_t val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_real(double val)
+plist_t plist_new_real(double val)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_REAL;
@@ -348,7 +511,7 @@ PLIST_API plist_t plist_new_real(double val)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_data(const char *val, uint64_t length)
+plist_t plist_new_data(const char *val, uint64_t length)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DATA;
@@ -358,7 +521,7 @@ PLIST_API plist_t plist_new_data(const char *val, uint64_t length)
return plist_new_node(data);
}
-PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec)
+plist_t plist_new_date(int32_t sec, int32_t usec)
{
plist_data_t data = plist_new_plist_data();
data->type = PLIST_DATE;
@@ -367,15 +530,41 @@ PLIST_API plist_t plist_new_date(int32_t sec, int32_t usec)
return plist_new_node(data);
}
-PLIST_API void plist_free(plist_t plist)
+plist_t plist_new_unix_date(int64_t sec)
+{
+ plist_data_t data = plist_new_plist_data();
+ 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();
+ data->type = PLIST_NULL;
+ data->intval = 0;
+ data->length = 0;
+ return plist_new_node(data);
+}
+
+void plist_free(plist_t plist)
{
if (plist)
{
- plist_free_node(plist);
+ plist_free_node((node_t)plist);
}
}
-static plist_t plist_copy_node(node_t *node)
+void plist_mem_free(void* ptr)
+{
+ if (ptr)
+ {
+ free(ptr);
+ }
+}
+
+static plist_t plist_copy_node(node_t node)
{
plist_type node_type = PLIST_NONE;
plist_t newnode = NULL;
@@ -395,7 +584,7 @@ static plist_t plist_copy_node(node_t *node)
break;
case PLIST_KEY:
case PLIST_STRING:
- newdata->strval = strdup((char *) data->strval);
+ newdata->strval = strdup(data->strval);
break;
case PLIST_ARRAY:
if (data->hashtable) {
@@ -416,13 +605,13 @@ static plist_t plist_copy_node(node_t *node)
}
newnode = plist_new_node(newdata);
- node_t *ch;
+ node_t ch;
unsigned int node_index = 0;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
/* copy child node */
plist_t newch = plist_copy_node(ch);
/* attach to new parent node */
- node_attach(newnode, newch);
+ node_attach((node_t)newnode, (node_t)newch);
/* if needed, add child node to lookup table of parent node */
switch (node_type) {
case PLIST_ARRAY:
@@ -432,7 +621,7 @@ static plist_t plist_copy_node(node_t *node)
break;
case PLIST_DICT:
if (newdata->hashtable && (node_index % 2 != 0)) {
- hash_table_insert((hashtable_t*)newdata->hashtable, (node_prev_sibling((node_t*)newch))->data, newch);
+ hash_table_insert((hashtable_t*)newdata->hashtable, (node_prev_sibling((node_t)newch))->data, newch);
}
break;
default:
@@ -443,136 +632,140 @@ static plist_t plist_copy_node(node_t *node)
return newnode;
}
-PLIST_API plist_t plist_copy(plist_t node)
+plist_t plist_copy(plist_t node)
{
- return node ? plist_copy_node(node) : NULL;
+ return node ? plist_copy_node((node_t)node) : NULL;
}
-PLIST_API uint32_t plist_array_get_size(plist_t node)
+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);
+ ret = node_n_children((node_t)node);
}
return ret;
}
-PLIST_API plist_t plist_array_get_item(plist_t node, uint32_t n)
+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) && n < INT_MAX)
{
- ptrarray_t *pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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, n);
+ ret = (plist_t)node_nth_child((node_t)node, n);
}
}
return ret;
}
-PLIST_API uint32_t plist_array_get_item_index(plist_t node)
+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);
+ return node_child_position((node_t)father, (node_t)node);
}
return UINT_MAX;
}
static void _plist_array_post_insert(plist_t node, plist_t item, long n)
{
- ptrarray_t *pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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);
} else {
- if (((node_t*)node)->count > 100) {
+ 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);
+ for (current = (plist_t)node_first_child((node_t)node);
pa && current;
- current = (plist_t)node_next_sibling(current))
+ current = (plist_t)node_next_sibling((node_t)current))
{
ptr_array_add(pa, current);
}
- ((plist_data_t)((node_t*)node)->data)->hashtable = pa;
+ ((plist_data_t)((node_t)node)->data)->hashtable = pa;
}
}
}
-PLIST_API void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
+void plist_array_set_item(plist_t node, plist_t item, uint32_t n)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
plist_t old_item = plist_array_get_item(node, n);
if (old_item)
{
- int idx = plist_free_node(old_item);
+ int idx = plist_free_node((node_t)old_item);
assert(idx >= 0);
if (idx < 0) {
return;
- } else {
- node_insert(node, idx, item);
- ptrarray_t* pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
- if (pa) {
- ptr_array_set(pa, item, idx);
- }
+ }
+ node_insert((node_t)node, idx, (node_t)item);
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)node)->data)->hashtable;
+ if (pa) {
+ ptr_array_set(pa, item, idx);
}
}
}
- return;
}
-PLIST_API void plist_array_append_item(plist_t node, plist_t item)
+void plist_array_append_item(plist_t node, plist_t item)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node))
{
- node_attach(node, item);
+ node_attach((node_t)node, (node_t)item);
_plist_array_post_insert(node, item, -1);
}
- return;
}
-PLIST_API void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
+void plist_array_insert_item(plist_t node, plist_t item, uint32_t n)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
- node_insert(node, n, item);
+ node_insert((node_t)node, n, (node_t)item);
_plist_array_post_insert(node, item, (long)n);
}
- return;
}
-PLIST_API void plist_array_remove_item(plist_t node, uint32_t n)
+void plist_array_remove_item(plist_t node, uint32_t n)
{
if (node && PLIST_ARRAY == plist_get_node_type(node) && n < INT_MAX)
{
plist_t old_item = plist_array_get_item(node, n);
if (old_item)
{
- ptrarray_t* pa = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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_API void plist_array_item_remove(plist_t node)
+void plist_array_item_remove(plist_t node)
{
plist_t father = plist_get_parent(node);
if (PLIST_ARRAY == plist_get_node_type(father))
{
- int n = node_child_position(father, node);
+ int n = node_child_position((node_t)father, (node_t)node);
if (n < 0) return;
- ptrarray_t* pa = ((plist_data_t)((node_t*)father)->data)->hashtable;
+ ptrarray_t* pa = (ptrarray_t*)((plist_data_t)((node_t)father)->data)->hashtable;
if (pa) {
ptr_array_remove(pa, n);
}
@@ -580,19 +773,18 @@ PLIST_API void plist_array_item_remove(plist_t node)
}
}
-PLIST_API void plist_array_new_iter(plist_t node, plist_array_iter *iter)
+void plist_array_new_iter(plist_t node, plist_array_iter *iter)
{
if (iter)
{
- *iter = malloc(sizeof(node_t*));
- *((node_t**)(*iter)) = node_first_child(node);
+ *iter = malloc(sizeof(node_t));
+ *((node_t*)(*iter)) = node_first_child((node_t)node);
}
- return;
}
-PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
+void plist_array_next_item(plist_t node, plist_array_iter iter, plist_t *item)
{
- node_t** iter_node = (node_t**)iter;
+ node_t* iter_node = (node_t*)iter;
if (item)
{
@@ -607,32 +799,30 @@ PLIST_API void plist_array_next_item(plist_t node, plist_array_iter iter, plist_
}
*iter_node = node_next_sibling(*iter_node);
}
- return;
}
-PLIST_API uint32_t plist_dict_get_size(plist_t node)
+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;
+ ret = node_n_children((node_t)node) / 2;
}
return ret;
}
-PLIST_API void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
+void plist_dict_new_iter(plist_t node, plist_dict_iter *iter)
{
if (iter)
{
- *iter = malloc(sizeof(node_t*));
- *((node_t**)(*iter)) = node_first_child(node);
+ *iter = malloc(sizeof(node_t));
+ *((node_t*)(*iter)) = node_first_child((node_t)node);
}
- return;
}
-PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
+void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **key, plist_t *val)
{
- node_t** iter_node = (node_t**)iter;
+ node_t* iter_node = (node_t*)iter;
if (key)
{
@@ -656,30 +846,29 @@ PLIST_API void plist_dict_next_item(plist_t node, plist_dict_iter iter, char **k
}
*iter_node = node_next_sibling(*iter_node);
}
- return;
}
-PLIST_API void plist_dict_get_item_key(plist_t node, char **key)
+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_get_key_val( (plist_t) node_prev_sibling((node_t)node), key);
}
}
-PLIST_API plist_t plist_dict_item_get_key(plist_t node)
+plist_t plist_dict_item_get_key(plist_t node)
{
plist_t ret = NULL;
plist_t father = plist_get_parent(node);
if (PLIST_DICT == plist_get_node_type(father))
{
- ret = (plist_t)node_prev_sibling(node);
+ ret = (plist_t)node_prev_sibling((node_t)node);
}
return ret;
}
-PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
+plist_t plist_dict_get_item(plist_t node, const char* key)
{
plist_t ret = NULL;
@@ -694,16 +883,16 @@ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
ret = (plist_t)hash_table_lookup(ht, &sdata);
} else {
plist_t current = NULL;
- for (current = (plist_t)node_first_child(node);
+ for (current = (plist_t)node_first_child((node_t)node);
current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
data = plist_get_data(current);
assert( PLIST_KEY == plist_get_node_type(current) );
if (data && !strcmp(key, data->strval))
{
- ret = (plist_t)node_next_sibling(current);
+ ret = (plist_t)node_next_sibling((node_t)current);
break;
}
}
@@ -712,74 +901,69 @@ PLIST_API plist_t plist_dict_get_item(plist_t node, const char* key)
return ret;
}
-PLIST_API void plist_dict_set_item(plist_t node, const char* key, plist_t item)
+void plist_dict_set_item(plist_t node, const char* key, plist_t item)
{
+ if (!item) {
+ return;
+ }
if (node && PLIST_DICT == plist_get_node_type(node)) {
- node_t* old_item = plist_dict_get_item(node, key);
+ plist_t old_item = plist_dict_get_item(node, key);
plist_t key_node = NULL;
if (old_item) {
- int idx = plist_free_node(old_item);
+ int idx = plist_free_node((node_t)old_item);
assert(idx >= 0);
if (idx < 0) {
return;
- } else {
- node_insert(node, idx, item);
}
- key_node = node_prev_sibling(item);
+ node_insert((node_t)node, idx, (node_t)item);
+ key_node = node_prev_sibling((node_t)item);
} else {
key_node = plist_new_key(key);
- node_attach(node, key_node);
- node_attach(node, item);
+ node_attach((node_t)node, (node_t)key_node);
+ node_attach((node_t)node, (node_t)item);
}
- hashtable_t *ht = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ hashtable_t *ht = (hashtable_t*)((plist_data_t)((node_t)node)->data)->hashtable;
if (ht) {
/* store pointer to item in hash table */
- hash_table_insert(ht, (plist_data_t)((node_t*)key_node)->data, item);
+ hash_table_insert(ht, (plist_data_t)((node_t)key_node)->data, item);
} else {
- if (((node_t*)node)->count > 500) {
+ 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);
+ for (current = (plist_t)node_first_child((node_t)node);
ht && current;
- current = (plist_t)node_next_sibling(node_next_sibling(current)))
+ current = (plist_t)node_next_sibling(node_next_sibling((node_t)current)))
{
- hash_table_insert(ht, ((node_t*)current)->data, node_next_sibling(current));
+ hash_table_insert(ht, ((node_t)current)->data, node_next_sibling((node_t)current));
}
- ((plist_data_t)((node_t*)node)->data)->hashtable = ht;
+ ((plist_data_t)((node_t)node)->data)->hashtable = ht;
}
}
}
- return;
-}
-
-PLIST_API void plist_dict_insert_item(plist_t node, const char* key, plist_t item)
-{
- plist_dict_set_item(node, key, item);
}
-PLIST_API void plist_dict_remove_item(plist_t node, const char* key)
+void plist_dict_remove_item(plist_t node, 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);
- hashtable_t* ht = ((plist_data_t)((node_t*)node)->data)->hashtable;
+ 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);
+ hash_table_remove(ht, ((node_t)key_node)->data);
}
plist_free(key_node);
plist_free(old_item);
}
}
- return;
}
-PLIST_API void plist_dict_merge(plist_t *target, plist_t source)
+void plist_dict_merge(plist_t *target, plist_t source)
{
if (!target || !*target || (plist_get_node_type(*target) != PLIST_DICT) || !source || (plist_get_node_type(source) != PLIST_DICT))
return;
@@ -800,10 +984,199 @@ PLIST_API void plist_dict_merge(plist_t *target, plist_t source)
free(key);
key = NULL;
} while (1);
- free(it);
-}
-
-PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
+ free(it);
+}
+
+uint8_t plist_dict_get_bool(plist_t dict, const char *key)
+{
+ 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)
{
plist_t current = plist;
plist_type type = PLIST_NONE;
@@ -827,7 +1200,7 @@ PLIST_API plist_t plist_access_pathv(plist_t plist, uint32_t length, va_list v)
return current;
}
-PLIST_API plist_t plist_access_path(plist_t plist, uint32_t length, ...)
+plist_t plist_access_path(plist_t plist, uint32_t length, ...)
{
plist_t ret = NULL;
va_list v;
@@ -855,7 +1228,7 @@ 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;
@@ -878,12 +1251,12 @@ static void plist_get_type_and_value(plist_t node, plist_type * type, void *valu
}
}
-PLIST_API plist_t plist_get_parent(plist_t node)
+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_API plist_type plist_get_node_type(plist_t node)
+plist_type plist_get_node_type(plist_t node)
{
if (node)
{
@@ -894,7 +1267,7 @@ PLIST_API plist_type plist_get_node_type(plist_t node)
return PLIST_NONE;
}
-PLIST_API void plist_get_key_val(plist_t node, char **val)
+void plist_get_key_val(plist_t node, char **val)
{
if (!node || !val)
return;
@@ -908,7 +1281,7 @@ PLIST_API void plist_get_key_val(plist_t node, char **val)
assert(length == strlen(*val));
}
-PLIST_API void plist_get_string_val(plist_t node, char **val)
+void plist_get_string_val(plist_t node, char **val)
{
if (!node || !val)
return;
@@ -922,7 +1295,7 @@ PLIST_API void plist_get_string_val(plist_t node, char **val)
assert(length == strlen(*val));
}
-PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length)
+const char* plist_get_string_ptr(plist_t node, uint64_t* length)
{
if (!node)
return NULL;
@@ -935,7 +1308,7 @@ PLIST_API const char* plist_get_string_ptr(plist_t node, uint64_t* length)
return (const char*)data->strval;
}
-PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val)
+void plist_get_bool_val(plist_t node, uint8_t * val)
{
if (!node || !val)
return;
@@ -947,19 +1320,24 @@ PLIST_API void plist_get_bool_val(plist_t node, uint8_t * val)
assert(length == sizeof(uint8_t));
}
-PLIST_API void plist_get_uint_val(plist_t node, uint64_t * val)
+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_UINT != type)
+ if (PLIST_INT != type)
return;
plist_get_type_and_value(node, &type, (void *) val, &length);
assert(length == sizeof(uint64_t) || length == 16);
}
-PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val)
+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;
@@ -971,7 +1349,7 @@ PLIST_API void plist_get_uid_val(plist_t node, uint64_t * val)
assert(length == sizeof(uint64_t));
}
-PLIST_API void plist_get_real_val(plist_t node, double *val)
+void plist_get_real_val(plist_t node, double *val)
{
if (!node || !val)
return;
@@ -983,7 +1361,7 @@ PLIST_API void plist_get_real_val(plist_t node, double *val)
assert(length == sizeof(double));
}
-PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length)
+void plist_get_data_val(plist_t node, char **val, uint64_t * length)
{
if (!node || !val || !length)
return;
@@ -993,7 +1371,7 @@ PLIST_API void plist_get_data_val(plist_t node, char **val, uint64_t * length)
plist_get_type_and_value(node, &type, (void *) val, length);
}
-PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length)
+const char* plist_get_data_ptr(plist_t node, uint64_t* length)
{
if (!node || !length)
return NULL;
@@ -1005,7 +1383,7 @@ PLIST_API const char* plist_get_data_ptr(plist_t node, uint64_t* length)
return (const char*)data->buff;
}
-PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
+void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
{
if (!node)
return;
@@ -1019,7 +1397,24 @@ PLIST_API void plist_get_date_val(plist_t node, int32_t * sec, int32_t * usec)
if (sec)
*sec = (int32_t)val;
if (usec)
- *usec = (int32_t)fabs((val - (int64_t)val) * 1000000);
+ {
+ 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)
@@ -1030,7 +1425,7 @@ int plist_data_compare(const void *a, const void *b)
if (!a || !b)
return FALSE;
- if (!((node_t*) a)->data || !((node_t*) b)->data)
+ if (!((node_t) a)->data || !((node_t) b)->data)
return FALSE;
val_a = plist_get_data((plist_t) a);
@@ -1042,46 +1437,36 @@ int plist_data_compare(const void *a, const void *b)
switch (val_a->type)
{
case PLIST_BOOLEAN:
- case PLIST_UINT:
+ case PLIST_NULL:
+ case PLIST_INT:
case PLIST_REAL:
case PLIST_DATE:
case PLIST_UID:
if (val_a->length != val_b->length)
return FALSE;
- if (val_a->intval == val_b->intval) //it is an union so this is sufficient
- return TRUE;
- else
- return FALSE;
+ return val_a->intval == val_b->intval; //it is an union so this is sufficient
case PLIST_KEY:
case PLIST_STRING:
- if (!strcmp(val_a->strval, val_b->strval))
- return TRUE;
- else
- return FALSE;
+ return strcmp(val_a->strval, val_b->strval) == 0;
case PLIST_DATA:
if (val_a->length != val_b->length)
return FALSE;
- if (!memcmp(val_a->buff, val_b->buff, val_a->length))
- return TRUE;
- else
- return FALSE;
+ 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;
+ return a == b;
+
default:
break;
}
return FALSE;
}
-PLIST_API char plist_compare_node_value(plist_t node_l, plist_t node_r)
+char plist_compare_node_value(plist_t node_l, plist_t node_r)
{
return plist_data_compare(node_l, node_r);
}
@@ -1117,7 +1502,7 @@ 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;
@@ -1140,7 +1525,7 @@ static void plist_set_element_val(plist_t node, plist_type type, const void *val
}
}
-PLIST_API void plist_set_key_val(plist_t node, const char *val)
+void plist_set_key_val(plist_t node, const char *val)
{
plist_t father = plist_get_parent(node);
plist_t item = plist_dict_get_item(father, val);
@@ -1150,43 +1535,54 @@ PLIST_API void plist_set_key_val(plist_t node, const char *val)
plist_set_element_val(node, PLIST_KEY, val, strlen(val));
}
-PLIST_API void plist_set_string_val(plist_t node, const char *val)
+void plist_set_string_val(plist_t node, const char *val)
{
plist_set_element_val(node, PLIST_STRING, val, strlen(val));
}
-PLIST_API void plist_set_bool_val(plist_t node, uint8_t val)
+void plist_set_bool_val(plist_t node, uint8_t val)
{
plist_set_element_val(node, PLIST_BOOLEAN, &val, sizeof(uint8_t));
}
-PLIST_API void plist_set_uint_val(plist_t node, uint64_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));
}
-PLIST_API void plist_set_uid_val(plist_t node, uint64_t val)
+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));
}
-PLIST_API void plist_set_real_val(plist_t node, double val)
+void plist_set_real_val(plist_t node, double val)
{
plist_set_element_val(node, PLIST_REAL, &val, sizeof(double));
}
-PLIST_API void plist_set_data_val(plist_t node, const char *val, uint64_t length)
+void plist_set_data_val(plist_t node, const char *val, uint64_t length)
{
plist_set_element_val(node, PLIST_DATA, val, length);
}
-PLIST_API void plist_set_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)
{
double val = (double)sec + (double)usec / 1000000;
- plist_set_element_val(node, PLIST_DATE, &val, sizeof(struct timeval));
+ 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));
}
-PLIST_API int plist_bool_val_is_true(plist_t boolnode)
+int plist_bool_val_is_true(plist_t boolnode)
{
if (!PLIST_IS_BOOLEAN(boolnode)) {
return 0;
@@ -1196,23 +1592,58 @@ PLIST_API int plist_bool_val_is_true(plist_t boolnode)
return (bv == 1);
}
-PLIST_API int plist_uint_val_compare(plist_t uintnode, uint64_t cmpval)
+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_UINT(uintnode)) {
+ 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;
- } else if (uintval < cmpval) {
+ }
+
+ if (uintval < cmpval) {
return -1;
- } else {
- return 1;
}
+
+ return 1;
}
-PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
+int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
{
if (!PLIST_IS_UID(uidnode)) {
return -1;
@@ -1221,14 +1652,16 @@ PLIST_API int plist_uid_val_compare(plist_t uidnode, uint64_t cmpval)
plist_get_uid_val(uidnode, &uidval);
if (uidval == cmpval) {
return 0;
- } else if (uidval < cmpval) {
+ }
+
+ if (uidval < cmpval) {
return -1;
- } else {
- return 1;
}
+
+ return 1;
}
-PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval)
+int plist_real_val_compare(plist_t realnode, double cmpval)
{
if (!PLIST_IS_REAL(realnode)) {
return -1;
@@ -1241,42 +1674,71 @@ PLIST_API int plist_real_val_compare(plist_t realnode, double cmpval)
double diff = fabs(a - b);
if (a == b) {
return 0;
- } else if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
+ }
+
+ if (a == 0 || b == 0 || (abs_a + abs_b < DBL_MIN)) {
if (diff < (DBL_EPSILON * DBL_MIN)) {
return 0;
- } else if (a < b) {
+ }
+
+ if (a < b) {
return -1;
}
} else {
if ((diff / fmin(abs_a + abs_b, DBL_MAX)) < DBL_EPSILON) {
return 0;
- } else if (a < b) {
+ }
+
+ if (a < b) {
return -1;
}
}
return 1;
}
-PLIST_API int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
+int plist_date_val_compare(plist_t datenode, int32_t cmpsec, int32_t cmpusec)
{
if (!PLIST_IS_DATE(datenode)) {
return -1;
}
- int32_t sec = 0;
- int32_t usec = 0;
- plist_get_date_val(datenode, &sec, &usec);
+ 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;
- } else if (dateval < cmpval) {
+ }
+
+ 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;
- } else {
- 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;
}
-PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval)
+int plist_string_val_compare(plist_t strnode, const char* cmpval)
{
if (!PLIST_IS_STRING(strnode)) {
return -1;
@@ -1285,7 +1747,7 @@ PLIST_API int plist_string_val_compare(plist_t strnode, const char* cmpval)
return strcmp(data->strval, cmpval);
}
-PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
+int plist_string_val_compare_with_size(plist_t strnode, const char* cmpval, size_t n)
{
if (!PLIST_IS_STRING(strnode)) {
return -1;
@@ -1294,7 +1756,7 @@ PLIST_API int plist_string_val_compare_with_size(plist_t strnode, const char* cm
return strncmp(data->strval, cmpval, n);
}
-PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr)
+int plist_string_val_contains(plist_t strnode, const char* substr)
{
if (!PLIST_IS_STRING(strnode)) {
return 0;
@@ -1303,7 +1765,7 @@ PLIST_API int plist_string_val_contains(plist_t strnode, const char* substr)
return (strstr(data->strval, substr) != NULL);
}
-PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval)
+int plist_key_val_compare(plist_t keynode, const char* cmpval)
{
if (!PLIST_IS_KEY(keynode)) {
return -1;
@@ -1312,7 +1774,7 @@ PLIST_API int plist_key_val_compare(plist_t keynode, const char* cmpval)
return strcmp(data->strval, cmpval);
}
-PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
+int plist_key_val_compare_with_size(plist_t keynode, const char* cmpval, size_t n)
{
if (!PLIST_IS_KEY(keynode)) {
return -1;
@@ -1321,7 +1783,7 @@ PLIST_API int plist_key_val_compare_with_size(plist_t keynode, const char* cmpva
return strncmp(data->strval, cmpval, n);
}
-PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr)
+int plist_key_val_contains(plist_t keynode, const char* substr)
{
if (!PLIST_IS_KEY(keynode)) {
return 0;
@@ -1330,7 +1792,7 @@ PLIST_API int plist_key_val_contains(plist_t keynode, const char* substr)
return (strstr(data->strval, substr) != NULL);
}
-PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
+int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, size_t n)
{
if (!PLIST_IS_DATA(datanode)) {
return -1;
@@ -1338,13 +1800,16 @@ PLIST_API int plist_data_val_compare(plist_t datanode, const uint8_t* cmpval, si
plist_data_t data = plist_get_data(datanode);
if (data->length < n) {
return -1;
- } else if (data->length > n) {
+ }
+
+ if (data->length > n) {
return 1;
}
+
return memcmp(data->buff, cmpval, n);
}
-PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t* cmpval, size_t 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;
@@ -1356,7 +1821,7 @@ PLIST_API int plist_data_val_compare_with_size(plist_t datanode, const uint8_t*
return memcmp(data->buff, cmpval, n);
}
-PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
+int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, size_t n)
{
if (!PLIST_IS_DATA(datanode)) {
return -1;
@@ -1364,3 +1829,187 @@ PLIST_API int plist_data_val_contains(plist_t datanode, const uint8_t* cmpval, s
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(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep(plist, output, length, ((options & PLIST_OPT_COMPACT) == 0));
+ 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(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
+ break;
+ case PLIST_FORMAT_OSTEP:
+ err = plist_to_openstep(plist, &output, &length, ((options & PLIST_OPT_COMPACT) == 0));
+ 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 7bf62a8..a993e3a 100644
--- a/src/plist.h
+++ b/src/plist.h
@@ -26,27 +26,31 @@
#include <config.h>
#endif
-#include "plist/plist.h"
-
#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 WIN32
+#ifdef LIBPLIST_STATIC
+ #define PLIST_API
+#elif defined(_WIN32)
#define PLIST_API __declspec( dllexport )
#else
- #ifdef HAVE_FVISIBILITY
+ #if __GNUC__ >= 4
#define PLIST_API __attribute__((visibility("default")))
#else
#define PLIST_API
#endif
#endif
+#include "plist/plist.h"
+
struct plist_data_s
{
union
@@ -65,10 +69,16 @@ struct plist_data_s
typedef struct plist_data_s *plist_data_t;
plist_t plist_new_node(plist_data_t data);
-plist_data_t plist_get_data(const plist_t node);
+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);
#endif
diff --git a/src/ptrarray.c b/src/ptrarray.c
index c499773..3a11031 100644
--- a/src/ptrarray.c
+++ b/src/ptrarray.c
@@ -45,7 +45,7 @@ void ptr_array_insert(ptrarray_t *pa, void *data, long array_index)
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;
}
if (array_index < 0 || array_index >= pa->len) {
diff --git a/src/strbuf.h b/src/strbuf.h
index 0d28edf..2fbfe93 100644
--- a/src/strbuf.h
+++ b/src/strbuf.h
@@ -27,6 +27,7 @@
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)
diff --git a/src/time64.c b/src/time64.c
index f1cffd4..cfbe7ac 100644
--- a/src/time64.c
+++ b/src/time64.c
@@ -1,4 +1,4 @@
-/*
+/*
Copyright (c) 2007-2010 Michael G Schwern
@@ -30,7 +30,7 @@ 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
+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().
@@ -59,11 +59,11 @@ static const short julian_days_by_month[2][12] = {
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335},
};
-static char wday_name[7][4] = {
+static const char wday_name[7][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
-static char mon_name[12][4] = {
+static const char mon_name[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
@@ -176,34 +176,28 @@ static int is_exception_century(Year year)
static int cmp_date( const struct TM* left, const struct tm* right ) {
if( left->tm_year > right->tm_year )
return 1;
- else if( left->tm_year < right->tm_year )
+ if( left->tm_year < right->tm_year )
return -1;
-
if( left->tm_mon > right->tm_mon )
return 1;
- else if( left->tm_mon < right->tm_mon )
+ if( left->tm_mon < right->tm_mon )
return -1;
-
if( left->tm_mday > right->tm_mday )
return 1;
- else if( left->tm_mday < right->tm_mday )
+ if( left->tm_mday < right->tm_mday )
return -1;
-
if( left->tm_hour > right->tm_hour )
return 1;
- else if( left->tm_hour < right->tm_hour )
+ if( left->tm_hour < right->tm_hour )
return -1;
-
if( left->tm_min > right->tm_min )
return 1;
- else if( left->tm_min < right->tm_min )
+ if( left->tm_min < right->tm_min )
return -1;
-
if( left->tm_sec > right->tm_sec )
return 1;
- else if( left->tm_sec < right->tm_sec )
+ if( left->tm_sec < right->tm_sec )
return -1;
-
return 0;
}
@@ -230,15 +224,11 @@ 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 ) {
- cycles = (orig_year - 100) / 400;
- orig_year -= cycles * 400;
- days += (Time64_T)cycles * days_in_gregorian_cycle;
- }
- else if( orig_year < -300 ) {
+ if( (orig_year > 100) || (orig_year < -300) ) {
cycles = (orig_year - 100) / 400;
orig_year -= cycles * 400;
days += (Time64_T)cycles * days_in_gregorian_cycle;
@@ -293,7 +283,7 @@ static int check_tm(struct TM *tm)
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)]);
@@ -368,7 +358,7 @@ static int safe_year(const Year year)
year_cycle += 17;
year_cycle %= SOLAR_CYCLE_LENGTH;
- if( year_cycle < 0 )
+ if( year_cycle < 0 )
year_cycle = SOLAR_CYCLE_LENGTH + year_cycle;
assert( year_cycle >= 0 );
@@ -542,7 +532,7 @@ Time64_T mktime64(struct TM *input_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));
+ timev += seconds_between_years(year, (Year)(safe_date.tm_year) + 1900);
return timev;
}
@@ -674,7 +664,7 @@ struct TM *gmtime64_r (const Time64_T *in_time, struct TM *p)
p->tm_hour = v_tm_hour;
p->tm_mon = v_tm_mon;
p->tm_wday = v_tm_wday;
-
+
assert(check_tm(p));
return p;
@@ -717,7 +707,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
)
{
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;
+ gm_tm.tm_year = safe_year((Year)(gm_tm.tm_year) + 1900) - 1900;
}
safe_time = (time_t)timegm64(&gm_tm);
@@ -756,7 +746,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
local_tm->tm_year++;
}
- /* GMT is Jan 1st, xx01 year, but localtime is still Dec 31st
+ /* 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
@@ -766,7 +756,7 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
local_tm->tm_yday--;
assert(check_tm(local_tm));
-
+
return local_tm;
}
@@ -774,15 +764,15 @@ struct TM *localtime64_r (const Time64_T *timev, struct TM *local_tm)
static int valid_tm_wday( const struct TM* date ) {
if( 0 <= date->tm_wday && date->tm_wday <= 6 )
return 1;
- else
- return 0;
+
+ return 0;
}
static int valid_tm_mon( const struct TM* date ) {
if( 0 <= date->tm_mon && date->tm_mon <= 11 )
return 1;
- else
- return 0;
+
+ return 0;
}
diff --git a/src/time64.h b/src/time64.h
index bf174c6..efdc716 100644
--- a/src/time64.h
+++ b/src/time64.h
@@ -39,7 +39,7 @@ struct TM64 {
#define TM TM64
#else
#define TM tm
-#endif
+#endif
/* Declare public functions */
diff --git a/src/xplist.c b/src/xplist.c
index a7d52e5..dc5213b 100644
--- a/src/xplist.c
+++ b/src/xplist.c
@@ -41,7 +41,6 @@
#include <limits.h>
#include <node.h>
-#include <node_list.h>
#include "plist.h"
#include "base64.h"
@@ -71,7 +70,7 @@
#define MAC_EPOCH 978307200
-#define MAX_DATA_BYTES_PER_LINE(__i) (((76 - (__i << 3)) >> 2) * 3)
+#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\
@@ -81,8 +80,10 @@ 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
void plist_xml_init(void)
@@ -101,6 +102,13 @@ void plist_xml_deinit(void)
/* deinit XML stuff */
}
+void plist_xml_set_debug(int debug)
+{
+#if DEBUG
+ plist_xml_debug = debug;
+#endif
+}
+
static size_t dtostr(char *buf, size_t bufsize, double realval)
{
size_t len = 0;
@@ -113,7 +121,7 @@ static size_t dtostr(char *buf, size_t bufsize, double realval)
} else {
size_t i = 0;
len = snprintf(buf, bufsize, "%.*g", 17, realval);
- for (i = 0; i < len; i++) {
+ for (i = 0; buf && i < len; i++) {
if (buf[i] == ',') {
buf[i] = '.';
break;
@@ -125,7 +133,7 @@ static size_t dtostr(char *buf, size_t bufsize, double realval)
return len;
}
-static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
+static plist_err_t node_to_xml(node_t node, bytearray_t **outbuf, uint32_t depth)
{
plist_data_t node_data = NULL;
@@ -139,8 +147,10 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
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;
+ }
node_data = plist_get_data(node);
@@ -158,14 +168,14 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
}
break;
- case PLIST_UINT:
+ case PLIST_INT:
tag = XPLIST_INT;
tag_len = XPLIST_INT_LEN;
val = (char*)malloc(64);
if (node_data->length == 16) {
- val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
} else {
- val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIi64, node_data->intval);
}
break;
@@ -211,8 +221,7 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
struct TM _btime;
struct TM *btime = gmtime64_r(&timev, &_btime);
if (btime) {
- val = (char*)malloc(24);
- memset(val, 0, 24);
+ val = (char*)calloc(1, 24);
struct tm _tmcopy;
copy_TM64_to_tm(btime, &_tmcopy);
val_len = strftime(val, 24, "%Y-%m-%dT%H:%M:%SZ", &_tmcopy);
@@ -228,13 +237,16 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
tag_len = XPLIST_DICT_LEN;
val = (char*)malloc(64);
if (node_data->length == 16) {
- val_len = snprintf(val, 64, "%"PRIu64, node_data->intval);
+ val_len = snprintf(val, 64, "%" PRIu64, node_data->intval);
} else {
- val_len = snprintf(val, 64, "%"PRIi64, node_data->intval);
+ val_len = snprintf(val, 64, "%" 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:
- break;
+ return PLIST_ERR_UNKNOWN;
}
for (i = 0; i < depth; i++) {
@@ -244,7 +256,7 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
/* 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) {
+ 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;
@@ -352,9 +364,10 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
if (node_data->type == PLIST_DICT && node->children) {
assert((node->children->count % 2) == 0);
}
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
- node_to_xml(ch, outbuf, depth+1);
+ plist_err_t res = node_to_xml(ch, outbuf, depth+1);
+ if (res < 0) return res;
}
/* fix indent for structured types */
@@ -370,8 +383,7 @@ static void node_to_xml(node_t* node, bytearray_t **outbuf, uint32_t depth)
str_buf_append(*outbuf, ">", 1);
}
str_buf_append(*outbuf, "\n", 1);
-
- return;
+ return PLIST_ERR_SUCCESS;
}
static void parse_date(const char *strval, struct TM *btime)
@@ -403,7 +415,7 @@ static int num_digits_i(int64_t i)
int64_t po10;
n=1;
if (i < 0) {
- i = -i;
+ i = (i == INT64_MIN) ? INT64_MAX : -i;
n++;
}
po10=10;
@@ -432,15 +444,15 @@ static int num_digits_u(uint64_t i)
return n;
}
-static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
+static plist_err_t node_estimate_size(node_t node, uint64_t *size, uint32_t depth)
{
plist_data_t data;
if (!node) {
- return;
+ return PLIST_ERR_INVALID_ARG;
}
data = plist_get_data(node);
if (node->children) {
- node_t *ch;
+ node_t ch;
for (ch = node_first_child(node); ch; ch = node_next_sibling(ch)) {
node_estimate_size(ch, size, depth + 1);
}
@@ -473,7 +485,7 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*size += data->length;
*size += (XPLIST_KEY_LEN << 1) + 6;
break;
- case PLIST_UINT:
+ case PLIST_INT:
if (data->length == 16) {
*size += num_digits_u(data->intval);
} else {
@@ -482,7 +494,7 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*size += (XPLIST_INT_LEN << 1) + 6;
break;
case PLIST_REAL:
- *size += num_digits_i((int64_t)data->realval) + 7;
+ *size += dtostr(NULL, 0, data->realval);
*size += (XPLIST_REAL_LEN << 1) + 6;
break;
case PLIST_DATE:
@@ -505,37 +517,58 @@ static void node_estimate_size(node_t *node, uint64_t *size, uint32_t depth)
*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:
- break;
+ PLIST_XML_WRITE_ERR("invalid node type encountered\n");
+ return PLIST_ERR_UNKNOWN;
}
*size += indent;
}
+ return PLIST_ERR_SUCCESS;
}
-PLIST_API void plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
+plist_err_t plist_to_xml(plist_t plist, char **plist_xml, uint32_t * length)
{
uint64_t size = 0;
- node_estimate_size(plist, &size, 0);
+ plist_err_t res;
+
+ if (!plist || !plist_xml || !length) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+
+ 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);
- node_to_xml(plist, &outbuf, 0);
+ 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 = outbuf->data;
+ *plist_xml = (char*)outbuf->data;
*length = outbuf->len - 1;
outbuf->data = NULL;
str_buf_free(outbuf);
-}
-PLIST_API void plist_to_xml_free(char *plist_xml)
-{
- free(plist_xml);
+ return PLIST_ERR_SUCCESS;
}
struct _parse_ctx {
@@ -638,14 +671,14 @@ static void text_parts_free(text_part_t *tp)
{
while (tp) {
text_part_t *tmp = tp;
- tp = tp->next;
+ 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 = malloc(sizeof(text_part_t));
+ 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;
@@ -751,7 +784,7 @@ static text_part_t* get_text_parts(parse_ctx ctx, const char* tag, size_t tag_le
}
} while (1);
ctx->pos++;
- if (ctx->pos >= ctx->end-tag_len || strncmp(ctx->pos, tag, tag_len)) {
+ 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++;
return NULL;
@@ -897,9 +930,9 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
text_part_t *tmp = tp;
while (tp && tp->begin) {
total_length += tp->length;
- tp = tp->next;
+ tp = (text_part_t*)tp->next;
}
- str = malloc(total_length + 1);
+ str = (char*)malloc(total_length + 1);
assert(str);
p = str;
tp = tmp;
@@ -914,7 +947,7 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
}
}
p += len;
- tp = tp->next;
+ tp = (text_part_t*)tp->next;
}
*p = '\0';
if (length) {
@@ -926,7 +959,7 @@ static char* text_parts_get_content(text_part_t *tp, int unesc_entities, size_t
return str;
}
-static void node_from_xml(parse_ctx ctx, plist_t *plist)
+static plist_err_t node_from_xml(parse_ctx ctx, plist_t *plist)
{
char *tag = NULL;
char *keyname = NULL;
@@ -967,7 +1000,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
ctx->err++;
goto err_out;
}
- if (strncmp(ctx->pos, "?>", 2)) {
+ if (strncmp(ctx->pos, "?>", 2) != 0) {
PLIST_XML_ERR("Couldn't find <? tag closing marker\n");
ctx->err++;
goto err_out;
@@ -979,7 +1012,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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)) {
+ if (ctx->pos > ctx->end-3 || strncmp(ctx->pos, "-->", 3) != 0) {
PLIST_XML_ERR("Couldn't find end of comment\n");
ctx->err++;
goto err_out;
@@ -1008,7 +1041,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
if (embedded_dtd) {
find_str(ctx, "]>", 2, 1);
- if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2)) {
+ if (ctx->pos > ctx->end-2 || strncmp(ctx->pos, "]>", 2) != 0) {
PLIST_XML_ERR("Couldn't find end of DOCTYPE\n");
ctx->err++;
goto err_out;
@@ -1033,8 +1066,8 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
ctx->err++;
goto err_out;
}
- int taglen = ctx->pos - p;
- tag = malloc(taglen + 1);
+ size_t taglen = ctx->pos - p;
+ tag = (char*)malloc(taglen + 1);
strncpy(tag, p, taglen);
tag[taglen] = '\0';
if (*ctx->pos != '>') {
@@ -1051,7 +1084,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
if (*(ctx->pos-1) == '/') {
- int idx = ctx->pos - p - 1;
+ size_t idx = ctx->pos - p - 1;
if (idx < taglen)
tag[idx] = '\0';
is_empty = 1;
@@ -1072,7 +1105,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
- struct node_path_item *path_item = malloc(sizeof(struct node_path_item));
+ 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++;
@@ -1100,7 +1133,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
struct node_path_item *path_item = node_path;
- node_path = node_path->prev;
+ node_path = (struct node_path_item*)node_path->prev;
free(path_item);
free(tag);
@@ -1123,7 +1156,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1132,7 +1165,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1144,7 +1177,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
str++;
}
- data->intval = strtoull((char*)str, NULL, 0);
+ data->intval = strtoull(str, NULL, 0);
if (is_negative || (data->intval <= INT64_MAX)) {
uint64_t v = data->intval;
if (is_negative) {
@@ -1161,20 +1194,20 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
} else {
is_empty = 1;
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
if (is_empty) {
data->intval = 0;
data->length = 8;
}
- data->type = PLIST_UINT;
+ 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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1183,7 +1216,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1192,7 +1225,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
data->type = PLIST_REAL;
data->length = 8;
@@ -1218,12 +1251,12 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
size_t length = 0;
if (!tp) {
PLIST_XML_ERR("Could not parse text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
str = text_parts_get_content(tp, 1, &length, NULL);
- text_parts_free(first_part.next);
+ 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++;
@@ -1241,6 +1274,14 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
data->length = length;
}
} else {
+ if (!strcmp(tag, "key") && !keyname && parent && (plist_get_node_type(parent) == PLIST_DICT)) {
+ keyname = strdup("");
+ free(tag);
+ tag = NULL;
+ plist_free(subnode);
+ subnode = NULL;
+ continue;
+ }
data->strval = strdup("");
data->length = 0;
}
@@ -1251,7 +1292,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1260,7 +1301,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, NULL, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1274,7 +1315,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
}
data->type = PLIST_DATA;
} else if (!strcmp(tag, XPLIST_DATE)) {
@@ -1283,7 +1324,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
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(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1294,7 +1335,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
char *str_content = text_parts_get_content(tp, 0, &length, &requires_free);
if (!str_content) {
PLIST_XML_ERR("Could not get text content for '%s' node\n", tag);
- text_parts_free(first_part.next);
+ text_parts_free((text_part_t*)first_part.next);
ctx->err++;
goto err_out;
}
@@ -1314,7 +1355,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
free(str_content);
}
}
- text_parts_free(tp->next);
+ text_parts_free((text_part_t*)tp->next);
data->realval = (double)(timev - MAC_EPOCH);
}
data->length = sizeof(double);
@@ -1358,7 +1399,7 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
}
}
if (!is_empty && (data->type == PLIST_DICT || data->type == PLIST_ARRAY)) {
- struct node_path_item *path_item = malloc(sizeof(struct node_path_item));
+ 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++;
@@ -1383,10 +1424,10 @@ static void node_from_xml(parse_ctx ctx, plist_t *plist)
goto err_out;
}
struct node_path_item *path_item = node_path;
- node_path = node_path->prev;
+ node_path = (struct node_path_item*)node_path->prev;
free(path_item);
- parent = ((node_t*)parent)->parent;
+ parent = ((node_t)parent)->parent;
if (!parent) {
goto err_out;
}
@@ -1414,24 +1455,41 @@ err_out:
/* clean up node_path if required */
while (node_path) {
struct node_path_item *path_item = node_path;
- node_path = path_item->prev;
+ node_path = (struct node_path_item*)path_item->prev;
free(path_item);
}
if (ctx->err) {
plist_free(*plist);
*plist = NULL;
+ return PLIST_ERR_PARSE;
+ }
+
+ /* 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_UINT(value)) {
+ uint64_t u64val = 0;
+ plist_get_uint_val(value, &u64val);
+ plist_free(*plist);
+ *plist = plist_new_uid(u64val);
+ }
}
+
+ return PLIST_ERR_SUCCESS;
}
-PLIST_API 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)
{
+ if (!plist) {
+ return PLIST_ERR_INVALID_ARG;
+ }
+ *plist = NULL;
if (!plist_xml || (length == 0)) {
- *plist = NULL;
- return;
+ return PLIST_ERR_INVALID_ARG;
}
struct _parse_ctx ctx = { plist_xml, plist_xml + length, 0 };
- node_from_xml(&ctx, plist);
+ return node_from_xml(&ctx, plist);
}
diff --git a/test/Makefile.am b/test/Makefile.am
index 4519de0..a4191c4 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,13 +1,42 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/include -I$(top_srcdir)/libcnary/include
+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
+noinst_PROGRAMS = \
+ plist_cmp \
+ plist_test \
+ plist_test++ \
+ integer_set_test \
+ plist_btest \
+ plist_jtest \
+ plist_otest
plist_cmp_SOURCES = plist_cmp.c
-plist_cmp_LDADD = $(top_builddir)/src/libplist.la $(top_builddir)/libcnary/libcnary.la
+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.la
+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
TESTS = \
empty.test \
@@ -16,6 +45,12 @@ TESTS = \
large.test \
huge.test \
bigarray.test \
+ empty++.test \
+ small++.test \
+ medium++.test \
+ large++.test \
+ huge++.test \
+ bigarray++.test \
dates.test \
timezone1.test \
timezone2.test \
@@ -32,7 +67,19 @@ TESTS = \
cdata.test \
offsetsize.test \
refsize.test \
- malformed_dict.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
EXTRA_DIST = \
$(TESTS) \
@@ -75,9 +122,20 @@ EXTRA_DIST = \
data/signedunsigned.bplist \
data/signedunsigned.plist \
data/unsigned.bplist \
- data/unsigned.plist
+ 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)
+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
index 0815391..3678f27 100755
--- a/test/amp.test
+++ b/test/amp.test
@@ -1,14 +1,16 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=amp.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/amp.test.out
rm -rf $DATAOUT0
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
-if test -f $DATAOUT0; then
+
+# 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
index 4b819f4..1b88d43 100755
--- a/test/bigarray.test
+++ b/test/bigarray.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/bigarray.test.out
diff --git a/test/cdata.test b/test/cdata.test
index 77e7e45..b4f3ed2 100755
--- a/test/cdata.test
+++ b/test/cdata.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=cdata.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/cdata.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
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/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/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/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/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/dates.test b/test/dates.test
index 2865717..06bf034 100755
--- a/test/dates.test
+++ b/test/dates.test
@@ -5,8 +5,8 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.xml
+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
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
index 0a21f33..bc71562 100755
--- a/test/empty.test
+++ b/test/empty.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/empty.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/empty.test.out
diff --git a/test/empty_keys.test b/test/empty_keys.test
index 4291e8c..19bbb08 100755
--- a/test/empty_keys.test
+++ b/test/empty_keys.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=empty_keys.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/empty_keys.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
diff --git a/test/entities.test b/test/entities.test
index b87e4c7..a828e15 100755
--- a/test/entities.test
+++ b/test/entities.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=entities.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/entities.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
diff --git a/test/hex.test b/test/hex.test
index 99b88bc..414cdbc 100755
--- a/test/hex.test
+++ b/test/hex.test
@@ -5,7 +5,7 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=hex.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
+DATAOUT0=$top_builddir/test/data/hex.test.bin
$top_builddir/tools/plistutil -i $DATAIN0 -o $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
index 8585ed5..560c21f 100755
--- a/test/huge.test
+++ b/test/huge.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/huge.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$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
index 079bcdd..5d4da93 100755
--- a/test/invalid_tag.test
+++ b/test/invalid_tag.test
@@ -1,14 +1,16 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=invalid_tag.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/invalid_tag.test.out
rm -rf $DATAOUT0
$top_builddir/tools/plistutil -i $DATAIN0 -o $DATAOUT0
-if test -f $DATAOUT0; then
+
+# 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
index b46743f..d3cce7a 100755
--- a/test/large.test
+++ b/test/large.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/large.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/large.test.out
diff --git a/test/malformed_dict.test b/test/malformed_dict.test
index f45ae7e..1d3ba2b 100755
--- a/test/malformed_dict.test
+++ b/test/malformed_dict.test
@@ -1,11 +1,15 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=malformed_dict.bplist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+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
index 39213b4..300c016 100755
--- a/test/medium.test
+++ b/test/medium.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/medium.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/medium.test.out
diff --git a/test/order.test b/test/order.test
index f8a7702..bd89b9d 100755
--- a/test/order.test
+++ b/test/order.test
@@ -6,7 +6,7 @@ DATASRC=$top_srcdir/test/data
TESTFILE=order.bplist
DATAIN0=$DATASRC/$TESTFILE
DATAIN1=$DATASRC/order.plist
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+DATAOUT0=$top_builddir/test/data/order.test.out
$top_builddir/tools/plistutil -i $DATAIN0 -o $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..0f2c1c8
--- /dev/null
+++ b/test/plist_btest.c
@@ -0,0 +1,131 @@
+/*
+ * 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));
+ fread(plist_bin, sizeof(char), size_in, iplist);
+ 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..130e3c7
--- /dev/null
+++ b/test/plist_jtest.c
@@ -0,0 +1,131 @@
+/*
+ * 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));
+ fread(plist_json, sizeof(char), size_in, iplist);
+ 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..14168f8
--- /dev/null
+++ b/test/plist_otest.c
@@ -0,0 +1,130 @@
+/*
+ * 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));
+ fread(plist_ostep, sizeof(char), size_in, iplist);
+ 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 b498e1d..6e3947a 100644
--- a/test/plist_test.c
+++ b/test/plist_test.c
@@ -77,36 +77,32 @@ 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;
diff --git a/test/recursion.test b/test/recursion.test
index 0120a12..97bfb45 100755
--- a/test/recursion.test
+++ b/test/recursion.test
@@ -1,11 +1,15 @@
## -*- sh -*-
-set -e
-
DATASRC=$top_srcdir/test/data
TESTFILE=recursion.bplist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.out
+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/signedunsigned1.test b/test/signedunsigned1.test
index 798de86..52f7080 100755
--- a/test/signedunsigned1.test
+++ b/test/signedunsigned1.test
@@ -13,8 +13,8 @@ CMPFILE1=unsigned.bplist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.bin
+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 --git a/test/signedunsigned2.test b/test/signedunsigned2.test
index 2ea8b49..192f1a6 100755
--- a/test/signedunsigned2.test
+++ b/test/signedunsigned2.test
@@ -13,8 +13,8 @@ CMPFILE1=unsigned.plist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.bin
+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 --git a/test/signedunsigned3.test b/test/signedunsigned3.test
index d732e72..87bdcbc 100755
--- a/test/signedunsigned3.test
+++ b/test/signedunsigned3.test
@@ -13,8 +13,8 @@ CMPFILE1=signedunsigned.plist
DATACMP0=$DATASRC/$CMPFILE0
DATACMP1=$DATASRC/$CMPFILE1
-DATAOUT0=$top_builddir/test/data/$TESTFILE0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE1.xml
+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 --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
index 8c25e70..dd2faf8 100755
--- a/test/small.test
+++ b/test/small.test
@@ -9,7 +9,7 @@ if ! test -d "$DATAOUT"; then
fi
echo "Converting"
-$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_test $DATASRC/$TESTFILE $DATAOUT/small.test.out
echo "Comparing"
-$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/$TESTFILE.out
+$top_builddir/test/plist_cmp $DATASRC/$TESTFILE $DATAOUT/small.test.out
diff --git a/test/timezone1.test b/test/timezone1.test
index 843b166..5de7cea 100755
--- a/test/timezone1.test
+++ b/test/timezone1.test
@@ -5,9 +5,9 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.tz0.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.tz1.bin
-DATAOUT2=$top_builddir/test/data/$TESTFILE.tz2.bin
+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
diff --git a/test/timezone2.test b/test/timezone2.test
index 57db720..79db523 100755
--- a/test/timezone2.test
+++ b/test/timezone2.test
@@ -5,10 +5,10 @@ set -e
DATASRC=$top_srcdir/test/data
TESTFILE=7.plist
DATAIN0=$DATASRC/$TESTFILE
-DATAOUT0=$top_builddir/test/data/$TESTFILE.bin
-DATAOUT1=$top_builddir/test/data/$TESTFILE.tz0.xml
-DATAOUT2=$top_builddir/test/data/$TESTFILE.tz1.xml
-DATAOUT3=$top_builddir/test/data/$TESTFILE.tz2.xml
+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
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/tools/Makefile.am b/tools/Makefile.am
index 67b7dd7..5883286 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,7 +1,14 @@
-AM_CFLAGS = $(GLOBAL_CFLAGS) -I$(top_srcdir)/include
+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.la
+plistutil_LDADD = $(top_builddir)/src/libplist-2.0.la
+
+endif
diff --git a/tools/plistutil.c b/tools/plistutil.c
index c7f1a3f..7e4eb67 100644
--- a/tools/plistutil.c
+++ b/tools/plistutil.c
@@ -2,8 +2,9 @@
* plistutil.c
* Simple tool to convert a plist into different formats
*
- * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved.
- * Copyright (c) 2008 Zach C. All Rights Reserved.
+ * 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
@@ -20,6 +21,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
#include "plist/plist.h"
@@ -28,39 +32,59 @@
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
+#ifndef _MSC_VER
#include <unistd.h>
-
-#define BUF_SIZE 2048 // Seems to be a decent start to cover most stdin files
+#endif
#ifdef _MSC_VER
#pragma warning(disable:4996)
+#define STDIN_FILENO _fileno(stdin)
#endif
typedef struct _options
{
char *in_file, *out_file;
- uint8_t debug, in_fmt, out_fmt; // fmts 0 = undef, 1 = bin, 2 = xml, 3 = json someday
+ 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)
static void print_usage(int argc, char *argv[])
{
char *name = NULL;
name = strrchr(argv[0], '/');
- printf("Usage: %s -i|--infile FILE [-o|--outfile FILE] [-d|--debug]\n", (name ? name + 1: argv[0]));
- printf("Convert a plist FILE from binary to XML format or vice-versa.\n\n");
+ 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 [bin|xml] Force output format, regardless of input type\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(" -c, --compact JSON and OpenStep only: Print output in compact form.\n");
+ printf(" By default, the output will be pretty-printed.\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[])
{
int i = 0;
- options_t *options = (options_t *) malloc(sizeof(options_t));
- memset(options, 0, sizeof(options_t));
+ options_t *options = (options_t*)calloc(1, sizeof(options_t));
options->out_fmt = 0;
for (i = 1; i < argc; i++)
@@ -76,8 +100,7 @@ static options_t *parse_arguments(int argc, char *argv[])
i++;
continue;
}
-
- if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o"))
+ else if (!strcmp(argv[i], "--outfile") || !strcmp(argv[i], "-o"))
{
if ((i + 1) == argc)
{
@@ -88,8 +111,7 @@ static options_t *parse_arguments(int argc, char *argv[])
i++;
continue;
}
-
- if (!strcmp(argv[i], "--format") || !strcmp(argv[i], "-f"))
+ else if (!strcmp(argv[i], "--format") || !strcmp(argv[i], "-f"))
{
if ((i + 1) == argc)
{
@@ -97,28 +119,69 @@ static options_t *parse_arguments(int argc, char *argv[])
return NULL;
}
if (!strncmp(argv[i+1], "bin", 3)) {
- options->out_fmt = 1;
+ options->out_fmt = PLIST_FORMAT_BINARY;
} else if (!strncmp(argv[i+1], "xml", 3)) {
- options->out_fmt = 2;
+ options->out_fmt = PLIST_FORMAT_XML;
+ } else if (!strncmp(argv[i+1], "json", 4)) {
+ options->out_fmt = PLIST_FORMAT_JSON;
+ } else if (!strncmp(argv[i+1], "openstep", 8) || !strncmp(argv[i+1], "ostep", 5)) {
+ options->out_fmt = PLIST_FORMAT_OSTEP;
} else {
- printf("ERROR: Unsupported output format\n");
+ fprintf(stderr, "ERROR: Unsupported output format\n");
free(options);
return NULL;
}
i++;
continue;
}
-
- if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d"))
+ else if (!strcmp(argv[i], "--compact") || !strcmp(argv[i], "-c"))
{
- options->debug = 1;
+ options->flags |= OPT_COMPACT;
}
-
- if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
+ else if (!strcmp(argv[i], "--sort") || !strcmp(argv[i], "-s"))
+ {
+ options->flags |= OPT_SORT;
+ }
+ else if (!strcmp(argv[i], "--print") || !strcmp(argv[i], "-p"))
+ {
+ if ((i + 1) == argc)
+ {
+ free(options);
+ return NULL;
+ }
+ options->in_file = argv[i + 1];
+ 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;
+ }
+ }
+ i++;
+ continue;
+ }
+ else if (!strcmp(argv[i], "--debug") || !strcmp(argv[i], "-d"))
+ {
+ options->flags |= OPT_DEBUG;
+ }
+ else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
{
free(options);
return NULL;
}
+ else if (!strcmp(argv[i], "--version") || !strcmp(argv[i], "-v"))
+ {
+ printf("plistutil %s\n", libplist_version());
+ exit(EXIT_SUCCESS);
+ }
+ else
+ {
+ fprintf(stderr, "ERROR: Invalid option '%s'\n", argv[i]);
+ free(options);
+ return NULL;
+ }
}
return options;
@@ -126,11 +189,15 @@ static options_t *parse_arguments(int argc, char *argv[])
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;
- int read_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);
@@ -141,26 +208,32 @@ int main(int argc, char *argv[])
return 0;
}
+ 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) * BUF_SIZE);
+ read_size = 0;
+ plist_entire = malloc(sizeof(char) * read_capacity);
if(plist_entire == NULL)
{
- printf("ERROR: Failed to allocate buffer to read from stdin");
- free(options);
+ fprintf(stderr, "ERROR: Failed to allocate buffer to read from stdin");
+ free(options);
return 1;
}
- plist_entire[read_size] = '\0';
+ plist_entire[read_size] = '\0';
char ch;
- while(read(STDIN_FILENO, &ch, 1) > 0)
+ while(read(STDIN_FILENO, &ch, 1) > 0)
{
- if (read_size >= BUF_SIZE) {
+ if (read_size >= read_capacity) {
char *old = plist_entire;
- plist_entire = realloc(plist_entire, sizeof(char) * (read_size + 1));
+ read_capacity += 4096;
+ plist_entire = realloc(plist_entire, sizeof(char) * read_capacity);
if (plist_entire == NULL)
{
- printf("ERROR: Failed to reallocate stdin buffer\n");
+ fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
free(old);
free(options);
return 1;
@@ -169,30 +242,34 @@ int main(int argc, char *argv[])
plist_entire[read_size] = ch;
read_size++;
}
+ if (read_size >= read_capacity) {
+ char *old = plist_entire;
+ plist_entire = realloc(plist_entire, sizeof(char) * (read_capacity+1));
+ if (plist_entire == NULL)
+ {
+ fprintf(stderr, "ERROR: Failed to reallocate stdin buffer\n");
+ free(old);
+ free(options);
+ return 1;
+ }
+ }
plist_entire[read_size] = '\0';
- // Not positive we need this, but it doesnt seem to hurt lol
+ // Not positive we need this, but it doesnt seem to hurt lol
if(ferror(stdin))
{
- printf("ERROR: reading from stdin.\n");
+ fprintf(stderr, "ERROR: reading from stdin.\n");
free(plist_entire);
free(options);
return 1;
}
-
- if (read_size < 8) {
- printf("ERROR: Input file is too small to contain valid plist data.\n");
- free(plist_entire);
- free(options);
- return 1;
- }
}
else
{
- // read input file
- iplist = fopen(options->in_file, "rb");
+ // read input file
+ iplist = fopen(options->in_file, "rb");
if (!iplist) {
- printf("ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
+ fprintf(stderr, "ERROR: Could not open input file '%s': %s\n", options->in_file, strerror(errno));
free(options);
return 1;
}
@@ -200,55 +277,56 @@ int main(int argc, char *argv[])
memset(&filestats, '\0', sizeof(struct stat));
fstat(fileno(iplist), &filestats);
- if (filestats.st_size < 8) {
- printf("ERROR: Input file is too small to contain valid plist data.\n");
- free(options);
- fclose(iplist);
- return -1;
- }
-
plist_entire = (char *) malloc(sizeof(char) * (filestats.st_size + 1));
read_size = fread(plist_entire, sizeof(char), filestats.st_size, iplist);
+ plist_entire[read_size] = '\0';
fclose(iplist);
}
if (options->out_fmt == 0) {
- // convert from binary to xml or vice-versa<br>
+ // convert from binary to xml or vice-versa
if (plist_is_binary(plist_entire, read_size))
{
- plist_from_bin(plist_entire, read_size, &root_node);
- plist_to_xml(root_node, &plist_out, &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
{
- plist_from_xml(plist_entire, read_size, &root_node);
- plist_to_bin(root_node, &plist_out, &size);
+ 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
{
- if (options->out_fmt == 1) {
- if (plist_is_binary(plist_entire, read_size))
- {
- plist_out = malloc(sizeof(char) * read_size);
- memcpy(plist_out, plist_entire, read_size);
- size = read_size;
+ input_res = plist_from_memory(plist_entire, read_size, &root_node, NULL);
+ if (input_res == PLIST_ERR_SUCCESS) {
+ if (options->flags & OPT_SORT) {
+ plist_sort(root_node);
}
- else
- {
- plist_from_xml(plist_entire, read_size, &root_node);
- plist_to_bin(root_node, &plist_out, &size);
- }
- } else if (options->out_fmt == 2) {
- if (plist_is_binary(plist_entire, read_size)) {
- plist_from_bin(plist_entire, read_size, &root_node);
- plist_to_xml(root_node, &plist_out, &size);
- }
- else
- {
- plist_out = malloc(sizeof(char) * read_size);
- memcpy(plist_out, plist_entire, read_size);
- size = read_size;
+ 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) {
+ output_res = plist_to_json(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT));
+ } else if (options->out_fmt == PLIST_FORMAT_OSTEP) {
+ output_res = plist_to_openstep(root_node, &plist_out, &size, !(options->flags & OPT_COMPACT));
+ } 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;
}
}
}
@@ -257,11 +335,11 @@ int main(int argc, char *argv[])
if (plist_out)
{
- if (options->out_file != NULL && strcmp(options->out_file, "-"))
+ if (options->out_file != NULL && strcmp(options->out_file, "-") != 0)
{
FILE *oplist = fopen(options->out_file, "wb");
if (!oplist) {
- printf("ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
+ fprintf(stderr, "ERROR: Could not open output file '%s': %s\n", options->out_file, strerror(errno));
free(options);
return 1;
}
@@ -274,9 +352,36 @@ int main(int argc, char *argv[])
free(plist_out);
}
- else
- printf("ERROR: Failed to convert input file.\n");
+
+ if (input_res == PLIST_ERR_SUCCESS) {
+ switch (output_res) {
+ case PLIST_ERR_SUCCESS:
+ 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;
+ }
+ } 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;
+ default:
+ fprintf(stderr, "ERROR: Could not parse plist data (%d)\n", input_res);
+ ret = 1;
+ break;
+ }
+ }
free(options);
- return 0;
+ return ret;
}