瀏覽代碼

Feat(Depend): Merge compilation

viest 6 年之前
父節點
當前提交
8ceca55f7b
共有 38 個文件被更改,包括 29149 次插入42 次删除
  1. 164 0
      .appveyor.yml
  2. 31 33
      config.m4
  3. 8 9
      config.w32
  4. 150 0
      library/License.txt
  5. 443 0
      library/src/app.c
  6. 6403 0
      library/src/chart.c
  7. 345 0
      library/src/content_types.c
  8. 293 0
      library/src/core.c
  9. 224 0
      library/src/custom.c
  10. 746 0
      library/src/drawing.c
  11. 728 0
      library/src/format.c
  12. 223 0
      library/src/hash_table.c
  13. 961 0
      library/src/packager.c
  14. 245 0
      library/src/relationships.c
  15. 266 0
      library/src/shared_strings.c
  16. 1088 0
      library/src/styles.c
  17. 348 0
      library/src/theme.c
  18. 555 0
      library/src/utility.c
  19. 1913 0
      library/src/workbook.c
  20. 5739 0
      library/src/worksheet.c
  21. 355 0
      library/src/xmlwriter.c
  22. 5 0
      library/third_party/minizip/README.txt
  23. 131 0
      library/third_party/minizip/crypt.h
  24. 247 0
      library/third_party/minizip/ioapi.c
  25. 208 0
      library/third_party/minizip/ioapi.h
  26. 456 0
      library/third_party/minizip/iowin32.c
  27. 28 0
      library/third_party/minizip/iowin32.h
  28. 7 0
      library/third_party/minizip/iowin32.loT
  29. 660 0
      library/third_party/minizip/miniunz.c
  30. 520 0
      library/third_party/minizip/minizip.c
  31. 291 0
      library/third_party/minizip/mztools.c
  32. 37 0
      library/third_party/minizip/mztools.h
  33. 2125 0
      library/third_party/minizip/unzip.c
  34. 437 0
      library/third_party/minizip/unzip.h
  35. 2007 0
      library/third_party/minizip/zip.c
  36. 367 0
      library/third_party/minizip/zip.h
  37. 342 0
      library/third_party/tmpfileplus/tmpfileplus.c
  38. 53 0
      library/third_party/tmpfileplus/tmpfileplus.h

+ 164 - 0
.appveyor.yml

@@ -0,0 +1,164 @@
+version: "{branch}.build.{build}"
+skip_tags: true
+
+branches:
+        only:
+                - master
+                - dev
+
+clone_folder:  c:\projects\xlswriter
+
+install:
+        ps: |
+                if (-not (Test-Path c:\build-cache)) {
+                        mkdir c:\build-cache
+                }
+                $bname = 'php-sdk-' + $env:BIN_SDK_VER + '.zip'
+                if (-not (Test-Path c:\build-cache\$bname)) {
+                        Invoke-WebRequest "https://github.com/OSTC/php-sdk-binary-tools/archive/$bname" -OutFile "c:\build-cache\$bname"
+                }
+                $dname0 = 'php-sdk-binary-tools-php-sdk-' + $env:BIN_SDK_VER
+                $dname1 = 'php-sdk-' + $env:BIN_SDK_VER
+                if (-not (Test-Path c:\build-cache\$dname1)) {
+                        7z x c:\build-cache\$bname -oc:\build-cache
+                        move c:\build-cache\$dname0 c:\build-cache\$dname1
+                }
+
+cache:
+        c:\build-cache -> .appveyor.yml
+
+environment:
+        BIN_SDK_VER: 2.1.2
+        LIBCMARK_VER: 0.28.3
+        matrix:
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x64
+                  VC: vc14
+                  PHP_VER: 7.0.28
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x64
+                  VC: vc14
+                  PHP_VER: 7.0.28
+                  TS: 1
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x86
+                  VC: vc14
+                  PHP_VER: 7.0.28
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x86
+                  VC: vc14
+                  PHP_VER: 7.0.28
+                  TS: 1
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x64
+                  VC: vc14
+                  PHP_VER: 7.1.15
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x64
+                  VC: vc14
+                  PHP_VER: 7.1.15
+                  TS: 1
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x86
+                  VC: vc14
+                  PHP_VER: 7.1.15
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
+                  ARCH: x86
+                  VC: vc14
+                  PHP_VER: 7.1.15
+                  TS: 1
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+                  ARCH: x64
+                  VC: vc15
+                  PHP_VER: 7.2.3
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+                  ARCH: x64
+                  VC: vc15
+                  PHP_VER: 7.2.3
+                  TS: 1
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+                  ARCH: x86
+                  VC: vc15
+                  PHP_VER: 7.2.3
+                  TS: 0
+                - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
+                  ARCH: x86
+                  VC: vc15
+                  PHP_VER: 7.2.3
+                  TS: 1
+
+build_script:
+        ps: |
+                $ts_part = ''
+                if ('0' -eq $env:TS) { $ts_part = '-nts' }
+                $bname = 'php-devel-pack-' + $env:PHP_VER + $ts_part + '-Win32-' + $env:VC.toUpper() + '-' + $env:ARCH + '.zip'
+                if (-not (Test-Path c:\build-cache\$bname)) {
+                        Invoke-WebRequest "http://windows.php.net/downloads/releases/archives/$bname" -OutFile "c:\build-cache\$bname"
+                        if (-not (Test-Path c:\build-cache\$bname)) {
+                                Invoke-WebRequest "http://windows.php.net/downloads/releases/$bname" -OutFile "c:\build-cache\$bname"
+                        }
+                }
+                $dname0 = 'php-' + $env:PHP_VER + '-devel-' + $env:VC.toUpper() + '-' + $env:ARCH
+                $dname1 = 'php-' + $env:PHP_VER + $ts_part + '-devel-' + $env:VC.toUpper() + '-' + $env:ARCH
+                if (-not (Test-Path c:\build-cache\$dname1)) {
+                        7z x c:\build-cache\$bname -oc:\build-cache
+                        move c:\build-cache\$dname0 c:\build-cache\$dname1
+                }
+                cd c:\projects\xlswriter
+                $env:PATH = 'c:\build-cache\' + $dname1 + ';' + $env:PATH
+                #echo "@echo off" | Out-File -Encoding "ASCII" task.bat
+                #echo "" | Out-File -Encoding "ASCII" -Append task.bat
+                echo "" | Out-File -Encoding "ASCII" task.bat
+                echo "call phpize 2>&1" | Out-File -Encoding "ASCII" -Append task.bat
+                $conf_cmd = 'call configure --with-xlswriter 2>&1'
+                echo $conf_cmd | Out-File -Encoding "ASCII" -Append task.bat
+                echo "nmake /nologo 2>&1" | Out-File -Encoding "ASCII" -Append task.bat
+                echo "exit %errorlevel%" | Out-File -Encoding "ASCII" -Append task.bat
+                $here = (Get-Item -Path "." -Verbose).FullName
+                $runner = 'c:\build-cache\php-sdk-' + $env:BIN_SDK_VER + '\phpsdk' + '-' + $env:VC + '-' + $env:ARCH + '.bat'
+                $task = $here + '\task.bat'
+                & $runner -t $task
+
+after_build:
+        ps: |
+                $ts_part = 'ts'
+                if ('0' -eq $env:TS) { $ts_part = 'nts' }
+                $zip_bname = 'php_xlswriter-' + $env:APPVEYOR_REPO_COMMIT.substring(0, 8) + '-' + $env:PHP_VER.substring(0, 3) + '-' + $ts_part + '-' + $env:VC + '-' + $env:ARCH + '.zip'
+                $dir = 'c:\projects\xlswriter\';
+                if ('x64' -eq $env:ARCH) { $dir = $dir + 'x64\' }
+                $dir = $dir + 'Release'
+                if ('1' -eq $env:TS) { $dir = $dir + '_TS' }
+                & 7z a c:\$zip_bname $dir\php_xlswriter.dll c:\projects\xlswriter\LICENSE
+                Push-AppveyorArtifact c:\$zip_bname
+
+test_script:
+        ps: |
+                $ts_part = ''
+                if ('0' -eq $env:TS) { $ts_part = '-nts' }
+                $bname = 'php-' + $env:PHP_VER + $ts_part + '-Win32-' + $env:VC.toUpper() + '-' + $env:ARCH + '.zip'
+                if (-not (Test-Path c:\build-cache\$bname)) {
+                        Invoke-WebRequest "http://windows.php.net/downloads/releases/archives/$bname" -OutFile "c:\build-cache\$bname"
+                        if (-not (Test-Path c:\build-cache\$bname)) {
+                                Invoke-WebRequest "http://windows.php.net/downloads/releases/$bname" -OutFile "c:\build-cache\$bname"
+                        }
+                }
+                $dname = 'php-' + $env:PHP_VER + $ts_part + '-' + $env:VC.toUpper() + '-' + $env:ARCH
+                if (-not (Test-Path c:\build-cache\$dname)) {
+                        7z x c:\build-cache\$bname -oc:\build-cache\$dname
+                }
+                cd c:\projects\xlswriter
+                echo "" | Out-File -Encoding "ASCII" task.bat
+                echo "set REPORT_EXIT_STATUS=1" | Out-File -Encoding "ASCII" -Append task.bat
+                $cmd = 'call configure --with-xlswriter --with-prefix=c:\build-cache\' + $dname + ' 2>&1'
+                echo $cmd | Out-File -Encoding "ASCII" -Append task.bat
+                echo 'nmake /nologo test TESTS="-q --show-diff --set-timeout 120" 2>&1' | Out-File -Encoding "ASCII" -Append task.bat
+                echo "exit %errorlevel%" | Out-File -Encoding "ASCII" -Append task.bat
+                $here = (Get-Item -Path "." -Verbose).FullName
+                $runner = 'c:\build-cache\php-sdk-' + $env:BIN_SDK_VER + '\phpsdk' + '-' + $env:VC + '-' + $env:ARCH + '.bat'
+                $task = $here + '\task.bat'
+                & $runner -t $task

+ 31 - 33
config.m4

@@ -2,47 +2,45 @@ PHP_ARG_WITH(xlsxwriter, xlswriter support,
 [  --with-xlswriter           Include xlswriter support])
 
 if test "$PHP_XLSWRITER" != "no"; then
-    xls_writer_sources="xls_writer.c \
+    xls_writer_sources="
+    library/third_party/minizip/ioapi.c \
+    library/third_party/minizip/mztools.c \
+    library/third_party/minizip/unzip.c \
+    library/third_party/minizip/zip.c \
+
+    library/third_party/tmpfileplus/tmpfileplus.c \
+
+    library/src/app.c \
+    library/src/chart.c \
+    library/src/content_types.c \
+    library/src/core.c \
+    library/src/custom.c \
+    library/src/drawing.c \
+    library/src/format.c \
+    library/src/hash_table.c \
+    library/src/packager.c \
+    library/src/relationships.c \
+    library/src/shared_strings.c \
+    library/src/styles.c \
+    library/src/theme.c \
+    library/src/utility.c \
+    library/src/workbook.c \
+    library/src/worksheet.c \
+    library/src/xmlwriter.c \
+
+    xls_writer.c \
     kernel/exception.c \
     kernel/resource.c \
     kernel/common.c \
-    "
-
-    AC_MSG_CHECKING([Check libxlsxwriter support])
-
-    for i in /usr/local /usr; do
-        if test -r $i/include/xlsxwriter.h; then
-            AC_MSG_CHECKING([Check libxlsxwriter library])
-            XLSXWRITER_DIR=$i
-            PHP_ADD_INCLUDE($i/include)
-            PHP_CHECK_LIBRARY(xlsxwriter, worksheet_write_string,
-            [
-                PHP_ADD_LIBRARY_WITH_PATH(xlsxwriter, $i/$PHP_LIBDIR, XLSWRITER_SHARED_LIBADD)
-                xls_writer_sources="$xls_writer_sources \
-                kernel/excel.c \
-                kernel/write.c \
-                kernel/format.c"
-            ],[
-                AC_MSG_ERROR([Wrong libxlsxwriter version or library not found])
-            ],[
-                -L$i/$PHP_LIBDIR -lm
-            ])
-
-            break
-        else
-            AC_MSG_RESULT([no, found in $i])
-        fi
-    done
-
-    if test -z "$XLSXWRITER_DIR"; then
-        AC_MSG_ERROR([libxlsxwriter library not found])
-    fi
+    kernel/excel.c \
+    kernel/write.c \
+    kernel/format.c"
 
     if test -z "$PHP_DEBUG"; then
         AC_ARG_ENABLE(debug, [--enable-debug compile with debugging system], [PHP_DEBUG=$enableval],[PHP_DEBUG=no])
     fi
 
-    PHP_SUBST(XLSWRITER_SHARED_LIBADD)
+    PHP_ADD_INCLUDE($XLSWRITER_DIR/include)
 
     PHP_NEW_EXTENSION(xlswriter, $xls_writer_sources, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
 

+ 8 - 9
config.w32

@@ -4,14 +4,13 @@ ARG_WITH("xlswriter", "xlswriter support", "no");
 
 if (PHP_XLSWRITER != "no") {
 
-    if (CHECK_LIB("xlsxwriter.lib;xlsxwriter_a.lib", "xlswriter", PHP_XLSWRITER) &&
-        CHECK_HEADER_ADD_INCLUDE("xlsxwriter.h", "CFLAGS_XLSWRITER", PHP_PHP_BUILD + "\\include;" + PHP_XLSWRITER) &&
-        CHECK_HEADER_ADD_INCLUDE("xlsxwriter/packager.h", "CFLAGS_XLSWRITER", PHP_PHP_BUILD + "\\include;" + PHP_XLSWRITER) &&
-        CHECK_HEADER_ADD_INCLUDE("xlsxwriter/format.h", "CFLAGS_XLSWRITER", PHP_PHP_BUILD + "\\include;" + PHP_XLSWRITER)) {
-        EXTENSION("xlswriter", "xls_writer.c")
-        ADD_SOURCES(configure_module_dirname + "\\kernel", "common.c resource.c exception.c excel.c write.c format.c", "xlswriter");
-    } else {
-        WARNING("xlswriter not enabled, xlsxwriter.lib or headers not found");
-    }
+    EXTENSION("xlswriter", "xls_writer.c")
+
+    ADD_FLAG("CFLAGS_XLSWRITER", "/I " + configure_module_dirname);
+
+    ADD_SOURCES(configure_module_dirname + "\\library\\third_party\\minizip", "ioapi.c iowin32.c miniunz.c minizip.c mztools.c unzip.c zip.c", "xlswriter");
+    ADD_SOURCES(configure_module_dirname + "\\library\\third_party\\tmpfileplus", "tmpfileplus.c", "xlswriter");
+    ADD_SOURCES(configure_module_dirname + "\\library\\src", "app.c chart.c content_types.c core.c custom.c drawing.c format.c hash_table.c packager.c relationships.c shared_strings.c styles.c theme.c utility.c workbook.c worksheet.c xmlwriter.c", "xlswriter");
+    ADD_SOURCES(configure_module_dirname + "\\kernel", "common.c resource.c exception.c excel.c write.c format.c", "xlswriter");
 
 }

+ 150 - 0
library/License.txt

@@ -0,0 +1,150 @@
+/**
+
+@page license License
+
+Libxlsxwriter is released under a FreeBSD license:
+
+    Copyright 2014-2018, John McNamara <[email protected]>
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    The views and conclusions contained in the software and documentation are
+    those of the authors and should not be interpreted as representing
+    official policies, either expressed or implied, of the FreeBSD Project.
+
+
+Libxlsxwriter includes `queue.h` and `tree.h` from FreeBSD, the `minizip`
+component of `zlib` and `tmpfileplus` which have the following licenses:
+
+
+Queue.h from FreeBSD:
+
+    Copyright (c) 1991, 1993
+     The Regents of the University of California.  All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+    4. Neither the name of the University nor the names of its contributors
+       may be used to endorse or promote products derived from this software
+       without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+
+
+Tree.h from FreeBSD:
+
+    Copyright 2002 Niels Provos <[email protected]>
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+The `minizip` files used in the libxlsxwriter source tree are taken from the
+`zlib` ` contrib/minizip` directory. [Zlib](http://www.zlib.net) has the
+following License/Copyright:
+
+    (C) 1995-2013 Jean-loup Gailly and Mark Adler
+
+    This software is provided 'as-is', without any express or implied
+    warranty.  In no event will the authors be held liable for any damages
+    arising from the use of this software.
+
+    Permission is granted to anyone to use this software for any purpose,
+    including commercial applications, and to alter it and redistribute it
+    freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+       claim that you wrote the original software. If you use this software
+       in a product, an acknowledgment in the product documentation would be
+       appreciated but is not required.
+    2. Altered source versions must be plainly marked as such, and must not be
+       misrepresented as being the original software.
+    3. This notice may not be removed or altered from any source distribution.
+
+    Jean-loup Gailly        Mark Adler
+    [email protected]          [email protected]
+
+The `minizip` files have the following additional copyright declarations:
+
+    Copyright (C) 1998-2010 Gilles Vollant
+    (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+    Modifications for Zip64 support
+    Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+Note, it is possible to compile libxlsxwriter without statically linking the
+`minizip` files and instead dynamically linking to `lminizip`, see
+@ref gsg_minizip.
+
+[Tmpfileplus](http://www.di-mgt.com.au/c_function_to_create_temp_file.html)
+has the following license:
+
+     This Source Code Form is subject to the terms of the Mozilla Public
+     License, v. 2.0. If a copy of the MPL was not distributed with this
+     file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+     Copyright (c) 2012-16 David Ireland, DI Management Services Pty Ltd
+     <http://www.di-mgt.com.au/contact/>.
+
+See the [Mozilla Public License, v. 2.0](http://mozilla.org/MPL/2.0/).
+
+Note, it is possible to compile libxlsxwriter using the standard library
+`tmpfile()` function instead of `tmpfileplus`, see @ref gsg_tmpdir.
+
+Next: @ref changes
+*/

+ 443 - 0
library/src/app.c

@@ -0,0 +1,443 @@
+/*****************************************************************************
+ * app - A library for creating Excel XLSX app files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/app.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new app object.
+ */
+lxw_app *
+lxw_app_new()
+{
+    lxw_app *app = calloc(1, sizeof(lxw_app));
+    GOTO_LABEL_ON_MEM_ERROR(app, mem_error);
+
+    app->heading_pairs = calloc(1, sizeof(struct lxw_heading_pairs));
+    GOTO_LABEL_ON_MEM_ERROR(app->heading_pairs, mem_error);
+    STAILQ_INIT(app->heading_pairs);
+
+    app->part_names = calloc(1, sizeof(struct lxw_part_names));
+    GOTO_LABEL_ON_MEM_ERROR(app->part_names, mem_error);
+    STAILQ_INIT(app->part_names);
+
+    return app;
+
+mem_error:
+    lxw_app_free(app);
+    return NULL;
+}
+
+/*
+ * Free a app object.
+ */
+void
+lxw_app_free(lxw_app *app)
+{
+    lxw_heading_pair *heading_pair;
+    lxw_part_name *part_name;
+
+    if (!app)
+        return;
+
+    /* Free the lists in the App object. */
+    if (app->heading_pairs) {
+        while (!STAILQ_EMPTY(app->heading_pairs)) {
+            heading_pair = STAILQ_FIRST(app->heading_pairs);
+            STAILQ_REMOVE_HEAD(app->heading_pairs, list_pointers);
+            free(heading_pair->key);
+            free(heading_pair->value);
+            free(heading_pair);
+        }
+        free(app->heading_pairs);
+    }
+
+    if (app->part_names) {
+        while (!STAILQ_EMPTY(app->part_names)) {
+            part_name = STAILQ_FIRST(app->part_names);
+            STAILQ_REMOVE_HEAD(app->part_names, list_pointers);
+            free(part_name->name);
+            free(part_name);
+        }
+        free(app->part_names);
+    }
+
+    free(app);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_app_xml_declaration(lxw_app *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <Properties> element.
+ */
+STATIC void
+_write_properties(lxw_app *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns[] = LXW_SCHEMA_OFFICEDOC "/extended-properties";
+    char xmlns_vt[] = LXW_SCHEMA_OFFICEDOC "/docPropsVTypes";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:vt", xmlns_vt);
+
+    lxw_xml_start_tag(self->file, "Properties", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <Application> element.
+ */
+STATIC void
+_write_application(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "Application", "Microsoft Excel", NULL);
+}
+
+/*
+ * Write the <DocSecurity> element.
+ */
+STATIC void
+_write_doc_security(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "DocSecurity", "0", NULL);
+}
+
+/*
+ * Write the <ScaleCrop> element.
+ */
+STATIC void
+_write_scale_crop(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "ScaleCrop", "false", NULL);
+}
+
+/*
+ * Write the <vt:lpstr> element.
+ */
+STATIC void
+_write_vt_lpstr(lxw_app *self, const char *str)
+{
+    lxw_xml_data_element(self->file, "vt:lpstr", str, NULL);
+}
+
+/*
+ * Write the <vt:i4> element.
+ */
+STATIC void
+_write_vt_i4(lxw_app *self, const char *value)
+{
+    lxw_xml_data_element(self->file, "vt:i4", value, NULL);
+}
+
+/*
+ * Write the <vt:variant> element.
+ */
+STATIC void
+_write_vt_variant(lxw_app *self, const char *key, const char *value)
+{
+    /* Write the vt:lpstr element. */
+    lxw_xml_start_tag(self->file, "vt:variant", NULL);
+    _write_vt_lpstr(self, key);
+    lxw_xml_end_tag(self->file, "vt:variant");
+
+    /* Write the vt:i4 element. */
+    lxw_xml_start_tag(self->file, "vt:variant", NULL);
+    _write_vt_i4(self, value);
+    lxw_xml_end_tag(self->file, "vt:variant");
+}
+
+/*
+ * Write the <vt:vector> element for the heading pairs.
+ */
+STATIC void
+_write_vt_vector_heading_pairs(lxw_app *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_heading_pair *heading_pair;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("size", self->num_heading_pairs * 2);
+    LXW_PUSH_ATTRIBUTES_STR("baseType", "variant");
+
+    lxw_xml_start_tag(self->file, "vt:vector", &attributes);
+
+    STAILQ_FOREACH(heading_pair, self->heading_pairs, list_pointers) {
+        _write_vt_variant(self, heading_pair->key, heading_pair->value);
+    }
+
+    lxw_xml_end_tag(self->file, "vt:vector");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <vt:vector> element for the named parts.
+ */
+STATIC void
+_write_vt_vector_lpstr_named_parts(lxw_app *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_part_name *part_name;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("size", self->num_part_names);
+    LXW_PUSH_ATTRIBUTES_STR("baseType", "lpstr");
+
+    lxw_xml_start_tag(self->file, "vt:vector", &attributes);
+
+    STAILQ_FOREACH(part_name, self->part_names, list_pointers) {
+        _write_vt_lpstr(self, part_name->name);
+    }
+
+    lxw_xml_end_tag(self->file, "vt:vector");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <HeadingPairs> element.
+ */
+STATIC void
+_write_heading_pairs(lxw_app *self)
+{
+    lxw_xml_start_tag(self->file, "HeadingPairs", NULL);
+
+    /* Write the vt:vector element. */
+    _write_vt_vector_heading_pairs(self);
+
+    lxw_xml_end_tag(self->file, "HeadingPairs");
+}
+
+/*
+ * Write the <TitlesOfParts> element.
+ */
+STATIC void
+_write_titles_of_parts(lxw_app *self)
+{
+    lxw_xml_start_tag(self->file, "TitlesOfParts", NULL);
+
+    /* Write the vt:vector element. */
+    _write_vt_vector_lpstr_named_parts(self);
+
+    lxw_xml_end_tag(self->file, "TitlesOfParts");
+}
+
+/*
+ * Write the <Manager> element.
+ */
+STATIC void
+_write_manager(lxw_app *self)
+{
+    lxw_doc_properties *properties = self->properties;
+
+    if (!properties)
+        return;
+
+    if (properties->manager)
+        lxw_xml_data_element(self->file, "Manager", properties->manager,
+                             NULL);
+}
+
+/*
+ * Write the <Company> element.
+ */
+STATIC void
+_write_company(lxw_app *self)
+{
+    lxw_doc_properties *properties = self->properties;
+
+    if (properties && properties->company)
+        lxw_xml_data_element(self->file, "Company", properties->company,
+                             NULL);
+    else
+        lxw_xml_data_element(self->file, "Company", "", NULL);
+}
+
+/*
+ * Write the <LinksUpToDate> element.
+ */
+STATIC void
+_write_links_up_to_date(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "LinksUpToDate", "false", NULL);
+}
+
+/*
+ * Write the <SharedDoc> element.
+ */
+STATIC void
+_write_shared_doc(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "SharedDoc", "false", NULL);
+}
+
+/*
+ * Write the <HyperlinkBase> element.
+ */
+STATIC void
+_write_hyperlink_base(lxw_app *self)
+{
+    lxw_doc_properties *properties = self->properties;
+
+    if (!properties)
+        return;
+
+    if (properties->hyperlink_base)
+        lxw_xml_data_element(self->file, "HyperlinkBase",
+                             properties->hyperlink_base, NULL);
+}
+
+/*
+ * Write the <HyperlinksChanged> element.
+ */
+STATIC void
+_write_hyperlinks_changed(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "HyperlinksChanged", "false", NULL);
+}
+
+/*
+ * Write the <AppVersion> element.
+ */
+STATIC void
+_write_app_version(lxw_app *self)
+{
+    lxw_xml_data_element(self->file, "AppVersion", "12.0000", NULL);
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_app_assemble_xml_file(lxw_app *self)
+{
+
+    /* Write the XML declaration. */
+    _app_xml_declaration(self);
+
+    _write_properties(self);
+    _write_application(self);
+    _write_doc_security(self);
+    _write_scale_crop(self);
+    _write_heading_pairs(self);
+    _write_titles_of_parts(self);
+    _write_manager(self);
+    _write_company(self);
+    _write_links_up_to_date(self);
+    _write_shared_doc(self);
+    _write_hyperlink_base(self);
+    _write_hyperlinks_changed(self);
+    _write_app_version(self);
+
+    lxw_xml_end_tag(self->file, "Properties");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Add the name of a workbook Part such as 'Sheet1' or 'Print_Titles'.
+ */
+void
+lxw_app_add_part_name(lxw_app *self, const char *name)
+{
+    lxw_part_name *part_name;
+
+    if (!name)
+        return;
+
+    part_name = calloc(1, sizeof(lxw_part_name));
+    GOTO_LABEL_ON_MEM_ERROR(part_name, mem_error);
+
+    part_name->name = lxw_strdup(name);
+    GOTO_LABEL_ON_MEM_ERROR(part_name->name, mem_error);
+
+    STAILQ_INSERT_TAIL(self->part_names, part_name, list_pointers);
+    self->num_part_names++;
+
+    return;
+
+mem_error:
+    if (part_name) {
+        free(part_name->name);
+        free(part_name);
+    }
+}
+
+/*
+ * Add the name of a workbook Heading Pair such as 'Worksheets', 'Charts' or
+ * 'Named Ranges'.
+ */
+void
+lxw_app_add_heading_pair(lxw_app *self, const char *key, const char *value)
+{
+    lxw_heading_pair *heading_pair;
+
+    if (!key || !value)
+        return;
+
+    heading_pair = calloc(1, sizeof(lxw_heading_pair));
+    GOTO_LABEL_ON_MEM_ERROR(heading_pair, mem_error);
+
+    heading_pair->key = lxw_strdup(key);
+    GOTO_LABEL_ON_MEM_ERROR(heading_pair->key, mem_error);
+
+    heading_pair->value = lxw_strdup(value);
+    GOTO_LABEL_ON_MEM_ERROR(heading_pair->value, mem_error);
+
+    STAILQ_INSERT_TAIL(self->heading_pairs, heading_pair, list_pointers);
+    self->num_heading_pairs++;
+
+    return;
+
+mem_error:
+    if (heading_pair) {
+        free(heading_pair->key);
+        free(heading_pair->value);
+        free(heading_pair);
+    }
+}

+ 6403 - 0
library/src/chart.c

@@ -0,0 +1,6403 @@
+/*****************************************************************************
+ * chart - A library for creating Excel XLSX chart files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/chart.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+STATIC void _chart_initialize(lxw_chart *self, uint8_t type);
+STATIC void _chart_axis_set_default_num_format(lxw_chart_axis *axis,
+                                               char *num_format);
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Free a series range object.
+ */
+STATIC void
+_chart_free_range(lxw_series_range *range)
+{
+    struct lxw_series_data_point *data_point;
+
+    if (!range)
+        return;
+
+    if (range->data_cache) {
+        while (!STAILQ_EMPTY(range->data_cache)) {
+            data_point = STAILQ_FIRST(range->data_cache);
+            free(data_point->string);
+            STAILQ_REMOVE_HEAD(range->data_cache, list_pointers);
+
+            free(data_point);
+        }
+        free(range->data_cache);
+    }
+
+    free(range->formula);
+    free(range->sheetname);
+    free(range);
+}
+
+STATIC void
+_chart_free_points(lxw_chart_series *series)
+{
+    uint16_t index;
+
+    for (index = 0; index < series->point_count; index++) {
+        lxw_chart_point *point = &series->points[index];
+
+        free(point->line);
+        free(point->fill);
+        free(point->pattern);
+    }
+
+    series->point_count = 0;
+    free(series->points);
+}
+
+/*
+ * Free a chart font object.
+ */
+STATIC void
+_chart_free_font(lxw_chart_font *font)
+{
+    if (!font)
+        return;
+
+    free(font->name);
+    free(font);
+}
+
+/*
+ * Free a series object.
+ */
+STATIC void
+_chart_series_free(lxw_chart_series *series)
+{
+    if (!series)
+        return;
+
+    free(series->title.name);
+    free(series->line);
+    free(series->fill);
+    free(series->pattern);
+    free(series->label_num_format);
+    _chart_free_font(series->label_font);
+
+    if (series->marker) {
+        free(series->marker->line);
+        free(series->marker->fill);
+        free(series->marker->pattern);
+        free(series->marker);
+    }
+
+    _chart_free_range(series->categories);
+    _chart_free_range(series->values);
+    _chart_free_range(series->title.range);
+    _chart_free_points(series);
+
+    if (series->x_error_bars) {
+        free(series->x_error_bars->line);
+        free(series->x_error_bars);
+    }
+
+    if (series->y_error_bars) {
+        free(series->y_error_bars->line);
+        free(series->y_error_bars);
+    }
+
+    free(series->trendline_line);
+    free(series->trendline_name);
+
+    free(series);
+}
+
+/*
+ * Initialize the data cache in a range object.
+ */
+STATIC lxw_error
+_chart_init_data_cache(lxw_series_range *range)
+{
+    /* Initialize the series range data cache. */
+    range->data_cache = calloc(1, sizeof(struct lxw_series_data_points));
+    RETURN_ON_MEM_ERROR(range->data_cache, LXW_ERROR_MEMORY_MALLOC_FAILED);
+    STAILQ_INIT(range->data_cache);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Free a chart object.
+ */
+void
+lxw_chart_free(lxw_chart *chart)
+{
+    lxw_chart_series *series;
+
+    if (!chart)
+        return;
+
+    /* Chart series. */
+    if (chart->series_list) {
+        while (!STAILQ_EMPTY(chart->series_list)) {
+            series = STAILQ_FIRST(chart->series_list);
+            STAILQ_REMOVE_HEAD(chart->series_list, list_pointers);
+
+            _chart_series_free(series);
+        }
+
+        free(chart->series_list);
+    }
+
+    /* X Axis. */
+    if (chart->x_axis) {
+        _chart_free_font(chart->x_axis->title.font);
+        _chart_free_font(chart->x_axis->num_font);
+        _chart_free_range(chart->x_axis->title.range);
+        free(chart->x_axis->title.name);
+        free(chart->x_axis->line);
+        free(chart->x_axis->fill);
+        free(chart->x_axis->pattern);
+        free(chart->x_axis->major_gridlines.line);
+        free(chart->x_axis->minor_gridlines.line);
+        free(chart->x_axis->num_format);
+        free(chart->x_axis->default_num_format);
+        free(chart->x_axis);
+    }
+
+    /* Y Axis. */
+    if (chart->y_axis) {
+        _chart_free_font(chart->y_axis->title.font);
+        _chart_free_font(chart->y_axis->num_font);
+        _chart_free_range(chart->y_axis->title.range);
+        free(chart->y_axis->title.name);
+        free(chart->y_axis->line);
+        free(chart->y_axis->fill);
+        free(chart->y_axis->pattern);
+        free(chart->y_axis->major_gridlines.line);
+        free(chart->y_axis->minor_gridlines.line);
+        free(chart->y_axis->num_format);
+        free(chart->y_axis->default_num_format);
+        free(chart->y_axis);
+    }
+
+    /* Chart title. */
+    _chart_free_font(chart->title.font);
+    _chart_free_range(chart->title.range);
+    free(chart->title.name);
+
+    /* Chart legend. */
+    _chart_free_font(chart->legend.font);
+    free(chart->delete_series);
+
+    free(chart->default_marker);
+
+    free(chart->chartarea_line);
+    free(chart->chartarea_fill);
+    free(chart->chartarea_pattern);
+    free(chart->plotarea_line);
+    free(chart->plotarea_fill);
+    free(chart->plotarea_pattern);
+
+    free(chart->drop_lines_line);
+    free(chart->high_low_lines_line);
+
+    free(chart->up_bar_line);
+    free(chart->up_bar_fill);
+    free(chart->down_bar_line);
+    free(chart->down_bar_fill);
+
+    _chart_free_font(chart->table_font);
+
+    free(chart);
+}
+
+/*
+ * Create a new chart object.
+ */
+lxw_chart *
+lxw_chart_new(uint8_t type)
+{
+    lxw_chart *chart = calloc(1, sizeof(lxw_chart));
+    GOTO_LABEL_ON_MEM_ERROR(chart, mem_error);
+
+    chart->series_list = calloc(1, sizeof(struct lxw_chart_series_list));
+    GOTO_LABEL_ON_MEM_ERROR(chart->series_list, mem_error);
+    STAILQ_INIT(chart->series_list);
+
+    chart->x_axis = calloc(1, sizeof(struct lxw_chart_axis));
+    GOTO_LABEL_ON_MEM_ERROR(chart->x_axis, mem_error);
+
+    chart->y_axis = calloc(1, sizeof(struct lxw_chart_axis));
+    GOTO_LABEL_ON_MEM_ERROR(chart->y_axis, mem_error);
+
+    chart->title.range = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(chart->title.range, mem_error);
+
+    chart->x_axis->title.range = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(chart->x_axis->title.range, mem_error);
+
+    chart->y_axis->title.range = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(chart->y_axis->title.range, mem_error);
+
+    /* Initialize the ranges in the chart titles. */
+    if (_chart_init_data_cache(chart->title.range) != LXW_NO_ERROR)
+        goto mem_error;
+
+    if (_chart_init_data_cache(chart->x_axis->title.range) != LXW_NO_ERROR)
+        goto mem_error;
+
+    if (_chart_init_data_cache(chart->y_axis->title.range) != LXW_NO_ERROR)
+        goto mem_error;
+
+    chart->type = type;
+    chart->style_id = 2;
+    chart->hole_size = 50;
+
+    /* Set the default axis positions. */
+    chart->x_axis->axis_position = LXW_CHART_AXIS_BOTTOM;
+    chart->y_axis->axis_position = LXW_CHART_AXIS_LEFT;
+
+    /* Set the default axis number formats. */
+    _chart_axis_set_default_num_format(chart->x_axis, "General");
+    _chart_axis_set_default_num_format(chart->y_axis, "General");
+
+    chart->x_axis->major_gridlines.visible = LXW_FALSE;
+    chart->y_axis->major_gridlines.visible = LXW_TRUE;
+
+    chart->has_horiz_cat_axis = LXW_FALSE;
+    chart->has_horiz_val_axis = LXW_TRUE;
+
+    chart->legend.position = LXW_CHART_LEGEND_RIGHT;
+
+    chart->gap_y1 = LXW_CHART_DEFAULT_GAP;
+    chart->gap_y2 = LXW_CHART_DEFAULT_GAP;
+
+    /* Initialize the chart specific properties. */
+    _chart_initialize(chart, chart->type);
+
+    return chart;
+
+mem_error:
+    lxw_chart_free(chart);
+    return NULL;
+}
+
+/*
+ * Create a copy of a user supplied font.
+ */
+STATIC lxw_chart_font *
+_chart_convert_font_args(lxw_chart_font *user_font)
+{
+    lxw_chart_font *font;
+
+    if (!user_font)
+        return NULL;
+
+    font = calloc(1, sizeof(struct lxw_chart_font));
+    RETURN_ON_MEM_ERROR(font, NULL);
+
+    /* Copy the user supplied properties. */
+    font->name = lxw_strdup(user_font->name);
+    font->size = user_font->size;
+    font->bold = user_font->bold;
+    font->italic = user_font->italic;
+    font->underline = user_font->underline;
+    font->rotation = user_font->rotation;
+    font->color = user_font->color;
+    font->pitch_family = user_font->pitch_family;
+    font->charset = user_font->charset;
+    font->baseline = user_font->baseline;
+
+    /* Convert font size units. */
+    if (font->size > 0.0)
+        font->size = font->size * 100.0;
+
+    /* Convert rotation into 60,000ths of a degree. */
+    if (font->rotation)
+        font->rotation = font->rotation * 60000;
+
+    if (font->color) {
+        font->color = lxw_format_check_color(font->color);
+        font->has_color = LXW_TRUE;
+    }
+
+    return font;
+}
+
+/*
+ * Create a copy of a user supplied line.
+ */
+STATIC lxw_chart_line *
+_chart_convert_line_args(lxw_chart_line *user_line)
+{
+    lxw_chart_line *line;
+
+    if (!user_line)
+        return NULL;
+
+    line = calloc(1, sizeof(struct lxw_chart_line));
+    RETURN_ON_MEM_ERROR(line, NULL);
+
+    /* Copy the user supplied properties. */
+    line->color = user_line->color;
+    line->none = user_line->none;
+    line->width = user_line->width;
+    line->dash_type = user_line->dash_type;
+    line->transparency = user_line->transparency;
+
+    if (line->color) {
+        line->color = lxw_format_check_color(line->color);
+        line->has_color = LXW_TRUE;
+    }
+
+    if (line->transparency > 100)
+        line->transparency = 0;
+
+    return line;
+}
+
+/*
+ * Create a copy of a user supplied fill.
+ */
+STATIC lxw_chart_fill *
+_chart_convert_fill_args(lxw_chart_fill *user_fill)
+{
+    lxw_chart_fill *fill;
+
+    if (!user_fill)
+        return NULL;
+
+    fill = calloc(1, sizeof(struct lxw_chart_fill));
+    RETURN_ON_MEM_ERROR(fill, NULL);
+
+    /* Copy the user supplied properties. */
+    fill->color = user_fill->color;
+    fill->none = user_fill->none;
+    fill->transparency = user_fill->transparency;
+
+    if (fill->color) {
+        fill->color = lxw_format_check_color(fill->color);
+        fill->has_color = LXW_TRUE;
+    }
+
+    if (fill->transparency > 100)
+        fill->transparency = 0;
+
+    return fill;
+}
+
+/*
+ * Create a copy of a user supplied pattern.
+ */
+STATIC lxw_chart_pattern *
+_chart_convert_pattern_args(lxw_chart_pattern *user_pattern)
+{
+    lxw_chart_pattern *pattern;
+
+    if (!user_pattern)
+        return NULL;
+
+    if (!user_pattern->type) {
+        LXW_WARN("chart_xxx_set_pattern: 'type' must be specified");
+        return NULL;
+    }
+
+    if (!user_pattern->fg_color) {
+        LXW_WARN("chart_xxx_set_pattern: 'fg_color' must be specified");
+        return NULL;
+    }
+
+    pattern = calloc(1, sizeof(struct lxw_chart_pattern));
+    RETURN_ON_MEM_ERROR(pattern, NULL);
+
+    /* Copy the user supplied properties. */
+    pattern->fg_color = user_pattern->fg_color;
+    pattern->bg_color = user_pattern->bg_color;
+    pattern->type = user_pattern->type;
+
+    pattern->fg_color = lxw_format_check_color(pattern->fg_color);
+    pattern->has_fg_color = LXW_TRUE;
+
+    if (pattern->bg_color) {
+        pattern->bg_color = lxw_format_check_color(pattern->bg_color);
+        pattern->has_bg_color = LXW_TRUE;
+    }
+    else {
+        /* Default background color in Excel is white, when unspecified. */
+        pattern->bg_color = LXW_COLOR_WHITE;
+        pattern->has_bg_color = LXW_TRUE;
+    }
+
+    return pattern;
+}
+
+/*
+ * Set a marker type for a series.
+ */
+STATIC void
+_chart_set_default_marker_type(lxw_chart *self, uint8_t type)
+{
+    if (!self->default_marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        self->default_marker = marker;
+    }
+
+    self->default_marker->type = type;
+}
+
+/*
+ * Set an axis number format.
+ */
+void
+_chart_axis_set_default_num_format(lxw_chart_axis *axis, char *num_format)
+{
+    if (!num_format)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->default_num_format);
+
+    axis->default_num_format = lxw_strdup(num_format);
+}
+
+/*
+ * Verify that a X/Y error bar property is support for the chart type.
+ * All chart types, except Bar have Y error bars. Only Bar and Scatter
+ * support X error bars.
+ */
+lxw_error
+_chart_check_error_bars(lxw_series_error_bars *error_bars, char *property)
+{
+    /* Check that the error bar type has been set for all error bar
+     * functions except the one that is used to set the type. */
+    if (strlen(property) && !error_bars->is_set) {
+        LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
+                         "error bar type must be set first using "
+                         "chart_series_set_error_bars()", property);
+
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    if (error_bars->is_x) {
+        if (error_bars->chart_group != LXW_CHART_SCATTER
+            && error_bars->chart_group != LXW_CHART_BAR) {
+
+            LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
+                             "'X error bar' properties only available for"
+                             " Scatter and Bar charts in Excel", property);
+
+            return LXW_ERROR_PARAMETER_VALIDATION;
+        }
+    }
+    else {
+        if (error_bars->chart_group == LXW_CHART_BAR) {
+            LXW_WARN_FORMAT1("chart_series_set_error_bars%s(): "
+                             "'Y error bar' properties not available for "
+                             "Bar charts in Excel", property);
+
+            return LXW_ERROR_PARAMETER_VALIDATION;
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Add unique ids for primary or secondary axes.
+ */
+STATIC void
+_chart_add_axis_ids(lxw_chart *self)
+{
+    uint32_t chart_id = 50010000 + self->id;
+    uint32_t axis_count = 1;
+
+    self->axis_id_1 = chart_id + axis_count;
+    self->axis_id_2 = self->axis_id_1 + 1;
+}
+
+/*
+ * Utility function to set a chart range.
+ */
+STATIC void
+_chart_set_range(lxw_series_range *range, const char *sheetname,
+                 lxw_row_t first_row, lxw_col_t first_col,
+                 lxw_row_t last_row, lxw_col_t last_col)
+{
+    char formula[LXW_MAX_FORMULA_RANGE_LENGTH] = { 0 };
+
+    /* Set the range properties. */
+    range->sheetname = lxw_strdup(sheetname);
+    range->first_row = first_row;
+    range->first_col = first_col;
+    range->last_row = last_row;
+    range->last_col = last_col;
+
+    /* Free any existing range. */
+    free(range->formula);
+
+    /* Convert the range properties to a formula like: Sheet1!$A$1:$A$5. */
+    lxw_rowcol_to_formula_abs(formula, sheetname,
+                              first_row, first_col, last_row, last_col);
+
+    range->formula = lxw_strdup(formula);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_chart_xml_declaration(lxw_chart *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <c:chartSpace> element.
+ */
+STATIC void
+_chart_write_chart_space(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns_c[] = LXW_SCHEMA_DRAWING "/chart";
+    char xmlns_a[] = LXW_SCHEMA_DRAWING "/main";
+    char xmlns_r[] = LXW_SCHEMA_OFFICEDOC "/relationships";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:c", xmlns_c);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:a", xmlns_a);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
+
+    lxw_xml_start_tag(self->file, "c:chartSpace", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:lang> element.
+ */
+STATIC void
+_chart_write_lang(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "en-US");
+
+    lxw_xml_empty_tag(self->file, "c:lang", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:style> element.
+ */
+STATIC void
+_chart_write_style(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    /* Don"t write an element for the default style, 2. */
+    if (self->style_id == 2)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", self->style_id);
+
+    lxw_xml_empty_tag(self->file, "c:style", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:layout> element.
+ */
+STATIC void
+_chart_write_layout(lxw_chart *self)
+{
+    lxw_xml_empty_tag(self->file, "c:layout", NULL);
+}
+
+/*
+ * Write the <c:grouping> element.
+ */
+STATIC void
+_chart_write_grouping(lxw_chart *self, uint8_t grouping)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (grouping == LXW_GROUPING_STANDARD)
+        LXW_PUSH_ATTRIBUTES_STR("val", "standard");
+    else if (grouping == LXW_GROUPING_PERCENTSTACKED)
+        LXW_PUSH_ATTRIBUTES_STR("val", "percentStacked");
+    else if (grouping == LXW_GROUPING_STACKED)
+        LXW_PUSH_ATTRIBUTES_STR("val", "stacked");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "clustered");
+
+    lxw_xml_empty_tag(self->file, "c:grouping", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:radarStyle> element.
+ */
+STATIC void
+_chart_write_radar_style(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (self->type == LXW_CHART_RADAR_FILLED)
+        LXW_PUSH_ATTRIBUTES_STR("val", "filled");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "marker");
+
+    lxw_xml_empty_tag(self->file, "c:radarStyle", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:varyColors> element.
+ */
+STATIC void
+_chart_write_vary_colors(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:varyColors", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:firstSliceAng> element.
+ */
+STATIC void
+_chart_write_first_slice_ang(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", self->rotation);
+
+    lxw_xml_empty_tag(self->file, "c:firstSliceAng", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:holeSize> element.
+ */
+STATIC void
+_chart_write_hole_size(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", self->hole_size);
+
+    lxw_xml_empty_tag(self->file, "c:holeSize", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:alpha> element.
+ */
+STATIC void
+_chart_write_a_alpha(lxw_chart *self, uint8_t transparency)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint32_t val;
+
+    LXW_INIT_ATTRIBUTES();
+
+    val = (100 - transparency) * 1000;
+
+    LXW_PUSH_ATTRIBUTES_INT("val", val);
+
+    lxw_xml_empty_tag(self->file, "a:alpha", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:srgbClr> element.
+ */
+STATIC void
+_chart_write_a_srgb_clr(lxw_chart *self, lxw_color_t color,
+                        uint8_t transparency)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    LXW_INIT_ATTRIBUTES();
+
+    lxw_snprintf(rgb_str, LXW_ATTR_32, "%06X", color & LXW_COLOR_MASK);
+    LXW_PUSH_ATTRIBUTES_STR("val", rgb_str);
+
+    if (transparency) {
+        lxw_xml_start_tag(self->file, "a:srgbClr", &attributes);
+
+        /* Write the a:alpha element. */
+        _chart_write_a_alpha(self, transparency);
+
+        lxw_xml_end_tag(self->file, "a:srgbClr");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "a:srgbClr", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:solidFill> element.
+ */
+STATIC void
+_chart_write_a_solid_fill(lxw_chart *self, lxw_color_t color,
+                          uint8_t transparency)
+{
+
+    lxw_xml_start_tag(self->file, "a:solidFill", NULL);
+
+    /* Write the a:srgbClr element. */
+    _chart_write_a_srgb_clr(self, color, transparency);
+
+    lxw_xml_end_tag(self->file, "a:solidFill");
+}
+
+/*
+ * Write the <a:t> element.
+ */
+STATIC void
+_chart_write_a_t(lxw_chart *self, char *name)
+{
+    lxw_xml_data_element(self->file, "a:t", name, NULL);
+}
+
+/*
+ * Write the <a:endParaRPr> element.
+ */
+STATIC void
+_chart_write_a_end_para_rpr(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("lang", "en-US");
+
+    lxw_xml_empty_tag(self->file, "a:endParaRPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:defRPr> element.
+ */
+STATIC void
+_chart_write_a_def_rpr(lxw_chart *self, lxw_chart_font *font)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint8_t has_color = LXW_FALSE;
+    uint8_t has_latin = LXW_FALSE;
+    uint8_t use_font_default = LXW_FALSE;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (font) {
+        has_color = font->color || font->has_color;
+        has_latin = font->name || font->pitch_family || font->charset;
+        use_font_default = !(has_color || has_latin || font->baseline == -1);
+
+        /* Set the font attributes. */
+        if (font->size > 0.0)
+            LXW_PUSH_ATTRIBUTES_DBL("sz", font->size);
+
+        if (use_font_default || font->bold)
+            LXW_PUSH_ATTRIBUTES_INT("b", font->bold & 0x1);
+
+        if (use_font_default || font->italic)
+            LXW_PUSH_ATTRIBUTES_INT("i", font->italic & 0x1);
+
+        if (font->underline)
+            LXW_PUSH_ATTRIBUTES_STR("u", "sng");
+
+        if (font->baseline != -1)
+            LXW_PUSH_ATTRIBUTES_INT("baseline", font->baseline);
+    }
+
+    /* There are sub-elements if the font name or color have changed. */
+    if (has_latin || has_color) {
+
+        lxw_xml_start_tag(self->file, "a:defRPr", &attributes);
+
+        if (has_color) {
+            _chart_write_a_solid_fill(self, font->color, LXW_FALSE);
+        }
+
+        if (has_latin) {
+            /* Free and reuse the attribute list for the latin attributes. */
+            LXW_FREE_ATTRIBUTES();
+
+            if (font->name)
+                LXW_PUSH_ATTRIBUTES_STR("typeface", font->name);
+
+            if (font->pitch_family)
+                LXW_PUSH_ATTRIBUTES_INT("pitchFamily", font->pitch_family);
+
+            if (font->pitch_family || font->charset)
+                LXW_PUSH_ATTRIBUTES_INT("charset", font->charset);
+
+            /* Write the <a:latin> element. */
+            lxw_xml_empty_tag(self->file, "a:latin", &attributes);
+        }
+
+        lxw_xml_end_tag(self->file, "a:defRPr");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "a:defRPr", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:rPr> element.
+ */
+STATIC void
+_chart_write_a_r_pr(lxw_chart *self, lxw_chart_font *font)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint8_t has_color = LXW_FALSE;
+    uint8_t has_latin = LXW_FALSE;
+    uint8_t use_font_default = LXW_FALSE;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("lang", "en-US");
+
+    if (font) {
+        has_color = font->color || font->has_color;
+        has_latin = font->name || font->pitch_family || font->charset;
+        use_font_default = !(has_color || has_latin || font->baseline == -1);
+
+        /* Set the font attributes. */
+        if (font->size > 0.0)
+            LXW_PUSH_ATTRIBUTES_DBL("sz", font->size);
+
+        if (use_font_default || font->bold)
+            LXW_PUSH_ATTRIBUTES_INT("b", font->bold & 0x1);
+
+        if (use_font_default || font->italic)
+            LXW_PUSH_ATTRIBUTES_INT("i", font->italic & 0x1);
+
+        if (font->underline)
+            LXW_PUSH_ATTRIBUTES_STR("u", "sng");
+
+        if (font->baseline != -1)
+            LXW_PUSH_ATTRIBUTES_INT("baseline", font->baseline);
+    }
+
+    /* There are sub-elements if the font name or color have changed. */
+    if (has_latin || has_color) {
+
+        lxw_xml_start_tag(self->file, "a:rPr", &attributes);
+
+        if (has_color) {
+            _chart_write_a_solid_fill(self, font->color, LXW_FALSE);
+        }
+
+        if (has_latin) {
+            /* Free and reuse the attribute list for the latin attributes. */
+            LXW_FREE_ATTRIBUTES();
+
+            if (font->name)
+                LXW_PUSH_ATTRIBUTES_STR("typeface", font->name);
+
+            if (font->pitch_family)
+                LXW_PUSH_ATTRIBUTES_INT("pitchFamily", font->pitch_family);
+
+            if (font->pitch_family || font->charset)
+                LXW_PUSH_ATTRIBUTES_INT("charset", font->charset);
+
+            /* Write the <a:latin> element. */
+            lxw_xml_empty_tag(self->file, "a:latin", &attributes);
+        }
+
+        lxw_xml_end_tag(self->file, "a:rPr");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "a:rPr", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:r> element.
+ */
+STATIC void
+_chart_write_a_r(lxw_chart *self, char *name, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:r", NULL);
+
+    /* Write the a:rPr element. */
+    _chart_write_a_r_pr(self, font);
+
+    /* Write the a:t element. */
+    _chart_write_a_t(self, name);
+
+    lxw_xml_end_tag(self->file, "a:r");
+}
+
+/*
+ * Write the <a:pPr> element.
+ */
+STATIC void
+_chart_write_a_p_pr_formula(lxw_chart *self, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:pPr", NULL);
+
+    /* Write the a:defRPr element. */
+    _chart_write_a_def_rpr(self, font);
+
+    lxw_xml_end_tag(self->file, "a:pPr");
+}
+
+/*
+ * Write the <a:pPr> element for pie chart legends.
+ */
+STATIC void
+_chart_write_a_p_pr_pie(lxw_chart *self, lxw_chart_font *font)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("rtl", "0");
+
+    lxw_xml_start_tag(self->file, "a:pPr", &attributes);
+
+    /* Write the a:defRPr element. */
+    _chart_write_a_def_rpr(self, font);
+
+    lxw_xml_end_tag(self->file, "a:pPr");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:pPr> element.
+ */
+STATIC void
+_chart_write_a_p_pr_rich(lxw_chart *self, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:pPr", NULL);
+
+    /* Write the a:defRPr element. */
+    _chart_write_a_def_rpr(self, font);
+
+    lxw_xml_end_tag(self->file, "a:pPr");
+}
+
+/*
+ * Write the <a:p> element.
+ */
+STATIC void
+_chart_write_a_p_formula(lxw_chart *self, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:p", NULL);
+
+    /* Write the a:pPr element. */
+    _chart_write_a_p_pr_formula(self, font);
+
+    /* Write the a:endParaRPr element. */
+    _chart_write_a_end_para_rpr(self);
+
+    lxw_xml_end_tag(self->file, "a:p");
+}
+
+/*
+ * Write the <a:p> element for pie chart legends.
+ */
+STATIC void
+_chart_write_a_p_pie(lxw_chart *self, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:p", NULL);
+
+    /* Write the a:pPr element. */
+    _chart_write_a_p_pr_pie(self, font);
+
+    /* Write the a:endParaRPr element. */
+    _chart_write_a_end_para_rpr(self);
+
+    lxw_xml_end_tag(self->file, "a:p");
+}
+
+/*
+ * Write the <a:p> element.
+ */
+STATIC void
+_chart_write_a_p_rich(lxw_chart *self, char *name, lxw_chart_font *font)
+{
+    lxw_xml_start_tag(self->file, "a:p", NULL);
+
+    /* Write the a:pPr element. */
+    _chart_write_a_p_pr_rich(self, font);
+
+    /* Write the a:r element. */
+    _chart_write_a_r(self, name, font);
+
+    lxw_xml_end_tag(self->file, "a:p");
+}
+
+/*
+ * Write the <a:lstStyle> element.
+ */
+STATIC void
+_chart_write_a_lst_style(lxw_chart *self)
+{
+    lxw_xml_empty_tag(self->file, "a:lstStyle", NULL);
+}
+
+/*
+ * Write the <a:bodyPr> element.
+ */
+STATIC void
+_chart_write_a_body_pr(lxw_chart *self, int32_t rotation,
+                       uint8_t is_horizontal)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (rotation == 0 && is_horizontal)
+        rotation = -5400000;
+
+    if (rotation)
+        LXW_PUSH_ATTRIBUTES_INT("rot", rotation);
+
+    if (is_horizontal)
+        LXW_PUSH_ATTRIBUTES_STR("vert", "horz");
+
+    lxw_xml_empty_tag(self->file, "a:bodyPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:ptCount> element.
+ */
+STATIC void
+_chart_write_pt_count(lxw_chart *self, uint16_t num_data_points)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", num_data_points);
+
+    lxw_xml_empty_tag(self->file, "c:ptCount", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:v> element.
+ */
+STATIC void
+_chart_write_v_num(lxw_chart *self, double number)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, number);
+
+    lxw_xml_data_element(self->file, "c:v", data, NULL);
+}
+
+/*
+ * Write the <c:v> element.
+ */
+STATIC void
+_chart_write_v_str(lxw_chart *self, char *str)
+{
+    lxw_xml_data_element(self->file, "c:v", str, NULL);
+}
+
+/*
+ * Write the <c:f> element.
+ */
+STATIC void
+_chart_write_f(lxw_chart *self, char *formula)
+{
+    lxw_xml_data_element(self->file, "c:f", formula, NULL);
+}
+
+/*
+ * Write the <c:pt> element.
+ */
+STATIC void
+_chart_write_pt(lxw_chart *self, uint16_t index,
+                lxw_series_data_point *data_point)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    /* Ignore chart points that have no data. */
+    if (data_point->no_data)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("idx", index);
+
+    lxw_xml_start_tag(self->file, "c:pt", &attributes);
+
+    if (data_point->is_string && data_point->string)
+        _chart_write_v_str(self, data_point->string);
+    else
+        _chart_write_v_num(self, data_point->number);
+
+    lxw_xml_end_tag(self->file, "c:pt");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:pt> element.
+ */
+STATIC void
+_chart_write_num_pt(lxw_chart *self, uint16_t index,
+                    lxw_series_data_point *data_point)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    /* Ignore chart points that have no data. */
+    if (data_point->no_data)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("idx", index);
+
+    lxw_xml_start_tag(self->file, "c:pt", &attributes);
+
+    _chart_write_v_num(self, data_point->number);
+
+    lxw_xml_end_tag(self->file, "c:pt");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:formatCode> element.
+ */
+STATIC void
+_chart_write_format_code(lxw_chart *self)
+{
+    lxw_xml_data_element(self->file, "c:formatCode", "General", NULL);
+}
+
+/*
+ * Write the <c:numCache> element.
+ */
+STATIC void
+_chart_write_num_cache(lxw_chart *self, lxw_series_range *range)
+{
+    lxw_series_data_point *data_point;
+    uint16_t index = 0;
+
+    lxw_xml_start_tag(self->file, "c:numCache", NULL);
+
+    /* Write the c:formatCode element. */
+    _chart_write_format_code(self);
+
+    /* Write the c:ptCount element. */
+    _chart_write_pt_count(self, range->num_data_points);
+
+    STAILQ_FOREACH(data_point, range->data_cache, list_pointers) {
+        /* Write the c:pt element. */
+        _chart_write_num_pt(self, index, data_point);
+        index++;
+    }
+
+    lxw_xml_end_tag(self->file, "c:numCache");
+}
+
+/*
+ * Write the <c:strCache> element.
+ */
+STATIC void
+_chart_write_str_cache(lxw_chart *self, lxw_series_range *range)
+{
+    lxw_series_data_point *data_point;
+    uint16_t index = 0;
+
+    lxw_xml_start_tag(self->file, "c:strCache", NULL);
+
+    /* Write the c:ptCount element. */
+    _chart_write_pt_count(self, range->num_data_points);
+
+    STAILQ_FOREACH(data_point, range->data_cache, list_pointers) {
+        /* Write the c:pt element. */
+        _chart_write_pt(self, index, data_point);
+        index++;
+    }
+
+    lxw_xml_end_tag(self->file, "c:strCache");
+}
+
+/*
+ * Write the <c:numRef> element.
+ */
+STATIC void
+_chart_write_num_ref(lxw_chart *self, lxw_series_range *range)
+{
+    lxw_xml_start_tag(self->file, "c:numRef", NULL);
+
+    /* Write the c:f element. */
+    _chart_write_f(self, range->formula);
+
+    if (!STAILQ_EMPTY(range->data_cache)) {
+        /* Write the c:numCache element. */
+        _chart_write_num_cache(self, range);
+    }
+
+    lxw_xml_end_tag(self->file, "c:numRef");
+}
+
+/*
+ * Write the <c:strRef> element.
+ */
+STATIC void
+_chart_write_str_ref(lxw_chart *self, lxw_series_range *range)
+{
+    lxw_xml_start_tag(self->file, "c:strRef", NULL);
+
+    /* Write the c:f element. */
+    _chart_write_f(self, range->formula);
+
+    if (!STAILQ_EMPTY(range->data_cache)) {
+        /* Write the c:strCache element. */
+        _chart_write_str_cache(self, range);
+    }
+
+    lxw_xml_end_tag(self->file, "c:strRef");
+}
+
+/*
+ * Write the cached data elements.
+ */
+STATIC void
+_chart_write_data_cache(lxw_chart *self, lxw_series_range *range,
+                        uint8_t has_string_cache)
+{
+    if (has_string_cache) {
+        /* Write the c:strRef element. */
+        _chart_write_str_ref(self, range);
+    }
+    else {
+        /* Write the c:numRef element. */
+        _chart_write_num_ref(self, range);
+    }
+}
+
+/*
+ * Write the <c:tx> element with a simple value such as for series names.
+ */
+STATIC void
+_chart_write_tx_value(lxw_chart *self, char *name)
+{
+    lxw_xml_start_tag(self->file, "c:tx", NULL);
+
+    /* Write the c:v element. */
+    _chart_write_v_str(self, name);
+
+    lxw_xml_end_tag(self->file, "c:tx");
+}
+
+/*
+ * Write the <c:tx> element with a simple value such as for series names.
+ */
+STATIC void
+_chart_write_tx_formula(lxw_chart *self, lxw_chart_title *title)
+{
+    lxw_xml_start_tag(self->file, "c:tx", NULL);
+
+    _chart_write_str_ref(self, title->range);
+
+    lxw_xml_end_tag(self->file, "c:tx");
+}
+
+/*
+ * Write the <c:txPr> element.
+ */
+STATIC void
+_chart_write_tx_pr(lxw_chart *self, uint8_t is_horizontal,
+                   lxw_chart_font *font)
+{
+    int32_t rotation = 0;
+
+    if (font)
+        rotation = font->rotation;
+
+    lxw_xml_start_tag(self->file, "c:txPr", NULL);
+
+    /* Write the a:bodyPr element. */
+    _chart_write_a_body_pr(self, rotation, is_horizontal);
+
+    /* Write the a:lstStyle element. */
+    _chart_write_a_lst_style(self);
+
+    /* Write the a:p element. */
+    _chart_write_a_p_formula(self, font);
+
+    lxw_xml_end_tag(self->file, "c:txPr");
+}
+
+/*
+ * Write the <c:txPr> element for pie chart legends.
+ */
+STATIC void
+_chart_write_tx_pr_pie(lxw_chart *self, uint8_t is_horizontal,
+                       lxw_chart_font *font)
+{
+    int32_t rotation = 0;
+
+    if (font)
+        rotation = font->rotation;
+
+    lxw_xml_start_tag(self->file, "c:txPr", NULL);
+
+    /* Write the a:bodyPr element. */
+    _chart_write_a_body_pr(self, rotation, is_horizontal);
+
+    /* Write the a:lstStyle element. */
+    _chart_write_a_lst_style(self);
+
+    /* Write the a:p element. */
+    _chart_write_a_p_pie(self, font);
+
+    lxw_xml_end_tag(self->file, "c:txPr");
+}
+
+/*
+ * Write the <c:txPr> element.
+ */
+STATIC void
+_chart_write_axis_font(lxw_chart *self, lxw_chart_font *font)
+{
+    if (!font)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:txPr", NULL);
+
+    /* Write the a:bodyPr element. */
+    _chart_write_a_body_pr(self, font->rotation, LXW_FALSE);
+
+    /* Write the a:lstStyle element. */
+    _chart_write_a_lst_style(self);
+
+    lxw_xml_start_tag(self->file, "a:p", NULL);
+
+    /* Write the a:pPr element. */
+    _chart_write_a_p_pr_rich(self, font);
+
+    /* Write the a:endParaRPr element. */
+    _chart_write_a_end_para_rpr(self);
+
+    lxw_xml_end_tag(self->file, "a:p");
+    lxw_xml_end_tag(self->file, "c:txPr");
+}
+
+/*
+ * Write the <c:rich> element.
+ */
+STATIC void
+_chart_write_rich(lxw_chart *self, char *name, uint8_t is_horizontal,
+                  lxw_chart_font *font)
+{
+    int32_t rotation = 0;
+
+    if (font)
+        rotation = font->rotation;
+
+    lxw_xml_start_tag(self->file, "c:rich", NULL);
+
+    /* Write the a:bodyPr element. */
+    _chart_write_a_body_pr(self, rotation, is_horizontal);
+
+    /* Write the a:lstStyle element. */
+    _chart_write_a_lst_style(self);
+
+    /* Write the a:p element. */
+    _chart_write_a_p_rich(self, name, font);
+
+    lxw_xml_end_tag(self->file, "c:rich");
+}
+
+/*
+ * Write the <c:tx> element.
+ */
+STATIC void
+_chart_write_tx_rich(lxw_chart *self, char *name, uint8_t is_horizontal,
+                     lxw_chart_font *font)
+{
+
+    lxw_xml_start_tag(self->file, "c:tx", NULL);
+
+    /* Write the c:rich element. */
+    _chart_write_rich(self, name, is_horizontal, font);
+
+    lxw_xml_end_tag(self->file, "c:tx");
+}
+
+/*
+ * Write the <c:title> element for rich strings.
+ */
+STATIC void
+_chart_write_title_rich(lxw_chart *self, lxw_chart_title *title)
+{
+    lxw_xml_start_tag(self->file, "c:title", NULL);
+
+    /* Write the c:tx element. */
+    _chart_write_tx_rich(self, title->name, title->is_horizontal,
+                         title->font);
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    lxw_xml_end_tag(self->file, "c:title");
+}
+
+/*
+ * Write the <c:title> element for a formula style title
+ */
+STATIC void
+_chart_write_title_formula(lxw_chart *self, lxw_chart_title *title)
+{
+    lxw_xml_start_tag(self->file, "c:title", NULL);
+
+    /* Write the c:tx element. */
+    _chart_write_tx_formula(self, title);
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    /* Write the c:txPr element. */
+    _chart_write_tx_pr(self, title->is_horizontal, title->font);
+
+    lxw_xml_end_tag(self->file, "c:title");
+}
+
+/*
+ * Write the <c:delete> element.
+ */
+STATIC void
+_chart_write_delete(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:delete", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:autoTitleDeleted> element.
+ */
+STATIC void
+_chart_write_auto_title_deleted(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:autoTitleDeleted", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:idx> element.
+ */
+STATIC void
+_chart_write_idx(lxw_chart *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", index);
+
+    lxw_xml_empty_tag(self->file, "c:idx", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:prstDash> element.
+ */
+STATIC void
+_chart_write_a_prst_dash(lxw_chart *self, uint8_t dash_type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (dash_type == LXW_CHART_LINE_DASH_ROUND_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "sysDot");
+    else if (dash_type == LXW_CHART_LINE_DASH_SQUARE_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "sysDash");
+    else if (dash_type == LXW_CHART_LINE_DASH_DASH_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "dashDot");
+    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH)
+        LXW_PUSH_ATTRIBUTES_STR("val", "lgDash");
+    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "lgDashDot");
+    else if (dash_type == LXW_CHART_LINE_DASH_LONG_DASH_DOT_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "lgDashDotDot");
+    else if (dash_type == LXW_CHART_LINE_DASH_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "dot");
+    else if (dash_type == LXW_CHART_LINE_DASH_SYSTEM_DASH_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "sysDashDot");
+    else if (dash_type == LXW_CHART_LINE_DASH_SYSTEM_DASH_DOT_DOT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "sysDashDotDot");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "dash");
+
+    lxw_xml_empty_tag(self->file, "a:prstDash", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:noFill> element.
+ */
+STATIC void
+_chart_write_a_no_fill(lxw_chart *self)
+{
+    lxw_xml_empty_tag(self->file, "a:noFill", NULL);
+}
+
+/*
+ * Write the <a:ln> element.
+ */
+STATIC void
+_chart_write_a_ln(lxw_chart *self, lxw_chart_line *line)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    float width_flt;
+    uint32_t width_int;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Round width to nearest 0.25, like Excel. */
+    width_flt = (float) (uint32_t) ((line->width + 0.125) * 4.0F) / 4.0F;
+
+    /* Convert to internal units. */
+    width_int = (uint32_t) (0.5 + (12700.0 * width_flt));
+
+    if (width_int)
+        LXW_PUSH_ATTRIBUTES_INT("w", width_int);
+
+    lxw_xml_start_tag(self->file, "a:ln", &attributes);
+
+    /* Write the line fill. */
+    if (line->none) {
+        /* Write the a:noFill element. */
+        _chart_write_a_no_fill(self);
+    }
+    else if (line->has_color) {
+        /* Write the a:solidFill element. */
+        _chart_write_a_solid_fill(self, line->color, line->transparency);
+    }
+
+    /* Write the line/dash type. */
+    if (line->dash_type) {
+        /* Write the a:prstDash element. */
+        _chart_write_a_prst_dash(self, line->dash_type);
+    }
+
+    lxw_xml_end_tag(self->file, "a:ln");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:fgClr> element.
+ */
+STATIC void
+_chart_write_a_fg_clr(lxw_chart *self, lxw_color_t color)
+{
+    lxw_xml_start_tag(self->file, "a:fgClr", NULL);
+
+    _chart_write_a_srgb_clr(self, color, LXW_FALSE);
+
+    lxw_xml_end_tag(self->file, "a:fgClr");
+}
+
+/*
+ * Write the <a:bgClr> element.
+ */
+STATIC void
+_chart_write_a_bg_clr(lxw_chart *self, lxw_color_t color)
+{
+    lxw_xml_start_tag(self->file, "a:bgClr", NULL);
+
+    _chart_write_a_srgb_clr(self, color, LXW_FALSE);
+
+    lxw_xml_end_tag(self->file, "a:bgClr");
+}
+
+/*
+ * Write the <a:pattFill> element.
+ */
+STATIC void
+_chart_write_a_patt_fill(lxw_chart *self, lxw_chart_pattern *pattern)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (pattern->type == LXW_CHART_PATTERN_NONE)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "none");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_5)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct5");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_10)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct10");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_20)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct20");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_25)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct25");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_30)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct30");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_40)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct40");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_50)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct50");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_60)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct60");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_70)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct70");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_75)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct75");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_80)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct80");
+    else if (pattern->type == LXW_CHART_PATTERN_PERCENT_90)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "pct90");
+    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_DOWNWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "ltDnDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_UPWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "ltUpDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_DARK_DOWNWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dkDnDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_DARK_UPWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dkUpDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_WIDE_DOWNWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "wdDnDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_WIDE_UPWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "wdUpDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_VERTICAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "ltVert");
+    else if (pattern->type == LXW_CHART_PATTERN_LIGHT_HORIZONTAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "ltHorz");
+    else if (pattern->type == LXW_CHART_PATTERN_NARROW_VERTICAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "narVert");
+    else if (pattern->type == LXW_CHART_PATTERN_NARROW_HORIZONTAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "narHorz");
+    else if (pattern->type == LXW_CHART_PATTERN_DARK_VERTICAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dkVert");
+    else if (pattern->type == LXW_CHART_PATTERN_DARK_HORIZONTAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dkHorz");
+    else if (pattern->type == LXW_CHART_PATTERN_DASHED_DOWNWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dashDnDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_DASHED_UPWARD_DIAGONAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dashUpDiag");
+    else if (pattern->type == LXW_CHART_PATTERN_DASHED_HORIZONTAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dashHorz");
+    else if (pattern->type == LXW_CHART_PATTERN_DASHED_VERTICAL)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dashVert");
+    else if (pattern->type == LXW_CHART_PATTERN_SMALL_CONFETTI)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "smConfetti");
+    else if (pattern->type == LXW_CHART_PATTERN_LARGE_CONFETTI)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "lgConfetti");
+    else if (pattern->type == LXW_CHART_PATTERN_ZIGZAG)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "zigZag");
+    else if (pattern->type == LXW_CHART_PATTERN_WAVE)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "wave");
+    else if (pattern->type == LXW_CHART_PATTERN_DIAGONAL_BRICK)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "diagBrick");
+    else if (pattern->type == LXW_CHART_PATTERN_HORIZONTAL_BRICK)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "horzBrick");
+    else if (pattern->type == LXW_CHART_PATTERN_WEAVE)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "weave");
+    else if (pattern->type == LXW_CHART_PATTERN_PLAID)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "plaid");
+    else if (pattern->type == LXW_CHART_PATTERN_DIVOT)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "divot");
+    else if (pattern->type == LXW_CHART_PATTERN_DOTTED_GRID)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dotGrid");
+    else if (pattern->type == LXW_CHART_PATTERN_DOTTED_DIAMOND)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "dotDmnd");
+    else if (pattern->type == LXW_CHART_PATTERN_SHINGLE)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "shingle");
+    else if (pattern->type == LXW_CHART_PATTERN_TRELLIS)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "trellis");
+    else if (pattern->type == LXW_CHART_PATTERN_SPHERE)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "sphere");
+    else if (pattern->type == LXW_CHART_PATTERN_SMALL_GRID)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "smGrid");
+    else if (pattern->type == LXW_CHART_PATTERN_LARGE_GRID)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "lgGrid");
+    else if (pattern->type == LXW_CHART_PATTERN_SMALL_CHECK)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "smCheck");
+    else if (pattern->type == LXW_CHART_PATTERN_LARGE_CHECK)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "lgCheck");
+    else if (pattern->type == LXW_CHART_PATTERN_OUTLINED_DIAMOND)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "openDmnd");
+    else if (pattern->type == LXW_CHART_PATTERN_SOLID_DIAMOND)
+        LXW_PUSH_ATTRIBUTES_STR("prst", "solidDmnd");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("prst", "percent_50");
+
+    lxw_xml_start_tag(self->file, "a:pattFill", &attributes);
+
+    if (pattern->has_fg_color)
+        _chart_write_a_fg_clr(self, pattern->fg_color);
+
+    if (pattern->has_bg_color)
+        _chart_write_a_bg_clr(self, pattern->bg_color);
+
+    lxw_xml_end_tag(self->file, "a:pattFill");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:spPr> element.
+ */
+STATIC void
+_chart_write_sp_pr(lxw_chart *self, lxw_chart_line *line,
+                   lxw_chart_fill *fill, lxw_chart_pattern *pattern)
+{
+    if (!line && !fill && !pattern)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:spPr", NULL);
+
+    /* Write the series fill. Note: a pattern fill overrides a solid fill. */
+    if (fill && !pattern) {
+        if (fill->none) {
+            /* Write the a:noFill element. */
+            _chart_write_a_no_fill(self);
+        }
+        else {
+            /* Write the a:solidFill element. */
+            _chart_write_a_solid_fill(self, fill->color, fill->transparency);
+        }
+    }
+
+    if (pattern) {
+        /* Write the a:pattFill element. */
+        _chart_write_a_patt_fill(self, pattern);
+    }
+
+    if (line) {
+        /* Write the a:ln element. */
+        _chart_write_a_ln(self, line);
+    }
+
+    lxw_xml_end_tag(self->file, "c:spPr");
+}
+
+/*
+ * Write the <c:order> element.
+ */
+STATIC void
+_chart_write_order(lxw_chart *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", index);
+
+    lxw_xml_empty_tag(self->file, "c:order", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:axId> element.
+ */
+STATIC void
+_chart_write_axis_id(lxw_chart *self, uint32_t axis_id)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", axis_id);
+
+    lxw_xml_empty_tag(self->file, "c:axId", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:axId> element.
+ */
+STATIC void
+_chart_write_axis_ids(lxw_chart *self)
+{
+    if (!self->axis_id_1)
+        _chart_add_axis_ids(self);
+
+    _chart_write_axis_id(self, self->axis_id_1);
+    _chart_write_axis_id(self, self->axis_id_2);
+}
+
+/*
+ * Write the series name.
+ */
+STATIC void
+_chart_write_series_name(lxw_chart *self, lxw_chart_series *series)
+{
+    if (series->title.name) {
+        /* Write the c:tx element. */
+        _chart_write_tx_value(self, series->title.name);
+    }
+    else if (series->title.range->formula) {
+        /* Write the c:tx element. */
+        _chart_write_tx_formula(self, &series->title);
+
+    }
+}
+
+/*
+ * Write the <c:majorTickMark> element.
+ */
+STATIC void
+_chart_write_major_tick_mark(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->major_tick_mark)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_NONE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "none");
+    else if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_INSIDE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "in");
+    else if (axis->major_tick_mark == LXW_CHART_AXIS_TICK_MARK_CROSSING)
+        LXW_PUSH_ATTRIBUTES_STR("val", "cross");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "out");
+
+    lxw_xml_empty_tag(self->file, "c:majorTickMark", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:minorTickMark> element.
+ */
+STATIC void
+_chart_write_minor_tick_mark(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->minor_tick_mark)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_NONE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "none");
+    else if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_INSIDE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "in");
+    else if (axis->minor_tick_mark == LXW_CHART_AXIS_TICK_MARK_CROSSING)
+        LXW_PUSH_ATTRIBUTES_STR("val", "cross");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "out");
+
+    lxw_xml_empty_tag(self->file, "c:minorTickMark", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:symbol> element.
+ */
+STATIC void
+_chart_write_symbol(lxw_chart *self, uint8_t type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (type == LXW_CHART_MARKER_SQUARE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "square");
+    else if (type == LXW_CHART_MARKER_DIAMOND)
+        LXW_PUSH_ATTRIBUTES_STR("val", "diamond");
+    else if (type == LXW_CHART_MARKER_TRIANGLE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "triangle");
+    else if (type == LXW_CHART_MARKER_X)
+        LXW_PUSH_ATTRIBUTES_STR("val", "x");
+    else if (type == LXW_CHART_MARKER_STAR)
+        LXW_PUSH_ATTRIBUTES_STR("val", "star");
+    else if (type == LXW_CHART_MARKER_SHORT_DASH)
+        LXW_PUSH_ATTRIBUTES_STR("val", "short_dash");
+    else if (type == LXW_CHART_MARKER_LONG_DASH)
+        LXW_PUSH_ATTRIBUTES_STR("val", "long_dash");
+    else if (type == LXW_CHART_MARKER_CIRCLE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "circle");
+    else if (type == LXW_CHART_MARKER_PLUS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "plus");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "none");
+
+    lxw_xml_empty_tag(self->file, "c:symbol", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dPt> element.
+ */
+STATIC void
+_chart_write_d_pt(lxw_chart *self, lxw_chart_point *point, uint16_t index)
+{
+    lxw_xml_start_tag(self->file, "c:dPt", NULL);
+
+    /* Write the c:idx element. */
+    _chart_write_idx(self, index);
+
+    /* Scatter/Line charts have an additional marker for the point. */
+    if (self->chart_group == LXW_CHART_SCATTER
+        || self->chart_group == LXW_CHART_LINE)
+        lxw_xml_start_tag(self->file, "c:marker", NULL);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, point->line, point->fill, point->pattern);
+
+    if (self->chart_group == LXW_CHART_SCATTER
+        || self->chart_group == LXW_CHART_LINE)
+        lxw_xml_end_tag(self->file, "c:marker");
+
+    lxw_xml_end_tag(self->file, "c:dPt");
+}
+
+/*
+ * Write the <c:dPt> element.
+ */
+STATIC void
+_chart_write_points(lxw_chart *self, lxw_chart_series *series)
+{
+    uint16_t index;
+
+    for (index = 0; index < series->point_count; index++) {
+        lxw_chart_point *point = &series->points[index];
+
+        /* Ignore empty points. */
+        if (!point->line && !point->fill && !point->pattern)
+            continue;
+
+        /* Write the c:dPt element. */
+        _chart_write_d_pt(self, &series->points[index], index);
+    }
+}
+
+/*
+ * Write the <c:invertIfNegative> element.
+ */
+STATIC void
+_chart_write_invert_if_negative(lxw_chart *self, lxw_chart_series *series)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!series->invert_if_negative)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:invertIfNegative", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showVal> element.
+ */
+STATIC void
+_chart_write_show_val(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showVal", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showCatName> element.
+ */
+STATIC void
+_chart_write_show_cat_name(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showCatName", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showSerName> element.
+ */
+STATIC void
+_chart_write_show_ser_name(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showSerName", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showLeaderLines> element.
+ */
+STATIC void
+_chart_write_show_leader_lines(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showLeaderLines", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dLblPos> element.
+ */
+STATIC void
+_chart_write_d_lbl_pos(lxw_chart *self, uint8_t position)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (position == LXW_CHART_LABEL_POSITION_RIGHT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "r");
+    else if (position == LXW_CHART_LABEL_POSITION_LEFT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "l");
+    else if (position == LXW_CHART_LABEL_POSITION_ABOVE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "t");
+    else if (position == LXW_CHART_LABEL_POSITION_BELOW)
+        LXW_PUSH_ATTRIBUTES_STR("val", "b");
+    else if (position == LXW_CHART_LABEL_POSITION_INSIDE_BASE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "inBase");
+    else if (position == LXW_CHART_LABEL_POSITION_INSIDE_END)
+        LXW_PUSH_ATTRIBUTES_STR("val", "inEnd");
+    else if (position == LXW_CHART_LABEL_POSITION_OUTSIDE_END)
+        LXW_PUSH_ATTRIBUTES_STR("val", "outEnd");
+    else if (position == LXW_CHART_LABEL_POSITION_BEST_FIT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "bestFit");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "ctr");
+
+    lxw_xml_empty_tag(self->file, "c:dLblPos", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:separator> element.
+ */
+STATIC void
+_chart_write_separator(lxw_chart *self, uint8_t separator)
+{
+    if (separator == LXW_CHART_LABEL_SEPARATOR_SEMICOLON)
+        lxw_xml_data_element(self->file, "c:separator", "; ", NULL);
+    else if (separator == LXW_CHART_LABEL_SEPARATOR_PERIOD)
+        lxw_xml_data_element(self->file, "c:separator", ". ", NULL);
+    else if (separator == LXW_CHART_LABEL_SEPARATOR_NEWLINE)
+        lxw_xml_data_element(self->file, "c:separator", "\n", NULL);
+    else if (separator == LXW_CHART_LABEL_SEPARATOR_SPACE)
+        lxw_xml_data_element(self->file, "c:separator", " ", NULL);
+    else
+        lxw_xml_data_element(self->file, "c:separator", ", ", NULL);
+}
+
+/*
+ * Write the <c:showLegendKey> element.
+ */
+STATIC void
+_chart_write_show_legend_key(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showLegendKey", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showPercent> element.
+ */
+STATIC void
+_chart_write_show_percent(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showPercent", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:numFmt> element.
+ */
+STATIC void
+_chart_write_label_num_fmt(lxw_chart *self, char *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("formatCode", format);
+    LXW_PUSH_ATTRIBUTES_STR("sourceLinked", "0");
+
+    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dLbls> element.
+ */
+STATIC void
+_chart_write_d_lbls(lxw_chart *self, lxw_chart_series *series)
+{
+    if (!series->has_labels)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:dLbls", NULL);
+
+    /* Write the c:numFmt element. */
+    if (series->label_num_format)
+        _chart_write_label_num_fmt(self, series->label_num_format);
+
+    if (series->label_font)
+        _chart_write_tx_pr(self, LXW_FALSE, series->label_font);
+
+    /* Write the c:dLblPos element. */
+    if (series->label_position)
+        _chart_write_d_lbl_pos(self, series->label_position);
+
+    /* Write the c:showLegendKey element. */
+    if (series->show_labels_legend)
+        _chart_write_show_legend_key(self);
+
+    /* Write the c:showVal element. */
+    if (series->show_labels_value)
+        _chart_write_show_val(self);
+
+    /* Write the c:showCatName element. */
+    if (series->show_labels_category)
+        _chart_write_show_cat_name(self);
+
+    /* Write the c:showSerName element. */
+    if (series->show_labels_name)
+        _chart_write_show_ser_name(self);
+
+    /* Write the c:showPercent element. */
+    if (series->show_labels_percent)
+        _chart_write_show_percent(self);
+
+    /* Write the c:separator element. */
+    if (series->label_separator)
+        _chart_write_separator(self, series->label_separator);
+
+    /* Write the c:showLeaderLines element. */
+    if (series->show_labels_leader)
+        _chart_write_show_leader_lines(self);
+
+    lxw_xml_end_tag(self->file, "c:dLbls");
+}
+
+/*
+ * Write the <c:intercept> element.
+ */
+STATIC void
+_chart_write_intercept(lxw_chart *self, double value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", value);
+
+    lxw_xml_empty_tag(self->file, "c:intercept", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dispRSqr> element.
+ */
+STATIC void
+_chart_write_disp_rsqr(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:dispRSqr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:trendlineLbl> element.
+ */
+STATIC void
+_chart_write_trendline_lbl(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    lxw_xml_start_tag(self->file, "c:trendlineLbl", NULL);
+
+    lxw_xml_empty_tag(self->file, "c:layout", NULL);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("formatCode", "General");
+    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", 0);
+
+    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);
+
+    lxw_xml_end_tag(self->file, "c:trendlineLbl");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dispEq> element.
+ */
+STATIC void
+_chart_write_disp_eq(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:dispEq", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:period> element.
+ */
+STATIC void
+_chart_write_period(lxw_chart *self, uint8_t value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", value);
+
+    lxw_xml_empty_tag(self->file, "c:period", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:forward> element.
+ */
+STATIC void
+_chart_write_forward(lxw_chart *self, double value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", value);
+
+    lxw_xml_empty_tag(self->file, "c:forward", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:backward> element.
+ */
+STATIC void
+_chart_write_backward(lxw_chart *self, double value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", value);
+
+    lxw_xml_empty_tag(self->file, "c:backward", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:name> element.
+ */
+STATIC void
+_chart_write_name(lxw_chart *self, char *name)
+{
+    lxw_xml_data_element(self->file, "c:name", name, NULL);
+}
+
+/*
+ * Write the <c:trendlineType> element.
+ */
+STATIC void
+_chart_write_trendline_type(lxw_chart *self, uint8_t type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (type == LXW_CHART_TRENDLINE_TYPE_LOG)
+        LXW_PUSH_ATTRIBUTES_STR("val", "log");
+    else if (type == LXW_CHART_TRENDLINE_TYPE_POLY)
+        LXW_PUSH_ATTRIBUTES_STR("val", "poly");
+    else if (type == LXW_CHART_TRENDLINE_TYPE_POWER)
+        LXW_PUSH_ATTRIBUTES_STR("val", "power");
+    else if (type == LXW_CHART_TRENDLINE_TYPE_EXP)
+        LXW_PUSH_ATTRIBUTES_STR("val", "exp");
+    else if (type == LXW_CHART_TRENDLINE_TYPE_AVERAGE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "movingAvg");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "linear");
+
+    lxw_xml_empty_tag(self->file, "c:trendlineType", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:trendline> element.
+ */
+STATIC void
+_chart_write_trendline(lxw_chart *self, lxw_chart_series *series)
+{
+    if (!series->has_trendline)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:trendline", NULL);
+
+    /* Write the c:name element. */
+    if (series->trendline_name)
+        _chart_write_name(self, series->trendline_name);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, series->trendline_line, NULL, NULL);
+
+    /* Write the c:trendlineType element. */
+    _chart_write_trendline_type(self, series->trendline_type);
+
+    /* Write the c:order element. */
+    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_POLY
+        && series->trendline_value >= 2) {
+
+        _chart_write_order(self, series->trendline_value);
+    }
+
+    /* Write the c:period element. */
+    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE
+        && series->trendline_value >= 2) {
+
+        _chart_write_period(self, series->trendline_value);
+    }
+
+    if (series->has_trendline_forecast) {
+        /* Write the c:forward element. */
+        _chart_write_forward(self, series->trendline_forward);
+
+        /* Write the c:backward element. */
+        _chart_write_backward(self, series->trendline_backward);
+    }
+
+    /* Write the c:intercept element. */
+    if (series->has_trendline_intercept)
+        _chart_write_intercept(self, series->trendline_intercept);
+
+    /* Write the c:dispRSqr element. */
+    if (series->has_trendline_r_squared)
+        _chart_write_disp_rsqr(self);
+
+    if (series->has_trendline_equation) {
+        /* Write the c:dispEq element. */
+        _chart_write_disp_eq(self);
+
+        /* Write the c:trendlineLbl element. */
+        _chart_write_trendline_lbl(self);
+
+    }
+
+    lxw_xml_end_tag(self->file, "c:trendline");
+}
+
+/*
+ * Write the <c:val> element.
+ */
+STATIC void
+_chart_write_error_val(lxw_chart *self, double value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", value);
+
+    lxw_xml_empty_tag(self->file, "c:val", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:noEndCap> element.
+ */
+STATIC void
+_chart_write_no_end_cap(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:noEndCap", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:errValType> element.
+ */
+STATIC void
+_chart_write_err_val_type(lxw_chart *self, uint8_t type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (type == LXW_CHART_ERROR_BAR_TYPE_FIXED)
+        LXW_PUSH_ATTRIBUTES_STR("val", "fixedVal");
+    else if (type == LXW_CHART_ERROR_BAR_TYPE_PERCENTAGE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "percentage");
+    else if (type == LXW_CHART_ERROR_BAR_TYPE_STD_DEV)
+        LXW_PUSH_ATTRIBUTES_STR("val", "stdDev");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "stdErr");
+
+    lxw_xml_empty_tag(self->file, "c:errValType", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:errBarType> element.
+ */
+STATIC void
+_chart_write_err_bar_type(lxw_chart *self, uint8_t direction)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (direction == LXW_CHART_ERROR_BAR_DIR_PLUS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "plus");
+    else if (direction == LXW_CHART_ERROR_BAR_DIR_MINUS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "minus");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "both");
+
+    lxw_xml_empty_tag(self->file, "c:errBarType", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:errDir> element.
+ */
+STATIC void
+_chart_write_err_dir(lxw_chart *self, uint8_t is_x)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (is_x)
+        LXW_PUSH_ATTRIBUTES_STR("val", "x");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "y");
+
+    lxw_xml_empty_tag(self->file, "c:errDir", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:errBars> element.
+ */
+STATIC void
+_chart_write_err_bars(lxw_chart *self, lxw_series_error_bars *error_bars)
+{
+    if (!error_bars->is_set)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:errBars", NULL);
+
+    /* Write the c:errDir element, except for Column/Bar charts. */
+    if (error_bars->chart_group != LXW_CHART_BAR
+        && error_bars->chart_group != LXW_CHART_COLUMN) {
+
+        _chart_write_err_dir(self, error_bars->is_x);
+    }
+
+    /* Write the c:errBarType element. */
+    _chart_write_err_bar_type(self, error_bars->direction);
+
+    /* Write the c:errValType element. */
+    _chart_write_err_val_type(self, error_bars->type);
+
+    /* Write the c:noEndCap element. */
+    if (error_bars->endcap == LXW_CHART_ERROR_BAR_NO_CAP)
+        _chart_write_no_end_cap(self);
+
+    /* Write the c:val element. */
+    if (error_bars->has_value)
+        _chart_write_error_val(self, error_bars->value);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, error_bars->line, NULL, NULL);
+
+    lxw_xml_end_tag(self->file, "c:errBars");
+}
+
+/*
+ * Write the <c:errBars> element.
+ */
+STATIC void
+_chart_write_error_bars(lxw_chart *self, lxw_chart_series *series)
+{
+    _chart_write_err_bars(self, series->x_error_bars);
+    _chart_write_err_bars(self, series->y_error_bars);
+}
+
+/*
+ * Write the <c:size> element.
+ */
+STATIC void
+_chart_write_marker_size(lxw_chart *self, uint8_t size)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", size);
+
+    lxw_xml_empty_tag(self->file, "c:size", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:marker> element.
+ */
+STATIC void
+_chart_write_marker(lxw_chart *self, lxw_chart_marker *marker)
+{
+    /* If there isn't a user defined marker use the default, if this chart
+     * type one. The default usually turns the marker off. */
+    if (!marker)
+        marker = self->default_marker;
+
+    if (!marker)
+        return;
+
+    if (marker->type == LXW_CHART_MARKER_AUTOMATIC)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:marker", NULL);
+
+    /* Write the c:symbol element. */
+    _chart_write_symbol(self, marker->type);
+
+    /* Write the c:size element. */
+    if (marker->size)
+        _chart_write_marker_size(self, marker->size);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, marker->line, marker->fill, marker->pattern);
+
+    lxw_xml_end_tag(self->file, "c:marker");
+}
+
+/*
+ * Write the <c:marker> element.
+ */
+STATIC void
+_chart_write_marker_value(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:marker", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:smooth> element.
+ */
+STATIC void
+_chart_write_smooth(lxw_chart *self, uint8_t smooth)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!smooth)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:smooth", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:scatterStyle> element.
+ */
+STATIC void
+_chart_write_scatter_style(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (self->type == LXW_CHART_SCATTER_SMOOTH
+        || self->type == LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "smoothMarker");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "lineMarker");
+
+    lxw_xml_empty_tag(self->file, "c:scatterStyle", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:cat> element.
+ */
+STATIC void
+_chart_write_cat(lxw_chart *self, lxw_chart_series *series)
+{
+    uint8_t has_string_cache = series->categories->has_string_cache;
+
+    /* Ignore <c:cat> elements for charts without category values. */
+    if (!series->categories->formula)
+        return;
+
+    self->cat_has_num_fmt = !has_string_cache;
+
+    lxw_xml_start_tag(self->file, "c:cat", NULL);
+
+    /* Write the c:numRef element. */
+    _chart_write_data_cache(self, series->categories, has_string_cache);
+
+    lxw_xml_end_tag(self->file, "c:cat");
+}
+
+/*
+ * Write the <c:xVal> element.
+ */
+STATIC void
+_chart_write_x_val(lxw_chart *self, lxw_chart_series *series)
+{
+    uint8_t has_string_cache = series->categories->has_string_cache;
+
+    lxw_xml_start_tag(self->file, "c:xVal", NULL);
+
+    /* Write the data cache elements. */
+    _chart_write_data_cache(self, series->categories, has_string_cache);
+
+    lxw_xml_end_tag(self->file, "c:xVal");
+}
+
+/*
+ * Write the <c:val> element.
+ */
+STATIC void
+_chart_write_val(lxw_chart *self, lxw_chart_series *series)
+{
+    lxw_xml_start_tag(self->file, "c:val", NULL);
+
+    /* Write the data cache elements. The string_cache is set to false since
+     * this should always be a number series. */
+    _chart_write_data_cache(self, series->values, LXW_FALSE);
+
+    lxw_xml_end_tag(self->file, "c:val");
+}
+
+/*
+ * Write the <c:yVal> element.
+ */
+STATIC void
+_chart_write_y_val(lxw_chart *self, lxw_chart_series *series)
+{
+    lxw_xml_start_tag(self->file, "c:yVal", NULL);
+
+    /* Write the data cache elements. The string_cache is set to false since
+     * this should always be a number series. */
+    _chart_write_data_cache(self, series->values, LXW_FALSE);
+
+    lxw_xml_end_tag(self->file, "c:yVal");
+}
+
+/*
+ * Write the <c:ser> element.
+ */
+STATIC void
+_chart_write_ser(lxw_chart *self, lxw_chart_series *series)
+{
+    uint16_t index = self->series_index++;
+
+    lxw_xml_start_tag(self->file, "c:ser", NULL);
+
+    /* Write the c:idx element. */
+    _chart_write_idx(self, index);
+
+    /* Write the c:order element. */
+    _chart_write_order(self, index);
+
+    /* Write the series name. */
+    _chart_write_series_name(self, series);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, series->line, series->fill, series->pattern);
+
+    /* Write the c:marker element. */
+    _chart_write_marker(self, series->marker);
+
+    /* Write the c:invertIfNegative element. */
+    _chart_write_invert_if_negative(self, series);
+
+    /* Write the char points. */
+    _chart_write_points(self, series);
+
+    /* Write the c:dLbls element. */
+    _chart_write_d_lbls(self, series);
+
+    /* Write the c:trendline element. */
+    _chart_write_trendline(self, series);
+
+    /* Write the c:errBars element. */
+    _chart_write_error_bars(self, series);
+
+    /* Write the c:cat element. */
+    _chart_write_cat(self, series);
+
+    /* Write the c:val element. */
+    _chart_write_val(self, series);
+
+    /* Write the c:smooth element. */
+    if (self->chart_group == LXW_CHART_SCATTER
+        || self->chart_group == LXW_CHART_LINE)
+        _chart_write_smooth(self, series->smooth);
+
+    lxw_xml_end_tag(self->file, "c:ser");
+}
+
+/*
+ * Write the <c:ser> element but with c:xVal/c:yVal instead of c:cat/c:val
+ * elements.
+ */
+STATIC void
+_chart_write_xval_ser(lxw_chart *self, lxw_chart_series *series)
+{
+    uint16_t index = self->series_index++;
+
+    lxw_xml_start_tag(self->file, "c:ser", NULL);
+
+    /* Write the c:idx element. */
+    _chart_write_idx(self, index);
+
+    /* Write the c:order element. */
+    _chart_write_order(self, index);
+
+    /* Write the series name. */
+    _chart_write_series_name(self, series);
+
+    /* Write the c:spPr element. */
+    _chart_write_sp_pr(self, series->line, series->fill, series->pattern);
+
+    /* Write the c:marker element. */
+    _chart_write_marker(self, series->marker);
+
+    /* Write the char points. */
+    _chart_write_points(self, series);
+
+    /* Write the c:dLbls element. */
+    _chart_write_d_lbls(self, series);
+
+    /* Write the c:trendline element. */
+    _chart_write_trendline(self, series);
+
+    /* Write the c:errBars element. */
+    _chart_write_error_bars(self, series);
+
+    /* Write the c:xVal element. */
+    _chart_write_x_val(self, series);
+
+    /* Write the yVal element. */
+    _chart_write_y_val(self, series);
+
+    /* Write the c:smooth element. */
+    _chart_write_smooth(self, series->smooth);
+
+    lxw_xml_end_tag(self->file, "c:ser");
+}
+
+/*
+ * Write the <c:orientation> element.
+ */
+STATIC void
+_chart_write_orientation(lxw_chart *self, uint8_t reverse)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    if (reverse)
+        LXW_PUSH_ATTRIBUTES_STR("val", "maxMin");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "minMax");
+
+    lxw_xml_empty_tag(self->file, "c:orientation", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:max> element.
+ */
+STATIC void
+_chart_write_max(lxw_chart *self, double max)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", max);
+
+    lxw_xml_empty_tag(self->file, "c:max", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:min> element.
+ */
+STATIC void
+_chart_write_min(lxw_chart *self, double min)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", min);
+
+    lxw_xml_empty_tag(self->file, "c:min", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:logBase> element.
+ */
+STATIC void
+_chart_write_log_base(lxw_chart *self, uint16_t log_base)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!log_base)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", log_base);
+
+    lxw_xml_empty_tag(self->file, "c:logBase", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:scaling> element.
+ */
+STATIC void
+_chart_write_scaling(lxw_chart *self, uint8_t reverse,
+                     uint8_t has_min, double min,
+                     uint8_t has_max, double max, uint16_t log_base)
+{
+    lxw_xml_start_tag(self->file, "c:scaling", NULL);
+
+    /* Write the c:logBase element. */
+    _chart_write_log_base(self, log_base);
+
+    /* Write the c:orientation element. */
+    _chart_write_orientation(self, reverse);
+
+    if (has_max) {
+        /* Write the c:max element. */
+        _chart_write_max(self, max);
+    }
+
+    if (has_min) {
+        /* Write the c:min element. */
+        _chart_write_min(self, min);
+    }
+
+    lxw_xml_end_tag(self->file, "c:scaling");
+}
+
+/*
+ * Write the <c:axPos> element.
+ */
+STATIC void
+_chart_write_axis_pos(lxw_chart *self, uint8_t position, uint8_t reverse)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Reverse the axis direction if required. */
+    position ^= reverse;
+
+    if (position == LXW_CHART_AXIS_RIGHT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "r");
+    else if (position == LXW_CHART_AXIS_LEFT)
+        LXW_PUSH_ATTRIBUTES_STR("val", "l");
+    else if (position == LXW_CHART_AXIS_TOP)
+        LXW_PUSH_ATTRIBUTES_STR("val", "t");
+    else if (position == LXW_CHART_AXIS_BOTTOM)
+        LXW_PUSH_ATTRIBUTES_STR("val", "b");
+
+    lxw_xml_empty_tag(self->file, "c:axPos", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:tickLblPos> element.
+ */
+STATIC void
+_chart_write_tick_label_pos(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_HIGH)
+        LXW_PUSH_ATTRIBUTES_STR("val", "high");
+    else if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_LOW)
+        LXW_PUSH_ATTRIBUTES_STR("val", "low");
+    else if (axis->label_position == LXW_CHART_AXIS_LABEL_POSITION_NONE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "none");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "nextTo");
+
+    lxw_xml_empty_tag(self->file, "c:tickLblPos", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:crossAx> element.
+ */
+STATIC void
+_chart_write_cross_axis(lxw_chart *self, uint32_t axis_id)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", axis_id);
+
+    lxw_xml_empty_tag(self->file, "c:crossAx", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:crosses> element.
+ */
+STATIC void
+_chart_write_crosses(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (axis->crossing_max)
+        LXW_PUSH_ATTRIBUTES_STR("val", "max");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "autoZero");
+
+    lxw_xml_empty_tag(self->file, "c:crosses", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:crossesAt> element.
+ */
+STATIC void
+_chart_write_crosses_at(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_DBL("val", axis->crossing);
+
+    lxw_xml_empty_tag(self->file, "c:crossesAt", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:auto> element.
+ */
+STATIC void
+_chart_write_auto(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:auto", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:lblAlgn> element.
+ */
+STATIC void
+_chart_write_label_align(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "ctr");
+
+    lxw_xml_empty_tag(self->file, "c:lblAlgn", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:tickLblSkip> element.
+ */
+STATIC void
+_chart_write_tick_label_skip(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->interval_unit)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", axis->interval_unit);
+
+    lxw_xml_empty_tag(self->file, "c:tickLblSkip", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:tickMarkSkip> element.
+ */
+STATIC void
+_chart_write_tick_mark_skip(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->interval_tick)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", axis->interval_tick);
+
+    lxw_xml_empty_tag(self->file, "c:tickMarkSkip", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:majorUnit> element.
+ */
+STATIC void
+_chart_write_major_unit(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->has_major_unit)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", axis->major_unit);
+
+    lxw_xml_empty_tag(self->file, "c:majorUnit", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:minorUnit> element.
+ */
+STATIC void
+_chart_write_minor_unit(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->has_minor_unit)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", axis->minor_unit);
+
+    lxw_xml_empty_tag(self->file, "c:minorUnit", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dispUnits> element.
+ */
+STATIC void
+_chart_write_disp_units(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!axis->display_units)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    lxw_xml_start_tag(self->file, "c:dispUnits", NULL);
+
+    if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDREDS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "hundreds");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_THOUSANDS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "thousands");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TEN_THOUSANDS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "tenThousands");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDRED_THOUSANDS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "hundredThousands");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_MILLIONS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "millions");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TEN_MILLIONS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "tenMillions");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_HUNDRED_MILLIONS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "hundredMillions");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_BILLIONS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "billions");
+    else if (axis->display_units == LXW_CHART_AXIS_UNITS_TRILLIONS)
+        LXW_PUSH_ATTRIBUTES_STR("val", "trillions");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "hundreds");
+
+    lxw_xml_empty_tag(self->file, "c:builtInUnit", &attributes);
+
+    if (axis->display_units_visible) {
+        lxw_xml_start_tag(self->file, "c:dispUnitsLbl", NULL);
+        lxw_xml_empty_tag(self->file, "c:layout", NULL);
+        lxw_xml_end_tag(self->file, "c:dispUnitsLbl");
+    }
+
+    lxw_xml_end_tag(self->file, "c:dispUnits");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:lblOffset> element.
+ */
+STATIC void
+_chart_write_label_offset(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "100");
+
+    lxw_xml_empty_tag(self->file, "c:lblOffset", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:majorGridlines> element.
+ */
+STATIC void
+_chart_write_major_gridlines(lxw_chart *self, lxw_chart_axis *axis)
+{
+    if (!axis->major_gridlines.visible)
+        return;
+
+    if (axis->major_gridlines.line) {
+        lxw_xml_start_tag(self->file, "c:majorGridlines", NULL);
+
+        /* Write the c:spPr element for the axis line. */
+        _chart_write_sp_pr(self, axis->major_gridlines.line, NULL, NULL);
+
+        lxw_xml_end_tag(self->file, "c:majorGridlines");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:majorGridlines", NULL);
+    }
+}
+
+/*
+ * Write the <c:minorGridlines> element.
+ */
+STATIC void
+_chart_write_minor_gridlines(lxw_chart *self, lxw_chart_axis *axis)
+{
+    if (!axis->minor_gridlines.visible)
+        return;
+
+    if (axis->minor_gridlines.line) {
+        lxw_xml_start_tag(self->file, "c:minorGridlines", NULL);
+
+        /* Write the c:spPr element for the axis line. */
+        _chart_write_sp_pr(self, axis->minor_gridlines.line, NULL, NULL);
+
+        lxw_xml_end_tag(self->file, "c:minorGridlines");
+
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:minorGridlines", NULL);
+    }
+}
+
+/*
+ * Write the <c:numberFormat> element. Note: It is assumed that if a user
+ * defined number format is supplied (i.e., non-default) then the sourceLinked
+ * attribute is 0. The user can override this if required.
+ */
+STATIC void
+_chart_write_number_format(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char *num_format;
+    uint8_t source_linked = 1;
+
+    /* Set the number format to the axis default if not set. */
+    if (axis->num_format)
+        num_format = axis->num_format;
+    else
+        num_format = axis->default_num_format;
+
+    /* Check if a user defined number format has been set. */
+    if (strcmp(num_format, axis->default_num_format))
+        source_linked = 0;
+
+    /* Allow override of sourceLinked. */
+    if (axis->source_linked)
+        source_linked = 1;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("formatCode", num_format);
+    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", source_linked);
+
+    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:numFmt> element. Special case handler for category axes which
+ * don't always have a number format.
+ */
+STATIC void
+_chart_write_cat_number_format(lxw_chart *self, lxw_chart_axis *axis)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char *num_format;
+    uint8_t source_linked = 1;
+    uint8_t default_format = LXW_TRUE;
+
+    /* Set the number format to the axis default if not set. */
+    if (axis->num_format)
+        num_format = axis->num_format;
+    else
+        num_format = axis->default_num_format;
+
+    /* Check if a user defined number format has been set. */
+    if (strcmp(num_format, axis->default_num_format)) {
+        source_linked = 0;
+        default_format = LXW_FALSE;
+    }
+
+    /* Allow override of sourceLinked. */
+    if (axis->source_linked)
+        source_linked = 1;
+
+    /* Skip if cat doesn't have a num format (unless it is non-default). */
+    if (!self->cat_has_num_fmt && default_format)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("formatCode", num_format);
+    LXW_PUSH_ATTRIBUTES_INT("sourceLinked", source_linked);
+
+    lxw_xml_empty_tag(self->file, "c:numFmt", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:crossBetween> element.
+ */
+STATIC void
+_chart_write_cross_between(lxw_chart *self, uint8_t position)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!position)
+        position = self->default_cross_between;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (position == LXW_CHART_AXIS_POSITION_ON_TICK)
+        LXW_PUSH_ATTRIBUTES_STR("val", "midCat");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "between");
+
+    lxw_xml_empty_tag(self->file, "c:crossBetween", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:overlay> element.
+ */
+STATIC void
+_chart_write_overlay(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:overlay", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:legendPos> element.
+ */
+STATIC void
+_chart_write_legend_pos(lxw_chart *self, char *position)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("val", position);
+
+    lxw_xml_empty_tag(self->file, "c:legendPos", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:legendEntry> element.
+ */
+STATIC void
+_chart_write_legend_entry(lxw_chart *self, uint16_t index)
+{
+    lxw_xml_start_tag(self->file, "c:legendEntry", NULL);
+
+    /* Write the c:idx element. */
+    _chart_write_idx(self, self->delete_series[index]);
+
+    /* Write the c:delete element. */
+    _chart_write_delete(self);
+
+    lxw_xml_end_tag(self->file, "c:legendEntry");
+}
+
+/*
+ * Write the <c:legend> element.
+ */
+STATIC void
+_chart_write_legend(lxw_chart *self)
+{
+    uint8_t has_overlay = LXW_FALSE;
+    uint16_t index;
+
+    if (self->legend.position == LXW_CHART_LEGEND_NONE)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:legend", NULL);
+
+    /* Write the c:legendPos element. */
+    switch (self->legend.position) {
+        case LXW_CHART_LEGEND_LEFT:
+            _chart_write_legend_pos(self, "l");
+            break;
+        case LXW_CHART_LEGEND_TOP:
+            _chart_write_legend_pos(self, "t");
+            break;
+        case LXW_CHART_LEGEND_BOTTOM:
+            _chart_write_legend_pos(self, "b");
+            break;
+        case LXW_CHART_LEGEND_OVERLAY_RIGHT:
+            _chart_write_legend_pos(self, "r");
+            has_overlay = LXW_TRUE;
+            break;
+        case LXW_CHART_LEGEND_OVERLAY_LEFT:
+            _chart_write_legend_pos(self, "l");
+            has_overlay = LXW_TRUE;
+            break;
+        default:
+            _chart_write_legend_pos(self, "r");
+    }
+
+    /* Remove series labels from the legend. */
+    for (index = 0; index < self->delete_series_count; index++) {
+        /* Write the c:legendEntry element. */
+        _chart_write_legend_entry(self, index);
+    }
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    if (self->chart_group == LXW_CHART_PIE
+        || self->chart_group == LXW_CHART_DOUGHNUT) {
+        /* Write the c:overlay element. */
+        if (has_overlay)
+            _chart_write_overlay(self);
+
+        /* Write the c:txPr element for Pie/Doughnut charts. */
+        _chart_write_tx_pr_pie(self, LXW_FALSE, self->legend.font);
+    }
+    else {
+        /* Write the c:txPr element for all other charts. */
+        if (self->legend.font)
+            _chart_write_tx_pr(self, LXW_FALSE, self->legend.font);
+
+        /* Write the c:overlay element. */
+        if (has_overlay)
+            _chart_write_overlay(self);
+    }
+
+    lxw_xml_end_tag(self->file, "c:legend");
+}
+
+/*
+ * Write the <c:plotVisOnly> element.
+ */
+STATIC void
+_chart_write_plot_vis_only(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (self->show_hidden_data)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:plotVisOnly", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:headerFooter> element.
+ */
+STATIC void
+_chart_write_header_footer(lxw_chart *self)
+{
+    lxw_xml_empty_tag(self->file, "c:headerFooter", NULL);
+}
+
+/*
+ * Write the <c:pageMargins> element.
+ */
+STATIC void
+_chart_write_page_margins(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("b", "0.75");
+    LXW_PUSH_ATTRIBUTES_STR("l", "0.7");
+    LXW_PUSH_ATTRIBUTES_STR("r", "0.7");
+    LXW_PUSH_ATTRIBUTES_STR("t", "0.75");
+    LXW_PUSH_ATTRIBUTES_STR("header", "0.3");
+    LXW_PUSH_ATTRIBUTES_STR("footer", "0.3");
+
+    lxw_xml_empty_tag(self->file, "c:pageMargins", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:pageSetup> element.
+ */
+STATIC void
+_chart_write_page_setup(lxw_chart *self)
+{
+    lxw_xml_empty_tag(self->file, "c:pageSetup", NULL);
+}
+
+/*
+ * Write the <c:printSettings> element.
+ */
+STATIC void
+_chart_write_print_settings(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:printSettings", NULL);
+
+    /* Write the c:headerFooter element. */
+    _chart_write_header_footer(self);
+
+    /* Write the c:pageMargins element. */
+    _chart_write_page_margins(self);
+
+    /* Write the c:pageSetup element. */
+    _chart_write_page_setup(self);
+
+    lxw_xml_end_tag(self->file, "c:printSettings");
+}
+
+/*
+ * Write the <c:overlap> element.
+ */
+STATIC void
+_chart_write_overlap(lxw_chart *self, int8_t overlap)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!overlap)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", overlap);
+
+    lxw_xml_empty_tag(self->file, "c:overlap", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:gapWidth> element.
+ */
+STATIC void
+_chart_write_gap_width(lxw_chart *self, uint16_t gap)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (gap == LXW_CHART_DEFAULT_GAP)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", gap);
+
+    lxw_xml_empty_tag(self->file, "c:gapWidth", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dispBlanksAs> element.
+ */
+STATIC void
+_chart_write_disp_blanks_as(lxw_chart *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (self->show_blanks_as != LXW_CHART_BLANKS_AS_ZERO
+        && self->show_blanks_as != LXW_CHART_BLANKS_AS_CONNECTED)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (self->show_blanks_as == LXW_CHART_BLANKS_AS_ZERO)
+        LXW_PUSH_ATTRIBUTES_STR("val", "zero");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "span");
+
+    lxw_xml_empty_tag(self->file, "c:dispBlanksAs", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showHorzBorder> element.
+ */
+STATIC void
+_chart_write_show_horz_border(lxw_chart *self, uint8_t value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!value)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showHorzBorder", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showVertBorder> element.
+ */
+STATIC void
+_chart_write_show_vert_border(lxw_chart *self, uint8_t value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!value)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showVertBorder", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showOutline> element.
+ */
+STATIC void
+_chart_write_show_outline(lxw_chart *self, uint8_t value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!value)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showOutline", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:showKeys> element.
+ */
+STATIC void
+_chart_write_show_keys(lxw_chart *self, uint8_t value)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!value)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("val", "1");
+
+    lxw_xml_empty_tag(self->file, "c:showKeys", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <c:dTable> element.
+ */
+STATIC void
+_chart_write_d_table(lxw_chart *self)
+{
+    if (!self->has_table)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:dTable", NULL);
+
+    /* Write the c:showHorzBorder element. */
+    _chart_write_show_horz_border(self, self->has_table_horizontal);
+
+    /* Write the c:showVertBorder element. */
+    _chart_write_show_vert_border(self, self->has_table_vertical);
+
+    /* Write the c:showOutline element. */
+    _chart_write_show_outline(self, self->has_table_outline);
+
+    /* Write the c:showKeys element. */
+    _chart_write_show_keys(self, self->has_table_legend_keys);
+
+    /* Write the c:txPr element. */
+    if (self->table_font)
+        _chart_write_tx_pr(self, LXW_FALSE, self->table_font);
+
+    lxw_xml_end_tag(self->file, "c:dTable");
+}
+
+/*
+ * Write the <c:upBars> element.
+ */
+STATIC void
+_chart_write_up_bars(lxw_chart *self, lxw_chart_line *line,
+                     lxw_chart_fill *fill)
+{
+    if (line || fill) {
+        lxw_xml_start_tag(self->file, "c:upBars", NULL);
+
+        /* Write the c:spPr element. */
+        _chart_write_sp_pr(self, line, fill, NULL);
+
+        lxw_xml_end_tag(self->file, "c:upBars");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:upBars", NULL);
+    }
+}
+
+/*
+ * Write the <c:downBars> element.
+ */
+STATIC void
+_chart_write_down_bars(lxw_chart *self, lxw_chart_line *line,
+                       lxw_chart_fill *fill)
+{
+    if (line || fill) {
+        lxw_xml_start_tag(self->file, "c:downBars", NULL);
+
+        /* Write the c:spPr element. */
+        _chart_write_sp_pr(self, line, fill, NULL);
+
+        lxw_xml_end_tag(self->file, "c:downBars");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:downBars", NULL);
+    }
+}
+
+/*
+ * Write the <c:upDownBars> element.
+ */
+STATIC void
+_chart_write_up_down_bars(lxw_chart *self)
+{
+    if (!self->has_up_down_bars)
+        return;
+
+    lxw_xml_start_tag(self->file, "c:upDownBars", NULL);
+
+    /* Write the c:gapWidth element. */
+    _chart_write_gap_width(self, 150);
+
+    /* Write the c:upBars element. */
+    _chart_write_up_bars(self, self->up_bar_line, self->up_bar_fill);
+
+    /* Write the c:downBars element. */
+    _chart_write_down_bars(self, self->down_bar_line, self->down_bar_fill);
+
+    lxw_xml_end_tag(self->file, "c:upDownBars");
+}
+
+/*
+ * Write the <c:dropLines> element.
+ */
+STATIC void
+_chart_write_drop_lines(lxw_chart *self)
+{
+    if (!self->has_drop_lines)
+        return;
+
+    if (self->drop_lines_line) {
+        lxw_xml_start_tag(self->file, "c:dropLines", NULL);
+
+        _chart_write_sp_pr(self, self->drop_lines_line, NULL, NULL);
+
+        lxw_xml_end_tag(self->file, "c:dropLines");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:dropLines", NULL);
+    }
+}
+
+/*
+ * Write the <c:hiLowLines> element.
+ */
+STATIC void
+_chart_write_hi_low_lines(lxw_chart *self)
+{
+    if (!self->has_high_low_lines)
+        return;
+
+    if (self->high_low_lines_line) {
+        lxw_xml_start_tag(self->file, "c:hiLowLines", NULL);
+
+        _chart_write_sp_pr(self, self->high_low_lines_line, NULL, NULL);
+
+        lxw_xml_end_tag(self->file, "c:hiLowLines");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "c:hiLowLines", NULL);
+    }
+}
+
+/*
+ * Write the <c:title> element.
+ */
+STATIC void
+_chart_write_title(lxw_chart *self, lxw_chart_title *title)
+{
+    if (title->name) {
+        /* Write the c:title element. */
+        _chart_write_title_rich(self, title);
+    }
+    else if (title->range->formula) {
+        /* Write the c:title element. */
+        _chart_write_title_formula(self, title);
+    }
+}
+
+/*
+ * Write the <c:title> element.
+ */
+STATIC void
+_chart_write_chart_title(lxw_chart *self)
+{
+    if (self->title.off) {
+        /* Write the c:autoTitleDeleted element. */
+        _chart_write_auto_title_deleted(self);
+    }
+    else {
+        /* Write the c:title element. */
+        _chart_write_title(self, &self->title);
+    }
+}
+
+/*
+ * Write the <c:catAx> element. Usually the X axis.
+ */
+STATIC void
+_chart_write_cat_axis(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:catAx", NULL);
+
+    _chart_write_axis_id(self, self->axis_id_1);
+
+    /* Write the c:scaling element. Note we can't set max, min, or log base
+     * for a Category axis in Excel.*/
+    _chart_write_scaling(self,
+                         self->x_axis->reverse,
+                         LXW_FALSE, 0.0, LXW_FALSE, 0.0, 0);
+
+    /* Write the c:delete element to hide axis. */
+    if (self->x_axis->hidden)
+        _chart_write_delete(self);
+
+    /* Write the c:axPos element. */
+    _chart_write_axis_pos(self, self->x_axis->axis_position,
+                          self->y_axis->reverse);
+
+    /* Write the c:majorGridlines element. */
+    _chart_write_major_gridlines(self, self->x_axis);
+
+    /* Write the c:minorGridlines element. */
+    _chart_write_minor_gridlines(self, self->x_axis);
+
+    /* Write the axis title elements. */
+    self->x_axis->title.is_horizontal = self->has_horiz_cat_axis;
+    _chart_write_title(self, &self->x_axis->title);
+
+    /* Write the c:numFmt element. */
+    _chart_write_cat_number_format(self, self->x_axis);
+
+    /* Write the c:majorTickMark element. */
+    _chart_write_major_tick_mark(self, self->x_axis);
+
+    /* Write the c:minorTickMark element. */
+    _chart_write_minor_tick_mark(self, self->x_axis);
+
+    /* Write the c:tickLblPos element. */
+    _chart_write_tick_label_pos(self, self->x_axis);
+
+    /* Write the c:spPr element for the axis line. */
+    _chart_write_sp_pr(self, self->x_axis->line, self->x_axis->fill,
+                       self->x_axis->pattern);
+
+    /* Write the axis font elements. */
+    _chart_write_axis_font(self, self->x_axis->num_font);
+
+    /* Write the c:crossAx element. */
+    _chart_write_cross_axis(self, self->axis_id_2);
+
+    /* Write the c:crosses element. */
+    if (!self->y_axis->has_crossing || self->y_axis->crossing_max)
+        _chart_write_crosses(self, self->y_axis);
+    else
+        _chart_write_crosses_at(self, self->y_axis);
+
+    /* Write the c:auto element. */
+    _chart_write_auto(self);
+
+    /* Write the c:lblAlgn element. */
+    _chart_write_label_align(self);
+
+    /* Write the c:lblOffset element. */
+    _chart_write_label_offset(self);
+
+    /* Write the c:tickLblSkip element. */
+    _chart_write_tick_label_skip(self, self->x_axis);
+
+    /* Write the c:tickMarkSkip element. */
+    _chart_write_tick_mark_skip(self, self->x_axis);
+
+    lxw_xml_end_tag(self->file, "c:catAx");
+}
+
+/*
+ * Write the <c:valAx> element.
+ */
+STATIC void
+_chart_write_val_axis(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:valAx", NULL);
+
+    _chart_write_axis_id(self, self->axis_id_2);
+
+    /* Write the c:scaling element. */
+    _chart_write_scaling(self,
+                         self->y_axis->reverse,
+                         self->y_axis->has_min, self->y_axis->min,
+                         self->y_axis->has_max, self->y_axis->max,
+                         self->y_axis->log_base);
+
+    /* Write the c:delete element to hide axis. */
+    if (self->y_axis->hidden)
+        _chart_write_delete(self);
+
+    /* Write the c:axPos element. */
+    _chart_write_axis_pos(self, self->y_axis->axis_position,
+                          self->x_axis->reverse);
+
+    /* Write the c:majorGridlines element. */
+    _chart_write_major_gridlines(self, self->y_axis);
+
+    /* Write the c:minorGridlines element. */
+    _chart_write_minor_gridlines(self, self->y_axis);
+
+    /* Write the axis title elements. */
+    self->y_axis->title.is_horizontal = self->has_horiz_val_axis;
+    _chart_write_title(self, &self->y_axis->title);
+
+    /* Write the c:numFmt element. */
+    _chart_write_number_format(self, self->y_axis);
+
+    /* Write the c:majorTickMark element. */
+    _chart_write_major_tick_mark(self, self->y_axis);
+
+    /* Write the c:minorTickMark element. */
+    _chart_write_minor_tick_mark(self, self->y_axis);
+
+    /* Write the c:tickLblPos element. */
+    _chart_write_tick_label_pos(self, self->y_axis);
+
+    /* Write the c:spPr element for the axis line. */
+    _chart_write_sp_pr(self, self->y_axis->line, self->y_axis->fill,
+                       self->y_axis->pattern);
+
+    /* Write the axis font elements. */
+    _chart_write_axis_font(self, self->y_axis->num_font);
+
+    /* Write the c:crossAx element. */
+    _chart_write_cross_axis(self, self->axis_id_1);
+
+    /* Write the c:crosses element. */
+    if (!self->x_axis->has_crossing || self->x_axis->crossing_max)
+        _chart_write_crosses(self, self->x_axis);
+    else
+        _chart_write_crosses_at(self, self->x_axis);
+
+    /* Write the c:crossBetween element. */
+    _chart_write_cross_between(self, self->x_axis->position_axis);
+
+    /* Write the c:majorUnit element. */
+    _chart_write_major_unit(self, self->y_axis);
+
+    /* Write the c:minorUnit element. */
+    _chart_write_minor_unit(self, self->y_axis);
+
+    /* Write the c:dispUnits element. */
+    _chart_write_disp_units(self, self->y_axis);
+
+    lxw_xml_end_tag(self->file, "c:valAx");
+}
+
+/*
+ * Write the <c:valAx> element. This is for the second valAx in scatter plots.
+ */
+STATIC void
+_chart_write_cat_val_axis(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:valAx", NULL);
+
+    _chart_write_axis_id(self, self->axis_id_1);
+
+    /* Write the c:scaling element. */
+    _chart_write_scaling(self,
+                         self->x_axis->reverse,
+                         self->x_axis->has_min, self->x_axis->min,
+                         self->x_axis->has_max, self->x_axis->max,
+                         self->x_axis->log_base);
+
+    /* Write the c:delete element to hide axis. */
+    if (self->x_axis->hidden)
+        _chart_write_delete(self);
+
+    /* Write the c:axPos element. */
+    _chart_write_axis_pos(self, self->x_axis->axis_position,
+                          self->y_axis->reverse);
+
+    /* Write the c:majorGridlines element. */
+    _chart_write_major_gridlines(self, self->x_axis);
+
+    /* Write the c:minorGridlines element. */
+    _chart_write_minor_gridlines(self, self->x_axis);
+
+    /* Write the axis title elements. */
+    self->x_axis->title.is_horizontal = self->has_horiz_val_axis;
+    _chart_write_title(self, &self->x_axis->title);
+
+    /* Write the c:numFmt element. */
+    _chart_write_number_format(self, self->x_axis);
+
+    /* Write the c:majorTickMark element. */
+    _chart_write_major_tick_mark(self, self->x_axis);
+
+    /* Write the c:minorTickMark element. */
+    _chart_write_minor_tick_mark(self, self->x_axis);
+
+    /* Write the c:tickLblPos element. */
+    _chart_write_tick_label_pos(self, self->x_axis);
+
+    /* Write the c:spPr element for the axis line. */
+    _chart_write_sp_pr(self, self->x_axis->line, self->x_axis->fill,
+                       self->x_axis->pattern);
+
+    /* Write the axis font elements. */
+    _chart_write_axis_font(self, self->x_axis->num_font);
+
+    /* Write the c:crossAx element. */
+    _chart_write_cross_axis(self, self->axis_id_2);
+
+    /* Write the c:crosses element. */
+    if (!self->y_axis->has_crossing || self->y_axis->crossing_max)
+        _chart_write_crosses(self, self->y_axis);
+    else
+        _chart_write_crosses_at(self, self->y_axis);
+
+    /* Write the c:crossBetween element. */
+    _chart_write_cross_between(self, self->y_axis->position_axis);
+
+    /* Write the c:majorUnit element. */
+    _chart_write_major_unit(self, self->x_axis);
+
+    /* Write the c:minorUnit element. */
+    _chart_write_minor_unit(self, self->x_axis);
+
+    /* Write the c:dispUnits element. */
+    _chart_write_disp_units(self, self->x_axis);
+
+    lxw_xml_end_tag(self->file, "c:valAx");
+}
+
+/*
+ * Write the <c:barDir> element.
+ */
+STATIC void
+_chart_write_bar_dir(lxw_chart *self, char *type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", type);
+
+    lxw_xml_empty_tag(self->file, "c:barDir", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write a area chart.
+ */
+STATIC void
+_chart_write_area_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:areaChart", NULL);
+
+    /* Write the c:grouping element. */
+    _chart_write_grouping(self, self->grouping);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:dropLines element. */
+    _chart_write_drop_lines(self);
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:areaChart");
+}
+
+/*
+ * Write a bar chart.
+ */
+STATIC void
+_chart_write_bar_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:barChart", NULL);
+
+    /* Write the c:barDir element. */
+    _chart_write_bar_dir(self, "bar");
+
+    /* Write the c:grouping element. */
+    _chart_write_grouping(self, self->grouping);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:gapWidth element. */
+    _chart_write_gap_width(self, self->gap_y1);
+
+    /* Write the c:overlap element. */
+    _chart_write_overlap(self, self->overlap_y1);
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:barChart");
+}
+
+/*
+ * Write a column chart.
+ */
+STATIC void
+_chart_write_column_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:barChart", NULL);
+
+    /* Write the c:barDir element. */
+    _chart_write_bar_dir(self, "col");
+
+    /* Write the c:grouping element. */
+    _chart_write_grouping(self, self->grouping);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:gapWidth element. */
+    _chart_write_gap_width(self, self->gap_y1);
+
+    /* Write the c:overlap element. */
+    _chart_write_overlap(self, self->overlap_y1);
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:barChart");
+}
+
+/*
+ * Write a doughnut chart.
+ */
+STATIC void
+_chart_write_doughnut_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:doughnutChart", NULL);
+
+    /* Write the c:varyColors element. */
+    _chart_write_vary_colors(self);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:firstSliceAng element. */
+    _chart_write_first_slice_ang(self);
+
+    /* Write the c:holeSize element. */
+    _chart_write_hole_size(self);
+
+    lxw_xml_end_tag(self->file, "c:doughnutChart");
+}
+
+/*
+ * Write a line chart.
+ */
+STATIC void
+_chart_write_line_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:lineChart", NULL);
+
+    /* Write the c:grouping element. */
+    _chart_write_grouping(self, self->grouping);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:dropLines element. */
+    _chart_write_drop_lines(self);
+
+    /* Write the c:hiLowLines element. */
+    _chart_write_hi_low_lines(self);
+
+    /* Write the c:upDownBars element. */
+    _chart_write_up_down_bars(self);
+
+    /* Write the c:marker element. */
+    _chart_write_marker_value(self);
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:lineChart");
+}
+
+/*
+ * Write a pie chart.
+ */
+STATIC void
+_chart_write_pie_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:pieChart", NULL);
+
+    /* Write the c:varyColors element. */
+    _chart_write_vary_colors(self);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:firstSliceAng element. */
+    _chart_write_first_slice_ang(self);
+
+    lxw_xml_end_tag(self->file, "c:pieChart");
+}
+
+/*
+ * Write a scatter chart.
+ */
+STATIC void
+_chart_write_scatter_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:scatterChart", NULL);
+
+    /* Write the c:scatterStyle element. */
+    _chart_write_scatter_style(self);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+
+        /* Add default scatter chart formatting to the series data unless
+         * it has already been specified by the user.*/
+        if (self->type == LXW_CHART_SCATTER) {
+            if (!series->line) {
+                lxw_chart_line line = {
+                    0x000000,
+                    LXW_TRUE,
+                    2.25,
+                    LXW_CHART_LINE_DASH_SOLID,
+                    0,
+                    LXW_FALSE
+                };
+                series->line = _chart_convert_line_args(&line);
+            }
+        }
+
+        /* Write the c:ser element. */
+        _chart_write_xval_ser(self, series);
+    }
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:scatterChart");
+}
+
+/*
+ * Write a radar chart.
+ */
+STATIC void
+_chart_write_radar_chart(lxw_chart *self)
+{
+    lxw_chart_series *series;
+
+    lxw_xml_start_tag(self->file, "c:radarChart", NULL);
+
+    /* Write the c:radarStyle element. */
+    _chart_write_radar_style(self);
+
+    STAILQ_FOREACH(series, self->series_list, list_pointers) {
+        /* Write the c:ser element. */
+        _chart_write_ser(self, series);
+    }
+
+    /* Write the c:axId elements. */
+    _chart_write_axis_ids(self);
+
+    lxw_xml_end_tag(self->file, "c:radarChart");
+}
+
+/*
+ * Reverse the opposite axis position if crossing position is "max".
+ */
+STATIC void
+_chart_adjust_max_crossing(lxw_chart *self)
+{
+    if (self->x_axis->crossing_max)
+        self->y_axis->axis_position ^= 1;
+
+    if (self->y_axis->crossing_max)
+        self->x_axis->axis_position ^= 1;
+}
+
+/*
+ * Write the <c:plotArea> element.
+ */
+STATIC void
+_chart_write_scatter_plot_area(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:plotArea", NULL);
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    /* Write subclass chart type elements for primary and secondary axes. */
+    self->write_chart_type(self);
+
+    /* Reverse the opposite axis position if crossing position is "max". */
+    _chart_adjust_max_crossing(self);
+
+    /* Write the c:catAx element. */
+    _chart_write_cat_val_axis(self);
+
+    self->has_horiz_val_axis = LXW_TRUE;
+
+    /* Write the c:valAx element. */
+    _chart_write_val_axis(self);
+
+    /* Write the c:spPr element for the plotarea formatting. */
+    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
+                       self->plotarea_pattern);
+
+    lxw_xml_end_tag(self->file, "c:plotArea");
+}
+
+/*
+ * Write the <c:plotArea> element. Special handling for pie/doughnut.
+ */
+STATIC void
+_chart_write_pie_plot_area(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:plotArea", NULL);
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    /* Write subclass chart type elements for primary and secondary axes. */
+    self->write_chart_type(self);
+
+    /* Write the c:spPr element for the plotarea formatting. */
+    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
+                       self->plotarea_pattern);
+
+    lxw_xml_end_tag(self->file, "c:plotArea");
+}
+
+/*
+ * Write the <c:plotArea> element.
+ */
+STATIC void
+_chart_write_plot_area(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:plotArea", NULL);
+
+    /* Write the c:layout element. */
+    _chart_write_layout(self);
+
+    /* Write subclass chart type elements for primary and secondary axes. */
+    self->write_chart_type(self);
+
+    /* Reverse the opposite axis position if crossing position is "max". */
+    _chart_adjust_max_crossing(self);
+
+    /* Write the c:catAx element. */
+    _chart_write_cat_axis(self);
+
+    /* Write the c:valAx element. */
+    _chart_write_val_axis(self);
+
+    /* Write the c:dTable element. */
+    _chart_write_d_table(self);
+
+    /* Write the c:spPr element for the plotarea formatting. */
+    _chart_write_sp_pr(self, self->plotarea_line, self->plotarea_fill,
+                       self->plotarea_pattern);
+
+    lxw_xml_end_tag(self->file, "c:plotArea");
+}
+
+/*
+ * Write the <c:chart> element.
+ */
+STATIC void
+_chart_write_chart(lxw_chart *self)
+{
+    lxw_xml_start_tag(self->file, "c:chart", NULL);
+
+    /* Write the c:title element. */
+    _chart_write_chart_title(self);
+
+    /* Write the c:plotArea element. */
+    self->write_plot_area(self);
+
+    /* Write the c:legend element. */
+    _chart_write_legend(self);
+
+    /* Write the c:plotVisOnly element. */
+    _chart_write_plot_vis_only(self);
+
+    /* Write the c:dispBlanksAs element. */
+    _chart_write_disp_blanks_as(self);
+
+    lxw_xml_end_tag(self->file, "c:chart");
+}
+
+/*
+ * Initialize a area chart.
+ */
+STATIC void
+_chart_initialize_area_chart(lxw_chart *self, uint8_t type)
+{
+    self->chart_group = LXW_CHART_AREA;
+    self->grouping = LXW_GROUPING_STANDARD;
+    self->default_cross_between = LXW_CHART_AXIS_POSITION_ON_TICK;
+    self->x_axis->is_category = LXW_TRUE;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_CENTER;
+
+    if (type == LXW_CHART_AREA_STACKED) {
+        self->grouping = LXW_GROUPING_STACKED;
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+    }
+
+    if (type == LXW_CHART_AREA_STACKED_PERCENT) {
+        self->grouping = LXW_GROUPING_PERCENTSTACKED;
+        _chart_axis_set_default_num_format(self->y_axis, "0%");
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+    }
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_area_chart;
+    self->write_plot_area = _chart_write_plot_area;
+}
+
+/*
+ * Swap/reverse the bar chart axes prior to writing. It is the only chart
+ * with the category axis in the vertical direction.
+ */
+STATIC void
+_chart_swap_bar_axes(lxw_chart *self)
+{
+    lxw_chart_axis *tmp = self->x_axis;
+    self->x_axis = self->y_axis;
+    self->y_axis = tmp;
+}
+
+/*
+ * Initialize a bar chart.
+ */
+STATIC void
+_chart_initialize_bar_chart(lxw_chart *self, uint8_t type)
+{
+    /* Note: Bar chart category/value axis are reversed in comparison to
+     *       other charts. Some of the defaults reflect this. */
+    self->chart_group = LXW_CHART_BAR;
+    self->x_axis->major_gridlines.visible = LXW_TRUE;
+    self->y_axis->major_gridlines.visible = LXW_FALSE;
+    self->y_axis->is_category = LXW_TRUE;
+    self->x_axis->is_value = LXW_TRUE;
+    self->has_horiz_cat_axis = LXW_TRUE;
+    self->has_horiz_val_axis = LXW_FALSE;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_OUTSIDE_END;
+
+    if (type == LXW_CHART_BAR_STACKED) {
+        self->grouping = LXW_GROUPING_STACKED;
+        self->has_overlap = LXW_TRUE;
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+        self->overlap_y1 = 100;
+
+    }
+
+    if (type == LXW_CHART_BAR_STACKED_PERCENT) {
+        self->grouping = LXW_GROUPING_PERCENTSTACKED;
+        _chart_axis_set_default_num_format(self->x_axis, "0%");
+        self->has_overlap = LXW_TRUE;
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+        self->overlap_y1 = 100;
+    }
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_bar_chart;
+    self->write_plot_area = _chart_write_plot_area;
+}
+
+/*
+ * Initialize a column chart.
+ */
+STATIC void
+_chart_initialize_column_chart(lxw_chart *self, uint8_t type)
+{
+    self->chart_group = LXW_CHART_COLUMN;
+    self->has_horiz_val_axis = LXW_FALSE;
+    self->x_axis->is_category = LXW_TRUE;
+    self->y_axis->is_value = LXW_TRUE;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_OUTSIDE_END;
+
+    if (type == LXW_CHART_COLUMN_STACKED) {
+        self->grouping = LXW_GROUPING_STACKED;
+        self->has_overlap = LXW_TRUE;
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+        self->overlap_y1 = 100;
+    }
+
+    if (type == LXW_CHART_COLUMN_STACKED_PERCENT) {
+        self->grouping = LXW_GROUPING_PERCENTSTACKED;
+        _chart_axis_set_default_num_format(self->y_axis, "0%");
+        self->has_overlap = LXW_TRUE;
+        self->subtype = LXW_CHART_SUBTYPE_STACKED;
+        self->overlap_y1 = 100;
+    }
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_column_chart;
+    self->write_plot_area = _chart_write_plot_area;
+}
+
+/*
+ * Initialize a doughnut chart.
+ */
+STATIC void
+_chart_initialize_doughnut_chart(lxw_chart *self)
+{
+    /* Initialize the function pointers for this chart type. */
+    self->chart_group = LXW_CHART_DOUGHNUT;
+    self->write_chart_type = _chart_write_doughnut_chart;
+    self->write_plot_area = _chart_write_pie_plot_area;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_BEST_FIT;
+}
+
+/*
+ * Initialize a line chart.
+ */
+STATIC void
+_chart_initialize_line_chart(lxw_chart *self)
+{
+    self->chart_group = LXW_CHART_LINE;
+    _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE);
+    self->grouping = LXW_GROUPING_STANDARD;
+    self->x_axis->is_category = LXW_TRUE;
+    self->y_axis->is_value = LXW_TRUE;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_RIGHT;
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_line_chart;
+    self->write_plot_area = _chart_write_plot_area;
+}
+
+/*
+ * Initialize a pie chart.
+ */
+STATIC void
+_chart_initialize_pie_chart(lxw_chart *self)
+{
+    /* Initialize the function pointers for this chart type. */
+    self->chart_group = LXW_CHART_PIE;
+    self->write_chart_type = _chart_write_pie_chart;
+    self->write_plot_area = _chart_write_pie_plot_area;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_BEST_FIT;
+}
+
+/*
+ * Initialize a scatter chart.
+ */
+STATIC void
+_chart_initialize_scatter_chart(lxw_chart *self)
+{
+    self->chart_group = LXW_CHART_SCATTER;
+    self->has_horiz_val_axis = LXW_FALSE;
+    self->default_cross_between = LXW_CHART_AXIS_POSITION_ON_TICK;
+    self->x_axis->is_value = LXW_TRUE;
+    self->y_axis->is_value = LXW_TRUE;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_RIGHT;
+
+    if (self->type == LXW_CHART_SCATTER_STRAIGHT
+        || self->type == LXW_CHART_SCATTER_SMOOTH) {
+
+        _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE);
+    }
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_scatter_chart;
+    self->write_plot_area = _chart_write_scatter_plot_area;
+}
+
+/*
+ * Initialize a radar chart.
+ */
+STATIC void
+_chart_initialize_radar_chart(lxw_chart *self, uint8_t type)
+{
+    if (type == LXW_CHART_RADAR)
+        _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE);
+
+    self->chart_group = LXW_CHART_RADAR;
+    self->x_axis->major_gridlines.visible = LXW_TRUE;
+    self->x_axis->is_category = LXW_TRUE;
+    self->y_axis->is_value = LXW_TRUE;
+    self->y_axis->major_tick_mark = LXW_CHART_AXIS_TICK_MARK_CROSSING;
+    self->default_label_position = LXW_CHART_LABEL_POSITION_CENTER;
+
+    /* Initialize the function pointers for this chart type. */
+    self->write_chart_type = _chart_write_radar_chart;
+    self->write_plot_area = _chart_write_plot_area;
+}
+
+/*
+ * Initialize the chart specific properties.
+ */
+STATIC void
+_chart_initialize(lxw_chart *self, uint8_t type)
+{
+    switch (type) {
+
+        case LXW_CHART_AREA:
+        case LXW_CHART_AREA_STACKED:
+        case LXW_CHART_AREA_STACKED_PERCENT:
+            _chart_initialize_area_chart(self, type);
+            break;
+
+        case LXW_CHART_BAR:
+        case LXW_CHART_BAR_STACKED:
+        case LXW_CHART_BAR_STACKED_PERCENT:
+            _chart_initialize_bar_chart(self, type);
+            break;
+
+        case LXW_CHART_COLUMN:
+        case LXW_CHART_COLUMN_STACKED:
+        case LXW_CHART_COLUMN_STACKED_PERCENT:
+            _chart_initialize_column_chart(self, type);
+            break;
+
+        case LXW_CHART_DOUGHNUT:
+            _chart_initialize_doughnut_chart(self);
+            break;
+
+        case LXW_CHART_LINE:
+            _chart_initialize_line_chart(self);
+            break;
+
+        case LXW_CHART_PIE:
+            _chart_initialize_pie_chart(self);
+            break;
+
+        case LXW_CHART_SCATTER:
+        case LXW_CHART_SCATTER_STRAIGHT:
+        case LXW_CHART_SCATTER_STRAIGHT_WITH_MARKERS:
+        case LXW_CHART_SCATTER_SMOOTH:
+        case LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS:
+            _chart_initialize_scatter_chart(self);
+            break;
+
+        case LXW_CHART_RADAR:
+        case LXW_CHART_RADAR_WITH_MARKERS:
+        case LXW_CHART_RADAR_FILLED:
+            _chart_initialize_radar_chart(self, type);
+            break;
+
+        default:
+            LXW_WARN_FORMAT1("workbook_add_chart(): "
+                             "unhandled chart type '%d'", type);
+    }
+}
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_chart_assemble_xml_file(lxw_chart *self)
+{
+    /* Reverse the X and Y axes for Bar charts. */
+    if (self->type == LXW_CHART_BAR || self->type == LXW_CHART_BAR_STACKED
+        || self->type == LXW_CHART_BAR_STACKED_PERCENT)
+        _chart_swap_bar_axes(self);
+
+    /* Write the XML declaration. */
+    _chart_xml_declaration(self);
+
+    /* Write the c:chartSpace element. */
+    _chart_write_chart_space(self);
+
+    /* Write the c:lang element. */
+    _chart_write_lang(self);
+
+    /* Write the c:style element. */
+    _chart_write_style(self);
+
+    /* Write the c:chart element. */
+    _chart_write_chart(self);
+
+    /* Write the c:spPr element for the chartarea formatting. */
+    _chart_write_sp_pr(self, self->chartarea_line, self->chartarea_fill,
+                       self->chartarea_pattern);
+
+    /* Write the c:printSettings element. */
+    _chart_write_print_settings(self);
+
+    lxw_xml_end_tag(self->file, "c:chartSpace");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Add data to a data cache in a range object, for testing only.
+ */
+lxw_error
+lxw_chart_add_data_cache(lxw_series_range *range, uint8_t *data,
+                         uint16_t rows, uint8_t cols, uint8_t col)
+{
+    struct lxw_series_data_point *data_point;
+    uint16_t i;
+
+    range->ignore_cache = LXW_TRUE;
+    range->num_data_points = rows;
+
+    /* Initialize the series range data cache. */
+    for (i = 0; i < rows; i++) {
+        data_point = calloc(1, sizeof(struct lxw_series_data_point));
+        RETURN_ON_MEM_ERROR(data_point, LXW_ERROR_MEMORY_MALLOC_FAILED);
+        STAILQ_INSERT_TAIL(range->data_cache, data_point, list_pointers);
+        data_point->number = data[i * cols + col];
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Insert an image into the worksheet.
+ */
+lxw_chart_series *
+chart_add_series(lxw_chart *self, const char *categories, const char *values)
+{
+    lxw_chart_series *series;
+
+    /* Scatter charts require categories and values. */
+    if (self->chart_group == LXW_CHART_SCATTER && values && !categories) {
+        LXW_WARN("chart_add_series(): scatter charts must have "
+                 "'categories' and 'values'");
+
+        return NULL;
+    }
+
+    /* Create a new object to hold the series. */
+    series = calloc(1, sizeof(lxw_chart_series));
+    GOTO_LABEL_ON_MEM_ERROR(series, mem_error);
+
+    series->categories = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(series->categories, mem_error);
+
+    series->values = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(series->values, mem_error);
+
+    series->title.range = calloc(1, sizeof(lxw_series_range));
+    GOTO_LABEL_ON_MEM_ERROR(series->title.range, mem_error);
+
+    series->x_error_bars = calloc(1, sizeof(lxw_series_error_bars));
+    GOTO_LABEL_ON_MEM_ERROR(series->x_error_bars, mem_error);
+
+    series->y_error_bars = calloc(1, sizeof(lxw_series_error_bars));
+    GOTO_LABEL_ON_MEM_ERROR(series->y_error_bars, mem_error);
+
+    if (categories) {
+        if (categories[0] == '=')
+            series->categories->formula = lxw_strdup(categories + 1);
+        else
+            series->categories->formula = lxw_strdup(categories);
+    }
+
+    if (values) {
+        if (values[0] == '=')
+            series->values->formula = lxw_strdup(values + 1);
+        else
+            series->values->formula = lxw_strdup(values);
+    }
+
+    if (_chart_init_data_cache(series->categories) != LXW_NO_ERROR)
+        goto mem_error;
+
+    if (_chart_init_data_cache(series->values) != LXW_NO_ERROR)
+        goto mem_error;
+
+    if (_chart_init_data_cache(series->title.range) != LXW_NO_ERROR)
+        goto mem_error;
+
+    if (self->type == LXW_CHART_SCATTER_SMOOTH)
+        series->smooth = LXW_TRUE;
+
+    if (self->type == LXW_CHART_SCATTER_SMOOTH_WITH_MARKERS)
+        series->smooth = LXW_TRUE;
+
+    series->y_error_bars->chart_group = self->chart_group;
+    series->x_error_bars->chart_group = self->chart_group;
+    series->x_error_bars->is_x = LXW_TRUE;
+
+    series->default_label_position = self->default_label_position;
+
+    STAILQ_INSERT_TAIL(self->series_list, series, list_pointers);
+
+    return series;
+
+mem_error:
+    _chart_series_free(series);
+    return NULL;
+}
+
+/*
+ * Set on of the 48 built-in Excel chart styles.
+ */
+void
+chart_set_style(lxw_chart *self, uint8_t style_id)
+{
+    /* The default style is 2. The range is 1 - 48 */
+    if (style_id < 1 || style_id > 48)
+        style_id = 2;
+
+    self->style_id = style_id;
+}
+
+/*
+ * Set a user defined name for a series.
+ */
+void
+chart_series_set_name(lxw_chart_series *series, const char *name)
+{
+    if (!name)
+        return;
+
+    if (name[0] == '=')
+        series->title.range->formula = lxw_strdup(name + 1);
+    else
+        series->title.name = lxw_strdup(name);
+}
+
+/*
+ * Set an axis caption, with a range instead or a formula..
+ */
+void
+chart_series_set_name_range(lxw_chart_series *series, const char *sheetname,
+                            lxw_row_t row, lxw_col_t col)
+{
+    if (!sheetname) {
+        LXW_WARN("chart_series_set_name_range(): "
+                 "sheetname must be specified");
+        return;
+    }
+
+    /* Start and end row, col are the same for single cell range. */
+    _chart_set_range(series->title.range, sheetname, row, col, row, col);
+}
+
+/*
+ * Set the categories range for a series.
+ */
+void
+chart_series_set_categories(lxw_chart_series *series, const char *sheetname,
+                            lxw_row_t first_row, lxw_col_t first_col,
+                            lxw_row_t last_row, lxw_col_t last_col)
+{
+    if (!sheetname) {
+        LXW_WARN("chart_series_set_categories(): "
+                 "sheetname must be specified");
+        return;
+    }
+
+    _chart_set_range(series->categories, sheetname,
+                     first_row, first_col, last_row, last_col);
+}
+
+/*
+ * Set the values range for a series.
+ */
+void
+chart_series_set_values(lxw_chart_series *series, const char *sheetname,
+                        lxw_row_t first_row, lxw_col_t first_col,
+                        lxw_row_t last_row, lxw_col_t last_col)
+{
+    if (!sheetname) {
+        LXW_WARN("chart_series_set_values(): sheetname must be specified");
+        return;
+    }
+
+    _chart_set_range(series->values, sheetname,
+                     first_row, first_col, last_row, last_col);
+}
+
+/*
+ * Set a line type for a series.
+ */
+void
+chart_series_set_line(lxw_chart_series *series, lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->line);
+
+    series->line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set a fill type for a series.
+ */
+void
+chart_series_set_fill(lxw_chart_series *series, lxw_chart_fill *fill)
+{
+    if (!fill)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->fill);
+
+    series->fill = _chart_convert_fill_args(fill);
+}
+
+/*
+ * Invert the colors of a fill for a series.
+ */
+void
+chart_series_set_invert_if_negative(lxw_chart_series *series)
+{
+    series->invert_if_negative = LXW_TRUE;
+}
+
+/*
+ * Set a pattern type for a series.
+ */
+void
+chart_series_set_pattern(lxw_chart_series *series, lxw_chart_pattern *pattern)
+{
+    if (!pattern)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->pattern);
+
+    series->pattern = _chart_convert_pattern_args(pattern);
+}
+
+/*
+ * Set a marker type for a series.
+ */
+void
+chart_series_set_marker_type(lxw_chart_series *series, uint8_t type)
+{
+    if (!series->marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        series->marker = marker;
+    }
+
+    series->marker->type = type;
+}
+
+/*
+ * Set a marker size for a series.
+ */
+void
+chart_series_set_marker_size(lxw_chart_series *series, uint8_t size)
+{
+    if (!series->marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        series->marker = marker;
+    }
+
+    series->marker->size = size;
+}
+
+/*
+ * Set a line type for a series marker.
+ */
+void
+chart_series_set_marker_line(lxw_chart_series *series, lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    if (!series->marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        series->marker = marker;
+    }
+
+    /* Free any previously allocated resource. */
+    free(series->marker->line);
+
+    series->marker->line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set a fill type for a series marker.
+ */
+void
+chart_series_set_marker_fill(lxw_chart_series *series, lxw_chart_fill *fill)
+{
+    if (!fill)
+        return;
+
+    if (!series->marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        series->marker = marker;
+    }
+
+    /* Free any previously allocated resource. */
+    free(series->marker->fill);
+
+    series->marker->fill = _chart_convert_fill_args(fill);
+}
+
+/*
+ * Set a pattern type for a series.
+ */
+void
+chart_series_set_marker_pattern(lxw_chart_series *series,
+                                lxw_chart_pattern *pattern)
+{
+    if (!pattern)
+        return;
+
+    if (!series->marker) {
+        lxw_chart_marker *marker = calloc(1, sizeof(struct lxw_chart_marker));
+        RETURN_VOID_ON_MEM_ERROR(marker);
+        series->marker = marker;
+    }
+
+    /* Free any previously allocated resource. */
+    free(series->marker->pattern);
+
+    series->marker->pattern = _chart_convert_pattern_args(pattern);
+}
+
+/*
+ * Store the horizontal page breaks on a worksheet.
+ */
+lxw_error
+chart_series_set_points(lxw_chart_series *series, lxw_chart_point *points[])
+{
+    uint16_t i = 0;
+    uint16_t point_count = 0;
+
+    if (points == NULL)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    while (points[point_count])
+        point_count++;
+
+    if (point_count == 0)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    /* Free any existing resource. */
+    _chart_free_points(series);
+
+    series->points = calloc(point_count, sizeof(lxw_chart_point));
+    RETURN_ON_MEM_ERROR(series->points, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    for (i = 0; i < point_count; i++) {
+        lxw_chart_point *src_point = points[i];
+        lxw_chart_point *dst_point = &series->points[i];
+
+        dst_point->line = _chart_convert_line_args(src_point->line);
+        dst_point->fill = _chart_convert_fill_args(src_point->fill);
+        dst_point->pattern = _chart_convert_pattern_args(src_point->pattern);
+    }
+
+    series->point_count = point_count;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the smooth property for a line or scatter series.
+ */
+void
+chart_series_set_smooth(lxw_chart_series *series, uint8_t smooth)
+{
+    series->smooth = smooth;
+}
+
+/*
+ * Turn on default data labels for a series.
+ */
+void
+chart_series_set_labels(lxw_chart_series *series)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_value = LXW_TRUE;
+}
+
+/*
+ * Set the data labels options for a series.
+ */
+void
+chart_series_set_labels_options(lxw_chart_series *series, uint8_t show_name,
+                                uint8_t show_category, uint8_t show_value)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_name = show_name;
+    series->show_labels_category = show_category;
+    series->show_labels_value = show_value;
+}
+
+/*
+ * Set the data labels separator for a series.
+ */
+void
+chart_series_set_labels_separator(lxw_chart_series *series, uint8_t separator)
+{
+    series->has_labels = LXW_TRUE;
+    series->label_separator = separator;
+}
+
+/*
+ * Set the data labels position for a series.
+ */
+void
+chart_series_set_labels_position(lxw_chart_series *series, uint8_t position)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_value = LXW_TRUE;
+
+    if (position != series->default_label_position)
+        series->label_position = position;
+}
+
+/*
+ * Set the data labels position for a series.
+ */
+void
+chart_series_set_labels_leader_line(lxw_chart_series *series)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_leader = LXW_TRUE;
+}
+
+/*
+ * Turn on the data labels legend for a series.
+ */
+void
+chart_series_set_labels_legend(lxw_chart_series *series)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_legend = LXW_TRUE;
+}
+
+/*
+ * Turn on the data labels percentage for a series.
+ */
+void
+chart_series_set_labels_percentage(lxw_chart_series *series)
+{
+    series->has_labels = LXW_TRUE;
+    series->show_labels_percent = LXW_TRUE;
+}
+
+/*
+ * Set an data labels number format.
+ */
+void
+chart_series_set_labels_num_format(lxw_chart_series *series,
+                                   const char *num_format)
+{
+    if (!num_format)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->label_num_format);
+
+    series->label_num_format = lxw_strdup(num_format);
+}
+
+/*
+ * Set an data labels font.
+ */
+void
+chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font)
+{
+    if (!font)
+        return;
+
+    /* Free any previously allocated resource. */
+    _chart_free_font(series->label_font);
+
+    series->label_font = _chart_convert_font_args(font);
+}
+
+/*
+ * Set the trendline for a chart series.
+ */
+void
+chart_series_set_trendline(lxw_chart_series *series, uint8_t type,
+                           uint8_t value)
+{
+    if (type == LXW_CHART_TRENDLINE_TYPE_POLY
+        || type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
+
+        if (value < 2) {
+            LXW_WARN("chart_series_set_trendline(): order/period value must "
+                     "be >= 2 for Polynomial and Moving Average types");
+            return;
+        }
+
+        series->trendline_value_type = type;
+    }
+
+    series->has_trendline = LXW_TRUE;
+    series->trendline_type = type;
+    series->trendline_value = value;
+}
+
+/*
+ * Set the trendline forecast for a chart series.
+ */
+void
+chart_series_set_trendline_forecast(lxw_chart_series *series, double forward,
+                                    double backward)
+{
+    if (!series->has_trendline) {
+        LXW_WARN("chart_series_set_trendline_forecast(): trendline type "
+                 "must be set first using chart_series_set_trendline()");
+        return;
+    }
+
+    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
+        LXW_WARN("chart_series_set_trendline(): forecast isn't available "
+                 "in Excel for a Moving Average trendline");
+        return;
+    }
+
+    series->has_trendline_forecast = LXW_TRUE;
+    series->trendline_forward = forward;
+    series->trendline_backward = backward;
+}
+
+/*
+ * Display the equation for a series trendline.
+ */
+void
+chart_series_set_trendline_equation(lxw_chart_series *series)
+{
+    if (!series->has_trendline) {
+        LXW_WARN("chart_series_set_trendline_equation(): trendline type "
+                 "must be set first using chart_series_set_trendline()");
+        return;
+    }
+
+    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
+        LXW_WARN("chart_series_set_trendline_equation(): equation isn't "
+                 "available in Excel for a Moving Average trendline");
+        return;
+    }
+
+    series->has_trendline_equation = LXW_TRUE;
+}
+
+/*
+ * Display the R squared value for a series trendline.
+ */
+void
+chart_series_set_trendline_r_squared(lxw_chart_series *series)
+{
+    if (!series->has_trendline) {
+        LXW_WARN("chart_series_set_trendline_r_squared(): trendline type "
+                 "must be set first using chart_series_set_trendline()");
+        return;
+    }
+
+    if (series->trendline_type == LXW_CHART_TRENDLINE_TYPE_AVERAGE) {
+        LXW_WARN("chart_series_set_trendline_r_squared(): R squared isn't "
+                 "available in Excel for a Moving Average trendline");
+        return;
+    }
+
+    series->has_trendline_r_squared = LXW_TRUE;
+}
+
+/*
+ * Set the trendline intercept for a chart series.
+ */
+void
+chart_series_set_trendline_intercept(lxw_chart_series *series,
+                                     double intercept)
+{
+    if (!series->has_trendline) {
+        LXW_WARN("chart_series_set_trendline_intercept(): trendline type "
+                 "must be set first using chart_series_set_trendline()");
+        return;
+    }
+
+    if (series->trendline_type != LXW_CHART_TRENDLINE_TYPE_EXP
+        && series->trendline_type != LXW_CHART_TRENDLINE_TYPE_LINEAR
+        && series->trendline_type != LXW_CHART_TRENDLINE_TYPE_POLY) {
+
+        LXW_WARN("chart_series_set_trendline_intercept(): intercept is only "
+                 "available in Excel for Exponential, Linear and Polynomial "
+                 "trendline types");
+        return;
+    }
+
+    series->has_trendline_intercept = LXW_TRUE;
+    series->trendline_intercept = intercept;
+}
+
+/*
+ * Set a line type for a series trendline.
+ */
+void
+chart_series_set_trendline_name(lxw_chart_series *series, const char *name)
+{
+    if (!name)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->trendline_name);
+
+    series->trendline_name = lxw_strdup(name);
+}
+
+/*
+ * Set a line type for a series trendline.
+ */
+void
+chart_series_set_trendline_line(lxw_chart_series *series,
+                                lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(series->trendline_line);
+
+    series->trendline_line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set the X or Y error bars from a chart series.
+ */
+lxw_series_error_bars *
+chart_series_get_error_bars(lxw_chart_series *series,
+                            lxw_chart_error_bar_axis axis_type)
+{
+    if (!series)
+        return NULL;
+
+    if (axis_type == LXW_CHART_ERROR_BAR_AXIS_X)
+        return series->x_error_bars;
+    else if (axis_type == LXW_CHART_ERROR_BAR_AXIS_Y)
+        return series->y_error_bars;
+    else
+        return NULL;
+}
+
+/*
+ * Set the error bars and type for a chart series.
+ */
+void
+chart_series_set_error_bars(lxw_series_error_bars *error_bars,
+                            uint8_t type, double value)
+{
+    if (_chart_check_error_bars(error_bars, ""))
+        return;
+
+    error_bars->type = type;
+    error_bars->value = value;
+    error_bars->has_value = LXW_TRUE;
+    error_bars->is_set = LXW_TRUE;
+
+    if (type == LXW_CHART_ERROR_BAR_TYPE_STD_ERROR)
+        error_bars->has_value = LXW_FALSE;
+}
+
+/*
+ * Set the error bars direction for a chart series.
+ */
+void
+chart_series_set_error_bars_direction(lxw_series_error_bars *error_bars,
+                                      uint8_t direction)
+{
+    if (_chart_check_error_bars(error_bars, "_direction"))
+        return;
+
+    error_bars->direction = direction;
+}
+
+/*
+ * Set the error bars end cap type for a chart series.
+ */
+void
+chart_series_set_error_bars_endcap(lxw_series_error_bars *error_bars,
+                                   uint8_t endcap)
+{
+    if (_chart_check_error_bars(error_bars, "_endcap"))
+        return;
+
+    error_bars->endcap = endcap;
+}
+
+/*
+ * Set a line type for a series error bars.
+ */
+void
+chart_series_set_error_bars_line(lxw_series_error_bars *error_bars,
+                                 lxw_chart_line *line)
+{
+    if (_chart_check_error_bars(error_bars, "_line"))
+        return;
+
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(error_bars->line);
+
+    error_bars->line = _chart_convert_line_args(line);
+}
+
+/*
+ * Get an axis pointer from a chart.
+ */
+lxw_chart_axis *
+chart_axis_get(lxw_chart *self, lxw_chart_axis_type axis_type)
+{
+    if (!self)
+        return NULL;
+
+    if (axis_type == LXW_CHART_AXIS_TYPE_X)
+        return self->x_axis;
+    else if (axis_type == LXW_CHART_AXIS_TYPE_Y)
+        return self->y_axis;
+    else
+        return NULL;
+}
+
+/*
+ * Set an axis caption.
+ */
+void
+chart_axis_set_name(lxw_chart_axis *axis, const char *name)
+{
+    if (!name)
+        return;
+
+    if (name[0] == '=')
+        axis->title.range->formula = lxw_strdup(name + 1);
+    else
+        axis->title.name = lxw_strdup(name);
+}
+
+/*
+ * Set an axis caption, with a range instead or a formula.
+ */
+void
+chart_axis_set_name_range(lxw_chart_axis *axis, const char *sheetname,
+                          lxw_row_t row, lxw_col_t col)
+{
+    if (!sheetname) {
+        LXW_WARN("chart_axis_set_name_range(): sheetname must be specified");
+        return;
+    }
+
+    /* Start and end row, col are the same for single cell range. */
+    _chart_set_range(axis->title.range, sheetname, row, col, row, col);
+}
+
+/*
+ * Set an axis title/name font.
+ */
+void
+chart_axis_set_name_font(lxw_chart_axis *axis, lxw_chart_font *font)
+{
+    if (!font)
+        return;
+
+    /* Free any previously allocated resource. */
+    _chart_free_font(axis->title.font);
+
+    axis->title.font = _chart_convert_font_args(font);
+}
+
+/*
+ * Set an axis number font.
+ */
+void
+chart_axis_set_num_font(lxw_chart_axis *axis, lxw_chart_font *font)
+{
+    if (!font)
+        return;
+
+    /* Free any previously allocated resource. */
+    _chart_free_font(axis->num_font);
+
+    axis->num_font = _chart_convert_font_args(font);
+}
+
+/*
+ * Set an axis number format.
+ */
+void
+chart_axis_set_num_format(lxw_chart_axis *axis, const char *num_format)
+{
+    if (!num_format)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->num_format);
+
+    axis->num_format = lxw_strdup(num_format);
+}
+
+/*
+ * Set a line type for an axis.
+ */
+void
+chart_axis_set_line(lxw_chart_axis *axis, lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->line);
+
+    axis->line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set a fill type for an axis.
+ */
+void
+chart_axis_set_fill(lxw_chart_axis *axis, lxw_chart_fill *fill)
+{
+    if (!fill)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->fill);
+
+    axis->fill = _chart_convert_fill_args(fill);
+}
+
+/*
+ * Set a pattern type for an axis.
+ */
+void
+chart_axis_set_pattern(lxw_chart_axis *axis, lxw_chart_pattern *pattern)
+{
+    if (!pattern)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->pattern);
+
+    axis->pattern = _chart_convert_pattern_args(pattern);
+}
+
+/*
+ * Reverse the direction of an axis.
+ */
+void
+chart_axis_set_reverse(lxw_chart_axis *axis)
+{
+    axis->reverse = LXW_TRUE;
+}
+
+/*
+ * Set the axis crossing position.
+ */
+void
+chart_axis_set_crossing(lxw_chart_axis *axis, double value)
+{
+    axis->has_crossing = LXW_TRUE;
+    axis->crossing = value;
+}
+
+/*
+ * Set the axis crossing position as the max possible value.
+ */
+void
+chart_axis_set_crossing_max(lxw_chart_axis *axis)
+{
+    axis->has_crossing = LXW_TRUE;
+    axis->crossing_max = LXW_TRUE;
+}
+
+/*
+ * Turn off/hide the axis.
+ */
+void
+chart_axis_off(lxw_chart_axis *axis)
+{
+    axis->hidden = LXW_TRUE;
+}
+
+/*
+ * Set the category axis position.
+ */
+void
+chart_axis_set_position(lxw_chart_axis *axis, uint8_t position)
+{
+    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_position");
+
+    axis->position_axis = position;
+}
+
+/*
+ * Set the axis label position.
+ */
+void
+chart_axis_set_label_position(lxw_chart_axis *axis, uint8_t position)
+{
+    axis->label_position = position;
+}
+
+/*
+ * Set the minimum value for an axis.
+ */
+void
+chart_axis_set_min(lxw_chart_axis *axis, double min)
+{
+    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_min");
+
+    axis->min = min;
+    axis->has_min = LXW_TRUE;
+}
+
+/*
+ * Set the maximum value for an axis.
+ */
+void
+chart_axis_set_max(lxw_chart_axis *axis, double max)
+{
+    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_max");
+
+    axis->max = max;
+    axis->has_max = LXW_TRUE;
+}
+
+/*
+ * Set the log base for an axis.
+ */
+void
+chart_axis_set_log_base(lxw_chart_axis *axis, uint16_t log_base)
+{
+    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_log_base");
+
+    /* Excel log range is 2-1000. */
+    if (log_base >= 2 && log_base <= 1000)
+        axis->log_base = log_base;
+}
+
+/*
+ * Set the major mark for an axis.
+ */
+void
+chart_axis_set_major_tick_mark(lxw_chart_axis *axis, uint8_t type)
+{
+    axis->major_tick_mark = type;
+}
+
+/*
+ * Set the minor mark for an axis.
+ */
+void
+chart_axis_set_minor_tick_mark(lxw_chart_axis *axis, uint8_t type)
+{
+    axis->minor_tick_mark = type;
+}
+
+/*
+ * Set interval unit for a category axis.
+ */
+void
+chart_axis_set_interval_unit(lxw_chart_axis *axis, uint16_t unit)
+{
+    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_major_unit");
+
+    axis->interval_unit = unit;
+}
+
+/*
+ * Set tick interval for a category axis.
+ */
+void
+chart_axis_set_interval_tick(lxw_chart_axis *axis, uint16_t unit)
+{
+    LXW_WARN_CAT_AND_DATE_AXIS_ONLY("chart_axis_set_major_tick");
+
+    axis->interval_tick = unit;
+}
+
+/*
+ * Set major unit for a value axis.
+ */
+void
+chart_axis_set_major_unit(lxw_chart_axis *axis, double unit)
+{
+    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_major_unit");
+
+    axis->has_major_unit = LXW_TRUE;
+    axis->major_unit = unit;
+}
+
+/*
+ * Set minor unit for a value axis.
+ */
+void
+chart_axis_set_minor_unit(lxw_chart_axis *axis, double unit)
+{
+    LXW_WARN_VALUE_AND_DATE_AXIS_ONLY("chart_axis_set_minor_unit");
+
+    axis->has_minor_unit = LXW_TRUE;
+    axis->minor_unit = unit;
+}
+
+/*
+ * Set the display units for a value axis.
+ */
+void
+chart_axis_set_display_units(lxw_chart_axis *axis, uint8_t units)
+{
+    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_display_units");
+
+    axis->display_units = units;
+    axis->display_units_visible = LXW_TRUE;
+}
+
+/*
+ * Turn on/off the display units for a value axis.
+ */
+void
+chart_axis_set_display_units_visible(lxw_chart_axis *axis, uint8_t visible)
+{
+    LXW_WARN_VALUE_AXIS_ONLY("chart_axis_set_display_units");
+
+    axis->display_units_visible = visible;
+}
+
+/*
+ * Set the axis major gridlines on/off.
+ */
+void
+chart_axis_major_gridlines_set_visible(lxw_chart_axis *axis, uint8_t visible)
+{
+    axis->major_gridlines.visible = visible;
+}
+
+/*
+ * Set a line type for the major gridlines.
+ */
+void
+chart_axis_major_gridlines_set_line(lxw_chart_axis *axis,
+                                    lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->major_gridlines.line);
+
+    axis->major_gridlines.line = _chart_convert_line_args(line);
+
+    /* If the gridline has a format it should also be visible. */
+    if (axis->major_gridlines.line)
+        axis->major_gridlines.visible = LXW_TRUE;
+}
+
+/*
+ * Set the axis minor gridlines on/off.
+ */
+void
+chart_axis_minor_gridlines_set_visible(lxw_chart_axis *axis, uint8_t visible)
+{
+    axis->minor_gridlines.visible = visible;
+}
+
+/*
+ * Set a line type for the minor gridlines.
+ */
+void
+chart_axis_minor_gridlines_set_line(lxw_chart_axis *axis,
+                                    lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(axis->minor_gridlines.line);
+
+    axis->minor_gridlines.line = _chart_convert_line_args(line);
+
+    /* If the gridline has a format it should also be visible. */
+    if (axis->minor_gridlines.line)
+        axis->minor_gridlines.visible = LXW_TRUE;
+}
+
+/*
+ * Set the chart title.
+ */
+void
+chart_title_set_name(lxw_chart *self, const char *name)
+{
+    if (!name)
+        return;
+
+    if (name[0] == '=')
+        self->title.range->formula = lxw_strdup(name + 1);
+    else
+        self->title.name = lxw_strdup(name);
+}
+
+/*
+ * Set the chart title, with a range instead or a formula.
+ */
+void
+chart_title_set_name_range(lxw_chart *self, const char *sheetname,
+                           lxw_row_t row, lxw_col_t col)
+{
+    if (!sheetname) {
+        LXW_WARN("chart_title_set_name_range(): sheetname must be specified");
+        return;
+    }
+
+    /* Start and end row, col are the same for single cell range. */
+    _chart_set_range(self->title.range, sheetname, row, col, row, col);
+}
+
+/*
+ * Set the chart title font.
+ */
+void
+chart_title_set_name_font(lxw_chart *self, lxw_chart_font *font)
+{
+    /* Free any previously allocated resource. */
+    _chart_free_font(self->title.font);
+
+    self->title.font = _chart_convert_font_args(font);
+}
+
+/*
+ * Turn off the chart title.
+ */
+void
+chart_title_off(lxw_chart *self)
+{
+    self->title.off = LXW_TRUE;
+}
+
+/*
+ * Set the chart legend position.
+ */
+void
+chart_legend_set_position(lxw_chart *self, uint8_t position)
+{
+    self->legend.position = position;
+}
+
+/*
+ * Set the legend font.
+ */
+void
+chart_legend_set_font(lxw_chart *self, lxw_chart_font *font)
+{
+    /* Free any previously allocated resource. */
+    _chart_free_font(self->legend.font);
+
+    self->legend.font = _chart_convert_font_args(font);
+}
+
+/*
+ * Remove one or more series from the the legend.
+ */
+lxw_error
+chart_legend_delete_series(lxw_chart *self, int16_t delete_series[])
+{
+    uint16_t count = 0;
+
+    if (delete_series == NULL)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    while (delete_series[count] >= 0)
+        count++;
+
+    if (count == 0)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    /* The maximum number of series in a chart is 255. */
+    if (count > 255)
+        count = 255;
+
+    self->delete_series = calloc(count, sizeof(int16_t));
+    RETURN_ON_MEM_ERROR(self->delete_series, LXW_ERROR_MEMORY_MALLOC_FAILED);
+    memcpy(self->delete_series, delete_series, count * sizeof(int16_t));
+    self->delete_series_count = count;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set a line type for the chartarea.
+ */
+void
+chart_chartarea_set_line(lxw_chart *self, lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->chartarea_line);
+
+    self->chartarea_line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set a fill type for the chartarea.
+ */
+void
+chart_chartarea_set_fill(lxw_chart *self, lxw_chart_fill *fill)
+{
+    if (!fill)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->chartarea_fill);
+
+    self->chartarea_fill = _chart_convert_fill_args(fill);
+}
+
+/*
+ * Set a pattern type for the chartarea.
+ */
+void
+chart_chartarea_set_pattern(lxw_chart *self, lxw_chart_pattern *pattern)
+{
+    if (!pattern)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->chartarea_pattern);
+
+    self->chartarea_pattern = _chart_convert_pattern_args(pattern);
+}
+
+/*
+ * Set a line type for the plotarea.
+ */
+void
+chart_plotarea_set_line(lxw_chart *self, lxw_chart_line *line)
+{
+    if (!line)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->plotarea_line);
+
+    self->plotarea_line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set a fill type for the plotarea.
+ */
+void
+chart_plotarea_set_fill(lxw_chart *self, lxw_chart_fill *fill)
+{
+    if (!fill)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->plotarea_fill);
+
+    self->plotarea_fill = _chart_convert_fill_args(fill);
+}
+
+/*
+ * Set a pattern type for the plotarea.
+ */
+void
+chart_plotarea_set_pattern(lxw_chart *self, lxw_chart_pattern *pattern)
+{
+    if (!pattern)
+        return;
+
+    /* Free any previously allocated resource. */
+    free(self->plotarea_pattern);
+
+    self->plotarea_pattern = _chart_convert_pattern_args(pattern);
+}
+
+/*
+ * Turn on the chart data table.
+ */
+void
+chart_set_table(lxw_chart *self)
+{
+    self->has_table = LXW_TRUE;
+    self->has_table_horizontal = LXW_TRUE;
+    self->has_table_vertical = LXW_TRUE;
+    self->has_table_outline = LXW_TRUE;
+    self->has_table_legend_keys = LXW_FALSE;
+}
+
+/*
+ * Set the options for the chart data table grid.
+ */
+void
+chart_set_table_grid(lxw_chart *self, uint8_t horizontal, uint8_t vertical,
+                     uint8_t outline, uint8_t legend_keys)
+{
+    self->has_table = LXW_TRUE;
+    self->has_table_horizontal = horizontal;
+    self->has_table_vertical = vertical;
+    self->has_table_outline = outline;
+    self->has_table_legend_keys = legend_keys;
+}
+
+/*
+ * Set the font for the chart data table grid.
+ */
+void
+chart_set_table_font(lxw_chart *self, lxw_chart_font *font)
+{
+    self->has_table = LXW_TRUE;
+
+    /* Free any previously allocated resource. */
+    _chart_free_font(self->table_font);
+
+    self->table_font = _chart_convert_font_args(font);
+}
+
+/*
+ * Turn on up-down bars for the chart.
+ */
+void
+chart_set_up_down_bars(lxw_chart *self)
+{
+    self->has_up_down_bars = LXW_TRUE;
+}
+
+/*
+ * Turn on up-down bars for the chart, with formatting.
+ */
+void
+chart_set_up_down_bars_format(lxw_chart *self, lxw_chart_line *up_bar_line,
+                              lxw_chart_fill *up_bar_fill,
+                              lxw_chart_line *down_bar_line,
+                              lxw_chart_fill *down_bar_fill)
+{
+    self->has_up_down_bars = LXW_TRUE;
+
+    /* Free any previously allocated resource. */
+    free(self->up_bar_line);
+    free(self->up_bar_fill);
+    free(self->down_bar_line);
+    free(self->down_bar_fill);
+
+    self->up_bar_line = _chart_convert_line_args(up_bar_line);
+    self->up_bar_fill = _chart_convert_fill_args(up_bar_fill);
+    self->down_bar_line = _chart_convert_line_args(down_bar_line);
+    self->down_bar_fill = _chart_convert_fill_args(down_bar_fill);
+}
+
+/*
+ * Turn on drop lines for the chart.
+ */
+void
+chart_set_drop_lines(lxw_chart *self, lxw_chart_line *line)
+{
+    /* Free any previously allocated resource. */
+    free(self->drop_lines_line);
+
+    self->has_drop_lines = LXW_TRUE;
+    self->drop_lines_line = _chart_convert_line_args(line);
+}
+
+/*
+ * Turn on high_low lines for the chart.
+ */
+void
+chart_set_high_low_lines(lxw_chart *self, lxw_chart_line *line)
+{
+    /* Free any previously allocated resource. */
+    free(self->high_low_lines_line);
+
+    self->has_high_low_lines = LXW_TRUE;
+    self->high_low_lines_line = _chart_convert_line_args(line);
+}
+
+/*
+ * Set the Bar/Column overlap for all data series.
+ */
+void
+chart_set_series_overlap(lxw_chart *self, int8_t overlap)
+{
+    if (overlap >= -100 && overlap <= 100)
+        self->overlap_y1 = overlap;
+    else
+        LXW_WARN_FORMAT1("chart_set_series_overlap(): Chart series overlap "
+                         "'%d' outside Excel range: -100 <= overlap <= 100",
+                         overlap);
+}
+
+/*
+ * Set the option for displaying blank data in a chart.
+ */
+void
+chart_show_blanks_as(lxw_chart *self, uint8_t option)
+{
+    self->show_blanks_as = option;
+}
+
+/*
+ * Display data on charts from hidden rows or columns.
+ */
+void
+chart_show_hidden_data(lxw_chart *self)
+{
+    self->show_hidden_data = LXW_TRUE;
+}
+
+/*
+ * Set the Bar/Column gap for all data series.
+ */
+void
+chart_set_series_gap(lxw_chart *self, uint16_t gap)
+{
+    if (gap <= 500)
+        self->gap_y1 = gap;
+    else
+        LXW_WARN_FORMAT1("chart_set_series_gap(): Chart series gap '%d' "
+                         "outside Excel range: 0 <= gap <= 500", gap);
+}
+
+/*
+ * Set the Pie/Doughnut chart rotation: the angle of the first slice.
+ */
+void
+chart_set_rotation(lxw_chart *self, uint16_t rotation)
+{
+    if (rotation <= 360)
+        self->rotation = rotation;
+    else
+        LXW_WARN_FORMAT1("chart_set_rotation(): Chart rotation '%d' outside "
+                         "Excel range: 0 <= rotation <= 360", rotation);
+}
+
+/*
+ * Set the Doughnut chart hole size.
+ */
+void
+chart_set_hole_size(lxw_chart *self, uint8_t size)
+{
+    if (size >= 10 && size <= 90)
+        self->hole_size = size;
+    else
+        LXW_WARN_FORMAT1("chart_set_hole_size(): Hole size '%d' outside "
+                         "Excel range: 10 <= size <= 90", size);
+}

+ 345 - 0
library/src/content_types.c

@@ -0,0 +1,345 @@
+/*****************************************************************************
+ * content_types - A library for creating Excel XLSX content_types files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/content_types.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new content_types object.
+ */
+lxw_content_types *
+lxw_content_types_new()
+{
+    lxw_content_types *content_types = calloc(1, sizeof(lxw_content_types));
+    GOTO_LABEL_ON_MEM_ERROR(content_types, mem_error);
+
+    content_types->default_types = calloc(1, sizeof(struct lxw_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(content_types->default_types, mem_error);
+    STAILQ_INIT(content_types->default_types);
+
+    content_types->overrides = calloc(1, sizeof(struct lxw_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(content_types->overrides, mem_error);
+    STAILQ_INIT(content_types->overrides);
+
+    lxw_ct_add_default(content_types, "rels",
+                       LXW_APP_PACKAGE "relationships+xml");
+    lxw_ct_add_default(content_types, "xml", "application/xml");
+
+    lxw_ct_add_override(content_types, "/docProps/app.xml",
+                        LXW_APP_DOCUMENT "extended-properties+xml");
+    lxw_ct_add_override(content_types, "/docProps/core.xml",
+                        LXW_APP_PACKAGE "core-properties+xml");
+    lxw_ct_add_override(content_types, "/xl/styles.xml",
+                        LXW_APP_DOCUMENT "spreadsheetml.styles+xml");
+    lxw_ct_add_override(content_types, "/xl/theme/theme1.xml",
+                        LXW_APP_DOCUMENT "theme+xml");
+    lxw_ct_add_override(content_types, "/xl/workbook.xml",
+                        LXW_APP_DOCUMENT "spreadsheetml.sheet.main+xml");
+
+    return content_types;
+
+mem_error:
+    lxw_content_types_free(content_types);
+    return NULL;
+}
+
+/*
+ * Free a content_types object.
+ */
+void
+lxw_content_types_free(lxw_content_types *content_types)
+{
+    lxw_tuple *default_type;
+    lxw_tuple *override;
+
+    if (!content_types)
+        return;
+
+    if (content_types->default_types) {
+        while (!STAILQ_EMPTY(content_types->default_types)) {
+            default_type = STAILQ_FIRST(content_types->default_types);
+            STAILQ_REMOVE_HEAD(content_types->default_types, list_pointers);
+            free(default_type->key);
+            free(default_type->value);
+            free(default_type);
+        }
+        free(content_types->default_types);
+    }
+
+    if (content_types->overrides) {
+        while (!STAILQ_EMPTY(content_types->overrides)) {
+            override = STAILQ_FIRST(content_types->overrides);
+            STAILQ_REMOVE_HEAD(content_types->overrides, list_pointers);
+            free(override->key);
+            free(override->value);
+            free(override);
+        }
+        free(content_types->overrides);
+    }
+
+    free(content_types);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_content_types_xml_declaration(lxw_content_types *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <Types> element.
+ */
+STATIC void
+_write_types(lxw_content_types *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", LXW_SCHEMA_CONTENT);
+
+    lxw_xml_start_tag(self->file, "Types", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <Default> element.
+ */
+STATIC void
+_write_default(lxw_content_types *self, const char *ext, const char *type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("Extension", ext);
+    LXW_PUSH_ATTRIBUTES_STR("ContentType", type);
+
+    lxw_xml_empty_tag(self->file, "Default", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <Override> element.
+ */
+STATIC void
+_write_override(lxw_content_types *self, const char *part_name,
+                const char *type)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("PartName", part_name);
+    LXW_PUSH_ATTRIBUTES_STR("ContentType", type);
+
+    lxw_xml_empty_tag(self->file, "Override", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write out all of the <Default> types.
+ */
+STATIC void
+_write_defaults(lxw_content_types *self)
+{
+    lxw_tuple *tuple;
+
+    STAILQ_FOREACH(tuple, self->default_types, list_pointers) {
+        _write_default(self, tuple->key, tuple->value);
+    }
+}
+
+/*
+ * Write out all of the <Override> types.
+ */
+STATIC void
+_write_overrides(lxw_content_types *self)
+{
+    lxw_tuple *tuple;
+
+    STAILQ_FOREACH(tuple, self->overrides, list_pointers) {
+        _write_override(self, tuple->key, tuple->value);
+    }
+}
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_content_types_assemble_xml_file(lxw_content_types *self)
+{
+    /* Write the XML declaration. */
+    _content_types_xml_declaration(self);
+
+    _write_types(self);
+    _write_defaults(self);
+    _write_overrides(self);
+
+    /* Close the content_types tag. */
+    lxw_xml_end_tag(self->file, "Types");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+/*
+ * Add elements to the ContentTypes defaults.
+ */
+void
+lxw_ct_add_default(lxw_content_types *self, const char *key,
+                   const char *value)
+{
+    lxw_tuple *tuple;
+
+    if (!key || !value)
+        return;
+
+    tuple = calloc(1, sizeof(lxw_tuple));
+    GOTO_LABEL_ON_MEM_ERROR(tuple, mem_error);
+
+    tuple->key = lxw_strdup(key);
+    GOTO_LABEL_ON_MEM_ERROR(tuple->key, mem_error);
+
+    tuple->value = lxw_strdup(value);
+    GOTO_LABEL_ON_MEM_ERROR(tuple->value, mem_error);
+
+    STAILQ_INSERT_TAIL(self->default_types, tuple, list_pointers);
+
+    return;
+
+mem_error:
+    if (tuple) {
+        free(tuple->key);
+        free(tuple->value);
+        free(tuple);
+    }
+}
+
+/*
+ * Add elements to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_override(lxw_content_types *self, const char *key,
+                    const char *value)
+{
+    lxw_tuple *tuple;
+
+    if (!key || !value)
+        return;
+
+    tuple = calloc(1, sizeof(lxw_tuple));
+    GOTO_LABEL_ON_MEM_ERROR(tuple, mem_error);
+
+    tuple->key = lxw_strdup(key);
+    GOTO_LABEL_ON_MEM_ERROR(tuple->key, mem_error);
+
+    tuple->value = lxw_strdup(value);
+    GOTO_LABEL_ON_MEM_ERROR(tuple->value, mem_error);
+
+    STAILQ_INSERT_TAIL(self->overrides, tuple, list_pointers);
+
+    return;
+
+mem_error:
+    if (tuple) {
+        free(tuple->key);
+        free(tuple->value);
+        free(tuple);
+    }
+}
+
+/*
+ * Add the name of a worksheet to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_worksheet_name(lxw_content_types *self, const char *name)
+{
+    lxw_ct_add_override(self, name,
+                        LXW_APP_DOCUMENT "spreadsheetml.worksheet+xml");
+}
+
+/*
+ * Add the name of a chart to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_chart_name(lxw_content_types *self, const char *name)
+{
+    lxw_ct_add_override(self, name, LXW_APP_DOCUMENT "drawingml.chart+xml");
+}
+
+/*
+ * Add the name of a drawing to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_drawing_name(lxw_content_types *self, const char *name)
+{
+    lxw_ct_add_override(self, name, LXW_APP_DOCUMENT "drawing+xml");
+}
+
+/*
+ * Add the sharedStrings link to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_shared_strings(lxw_content_types *self)
+{
+    lxw_ct_add_override(self, "/xl/sharedStrings.xml",
+                        LXW_APP_DOCUMENT "spreadsheetml.sharedStrings+xml");
+}
+
+/*
+ * Add the calcChain link to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_calc_chain(lxw_content_types *self)
+{
+    lxw_ct_add_override(self, "/xl/calcChain.xml",
+                        LXW_APP_DOCUMENT "spreadsheetml.calcChain+xml");
+}
+
+/*
+ * Add the custom properties to the ContentTypes overrides.
+ */
+void
+lxw_ct_add_custom_properties(lxw_content_types *self)
+{
+    lxw_ct_add_override(self, "/docProps/custom.xml",
+                        LXW_APP_DOCUMENT "custom-properties+xml");
+}

+ 293 - 0
library/src/core.c

@@ -0,0 +1,293 @@
+/*****************************************************************************
+ * core - A library for creating Excel XLSX core files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/core.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new core object.
+ */
+lxw_core *
+lxw_core_new()
+{
+    lxw_core *core = calloc(1, sizeof(lxw_core));
+    GOTO_LABEL_ON_MEM_ERROR(core, mem_error);
+
+    return core;
+
+mem_error:
+    lxw_core_free(core);
+    return NULL;
+}
+
+/*
+ * Free a core object.
+ */
+void
+lxw_core_free(lxw_core *core)
+{
+    if (!core)
+        return;
+
+    free(core);
+}
+
+/*
+ * Convert a time_t struct to a ISO 8601 style "2010-01-01T00:00:00Z" date.
+ */
+static void
+_datetime_to_iso8601_date(time_t *timer, char *str, size_t size)
+{
+    struct tm *tmp_datetime;
+    time_t current_time = time(NULL);
+
+    if (*timer)
+        tmp_datetime = gmtime(timer);
+    else
+        tmp_datetime = gmtime(&current_time);
+
+    strftime(str, size - 1, "%Y-%m-%dT%H:%M:%SZ", tmp_datetime);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_core_xml_declaration(lxw_core *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <cp:coreProperties> element.
+ */
+STATIC void
+_write_cp_core_properties(lxw_core *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:cp",
+                            "http://schemas.openxmlformats.org/package/2006/metadata/core-properties");
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:dc", "http://purl.org/dc/elements/1.1/");
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:dcterms", "http://purl.org/dc/terms/");
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:dcmitype", "http://purl.org/dc/dcmitype/");
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:xsi",
+                            "http://www.w3.org/2001/XMLSchema-instance");
+
+    lxw_xml_start_tag(self->file, "cp:coreProperties", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dc:creator> element.
+ */
+STATIC void
+_write_dc_creator(lxw_core *self)
+{
+    if (self->properties->author) {
+        lxw_xml_data_element(self->file, "dc:creator",
+                             self->properties->author, NULL);
+    }
+    else {
+        lxw_xml_data_element(self->file, "dc:creator", "", NULL);
+    }
+}
+
+/*
+ * Write the <cp:lastModifiedBy> element.
+ */
+STATIC void
+_write_cp_last_modified_by(lxw_core *self)
+{
+    if (self->properties->author) {
+        lxw_xml_data_element(self->file, "cp:lastModifiedBy",
+                             self->properties->author, NULL);
+    }
+    else {
+        lxw_xml_data_element(self->file, "cp:lastModifiedBy", "", NULL);
+    }
+}
+
+/*
+ * Write the <dcterms:created> element.
+ */
+STATIC void
+_write_dcterms_created(lxw_core *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char datetime[LXW_ATTR_32];
+
+    _datetime_to_iso8601_date(&self->properties->created, datetime,
+                              LXW_ATTR_32);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xsi:type", "dcterms:W3CDTF");
+
+    lxw_xml_data_element(self->file, "dcterms:created", datetime,
+                         &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dcterms:modified> element.
+ */
+STATIC void
+_write_dcterms_modified(lxw_core *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char datetime[LXW_ATTR_32];
+
+    _datetime_to_iso8601_date(&self->properties->created, datetime,
+                              LXW_ATTR_32);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xsi:type", "dcterms:W3CDTF");
+
+    lxw_xml_data_element(self->file, "dcterms:modified", datetime,
+                         &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dc:title> element.
+ */
+STATIC void
+_write_dc_title(lxw_core *self)
+{
+    if (!self->properties->title)
+        return;
+
+    lxw_xml_data_element(self->file, "dc:title", self->properties->title,
+                         NULL);
+}
+
+/*
+ * Write the <dc:subject> element.
+ */
+STATIC void
+_write_dc_subject(lxw_core *self)
+{
+    if (!self->properties->subject)
+        return;
+
+    lxw_xml_data_element(self->file, "dc:subject", self->properties->subject,
+                         NULL);
+}
+
+/*
+ * Write the <cp:keywords> element.
+ */
+STATIC void
+_write_cp_keywords(lxw_core *self)
+{
+    if (!self->properties->keywords)
+        return;
+
+    lxw_xml_data_element(self->file, "cp:keywords",
+                         self->properties->keywords, NULL);
+}
+
+/*
+ * Write the <dc:description> element.
+ */
+STATIC void
+_write_dc_description(lxw_core *self)
+{
+    if (!self->properties->comments)
+        return;
+
+    lxw_xml_data_element(self->file, "dc:description",
+                         self->properties->comments, NULL);
+}
+
+/*
+ * Write the <cp:category> element.
+ */
+STATIC void
+_write_cp_category(lxw_core *self)
+{
+    if (!self->properties->category)
+        return;
+
+    lxw_xml_data_element(self->file, "cp:category",
+                         self->properties->category, NULL);
+}
+
+/*
+ * Write the <cp:contentStatus> element.
+ */
+STATIC void
+_write_cp_content_status(lxw_core *self)
+{
+    if (!self->properties->status)
+        return;
+
+    lxw_xml_data_element(self->file, "cp:contentStatus",
+                         self->properties->status, NULL);
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_core_assemble_xml_file(lxw_core *self)
+{
+    /* Write the XML declaration. */
+    _core_xml_declaration(self);
+
+    _write_cp_core_properties(self);
+    _write_dc_title(self);
+    _write_dc_subject(self);
+    _write_dc_creator(self);
+    _write_cp_keywords(self);
+    _write_dc_description(self);
+    _write_cp_last_modified_by(self);
+    _write_dcterms_created(self);
+    _write_dcterms_modified(self);
+    _write_cp_category(self);
+    _write_cp_content_status(self);
+
+    lxw_xml_end_tag(self->file, "cp:coreProperties");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/

+ 224 - 0
library/src/custom.c

@@ -0,0 +1,224 @@
+/*****************************************************************************
+ * custom - A library for creating Excel custom property files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/custom.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new custom object.
+ */
+lxw_custom *
+lxw_custom_new()
+{
+    lxw_custom *custom = calloc(1, sizeof(lxw_custom));
+    GOTO_LABEL_ON_MEM_ERROR(custom, mem_error);
+
+    return custom;
+
+mem_error:
+    lxw_custom_free(custom);
+    return NULL;
+}
+
+/*
+ * Free a custom object.
+ */
+void
+lxw_custom_free(lxw_custom *custom)
+{
+    if (!custom)
+        return;
+
+    free(custom);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_custom_xml_declaration(lxw_custom *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <vt:lpwstr> element.
+ */
+STATIC void
+_chart_write_vt_lpwstr(lxw_custom *self, char *value)
+{
+    lxw_xml_data_element(self->file, "vt:lpwstr", value, NULL);
+}
+
+/*
+ * Write the <vt:r8> element.
+ */
+STATIC void
+_chart_write_vt_r_8(lxw_custom *self, double value)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, value);
+
+    lxw_xml_data_element(self->file, "vt:r8", data, NULL);
+}
+
+/*
+ * Write the <vt:i4> element.
+ */
+STATIC void
+_custom_write_vt_i_4(lxw_custom *self, int32_t value)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_snprintf(data, LXW_ATTR_32, "%d", value);
+
+    lxw_xml_data_element(self->file, "vt:i4", data, NULL);
+}
+
+/*
+ * Write the <vt:bool> element.
+ */
+STATIC void
+_custom_write_vt_bool(lxw_custom *self, uint8_t value)
+{
+    if (value)
+        lxw_xml_data_element(self->file, "vt:bool", "true", NULL);
+    else
+        lxw_xml_data_element(self->file, "vt:bool", "false", NULL);
+}
+
+/*
+ * Write the <vt:filetime> element.
+ */
+STATIC void
+_custom_write_vt_filetime(lxw_custom *self, lxw_datetime *datetime)
+{
+    char data[LXW_DATETIME_LENGTH];
+
+    lxw_snprintf(data, LXW_DATETIME_LENGTH, "%4d-%02d-%02dT%02d:%02d:%02dZ",
+                 datetime->year, datetime->month, datetime->day,
+                 datetime->hour, datetime->min, (int) datetime->sec);
+
+    lxw_xml_data_element(self->file, "vt:filetime", data, NULL);
+}
+
+/*
+ * Write the <property> element.
+ */
+STATIC void
+_chart_write_custom_property(lxw_custom *self,
+                             lxw_custom_property *custom_property)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char fmtid[] = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
+
+    self->pid++;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("fmtid", fmtid);
+    LXW_PUSH_ATTRIBUTES_INT("pid", self->pid + 1);
+    LXW_PUSH_ATTRIBUTES_STR("name", custom_property->name);
+
+    lxw_xml_start_tag(self->file, "property", &attributes);
+
+    if (custom_property->type == LXW_CUSTOM_STRING) {
+        /* Write the vt:lpwstr element. */
+        _chart_write_vt_lpwstr(self, custom_property->u.string);
+    }
+    else if (custom_property->type == LXW_CUSTOM_DOUBLE) {
+        /* Write the vt:r8 element. */
+        _chart_write_vt_r_8(self, custom_property->u.number);
+    }
+    else if (custom_property->type == LXW_CUSTOM_INTEGER) {
+        /* Write the vt:i4 element. */
+        _custom_write_vt_i_4(self, custom_property->u.integer);
+    }
+    else if (custom_property->type == LXW_CUSTOM_BOOLEAN) {
+        /* Write the vt:bool element. */
+        _custom_write_vt_bool(self, custom_property->u.boolean);
+    }
+    else if (custom_property->type == LXW_CUSTOM_DATETIME) {
+        /* Write the vt:filetime element. */
+        _custom_write_vt_filetime(self, &custom_property->u.datetime);
+    }
+
+    lxw_xml_end_tag(self->file, "property");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <Properties> element.
+ */
+STATIC void
+_write_custom_properties(lxw_custom *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns[] = LXW_SCHEMA_OFFICEDOC "/custom-properties";
+    char xmlns_vt[] = LXW_SCHEMA_OFFICEDOC "/docPropsVTypes";
+    lxw_custom_property *custom_property;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:vt", xmlns_vt);
+
+    lxw_xml_start_tag(self->file, "Properties", &attributes);
+
+    STAILQ_FOREACH(custom_property, self->custom_properties, list_pointers) {
+        _chart_write_custom_property(self, custom_property);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_custom_assemble_xml_file(lxw_custom *self)
+{
+    /* Write the XML declaration. */
+    _custom_xml_declaration(self);
+
+    _write_custom_properties(self);
+
+    lxw_xml_end_tag(self->file, "Properties");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/

+ 746 - 0
library/src/drawing.c

@@ -0,0 +1,746 @@
+/*****************************************************************************
+ * drawing - A library for creating Excel XLSX drawing files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/common.h"
+#include "xlsxwriter/drawing.h"
+#include "xlsxwriter/utility.h"
+
+#define LXW_OBJ_NAME_LENGTH 14  /* "Picture 65536", or "Chart 65536" */
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new drawing collection.
+ */
+lxw_drawing *
+lxw_drawing_new()
+{
+    lxw_drawing *drawing = calloc(1, sizeof(lxw_drawing));
+    GOTO_LABEL_ON_MEM_ERROR(drawing, mem_error);
+
+    drawing->drawing_objects = calloc(1, sizeof(struct lxw_drawing_objects));
+    GOTO_LABEL_ON_MEM_ERROR(drawing->drawing_objects, mem_error);
+
+    STAILQ_INIT(drawing->drawing_objects);
+
+    return drawing;
+
+mem_error:
+    lxw_drawing_free(drawing);
+    return NULL;
+}
+
+/*
+ * Free a drawing object.
+ */
+void
+lxw_free_drawing_object(lxw_drawing_object *drawing_object)
+{
+    if (!drawing_object)
+        return;
+
+    free(drawing_object->description);
+    free(drawing_object->url);
+    free(drawing_object->tip);
+
+    free(drawing_object);
+}
+
+/*
+ * Free a drawing collection.
+ */
+void
+lxw_drawing_free(lxw_drawing *drawing)
+{
+    lxw_drawing_object *drawing_object;
+
+    if (!drawing)
+        return;
+
+    if (drawing->drawing_objects) {
+        while (!STAILQ_EMPTY(drawing->drawing_objects)) {
+            drawing_object = STAILQ_FIRST(drawing->drawing_objects);
+            STAILQ_REMOVE_HEAD(drawing->drawing_objects, list_pointers);
+            lxw_free_drawing_object(drawing_object);
+        }
+
+        free(drawing->drawing_objects);
+    }
+
+    free(drawing);
+}
+
+/*
+ * Add a drawing object to the drawing collection.
+ */
+void
+lxw_add_drawing_object(lxw_drawing *drawing,
+                       lxw_drawing_object *drawing_object)
+{
+    STAILQ_INSERT_TAIL(drawing->drawing_objects, drawing_object,
+                       list_pointers);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_drawing_xml_declaration(lxw_drawing *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <xdr:wsDr> element.
+ */
+STATIC void
+_write_drawing_workspace(lxw_drawing *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns_xdr[] = LXW_SCHEMA_DRAWING "/spreadsheetDrawing";
+    char xmlns_a[] = LXW_SCHEMA_DRAWING "/main";
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:xdr", xmlns_xdr);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:a", xmlns_a);
+
+    lxw_xml_start_tag(self->file, "xdr:wsDr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xdr:col> element.
+ */
+STATIC void
+_drawing_write_col(lxw_drawing *self, char *data)
+{
+    lxw_xml_data_element(self->file, "xdr:col", data, NULL);
+}
+
+/*
+ * Write the <xdr:colOff> element.
+ */
+STATIC void
+_drawing_write_col_off(lxw_drawing *self, char *data)
+{
+    lxw_xml_data_element(self->file, "xdr:colOff", data, NULL);
+}
+
+/*
+ * Write the <xdr:row> element.
+ */
+STATIC void
+_drawing_write_row(lxw_drawing *self, char *data)
+{
+    lxw_xml_data_element(self->file, "xdr:row", data, NULL);
+}
+
+/*
+ * Write the <xdr:rowOff> element.
+ */
+STATIC void
+_drawing_write_row_off(lxw_drawing *self, char *data)
+{
+    lxw_xml_data_element(self->file, "xdr:rowOff", data, NULL);
+}
+
+/*
+ * Write the <xdr:from> element.
+ */
+STATIC void
+_drawing_write_from(lxw_drawing *self, lxw_drawing_coords *coords)
+{
+    char data[LXW_UINT32_T_LENGTH];
+
+    lxw_xml_start_tag(self->file, "xdr:from", NULL);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u", coords->col);
+    _drawing_write_col(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u",
+                 (uint32_t) coords->col_offset);
+    _drawing_write_col_off(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u", coords->row);
+    _drawing_write_row(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u",
+                 (uint32_t) coords->row_offset);
+    _drawing_write_row_off(self, data);
+
+    lxw_xml_end_tag(self->file, "xdr:from");
+}
+
+/*
+ * Write the <xdr:to> element.
+ */
+STATIC void
+_drawing_write_to(lxw_drawing *self, lxw_drawing_coords *coords)
+{
+    char data[LXW_UINT32_T_LENGTH];
+
+    lxw_xml_start_tag(self->file, "xdr:to", NULL);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u", coords->col);
+    _drawing_write_col(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u",
+                 (uint32_t) coords->col_offset);
+    _drawing_write_col_off(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u", coords->row);
+    _drawing_write_row(self, data);
+
+    lxw_snprintf(data, LXW_UINT32_T_LENGTH, "%u",
+                 (uint32_t) coords->row_offset);
+    _drawing_write_row_off(self, data);
+
+    lxw_xml_end_tag(self->file, "xdr:to");
+}
+
+/*
+ * Write the <xdr:cNvPr> element.
+ */
+STATIC void
+_drawing_write_c_nv_pr(lxw_drawing *self, char *object_name, uint16_t index,
+                       lxw_drawing_object *drawing_object)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    char name[LXW_OBJ_NAME_LENGTH];
+    lxw_snprintf(name, LXW_OBJ_NAME_LENGTH, "%s %d", object_name, index);
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_INT("id", index + 1);
+    LXW_PUSH_ATTRIBUTES_STR("name", name);
+
+    if (drawing_object)
+        LXW_PUSH_ATTRIBUTES_STR("descr", drawing_object->description);
+
+    lxw_xml_empty_tag(self->file, "xdr:cNvPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:picLocks> element.
+ */
+STATIC void
+_drawing_write_a_pic_locks(lxw_drawing *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("noChangeAspect", "1");
+
+    lxw_xml_empty_tag(self->file, "a:picLocks", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xdr:cNvPicPr> element.
+ */
+STATIC void
+_drawing_write_c_nv_pic_pr(lxw_drawing *self)
+{
+    lxw_xml_start_tag(self->file, "xdr:cNvPicPr", NULL);
+
+    /* Write the a:picLocks element. */
+    _drawing_write_a_pic_locks(self);
+
+    lxw_xml_end_tag(self->file, "xdr:cNvPicPr");
+}
+
+/*
+ * Write the <xdr:nvPicPr> element.
+ */
+STATIC void
+_drawing_write_nv_pic_pr(lxw_drawing *self, uint16_t index,
+                         lxw_drawing_object *drawing_object)
+{
+    lxw_xml_start_tag(self->file, "xdr:nvPicPr", NULL);
+
+    /* Write the xdr:cNvPr element. */
+    _drawing_write_c_nv_pr(self, "Picture", index, drawing_object);
+
+    /* Write the xdr:cNvPicPr element. */
+    _drawing_write_c_nv_pic_pr(self);
+
+    lxw_xml_end_tag(self->file, "xdr:nvPicPr");
+}
+
+/*
+ * Write the <a:blip> element.
+ */
+STATIC void
+_drawing_write_a_blip(lxw_drawing *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns_r[] = LXW_SCHEMA_OFFICEDOC "/relationships";
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
+
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", index);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
+    LXW_PUSH_ATTRIBUTES_STR("r:embed", r_id);
+
+    lxw_xml_empty_tag(self->file, "a:blip", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:fillRect> element.
+ */
+STATIC void
+_drawing_write_a_fill_rect(lxw_drawing *self)
+{
+    lxw_xml_empty_tag(self->file, "a:fillRect", NULL);
+}
+
+/*
+ * Write the <a:stretch> element.
+ */
+STATIC void
+_drawing_write_a_stretch(lxw_drawing *self)
+{
+    lxw_xml_start_tag(self->file, "a:stretch", NULL);
+
+    /* Write the a:fillRect element. */
+    _drawing_write_a_fill_rect(self);
+
+    lxw_xml_end_tag(self->file, "a:stretch");
+}
+
+/*
+ * Write the <xdr:blipFill> element.
+ */
+STATIC void
+_drawing_write_blip_fill(lxw_drawing *self, uint16_t index)
+{
+    lxw_xml_start_tag(self->file, "xdr:blipFill", NULL);
+
+    /* Write the a:blip element. */
+    _drawing_write_a_blip(self, index);
+
+    /* Write the a:stretch element. */
+    _drawing_write_a_stretch(self);
+
+    lxw_xml_end_tag(self->file, "xdr:blipFill");
+}
+
+/*
+ * Write the <a:ext> element.
+ */
+STATIC void
+_drawing_write_a_ext(lxw_drawing *self, lxw_drawing_object *drawing_object)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("cx", drawing_object->width);
+    LXW_PUSH_ATTRIBUTES_INT("cy", drawing_object->height);
+
+    lxw_xml_empty_tag(self->file, "a:ext", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:off> element.
+ */
+STATIC void
+_drawing_write_a_off(lxw_drawing *self, lxw_drawing_object *drawing_object)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("x", drawing_object->col_absolute);
+    LXW_PUSH_ATTRIBUTES_INT("y", drawing_object->row_absolute);
+
+    lxw_xml_empty_tag(self->file, "a:off", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:xfrm> element.
+ */
+STATIC void
+_drawing_write_a_xfrm(lxw_drawing *self, lxw_drawing_object *drawing_object)
+{
+    lxw_xml_start_tag(self->file, "a:xfrm", NULL);
+
+    /* Write the a:off element. */
+    _drawing_write_a_off(self, drawing_object);
+
+    /* Write the a:ext element. */
+    _drawing_write_a_ext(self, drawing_object);
+
+    lxw_xml_end_tag(self->file, "a:xfrm");
+}
+
+/*
+ * Write the <a:avLst> element.
+ */
+STATIC void
+_drawing_write_a_av_lst(lxw_drawing *self)
+{
+    lxw_xml_empty_tag(self->file, "a:avLst", NULL);
+}
+
+/*
+ * Write the <a:prstGeom> element.
+ */
+STATIC void
+_drawing_write_a_prst_geom(lxw_drawing *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("prst", "rect");
+
+    lxw_xml_start_tag(self->file, "a:prstGeom", &attributes);
+
+    /* Write the a:avLst element. */
+    _drawing_write_a_av_lst(self);
+
+    lxw_xml_end_tag(self->file, "a:prstGeom");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xdr:spPr> element.
+ */
+STATIC void
+_drawing_write_sp_pr(lxw_drawing *self, lxw_drawing_object *drawing_object)
+{
+    lxw_xml_start_tag(self->file, "xdr:spPr", NULL);
+
+    /* Write the a:xfrm element. */
+    _drawing_write_a_xfrm(self, drawing_object);
+
+    /* Write the a:prstGeom element. */
+    _drawing_write_a_prst_geom(self);
+
+    lxw_xml_end_tag(self->file, "xdr:spPr");
+}
+
+/*
+ * Write the <xdr:pic> element.
+ */
+STATIC void
+_drawing_write_pic(lxw_drawing *self, uint16_t index,
+                   lxw_drawing_object *drawing_object)
+{
+    lxw_xml_start_tag(self->file, "xdr:pic", NULL);
+
+    /* Write the xdr:nvPicPr element. */
+    _drawing_write_nv_pic_pr(self, index, drawing_object);
+
+    /* Write the xdr:blipFill element. */
+    _drawing_write_blip_fill(self, index);
+
+    /* Write the xdr:spPr element. */
+    _drawing_write_sp_pr(self, drawing_object);
+
+    lxw_xml_end_tag(self->file, "xdr:pic");
+}
+
+/*
+ * Write the <xdr:clientData> element.
+ */
+STATIC void
+_drawing_write_client_data(lxw_drawing *self)
+{
+    lxw_xml_empty_tag(self->file, "xdr:clientData", NULL);
+}
+
+/*
+ * Write the <xdr:cNvGraphicFramePr> element.
+ */
+STATIC void
+_drawing_write_c_nv_graphic_frame_pr(lxw_drawing *self)
+{
+    lxw_xml_empty_tag(self->file, "xdr:cNvGraphicFramePr", NULL);
+}
+
+/*
+ * Write the <xdr:nvGraphicFramePr> element.
+ */
+STATIC void
+_drawing_write_nv_graphic_frame_pr(lxw_drawing *self, uint16_t index)
+{
+    lxw_xml_start_tag(self->file, "xdr:nvGraphicFramePr", NULL);
+
+    /* Write the xdr:cNvPr element. */
+    _drawing_write_c_nv_pr(self, "Chart", index, NULL);
+
+    /* Write the xdr:cNvGraphicFramePr element. */
+    _drawing_write_c_nv_graphic_frame_pr(self);
+
+    lxw_xml_end_tag(self->file, "xdr:nvGraphicFramePr");
+}
+
+/*
+ * Write the <a:off> element.
+ */
+STATIC void
+_drawing_write_xfrm_offset(lxw_drawing *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("x", "0");
+    LXW_PUSH_ATTRIBUTES_STR("y", "0");
+
+    lxw_xml_empty_tag(self->file, "a:off", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:ext> element.
+ */
+STATIC void
+_drawing_write_xfrm_extension(lxw_drawing *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("cx", "0");
+    LXW_PUSH_ATTRIBUTES_STR("cy", "0");
+
+    lxw_xml_empty_tag(self->file, "a:ext", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xdr:xfrm> element.
+ */
+STATIC void
+_drawing_write_xfrm(lxw_drawing *self)
+{
+    lxw_xml_start_tag(self->file, "xdr:xfrm", NULL);
+
+    /* Write the a:off element. */
+    _drawing_write_xfrm_offset(self);
+
+    /* Write the a:ext element. */
+    _drawing_write_xfrm_extension(self);
+
+    lxw_xml_end_tag(self->file, "xdr:xfrm");
+}
+
+/*
+ * Write the <c:chart> element.
+ */
+STATIC void
+_drawing_write_chart(lxw_drawing *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns_c[] = LXW_SCHEMA_DRAWING "/chart";
+    char xmlns_r[] = LXW_SCHEMA_OFFICEDOC "/relationships";
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
+
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", index);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:c", xmlns_c);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
+    LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
+
+    lxw_xml_empty_tag(self->file, "c:chart", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:graphicData> element.
+ */
+STATIC void
+_drawing_write_a_graphic_data(lxw_drawing *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char uri[] = LXW_SCHEMA_DRAWING "/chart";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("uri", uri);
+
+    lxw_xml_start_tag(self->file, "a:graphicData", &attributes);
+
+    /* Write the c:chart element. */
+    _drawing_write_chart(self, index);
+
+    lxw_xml_end_tag(self->file, "a:graphicData");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <a:graphic> element.
+ */
+STATIC void
+_drawing_write_a_graphic(lxw_drawing *self, uint16_t index)
+{
+
+    lxw_xml_start_tag(self->file, "a:graphic", NULL);
+
+    /* Write the a:graphicData element. */
+    _drawing_write_a_graphic_data(self, index);
+
+    lxw_xml_end_tag(self->file, "a:graphic");
+}
+
+/*
+ * Write the <xdr:graphicFrame> element.
+ */
+STATIC void
+_drawing_write_graphic_frame(lxw_drawing *self, uint16_t index)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("macro", "");
+
+    lxw_xml_start_tag(self->file, "xdr:graphicFrame", &attributes);
+
+    /* Write the xdr:nvGraphicFramePr element. */
+    _drawing_write_nv_graphic_frame_pr(self, index);
+
+    /* Write the xdr:xfrm element. */
+    _drawing_write_xfrm(self);
+
+    /* Write the a:graphic element. */
+    _drawing_write_a_graphic(self, index);
+
+    lxw_xml_end_tag(self->file, "xdr:graphicFrame");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xdr:twoCellAnchor> element.
+ */
+STATIC void
+_drawing_write_two_cell_anchor(lxw_drawing *self, uint16_t index,
+                               lxw_drawing_object *drawing_object)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (drawing_object->anchor_type == LXW_ANCHOR_TYPE_IMAGE) {
+
+        if (drawing_object->edit_as == LXW_ANCHOR_EDIT_AS_ABSOLUTE)
+            LXW_PUSH_ATTRIBUTES_STR("editAs", "absolute");
+        else if (drawing_object->edit_as != LXW_ANCHOR_EDIT_AS_RELATIVE)
+            LXW_PUSH_ATTRIBUTES_STR("editAs", "oneCell");
+    }
+
+    lxw_xml_start_tag(self->file, "xdr:twoCellAnchor", &attributes);
+
+    _drawing_write_from(self, &drawing_object->from);
+    _drawing_write_to(self, &drawing_object->to);
+
+    if (drawing_object->anchor_type == LXW_ANCHOR_TYPE_CHART) {
+        /* Write the xdr:graphicFrame element for charts. */
+        _drawing_write_graphic_frame(self, index);
+    }
+    else if (drawing_object->anchor_type == LXW_ANCHOR_TYPE_IMAGE) {
+        /* Write the xdr:pic element. */
+        _drawing_write_pic(self, index, drawing_object);
+    }
+    else {
+        /* Write the xdr:sp element for shapes. */
+        /* _drawing_write_sp(self, index, col_absolute, row_absolute, width,
+           height,  shape); */
+    }
+
+    /* Write the xdr:clientData element. */
+    _drawing_write_client_data(self);
+
+    lxw_xml_end_tag(self->file, "xdr:twoCellAnchor");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_drawing_assemble_xml_file(lxw_drawing *self)
+{
+    uint16_t index;
+    lxw_drawing_object *drawing_object;
+
+    /* Write the XML declaration. */
+    _drawing_xml_declaration(self);
+
+    /* Write the xdr:wsDr element. */
+    _write_drawing_workspace(self);
+
+    if (self->embedded) {
+        index = 1;
+
+        STAILQ_FOREACH(drawing_object, self->drawing_objects, list_pointers) {
+            _drawing_write_two_cell_anchor(self, index, drawing_object);
+            index++;
+        }
+
+    }
+
+    lxw_xml_end_tag(self->file, "xdr:wsDr");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/

+ 728 - 0
library/src/format.c

@@ -0,0 +1,728 @@
+/*****************************************************************************
+ * format - A library for creating Excel XLSX format files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/format.h"
+#include "xlsxwriter/utility.h"
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new format object.
+ */
+lxw_format *
+lxw_format_new()
+{
+    lxw_format *format = calloc(1, sizeof(lxw_format));
+    GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
+
+    format->xf_format_indices = NULL;
+
+    format->xf_index = LXW_PROPERTY_UNSET;
+    format->dxf_index = LXW_PROPERTY_UNSET;
+
+    format->font_name[0] = '\0';
+    format->font_scheme[0] = '\0';
+    format->num_format[0] = '\0';
+    format->num_format_index = 0;
+    format->font_index = 0;
+    format->has_font = LXW_FALSE;
+    format->has_dxf_font = LXW_FALSE;
+    format->font_size = 11.0;
+    format->bold = LXW_FALSE;
+    format->italic = LXW_FALSE;
+    format->font_color = LXW_COLOR_UNSET;
+    format->underline = LXW_FALSE;
+    format->font_strikeout = LXW_FALSE;
+    format->font_outline = LXW_FALSE;
+    format->font_shadow = LXW_FALSE;
+    format->font_script = LXW_FALSE;
+    format->font_family = LXW_DEFAULT_FONT_FAMILY;
+    format->font_charset = LXW_FALSE;
+    format->font_condense = LXW_FALSE;
+    format->font_extend = LXW_FALSE;
+    format->theme = LXW_FALSE;
+    format->hyperlink = LXW_FALSE;
+
+    format->hidden = LXW_FALSE;
+    format->locked = LXW_TRUE;
+
+    format->text_h_align = LXW_ALIGN_NONE;
+    format->text_wrap = LXW_FALSE;
+    format->text_v_align = LXW_ALIGN_NONE;
+    format->text_justlast = LXW_FALSE;
+    format->rotation = 0;
+
+    format->fg_color = LXW_COLOR_UNSET;
+    format->bg_color = LXW_COLOR_UNSET;
+    format->pattern = LXW_PATTERN_NONE;
+    format->has_fill = LXW_FALSE;
+    format->has_dxf_fill = LXW_FALSE;
+    format->fill_index = 0;
+    format->fill_count = 0;
+
+    format->border_index = 0;
+    format->has_border = LXW_FALSE;
+    format->has_dxf_border = LXW_FALSE;
+    format->border_count = 0;
+
+    format->bottom = LXW_BORDER_NONE;
+    format->left = LXW_BORDER_NONE;
+    format->right = LXW_BORDER_NONE;
+    format->top = LXW_BORDER_NONE;
+    format->diag_border = LXW_BORDER_NONE;
+    format->diag_type = LXW_BORDER_NONE;
+    format->bottom_color = LXW_COLOR_UNSET;
+    format->left_color = LXW_COLOR_UNSET;
+    format->right_color = LXW_COLOR_UNSET;
+    format->top_color = LXW_COLOR_UNSET;
+    format->diag_color = LXW_COLOR_UNSET;
+
+    format->indent = 0;
+    format->shrink = LXW_FALSE;
+    format->merge_range = LXW_FALSE;
+    format->reading_order = 0;
+    format->just_distrib = LXW_FALSE;
+    format->color_indexed = LXW_FALSE;
+    format->font_only = LXW_FALSE;
+
+    return format;
+
+mem_error:
+    lxw_format_free(format);
+    return NULL;
+}
+
+/*
+ * Free a format object.
+ */
+void
+lxw_format_free(lxw_format *format)
+{
+    if (!format)
+        return;
+
+    free(format);
+    format = NULL;
+}
+
+/*
+ * Check a user input color.
+ */
+lxw_color_t
+lxw_format_check_color(lxw_color_t color)
+{
+    if (color == LXW_COLOR_UNSET)
+        return color;
+    else
+        return color & LXW_COLOR_MASK;
+}
+
+/*
+ * Check a user input border.
+ */
+STATIC uint8_t
+_check_border(uint8_t border)
+{
+    if (border >= LXW_BORDER_THIN && border <= LXW_BORDER_SLANT_DASH_DOT)
+        return border;
+    else
+        return LXW_BORDER_NONE;
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Returns a format struct suitable for hashing as a lookup key. This is
+ * mainly a memcpy with any pointer members set to NULL.
+ */
+STATIC lxw_format *
+_get_format_key(lxw_format *self)
+{
+    lxw_format *key = calloc(1, sizeof(lxw_format));
+    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);
+
+    memcpy(key, self, sizeof(lxw_format));
+
+    /* Set pointer members to NULL since they aren't part of the comparison. */
+    key->xf_format_indices = NULL;
+    key->num_xf_formats = NULL;
+    key->list_pointers.stqe_next = NULL;
+
+    return key;
+
+mem_error:
+    return NULL;
+}
+
+/*
+ * Returns a font struct suitable for hashing as a lookup key.
+ */
+lxw_font *
+lxw_format_get_font_key(lxw_format *self)
+{
+    lxw_font *key = calloc(1, sizeof(lxw_font));
+    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);
+
+    LXW_FORMAT_FIELD_COPY(key->font_name, self->font_name);
+    key->font_size = self->font_size;
+    key->bold = self->bold;
+    key->italic = self->italic;
+    key->font_color = self->font_color;
+    key->underline = self->underline;
+    key->font_strikeout = self->font_strikeout;
+    key->font_outline = self->font_outline;
+    key->font_shadow = self->font_shadow;
+    key->font_script = self->font_script;
+    key->font_family = self->font_family;
+    key->font_charset = self->font_charset;
+    key->font_condense = self->font_condense;
+    key->font_extend = self->font_extend;
+
+    return key;
+
+mem_error:
+    return NULL;
+}
+
+/*
+ * Returns a border struct suitable for hashing as a lookup key.
+ */
+lxw_border *
+lxw_format_get_border_key(lxw_format *self)
+{
+    lxw_border *key = calloc(1, sizeof(lxw_border));
+    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);
+
+    key->bottom = self->bottom;
+    key->left = self->left;
+    key->right = self->right;
+    key->top = self->top;
+    key->diag_border = self->diag_border;
+    key->diag_type = self->diag_type;
+    key->bottom_color = self->bottom_color;
+    key->left_color = self->left_color;
+    key->right_color = self->right_color;
+    key->top_color = self->top_color;
+    key->diag_color = self->diag_color;
+
+    return key;
+
+mem_error:
+    return NULL;
+}
+
+/*
+ * Returns a pattern fill struct suitable for hashing as a lookup key.
+ */
+lxw_fill *
+lxw_format_get_fill_key(lxw_format *self)
+{
+    lxw_fill *key = calloc(1, sizeof(lxw_fill));
+    GOTO_LABEL_ON_MEM_ERROR(key, mem_error);
+
+    key->fg_color = self->fg_color;
+    key->bg_color = self->bg_color;
+    key->pattern = self->pattern;
+
+    return key;
+
+mem_error:
+    return NULL;
+}
+
+/*
+ * Returns the XF index number used by Excel to identify a format.
+ */
+int32_t
+lxw_format_get_xf_index(lxw_format *self)
+{
+    lxw_format *format_key;
+    lxw_format *existing_format;
+    lxw_hash_element *hash_element;
+    lxw_hash_table *formats_hash_table = self->xf_format_indices;
+    int32_t index;
+
+    /* Note: The formats_hash_table/xf_format_indices contains the unique and
+     * more importantly the *used* formats in the workbook.
+     */
+
+    /* Format already has an index number so return it. */
+    if (self->xf_index != LXW_PROPERTY_UNSET) {
+        return self->xf_index;
+    }
+
+    /* Otherwise, the format doesn't have an index number so we assign one.
+     * First generate a unique key to identify the format in the hash table.
+     */
+    format_key = _get_format_key(self);
+
+    /* Return the default format index if the key generation failed. */
+    if (!format_key)
+        return 0;
+
+    /* Look up the format in the hash table. */
+    hash_element =
+        lxw_hash_key_exists(formats_hash_table, format_key,
+                            sizeof(lxw_format));
+
+    if (hash_element) {
+        /* Format matches existing format with an index. */
+        free(format_key);
+        existing_format = hash_element->value;
+        return existing_format->xf_index;
+    }
+    else {
+        /* New format requiring an index. */
+        index = formats_hash_table->unique_count;
+        self->xf_index = index;
+        lxw_insert_hash_element(formats_hash_table, format_key, self,
+                                sizeof(lxw_format));
+        return index;
+    }
+}
+
+/*
+ * Set the font_name property.
+ */
+void
+format_set_font_name(lxw_format *self, const char *font_name)
+{
+    LXW_FORMAT_FIELD_COPY(self->font_name, font_name);
+}
+
+/*
+ * Set the font_size property.
+ */
+void
+format_set_font_size(lxw_format *self, double size)
+{
+
+    if (size >= LXW_MIN_FONT_SIZE && size <= LXW_MAX_FONT_SIZE)
+        self->font_size = size;
+}
+
+/*
+ * Set the font_color property.
+ */
+void
+format_set_font_color(lxw_format *self, lxw_color_t color)
+{
+    self->font_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the bold property.
+ */
+void
+format_set_bold(lxw_format *self)
+{
+    self->bold = LXW_TRUE;
+}
+
+/*
+ * Set the italic property.
+ */
+
+void
+format_set_italic(lxw_format *self)
+{
+    self->italic = LXW_TRUE;
+}
+
+/*
+ * Set the underline property.
+ */
+void
+format_set_underline(lxw_format *self, uint8_t style)
+{
+    if (style >= LXW_UNDERLINE_SINGLE
+        && style <= LXW_UNDERLINE_DOUBLE_ACCOUNTING)
+        self->underline = style;
+}
+
+/*
+ * Set the font_strikeout property.
+ */
+void
+format_set_font_strikeout(lxw_format *self)
+{
+    self->font_strikeout = LXW_TRUE;
+}
+
+/*
+ * Set the font_script property.
+ */
+void
+format_set_font_script(lxw_format *self, uint8_t style)
+{
+    if (style >= LXW_FONT_SUPERSCRIPT && style <= LXW_FONT_SUBSCRIPT)
+        self->font_script = style;
+}
+
+/*
+ * Set the font_outline property.
+ */
+void
+format_set_font_outline(lxw_format *self)
+{
+    self->font_outline = LXW_TRUE;
+}
+
+/*
+ * Set the font_shadow property.
+ */
+void
+format_set_font_shadow(lxw_format *self)
+{
+    self->font_shadow = LXW_TRUE;
+}
+
+/*
+ * Set the num_format property.
+ */
+void
+format_set_num_format(lxw_format *self, const char *num_format)
+{
+    LXW_FORMAT_FIELD_COPY(self->num_format, num_format);
+}
+
+/*
+ * Set the unlocked property.
+ */
+void
+format_set_unlocked(lxw_format *self)
+{
+    self->locked = LXW_FALSE;
+}
+
+/*
+ * Set the hidden property.
+ */
+void
+format_set_hidden(lxw_format *self)
+{
+    self->hidden = LXW_TRUE;
+}
+
+/*
+ * Set the align property.
+ */
+void
+format_set_align(lxw_format *self, uint8_t value)
+{
+    if (value >= LXW_ALIGN_LEFT && value <= LXW_ALIGN_DISTRIBUTED) {
+        self->text_h_align = value;
+    }
+
+    if (value >= LXW_ALIGN_VERTICAL_TOP
+        && value <= LXW_ALIGN_VERTICAL_DISTRIBUTED) {
+        self->text_v_align = value;
+    }
+}
+
+/*
+ * Set the text_wrap property.
+ */
+void
+format_set_text_wrap(lxw_format *self)
+{
+    self->text_wrap = LXW_TRUE;
+}
+
+/*
+ * Set the rotation property.
+ */
+void
+format_set_rotation(lxw_format *self, int16_t angle)
+{
+    /* Convert user angle to Excel angle. */
+    if (angle == 270) {
+        self->rotation = 255;
+    }
+    else if (angle >= -90 || angle <= 90) {
+        if (angle < 0)
+            angle = -angle + 90;
+
+        self->rotation = angle;
+    }
+    else {
+        LXW_WARN("Rotation rotation outside range: -90 <= angle <= 90.");
+        self->rotation = 0;
+    }
+}
+
+/*
+ * Set the indent property.
+ */
+void
+format_set_indent(lxw_format *self, uint8_t value)
+{
+    self->indent = value;
+}
+
+/*
+ * Set the shrink property.
+ */
+void
+format_set_shrink(lxw_format *self)
+{
+    self->shrink = LXW_TRUE;
+}
+
+/*
+ * Set the text_justlast property.
+ */
+void
+format_set_text_justlast(lxw_format *self)
+{
+    self->text_justlast = LXW_TRUE;
+}
+
+/*
+ * Set the pattern property.
+ */
+void
+format_set_pattern(lxw_format *self, uint8_t value)
+{
+    self->pattern = value;
+}
+
+/*
+ * Set the bg_color property.
+ */
+void
+format_set_bg_color(lxw_format *self, lxw_color_t color)
+{
+    self->bg_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the fg_color property.
+ */
+void
+format_set_fg_color(lxw_format *self, lxw_color_t color)
+{
+    self->fg_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the border property.
+ */
+void
+format_set_border(lxw_format *self, uint8_t style)
+{
+    style = _check_border(style);
+    self->bottom = style;
+    self->top = style;
+    self->left = style;
+    self->right = style;
+}
+
+/*
+ * Set the border_color property.
+ */
+void
+format_set_border_color(lxw_format *self, lxw_color_t color)
+{
+    color = lxw_format_check_color(color);
+    self->bottom_color = color;
+    self->top_color = color;
+    self->left_color = color;
+    self->right_color = color;
+}
+
+/*
+ * Set the bottom property.
+ */
+void
+format_set_bottom(lxw_format *self, uint8_t style)
+{
+    self->bottom = _check_border(style);
+}
+
+/*
+ * Set the bottom_color property.
+ */
+void
+format_set_bottom_color(lxw_format *self, lxw_color_t color)
+{
+    self->bottom_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the left property.
+ */
+void
+format_set_left(lxw_format *self, uint8_t style)
+{
+    self->left = _check_border(style);
+}
+
+/*
+ * Set the left_color property.
+ */
+void
+format_set_left_color(lxw_format *self, lxw_color_t color)
+{
+    self->left_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the right property.
+ */
+void
+format_set_right(lxw_format *self, uint8_t style)
+{
+    self->right = _check_border(style);
+}
+
+/*
+ * Set the right_color property.
+ */
+void
+format_set_right_color(lxw_format *self, lxw_color_t color)
+{
+    self->right_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the top property.
+ */
+void
+format_set_top(lxw_format *self, uint8_t style)
+{
+    self->top = _check_border(style);
+}
+
+/*
+ * Set the top_color property.
+ */
+void
+format_set_top_color(lxw_format *self, lxw_color_t color)
+{
+    self->top_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the diag_type property.
+ */
+void
+format_set_diag_type(lxw_format *self, uint8_t type)
+{
+    if (type >= LXW_DIAGONAL_BORDER_UP && type <= LXW_DIAGONAL_BORDER_UP_DOWN)
+        self->diag_type = type;
+}
+
+/*
+ * Set the diag_color property.
+ */
+void
+format_set_diag_color(lxw_format *self, lxw_color_t color)
+{
+    self->diag_color = lxw_format_check_color(color);
+}
+
+/*
+ * Set the diag_border property.
+ */
+void
+format_set_diag_border(lxw_format *self, uint8_t style)
+{
+    self->diag_border = style;
+}
+
+/*
+ * Set the num_format_index property.
+ */
+void
+format_set_num_format_index(lxw_format *self, uint8_t value)
+{
+    self->num_format_index = value;
+}
+
+/*
+ * Set the valign property.
+ */
+void
+format_set_valign(lxw_format *self, uint8_t value)
+{
+    self->text_v_align = value;
+}
+
+/*
+ * Set the reading_order property.
+ */
+void
+format_set_reading_order(lxw_format *self, uint8_t value)
+{
+    self->reading_order = value;
+}
+
+/*
+ * Set the font_family property.
+ */
+void
+format_set_font_family(lxw_format *self, uint8_t value)
+{
+    self->font_family = value;
+}
+
+/*
+ * Set the font_charset property.
+ */
+void
+format_set_font_charset(lxw_format *self, uint8_t value)
+{
+    self->font_charset = value;
+}
+
+/*
+ * Set the font_scheme property.
+ */
+void
+format_set_font_scheme(lxw_format *self, const char *font_scheme)
+{
+    LXW_FORMAT_FIELD_COPY(self->font_scheme, font_scheme);
+}
+
+/*
+ * Set the font_condense property.
+ */
+void
+format_set_font_condense(lxw_format *self)
+{
+    self->font_condense = LXW_TRUE;
+}
+
+/*
+ * Set the font_extend property.
+ */
+void
+format_set_font_extend(lxw_format *self)
+{
+    self->font_extend = LXW_TRUE;
+}
+
+/*
+ * Set the theme property.
+ */
+void
+format_set_theme(lxw_format *self, uint8_t value)
+{
+    self->theme = value;
+}

+ 223 - 0
library/src/hash_table.c

@@ -0,0 +1,223 @@
+/*****************************************************************************
+ * hash_table - Hash table functions for libxlsxwriter.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include "xlsxwriter/hash_table.h"
+
+/*
+ * Calculate the hash key using the FNV function. See:
+ * http://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function
+ */
+STATIC size_t
+_generate_hash_key(void *data, size_t data_len, size_t num_buckets)
+{
+    unsigned char *p = data;
+    size_t hash = 2166136261U;
+    size_t i;
+
+    for (i = 0; i < data_len; i++)
+        hash = (hash * 16777619) ^ p[i];
+
+    return hash % num_buckets;
+}
+
+/*
+ * Check if an element exists in the hash table and return a pointer
+ * to it if it does.
+ */
+lxw_hash_element *
+lxw_hash_key_exists(lxw_hash_table *lxw_hash, void *key, size_t key_len)
+{
+    size_t hash_key = _generate_hash_key(key, key_len, lxw_hash->num_buckets);
+    struct lxw_hash_bucket_list *list;
+    lxw_hash_element *element;
+
+    if (!lxw_hash->buckets[hash_key]) {
+        /* The key isn't in the LXW_HASH hash table. */
+        return NULL;
+    }
+    else {
+        /* The key is already in the table or there is a hash collision. */
+        list = lxw_hash->buckets[hash_key];
+
+        /* Iterate over the keys in the bucket's linked list. */
+        SLIST_FOREACH(element, list, lxw_hash_list_pointers) {
+            if (memcmp(element->key, key, key_len) == 0) {
+                /* The key already exists in the table. */
+                return element;
+            }
+        }
+
+        /* Key doesn't exist in the list so this is a hash collision. */
+        return NULL;
+    }
+}
+
+/*
+ * Insert or update a value in the LXW_HASH table based on a key
+ * and return a pointer to the new or updated element.
+ */
+lxw_hash_element *
+lxw_insert_hash_element(lxw_hash_table *lxw_hash, void *key, void *value,
+                        size_t key_len)
+{
+    size_t hash_key = _generate_hash_key(key, key_len, lxw_hash->num_buckets);
+    struct lxw_hash_bucket_list *list = NULL;
+    lxw_hash_element *element = NULL;
+
+    if (!lxw_hash->buckets[hash_key]) {
+        /* The key isn't in the LXW_HASH hash table. */
+
+        /* Create a linked list in the bucket to hold the lxw_hash keys. */
+        list = calloc(1, sizeof(struct lxw_hash_bucket_list));
+        GOTO_LABEL_ON_MEM_ERROR(list, mem_error1);
+
+        /* Initialize the bucket linked list. */
+        SLIST_INIT(list);
+
+        /* Create an lxw_hash element to add to the linked list. */
+        element = calloc(1, sizeof(lxw_hash_element));
+        GOTO_LABEL_ON_MEM_ERROR(element, mem_error1);
+
+        /* Store the key and value. */
+        element->key = key;
+        element->value = value;
+
+        /* Add the lxw_hash element to the bucket's linked list. */
+        SLIST_INSERT_HEAD(list, element, lxw_hash_list_pointers);
+
+        /* Also add it to the insertion order linked list. */
+        STAILQ_INSERT_TAIL(lxw_hash->order_list, element,
+                           lxw_hash_order_pointers);
+
+        /* Store the bucket list at the hash index. */
+        lxw_hash->buckets[hash_key] = list;
+
+        lxw_hash->used_buckets++;
+        lxw_hash->unique_count++;
+
+        return element;
+    }
+    else {
+        /* The key is already in the table or there is a hash collision. */
+        list = lxw_hash->buckets[hash_key];
+
+        /* Iterate over the keys in the bucket's linked list. */
+        SLIST_FOREACH(element, list, lxw_hash_list_pointers) {
+            if (memcmp(element->key, key, key_len) == 0) {
+                /* The key already exists in the table. Update the value. */
+                if (lxw_hash->free_value)
+                    free(element->value);
+
+                element->value = value;
+                return element;
+            }
+        }
+
+        /* Key doesn't exist in the list so this is a hash collision.
+         * Create an lxw_hash element to add to the linked list. */
+        element = calloc(1, sizeof(lxw_hash_element));
+        GOTO_LABEL_ON_MEM_ERROR(element, mem_error2);
+
+        /* Store the key and value. */
+        element->key = key;
+        element->value = value;
+
+        /* Add the lxw_hash element to the bucket linked list. */
+        SLIST_INSERT_HEAD(list, element, lxw_hash_list_pointers);
+
+        /* Also add it to the insertion order linked list. */
+        STAILQ_INSERT_TAIL(lxw_hash->order_list, element,
+                           lxw_hash_order_pointers);
+
+        lxw_hash->unique_count++;
+
+        return element;
+    }
+
+mem_error1:
+    free(list);
+
+mem_error2:
+    free(element);
+    return NULL;
+}
+
+/*
+ * Create a new LXW_HASH hash table object.
+ */
+lxw_hash_table *
+lxw_hash_new(uint32_t num_buckets, uint8_t free_key, uint8_t free_value)
+{
+    /* Create the new hash table. */
+    lxw_hash_table *lxw_hash = calloc(1, sizeof(lxw_hash_table));
+    RETURN_ON_MEM_ERROR(lxw_hash, NULL);
+
+    lxw_hash->free_key = free_key;
+    lxw_hash->free_value = free_value;
+
+    /* Add the lxw_hash element buckets. */
+    lxw_hash->buckets =
+        calloc(num_buckets, sizeof(struct lxw_hash_bucket_list *));
+    GOTO_LABEL_ON_MEM_ERROR(lxw_hash->buckets, mem_error);
+
+    /* Add a list for tracking the insertion order. */
+    lxw_hash->order_list = calloc(1, sizeof(struct lxw_hash_order_list));
+    GOTO_LABEL_ON_MEM_ERROR(lxw_hash->order_list, mem_error);
+
+    /* Initialize the order list. */
+    STAILQ_INIT(lxw_hash->order_list);
+
+    /* Store the number of buckets to calculate the load factor. */
+    lxw_hash->num_buckets = num_buckets;
+
+    return lxw_hash;
+
+mem_error:
+    lxw_hash_free(lxw_hash);
+    return NULL;
+}
+
+/*
+ * Free the LXW_HASH hash table object.
+ */
+void
+lxw_hash_free(lxw_hash_table *lxw_hash)
+{
+    size_t i;
+    lxw_hash_element *element;
+    lxw_hash_element *element_temp;
+
+    if (!lxw_hash)
+        return;
+
+    /* Free the lxw_hash_elements and data using the ordered linked list. */
+    if (lxw_hash->order_list) {
+        STAILQ_FOREACH_SAFE(element, lxw_hash->order_list,
+                            lxw_hash_order_pointers, element_temp) {
+            if (lxw_hash->free_key)
+                free(element->key);
+            if (lxw_hash->free_value)
+                free(element->value);
+            free(element);
+        }
+    }
+
+    /* Free the buckets from the hash table. */
+    for (i = 0; i < lxw_hash->num_buckets; i++) {
+        free(lxw_hash->buckets[i]);
+    }
+
+    free(lxw_hash->order_list);
+    free(lxw_hash->buckets);
+    free(lxw_hash);
+}

+ 961 - 0
library/src/packager.c

@@ -0,0 +1,961 @@
+/*****************************************************************************
+ * packager - A library for creating Excel XLSX packager files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/packager.h"
+#include "xlsxwriter/hash_table.h"
+#include "xlsxwriter/utility.h"
+
+STATIC lxw_error _add_file_to_zip(lxw_packager *self, FILE * file,
+                                  const char *filename);
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+/* Avoid non MSVC definition of _WIN32 in MinGW. */
+
+#ifdef __MINGW32__
+#undef _WIN32
+#endif
+
+#ifdef _WIN32
+
+/* Silence Windows warning with duplicate symbol for SLIST_ENTRY in local
+ * queue.h and widows.h. */
+#undef SLIST_ENTRY
+
+#include <windows.h>
+
+#ifdef USE_SYSTEM_MINIZIP
+#include "minizip/iowin32.h"
+#else
+#include "../third_party/minizip/iowin32.h"
+#endif
+
+zipFile
+_open_zipfile_win32(const char *filename)
+{
+    int n;
+    zlib_filefunc64_def filefunc;
+
+    wchar_t wide_filename[_MAX_PATH + 1] = L"";
+
+    /* Build a UTF-16 filename for Win32. */
+    n = MultiByteToWideChar(CP_UTF8, 0, filename, (int) strlen(filename),
+                            wide_filename, _MAX_PATH);
+
+    if (n == 0) {
+        LXW_ERROR("MultiByteToWideChar error");
+        return NULL;
+    }
+
+    /* Use the native Win32 file handling functions with minizip. */
+    fill_win32_filefunc64W(&filefunc);
+
+    return zipOpen2_64(wide_filename, 0, NULL, &filefunc);
+}
+
+#endif
+
+/*
+ * Create a new packager object.
+ */
+lxw_packager *
+lxw_packager_new(const char *filename, char *tmpdir)
+{
+    lxw_packager *packager = calloc(1, sizeof(lxw_packager));
+    GOTO_LABEL_ON_MEM_ERROR(packager, mem_error);
+
+    packager->buffer = calloc(1, LXW_ZIP_BUFFER_SIZE);
+    GOTO_LABEL_ON_MEM_ERROR(packager->buffer, mem_error);
+
+    packager->filename = lxw_strdup(filename);
+    packager->tmpdir = tmpdir;
+    GOTO_LABEL_ON_MEM_ERROR(packager->filename, mem_error);
+
+    packager->buffer_size = LXW_ZIP_BUFFER_SIZE;
+
+    /* Initialize the zip_fileinfo struct to Jan 1 1980 like Excel. */
+    packager->zipfile_info.tmz_date.tm_sec = 0;
+    packager->zipfile_info.tmz_date.tm_min = 0;
+    packager->zipfile_info.tmz_date.tm_hour = 0;
+    packager->zipfile_info.tmz_date.tm_mday = 1;
+    packager->zipfile_info.tmz_date.tm_mon = 0;
+    packager->zipfile_info.tmz_date.tm_year = 1980;
+    packager->zipfile_info.dosDate = 0;
+    packager->zipfile_info.internal_fa = 0;
+    packager->zipfile_info.external_fa = 0;
+
+    /* Create a zip container for the xlsx file. */
+#ifdef _WIN32
+    packager->zipfile = _open_zipfile_win32(packager->filename);
+#else
+    packager->zipfile = zipOpen(packager->filename, 0);
+#endif
+
+    if (packager->zipfile == NULL)
+        goto mem_error;
+
+    return packager;
+
+mem_error:
+    lxw_packager_free(packager);
+    return NULL;
+}
+
+/*
+ * Free a packager object.
+ */
+void
+lxw_packager_free(lxw_packager *packager)
+{
+    if (!packager)
+        return;
+
+    free(packager->buffer);
+    free(packager->filename);
+    free(packager);
+}
+
+/*****************************************************************************
+ *
+ * File assembly functions.
+ *
+ ****************************************************************************/
+/*
+ * Write the workbook.xml file.
+ */
+STATIC lxw_error
+_write_workbook_file(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_error err;
+
+    workbook->file = lxw_tmpfile(self->tmpdir);
+    if (!workbook->file)
+        return LXW_ERROR_CREATING_TMPFILE;
+
+    lxw_workbook_assemble_xml_file(workbook);
+
+    err = _add_file_to_zip(self, workbook->file, "xl/workbook.xml");
+    RETURN_ON_ERROR(err);
+
+    fclose(workbook->file);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the worksheet files.
+ */
+STATIC lxw_error
+_write_worksheet_files(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    char sheetname[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err;
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+        lxw_snprintf(sheetname, LXW_FILENAME_LENGTH,
+                     "xl/worksheets/sheet%d.xml", index++);
+
+        if (worksheet->optimize_row)
+            lxw_worksheet_write_single_row(worksheet);
+
+        worksheet->file = lxw_tmpfile(self->tmpdir);
+        if (!worksheet->file)
+            return LXW_ERROR_CREATING_TMPFILE;
+
+        lxw_worksheet_assemble_xml_file(worksheet);
+
+        err = _add_file_to_zip(self, worksheet->file, sheetname);
+        RETURN_ON_ERROR(err);
+
+        fclose(worksheet->file);
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the /xl/media/image?.xml files.
+ */
+STATIC lxw_error
+_write_image_files(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    lxw_image_options *image;
+    lxw_error err;
+    FILE *image_stream;
+
+    char filename[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+
+        if (STAILQ_EMPTY(worksheet->image_data))
+            continue;
+
+        STAILQ_FOREACH(image, worksheet->image_data, list_pointers) {
+
+            lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                         "xl/media/image%d.%s", index++, image->extension);
+
+            /* Check that the image file exists and can be opened. */
+            image_stream = fopen(image->filename, "rb");
+            if (!image_stream) {
+                LXW_WARN_FORMAT1("Error adding image to xlsx file: file "
+                                 "doesn't exist or can't be opened: %s.",
+                                 image->filename);
+                return LXW_ERROR_CREATING_TMPFILE;
+            }
+
+            err = _add_file_to_zip(self, image_stream, filename);
+            fclose(image_stream);
+
+            RETURN_ON_ERROR(err);
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the chart files.
+ */
+STATIC lxw_error
+_write_chart_files(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_chart *chart;
+    char sheetname[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err;
+
+    STAILQ_FOREACH(chart, workbook->ordered_charts, ordered_list_pointers) {
+
+        lxw_snprintf(sheetname, LXW_FILENAME_LENGTH,
+                     "xl/charts/chart%d.xml", index++);
+
+        chart->file = lxw_tmpfile(self->tmpdir);
+        if (!chart->file)
+            return LXW_ERROR_CREATING_TMPFILE;
+
+        lxw_chart_assemble_xml_file(chart);
+
+        err = _add_file_to_zip(self, chart->file, sheetname);
+        RETURN_ON_ERROR(err);
+
+        self->chart_count++;
+
+        fclose(chart->file);
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the drawing files.
+ */
+STATIC lxw_error
+_write_drawing_files(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    lxw_drawing *drawing;
+    char filename[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err;
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+        drawing = worksheet->drawing;
+
+        if (drawing) {
+            lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                         "xl/drawings/drawing%d.xml", index++);
+
+            drawing->file = lxw_tmpfile(self->tmpdir);
+            if (!drawing->file)
+                return LXW_ERROR_CREATING_TMPFILE;
+
+            lxw_drawing_assemble_xml_file(drawing);
+            err = _add_file_to_zip(self, drawing->file, filename);
+            RETURN_ON_ERROR(err);
+
+            fclose(drawing->file);
+
+            self->drawing_count++;
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the sharedStrings.xml file.
+ */
+STATIC lxw_error
+_write_shared_strings_file(lxw_packager *self)
+{
+    lxw_sst *sst = self->workbook->sst;
+    lxw_error err;
+
+    /* Skip the sharedStrings file if there are no shared strings. */
+    if (!sst->string_count)
+        return LXW_NO_ERROR;
+
+    sst->file = lxw_tmpfile(self->tmpdir);
+    if (!sst->file)
+        return LXW_ERROR_CREATING_TMPFILE;
+
+    lxw_sst_assemble_xml_file(sst);
+
+    err = _add_file_to_zip(self, sst->file, "xl/sharedStrings.xml");
+    RETURN_ON_ERROR(err);
+
+    fclose(sst->file);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the app.xml file.
+ */
+STATIC lxw_error
+_write_app_file(lxw_packager *self)
+{
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    lxw_defined_name *defined_name;
+    lxw_app *app;
+    uint16_t named_range_count = 0;
+    char *autofilter;
+    char *has_range;
+    char number[LXW_ATTR_32] = { 0 };
+    lxw_error err = LXW_NO_ERROR;
+
+    app = lxw_app_new();
+    if (!app) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    app->file = lxw_tmpfile(self->tmpdir);
+    if (!app->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    lxw_snprintf(number, LXW_ATTR_32, "%d", self->workbook->num_sheets);
+
+    lxw_app_add_heading_pair(app, "Worksheets", number);
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+        lxw_app_add_part_name(app, worksheet->name);
+    }
+
+    /* Add the Named Ranges parts. */
+    TAILQ_FOREACH(defined_name, workbook->defined_names, list_pointers) {
+
+        has_range = strchr(defined_name->formula, '!');
+        autofilter = strstr(defined_name->app_name, "_FilterDatabase");
+
+        /* Only store defined names with ranges (except for autofilters). */
+        if (has_range && !autofilter) {
+            lxw_app_add_part_name(app, defined_name->app_name);
+            named_range_count++;
+        }
+    }
+
+    /* Add the Named Range heading pairs. */
+    if (named_range_count) {
+        lxw_snprintf(number, LXW_ATTR_32, "%d", named_range_count);
+        lxw_app_add_heading_pair(app, "Named Ranges", number);
+    }
+
+    /* Set the app/doc properties. */
+    app->properties = workbook->properties;
+
+    lxw_app_assemble_xml_file(app);
+
+    err = _add_file_to_zip(self, app->file, "docProps/app.xml");
+
+    fclose(app->file);
+
+mem_error:
+    lxw_app_free(app);
+
+    return err;
+}
+
+/*
+ * Write the core.xml file.
+ */
+STATIC lxw_error
+_write_core_file(lxw_packager *self)
+{
+    lxw_error err = LXW_NO_ERROR;
+    lxw_core *core = lxw_core_new();
+
+    if (!core) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    core->file = lxw_tmpfile(self->tmpdir);
+    if (!core->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    core->properties = self->workbook->properties;
+
+    lxw_core_assemble_xml_file(core);
+
+    err = _add_file_to_zip(self, core->file, "docProps/core.xml");
+
+    fclose(core->file);
+
+mem_error:
+    lxw_core_free(core);
+
+    return err;
+}
+
+/*
+ * Write the custom.xml file.
+ */
+STATIC lxw_error
+_write_custom_file(lxw_packager *self)
+{
+    lxw_custom *custom;
+    lxw_error err = LXW_NO_ERROR;
+
+    if (STAILQ_EMPTY(self->workbook->custom_properties))
+        return LXW_NO_ERROR;
+
+    custom = lxw_custom_new();
+    if (!custom) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    custom->file = lxw_tmpfile(self->tmpdir);
+    if (!custom->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    custom->custom_properties = self->workbook->custom_properties;
+
+    lxw_custom_assemble_xml_file(custom);
+
+    err = _add_file_to_zip(self, custom->file, "docProps/custom.xml");
+
+    fclose(custom->file);
+
+mem_error:
+    lxw_custom_free(custom);
+    return err;
+}
+
+/*
+ * Write the theme.xml file.
+ */
+STATIC lxw_error
+_write_theme_file(lxw_packager *self)
+{
+    lxw_error err = LXW_NO_ERROR;
+    lxw_theme *theme = lxw_theme_new();
+
+    if (!theme) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    theme->file = lxw_tmpfile(self->tmpdir);
+    if (!theme->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    lxw_theme_assemble_xml_file(theme);
+
+    err = _add_file_to_zip(self, theme->file, "xl/theme/theme1.xml");
+
+    fclose(theme->file);
+
+mem_error:
+    lxw_theme_free(theme);
+
+    return err;
+}
+
+/*
+ * Write the styles.xml file.
+ */
+STATIC lxw_error
+_write_styles_file(lxw_packager *self)
+{
+    lxw_styles *styles = lxw_styles_new();
+    lxw_hash_element *hash_element;
+    lxw_error err = LXW_NO_ERROR;
+
+    if (!styles) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    /* Copy the unique and in-use formats from the workbook to the styles
+     * xf_format list. */
+    LXW_FOREACH_ORDERED(hash_element, self->workbook->used_xf_formats) {
+        lxw_format *workbook_format = (lxw_format *) hash_element->value;
+        lxw_format *style_format = lxw_format_new();
+
+        if (!style_format) {
+            err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+            goto mem_error;
+        }
+
+        memcpy(style_format, workbook_format, sizeof(lxw_format));
+        STAILQ_INSERT_TAIL(styles->xf_formats, style_format, list_pointers);
+    }
+
+    styles->font_count = self->workbook->font_count;
+    styles->border_count = self->workbook->border_count;
+    styles->fill_count = self->workbook->fill_count;
+    styles->num_format_count = self->workbook->num_format_count;
+    styles->xf_count = self->workbook->used_xf_formats->unique_count;
+
+    styles->file = lxw_tmpfile(self->tmpdir);
+    if (!styles->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    lxw_styles_assemble_xml_file(styles);
+
+    err = _add_file_to_zip(self, styles->file, "xl/styles.xml");
+
+    fclose(styles->file);
+
+mem_error:
+    lxw_styles_free(styles);
+
+    return err;
+}
+
+/*
+ * Write the ContentTypes.xml file.
+ */
+STATIC lxw_error
+_write_content_types_file(lxw_packager *self)
+{
+    lxw_content_types *content_types = lxw_content_types_new();
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    char filename[LXW_MAX_ATTRIBUTE_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err = LXW_NO_ERROR;
+
+    if (!content_types) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    content_types->file = lxw_tmpfile(self->tmpdir);
+    if (!content_types->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    if (workbook->has_png)
+        lxw_ct_add_default(content_types, "png", "image/png");
+
+    if (workbook->has_jpeg)
+        lxw_ct_add_default(content_types, "jpeg", "image/jpeg");
+
+    if (workbook->has_bmp)
+        lxw_ct_add_default(content_types, "bmp", "image/bmp");
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+        lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                     "/xl/worksheets/sheet%d.xml", index++);
+        lxw_ct_add_worksheet_name(content_types, filename);
+    }
+
+    for (index = 1; index <= self->chart_count; index++) {
+        lxw_snprintf(filename, LXW_FILENAME_LENGTH, "/xl/charts/chart%d.xml",
+                     index);
+        lxw_ct_add_chart_name(content_types, filename);
+    }
+
+    for (index = 1; index <= self->drawing_count; index++) {
+        lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                     "/xl/drawings/drawing%d.xml", index);
+        lxw_ct_add_drawing_name(content_types, filename);
+    }
+
+    if (workbook->sst->string_count)
+        lxw_ct_add_shared_strings(content_types);
+
+    if (!STAILQ_EMPTY(self->workbook->custom_properties))
+        lxw_ct_add_custom_properties(content_types);
+
+    lxw_content_types_assemble_xml_file(content_types);
+
+    err = _add_file_to_zip(self, content_types->file, "[Content_Types].xml");
+
+    fclose(content_types->file);
+
+mem_error:
+    lxw_content_types_free(content_types);
+
+    return err;
+}
+
+/*
+ * Write the workbook .rels xml file.
+ */
+STATIC lxw_error
+_write_workbook_rels_file(lxw_packager *self)
+{
+    lxw_relationships *rels = lxw_relationships_new();
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    char sheetname[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err = LXW_NO_ERROR;
+
+    if (!rels) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    rels->file = lxw_tmpfile(self->tmpdir);
+    if (!rels->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+        lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "worksheets/sheet%d.xml",
+                     index++);
+        lxw_add_document_relationship(rels, "/worksheet", sheetname);
+    }
+
+    lxw_add_document_relationship(rels, "/theme", "theme/theme1.xml");
+    lxw_add_document_relationship(rels, "/styles", "styles.xml");
+
+    if (workbook->sst->string_count)
+        lxw_add_document_relationship(rels, "/sharedStrings",
+                                      "sharedStrings.xml");
+
+    lxw_relationships_assemble_xml_file(rels);
+
+    err = _add_file_to_zip(self, rels->file, "xl/_rels/workbook.xml.rels");
+
+    fclose(rels->file);
+
+mem_error:
+    lxw_free_relationships(rels);
+
+    return err;
+}
+
+/*
+ * Write the worksheet .rels files for worksheets that contain links to
+ * external data such as hyperlinks or drawings.
+ */
+STATIC lxw_error
+_write_worksheet_rels_file(lxw_packager *self)
+{
+    lxw_relationships *rels;
+    lxw_rel_tuple *rel;
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    char sheetname[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 0;
+    lxw_error err;
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+
+        index++;
+
+        if (STAILQ_EMPTY(worksheet->external_hyperlinks) &&
+            STAILQ_EMPTY(worksheet->external_drawing_links))
+            continue;
+
+        rels = lxw_relationships_new();
+
+        rels->file = lxw_tmpfile(self->tmpdir);
+        if (!rels->file) {
+            lxw_free_relationships(rels);
+            return LXW_ERROR_CREATING_TMPFILE;
+        }
+
+        STAILQ_FOREACH(rel, worksheet->external_hyperlinks, list_pointers) {
+            lxw_add_worksheet_relationship(rels, rel->type, rel->target,
+                                           rel->target_mode);
+        }
+
+        STAILQ_FOREACH(rel, worksheet->external_drawing_links, list_pointers) {
+            lxw_add_worksheet_relationship(rels, rel->type, rel->target,
+                                           rel->target_mode);
+        }
+
+        lxw_snprintf(sheetname, LXW_FILENAME_LENGTH,
+                     "xl/worksheets/_rels/sheet%d.xml.rels", index);
+
+        lxw_relationships_assemble_xml_file(rels);
+
+        err = _add_file_to_zip(self, rels->file, sheetname);
+
+        fclose(rels->file);
+        lxw_free_relationships(rels);
+
+        RETURN_ON_ERROR(err);
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the drawing .rels files for worksheets that contain charts or
+ * drawings.
+ */
+STATIC lxw_error
+_write_drawing_rels_file(lxw_packager *self)
+{
+    lxw_relationships *rels;
+    lxw_rel_tuple *rel;
+    lxw_workbook *workbook = self->workbook;
+    lxw_worksheet *worksheet;
+    char sheetname[LXW_FILENAME_LENGTH] = { 0 };
+    uint16_t index = 1;
+    lxw_error err;
+
+    STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
+
+        if (STAILQ_EMPTY(worksheet->drawing_links))
+            continue;
+
+        rels = lxw_relationships_new();
+
+        rels->file = lxw_tmpfile(self->tmpdir);
+        if (!rels->file) {
+            lxw_free_relationships(rels);
+            return LXW_ERROR_CREATING_TMPFILE;
+        }
+
+        STAILQ_FOREACH(rel, worksheet->drawing_links, list_pointers) {
+            lxw_add_worksheet_relationship(rels, rel->type, rel->target,
+                                           rel->target_mode);
+
+        }
+
+        lxw_snprintf(sheetname, LXW_FILENAME_LENGTH,
+                     "xl/drawings/_rels/drawing%d.xml.rels", index++);
+
+        lxw_relationships_assemble_xml_file(rels);
+
+        err = _add_file_to_zip(self, rels->file, sheetname);
+
+        fclose(rels->file);
+        lxw_free_relationships(rels);
+
+        RETURN_ON_ERROR(err);
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the _rels/.rels xml file.
+ */
+STATIC lxw_error
+_write_root_rels_file(lxw_packager *self)
+{
+    lxw_relationships *rels = lxw_relationships_new();
+    lxw_error err = LXW_NO_ERROR;
+
+    if (!rels) {
+        err = LXW_ERROR_MEMORY_MALLOC_FAILED;
+        goto mem_error;
+    }
+
+    rels->file = lxw_tmpfile(self->tmpdir);
+    if (!rels->file) {
+        err = LXW_ERROR_CREATING_TMPFILE;
+        goto mem_error;
+    }
+
+    lxw_add_document_relationship(rels, "/officeDocument", "xl/workbook.xml");
+
+    lxw_add_package_relationship(rels,
+                                 "/metadata/core-properties",
+                                 "docProps/core.xml");
+
+    lxw_add_document_relationship(rels,
+                                  "/extended-properties", "docProps/app.xml");
+
+    if (!STAILQ_EMPTY(self->workbook->custom_properties))
+        lxw_add_document_relationship(rels,
+                                      "/custom-properties",
+                                      "docProps/custom.xml");
+
+    lxw_relationships_assemble_xml_file(rels);
+
+    err = _add_file_to_zip(self, rels->file, "_rels/.rels");
+
+    fclose(rels->file);
+
+mem_error:
+    lxw_free_relationships(rels);
+
+    return err;
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+STATIC lxw_error
+_add_file_to_zip(lxw_packager *self, FILE * file, const char *filename)
+{
+    int16_t error = ZIP_OK;
+    size_t size_read;
+
+    error = zipOpenNewFileInZip4_64(self->zipfile,
+                                    filename,
+                                    &self->zipfile_info,
+                                    NULL, 0, NULL, 0, NULL,
+                                    Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0,
+                                    -MAX_WBITS, DEF_MEM_LEVEL,
+                                    Z_DEFAULT_STRATEGY, NULL, 0, 0, 0, 0);
+
+    if (error != ZIP_OK) {
+        LXW_ERROR("Error adding member to zipfile");
+        RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD);
+    }
+
+    fflush(file);
+    rewind(file);
+
+    size_read = fread(self->buffer, 1, self->buffer_size, file);
+
+    while (size_read) {
+
+        if (size_read < self->buffer_size) {
+            if (feof(file) == 0) {
+                LXW_ERROR("Error reading member file data");
+                RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD);
+            }
+        }
+
+        error = zipWriteInFileInZip(self->zipfile,
+                                    self->buffer, (unsigned int) size_read);
+
+        if (error < 0) {
+            LXW_ERROR("Error in writing member in the zipfile");
+            RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD);
+        }
+
+        size_read = fread(self->buffer, 1, self->buffer_size, file);
+    }
+
+    if (error < 0) {
+        RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD);
+    }
+    else {
+        error = zipCloseFileInZip(self->zipfile);
+        if (error != ZIP_OK) {
+            LXW_ERROR("Error in closing member in the zipfile");
+            RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD);
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write the xml files that make up the XLXS OPC package.
+ */
+lxw_error
+lxw_create_package(lxw_packager *self)
+{
+    lxw_error error;
+    int8_t zip_error;
+
+    error = _write_worksheet_files(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_workbook_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_chart_files(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_drawing_files(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_shared_strings_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_app_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_core_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_custom_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_theme_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_styles_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_content_types_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_workbook_rels_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_worksheet_rels_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_drawing_rels_file(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_image_files(self);
+    RETURN_ON_ERROR(error);
+
+    error = _write_root_rels_file(self);
+    RETURN_ON_ERROR(error);
+
+    zip_error = zipClose(self->zipfile, NULL);
+    if (zip_error) {
+        RETURN_ON_ZIP_ERROR(zip_error, LXW_ERROR_ZIP_CLOSE);
+    }
+
+    return LXW_NO_ERROR;
+}

+ 245 - 0
library/src/relationships.c

@@ -0,0 +1,245 @@
+/*****************************************************************************
+ * relationships - A library for creating Excel XLSX relationships files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <string.h>
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/relationships.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new relationships object.
+ */
+lxw_relationships *
+lxw_relationships_new()
+{
+    lxw_relationships *rels = calloc(1, sizeof(lxw_relationships));
+    GOTO_LABEL_ON_MEM_ERROR(rels, mem_error);
+
+    rels->relationships = calloc(1, sizeof(struct lxw_rel_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(rels->relationships, mem_error);
+    STAILQ_INIT(rels->relationships);
+
+    return rels;
+
+mem_error:
+    lxw_free_relationships(rels);
+    return NULL;
+}
+
+/*
+ * Free a relationships object.
+ */
+void
+lxw_free_relationships(lxw_relationships *rels)
+{
+    lxw_rel_tuple *relationship;
+
+    if (!rels)
+        return;
+
+    if (rels->relationships) {
+        while (!STAILQ_EMPTY(rels->relationships)) {
+            relationship = STAILQ_FIRST(rels->relationships);
+            STAILQ_REMOVE_HEAD(rels->relationships, list_pointers);
+            free(relationship->type);
+            free(relationship->target);
+            free(relationship->target_mode);
+            free(relationship);
+        }
+
+        free(rels->relationships);
+    }
+
+    free(rels);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_relationships_xml_declaration(lxw_relationships *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <Relationship> element.
+ */
+STATIC void
+_write_relationship(lxw_relationships *self, const char *type,
+                    const char *target, const char *target_mode)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH] = { 0 };
+
+    self->rel_id++;
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_id);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("Id", r_id);
+    LXW_PUSH_ATTRIBUTES_STR("Type", type);
+    LXW_PUSH_ATTRIBUTES_STR("Target", target);
+
+    if (target_mode)
+        LXW_PUSH_ATTRIBUTES_STR("TargetMode", target_mode);
+
+    lxw_xml_empty_tag(self->file, "Relationship", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <Relationships> element.
+ */
+STATIC void
+_write_relationships(lxw_relationships *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_rel_tuple *rel;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", LXW_SCHEMA_PACKAGE);
+
+    lxw_xml_start_tag(self->file, "Relationships", &attributes);
+
+    STAILQ_FOREACH(rel, self->relationships, list_pointers) {
+        _write_relationship(self, rel->type, rel->target, rel->target_mode);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_relationships_assemble_xml_file(lxw_relationships *self)
+{
+    /* Write the XML declaration. */
+    _relationships_xml_declaration(self);
+
+    _write_relationships(self);
+
+    /* Close the relationships tag. */
+    lxw_xml_end_tag(self->file, "Relationships");
+}
+
+/*
+ * Add a generic container relationship to XLSX .rels xml files.
+ */
+STATIC void
+_add_relationship(lxw_relationships *self, const char *schema,
+                  const char *type, const char *target,
+                  const char *target_mode)
+{
+    lxw_rel_tuple *relationship;
+
+    if (!schema || !type || !target)
+        return;
+
+    relationship = calloc(1, sizeof(lxw_rel_tuple));
+    GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+    relationship->type = calloc(1, LXW_MAX_ATTRIBUTE_LENGTH);
+    GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+    /* Add the schema to the relationship type. */
+    lxw_snprintf(relationship->type, LXW_MAX_ATTRIBUTE_LENGTH, "%s%s",
+                 schema, type);
+
+    relationship->target = lxw_strdup(target);
+    GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+    if (target_mode) {
+        relationship->target_mode = lxw_strdup(target_mode);
+        GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
+    }
+
+    STAILQ_INSERT_TAIL(self->relationships, relationship, list_pointers);
+
+    return;
+
+mem_error:
+    if (relationship) {
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Add a document relationship to XLSX .rels xml files.
+ */
+void
+lxw_add_document_relationship(lxw_relationships *self, const char *type,
+                              const char *target)
+{
+    _add_relationship(self, LXW_SCHEMA_DOCUMENT, type, target, NULL);
+}
+
+/*
+ * Add a package relationship to XLSX .rels xml files.
+ */
+void
+lxw_add_package_relationship(lxw_relationships *self, const char *type,
+                             const char *target)
+{
+    _add_relationship(self, LXW_SCHEMA_PACKAGE, type, target, NULL);
+}
+
+/*
+ * Add a MS schema package relationship to XLSX .rels xml files.
+ */
+void
+lxw_add_ms_package_relationship(lxw_relationships *self, const char *type,
+                                const char *target)
+{
+    _add_relationship(self, LXW_SCHEMA_MS, type, target, NULL);
+}
+
+/*
+ * Add a worksheet relationship to sheet .rels xml files.
+ */
+void
+lxw_add_worksheet_relationship(lxw_relationships *self, const char *type,
+                               const char *target, const char *target_mode)
+{
+    _add_relationship(self, LXW_SCHEMA_DOCUMENT, type, target, target_mode);
+}

+ 266 - 0
library/src/shared_strings.c

@@ -0,0 +1,266 @@
+/*****************************************************************************
+ * shared_strings - A library for creating Excel XLSX sst files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/shared_strings.h"
+#include "xlsxwriter/utility.h"
+#include <ctype.h>
+
+/*
+ * Forward declarations.
+ */
+
+STATIC int _element_cmp(struct sst_element *element1,
+                        struct sst_element *element2);
+
+#ifndef __clang_analyzer__
+LXW_RB_GENERATE_ELEMENT(sst_rb_tree, sst_element, sst_tree_pointers,
+                        _element_cmp);
+#endif
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new SST SharedString object.
+ */
+lxw_sst *
+lxw_sst_new()
+{
+    /* Create the new shared string table. */
+    lxw_sst *sst = calloc(1, sizeof(lxw_sst));
+    RETURN_ON_MEM_ERROR(sst, NULL);
+
+    /* Add the sst RB tree. */
+    sst->rb_tree = calloc(1, sizeof(struct sst_rb_tree));
+    GOTO_LABEL_ON_MEM_ERROR(sst->rb_tree, mem_error);
+
+    /* Add a list for tracking the insertion order. */
+    sst->order_list = calloc(1, sizeof(struct sst_order_list));
+    GOTO_LABEL_ON_MEM_ERROR(sst->order_list, mem_error);
+
+    /* Initialize the order list. */
+    STAILQ_INIT(sst->order_list);
+
+    /* Initialize the RB tree. */
+    RB_INIT(sst->rb_tree);
+
+    return sst;
+
+mem_error:
+    lxw_sst_free(sst);
+    return NULL;
+}
+
+/*
+ * Free a SST SharedString table object.
+ */
+void
+lxw_sst_free(lxw_sst *sst)
+{
+    struct sst_element *sst_element;
+    struct sst_element *sst_element_temp;
+
+    if (!sst)
+        return;
+
+    /* Free the sst_elements and their data using the ordered linked list. */
+    if (sst->order_list) {
+        STAILQ_FOREACH_SAFE(sst_element, sst->order_list, sst_order_pointers,
+                            sst_element_temp) {
+
+            if (sst_element && sst_element->string)
+                free(sst_element->string);
+            if (sst_element)
+                free(sst_element);
+        }
+    }
+
+    free(sst->order_list);
+    free(sst->rb_tree);
+    free(sst);
+}
+
+/*
+ * Comparator for the element structure
+ */
+STATIC int
+_element_cmp(struct sst_element *element1, struct sst_element *element2)
+{
+    return strcmp(element1->string, element2->string);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_sst_xml_declaration(lxw_sst *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <t> element.
+ */
+STATIC void
+_write_t(lxw_sst *self, char *string)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Add attribute to preserve leading or trailing whitespace. */
+    if (isspace((unsigned char) string[0])
+        || isspace((unsigned char) string[strlen(string) - 1]))
+        LXW_PUSH_ATTRIBUTES_STR("xml:space", "preserve");
+
+    lxw_xml_data_element(self->file, "t", string, &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <si> element.
+ */
+STATIC void
+_write_si(lxw_sst *self, char *string)
+{
+    uint8_t escaped_string = LXW_FALSE;
+
+    lxw_xml_start_tag(self->file, "si", NULL);
+
+    /* Look for and escape control chars in the string. */
+    if (strpbrk(string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C"
+                "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"
+                "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) {
+        string = lxw_escape_control_characters(string);
+        escaped_string = LXW_TRUE;
+    }
+
+    /* Write the t element. */
+    _write_t(self, string);
+
+    lxw_xml_end_tag(self->file, "si");
+
+    if (escaped_string)
+        free(string);
+}
+
+/*
+ * Write the <sst> element.
+ */
+STATIC void
+_write_sst(lxw_sst *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns[] =
+        "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
+    LXW_PUSH_ATTRIBUTES_INT("count", self->string_count);
+    LXW_PUSH_ATTRIBUTES_INT("uniqueCount", self->unique_count);
+
+    lxw_xml_start_tag(self->file, "sst", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+STATIC void
+_write_sst_strings(lxw_sst *self)
+{
+    struct sst_element *sst_element;
+
+    STAILQ_FOREACH(sst_element, self->order_list, sst_order_pointers) {
+        /* Write the si element. */
+        _write_si(self, sst_element->string);
+    }
+}
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_sst_assemble_xml_file(lxw_sst *self)
+{
+    /* Write the XML declaration. */
+    _sst_xml_declaration(self);
+
+    /* Write the sst element. */
+    _write_sst(self);
+
+    /* Write the sst strings. */
+    _write_sst_strings(self);
+
+    /* Close the sst tag. */
+    lxw_xml_end_tag(self->file, "sst");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+/*
+ * Add to or find a string in the SST SharedString table and return it's index.
+ */
+struct sst_element *
+lxw_get_sst_index(lxw_sst *sst, const char *string)
+{
+    struct sst_element *element;
+    struct sst_element *existing_element;
+
+    /* Create an sst element to potentially add to the table. */
+    element = calloc(1, sizeof(struct sst_element));
+    if (!element)
+        return NULL;
+
+    /* Create potential new element with the string and its index. */
+    element->index = sst->unique_count;
+    element->string = lxw_strdup(string);
+
+    /* Try to insert it and see whether we already have that string. */
+    existing_element = RB_INSERT(sst_rb_tree, sst->rb_tree, element);
+
+    /* If existing_element is not NULL, then it already existed. */
+    /* Free new created element. */
+    if (existing_element) {
+        free(element->string);
+        free(element);
+        sst->string_count++;
+        return existing_element;
+    }
+
+    /* If it didn't exist, also add it to the insertion order linked list. */
+    STAILQ_INSERT_TAIL(sst->order_list, element, sst_order_pointers);
+
+    /* Update SST string counts. */
+    sst->string_count++;
+    sst->unique_count++;
+    return element;
+}

+ 1088 - 0
library/src/styles.c

@@ -0,0 +1,1088 @@
+/*****************************************************************************
+ * styles - A library for creating Excel XLSX styles files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/styles.h"
+#include "xlsxwriter/utility.h"
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new styles object.
+ */
+lxw_styles *
+lxw_styles_new()
+{
+    lxw_styles *styles = calloc(1, sizeof(lxw_styles));
+    GOTO_LABEL_ON_MEM_ERROR(styles, mem_error);
+
+    styles->xf_formats = calloc(1, sizeof(struct lxw_formats));
+    GOTO_LABEL_ON_MEM_ERROR(styles->xf_formats, mem_error);
+
+    STAILQ_INIT(styles->xf_formats);
+
+    return styles;
+
+mem_error:
+    lxw_styles_free(styles);
+    return NULL;
+}
+
+/*
+ * Free a styles object.
+ */
+void
+lxw_styles_free(lxw_styles *styles)
+{
+    lxw_format *format;
+
+    if (!styles)
+        return;
+
+    /* Free the formats in the styles. */
+    if (styles->xf_formats) {
+        while (!STAILQ_EMPTY(styles->xf_formats)) {
+            format = STAILQ_FIRST(styles->xf_formats);
+            STAILQ_REMOVE_HEAD(styles->xf_formats, list_pointers);
+            free(format);
+        }
+        free(styles->xf_formats);
+    }
+
+    free(styles);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_styles_xml_declaration(lxw_styles *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <styleSheet> element.
+ */
+STATIC void
+_write_style_sheet(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns",
+                            "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
+
+    lxw_xml_start_tag(self->file, "styleSheet", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <numFmt> element.
+ */
+STATIC void
+_write_num_fmt(lxw_styles *self, uint16_t num_fmt_id, char *format_code)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("numFmtId", num_fmt_id);
+    LXW_PUSH_ATTRIBUTES_STR("formatCode", format_code);
+
+    lxw_xml_empty_tag(self->file, "numFmt", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <numFmts> element.
+ */
+STATIC void
+_write_num_fmts(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_format *format;
+
+    if (!self->num_format_count)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->num_format_count);
+
+    lxw_xml_start_tag(self->file, "numFmts", &attributes);
+
+    /* Write the numFmts elements. */
+    STAILQ_FOREACH(format, self->xf_formats, list_pointers) {
+
+        /* Ignore built-in number formats, i.e., < 164. */
+        if (format->num_format_index < 164)
+            continue;
+
+        _write_num_fmt(self, format->num_format_index, format->num_format);
+    }
+
+    lxw_xml_end_tag(self->file, "numFmts");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <sz> element.
+ */
+STATIC void
+_write_font_size(lxw_styles *self, double font_size)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("val", font_size);
+
+    lxw_xml_empty_tag(self->file, "sz", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <color> element for themes.
+ */
+STATIC void
+_write_font_color_theme(lxw_styles *self, uint8_t theme)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("theme", theme);
+
+    lxw_xml_empty_tag(self->file, "color", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <color> element for RGB colors.
+ */
+STATIC void
+_write_font_color_rgb(lxw_styles *self, int32_t rgb)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X", rgb & LXW_COLOR_MASK);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
+
+    lxw_xml_empty_tag(self->file, "color", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <name> element.
+ */
+STATIC void
+_write_font_name(lxw_styles *self, const char *font_name)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (*font_name)
+        LXW_PUSH_ATTRIBUTES_STR("val", font_name);
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", LXW_DEFAULT_FONT_NAME);
+
+    lxw_xml_empty_tag(self->file, "name", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <family> element.
+ */
+STATIC void
+_write_font_family(lxw_styles *self, uint8_t font_family)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("val", font_family);
+
+    lxw_xml_empty_tag(self->file, "family", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <scheme> element.
+ */
+STATIC void
+_write_font_scheme(lxw_styles *self, const char *font_scheme)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (*font_scheme)
+        LXW_PUSH_ATTRIBUTES_STR("val", font_scheme);
+    else
+        LXW_PUSH_ATTRIBUTES_STR("val", "minor");
+
+    lxw_xml_empty_tag(self->file, "scheme", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the underline font element.
+ */
+STATIC void
+_write_font_underline(lxw_styles *self, uint8_t underline)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Handle the underline variants. */
+    if (underline == LXW_UNDERLINE_DOUBLE)
+        LXW_PUSH_ATTRIBUTES_STR("val", "double");
+    else if (underline == LXW_UNDERLINE_SINGLE_ACCOUNTING)
+        LXW_PUSH_ATTRIBUTES_STR("val", "singleAccounting");
+    else if (underline == LXW_UNDERLINE_DOUBLE_ACCOUNTING)
+        LXW_PUSH_ATTRIBUTES_STR("val", "doubleAccounting");
+    /* Default to single underline. */
+
+    lxw_xml_empty_tag(self->file, "u", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+
+}
+
+/*
+ * Write the <vertAlign> font sub-element.
+ */
+STATIC void
+_write_vert_align(lxw_styles *self, const char *align)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("val", align);
+
+    lxw_xml_empty_tag(self->file, "vertAlign", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <font> element.
+ */
+STATIC void
+_write_font(lxw_styles *self, lxw_format *format)
+{
+    lxw_xml_start_tag(self->file, "font", NULL);
+
+    if (format->bold)
+        lxw_xml_empty_tag(self->file, "b", NULL);
+
+    if (format->italic)
+        lxw_xml_empty_tag(self->file, "i", NULL);
+
+    if (format->font_strikeout)
+        lxw_xml_empty_tag(self->file, "strike", NULL);
+
+    if (format->font_outline)
+        lxw_xml_empty_tag(self->file, "outline", NULL);
+
+    if (format->font_shadow)
+        lxw_xml_empty_tag(self->file, "shadow", NULL);
+
+    if (format->underline)
+        _write_font_underline(self, format->underline);
+
+    if (format->font_script == LXW_FONT_SUPERSCRIPT)
+        _write_vert_align(self, "superscript");
+
+    if (format->font_script == LXW_FONT_SUBSCRIPT)
+        _write_vert_align(self, "subscript");
+
+    if (format->font_size > 0.0)
+        _write_font_size(self, format->font_size);
+
+    if (format->theme)
+        _write_font_color_theme(self, format->theme);
+    else if (format->font_color != LXW_COLOR_UNSET)
+        _write_font_color_rgb(self, format->font_color);
+    else
+        _write_font_color_theme(self, LXW_DEFAULT_FONT_THEME);
+
+    _write_font_name(self, format->font_name);
+    _write_font_family(self, format->font_family);
+
+    /* Only write the scheme element for the default font type if it
+     * is a hyperlink. */
+    if ((!*format->font_name
+         || strcmp(LXW_DEFAULT_FONT_NAME, format->font_name) == 0)
+        && !format->hyperlink) {
+        _write_font_scheme(self, format->font_scheme);
+    }
+
+    lxw_xml_end_tag(self->file, "font");
+}
+
+/*
+ * Write the <fonts> element.
+ */
+STATIC void
+_write_fonts(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_format *format;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->font_count);
+
+    lxw_xml_start_tag(self->file, "fonts", &attributes);
+
+    STAILQ_FOREACH(format, self->xf_formats, list_pointers) {
+        if (format->has_font)
+            _write_font(self, format);
+    }
+
+    lxw_xml_end_tag(self->file, "fonts");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the default <fill> element.
+ */
+STATIC void
+_write_default_fill(lxw_styles *self, const char *pattern)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("patternType", pattern);
+
+    lxw_xml_start_tag(self->file, "fill", NULL);
+    lxw_xml_empty_tag(self->file, "patternFill", &attributes);
+    lxw_xml_end_tag(self->file, "fill");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <fgColor> element.
+ */
+STATIC void
+_write_fg_color(lxw_styles *self, lxw_color_t color)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    LXW_INIT_ATTRIBUTES();
+
+    lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK);
+    LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
+
+    lxw_xml_empty_tag(self->file, "fgColor", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <bgColor> element.
+ */
+STATIC void
+_write_bg_color(lxw_styles *self, lxw_color_t color)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (color == LXW_COLOR_UNSET) {
+        LXW_PUSH_ATTRIBUTES_STR("indexed", "64");
+    }
+    else {
+        lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK);
+        LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
+    }
+
+    lxw_xml_empty_tag(self->file, "bgColor", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <fill> element.
+ */
+STATIC void
+_write_fill(lxw_styles *self, lxw_format *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    uint8_t pattern = format->pattern;
+    lxw_color_t bg_color = format->bg_color;
+    lxw_color_t fg_color = format->fg_color;
+
+    char *patterns[] = {
+        "none",
+        "solid",
+        "mediumGray",
+        "darkGray",
+        "lightGray",
+        "darkHorizontal",
+        "darkVertical",
+        "darkDown",
+        "darkUp",
+        "darkGrid",
+        "darkTrellis",
+        "lightHorizontal",
+        "lightVertical",
+        "lightDown",
+        "lightUp",
+        "lightGrid",
+        "lightTrellis",
+        "gray125",
+        "gray0625",
+    };
+
+    LXW_INIT_ATTRIBUTES();
+
+    lxw_xml_start_tag(self->file, "fill", NULL);
+
+    if (pattern)
+        LXW_PUSH_ATTRIBUTES_STR("patternType", patterns[pattern]);
+
+    lxw_xml_start_tag(self->file, "patternFill", &attributes);
+
+    if (fg_color != LXW_COLOR_UNSET)
+        _write_fg_color(self, fg_color);
+
+    _write_bg_color(self, bg_color);
+
+    lxw_xml_end_tag(self->file, "patternFill");
+    lxw_xml_end_tag(self->file, "fill");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <fills> element.
+ */
+STATIC void
+_write_fills(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_format *format;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->fill_count);
+
+    lxw_xml_start_tag(self->file, "fills", &attributes);
+
+    /* Write the default fills. */
+    _write_default_fill(self, "none");
+    _write_default_fill(self, "gray125");
+
+    STAILQ_FOREACH(format, self->xf_formats, list_pointers) {
+        if (format->has_fill)
+            _write_fill(self, format);
+    }
+
+    lxw_xml_end_tag(self->file, "fills");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the border <color> element.
+ */
+STATIC void
+_write_border_color(lxw_styles *self, lxw_color_t color)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (color != LXW_COLOR_UNSET) {
+        lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK);
+        LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
+    }
+    else {
+        LXW_PUSH_ATTRIBUTES_STR("auto", "1");
+    }
+
+    lxw_xml_empty_tag(self->file, "color", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <border> sub elements such as <right>, <top>, etc.
+ */
+STATIC void
+_write_sub_border(lxw_styles *self, const char *type, uint8_t style,
+                  lxw_color_t color)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    char *border_styles[] = {
+        "none",
+        "thin",
+        "medium",
+        "dashed",
+        "dotted",
+        "thick",
+        "double",
+        "hair",
+        "mediumDashed",
+        "dashDot",
+        "mediumDashDot",
+        "dashDotDot",
+        "mediumDashDotDot",
+        "slantDashDot",
+    };
+
+    if (!style) {
+        lxw_xml_empty_tag(self->file, type, NULL);
+        return;
+    }
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("style", border_styles[style]);
+
+    lxw_xml_start_tag(self->file, type, &attributes);
+
+    _write_border_color(self, color);
+
+    lxw_xml_end_tag(self->file, type);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <border> element.
+ */
+STATIC void
+_write_border(lxw_styles *self, lxw_format *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Add attributes for diagonal borders. */
+    if (format->diag_type == LXW_DIAGONAL_BORDER_UP) {
+        LXW_PUSH_ATTRIBUTES_STR("diagonalUp", "1");
+    }
+    else if (format->diag_type == LXW_DIAGONAL_BORDER_DOWN) {
+        LXW_PUSH_ATTRIBUTES_STR("diagonalDown", "1");
+    }
+    else if (format->diag_type == LXW_DIAGONAL_BORDER_UP_DOWN) {
+        LXW_PUSH_ATTRIBUTES_STR("diagonalUp", "1");
+        LXW_PUSH_ATTRIBUTES_STR("diagonalDown", "1");
+    }
+
+    /* Ensure that a default diag border is set if the diag type is set. */
+    if (format->diag_type && !format->diag_border) {
+        format->diag_border = 1;
+    }
+
+    /* Write the start border tag. */
+    lxw_xml_start_tag(self->file, "border", &attributes);
+
+    /* Write the <border> sub elements. */
+    _write_sub_border(self, "left", format->left, format->left_color);
+    _write_sub_border(self, "right", format->right, format->right_color);
+    _write_sub_border(self, "top", format->top, format->top_color);
+    _write_sub_border(self, "bottom", format->bottom, format->bottom_color);
+    _write_sub_border(self,
+                      "diagonal", format->diag_border, format->diag_color);
+
+    lxw_xml_end_tag(self->file, "border");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <borders> element.
+ */
+STATIC void
+_write_borders(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_format *format;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->border_count);
+
+    lxw_xml_start_tag(self->file, "borders", &attributes);
+
+    STAILQ_FOREACH(format, self->xf_formats, list_pointers) {
+        if (format->has_border)
+            _write_border(self, format);
+    }
+
+    lxw_xml_end_tag(self->file, "borders");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xf> element for styles.
+ */
+STATIC void
+_write_style_xf(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("numFmtId", "0");
+    LXW_PUSH_ATTRIBUTES_STR("fontId", "0");
+    LXW_PUSH_ATTRIBUTES_STR("fillId", "0");
+    LXW_PUSH_ATTRIBUTES_STR("borderId", "0");
+
+    lxw_xml_empty_tag(self->file, "xf", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <cellStyleXfs> element.
+ */
+STATIC void
+_write_cell_style_xfs(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("count", "1");
+
+    lxw_xml_start_tag(self->file, "cellStyleXfs", &attributes);
+    _write_style_xf(self);
+    lxw_xml_end_tag(self->file, "cellStyleXfs");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Check if a format struct has alignment properties set and the
+ * "applyAlignment" attribute should be set.
+ */
+STATIC uint8_t
+_apply_alignment(lxw_format *format)
+{
+    return format->text_h_align != LXW_ALIGN_NONE
+        || format->text_v_align != LXW_ALIGN_NONE
+        || format->indent != 0
+        || format->rotation != 0
+        || format->text_wrap != 0
+        || format->shrink != 0 || format->reading_order != 0;
+}
+
+/*
+ * Check if a format struct has alignment properties set apart from the
+ * LXW_ALIGN_VERTICAL_BOTTOM which Excel treats as a default.
+ */
+STATIC uint8_t
+_has_alignment(lxw_format *format)
+{
+    return format->text_h_align != LXW_ALIGN_NONE
+        || !(format->text_v_align == LXW_ALIGN_NONE ||
+             format->text_v_align == LXW_ALIGN_VERTICAL_BOTTOM)
+        || format->indent != 0
+        || format->rotation != 0
+        || format->text_wrap != 0
+        || format->shrink != 0 || format->reading_order != 0;
+}
+
+/*
+ * Write the <alignment> element.
+ */
+STATIC void
+_write_alignment(lxw_styles *self, lxw_format *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    int16_t rotation = format->rotation;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Indent is only allowed for horizontal left, right and distributed. */
+    /* If it is defined for any other alignment or no alignment has been  */
+    /* set then default to left alignment. */
+    if (format->indent
+        && format->text_h_align != LXW_ALIGN_LEFT
+        && format->text_h_align != LXW_ALIGN_RIGHT
+        && format->text_h_align != LXW_ALIGN_DISTRIBUTED) {
+        format->text_h_align = LXW_ALIGN_LEFT;
+    }
+
+    /* Check for properties that are mutually exclusive. */
+    if (format->text_wrap)
+        format->shrink = 0;
+
+    if (format->text_h_align == LXW_ALIGN_FILL)
+        format->shrink = 0;
+
+    if (format->text_h_align == LXW_ALIGN_JUSTIFY)
+        format->shrink = 0;
+
+    if (format->text_h_align == LXW_ALIGN_DISTRIBUTED)
+        format->shrink = 0;
+
+    if (format->text_h_align != LXW_ALIGN_DISTRIBUTED)
+        format->just_distrib = 0;
+
+    if (format->indent)
+        format->just_distrib = 0;
+
+    if (format->text_h_align == LXW_ALIGN_LEFT)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "left");
+
+    if (format->text_h_align == LXW_ALIGN_CENTER)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "center");
+
+    if (format->text_h_align == LXW_ALIGN_RIGHT)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "right");
+
+    if (format->text_h_align == LXW_ALIGN_FILL)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "fill");
+
+    if (format->text_h_align == LXW_ALIGN_JUSTIFY)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "justify");
+
+    if (format->text_h_align == LXW_ALIGN_CENTER_ACROSS)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "centerContinuous");
+
+    if (format->text_h_align == LXW_ALIGN_DISTRIBUTED)
+        LXW_PUSH_ATTRIBUTES_STR("horizontal", "distributed");
+
+    if (format->just_distrib)
+        LXW_PUSH_ATTRIBUTES_STR("justifyLastLine", "1");
+
+    if (format->text_v_align == LXW_ALIGN_VERTICAL_TOP)
+        LXW_PUSH_ATTRIBUTES_STR("vertical", "top");
+
+    if (format->text_v_align == LXW_ALIGN_VERTICAL_CENTER)
+        LXW_PUSH_ATTRIBUTES_STR("vertical", "center");
+
+    if (format->text_v_align == LXW_ALIGN_VERTICAL_JUSTIFY)
+        LXW_PUSH_ATTRIBUTES_STR("vertical", "justify");
+
+    if (format->text_v_align == LXW_ALIGN_VERTICAL_DISTRIBUTED)
+        LXW_PUSH_ATTRIBUTES_STR("vertical", "distributed");
+
+    if (format->indent)
+        LXW_PUSH_ATTRIBUTES_INT("indent", format->indent);
+
+    /* Map rotation to Excel values. */
+    if (rotation) {
+        if (rotation == 270)
+            rotation = 255;
+        else if (rotation < 0)
+            rotation = -rotation + 90;
+
+        LXW_PUSH_ATTRIBUTES_INT("textRotation", rotation);
+    }
+
+    if (format->text_wrap)
+        LXW_PUSH_ATTRIBUTES_STR("wrapText", "1");
+
+    if (format->shrink)
+        LXW_PUSH_ATTRIBUTES_STR("shrinkToFit", "1");
+
+    if (format->reading_order == 1)
+        LXW_PUSH_ATTRIBUTES_STR("readingOrder", "1");
+
+    if (format->reading_order == 2)
+        LXW_PUSH_ATTRIBUTES_STR("readingOrder", "2");
+
+    if (!STAILQ_EMPTY(&attributes))
+        lxw_xml_empty_tag(self->file, "alignment", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <protection> element.
+ */
+STATIC void
+_write_protection(lxw_styles *self, lxw_format *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (!format->locked)
+        LXW_PUSH_ATTRIBUTES_STR("locked", "0");
+
+    if (format->hidden)
+        LXW_PUSH_ATTRIBUTES_STR("hidden", "1");
+
+    lxw_xml_empty_tag(self->file, "protection", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <xf> element.
+ */
+STATIC void
+_write_xf(lxw_styles *self, lxw_format *format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint8_t has_protection = (!format->locked) | format->hidden;
+    uint8_t has_alignment = _has_alignment(format);
+    uint8_t apply_alignment = _apply_alignment(format);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("numFmtId", format->num_format_index);
+    LXW_PUSH_ATTRIBUTES_INT("fontId", format->font_index);
+    LXW_PUSH_ATTRIBUTES_INT("fillId", format->fill_index);
+    LXW_PUSH_ATTRIBUTES_INT("borderId", format->border_index);
+    LXW_PUSH_ATTRIBUTES_STR("xfId", "0");
+
+    if (format->num_format_index > 0)
+        LXW_PUSH_ATTRIBUTES_STR("applyNumberFormat", "1");
+
+    /* Add applyFont attribute if XF format uses a font element. */
+    if (format->font_index > 0)
+        LXW_PUSH_ATTRIBUTES_STR("applyFont", "1");
+
+    /* Add applyFill attribute if XF format uses a fill element. */
+    if (format->fill_index > 0)
+        LXW_PUSH_ATTRIBUTES_STR("applyFill", "1");
+
+    /* Add applyBorder attribute if XF format uses a border element. */
+    if (format->border_index > 0)
+        LXW_PUSH_ATTRIBUTES_STR("applyBorder", "1");
+
+    /* We can also have applyAlignment without a sub-element. */
+    if (apply_alignment)
+        LXW_PUSH_ATTRIBUTES_STR("applyAlignment", "1");
+
+    if (has_protection)
+        LXW_PUSH_ATTRIBUTES_STR("applyProtection", "1");
+
+    /* Write XF with sub-elements if required. */
+    if (has_alignment || has_protection) {
+        lxw_xml_start_tag(self->file, "xf", &attributes);
+
+        if (has_alignment)
+            _write_alignment(self, format);
+
+        if (has_protection)
+            _write_protection(self, format);
+
+        lxw_xml_end_tag(self->file, "xf");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "xf", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <cellXfs> element.
+ */
+STATIC void
+_write_cell_xfs(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_format *format;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->xf_count);
+
+    lxw_xml_start_tag(self->file, "cellXfs", &attributes);
+
+    STAILQ_FOREACH(format, self->xf_formats, list_pointers) {
+        _write_xf(self, format);
+    }
+
+    lxw_xml_end_tag(self->file, "cellXfs");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <cellStyle> element.
+ */
+STATIC void
+_write_cell_style(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("name", "Normal");
+    LXW_PUSH_ATTRIBUTES_STR("xfId", "0");
+    LXW_PUSH_ATTRIBUTES_STR("builtinId", "0");
+
+    lxw_xml_empty_tag(self->file, "cellStyle", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <cellStyles> element.
+ */
+STATIC void
+_write_cell_styles(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("count", "1");
+
+    lxw_xml_start_tag(self->file, "cellStyles", &attributes);
+    _write_cell_style(self);
+    lxw_xml_end_tag(self->file, "cellStyles");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dxfs> element.
+ */
+STATIC void
+_write_dxfs(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("count", "0");
+
+    lxw_xml_empty_tag(self->file, "dxfs", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <tableStyles> element.
+ */
+STATIC void
+_write_table_styles(lxw_styles *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("count", "0");
+    LXW_PUSH_ATTRIBUTES_STR("defaultTableStyle", "TableStyleMedium9");
+    LXW_PUSH_ATTRIBUTES_STR("defaultPivotStyle", "PivotStyleLight16");
+
+    lxw_xml_empty_tag(self->file, "tableStyles", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_styles_assemble_xml_file(lxw_styles *self)
+{
+    /* Write the XML declaration. */
+    _styles_xml_declaration(self);
+
+    /* Add the style sheet. */
+    _write_style_sheet(self);
+
+    /* Write the number formats. */
+    _write_num_fmts(self);
+
+    /* Write the fonts. */
+    _write_fonts(self);
+
+    /* Write the fills. */
+    _write_fills(self);
+
+    /* Write the borders element. */
+    _write_borders(self);
+
+    /* Write the cellStyleXfs element. */
+    _write_cell_style_xfs(self);
+
+    /* Write the cellXfs element. */
+    _write_cell_xfs(self);
+
+    /* Write the cellStyles element. */
+    _write_cell_styles(self);
+
+    /* Write the dxfs element. */
+    _write_dxfs(self);
+
+    /* Write the tableStyles element. */
+    _write_table_styles(self);
+
+    /* Write the colors element. */
+    /* _write_colors(self); */
+
+    /* Close the style sheet tag. */
+    lxw_xml_end_tag(self->file, "styleSheet");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/

+ 348 - 0
library/src/theme.c

@@ -0,0 +1,348 @@
+/*****************************************************************************
+ * theme - A library for creating Excel XLSX theme files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <string.h>
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/theme.h"
+#include "xlsxwriter/utility.h"
+
+const char *theme_strs[] = {
+    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n",
+    "<a:theme xmlns:a=\"http://schemas.openxmlformats.org/",
+    "drawingml/2006/main\" name=\"Office Theme\">",
+    "<a:themeElements>",
+    "<a:clrScheme name=\"Office\"><a:dk1>",
+    "<a:sysClr val=\"windowText\" lastClr=\"000000\"/>",
+    "</a:dk1><a:lt1>",
+    "<a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1><a:dk2>",
+    "<a:srgbClr val=\"1F497D\"/></a:dk2><a:lt2>",
+    "<a:srgbClr val=\"EEECE1\"/></a:lt2><a:accent1>",
+    "<a:srgbClr val=\"4F81BD\"/></a:accent1><a:accent2>",
+    "<a:srgbClr val=\"C0504D\"/></a:accent2><a:accent3>",
+    "<a:srgbClr val=\"9BBB59\"/></a:accent3><a:accent4>",
+    "<a:srgbClr val=\"8064A2\"/></a:accent4><a:accent5>",
+    "<a:srgbClr val=\"4BACC6\"/></a:accent5><a:accent6>",
+    "<a:srgbClr val=\"F79646\"/></a:accent6><a:hlink>",
+    "<a:srgbClr val=\"0000FF\"/></a:hlink><a:folHlink>",
+    "<a:srgbClr val=\"800080\"/></a:folHlink></a:clrScheme>",
+    "<a:fontScheme name=\"Office\"><a:majorFont>",
+    "<a:latin typeface=\"Cambria\"/><a:ea typeface=\"\"/>",
+    "<a:cs typeface=\"\"/>",
+    "<a:font script=\"Jpan\" typeface=\"MS Pゴシック\"/>",
+    "<a:font script=\"Hang\" typeface=\"맑은 고딕\"/>",
+    "<a:font script=\"Hans\" typeface=\"宋体\"/>",
+    "<a:font script=\"Hant\" typeface=\"新細明體\"/>",
+    "<a:font script=\"Arab\" typeface=\"Times New Roman\"/>",
+    "<a:font script=\"Hebr\" typeface=\"Times New Roman\"/>",
+    "<a:font script=\"Thai\" typeface=\"Tahoma\"/>",
+    "<a:font script=\"Ethi\" typeface=\"Nyala\"/>",
+    "<a:font script=\"Beng\" typeface=\"Vrinda\"/>",
+    "<a:font script=\"Gujr\" typeface=\"Shruti\"/>",
+    "<a:font script=\"Khmr\" typeface=\"MoolBoran\"/>",
+    "<a:font script=\"Knda\" typeface=\"Tunga\"/>",
+    "<a:font script=\"Guru\" typeface=\"Raavi\"/>",
+    "<a:font script=\"Cans\" typeface=\"Euphemia\"/>",
+    "<a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/>",
+    "<a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/>",
+    "<a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/>",
+    "<a:font script=\"Thaa\" typeface=\"MV Boli\"/>",
+    "<a:font script=\"Deva\" typeface=\"Mangal\"/>",
+    "<a:font script=\"Telu\" typeface=\"Gautami\"/>",
+    "<a:font script=\"Taml\" typeface=\"Latha\"/>",
+    "<a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/>",
+    "<a:font script=\"Orya\" typeface=\"Kalinga\"/>",
+    "<a:font script=\"Mlym\" typeface=\"Kartika\"/>",
+    "<a:font script=\"Laoo\" typeface=\"DokChampa\"/>",
+    "<a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/>",
+    "<a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/>",
+    "<a:font script=\"Viet\" typeface=\"Times New Roman\"/>",
+    "<a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/>",
+    "</a:majorFont>",
+    "<a:minorFont>",
+    "<a:latin typeface=\"Calibri\"/>",
+    "<a:ea typeface=\"\"/>",
+    "<a:cs typeface=\"\"/>",
+    "<a:font script=\"Jpan\" typeface=\"MS Pゴシック\"/>",
+    "<a:font script=\"Hang\" typeface=\"맑은 고딕\"/>",
+    "<a:font script=\"Hans\" typeface=\"宋体\"/>",
+    "<a:font script=\"Hant\" typeface=\"新細明體\"/>",
+    "<a:font script=\"Arab\" typeface=\"Arial\"/>",
+    "<a:font script=\"Hebr\" typeface=\"Arial\"/>",
+    "<a:font script=\"Thai\" typeface=\"Tahoma\"/>",
+    "<a:font script=\"Ethi\" typeface=\"Nyala\"/>",
+    "<a:font script=\"Beng\" typeface=\"Vrinda\"/>",
+    "<a:font script=\"Gujr\" typeface=\"Shruti\"/>",
+    "<a:font script=\"Khmr\" typeface=\"DaunPenh\"/>",
+    "<a:font script=\"Knda\" typeface=\"Tunga\"/>",
+    "<a:font script=\"Guru\" typeface=\"Raavi\"/>",
+    "<a:font script=\"Cans\" typeface=\"Euphemia\"/>",
+    "<a:font script=\"Cher\" typeface=\"Plantagenet Cherokee\"/>",
+    "<a:font script=\"Yiii\" typeface=\"Microsoft Yi Baiti\"/>",
+    "<a:font script=\"Tibt\" typeface=\"Microsoft Himalaya\"/>",
+    "<a:font script=\"Thaa\" typeface=\"MV Boli\"/>",
+    "<a:font script=\"Deva\" typeface=\"Mangal\"/>",
+    "<a:font script=\"Telu\" typeface=\"Gautami\"/>",
+    "<a:font script=\"Taml\" typeface=\"Latha\"/>",
+    "<a:font script=\"Syrc\" typeface=\"Estrangelo Edessa\"/>",
+    "<a:font script=\"Orya\" typeface=\"Kalinga\"/>",
+    "<a:font script=\"Mlym\" typeface=\"Kartika\"/>",
+    "<a:font script=\"Laoo\" typeface=\"DokChampa\"/>",
+    "<a:font script=\"Sinh\" typeface=\"Iskoola Pota\"/>",
+    "<a:font script=\"Mong\" typeface=\"Mongolian Baiti\"/>",
+    "<a:font script=\"Viet\" typeface=\"Arial\"/>",
+    "<a:font script=\"Uigh\" typeface=\"Microsoft Uighur\"/>",
+    "</a:minorFont>",
+    "</a:fontScheme><a:fmtScheme name=\"Office\">",
+    "<a:fillStyleLst>",
+    "<a:solidFill>",
+    "<a:schemeClr val=\"phClr\"/>",
+    "</a:solidFill>",
+    "<a:gradFill rotWithShape=\"1\">",
+    "<a:gsLst>",
+    "<a:gs pos=\"0\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"50000\"/>",
+    "<a:satMod val=\"300000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"35000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"37000\"/>",
+    "<a:satMod val=\"300000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"100000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"15000\"/>",
+    "<a:satMod val=\"350000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "</a:gsLst>",
+    "<a:lin ang=\"16200000\" scaled=\"1\"/>",
+    "</a:gradFill>",
+    "<a:gradFill rotWithShape=\"1\">",
+    "<a:gsLst>",
+    "<a:gs pos=\"0\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"51000\"/>",
+    "<a:satMod val=\"130000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"80000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"93000\"/>",
+    "<a:satMod val=\"130000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"100000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"94000\"/>",
+    "<a:satMod val=\"135000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "</a:gsLst>",
+    "<a:lin ang=\"16200000\" scaled=\"0\"/>",
+    "</a:gradFill>",
+    "</a:fillStyleLst>",
+    "<a:lnStyleLst>",
+    "<a:ln w=\"9525\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">",
+    "<a:solidFill>",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"95000\"/>",
+    "<a:satMod val=\"105000\"/>",
+    "</a:schemeClr>",
+    "</a:solidFill>",
+    "<a:prstDash val=\"solid\"/>",
+    "</a:ln>",
+    "<a:ln w=\"25400\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">",
+    "<a:solidFill>",
+    "<a:schemeClr val=\"phClr\"/>",
+    "</a:solidFill>",
+    "<a:prstDash val=\"solid\"/>",
+    "</a:ln>",
+    "<a:ln w=\"38100\" cap=\"flat\" cmpd=\"sng\" algn=\"ctr\">",
+    "<a:solidFill>",
+    "<a:schemeClr val=\"phClr\"/>",
+    "</a:solidFill>",
+    "<a:prstDash val=\"solid\"/>",
+    "</a:ln>",
+    "</a:lnStyleLst>",
+    "<a:effectStyleLst>",
+    "<a:effectStyle>",
+    "<a:effectLst>",
+    "<a:outerShdw blurRad=\"40000\" dist=\"20000\" ",
+    "dir=\"5400000\" rotWithShape=\"0\">",
+    "<a:srgbClr val=\"000000\">",
+    "<a:alpha val=\"38000\"/>",
+    "</a:srgbClr>",
+    "</a:outerShdw>",
+    "</a:effectLst>",
+    "</a:effectStyle>",
+    "<a:effectStyle>",
+    "<a:effectLst>",
+    "<a:outerShdw blurRad=\"40000\" dist=\"23000\" ",
+    "dir=\"5400000\" rotWithShape=\"0\">",
+    "<a:srgbClr val=\"000000\">",
+    "<a:alpha val=\"35000\"/>",
+    "</a:srgbClr>",
+    "</a:outerShdw>",
+    "</a:effectLst>",
+    "</a:effectStyle>",
+    "<a:effectStyle>",
+    "<a:effectLst>",
+    "<a:outerShdw blurRad=\"40000\" dist=\"23000\" ",
+    "dir=\"5400000\" rotWithShape=\"0\">",
+    "<a:srgbClr val=\"000000\">",
+    "<a:alpha val=\"35000\"/>",
+    "</a:srgbClr>",
+    "</a:outerShdw>",
+    "</a:effectLst>",
+    "<a:scene3d>",
+    "<a:camera prst=\"orthographicFront\">",
+    "<a:rot lat=\"0\" lon=\"0\" rev=\"0\"/>",
+    "</a:camera>",
+    "<a:lightRig rig=\"threePt\" dir=\"t\">",
+    "<a:rot lat=\"0\" lon=\"0\" rev=\"1200000\"/>",
+    "</a:lightRig>",
+    "</a:scene3d>",
+    "<a:sp3d>",
+    "<a:bevelT w=\"63500\" h=\"25400\"/>",
+    "</a:sp3d>",
+    "</a:effectStyle>",
+    "</a:effectStyleLst>",
+    "<a:bgFillStyleLst>",
+    "<a:solidFill>",
+    "<a:schemeClr val=\"phClr\"/>",
+    "</a:solidFill>",
+    "<a:gradFill rotWithShape=\"1\">",
+    "<a:gsLst>",
+    "<a:gs pos=\"0\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"40000\"/>",
+    "<a:satMod val=\"350000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"40000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"45000\"/>",
+    "<a:shade val=\"99000\"/>",
+    "<a:satMod val=\"350000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"100000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"20000\"/>",
+    "<a:satMod val=\"255000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "</a:gsLst>",
+    "<a:path path=\"circle\">",
+    "<a:fillToRect l=\"50000\" t=\"-80000\" r=\"50000\" b=\"180000\"/>",
+    "</a:path>",
+    "</a:gradFill>",
+    "<a:gradFill rotWithShape=\"1\">",
+    "<a:gsLst>",
+    "<a:gs pos=\"0\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:tint val=\"80000\"/>",
+    "<a:satMod val=\"300000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "<a:gs pos=\"100000\">",
+    "<a:schemeClr val=\"phClr\">",
+    "<a:shade val=\"30000\"/>",
+    "<a:satMod val=\"200000\"/>",
+    "</a:schemeClr>",
+    "</a:gs>",
+    "</a:gsLst>",
+    "<a:path path=\"circle\">",
+    "<a:fillToRect l=\"50000\" t=\"50000\" r=\"50000\" b=\"50000\"/>",
+    "</a:path>",
+    "</a:gradFill>",
+    "</a:bgFillStyleLst>",
+    "</a:fmtScheme>",
+    "</a:themeElements>",
+    "<a:objectDefaults/>",
+    "<a:extraClrSchemeLst/>",
+    "</a:theme>\n",
+    ""
+};
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new theme object.
+ */
+lxw_theme *
+lxw_theme_new()
+{
+    lxw_theme *theme = calloc(1, sizeof(lxw_theme));
+    GOTO_LABEL_ON_MEM_ERROR(theme, mem_error);
+
+    return theme;
+
+mem_error:
+    lxw_theme_free(theme);
+    return NULL;
+}
+
+/*
+ * Free a theme object.
+ */
+void
+lxw_theme_free(lxw_theme *theme)
+{
+    if (!theme)
+        return;
+
+    free(theme);
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/* This library isn't a xmlwriter. */
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_theme_assemble_xml_file(lxw_theme *self)
+{
+    int i = 0;
+
+    while (strlen(theme_strs[i])) {
+        fprintf(self->file, "%s", theme_strs[i]);
+        i++;
+    }
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/

+ 555 - 0
library/src/utility.c

@@ -0,0 +1,555 @@
+/*****************************************************************************
+ * utility - Utility functions for libxlsxwriter.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "xlsxwriter/utility.h"
+#include "xlsxwriter/third_party/tmpfileplus.h"
+
+char *error_strings[LXW_MAX_ERRNO + 1] = {
+    "No error.",
+    "Memory error, failed to malloc() required memory.",
+    "Error creating output xlsx file. Usually a permissions error.",
+    "Error encountered when creating a tmpfile during file assembly.",
+    "Zlib error with a file operation while creating xlsx file.",
+    "Zlib error when adding sub file to xlsx file.",
+    "Zlib error when closing xlsx file.",
+    "NULL function parameter ignored.",
+    "Function parameter validation error.",
+    "Worksheet name exceeds Excel's limit of 31 characters.",
+    "Worksheet name contains invalid Excel character: '[]:*?/\\'",
+    "Worksheet name is already in use.",
+    "Parameter exceeds Excel's limit of 32 characters.",
+    "Parameter exceeds Excel's limit of 128 characters.",
+    "Parameter exceeds Excel's limit of 255 characters.",
+    "String exceeds Excel's limit of 32,767 characters.",
+    "Error finding internal string index.",
+    "Worksheet row or column index out of range.",
+    "Maximum number of worksheet URLs (65530) exceeded.",
+    "Couldn't read image dimensions or DPI.",
+    "Unknown error number."
+};
+
+char *
+lxw_strerror(lxw_error error_num)
+{
+    if (error_num > LXW_MAX_ERRNO)
+        error_num = LXW_MAX_ERRNO;
+
+    return error_strings[error_num];
+}
+
+/*
+ * Convert Excel A-XFD style column name to zero based number.
+ */
+void
+lxw_col_to_name(char *col_name, lxw_col_t col_num, uint8_t absolute)
+{
+    uint8_t pos = 0;
+    size_t len;
+    uint8_t i;
+
+    /* Change from 0 index to 1 index. */
+    col_num++;
+
+    /* Convert the column number to a string in reverse order. */
+    while (col_num) {
+
+        /* Get the remainder in base 26. */
+        int remainder = col_num % 26;
+
+        if (remainder == 0)
+            remainder = 26;
+
+        /* Convert the remainder value to a character. */
+        col_name[pos++] = 'A' + remainder - 1;
+        col_name[pos] = '\0';
+
+        /* Get the next order of magnitude. */
+        col_num = (col_num - 1) / 26;
+    }
+
+    if (absolute) {
+        col_name[pos] = '$';
+        col_name[pos + 1] = '\0';
+    }
+
+    /* Reverse the column name string. */
+    len = strlen(col_name);
+    for (i = 0; i < (len / 2); i++) {
+        char tmp = col_name[i];
+        col_name[i] = col_name[len - i - 1];
+        col_name[len - i - 1] = tmp;
+    }
+}
+
+/*
+ * Convert zero indexed row and column to an Excel style A1 cell reference.
+ */
+void
+lxw_rowcol_to_cell(char *cell_name, lxw_row_t row, lxw_col_t col)
+{
+    size_t pos;
+
+    /* Add the column to the cell. */
+    lxw_col_to_name(cell_name, col, 0);
+
+    /* Get the end of the cell. */
+    pos = strlen(cell_name);
+
+    /* Add the row to the cell. */
+    lxw_snprintf(&cell_name[pos], LXW_MAX_ROW_NAME_LENGTH, "%d", ++row);
+}
+
+/*
+ * Convert zero indexed row and column to an Excel style $A$1 cell with
+ * an absolute reference.
+ */
+void
+lxw_rowcol_to_cell_abs(char *cell_name, lxw_row_t row, lxw_col_t col,
+                       uint8_t abs_row, uint8_t abs_col)
+{
+    size_t pos;
+
+    /* Add the column to the cell. */
+    lxw_col_to_name(cell_name, col, abs_col);
+
+    /* Get the end of the cell. */
+    pos = strlen(cell_name);
+
+    if (abs_row)
+        cell_name[pos++] = '$';
+
+    /* Add the row to the cell. */
+    lxw_snprintf(&cell_name[pos], LXW_MAX_ROW_NAME_LENGTH, "%d", ++row);
+}
+
+/*
+ * Convert zero indexed row and column pair to an Excel style A1:C5
+ * range reference.
+ */
+void
+lxw_rowcol_to_range(char *range,
+                    lxw_row_t first_row, lxw_col_t first_col,
+                    lxw_row_t last_row, lxw_col_t last_col)
+{
+    size_t pos;
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell(range, first_row, first_col);
+
+    /* If the start and end cells are the same just return a single cell. */
+    if (first_row == last_row && first_col == last_col)
+        return;
+
+    /* Get the end of the cell. */
+    pos = strlen(range);
+
+    /* Add the range separator. */
+    range[pos++] = ':';
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell(&range[pos], last_row, last_col);
+}
+
+/*
+ * Convert zero indexed row and column pairs to an Excel style $A$1:$C$5
+ * range reference with absolute values.
+ */
+void
+lxw_rowcol_to_range_abs(char *range,
+                        lxw_row_t first_row, lxw_col_t first_col,
+                        lxw_row_t last_row, lxw_col_t last_col)
+{
+    size_t pos;
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell_abs(range, first_row, first_col, 1, 1);
+
+    /* If the start and end cells are the same just return a single cell. */
+    if (first_row == last_row && first_col == last_col)
+        return;
+
+    /* Get the end of the cell. */
+    pos = strlen(range);
+
+    /* Add the range separator. */
+    range[pos++] = ':';
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell_abs(&range[pos], last_row, last_col, 1, 1);
+}
+
+/*
+ * Convert sheetname and zero indexed row and column pairs to an Excel style
+ * Sheet1!$A$1:$C$5 formula reference with absolute values.
+ */
+void
+lxw_rowcol_to_formula_abs(char *formula, const char *sheetname,
+                          lxw_row_t first_row, lxw_col_t first_col,
+                          lxw_row_t last_row, lxw_col_t last_col)
+{
+    size_t pos;
+    char *quoted_name = lxw_quote_sheetname(sheetname);
+
+    strncpy(formula, quoted_name, LXW_MAX_FORMULA_RANGE_LENGTH - 1);
+    free(quoted_name);
+
+    /* Get the end of the sheetname. */
+    pos = strlen(formula);
+
+    /* Add the range separator. */
+    formula[pos++] = '!';
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell_abs(&formula[pos], first_row, first_col, 1, 1);
+
+    /* If the start and end cells are the same just return a single cell. */
+    if (first_row == last_row && first_col == last_col)
+        return;
+
+    /* Get the end of the cell. */
+    pos = strlen(formula);
+
+    /* Add the range separator. */
+    formula[pos++] = ':';
+
+    /* Add the first cell to the range. */
+    lxw_rowcol_to_cell_abs(&formula[pos], last_row, last_col, 1, 1);
+}
+
+/*
+ * Convert an Excel style A1 cell reference to a zero indexed row number.
+ */
+lxw_row_t
+lxw_name_to_row(const char *row_str)
+{
+    lxw_row_t row_num = 0;
+    const char *p = row_str;
+
+    /* Skip the column letters and absolute symbol of the A1 cell. */
+    while (p && !isdigit((unsigned char) *p))
+        p++;
+
+    /* Convert the row part of the A1 cell to a number. */
+    if (p)
+        row_num = atoi(p);
+
+    if (row_num)
+        return row_num - 1;
+    else
+        return 0;
+}
+
+/*
+ * Convert an Excel style A1 cell reference to a zero indexed column number.
+ */
+lxw_col_t
+lxw_name_to_col(const char *col_str)
+{
+    lxw_col_t col_num = 0;
+    const char *p = col_str;
+
+    /* Convert leading column letters of A1 cell. Ignore absolute $ marker. */
+    while (p && (isupper((unsigned char) *p) || *p == '$')) {
+        if (*p != '$')
+            col_num = (col_num * 26) + (*p - 'A' + 1);
+        p++;
+    }
+
+    return col_num - 1;
+}
+
+/*
+ * Convert the second row of an Excel range ref to a zero indexed number.
+ */
+uint32_t
+lxw_name_to_row_2(const char *row_str)
+{
+    const char *p = row_str;
+
+    /* Find the : separator in the range. */
+    while (p && *p != ':')
+        p++;
+
+    if (p)
+        return lxw_name_to_row(++p);
+    else
+        return -1;
+}
+
+/*
+ * Convert the second column of an Excel range ref to a zero indexed number.
+ */
+uint16_t
+lxw_name_to_col_2(const char *col_str)
+{
+    const char *p = col_str;
+
+    /* Find the : separator in the range. */
+    while (p && *p != ':')
+        p++;
+
+    if (p)
+        return lxw_name_to_col(++p);
+    else
+        return -1;
+}
+
+/*
+ * Convert a lxw_datetime struct to an Excel serial date.
+ */
+double
+lxw_datetime_to_excel_date(lxw_datetime *datetime, uint8_t date_1904)
+{
+    int year = datetime->year;
+    int month = datetime->month;
+    int day = datetime->day;
+    int hour = datetime->hour;
+    int min = datetime->min;
+    double sec = datetime->sec;
+    double seconds;
+    int epoch = date_1904 ? 1904 : 1900;
+    int offset = date_1904 ? 4 : 0;
+    int norm = 300;
+    int range;
+    /* Set month days and check for leap year. */
+    int mdays[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+    int leap = 0;
+    int days = 0;
+    int i;
+
+    /* For times without dates set the default date for the epoch. */
+    if (!year) {
+        if (!date_1904) {
+            year = 1899;
+            month = 12;
+            day = 31;
+        }
+        else {
+            year = 1904;
+            month = 1;
+            day = 1;
+        }
+    }
+
+    /* Convert the Excel seconds to a fraction of the seconds in 24 hours. */
+    seconds = (hour * 60 * 60 + min * 60 + sec) / (24 * 60 * 60.0);
+
+    /* Special cases for Excel dates in the 1900 epoch. */
+    if (!date_1904) {
+        /* Excel 1900 epoch. */
+        if (year == 1899 && month == 12 && day == 31)
+            return seconds;
+
+        /* Excel 1900 epoch. */
+        if (year == 1900 && month == 1 && day == 0)
+            return seconds;
+
+        /* Excel false leapday */
+        if (year == 1900 && month == 2 && day == 29)
+            return 60 + seconds;
+    }
+
+    /* We calculate the date by calculating the number of days since the */
+    /* epoch and adjust for the number of leap days. We calculate the */
+    /* number of leap days by normalizing the year in relation to the */
+    /* epoch. Thus the year 2000 becomes 100 for 4-year and 100-year */
+    /* leapdays and 400 for 400-year leapdays. */
+    range = year - epoch;
+
+    if (year % 4 == 0 && (year % 100 > 0 || year % 400 == 0)) {
+        leap = 1;
+        mdays[2] = 29;
+    }
+
+    /*
+     * Calculate the serial date by accumulating the number of days
+     * since the epoch.
+     */
+
+    /* Add days for previous months. */
+    for (i = 0; i < month; i++) {
+        days += mdays[i];
+    }
+    /* Add days for current month. */
+    days += day;
+    /* Add days for all previous years.  */
+    days += range * 365;
+    /* Add 4 year leapdays. */
+    days += (range) / 4;
+    /* Remove 100 year leapdays. */
+    days -= (range + offset) / 100;
+    /* Add 400 year leapdays. */
+    days += (range + offset + norm) / 400;
+    /* Remove leap days already counted. */
+    days -= leap;
+
+    /* Adjust for Excel erroneously treating 1900 as a leap year. */
+    if (!date_1904 && days > 59)
+        days++;
+
+    return days + seconds;
+}
+
+/* Simple strdup() implementation since it isn't ANSI C. */
+char *
+lxw_strdup(const char *str)
+{
+    size_t len;
+    char *copy;
+
+    if (!str)
+        return NULL;
+
+    len = strlen(str) + 1;
+    copy = malloc(len);
+
+    if (copy)
+        memcpy(copy, str, len);
+
+    return copy;
+}
+
+/* Simple function to strdup() a formula string without the leading "=". */
+char *
+lxw_strdup_formula(const char *formula)
+{
+    if (!formula)
+        return NULL;
+
+    if (formula[0] == '=')
+        return lxw_strdup(formula + 1);
+    else
+        return lxw_strdup(formula);
+}
+
+/* Simple strlen that counts UTF-8 characters. Assumes well formed UTF-8. */
+size_t
+lxw_utf8_strlen(const char *str)
+{
+    size_t byte_count = 0;
+    size_t char_count = 0;
+
+    while (str[byte_count]) {
+        if ((str[byte_count] & 0xc0) != 0x80)
+            char_count++;
+
+        byte_count++;
+    }
+
+    return char_count;
+}
+
+/* Simple tolower() for strings. */
+void
+lxw_str_tolower(char *str)
+{
+    int i;
+
+    for (i = 0; str[i]; i++)
+        str[i] = tolower(str[i]);
+}
+
+/* Create a quoted version of the worksheet name, or return an unmodified
+ * copy if it doesn't required quoting. */
+char *
+lxw_quote_sheetname(const char *str)
+{
+
+    uint8_t needs_quoting = 0;
+    size_t number_of_quotes = 2;
+    size_t i, j;
+    size_t len = strlen(str);
+
+    /* Don't quote the sheetname if it is already quoted. */
+    if (str[0] == '\'')
+        return lxw_strdup(str);
+
+    /* Check if the sheetname contains any characters that require it
+     * to be quoted. Also check for single quotes within the string. */
+    for (i = 0; i < len; i++) {
+        if (!isalnum((unsigned char) str[i]) && str[i] != '_'
+            && str[i] != '.')
+            needs_quoting = 1;
+
+        if (str[i] == '\'') {
+            needs_quoting = 1;
+            number_of_quotes++;
+        }
+    }
+
+    if (!needs_quoting) {
+        return lxw_strdup(str);
+    }
+    else {
+        /* Add single quotes to the start and end of the string. */
+        char *quoted_name = calloc(1, len + number_of_quotes + 1);
+        RETURN_ON_MEM_ERROR(quoted_name, NULL);
+
+        quoted_name[0] = '\'';
+
+        for (i = 0, j = 1; i < len; i++, j++) {
+            quoted_name[j] = str[i];
+
+            /* Double quote inline single quotes. */
+            if (str[i] == '\'') {
+                quoted_name[++j] = '\'';
+            }
+        }
+        quoted_name[j++] = '\'';
+        quoted_name[j++] = '\0';
+
+        return quoted_name;
+    }
+}
+
+/*
+ * Thin wrapper for tmpfile() so it can be over-ridden with a user defined
+ * version if required for safety or portability.
+ */
+FILE *
+lxw_tmpfile(char *tmpdir)
+{
+#ifndef USE_STANDARD_TMPFILE
+    return tmpfileplus(tmpdir, NULL, NULL, 0);
+#else
+    (void) tmpdir;
+    return tmpfile();
+#endif
+}
+
+/*
+ * Sample function to handle sprintf of doubles for locale portable code. This
+ * is usually handled by a lxw_sprintf_dbl() macro but it can be replaced with
+ * a function of the same name.
+ *
+ * The code below is a simplified example that changes numbers like 123,45 to
+ * 123.45. End-users can replace this with something more rigorous if
+ * required.
+ */
+#ifdef USE_DOUBLE_FUNCTION
+int
+lxw_sprintf_dbl(char *data, double number)
+{
+    char *tmp;
+
+    lxw_snprintf(data, LXW_ATTR_32, "%.16g", number);
+
+    /* Replace comma with decimal point. */
+    tmp = strchr(data, ',');
+    if (tmp)
+        *tmp = '.';
+
+    return 0;
+}
+#endif

+ 1913 - 0
library/src/workbook.c

@@ -0,0 +1,1913 @@
+/*****************************************************************************
+ * workbook - A library for creating Excel XLSX workbook files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/workbook.h"
+#include "xlsxwriter/utility.h"
+#include "xlsxwriter/packager.h"
+#include "xlsxwriter/hash_table.h"
+
+STATIC int _name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2);
+#ifndef __clang_analyzer__
+LXW_RB_GENERATE_NAMES(lxw_worksheet_names, lxw_worksheet_name, tree_pointers,
+                      _name_cmp);
+#endif
+
+/*
+ * Forward declarations.
+ */
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Comparator for the worksheet names structure red/black tree.
+ */
+STATIC int
+_name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2)
+{
+    return strcmp(name1->name, name2->name);
+}
+
+/*
+ * Free workbook properties.
+ */
+STATIC void
+_free_doc_properties(lxw_doc_properties *properties)
+{
+    if (properties) {
+        free(properties->title);
+        free(properties->subject);
+        free(properties->author);
+        free(properties->manager);
+        free(properties->company);
+        free(properties->category);
+        free(properties->keywords);
+        free(properties->comments);
+        free(properties->status);
+        free(properties->hyperlink_base);
+    }
+
+    free(properties);
+}
+
+/*
+ * Free workbook custom property.
+ */
+STATIC void
+_free_custom_doc_property(lxw_custom_property *custom_property)
+{
+    if (custom_property) {
+        free(custom_property->name);
+        if (custom_property->type == LXW_CUSTOM_STRING)
+            free(custom_property->u.string);
+    }
+
+    free(custom_property);
+}
+
+/*
+ * Free a workbook object.
+ */
+void
+lxw_workbook_free(lxw_workbook *workbook)
+{
+    lxw_worksheet *worksheet;
+    struct lxw_worksheet_name *worksheet_name;
+    struct lxw_worksheet_name *next_name;
+    lxw_chart *chart;
+    lxw_format *format;
+    lxw_defined_name *defined_name;
+    lxw_defined_name *defined_name_tmp;
+    lxw_custom_property *custom_property;
+
+    if (!workbook)
+        return;
+
+    _free_doc_properties(workbook->properties);
+
+    free(workbook->filename);
+
+    /* Free the worksheets in the workbook. */
+    if (workbook->worksheets) {
+        while (!STAILQ_EMPTY(workbook->worksheets)) {
+            worksheet = STAILQ_FIRST(workbook->worksheets);
+            STAILQ_REMOVE_HEAD(workbook->worksheets, list_pointers);
+            lxw_worksheet_free(worksheet);
+        }
+        free(workbook->worksheets);
+
+    }
+
+    /* Free the charts in the workbook. */
+    if (workbook->charts) {
+        while (!STAILQ_EMPTY(workbook->charts)) {
+            chart = STAILQ_FIRST(workbook->charts);
+            STAILQ_REMOVE_HEAD(workbook->charts, list_pointers);
+            lxw_chart_free(chart);
+        }
+        free(workbook->charts);
+    }
+
+    /* Free the formats in the workbook. */
+    if (workbook->formats) {
+        while (!STAILQ_EMPTY(workbook->formats)) {
+            format = STAILQ_FIRST(workbook->formats);
+            STAILQ_REMOVE_HEAD(workbook->formats, list_pointers);
+            lxw_format_free(format);
+        }
+        free(workbook->formats);
+    }
+
+    /* Free the defined_names in the workbook. */
+    if (workbook->defined_names) {
+        defined_name = TAILQ_FIRST(workbook->defined_names);
+        while (defined_name) {
+
+            defined_name_tmp = TAILQ_NEXT(defined_name, list_pointers);
+            free(defined_name);
+            defined_name = defined_name_tmp;
+        }
+        free(workbook->defined_names);
+    }
+
+    /* Free the custom_properties in the workbook. */
+    if (workbook->custom_properties) {
+        while (!STAILQ_EMPTY(workbook->custom_properties)) {
+            custom_property = STAILQ_FIRST(workbook->custom_properties);
+            STAILQ_REMOVE_HEAD(workbook->custom_properties, list_pointers);
+            _free_custom_doc_property(custom_property);
+        }
+        free(workbook->custom_properties);
+    }
+
+    if (workbook->worksheet_names) {
+        for (worksheet_name =
+             RB_MIN(lxw_worksheet_names, workbook->worksheet_names);
+             worksheet_name; worksheet_name = next_name) {
+
+            next_name = RB_NEXT(lxw_worksheet_names,
+                                workbook->worksheet_name, worksheet_name);
+            RB_REMOVE(lxw_worksheet_names,
+                      workbook->worksheet_names, worksheet_name);
+            free(worksheet_name);
+        }
+
+        free(workbook->worksheet_names);
+    }
+
+    lxw_hash_free(workbook->used_xf_formats);
+    lxw_sst_free(workbook->sst);
+    free(workbook->options.tmpdir);
+    free(workbook->ordered_charts);
+    free(workbook);
+}
+
+/*
+ * Set the default index for each format. This is only used for testing.
+ */
+void
+lxw_workbook_set_default_xf_indices(lxw_workbook *self)
+{
+    lxw_format *format;
+
+    STAILQ_FOREACH(format, self->formats, list_pointers) {
+        lxw_format_get_xf_index(format);
+    }
+}
+
+/*
+ * Iterate through the XF Format objects and give them an index to non-default
+ * font elements.
+ */
+STATIC void
+_prepare_fonts(lxw_workbook *self)
+{
+
+    lxw_hash_table *fonts = lxw_hash_new(128, 1, 1);
+    lxw_hash_element *hash_element;
+    lxw_hash_element *used_format_element;
+    uint16_t index = 0;
+
+    LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
+        lxw_format *format = (lxw_format *) used_format_element->value;
+        lxw_font *key = lxw_format_get_font_key(format);
+
+        if (key) {
+            /* Look up the format in the hash table. */
+            hash_element = lxw_hash_key_exists(fonts, key, sizeof(lxw_font));
+
+            if (hash_element) {
+                /* Font has already been used. */
+                format->font_index = *(uint16_t *) hash_element->value;
+                format->has_font = LXW_FALSE;
+                free(key);
+            }
+            else {
+                /* This is a new font. */
+                uint16_t *font_index = calloc(1, sizeof(uint16_t));
+                *font_index = index;
+                format->font_index = index;
+                format->has_font = 1;
+                lxw_insert_hash_element(fonts, key, font_index,
+                                        sizeof(lxw_font));
+                index++;
+            }
+        }
+    }
+
+    lxw_hash_free(fonts);
+
+    self->font_count = index;
+}
+
+/*
+ * Iterate through the XF Format objects and give them an index to non-default
+ * border elements.
+ */
+STATIC void
+_prepare_borders(lxw_workbook *self)
+{
+
+    lxw_hash_table *borders = lxw_hash_new(128, 1, 1);
+    lxw_hash_element *hash_element;
+    lxw_hash_element *used_format_element;
+    uint16_t index = 0;
+
+    LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
+        lxw_format *format = (lxw_format *) used_format_element->value;
+        lxw_border *key = lxw_format_get_border_key(format);
+
+        if (key) {
+            /* Look up the format in the hash table. */
+            hash_element =
+                lxw_hash_key_exists(borders, key, sizeof(lxw_border));
+
+            if (hash_element) {
+                /* Border has already been used. */
+                format->border_index = *(uint16_t *) hash_element->value;
+                format->has_border = LXW_FALSE;
+                free(key);
+            }
+            else {
+                /* This is a new border. */
+                uint16_t *border_index = calloc(1, sizeof(uint16_t));
+                *border_index = index;
+                format->border_index = index;
+                format->has_border = 1;
+                lxw_insert_hash_element(borders, key, border_index,
+                                        sizeof(lxw_border));
+                index++;
+            }
+        }
+    }
+
+    lxw_hash_free(borders);
+
+    self->border_count = index;
+}
+
+/*
+ * Iterate through the XF Format objects and give them an index to non-default
+ * fill elements.
+ */
+STATIC void
+_prepare_fills(lxw_workbook *self)
+{
+
+    lxw_hash_table *fills = lxw_hash_new(128, 1, 1);
+    lxw_hash_element *hash_element;
+    lxw_hash_element *used_format_element;
+    uint16_t index = 2;
+    lxw_fill *default_fill_1 = NULL;
+    lxw_fill *default_fill_2 = NULL;
+    uint16_t *fill_index1 = NULL;
+    uint16_t *fill_index2 = NULL;
+
+    default_fill_1 = calloc(1, sizeof(lxw_fill));
+    GOTO_LABEL_ON_MEM_ERROR(default_fill_1, mem_error);
+
+    default_fill_2 = calloc(1, sizeof(lxw_fill));
+    GOTO_LABEL_ON_MEM_ERROR(default_fill_2, mem_error);
+
+    fill_index1 = calloc(1, sizeof(uint16_t));
+    GOTO_LABEL_ON_MEM_ERROR(fill_index1, mem_error);
+
+    fill_index2 = calloc(1, sizeof(uint16_t));
+    GOTO_LABEL_ON_MEM_ERROR(fill_index2, mem_error);
+
+    /* Add the default fills. */
+    default_fill_1->pattern = LXW_PATTERN_NONE;
+    default_fill_1->fg_color = LXW_COLOR_UNSET;
+    default_fill_1->bg_color = LXW_COLOR_UNSET;
+    *fill_index1 = 0;
+    lxw_insert_hash_element(fills, default_fill_1, fill_index1,
+                            sizeof(lxw_fill));
+
+    default_fill_2->pattern = LXW_PATTERN_GRAY_125;
+    default_fill_2->fg_color = LXW_COLOR_UNSET;
+    default_fill_2->bg_color = LXW_COLOR_UNSET;
+    *fill_index2 = 1;
+    lxw_insert_hash_element(fills, default_fill_2, fill_index2,
+                            sizeof(lxw_fill));
+
+    LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
+        lxw_format *format = (lxw_format *) used_format_element->value;
+        lxw_fill *key = lxw_format_get_fill_key(format);
+
+        /* The following logical statements jointly take care of special */
+        /* cases in relation to cell colors and patterns:                */
+        /* 1. For a solid fill (pattern == 1) Excel reverses the role of */
+        /*    foreground and background colors, and                      */
+        /* 2. If the user specifies a foreground or background color     */
+        /*    without a pattern they probably wanted a solid fill, so    */
+        /*    we fill in the defaults.                                   */
+        if (format->pattern == LXW_PATTERN_SOLID
+            && format->bg_color != LXW_COLOR_UNSET
+            && format->fg_color != LXW_COLOR_UNSET) {
+            lxw_color_t tmp = format->fg_color;
+            format->fg_color = format->bg_color;
+            format->bg_color = tmp;
+        }
+
+        if (format->pattern <= LXW_PATTERN_SOLID
+            && format->bg_color != LXW_COLOR_UNSET
+            && format->fg_color == LXW_COLOR_UNSET) {
+            format->fg_color = format->bg_color;
+            format->bg_color = LXW_COLOR_UNSET;
+            format->pattern = LXW_PATTERN_SOLID;
+        }
+
+        if (format->pattern <= LXW_PATTERN_SOLID
+            && format->bg_color == LXW_COLOR_UNSET
+            && format->fg_color != LXW_COLOR_UNSET) {
+            format->bg_color = LXW_COLOR_UNSET;
+            format->pattern = LXW_PATTERN_SOLID;
+        }
+
+        if (key) {
+            /* Look up the format in the hash table. */
+            hash_element = lxw_hash_key_exists(fills, key, sizeof(lxw_fill));
+
+            if (hash_element) {
+                /* Fill has already been used. */
+                format->fill_index = *(uint16_t *) hash_element->value;
+                format->has_fill = LXW_FALSE;
+                free(key);
+            }
+            else {
+                /* This is a new fill. */
+                uint16_t *fill_index = calloc(1, sizeof(uint16_t));
+                *fill_index = index;
+                format->fill_index = index;
+                format->has_fill = 1;
+                lxw_insert_hash_element(fills, key, fill_index,
+                                        sizeof(lxw_fill));
+                index++;
+            }
+        }
+    }
+
+    lxw_hash_free(fills);
+
+    self->fill_count = index;
+
+    return;
+
+mem_error:
+    free(fill_index2);
+    free(fill_index1);
+    free(default_fill_2);
+    free(default_fill_1);
+    lxw_hash_free(fills);
+}
+
+/*
+ * Iterate through the XF Format objects and give them an index to non-default
+ * number format elements. Note, user defined records start from index 0xA4.
+ */
+STATIC void
+_prepare_num_formats(lxw_workbook *self)
+{
+
+    lxw_hash_table *num_formats = lxw_hash_new(128, 0, 1);
+    lxw_hash_element *hash_element;
+    lxw_hash_element *used_format_element;
+    uint16_t index = 0xA4;
+    uint16_t num_format_count = 0;
+    uint16_t *num_format_index;
+
+    LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
+        lxw_format *format = (lxw_format *) used_format_element->value;
+
+        /* Format already has a number format index. */
+        if (format->num_format_index)
+            continue;
+
+        /* Check if there is a user defined number format string. */
+        if (*format->num_format) {
+            char num_format[LXW_FORMAT_FIELD_LEN] = { 0 };
+            lxw_snprintf(num_format, LXW_FORMAT_FIELD_LEN, "%s",
+                         format->num_format);
+
+            /* Look up the num_format in the hash table. */
+            hash_element = lxw_hash_key_exists(num_formats, num_format,
+                                               LXW_FORMAT_FIELD_LEN);
+
+            if (hash_element) {
+                /* Num_Format has already been used. */
+                format->num_format_index = *(uint16_t *) hash_element->value;
+            }
+            else {
+                /* This is a new num_format. */
+                num_format_index = calloc(1, sizeof(uint16_t));
+                *num_format_index = index;
+                format->num_format_index = index;
+                lxw_insert_hash_element(num_formats, num_format,
+                                        num_format_index,
+                                        LXW_FORMAT_FIELD_LEN);
+                index++;
+                num_format_count++;
+            }
+        }
+    }
+
+    lxw_hash_free(num_formats);
+
+    self->num_format_count = num_format_count;
+}
+
+/*
+ * Prepare workbook and sub-objects for writing.
+ */
+STATIC void
+_prepare_workbook(lxw_workbook *self)
+{
+    /* Set the font index for the format objects. */
+    _prepare_fonts(self);
+
+    /* Set the number format index for the format objects. */
+    _prepare_num_formats(self);
+
+    /* Set the border index for the format objects. */
+    _prepare_borders(self);
+
+    /* Set the fill index for the format objects. */
+    _prepare_fills(self);
+
+}
+
+/*
+ * Compare two defined_name structures.
+ */
+static int
+_compare_defined_names(lxw_defined_name *a, lxw_defined_name *b)
+{
+    int res = strcmp(a->normalised_name, b->normalised_name);
+
+    /* Primary comparison based on defined name. */
+    if (res)
+        return res;
+
+    /* Secondary comparison based on worksheet name. */
+    res = strcmp(a->normalised_sheetname, b->normalised_sheetname);
+
+    return res;
+}
+
+/*
+ * Process and store the defined names. The defined names are stored with
+ * the Workbook.xml but also with the App.xml if they refer to a sheet
+ * range like "Sheet1!:A1". The defined names are store in sorted
+ * order for consistency with Excel. The names need to be normalized before
+ * sorting.
+ */
+STATIC lxw_error
+_store_defined_name(lxw_workbook *self, const char *name,
+                    const char *app_name, const char *formula, int16_t index,
+                    uint8_t hidden)
+{
+    lxw_worksheet *worksheet;
+    lxw_defined_name *defined_name;
+    lxw_defined_name *list_defined_name;
+    char name_copy[LXW_DEFINED_NAME_LENGTH];
+    char *tmp_str;
+    char *worksheet_name;
+
+    /* Do some checks on the input data */
+    if (!name || !formula)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    if (lxw_utf8_strlen(name) > LXW_DEFINED_NAME_LENGTH ||
+        lxw_utf8_strlen(formula) > LXW_DEFINED_NAME_LENGTH) {
+        return LXW_ERROR_128_STRING_LENGTH_EXCEEDED;
+    }
+
+    /* Allocate a new defined_name to be added to the linked list of names. */
+    defined_name = calloc(1, sizeof(struct lxw_defined_name));
+    RETURN_ON_MEM_ERROR(defined_name, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    /* Copy the user input string. */
+    lxw_strcpy(name_copy, name);
+
+    /* Set the worksheet index or -1 for a global defined name. */
+    defined_name->index = index;
+    defined_name->hidden = hidden;
+
+    /* Check for local defined names like like "Sheet1!name". */
+    tmp_str = strchr(name_copy, '!');
+
+    if (tmp_str == NULL) {
+        /* The name is global. We just store the defined name string. */
+        lxw_strcpy(defined_name->name, name_copy);
+    }
+    else {
+        /* The name is worksheet local. We need to extract the sheet name
+         * and map it to a sheet index. */
+
+        /* Split the into the worksheet name and defined name. */
+        *tmp_str = '\0';
+        tmp_str++;
+        worksheet_name = name_copy;
+
+        /* Remove any worksheet quoting. */
+        if (worksheet_name[0] == '\'')
+            worksheet_name++;
+        if (worksheet_name[strlen(worksheet_name) - 1] == '\'')
+            worksheet_name[strlen(worksheet_name) - 1] = '\0';
+
+        /* Search for worksheet name to get the equivalent worksheet index. */
+        STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
+            if (strcmp(worksheet_name, worksheet->name) == 0) {
+                defined_name->index = worksheet->index;
+                lxw_strcpy(defined_name->normalised_sheetname,
+                           worksheet_name);
+            }
+        }
+
+        /* If we didn't find the worksheet name we exit. */
+        if (defined_name->index == -1)
+            goto mem_error;
+
+        lxw_strcpy(defined_name->name, tmp_str);
+    }
+
+    /* Print titles and repeat title pass in the name used for App.xml. */
+    if (app_name) {
+        lxw_strcpy(defined_name->app_name, app_name);
+        lxw_strcpy(defined_name->normalised_sheetname, app_name);
+    }
+    else {
+        lxw_strcpy(defined_name->app_name, name);
+    }
+
+    /* We need to normalize the defined names for sorting. This involves
+     * removing any _xlnm namespace  and converting it to lowercase. */
+    tmp_str = strstr(name_copy, "_xlnm.");
+
+    if (tmp_str)
+        lxw_strcpy(defined_name->normalised_name, defined_name->name + 6);
+    else
+        lxw_strcpy(defined_name->normalised_name, defined_name->name);
+
+    lxw_str_tolower(defined_name->normalised_name);
+    lxw_str_tolower(defined_name->normalised_sheetname);
+
+    /* Strip leading "=" from the formula. */
+    if (formula[0] == '=')
+        lxw_strcpy(defined_name->formula, formula + 1);
+    else
+        lxw_strcpy(defined_name->formula, formula);
+
+    /* We add the defined name to the list in sorted order. */
+    list_defined_name = TAILQ_FIRST(self->defined_names);
+
+    if (list_defined_name == NULL ||
+        _compare_defined_names(defined_name, list_defined_name) < 1) {
+        /* List is empty or defined name goes to the head. */
+        TAILQ_INSERT_HEAD(self->defined_names, defined_name, list_pointers);
+        return LXW_NO_ERROR;
+    }
+
+    TAILQ_FOREACH(list_defined_name, self->defined_names, list_pointers) {
+        int res = _compare_defined_names(defined_name, list_defined_name);
+
+        /* The entry already exists. We exit and don't overwrite. */
+        if (res == 0)
+            goto mem_error;
+
+        /* New defined name is inserted in sorted order before other entries. */
+        if (res < 0) {
+            TAILQ_INSERT_BEFORE(list_defined_name, defined_name,
+                                list_pointers);
+            return LXW_NO_ERROR;
+        }
+    }
+
+    /* If the entry wasn't less than any of the entries in the list we add it
+     * to the end. */
+    TAILQ_INSERT_TAIL(self->defined_names, defined_name, list_pointers);
+    return LXW_NO_ERROR;
+
+mem_error:
+    free(defined_name);
+    return LXW_ERROR_MEMORY_MALLOC_FAILED;
+}
+
+/*
+ * Populate the data cache of a chart data series by reading the data from the
+ * relevant worksheet and adding it to the cached in the range object as a
+ * list of points.
+ *
+ * Note, the data cache isn't strictly required by Excel but it helps if the
+ * chart is embedded in another application such as PowerPoint and it also
+ * helps with comparison testing.
+ */
+STATIC void
+_populate_range_data_cache(lxw_workbook *self, lxw_series_range *range)
+{
+    lxw_worksheet *worksheet;
+    lxw_row_t row_num;
+    lxw_col_t col_num;
+    lxw_row *row_obj;
+    lxw_cell *cell_obj;
+    struct lxw_series_data_point *data_point;
+    uint16_t num_data_points = 0;
+
+    /* If ignore_cache is set then don't try to populate the cache. This flag
+     * may be set manually, for testing, or due to a case where the cache
+     * can't be calculated.
+     */
+    if (range->ignore_cache)
+        return;
+
+    /* Currently we only handle 2D ranges so ensure either the rows or cols
+     * are the same.
+     */
+    if (range->first_row != range->last_row
+        && range->first_col != range->last_col) {
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+
+    /* Check that the sheetname exists. */
+    worksheet = workbook_get_worksheet_by_name(self, range->sheetname);
+    if (!worksheet) {
+        LXW_WARN_FORMAT2("workbook_add_chart(): worksheet name '%s' "
+                         "in chart formula '%s' doesn't exist.",
+                         range->sheetname, range->formula);
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+
+    /* We can't read the data when worksheet optimization is on. */
+    if (worksheet->optimize) {
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+
+    /* Iterate through the worksheet data and populate the range cache. */
+    for (row_num = range->first_row; row_num <= range->last_row; row_num++) {
+        row_obj = lxw_worksheet_find_row(worksheet, row_num);
+
+        for (col_num = range->first_col; col_num <= range->last_col;
+             col_num++) {
+
+            data_point = calloc(1, sizeof(struct lxw_series_data_point));
+            if (!data_point) {
+                range->ignore_cache = LXW_TRUE;
+                return;
+            }
+
+            cell_obj = lxw_worksheet_find_cell(row_obj, col_num);
+
+            if (cell_obj) {
+                if (cell_obj->type == NUMBER_CELL) {
+                    data_point->number = cell_obj->u.number;
+                }
+
+                if (cell_obj->type == STRING_CELL) {
+                    data_point->string = lxw_strdup(cell_obj->sst_string);
+                    data_point->is_string = LXW_TRUE;
+                    range->has_string_cache = LXW_TRUE;
+                }
+            }
+            else {
+                data_point->no_data = LXW_TRUE;
+            }
+
+            STAILQ_INSERT_TAIL(range->data_cache, data_point, list_pointers);
+            num_data_points++;
+        }
+    }
+
+    range->num_data_points = num_data_points;
+
+}
+
+/* Convert a chart range such as Sheet1!$A$1:$A$5 to a sheet name and row-col
+ * dimensions, or vice-versa. This gives us the dimensions to read data back
+ * from the worksheet.
+ */
+STATIC void
+_populate_range_dimensions(lxw_workbook *self, lxw_series_range *range)
+{
+
+    char formula[LXW_MAX_FORMULA_RANGE_LENGTH] = { 0 };
+    char *tmp_str;
+    char *sheetname;
+
+    /* If neither the range formula or sheetname is defined then this probably
+     * isn't a valid range.
+     */
+    if (!range->formula && !range->sheetname) {
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+
+    /* If the sheetname is already defined it was already set via
+     * chart_series_set_categories() or  chart_series_set_values().
+     */
+    if (range->sheetname)
+        return;
+
+    /* Ignore non-contiguous range like (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5) */
+    if (range->formula[0] == '(') {
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+
+    /* Create a copy of the formula to modify and parse into parts. */
+    lxw_snprintf(formula, LXW_MAX_FORMULA_RANGE_LENGTH, "%s", range->formula);
+
+    /* Check for valid formula. TODO. This needs stronger validation. */
+    tmp_str = strchr(formula, '!');
+
+    if (tmp_str == NULL) {
+        range->ignore_cache = LXW_TRUE;
+        return;
+    }
+    else {
+        /* Split the formulas into sheetname and row-col data. */
+        *tmp_str = '\0';
+        tmp_str++;
+        sheetname = formula;
+
+        /* Remove any worksheet quoting. */
+        if (sheetname[0] == '\'')
+            sheetname++;
+        if (sheetname[strlen(sheetname) - 1] == '\'')
+            sheetname[strlen(sheetname) - 1] = '\0';
+
+        /* Check that the sheetname exists. */
+        if (!workbook_get_worksheet_by_name(self, sheetname)) {
+            LXW_WARN_FORMAT2("workbook_add_chart(): worksheet name '%s' "
+                             "in chart formula '%s' doesn't exist.",
+                             sheetname, range->formula);
+            range->ignore_cache = LXW_TRUE;
+            return;
+        }
+
+        range->sheetname = lxw_strdup(sheetname);
+        range->first_row = lxw_name_to_row(tmp_str);
+        range->first_col = lxw_name_to_col(tmp_str);
+
+        if (strchr(tmp_str, ':')) {
+            /* 2D range. */
+            range->last_row = lxw_name_to_row_2(tmp_str);
+            range->last_col = lxw_name_to_col_2(tmp_str);
+        }
+        else {
+            /* 1D range. */
+            range->last_row = range->first_row;
+            range->last_col = range->first_col;
+        }
+
+    }
+}
+
+/* Set the range dimensions and set the data cache.
+ */
+STATIC void
+_populate_range(lxw_workbook *self, lxw_series_range *range)
+{
+    _populate_range_dimensions(self, range);
+    _populate_range_data_cache(self, range);
+}
+
+/*
+ * Add "cached" data to charts to provide the numCache and strCache data for
+ * series and title/axis ranges.
+ */
+STATIC void
+_add_chart_cache_data(lxw_workbook *self)
+{
+    lxw_chart *chart;
+    lxw_chart_series *series;
+
+    STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) {
+
+        _populate_range(self, chart->title.range);
+        _populate_range(self, chart->x_axis->title.range);
+        _populate_range(self, chart->y_axis->title.range);
+
+        if (STAILQ_EMPTY(chart->series_list))
+            continue;
+
+        STAILQ_FOREACH(series, chart->series_list, list_pointers) {
+            _populate_range(self, series->categories);
+            _populate_range(self, series->values);
+            _populate_range(self, series->title.range);
+        }
+    }
+}
+
+/*
+ * Iterate through the worksheets and set up any chart or image drawings.
+ */
+STATIC void
+_prepare_drawings(lxw_workbook *self)
+{
+    lxw_worksheet *worksheet;
+    lxw_image_options *image_options;
+    uint16_t chart_ref_id = 0;
+    uint16_t image_ref_id = 0;
+    uint16_t drawing_id = 0;
+
+    STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
+
+        if (STAILQ_EMPTY(worksheet->image_data)
+            && STAILQ_EMPTY(worksheet->chart_data))
+            continue;
+
+        drawing_id++;
+
+        STAILQ_FOREACH(image_options, worksheet->chart_data, list_pointers) {
+            chart_ref_id++;
+            lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id,
+                                        image_options);
+            if (image_options->chart)
+                STAILQ_INSERT_TAIL(self->ordered_charts, image_options->chart,
+                                   ordered_list_pointers);
+        }
+
+        STAILQ_FOREACH(image_options, worksheet->image_data, list_pointers) {
+
+            if (image_options->image_type == LXW_IMAGE_PNG)
+                self->has_png = LXW_TRUE;
+
+            if (image_options->image_type == LXW_IMAGE_JPEG)
+                self->has_jpeg = LXW_TRUE;
+
+            if (image_options->image_type == LXW_IMAGE_BMP)
+                self->has_bmp = LXW_TRUE;
+
+            image_ref_id++;
+
+            lxw_worksheet_prepare_image(worksheet, image_ref_id, drawing_id,
+                                        image_options);
+        }
+    }
+
+    self->drawing_count = drawing_id;
+}
+
+/*
+ * Iterate through the worksheets and store any defined names used for print
+ * ranges or repeat rows/columns.
+ */
+STATIC void
+_prepare_defined_names(lxw_workbook *self)
+{
+    lxw_worksheet *worksheet;
+    char app_name[LXW_DEFINED_NAME_LENGTH];
+    char range[LXW_DEFINED_NAME_LENGTH];
+    char area[LXW_MAX_CELL_RANGE_LENGTH];
+    char first_col[8];
+    char last_col[8];
+
+    STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
+
+        /*
+         * Check for autofilter settings and store them.
+         */
+        if (worksheet->autofilter.in_use) {
+
+            lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
+                         "%s!_FilterDatabase", worksheet->quoted_name);
+
+            lxw_rowcol_to_range_abs(area,
+                                    worksheet->autofilter.first_row,
+                                    worksheet->autofilter.first_col,
+                                    worksheet->autofilter.last_row,
+                                    worksheet->autofilter.last_col);
+
+            lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
+                         worksheet->quoted_name, area);
+
+            /* Autofilters are the only defined name to set the hidden flag. */
+            _store_defined_name(self, "_xlnm._FilterDatabase", app_name,
+                                range, worksheet->index, LXW_TRUE);
+        }
+
+        /*
+         * Check for Print Area settings and store them.
+         */
+        if (worksheet->print_area.in_use) {
+
+            lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
+                         "%s!Print_Area", worksheet->quoted_name);
+
+            /* Check for print area that is the max row range. */
+            if (worksheet->print_area.first_row == 0
+                && worksheet->print_area.last_row == LXW_ROW_MAX - 1) {
+
+                lxw_col_to_name(first_col,
+                                worksheet->print_area.first_col, LXW_FALSE);
+
+                lxw_col_to_name(last_col,
+                                worksheet->print_area.last_col, LXW_FALSE);
+
+                lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%s:$%s",
+                             first_col, last_col);
+
+            }
+            /* Check for print area that is the max column range. */
+            else if (worksheet->print_area.first_col == 0
+                     && worksheet->print_area.last_col == LXW_COL_MAX - 1) {
+
+                lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%d:$%d",
+                             worksheet->print_area.first_row + 1,
+                             worksheet->print_area.last_row + 1);
+
+            }
+            else {
+                lxw_rowcol_to_range_abs(area,
+                                        worksheet->print_area.first_row,
+                                        worksheet->print_area.first_col,
+                                        worksheet->print_area.last_row,
+                                        worksheet->print_area.last_col);
+            }
+
+            lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
+                         worksheet->quoted_name, area);
+
+            _store_defined_name(self, "_xlnm.Print_Area", app_name,
+                                range, worksheet->index, LXW_FALSE);
+        }
+
+        /*
+         * Check for repeat rows/cols. aka, Print Titles and store them.
+         */
+        if (worksheet->repeat_rows.in_use || worksheet->repeat_cols.in_use) {
+            if (worksheet->repeat_rows.in_use
+                && worksheet->repeat_cols.in_use) {
+                lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
+                             "%s!Print_Titles", worksheet->quoted_name);
+
+                lxw_col_to_name(first_col,
+                                worksheet->repeat_cols.first_col, LXW_FALSE);
+
+                lxw_col_to_name(last_col,
+                                worksheet->repeat_cols.last_col, LXW_FALSE);
+
+                lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
+                             "%s!$%s:$%s,%s!$%d:$%d",
+                             worksheet->quoted_name, first_col,
+                             last_col, worksheet->quoted_name,
+                             worksheet->repeat_rows.first_row + 1,
+                             worksheet->repeat_rows.last_row + 1);
+
+                _store_defined_name(self, "_xlnm.Print_Titles", app_name,
+                                    range, worksheet->index, LXW_FALSE);
+            }
+            else if (worksheet->repeat_rows.in_use) {
+
+                lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
+                             "%s!Print_Titles", worksheet->quoted_name);
+
+                lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
+                             "%s!$%d:$%d", worksheet->quoted_name,
+                             worksheet->repeat_rows.first_row + 1,
+                             worksheet->repeat_rows.last_row + 1);
+
+                _store_defined_name(self, "_xlnm.Print_Titles", app_name,
+                                    range, worksheet->index, LXW_FALSE);
+            }
+            else if (worksheet->repeat_cols.in_use) {
+                lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
+                             "%s!Print_Titles", worksheet->quoted_name);
+
+                lxw_col_to_name(first_col,
+                                worksheet->repeat_cols.first_col, LXW_FALSE);
+
+                lxw_col_to_name(last_col,
+                                worksheet->repeat_cols.last_col, LXW_FALSE);
+
+                lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
+                             "%s!$%s:$%s", worksheet->quoted_name,
+                             first_col, last_col);
+
+                _store_defined_name(self, "_xlnm.Print_Titles", app_name,
+                                    range, worksheet->index, LXW_FALSE);
+            }
+        }
+    }
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_workbook_xml_declaration(lxw_workbook *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <workbook> element.
+ */
+STATIC void
+_write_workbook(lxw_workbook *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns[] = "http://schemas.openxmlformats.org"
+        "/spreadsheetml/2006/main";
+    char xmlns_r[] = "http://schemas.openxmlformats.org"
+        "/officeDocument/2006/relationships";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
+
+    lxw_xml_start_tag(self->file, "workbook", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <fileVersion> element.
+ */
+STATIC void
+_write_file_version(lxw_workbook *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("appName", "xl");
+    LXW_PUSH_ATTRIBUTES_STR("lastEdited", "4");
+    LXW_PUSH_ATTRIBUTES_STR("lowestEdited", "4");
+    LXW_PUSH_ATTRIBUTES_STR("rupBuild", "4505");
+
+    lxw_xml_empty_tag(self->file, "fileVersion", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <workbookPr> element.
+ */
+STATIC void
+_write_workbook_pr(lxw_workbook *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("defaultThemeVersion", "124226");
+
+    lxw_xml_empty_tag(self->file, "workbookPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <workbookView> element.
+ */
+STATIC void
+_write_workbook_view(lxw_workbook *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xWindow", "240");
+    LXW_PUSH_ATTRIBUTES_STR("yWindow", "15");
+    LXW_PUSH_ATTRIBUTES_STR("windowWidth", "16095");
+    LXW_PUSH_ATTRIBUTES_STR("windowHeight", "9660");
+
+    if (self->first_sheet)
+        LXW_PUSH_ATTRIBUTES_INT("firstSheet", self->first_sheet);
+
+    if (self->active_sheet)
+        LXW_PUSH_ATTRIBUTES_INT("activeTab", self->active_sheet);
+
+    lxw_xml_empty_tag(self->file, "workbookView", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <bookViews> element.
+ */
+STATIC void
+_write_book_views(lxw_workbook *self)
+{
+    lxw_xml_start_tag(self->file, "bookViews", NULL);
+
+    _write_workbook_view(self);
+
+    lxw_xml_end_tag(self->file, "bookViews");
+}
+
+/*
+ * Write the <sheet> element.
+ */
+STATIC void
+_write_sheet(lxw_workbook *self, const char *name, uint32_t sheet_id,
+             uint8_t hidden)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH] = "rId1";
+
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", sheet_id);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("name", name);
+    LXW_PUSH_ATTRIBUTES_INT("sheetId", sheet_id);
+
+    if (hidden)
+        LXW_PUSH_ATTRIBUTES_STR("state", "hidden");
+
+    LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
+
+    lxw_xml_empty_tag(self->file, "sheet", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <sheets> element.
+ */
+STATIC void
+_write_sheets(lxw_workbook *self)
+{
+    lxw_worksheet *worksheet;
+
+    lxw_xml_start_tag(self->file, "sheets", NULL);
+
+    STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
+        _write_sheet(self, worksheet->name, worksheet->index + 1,
+                     worksheet->hidden);
+    }
+
+    lxw_xml_end_tag(self->file, "sheets");
+}
+
+/*
+ * Write the <calcPr> element.
+ */
+STATIC void
+_write_calc_pr(lxw_workbook *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("calcId", "124519");
+    LXW_PUSH_ATTRIBUTES_STR("fullCalcOnLoad", "1");
+
+    lxw_xml_empty_tag(self->file, "calcPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <definedName> element.
+ */
+STATIC void
+_write_defined_name(lxw_workbook *self, lxw_defined_name *defined_name)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("name", defined_name->name);
+
+    if (defined_name->index != -1)
+        LXW_PUSH_ATTRIBUTES_INT("localSheetId", defined_name->index);
+
+    if (defined_name->hidden)
+        LXW_PUSH_ATTRIBUTES_INT("hidden", 1);
+
+    lxw_xml_data_element(self->file, "definedName", defined_name->formula,
+                         &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <definedNames> element.
+ */
+STATIC void
+_write_defined_names(lxw_workbook *self)
+{
+    lxw_defined_name *defined_name;
+
+    if (TAILQ_EMPTY(self->defined_names))
+        return;
+
+    lxw_xml_start_tag(self->file, "definedNames", NULL);
+
+    TAILQ_FOREACH(defined_name, self->defined_names, list_pointers) {
+        _write_defined_name(self, defined_name);
+    }
+
+    lxw_xml_end_tag(self->file, "definedNames");
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_workbook_assemble_xml_file(lxw_workbook *self)
+{
+    /* Prepare workbook and sub-objects for writing. */
+    _prepare_workbook(self);
+
+    /* Write the XML declaration. */
+    _workbook_xml_declaration(self);
+
+    /* Write the root workbook element. */
+    _write_workbook(self);
+
+    /* Write the XLSX file version. */
+    _write_file_version(self);
+
+    /* Write the workbook properties. */
+    _write_workbook_pr(self);
+
+    /* Write the workbook view properties. */
+    _write_book_views(self);
+
+    /* Write the worksheet names and ids. */
+    _write_sheets(self);
+
+    /* Write the workbook defined names. */
+    _write_defined_names(self);
+
+    /* Write the workbook calculation properties. */
+    _write_calc_pr(self);
+
+    /* Close the workbook tag. */
+    lxw_xml_end_tag(self->file, "workbook");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Create a new workbook object.
+ */
+lxw_workbook *
+workbook_new(const char *filename)
+{
+    return workbook_new_opt(filename, NULL);
+}
+
+/* Deprecated function name for backwards compatibility. */
+lxw_workbook *
+new_workbook(const char *filename)
+{
+    return workbook_new_opt(filename, NULL);
+}
+
+/* Deprecated function name for backwards compatibility. */
+lxw_workbook *
+new_workbook_opt(const char *filename, lxw_workbook_options *options)
+{
+    return workbook_new_opt(filename, options);
+}
+
+/*
+ * Create a new workbook object with options.
+ */
+lxw_workbook *
+workbook_new_opt(const char *filename, lxw_workbook_options *options)
+{
+    lxw_format *format;
+    lxw_workbook *workbook;
+
+    /* Create the workbook object. */
+    workbook = calloc(1, sizeof(lxw_workbook));
+    GOTO_LABEL_ON_MEM_ERROR(workbook, mem_error);
+    workbook->filename = lxw_strdup(filename);
+
+    /* Add the worksheets list. */
+    workbook->worksheets = calloc(1, sizeof(struct lxw_worksheets));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->worksheets, mem_error);
+    STAILQ_INIT(workbook->worksheets);
+
+    /* Add the worksheet names tree. */
+    workbook->worksheet_names = calloc(1, sizeof(struct lxw_worksheet_names));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->worksheet_names, mem_error);
+    RB_INIT(workbook->worksheet_names);
+
+    /* Add the charts list. */
+    workbook->charts = calloc(1, sizeof(struct lxw_charts));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->charts, mem_error);
+    STAILQ_INIT(workbook->charts);
+
+    /* Add the ordered charts list to track chart insertion order. */
+    workbook->ordered_charts = calloc(1, sizeof(struct lxw_charts));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->ordered_charts, mem_error);
+    STAILQ_INIT(workbook->ordered_charts);
+
+    /* Add the formats list. */
+    workbook->formats = calloc(1, sizeof(struct lxw_formats));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->formats, mem_error);
+    STAILQ_INIT(workbook->formats);
+
+    /* Add the defined_names list. */
+    workbook->defined_names = calloc(1, sizeof(struct lxw_defined_names));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->defined_names, mem_error);
+    TAILQ_INIT(workbook->defined_names);
+
+    /* Add the shared strings table. */
+    workbook->sst = lxw_sst_new();
+    GOTO_LABEL_ON_MEM_ERROR(workbook->sst, mem_error);
+
+    /* Add the default workbook properties. */
+    workbook->properties = calloc(1, sizeof(lxw_doc_properties));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->properties, mem_error);
+
+    /* Add a hash table to track format indices. */
+    workbook->used_xf_formats = lxw_hash_new(128, 1, 0);
+    GOTO_LABEL_ON_MEM_ERROR(workbook->used_xf_formats, mem_error);
+
+    /* Add the worksheets list. */
+    workbook->custom_properties =
+        calloc(1, sizeof(struct lxw_custom_properties));
+    GOTO_LABEL_ON_MEM_ERROR(workbook->custom_properties, mem_error);
+    STAILQ_INIT(workbook->custom_properties);
+
+    /* Add the default cell format. */
+    format = workbook_add_format(workbook);
+    GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
+
+    /* Initialize its index. */
+    lxw_format_get_xf_index(format);
+
+    if (options) {
+        workbook->options.constant_memory = options->constant_memory;
+        workbook->options.tmpdir = lxw_strdup(options->tmpdir);
+    }
+
+    return workbook;
+
+mem_error:
+    lxw_workbook_free(workbook);
+    workbook = NULL;
+    return NULL;
+}
+
+/*
+ * Add a new worksheet to the Excel workbook.
+ */
+lxw_worksheet *
+workbook_add_worksheet(lxw_workbook *self, const char *sheetname)
+{
+    lxw_worksheet *worksheet;
+    lxw_worksheet_name *worksheet_name = NULL;
+    lxw_error error;
+    lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+    char *new_name = NULL;
+
+    if (sheetname) {
+        /* Use the user supplied name. */
+        init_data.name = lxw_strdup(sheetname);
+        init_data.quoted_name = lxw_quote_sheetname((char *) sheetname);
+    }
+    else {
+        /* Use the default SheetN name. */
+        new_name = malloc(LXW_MAX_SHEETNAME_LENGTH);
+        GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error);
+
+        lxw_snprintf(new_name, LXW_MAX_SHEETNAME_LENGTH, "Sheet%d",
+                     self->num_sheets + 1);
+        init_data.name = new_name;
+        init_data.quoted_name = lxw_strdup(new_name);
+    }
+
+    /* Check that the worksheet name is valid. */
+    error = workbook_validate_worksheet_name(self, init_data.name);
+    if (error) {
+        LXW_WARN_FORMAT2("workbook_add_worksheet(): worksheet name '%s' has "
+                         "error: %s", init_data.name, lxw_strerror(error));
+        goto mem_error;
+    }
+
+    /* Create a struct to find/store the worksheet name/pointer. */
+    worksheet_name = calloc(1, sizeof(struct lxw_worksheet_name));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet_name, mem_error);
+
+    /* Initialize the metadata to pass to the worksheet. */
+    init_data.hidden = 0;
+    init_data.index = self->num_sheets;
+    init_data.sst = self->sst;
+    init_data.optimize = self->options.constant_memory;
+    init_data.active_sheet = &self->active_sheet;
+    init_data.first_sheet = &self->first_sheet;
+    init_data.tmpdir = self->options.tmpdir;
+
+    /* Create a new worksheet object. */
+    worksheet = lxw_worksheet_new(&init_data);
+    GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
+
+    self->num_sheets++;
+    STAILQ_INSERT_TAIL(self->worksheets, worksheet, list_pointers);
+
+    /* Store the worksheet so we can look it up by name. */
+    worksheet_name->name = init_data.name;
+    worksheet_name->worksheet = worksheet;
+    RB_INSERT(lxw_worksheet_names, self->worksheet_names, worksheet_name);
+
+    return worksheet;
+
+mem_error:
+    free(init_data.name);
+    free(init_data.quoted_name);
+    free(worksheet_name);
+    return NULL;
+}
+
+/*
+ * Add a new chart to the Excel workbook.
+ */
+lxw_chart *
+workbook_add_chart(lxw_workbook *self, uint8_t type)
+{
+    lxw_chart *chart;
+
+    /* Create a new chart object. */
+    chart = lxw_chart_new(type);
+
+    if (chart)
+        STAILQ_INSERT_TAIL(self->charts, chart, list_pointers);
+
+    return chart;
+}
+
+/*
+ * Add a new format to the Excel workbook.
+ */
+lxw_format *
+workbook_add_format(lxw_workbook *self)
+{
+    /* Create a new format object. */
+    lxw_format *format = lxw_format_new();
+    RETURN_ON_MEM_ERROR(format, NULL);
+
+    format->xf_format_indices = self->used_xf_formats;
+    format->num_xf_formats = &self->num_xf_formats;
+
+    STAILQ_INSERT_TAIL(self->formats, format, list_pointers);
+
+    return format;
+}
+
+/*
+ * Call finalization code and close file.
+ */
+lxw_error
+workbook_close(lxw_workbook *self)
+{
+    lxw_worksheet *worksheet = NULL;
+    lxw_packager *packager = NULL;
+    lxw_error error = LXW_NO_ERROR;
+
+    /* Add a default worksheet if non have been added. */
+    if (!self->num_sheets)
+        workbook_add_worksheet(self, NULL);
+
+    /* Ensure that at least one worksheet has been selected. */
+    if (self->active_sheet == 0) {
+        worksheet = STAILQ_FIRST(self->worksheets);
+        worksheet->selected = 1;
+        worksheet->hidden = 0;
+    }
+
+    /* Set the active sheet. */
+    STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
+        if (worksheet->index == self->active_sheet)
+            worksheet->active = 1;
+    }
+
+    /* Set the defined names for the worksheets such as Print Titles. */
+    _prepare_defined_names(self);
+
+    /* Prepare the drawings, charts and images. */
+    _prepare_drawings(self);
+
+    /* Add cached data to charts. */
+    _add_chart_cache_data(self);
+
+    /* Create a packager object to assemble sub-elements into a zip file. */
+    packager = lxw_packager_new(self->filename, self->options.tmpdir);
+
+    /* If the packager fails it is generally due to a zip permission error. */
+    if (packager == NULL) {
+        fprintf(stderr, "[ERROR] workbook_close(): "
+                "Error creating '%s'. "
+                "Error = %s\n", self->filename, strerror(errno));
+
+        error = LXW_ERROR_CREATING_XLSX_FILE;
+        goto mem_error;
+    }
+
+    /* Set the workbook object in the packager. */
+    packager->workbook = self;
+
+    /* Assemble all the sub-files in the xlsx package. */
+    error = lxw_create_package(packager);
+
+    /* Error and non-error conditions fall through to the cleanup code. */
+    if (error == LXW_ERROR_CREATING_TMPFILE) {
+        fprintf(stderr, "[ERROR] workbook_close(): "
+                "Error creating tmpfile(s) to assemble '%s'. "
+                "Error = %s\n", self->filename, strerror(errno));
+    }
+
+    /* If LXW_ERROR_ZIP_FILE_OPERATION then errno is set by zlib. */
+    if (error == LXW_ERROR_ZIP_FILE_OPERATION) {
+        fprintf(stderr, "[ERROR] workbook_close(): "
+                "Zlib error while creating xlsx file '%s'. "
+                "Error = %s\n", self->filename, strerror(errno));
+    }
+
+    /* The next 2 error conditions don't set errno. */
+    if (error == LXW_ERROR_ZIP_FILE_ADD) {
+        fprintf(stderr, "[ERROR] workbook_close(): "
+                "Zlib error adding file to xlsx file '%s'.\n",
+                self->filename);
+    }
+
+    if (error == LXW_ERROR_ZIP_CLOSE) {
+        fprintf(stderr, "[ERROR] workbook_close(): "
+                "Zlib error closing xlsx file '%s'.\n", self->filename);
+    }
+
+mem_error:
+    lxw_packager_free(packager);
+    lxw_workbook_free(self);
+    return error;
+}
+
+/*
+ * Create a defined name in Excel. We handle global/workbook level names and
+ * local/worksheet names.
+ */
+lxw_error
+workbook_define_name(lxw_workbook *self, const char *name,
+                     const char *formula)
+{
+    return _store_defined_name(self, name, NULL, formula, -1, LXW_FALSE);
+}
+
+/*
+ * Set the document properties such as Title, Author etc.
+ */
+lxw_error
+workbook_set_properties(lxw_workbook *self, lxw_doc_properties *user_props)
+{
+    lxw_doc_properties *doc_props;
+
+    /* Free any existing properties. */
+    _free_doc_properties(self->properties);
+
+    doc_props = calloc(1, sizeof(lxw_doc_properties));
+    GOTO_LABEL_ON_MEM_ERROR(doc_props, mem_error);
+
+    /* Copy the user properties to an internal structure. */
+    if (user_props->title) {
+        doc_props->title = lxw_strdup(user_props->title);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->title, mem_error);
+    }
+
+    if (user_props->subject) {
+        doc_props->subject = lxw_strdup(user_props->subject);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->subject, mem_error);
+    }
+
+    if (user_props->author) {
+        doc_props->author = lxw_strdup(user_props->author);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->author, mem_error);
+    }
+
+    if (user_props->manager) {
+        doc_props->manager = lxw_strdup(user_props->manager);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->manager, mem_error);
+    }
+
+    if (user_props->company) {
+        doc_props->company = lxw_strdup(user_props->company);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->company, mem_error);
+    }
+
+    if (user_props->category) {
+        doc_props->category = lxw_strdup(user_props->category);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->category, mem_error);
+    }
+
+    if (user_props->keywords) {
+        doc_props->keywords = lxw_strdup(user_props->keywords);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->keywords, mem_error);
+    }
+
+    if (user_props->comments) {
+        doc_props->comments = lxw_strdup(user_props->comments);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->comments, mem_error);
+    }
+
+    if (user_props->status) {
+        doc_props->status = lxw_strdup(user_props->status);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->status, mem_error);
+    }
+
+    if (user_props->hyperlink_base) {
+        doc_props->hyperlink_base = lxw_strdup(user_props->hyperlink_base);
+        GOTO_LABEL_ON_MEM_ERROR(doc_props->hyperlink_base, mem_error);
+    }
+
+    self->properties = doc_props;
+
+    return LXW_NO_ERROR;
+
+mem_error:
+    _free_doc_properties(doc_props);
+    return LXW_ERROR_MEMORY_MALLOC_FAILED;
+}
+
+/*
+ * Set a string custom document property.
+ */
+lxw_error
+workbook_set_custom_property_string(lxw_workbook *self, const char *name,
+                                    const char *value)
+{
+    lxw_custom_property *custom_property;
+
+    if (!name) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_string(): "
+                        "parameter 'name' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (!value) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_string(): "
+                        "parameter 'value' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (lxw_utf8_strlen(name) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_string(): parameter "
+                        "'name' exceeds Excel length limit of 255.");
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+    }
+
+    if (lxw_utf8_strlen(value) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_string(): parameter "
+                        "'value' exceeds Excel length limit of 255.");
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+    }
+
+    /* Create a struct to hold the custom property. */
+    custom_property = calloc(1, sizeof(struct lxw_custom_property));
+    RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    custom_property->name = lxw_strdup(name);
+    custom_property->u.string = lxw_strdup(value);
+    custom_property->type = LXW_CUSTOM_STRING;
+
+    STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
+                       list_pointers);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set a double number custom document property.
+ */
+lxw_error
+workbook_set_custom_property_number(lxw_workbook *self, const char *name,
+                                    double value)
+{
+    lxw_custom_property *custom_property;
+
+    if (!name) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter "
+                        "'name' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (lxw_utf8_strlen(name) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter "
+                        "'name' exceeds Excel length limit of 255.");
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+    }
+
+    /* Create a struct to hold the custom property. */
+    custom_property = calloc(1, sizeof(struct lxw_custom_property));
+    RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    custom_property->name = lxw_strdup(name);
+    custom_property->u.number = value;
+    custom_property->type = LXW_CUSTOM_DOUBLE;
+
+    STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
+                       list_pointers);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set a integer number custom document property.
+ */
+lxw_error
+workbook_set_custom_property_integer(lxw_workbook *self, const char *name,
+                                     int32_t value)
+{
+    lxw_custom_property *custom_property;
+
+    if (!name) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter "
+                        "'name' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (strlen(name) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter "
+                        "'name' exceeds Excel length limit of 255.");
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+    }
+
+    /* Create a struct to hold the custom property. */
+    custom_property = calloc(1, sizeof(struct lxw_custom_property));
+    RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    custom_property->name = lxw_strdup(name);
+    custom_property->u.integer = value;
+    custom_property->type = LXW_CUSTOM_INTEGER;
+
+    STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
+                       list_pointers);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set a boolean custom document property.
+ */
+lxw_error
+workbook_set_custom_property_boolean(lxw_workbook *self, const char *name,
+                                     uint8_t value)
+{
+    lxw_custom_property *custom_property;
+
+    if (!name) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter "
+                        "'name' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (lxw_utf8_strlen(name) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter "
+                        "'name' exceeds Excel length limit of 255.");
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+    }
+
+    /* Create a struct to hold the custom property. */
+    custom_property = calloc(1, sizeof(struct lxw_custom_property));
+    RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    custom_property->name = lxw_strdup(name);
+    custom_property->u.boolean = value;
+    custom_property->type = LXW_CUSTOM_BOOLEAN;
+
+    STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
+                       list_pointers);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set a datetime custom document property.
+ */
+lxw_error
+workbook_set_custom_property_datetime(lxw_workbook *self, const char *name,
+                                      lxw_datetime *datetime)
+{
+    lxw_custom_property *custom_property;
+
+    if (!name) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
+                        "'name' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (lxw_utf8_strlen(name) > 255) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
+                        "'name' exceeds Excel length limit of 255.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    if (!datetime) {
+        LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
+                        "'datetime' cannot be NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    /* Create a struct to hold the custom property. */
+    custom_property = calloc(1, sizeof(struct lxw_custom_property));
+    RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    custom_property->name = lxw_strdup(name);
+
+    memcpy(&custom_property->u.datetime, datetime, sizeof(lxw_datetime));
+    custom_property->type = LXW_CUSTOM_DATETIME;
+
+    STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
+                       list_pointers);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Get a worksheet object from its name.
+ */
+lxw_worksheet *
+workbook_get_worksheet_by_name(lxw_workbook *self, const char *name)
+{
+    lxw_worksheet_name worksheet_name;
+    lxw_worksheet_name *found;
+
+    if (!name)
+        return NULL;
+
+    worksheet_name.name = name;
+    found = RB_FIND(lxw_worksheet_names,
+                    self->worksheet_names, &worksheet_name);
+
+    if (found)
+        return found->worksheet;
+    else
+        return NULL;
+}
+
+/*
+ * Validate the worksheet name based on Excel's rules.
+ */
+lxw_error
+workbook_validate_worksheet_name(lxw_workbook *self, const char *sheetname)
+{
+    /* Check the UTF-8 length of the worksheet name. */
+    if (lxw_utf8_strlen(sheetname) > LXW_SHEETNAME_MAX)
+        return LXW_ERROR_SHEETNAME_LENGTH_EXCEEDED;
+
+    /* Check that the worksheet name doesn't contain invalid characters. */
+    if (strpbrk(sheetname, "[]:*?/\\"))
+        return LXW_ERROR_INVALID_SHEETNAME_CHARACTER;
+
+    /* Check if the worksheet name is already in use. */
+    if (workbook_get_worksheet_by_name(self, sheetname))
+        return LXW_ERROR_SHEETNAME_ALREADY_USED;
+
+    return LXW_NO_ERROR;
+}

+ 5739 - 0
library/src/worksheet.c

@@ -0,0 +1,5739 @@
+/*****************************************************************************
+ * worksheet - A library for creating Excel XLSX worksheet files.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <ctype.h>
+
+#include "xlsxwriter/xmlwriter.h"
+#include "xlsxwriter/worksheet.h"
+#include "xlsxwriter/format.h"
+#include "xlsxwriter/utility.h"
+#include "xlsxwriter/relationships.h"
+
+#define LXW_STR_MAX                      32767
+#define LXW_BUFFER_SIZE                  4096
+#define LXW_PORTRAIT                     1
+#define LXW_LANDSCAPE                    0
+#define LXW_PRINT_ACROSS                 1
+#define LXW_VALIDATION_MAX_TITLE_LENGTH  32
+#define LXW_VALIDATION_MAX_STRING_LENGTH 255
+
+/*
+ * Forward declarations.
+ */
+STATIC void _worksheet_write_rows(lxw_worksheet *self);
+STATIC int _row_cmp(lxw_row *row1, lxw_row *row2);
+STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2);
+
+#ifndef __clang_analyzer__
+LXW_RB_GENERATE_ROW(lxw_table_rows, lxw_row, tree_pointers, _row_cmp);
+LXW_RB_GENERATE_CELL(lxw_table_cells, lxw_cell, tree_pointers, _cell_cmp);
+#endif
+
+/*****************************************************************************
+ *
+ * Private functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Find but don't create a row object for a given row number.
+ */
+lxw_row *
+lxw_worksheet_find_row(lxw_worksheet *self, lxw_row_t row_num)
+{
+    lxw_row row;
+
+    row.row_num = row_num;
+
+    return RB_FIND(lxw_table_rows, self->table, &row);
+}
+
+/*
+ * Find but don't create a cell object for a given row object and col number.
+ */
+lxw_cell *
+lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num)
+{
+    lxw_cell cell;
+
+    if (!row)
+        return NULL;
+
+    cell.col_num = col_num;
+
+    return RB_FIND(lxw_table_cells, row->cells, &cell);
+}
+
+/*
+ * Create a new worksheet object.
+ */
+lxw_worksheet *
+lxw_worksheet_new(lxw_worksheet_init_data *init_data)
+{
+    lxw_worksheet *worksheet = calloc(1, sizeof(lxw_worksheet));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
+
+    worksheet->table = calloc(1, sizeof(struct lxw_table_rows));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->table, mem_error);
+    RB_INIT(worksheet->table);
+
+    worksheet->hyperlinks = calloc(1, sizeof(struct lxw_table_rows));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->hyperlinks, mem_error);
+    RB_INIT(worksheet->hyperlinks);
+
+    /* Initialize the cached rows. */
+    worksheet->table->cached_row_num = LXW_ROW_MAX + 1;
+    worksheet->hyperlinks->cached_row_num = LXW_ROW_MAX + 1;
+
+    if (init_data && init_data->optimize) {
+        worksheet->array = calloc(LXW_COL_MAX, sizeof(struct lxw_cell *));
+        GOTO_LABEL_ON_MEM_ERROR(worksheet->array, mem_error);
+    }
+
+    worksheet->col_options =
+        calloc(LXW_COL_META_MAX, sizeof(lxw_col_options *));
+    worksheet->col_options_max = LXW_COL_META_MAX;
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->col_options, mem_error);
+
+    worksheet->col_formats = calloc(LXW_COL_META_MAX, sizeof(lxw_format *));
+    worksheet->col_formats_max = LXW_COL_META_MAX;
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->col_formats, mem_error);
+
+    worksheet->optimize_row = calloc(1, sizeof(struct lxw_row));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_row, mem_error);
+    worksheet->optimize_row->height = LXW_DEF_ROW_HEIGHT;
+
+    worksheet->merged_ranges = calloc(1, sizeof(struct lxw_merged_ranges));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->merged_ranges, mem_error);
+    STAILQ_INIT(worksheet->merged_ranges);
+
+    worksheet->image_data = calloc(1, sizeof(struct lxw_image_data));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->image_data, mem_error);
+    STAILQ_INIT(worksheet->image_data);
+
+    worksheet->chart_data = calloc(1, sizeof(struct lxw_chart_data));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->chart_data, mem_error);
+    STAILQ_INIT(worksheet->chart_data);
+
+    worksheet->selections = calloc(1, sizeof(struct lxw_selections));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error);
+    STAILQ_INIT(worksheet->selections);
+
+    worksheet->data_validations =
+        calloc(1, sizeof(struct lxw_data_validations));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->data_validations, mem_error);
+    STAILQ_INIT(worksheet->data_validations);
+
+    worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error);
+    STAILQ_INIT(worksheet->external_hyperlinks);
+
+    worksheet->external_drawing_links =
+        calloc(1, sizeof(struct lxw_rel_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->external_drawing_links, mem_error);
+    STAILQ_INIT(worksheet->external_drawing_links);
+
+    worksheet->drawing_links = calloc(1, sizeof(struct lxw_rel_tuples));
+    GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error);
+    STAILQ_INIT(worksheet->drawing_links);
+
+    if (init_data && init_data->optimize) {
+        FILE *tmpfile;
+
+        tmpfile = lxw_tmpfile(init_data->tmpdir);
+        if (!tmpfile) {
+            LXW_ERROR("Error creating tmpfile() for worksheet in "
+                      "'constant_memory' mode.");
+            goto mem_error;
+        }
+
+        worksheet->optimize_tmpfile = tmpfile;
+        GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_tmpfile, mem_error);
+        worksheet->file = worksheet->optimize_tmpfile;
+    }
+
+    /* Initialize the worksheet dimensions. */
+    worksheet->dim_rowmax = 0;
+    worksheet->dim_colmax = 0;
+    worksheet->dim_rowmin = LXW_ROW_MAX;
+    worksheet->dim_colmin = LXW_COL_MAX;
+
+    worksheet->default_row_height = LXW_DEF_ROW_HEIGHT;
+    worksheet->default_row_pixels = 20;
+    worksheet->default_col_pixels = 64;
+
+    /* Initialize the page setup properties. */
+    worksheet->fit_height = 0;
+    worksheet->fit_width = 0;
+    worksheet->page_start = 0;
+    worksheet->print_scale = 100;
+    worksheet->fit_page = 0;
+    worksheet->orientation = LXW_TRUE;
+    worksheet->page_order = 0;
+    worksheet->page_setup_changed = LXW_FALSE;
+    worksheet->page_view = LXW_FALSE;
+    worksheet->paper_size = 0;
+    worksheet->vertical_dpi = 0;
+    worksheet->horizontal_dpi = 0;
+    worksheet->margin_left = 0.7;
+    worksheet->margin_right = 0.7;
+    worksheet->margin_top = 0.75;
+    worksheet->margin_bottom = 0.75;
+    worksheet->margin_header = 0.3;
+    worksheet->margin_footer = 0.3;
+    worksheet->print_gridlines = 0;
+    worksheet->screen_gridlines = 1;
+    worksheet->print_options_changed = 0;
+    worksheet->zoom = 100;
+    worksheet->zoom_scale_normal = LXW_TRUE;
+    worksheet->show_zeros = LXW_TRUE;
+    worksheet->outline_on = LXW_TRUE;
+    worksheet->outline_style = LXW_TRUE;
+    worksheet->outline_below = LXW_TRUE;
+    worksheet->outline_right = LXW_FALSE;
+    worksheet->tab_color = LXW_COLOR_UNSET;
+
+    if (init_data) {
+        worksheet->name = init_data->name;
+        worksheet->quoted_name = init_data->quoted_name;
+        worksheet->tmpdir = init_data->tmpdir;
+        worksheet->index = init_data->index;
+        worksheet->hidden = init_data->hidden;
+        worksheet->sst = init_data->sst;
+        worksheet->optimize = init_data->optimize;
+        worksheet->active_sheet = init_data->active_sheet;
+        worksheet->first_sheet = init_data->first_sheet;
+    }
+
+    return worksheet;
+
+mem_error:
+    lxw_worksheet_free(worksheet);
+    return NULL;
+}
+
+/*
+ * Free a worksheet cell.
+ */
+STATIC void
+_free_cell(lxw_cell *cell)
+{
+    if (!cell)
+        return;
+
+    if (cell->type != NUMBER_CELL && cell->type != STRING_CELL
+        && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL) {
+
+        free(cell->u.string);
+    }
+
+    free(cell->user_data1);
+    free(cell->user_data2);
+
+    free(cell);
+}
+
+/*
+ * Free a worksheet row.
+ */
+STATIC void
+_free_row(lxw_row *row)
+{
+    lxw_cell *cell;
+    lxw_cell *next_cell;
+
+    if (!row)
+        return;
+
+    for (cell = RB_MIN(lxw_table_cells, row->cells); cell; cell = next_cell) {
+        next_cell = RB_NEXT(lxw_table_cells, row->cells, cell);
+        RB_REMOVE(lxw_table_cells, row->cells, cell);
+        _free_cell(cell);
+    }
+
+    free(row->cells);
+    free(row);
+}
+
+/*
+ * Free a worksheet image_options.
+ */
+STATIC void
+_free_image_options(lxw_image_options *image)
+{
+    if (!image)
+        return;
+
+    free(image->filename);
+    free(image->short_name);
+    free(image->extension);
+    free(image->url);
+    free(image->tip);
+    free(image);
+}
+
+/*
+ * Free a worksheet data_validation.
+ */
+STATIC void
+_free_data_validation(lxw_data_validation *data_validation)
+{
+    if (!data_validation)
+        return;
+
+    free(data_validation->value_formula);
+    free(data_validation->maximum_formula);
+    free(data_validation->input_title);
+    free(data_validation->input_message);
+    free(data_validation->error_title);
+    free(data_validation->error_message);
+    free(data_validation->minimum_formula);
+
+    free(data_validation);
+}
+
+/*
+ * Free a worksheet object.
+ */
+void
+lxw_worksheet_free(lxw_worksheet *worksheet)
+{
+    lxw_row *row;
+    lxw_row *next_row;
+    lxw_col_t col;
+    lxw_merged_range *merged_range;
+    lxw_image_options *image_options;
+    lxw_selection *selection;
+    lxw_data_validation *data_validation;
+    lxw_rel_tuple *relationship;
+
+    if (!worksheet)
+        return;
+
+    if (worksheet->col_options) {
+        for (col = 0; col < worksheet->col_options_max; col++) {
+            if (worksheet->col_options[col])
+                free(worksheet->col_options[col]);
+        }
+    }
+
+    free(worksheet->col_options);
+    free(worksheet->col_sizes);
+    free(worksheet->col_formats);
+
+    if (worksheet->table) {
+        for (row = RB_MIN(lxw_table_rows, worksheet->table); row;
+             row = next_row) {
+
+            next_row = RB_NEXT(lxw_table_rows, worksheet->table, row);
+            RB_REMOVE(lxw_table_rows, worksheet->table, row);
+            _free_row(row);
+        }
+
+        free(worksheet->table);
+    }
+
+    if (worksheet->hyperlinks) {
+        for (row = RB_MIN(lxw_table_rows, worksheet->hyperlinks); row;
+             row = next_row) {
+
+            next_row = RB_NEXT(lxw_table_rows, worksheet->hyperlinks, row);
+            RB_REMOVE(lxw_table_rows, worksheet->hyperlinks, row);
+            _free_row(row);
+        }
+
+        free(worksheet->hyperlinks);
+    }
+
+    if (worksheet->merged_ranges) {
+        while (!STAILQ_EMPTY(worksheet->merged_ranges)) {
+            merged_range = STAILQ_FIRST(worksheet->merged_ranges);
+            STAILQ_REMOVE_HEAD(worksheet->merged_ranges, list_pointers);
+            free(merged_range);
+        }
+
+        free(worksheet->merged_ranges);
+    }
+
+    if (worksheet->image_data) {
+        while (!STAILQ_EMPTY(worksheet->image_data)) {
+            image_options = STAILQ_FIRST(worksheet->image_data);
+            STAILQ_REMOVE_HEAD(worksheet->image_data, list_pointers);
+            _free_image_options(image_options);
+        }
+
+        free(worksheet->image_data);
+    }
+
+    if (worksheet->chart_data) {
+        while (!STAILQ_EMPTY(worksheet->chart_data)) {
+            image_options = STAILQ_FIRST(worksheet->chart_data);
+            STAILQ_REMOVE_HEAD(worksheet->chart_data, list_pointers);
+            _free_image_options(image_options);
+        }
+
+        free(worksheet->chart_data);
+    }
+
+    if (worksheet->selections) {
+        while (!STAILQ_EMPTY(worksheet->selections)) {
+            selection = STAILQ_FIRST(worksheet->selections);
+            STAILQ_REMOVE_HEAD(worksheet->selections, list_pointers);
+            free(selection);
+        }
+
+        free(worksheet->selections);
+    }
+
+    if (worksheet->data_validations) {
+        while (!STAILQ_EMPTY(worksheet->data_validations)) {
+            data_validation = STAILQ_FIRST(worksheet->data_validations);
+            STAILQ_REMOVE_HEAD(worksheet->data_validations, list_pointers);
+            _free_data_validation(data_validation);
+        }
+
+        free(worksheet->data_validations);
+    }
+
+    /* TODO. Add function for freeing the relationship lists. */
+    while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) {
+        relationship = STAILQ_FIRST(worksheet->external_hyperlinks);
+        STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers);
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+    free(worksheet->external_hyperlinks);
+
+    while (!STAILQ_EMPTY(worksheet->external_drawing_links)) {
+        relationship = STAILQ_FIRST(worksheet->external_drawing_links);
+        STAILQ_REMOVE_HEAD(worksheet->external_drawing_links, list_pointers);
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+    free(worksheet->external_drawing_links);
+
+    while (!STAILQ_EMPTY(worksheet->drawing_links)) {
+        relationship = STAILQ_FIRST(worksheet->drawing_links);
+        STAILQ_REMOVE_HEAD(worksheet->drawing_links, list_pointers);
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+    free(worksheet->drawing_links);
+
+    if (worksheet->array) {
+        for (col = 0; col < LXW_COL_MAX; col++) {
+            _free_cell(worksheet->array[col]);
+        }
+        free(worksheet->array);
+    }
+
+    if (worksheet->optimize_row)
+        free(worksheet->optimize_row);
+
+    if (worksheet->drawing)
+        lxw_drawing_free(worksheet->drawing);
+
+    free(worksheet->hbreaks);
+    free(worksheet->vbreaks);
+    free(worksheet->name);
+    free(worksheet->quoted_name);
+
+    free(worksheet);
+    worksheet = NULL;
+}
+
+/*
+ * Create a new worksheet row object.
+ */
+STATIC lxw_row *
+_new_row(lxw_row_t row_num)
+{
+    lxw_row *row = calloc(1, sizeof(lxw_row));
+
+    if (row) {
+        row->row_num = row_num;
+        row->cells = calloc(1, sizeof(struct lxw_table_cells));
+        row->height = LXW_DEF_ROW_HEIGHT;
+
+        if (row->cells)
+            RB_INIT(row->cells);
+        else
+            LXW_MEM_ERROR();
+    }
+    else {
+        LXW_MEM_ERROR();
+    }
+
+    return row;
+}
+
+/*
+ * Create a new worksheet number cell object.
+ */
+STATIC lxw_cell *
+_new_number_cell(lxw_row_t row_num,
+                 lxw_col_t col_num, double value, lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = NUMBER_CELL;
+    cell->format = format;
+    cell->u.number = value;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet string cell object.
+ */
+STATIC lxw_cell *
+_new_string_cell(lxw_row_t row_num,
+                 lxw_col_t col_num, int32_t string_id, char *sst_string,
+                 lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = STRING_CELL;
+    cell->format = format;
+    cell->u.string_id = string_id;
+    cell->sst_string = sst_string;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet inline_string cell object.
+ */
+STATIC lxw_cell *
+_new_inline_string_cell(lxw_row_t row_num,
+                        lxw_col_t col_num, char *string, lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = INLINE_STRING_CELL;
+    cell->format = format;
+    cell->u.string = string;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet formula cell object.
+ */
+STATIC lxw_cell *
+_new_formula_cell(lxw_row_t row_num,
+                  lxw_col_t col_num, char *formula, lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = FORMULA_CELL;
+    cell->format = format;
+    cell->u.string = formula;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet array formula cell object.
+ */
+STATIC lxw_cell *
+_new_array_formula_cell(lxw_row_t row_num, lxw_col_t col_num, char *formula,
+                        char *range, lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = ARRAY_FORMULA_CELL;
+    cell->format = format;
+    cell->u.string = formula;
+    cell->user_data1 = range;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet blank cell object.
+ */
+STATIC lxw_cell *
+_new_blank_cell(lxw_row_t row_num, lxw_col_t col_num, lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = BLANK_CELL;
+    cell->format = format;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet boolean cell object.
+ */
+STATIC lxw_cell *
+_new_boolean_cell(lxw_row_t row_num, lxw_col_t col_num, int value,
+                  lxw_format *format)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = BOOLEAN_CELL;
+    cell->format = format;
+    cell->u.number = value;
+
+    return cell;
+}
+
+/*
+ * Create a new worksheet hyperlink cell object.
+ */
+STATIC lxw_cell *
+_new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num,
+                    enum cell_types link_type, char *url, char *string,
+                    char *tooltip)
+{
+    lxw_cell *cell = calloc(1, sizeof(lxw_cell));
+    RETURN_ON_MEM_ERROR(cell, cell);
+
+    cell->row_num = row_num;
+    cell->col_num = col_num;
+    cell->type = link_type;
+    cell->u.string = url;
+    cell->user_data1 = string;
+    cell->user_data2 = tooltip;
+
+    return cell;
+}
+
+/*
+ * Get or create the row object for a given row number.
+ */
+STATIC lxw_row *
+_get_row_list(struct lxw_table_rows *table, lxw_row_t row_num)
+{
+    lxw_row *row;
+    lxw_row *existing_row;
+
+    if (table->cached_row_num == row_num)
+        return table->cached_row;
+
+    /* Create a new row and try and insert it. */
+    row = _new_row(row_num);
+    existing_row = RB_INSERT(lxw_table_rows, table, row);
+
+    /* If existing_row is not NULL, then it already existed. Free new row */
+    /* and return existing_row. */
+    if (existing_row) {
+        _free_row(row);
+        row = existing_row;
+    }
+
+    table->cached_row = row;
+    table->cached_row_num = row_num;
+
+    return row;
+}
+
+/*
+ * Get or create the row object for a given row number.
+ */
+STATIC lxw_row *
+_get_row(lxw_worksheet *self, lxw_row_t row_num)
+{
+    lxw_row *row;
+
+    if (!self->optimize) {
+        row = _get_row_list(self->table, row_num);
+        return row;
+    }
+    else {
+        if (row_num < self->optimize_row->row_num) {
+            return NULL;
+        }
+        else if (row_num == self->optimize_row->row_num) {
+            return self->optimize_row;
+        }
+        else {
+            /* Flush row. */
+            lxw_worksheet_write_single_row(self);
+            row = self->optimize_row;
+            row->row_num = row_num;
+            return row;
+        }
+    }
+}
+
+/*
+ * Insert a cell object in the cell list of a row object.
+ */
+STATIC void
+_insert_cell_list(struct lxw_table_cells *cell_list,
+                  lxw_cell *cell, lxw_col_t col_num)
+{
+    lxw_cell *existing_cell;
+
+    cell->col_num = col_num;
+
+    existing_cell = RB_INSERT(lxw_table_cells, cell_list, cell);
+
+    /* If existing_cell is not NULL, then that cell already existed. */
+    /* Remove existing_cell and add new one in again. */
+    if (existing_cell) {
+        RB_REMOVE(lxw_table_cells, cell_list, existing_cell);
+
+        /* Add it in again. */
+        RB_INSERT(lxw_table_cells, cell_list, cell);
+        _free_cell(existing_cell);
+    }
+
+    return;
+}
+
+/*
+ * Insert a cell object into the cell list or array.
+ */
+STATIC void
+_insert_cell(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
+             lxw_cell *cell)
+{
+    lxw_row *row = _get_row(self, row_num);
+
+    if (!self->optimize) {
+        row->data_changed = LXW_TRUE;
+        _insert_cell_list(row->cells, cell, col_num);
+    }
+    else {
+        if (row) {
+            row->data_changed = LXW_TRUE;
+
+            /* Overwrite an existing cell if necessary. */
+            if (self->array[col_num])
+                _free_cell(self->array[col_num]);
+
+            self->array[col_num] = cell;
+        }
+    }
+}
+
+/*
+ * Insert a hyperlink object into the hyperlink list.
+ */
+STATIC void
+_insert_hyperlink(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
+                  lxw_cell *link)
+{
+    lxw_row *row = _get_row_list(self->hyperlinks, row_num);
+
+    _insert_cell_list(row->cells, link, col_num);
+}
+
+/*
+ * Next power of two for column reallocs. Taken from bithacks in the public
+ * domain.
+ */
+STATIC lxw_col_t
+_next_power_of_two(uint16_t col)
+{
+    col--;
+    col |= col >> 1;
+    col |= col >> 2;
+    col |= col >> 4;
+    col |= col >> 8;
+    col++;
+
+    return col;
+}
+
+/*
+ * Check that row and col are within the allowed Excel range and store max
+ * and min values for use in other methods/elements.
+ *
+ * The ignore_row/ignore_col flags are used to indicate that we wish to
+ * perform the dimension check without storing the value.
+ */
+STATIC lxw_error
+_check_dimensions(lxw_worksheet *self,
+                  lxw_row_t row_num,
+                  lxw_col_t col_num, int8_t ignore_row, int8_t ignore_col)
+{
+    if (row_num >= LXW_ROW_MAX)
+        return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
+
+    if (col_num >= LXW_COL_MAX)
+        return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
+
+    /* In optimization mode we don't change dimensions for rows that are */
+    /* already written. */
+    if (!ignore_row && !ignore_col && self->optimize) {
+        if (row_num < self->optimize_row->row_num)
+            return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
+    }
+
+    if (!ignore_row) {
+        if (row_num < self->dim_rowmin)
+            self->dim_rowmin = row_num;
+        if (row_num > self->dim_rowmax)
+            self->dim_rowmax = row_num;
+    }
+
+    if (!ignore_col) {
+        if (col_num < self->dim_colmin)
+            self->dim_colmin = col_num;
+        if (col_num > self->dim_colmax)
+            self->dim_colmax = col_num;
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Comparator for the row structure red/black tree.
+ */
+STATIC int
+_row_cmp(lxw_row *row1, lxw_row *row2)
+{
+    if (row1->row_num > row2->row_num)
+        return 1;
+    if (row1->row_num < row2->row_num)
+        return -1;
+    return 0;
+}
+
+/*
+ * Comparator for the cell structure red/black tree.
+ */
+STATIC int
+_cell_cmp(lxw_cell *cell1, lxw_cell *cell2)
+{
+    if (cell1->col_num > cell2->col_num)
+        return 1;
+    if (cell1->col_num < cell2->col_num)
+        return -1;
+    return 0;
+}
+
+/*
+ * Hash a worksheet password. Based on the algorithm provided by Daniel Rentz
+ * of OpenOffice.
+ */
+STATIC uint16_t
+_hash_password(const char *password)
+{
+    size_t count;
+    uint8_t i;
+    uint16_t hash = 0x0000;
+
+    count = strlen(password);
+
+    for (i = 0; i < count; i++) {
+        uint32_t low_15;
+        uint32_t high_15;
+        uint32_t letter = password[i] << (i + 1);
+
+        low_15 = letter & 0x7fff;
+        high_15 = letter & (0x7fff << 15);
+        high_15 = high_15 >> 15;
+        letter = low_15 | high_15;
+
+        hash ^= letter;
+    }
+
+    hash ^= count;
+    hash ^= 0xCE4B;
+
+    return hash;
+}
+
+/*
+ * Simple replacement for libgen.h basename() for compatibility with MSVC. It
+ * handles forward and back slashes. It doesn't copy exactly the return
+ * format of basename().
+ */
+char *
+lxw_basename(const char *path)
+{
+
+    char *forward_slash;
+    char *back_slash;
+
+    if (!path)
+        return NULL;
+
+    forward_slash = strrchr(path, '/');
+    back_slash = strrchr(path, '\\');
+
+    if (!forward_slash && !back_slash)
+        return (char *) path;
+
+    if (forward_slash > back_slash)
+        return forward_slash + 1;
+    else
+        return back_slash + 1;
+}
+
+/* Function to count the total concatenated length of the strings in a
+ * validation list array, including commas. */
+size_t
+_validation_list_length(char **list)
+{
+    uint8_t i = 0;
+    size_t length = 0;
+
+    if (!list || !list[0])
+        return 0;
+
+    while (list[i] && length <= LXW_VALIDATION_MAX_STRING_LENGTH) {
+        /* Include commas in the length. */
+        length += 1 + lxw_utf8_strlen(list[i]);
+        i++;
+    }
+
+    /* Adjust the count for extraneous comma at end. */
+    length--;
+
+    return length;
+}
+
+/* Function to convert an array of strings into a CSV string for data
+ * validation lists. */
+char *
+_validation_list_to_csv(char **list)
+{
+    uint8_t i = 0;
+    char *str;
+
+    /* Create a buffer for the concatenated, and quoted, string. */
+    /* Add +3 for quotes and EOL. */
+    str = calloc(1, LXW_VALIDATION_MAX_STRING_LENGTH + 3);
+    if (!str)
+        return NULL;
+
+    /* Add the start quote and first element. */
+    strcat(str, "\"");
+    strcat(str, list[0]);
+
+    /* Add the other elements preceded by a comma. */
+    i = 1;
+    while (list[i]) {
+        strcat(str, ",");
+        strcat(str, list[i]);
+        i++;
+    }
+
+    /* Add the end quote. */
+    strcat(str, "\"");
+
+    return str;
+}
+
+/*****************************************************************************
+ *
+ * XML functions.
+ *
+ ****************************************************************************/
+/*
+ * Write the XML declaration.
+ */
+STATIC void
+_worksheet_xml_declaration(lxw_worksheet *self)
+{
+    lxw_xml_declaration(self->file);
+}
+
+/*
+ * Write the <worksheet> element.
+ */
+STATIC void
+_worksheet_write_worksheet(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char xmlns[] = "http://schemas.openxmlformats.org/"
+        "spreadsheetml/2006/main";
+    char xmlns_r[] = "http://schemas.openxmlformats.org/"
+        "officeDocument/2006/relationships";
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
+    LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
+
+    lxw_xml_start_tag(self->file, "worksheet", &attributes);
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dimension> element.
+ */
+STATIC void
+_worksheet_write_dimension(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char ref[LXW_MAX_CELL_RANGE_LENGTH];
+    lxw_row_t dim_rowmin = self->dim_rowmin;
+    lxw_row_t dim_rowmax = self->dim_rowmax;
+    lxw_col_t dim_colmin = self->dim_colmin;
+    lxw_col_t dim_colmax = self->dim_colmax;
+
+    if (dim_rowmin == LXW_ROW_MAX && dim_colmin == LXW_COL_MAX) {
+        /* If the rows and cols are still the defaults then no dimensions have
+         * been set and we use the default range "A1". */
+        lxw_rowcol_to_range(ref, 0, 0, 0, 0);
+    }
+    else if (dim_rowmin == LXW_ROW_MAX && dim_colmin != LXW_COL_MAX) {
+        /* If the rows aren't set but the columns are then the dimensions have
+         * been changed via set_column(). */
+        lxw_rowcol_to_range(ref, 0, dim_colmin, 0, dim_colmax);
+    }
+    else {
+        lxw_rowcol_to_range(ref, dim_rowmin, dim_colmin, dim_rowmax,
+                            dim_colmax);
+    }
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("ref", ref);
+
+    lxw_xml_empty_tag(self->file, "dimension", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <pane> element for freeze panes.
+ */
+STATIC void
+_worksheet_write_freeze_panes(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    lxw_selection *selection;
+    lxw_selection *user_selection;
+    lxw_row_t row = self->panes.first_row;
+    lxw_col_t col = self->panes.first_col;
+    lxw_row_t top_row = self->panes.top_row;
+    lxw_col_t left_col = self->panes.left_col;
+
+    char row_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char col_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char top_left_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char active_pane[LXW_PANE_NAME_LENGTH];
+
+    /* If there is a user selection we remove it from the list and use it. */
+    if (!STAILQ_EMPTY(self->selections)) {
+        user_selection = STAILQ_FIRST(self->selections);
+        STAILQ_REMOVE_HEAD(self->selections, list_pointers);
+    }
+    else {
+        /* or else create a new blank selection. */
+        user_selection = calloc(1, sizeof(lxw_selection));
+        RETURN_VOID_ON_MEM_ERROR(user_selection);
+    }
+
+    LXW_INIT_ATTRIBUTES();
+
+    lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
+
+    /* Set the active pane. */
+    if (row && col) {
+        lxw_strcpy(active_pane, "bottomRight");
+
+        lxw_rowcol_to_cell(row_cell, row, 0);
+        lxw_rowcol_to_cell(col_cell, 0, col);
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "topRight");
+            lxw_strcpy(selection->active_cell, col_cell);
+            lxw_strcpy(selection->sqref, col_cell);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomLeft");
+            lxw_strcpy(selection->active_cell, row_cell);
+            lxw_strcpy(selection->sqref, row_cell);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomRight");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+    else if (col) {
+        lxw_strcpy(active_pane, "topRight");
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "topRight");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+    else {
+        lxw_strcpy(active_pane, "bottomLeft");
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomLeft");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+
+    if (col)
+        LXW_PUSH_ATTRIBUTES_INT("xSplit", col);
+
+    if (row)
+        LXW_PUSH_ATTRIBUTES_INT("ySplit", row);
+
+    LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
+    LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane);
+
+    if (self->panes.type == FREEZE_PANES)
+        LXW_PUSH_ATTRIBUTES_STR("state", "frozen");
+    else if (self->panes.type == FREEZE_SPLIT_PANES)
+        LXW_PUSH_ATTRIBUTES_STR("state", "frozenSplit");
+
+    lxw_xml_empty_tag(self->file, "pane", &attributes);
+
+    free(user_selection);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Convert column width from user units to pane split width.
+ */
+STATIC uint32_t
+_worksheet_calculate_x_split_width(double x_split)
+{
+    uint32_t width;
+    uint32_t pixels;
+    uint32_t points;
+    uint32_t twips;
+    double max_digit_width = 7.0;       /* For Calabri 11. */
+    double padding = 5.0;
+
+    /* Convert to pixels. */
+    if (x_split < 1.0) {
+        pixels = (uint32_t) (x_split * (max_digit_width + padding) + 0.5);
+    }
+    else {
+        pixels = (uint32_t) (x_split * max_digit_width + 0.5) + 5;
+    }
+
+    /* Convert to points. */
+    points = (pixels * 3) / 4;
+
+    /* Convert to twips (twentieths of a point). */
+    twips = points * 20;
+
+    /* Add offset/padding. */
+    width = twips + 390;
+
+    return width;
+}
+
+/*
+ * Write the <pane> element for split panes.
+ */
+STATIC void
+_worksheet_write_split_panes(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    lxw_selection *selection;
+    lxw_selection *user_selection;
+    lxw_row_t row = self->panes.first_row;
+    lxw_col_t col = self->panes.first_col;
+    lxw_row_t top_row = self->panes.top_row;
+    lxw_col_t left_col = self->panes.left_col;
+    double x_split = self->panes.x_split;
+    double y_split = self->panes.y_split;
+    uint8_t has_selection = LXW_FALSE;
+
+    char row_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char col_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char top_left_cell[LXW_MAX_CELL_NAME_LENGTH];
+    char active_pane[LXW_PANE_NAME_LENGTH];
+
+    /* If there is a user selection we remove it from the list and use it. */
+    if (!STAILQ_EMPTY(self->selections)) {
+        user_selection = STAILQ_FIRST(self->selections);
+        STAILQ_REMOVE_HEAD(self->selections, list_pointers);
+        has_selection = LXW_TRUE;
+    }
+    else {
+        /* or else create a new blank selection. */
+        user_selection = calloc(1, sizeof(lxw_selection));
+        RETURN_VOID_ON_MEM_ERROR(user_selection);
+    }
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Convert the row and col to 1/20 twip units with padding. */
+    if (y_split > 0.0)
+        y_split = (uint32_t) (20 * y_split + 300);
+
+    if (x_split > 0.0)
+        x_split = _worksheet_calculate_x_split_width(x_split);
+
+    /* For non-explicit topLeft definitions, estimate the cell offset based on
+     * the pixels dimensions. This is only a workaround and doesn't take
+     * adjusted cell dimensions into account.
+     */
+    if (top_row == row && left_col == col) {
+        top_row = (lxw_row_t) (0.5 + (y_split - 300.0) / 20.0 / 15.0);
+        left_col = (lxw_col_t) (0.5 + (x_split - 390.0) / 20.0 / 3.0 / 16.0);
+    }
+
+    lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
+
+    /* If there is no selection set the active cell to the top left cell. */
+    if (!has_selection) {
+        lxw_strcpy(user_selection->active_cell, top_left_cell);
+        lxw_strcpy(user_selection->sqref, top_left_cell);
+    }
+
+    /* Set the active pane. */
+    if (y_split > 0.0 && x_split > 0.0) {
+        lxw_strcpy(active_pane, "bottomRight");
+
+        lxw_rowcol_to_cell(row_cell, top_row, 0);
+        lxw_rowcol_to_cell(col_cell, 0, left_col);
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "topRight");
+            lxw_strcpy(selection->active_cell, col_cell);
+            lxw_strcpy(selection->sqref, col_cell);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomLeft");
+            lxw_strcpy(selection->active_cell, row_cell);
+            lxw_strcpy(selection->sqref, row_cell);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomRight");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+    else if (x_split > 0.0) {
+        lxw_strcpy(active_pane, "topRight");
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "topRight");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+    else {
+        lxw_strcpy(active_pane, "bottomLeft");
+
+        selection = calloc(1, sizeof(lxw_selection));
+        if (selection) {
+            lxw_strcpy(selection->pane, "bottomLeft");
+            lxw_strcpy(selection->active_cell, user_selection->active_cell);
+            lxw_strcpy(selection->sqref, user_selection->sqref);
+
+            STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+        }
+    }
+
+    if (x_split > 0.0)
+        LXW_PUSH_ATTRIBUTES_DBL("xSplit", x_split);
+
+    if (y_split > 0.0)
+        LXW_PUSH_ATTRIBUTES_DBL("ySplit", y_split);
+
+    LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
+
+    if (has_selection)
+        LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane);
+
+    lxw_xml_empty_tag(self->file, "pane", &attributes);
+
+    free(user_selection);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <selection> element.
+ */
+STATIC void
+_worksheet_write_selection(lxw_worksheet *self, lxw_selection *selection)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (*selection->pane)
+        LXW_PUSH_ATTRIBUTES_STR("pane", selection->pane);
+
+    if (*selection->active_cell)
+        LXW_PUSH_ATTRIBUTES_STR("activeCell", selection->active_cell);
+
+    if (*selection->sqref)
+        LXW_PUSH_ATTRIBUTES_STR("sqref", selection->sqref);
+
+    lxw_xml_empty_tag(self->file, "selection", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <selection> elements.
+ */
+STATIC void
+_worksheet_write_selections(lxw_worksheet *self)
+{
+    lxw_selection *selection;
+
+    STAILQ_FOREACH(selection, self->selections, list_pointers) {
+        _worksheet_write_selection(self, selection);
+    }
+}
+
+/*
+ * Write the frozen or split <pane> elements.
+ */
+STATIC void
+_worksheet_write_panes(lxw_worksheet *self)
+{
+    if (self->panes.type == NO_PANES)
+        return;
+
+    else if (self->panes.type == FREEZE_PANES)
+        _worksheet_write_freeze_panes(self);
+
+    else if (self->panes.type == FREEZE_SPLIT_PANES)
+        _worksheet_write_freeze_panes(self);
+
+    else if (self->panes.type == SPLIT_PANES)
+        _worksheet_write_split_panes(self);
+}
+
+/*
+ * Write the <sheetView> element.
+ */
+STATIC void
+_worksheet_write_sheet_view(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Hide screen gridlines if required */
+    if (!self->screen_gridlines)
+        LXW_PUSH_ATTRIBUTES_STR("showGridLines", "0");
+
+    /* Hide zeroes in cells. */
+    if (!self->show_zeros) {
+        LXW_PUSH_ATTRIBUTES_STR("showZeros", "0");
+    }
+
+    /* Display worksheet right to left for Hebrew, Arabic and others. */
+    if (self->right_to_left) {
+        LXW_PUSH_ATTRIBUTES_STR("rightToLeft", "1");
+    }
+
+    /* Show that the sheet tab is selected. */
+    if (self->selected)
+        LXW_PUSH_ATTRIBUTES_STR("tabSelected", "1");
+
+    /* Turn outlines off. Also required in the outlinePr element. */
+    if (!self->outline_on) {
+        LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0");
+    }
+
+    /* Set the page view/layout mode if required. */
+    if (self->page_view)
+        LXW_PUSH_ATTRIBUTES_STR("view", "pageLayout");
+
+    /* Set the zoom level. */
+    if (self->zoom != 100) {
+        if (!self->page_view) {
+            LXW_PUSH_ATTRIBUTES_INT("zoomScale", self->zoom);
+
+            if (self->zoom_scale_normal)
+                LXW_PUSH_ATTRIBUTES_INT("zoomScaleNormal", self->zoom);
+        }
+    }
+
+    LXW_PUSH_ATTRIBUTES_STR("workbookViewId", "0");
+
+    if (self->panes.type != NO_PANES || !STAILQ_EMPTY(self->selections)) {
+        lxw_xml_start_tag(self->file, "sheetView", &attributes);
+        _worksheet_write_panes(self);
+        _worksheet_write_selections(self);
+        lxw_xml_end_tag(self->file, "sheetView");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "sheetView", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <sheetViews> element.
+ */
+STATIC void
+_worksheet_write_sheet_views(lxw_worksheet *self)
+{
+    lxw_xml_start_tag(self->file, "sheetViews", NULL);
+
+    /* Write the sheetView element. */
+    _worksheet_write_sheet_view(self);
+
+    lxw_xml_end_tag(self->file, "sheetViews");
+}
+
+/*
+ * Write the <sheetFormatPr> element.
+ */
+STATIC void
+_worksheet_write_sheet_format_pr(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("defaultRowHeight", self->default_row_height);
+
+    if (self->default_row_height != LXW_DEF_ROW_HEIGHT)
+        LXW_PUSH_ATTRIBUTES_STR("customHeight", "1");
+
+    if (self->default_row_zeroed)
+        LXW_PUSH_ATTRIBUTES_STR("zeroHeight", "1");
+
+    if (self->outline_row_level)
+        LXW_PUSH_ATTRIBUTES_INT("outlineLevelRow", self->outline_row_level);
+
+    if (self->outline_col_level)
+        LXW_PUSH_ATTRIBUTES_INT("outlineLevelCol", self->outline_col_level);
+
+    lxw_xml_empty_tag(self->file, "sheetFormatPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <sheetData> element.
+ */
+STATIC void
+_worksheet_write_sheet_data(lxw_worksheet *self)
+{
+    if (RB_EMPTY(self->table)) {
+        lxw_xml_empty_tag(self->file, "sheetData", NULL);
+    }
+    else {
+        lxw_xml_start_tag(self->file, "sheetData", NULL);
+        _worksheet_write_rows(self);
+        lxw_xml_end_tag(self->file, "sheetData");
+    }
+}
+
+/*
+ * Write the <sheetData> element when the memory optimization is on. In which
+ * case we read the data stored in the temp file and rewrite it to the XML
+ * sheet file.
+ */
+STATIC void
+_worksheet_write_optimized_sheet_data(lxw_worksheet *self)
+{
+    size_t read_size = 1;
+    char buffer[LXW_BUFFER_SIZE];
+
+    if (self->dim_rowmin == LXW_ROW_MAX) {
+        /* If the dimensions aren't defined then there is no data to write. */
+        lxw_xml_empty_tag(self->file, "sheetData", NULL);
+    }
+    else {
+
+        lxw_xml_start_tag(self->file, "sheetData", NULL);
+
+        /* Flush and rewind the temp file. */
+        fflush(self->optimize_tmpfile);
+        rewind(self->optimize_tmpfile);
+
+        while (read_size) {
+            read_size =
+                fread(buffer, 1, LXW_BUFFER_SIZE, self->optimize_tmpfile);
+            fwrite(buffer, 1, read_size, self->file);
+        }
+
+        fclose(self->optimize_tmpfile);
+
+        lxw_xml_end_tag(self->file, "sheetData");
+    }
+}
+
+/*
+ * Write the <pageMargins> element.
+ */
+STATIC void
+_worksheet_write_page_margins(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    double left = self->margin_left;
+    double right = self->margin_right;
+    double top = self->margin_top;
+    double bottom = self->margin_bottom;
+    double header = self->margin_header;
+    double footer = self->margin_footer;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_DBL("left", left);
+    LXW_PUSH_ATTRIBUTES_DBL("right", right);
+    LXW_PUSH_ATTRIBUTES_DBL("top", top);
+    LXW_PUSH_ATTRIBUTES_DBL("bottom", bottom);
+    LXW_PUSH_ATTRIBUTES_DBL("header", header);
+    LXW_PUSH_ATTRIBUTES_DBL("footer", footer);
+
+    lxw_xml_empty_tag(self->file, "pageMargins", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <pageSetup> element.
+ * The following is an example taken from Excel.
+ * <pageSetup
+ *     paperSize="9"
+ *     scale="110"
+ *     fitToWidth="2"
+ *     fitToHeight="2"
+ *     pageOrder="overThenDown"
+ *     orientation="portrait"
+ *     blackAndWhite="1"
+ *     draft="1"
+ *     horizontalDpi="200"
+ *     verticalDpi="200"
+ *     r:id="rId1"
+ * />
+ */
+STATIC void
+_worksheet_write_page_setup(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (!self->page_setup_changed)
+        return;
+
+    /* Set paper size. */
+    if (self->paper_size)
+        LXW_PUSH_ATTRIBUTES_INT("paperSize", self->paper_size);
+
+    /* Set the print_scale. */
+    if (self->print_scale != 100)
+        LXW_PUSH_ATTRIBUTES_INT("scale", self->print_scale);
+
+    /* Set the "Fit to page" properties. */
+    if (self->fit_page && self->fit_width != 1)
+        LXW_PUSH_ATTRIBUTES_INT("fitToWidth", self->fit_width);
+
+    if (self->fit_page && self->fit_height != 1)
+        LXW_PUSH_ATTRIBUTES_INT("fitToHeight", self->fit_height);
+
+    /* Set the page print direction. */
+    if (self->page_order)
+        LXW_PUSH_ATTRIBUTES_STR("pageOrder", "overThenDown");
+
+    /* Set start page. */
+    if (self->page_start > 1)
+        LXW_PUSH_ATTRIBUTES_INT("firstPageNumber", self->page_start);
+
+    /* Set page orientation. */
+    if (self->orientation)
+        LXW_PUSH_ATTRIBUTES_STR("orientation", "portrait");
+    else
+        LXW_PUSH_ATTRIBUTES_STR("orientation", "landscape");
+
+    /* Set start page active flag. */
+    if (self->page_start)
+        LXW_PUSH_ATTRIBUTES_INT("useFirstPageNumber", 1);
+
+    /* Set the DPI. Mainly only for testing. */
+    if (self->horizontal_dpi)
+        LXW_PUSH_ATTRIBUTES_INT("horizontalDpi", self->horizontal_dpi);
+
+    if (self->vertical_dpi)
+        LXW_PUSH_ATTRIBUTES_INT("verticalDpi", self->vertical_dpi);
+
+    lxw_xml_empty_tag(self->file, "pageSetup", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <printOptions> element.
+ */
+STATIC void
+_worksheet_write_print_options(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    if (!self->print_options_changed)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Set horizontal centering. */
+    if (self->hcenter) {
+        LXW_PUSH_ATTRIBUTES_STR("horizontalCentered", "1");
+    }
+
+    /* Set vertical centering. */
+    if (self->vcenter) {
+        LXW_PUSH_ATTRIBUTES_STR("verticalCentered", "1");
+    }
+
+    /* Enable row and column headers. */
+    if (self->print_headers) {
+        LXW_PUSH_ATTRIBUTES_STR("headings", "1");
+    }
+
+    /* Set printed gridlines. */
+    if (self->print_gridlines) {
+        LXW_PUSH_ATTRIBUTES_STR("gridLines", "1");
+    }
+
+    lxw_xml_empty_tag(self->file, "printOptions", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <row> element.
+ */
+STATIC void
+_write_row(lxw_worksheet *self, lxw_row *row, char *spans)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    int32_t xf_index = 0;
+    double height;
+
+    if (row->format) {
+        xf_index = lxw_format_get_xf_index(row->format);
+    }
+
+    if (row->height_changed)
+        height = row->height;
+    else
+        height = self->default_row_height;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("r", row->row_num + 1);
+
+    if (spans)
+        LXW_PUSH_ATTRIBUTES_STR("spans", spans);
+
+    if (xf_index)
+        LXW_PUSH_ATTRIBUTES_INT("s", xf_index);
+
+    if (row->format)
+        LXW_PUSH_ATTRIBUTES_STR("customFormat", "1");
+
+    if (height != LXW_DEF_ROW_HEIGHT)
+        LXW_PUSH_ATTRIBUTES_DBL("ht", height);
+
+    if (row->hidden)
+        LXW_PUSH_ATTRIBUTES_STR("hidden", "1");
+
+    if (height != LXW_DEF_ROW_HEIGHT)
+        LXW_PUSH_ATTRIBUTES_STR("customHeight", "1");
+
+    if (row->level)
+        LXW_PUSH_ATTRIBUTES_INT("outlineLevel", row->level);
+
+    if (row->collapsed)
+        LXW_PUSH_ATTRIBUTES_STR("collapsed", "1");
+
+    if (!row->data_changed)
+        lxw_xml_empty_tag(self->file, "row", &attributes);
+    else
+        lxw_xml_start_tag(self->file, "row", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Convert the width of a cell from user's units to pixels. Excel rounds the
+ * column width to the nearest pixel. If the width hasn't been set by the user
+ * we use the default value. If the column is hidden it has a value of zero.
+ */
+STATIC int32_t
+_worksheet_size_col(lxw_worksheet *self, lxw_col_t col_num)
+{
+    lxw_col_options *col_opt = NULL;
+    uint32_t pixels;
+    double width;
+    double max_digit_width = 7.0;       /* For Calabri 11. */
+    double padding = 5.0;
+    lxw_col_t col_index;
+
+    /* Search for the col number in the array of col_options. Each col_option
+     * entry contains the start and end column for a range.
+     */
+    for (col_index = 0; col_index < self->col_options_max; col_index++) {
+        col_opt = self->col_options[col_index];
+
+        if (col_opt) {
+            if (col_num >= col_opt->firstcol && col_num <= col_opt->lastcol)
+                break;
+            else
+                col_opt = NULL;
+        }
+    }
+
+    if (col_opt) {
+        width = col_opt->width;
+
+        /* Convert to pixels. */
+        if (width == 0) {
+            pixels = 0;
+        }
+        else if (width < 1.0) {
+            pixels = (uint32_t) (width * (max_digit_width + padding) + 0.5);
+        }
+        else {
+            pixels = (uint32_t) (width * max_digit_width + 0.5) + 5;
+        }
+    }
+    else {
+        pixels = self->default_col_pixels;
+    }
+
+    return pixels;
+}
+
+/*
+ * Convert the height of a cell from user's units to pixels. If the height
+ * hasn't been set by the user we use the default value. If the row is hidden
+ * it has a value of zero.
+ */
+STATIC int32_t
+_worksheet_size_row(lxw_worksheet *self, lxw_row_t row_num)
+{
+    lxw_row *row;
+    uint32_t pixels;
+    double height;
+
+    row = lxw_worksheet_find_row(self, row_num);
+
+    if (row) {
+        height = row->height;
+
+        if (height == 0)
+            pixels = 0;
+        else
+            pixels = (uint32_t) (4.0 / 3.0 * height);
+    }
+    else {
+        pixels = (uint32_t) (4.0 / 3.0 * self->default_row_height);
+    }
+
+    return pixels;
+}
+
+/*
+ * Calculate the vertices that define the position of a graphical object
+ * within the worksheet in pixels.
+ *         +------------+------------+
+ *         |     A      |      B     |
+ *   +-----+------------+------------+
+ *   |     |(x1,y1)     |            |
+ *   |  1  |(A1)._______|______      |
+ *   |     |    |              |     |
+ *   |     |    |              |     |
+ *   +-----+----|    BITMAP    |-----+
+ *   |     |    |              |     |
+ *   |  2  |    |______________.     |
+ *   |     |            |        (B2)|
+ *   |     |            |     (x2,y2)|
+ *   +---- +------------+------------+
+ *
+ * Example of an object that covers some of the area from cell A1 to cell B2.
+ * Based on the width and height of the object we need to calculate 8 vars:
+ *
+ *     col_start, row_start, col_end, row_end, x1, y1, x2, y2.
+ *
+ * We also calculate the absolute x and y position of the top left vertex of
+ * the object. This is required for images:
+ *
+ *    x_abs, y_abs
+ *
+ * The width and height of the cells that the object occupies can be variable
+ * and have to be taken into account.
+ *
+ * The values of col_start and row_start are passed in from the calling
+ * function. The values of col_end and row_end are calculated by subtracting
+ * the width and height of the object from the width and height of the
+ * underlying cells.
+ */
+STATIC void
+_worksheet_position_object_pixels(lxw_worksheet *self,
+                                  lxw_image_options *image,
+                                  lxw_drawing_object *drawing_object)
+{
+    lxw_col_t col_start;        /* Column containing upper left corner.  */
+    int32_t x1;                 /* Distance to left side of object.      */
+
+    lxw_row_t row_start;        /* Row containing top left corner.       */
+    int32_t y1;                 /* Distance to top of object.            */
+
+    lxw_col_t col_end;          /* Column containing lower right corner. */
+    double x2;                  /* Distance to right side of object.     */
+
+    lxw_row_t row_end;          /* Row containing bottom right corner.   */
+    double y2;                  /* Distance to bottom of object.         */
+
+    double width;               /* Width of object frame.                */
+    double height;              /* Height of object frame.               */
+
+    uint32_t x_abs = 0;         /* Abs. distance to left side of object. */
+    uint32_t y_abs = 0;         /* Abs. distance to top  side of object. */
+
+    uint32_t i;
+
+    col_start = image->col;
+    row_start = image->row;
+    x1 = image->x_offset;
+    y1 = image->y_offset;
+    width = image->width;
+    height = image->height;
+
+    /* Adjust start column for negative offsets. */
+    while (x1 < 0 && col_start > 0) {
+        x1 += _worksheet_size_col(self, col_start - 1);
+        col_start--;
+    }
+
+    /* Adjust start row for negative offsets. */
+    while (y1 < 0 && row_start > 0) {
+        y1 += _worksheet_size_row(self, row_start - 1);
+        row_start--;
+    }
+
+    /* Ensure that the image isn't shifted off the page at top left. */
+    if (x1 < 0)
+        x1 = 0;
+
+    if (y1 < 0)
+        y1 = 0;
+
+    /* Calculate the absolute x offset of the top-left vertex. */
+    if (self->col_size_changed) {
+        for (i = 0; i < col_start; i++)
+            x_abs += _worksheet_size_col(self, i);
+    }
+    else {
+        /* Optimization for when the column widths haven't changed. */
+        x_abs += self->default_col_pixels * col_start;
+    }
+
+    x_abs += x1;
+
+    /* Calculate the absolute y offset of the top-left vertex. */
+    /* Store the column change to allow optimizations. */
+    if (self->row_size_changed) {
+        for (i = 0; i < row_start; i++)
+            y_abs += _worksheet_size_row(self, i);
+    }
+    else {
+        /* Optimization for when the row heights haven"t changed. */
+        y_abs += self->default_row_pixels * row_start;
+    }
+
+    y_abs += y1;
+
+    /* Adjust start col for offsets that are greater than the col width. */
+    while (x1 >= _worksheet_size_col(self, col_start)) {
+        x1 -= _worksheet_size_col(self, col_start);
+        col_start++;
+    }
+
+    /* Adjust start row for offsets that are greater than the row height. */
+    while (y1 >= _worksheet_size_row(self, row_start)) {
+        y1 -= _worksheet_size_row(self, row_start);
+        row_start++;
+    }
+
+    /* Initialize end cell to the same as the start cell. */
+    col_end = col_start;
+    row_end = row_start;
+
+    width = width + x1;
+    height = height + y1;
+
+    /* Subtract the underlying cell widths to find the end cell. */
+    while (width >= _worksheet_size_col(self, col_end)) {
+        width -= _worksheet_size_col(self, col_end);
+        col_end++;
+    }
+
+    /* Subtract the underlying cell heights to find the end cell. */
+    while (height >= _worksheet_size_row(self, row_end)) {
+        height -= _worksheet_size_row(self, row_end);
+        row_end++;
+    }
+
+    /* The end vertices are whatever is left from the width and height. */
+    x2 = width;
+    y2 = height;
+
+    /* Add the dimensions to the drawing object. */
+    drawing_object->from.col = col_start;
+    drawing_object->from.row = row_start;
+    drawing_object->from.col_offset = x1;
+    drawing_object->from.row_offset = y1;
+    drawing_object->to.col = col_end;
+    drawing_object->to.row = row_end;
+    drawing_object->to.col_offset = x2;
+    drawing_object->to.row_offset = y2;
+    drawing_object->col_absolute = x_abs;
+    drawing_object->row_absolute = y_abs;
+
+}
+
+/*
+ * Calculate the vertices that define the position of a graphical object
+ * within the worksheet in EMUs. The vertices are expressed as English
+ * Metric Units (EMUs). There are 12,700 EMUs per point.
+ * Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel.
+ */
+STATIC void
+_worksheet_position_object_emus(lxw_worksheet *self,
+                                lxw_image_options *image,
+                                lxw_drawing_object *drawing_object)
+{
+
+    _worksheet_position_object_pixels(self, image, drawing_object);
+
+    /* Convert the pixel values to EMUs. See above. */
+    drawing_object->from.col_offset *= 9525;
+    drawing_object->from.row_offset *= 9525;
+    drawing_object->to.col_offset *= 9525;
+    drawing_object->to.row_offset *= 9525;
+    drawing_object->to.col_offset += 0.5;
+    drawing_object->to.row_offset += 0.5;
+    drawing_object->col_absolute *= 9525;
+    drawing_object->row_absolute *= 9525;
+}
+
+/*
+ * Set up image/drawings.
+ */
+void
+lxw_worksheet_prepare_image(lxw_worksheet *self,
+                            uint16_t image_ref_id, uint16_t drawing_id,
+                            lxw_image_options *image_data)
+{
+    lxw_drawing_object *drawing_object;
+    lxw_rel_tuple *relationship;
+    double width;
+    double height;
+    char filename[LXW_FILENAME_LENGTH];
+
+    if (!self->drawing) {
+        self->drawing = lxw_drawing_new();
+        self->drawing->embedded = LXW_TRUE;
+        RETURN_VOID_ON_MEM_ERROR(self->drawing);
+
+        relationship = calloc(1, sizeof(lxw_rel_tuple));
+        GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+        relationship->type = lxw_strdup("/drawing");
+        GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+        lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                     "../drawings/drawing%d.xml", drawing_id);
+
+        relationship->target = lxw_strdup(filename);
+        GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+        STAILQ_INSERT_TAIL(self->external_drawing_links, relationship,
+                           list_pointers);
+    }
+
+    drawing_object = calloc(1, sizeof(lxw_drawing_object));
+    RETURN_VOID_ON_MEM_ERROR(drawing_object);
+
+    drawing_object->anchor_type = LXW_ANCHOR_TYPE_IMAGE;
+    drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL;
+    drawing_object->description = lxw_strdup(image_data->short_name);
+
+    /* Scale to user scale. */
+    width = image_data->width * image_data->x_scale;
+    height = image_data->height * image_data->y_scale;
+
+    /* Scale by non 96dpi resolutions. */
+    width *= 96.0 / image_data->x_dpi;
+    height *= 96.0 / image_data->y_dpi;
+
+    /* Convert to the nearest pixel. */
+    image_data->width = width;
+    image_data->height = height;
+
+    _worksheet_position_object_emus(self, image_data, drawing_object);
+
+    /* Convert from pixels to emus. */
+    drawing_object->width = (uint32_t) (0.5 + width * 9525);
+    drawing_object->height = (uint32_t) (0.5 + height * 9525);
+
+    lxw_add_drawing_object(self->drawing, drawing_object);
+
+    relationship = calloc(1, sizeof(lxw_rel_tuple));
+    GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+    relationship->type = lxw_strdup("/image");
+    GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+    lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id,
+                 image_data->extension);
+
+    relationship->target = lxw_strdup(filename);
+    GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+    STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers);
+
+    return;
+
+mem_error:
+    if (relationship) {
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+}
+
+/*
+ * Set up chart/drawings.
+ */
+void
+lxw_worksheet_prepare_chart(lxw_worksheet *self,
+                            uint16_t chart_ref_id, uint16_t drawing_id,
+                            lxw_image_options *image_data)
+{
+    lxw_drawing_object *drawing_object;
+    lxw_rel_tuple *relationship;
+    double width;
+    double height;
+    char filename[LXW_FILENAME_LENGTH];
+
+    if (!self->drawing) {
+        self->drawing = lxw_drawing_new();
+        self->drawing->embedded = LXW_TRUE;
+        RETURN_VOID_ON_MEM_ERROR(self->drawing);
+
+        relationship = calloc(1, sizeof(lxw_rel_tuple));
+        GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+        relationship->type = lxw_strdup("/drawing");
+        GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+        lxw_snprintf(filename, LXW_FILENAME_LENGTH,
+                     "../drawings/drawing%d.xml", drawing_id);
+
+        relationship->target = lxw_strdup(filename);
+        GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+        STAILQ_INSERT_TAIL(self->external_drawing_links, relationship,
+                           list_pointers);
+    }
+
+    drawing_object = calloc(1, sizeof(lxw_drawing_object));
+    RETURN_VOID_ON_MEM_ERROR(drawing_object);
+
+    drawing_object->anchor_type = LXW_ANCHOR_TYPE_CHART;
+    drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL;
+    drawing_object->description = lxw_strdup("TODO_DESC");
+
+    /* Scale to user scale. */
+    width = image_data->width * image_data->x_scale;
+    height = image_data->height * image_data->y_scale;
+
+    /* Convert to the nearest pixel. */
+    image_data->width = width;
+    image_data->height = height;
+
+    _worksheet_position_object_emus(self, image_data, drawing_object);
+
+    /* Convert from pixels to emus. */
+    drawing_object->width = (uint32_t) (0.5 + width * 9525);
+    drawing_object->height = (uint32_t) (0.5 + height * 9525);
+
+    lxw_add_drawing_object(self->drawing, drawing_object);
+
+    relationship = calloc(1, sizeof(lxw_rel_tuple));
+    GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+    relationship->type = lxw_strdup("/chart");
+    GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+    lxw_snprintf(filename, 32, "../charts/chart%d.xml", chart_ref_id);
+
+    relationship->target = lxw_strdup(filename);
+    GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+    STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers);
+
+    return;
+
+mem_error:
+    if (relationship) {
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+}
+
+/*
+ * Extract width and height information from a PNG file.
+ */
+STATIC lxw_error
+_process_png(lxw_image_options *image_options)
+{
+    uint32_t length;
+    uint32_t offset;
+    char type[4];
+    uint32_t width = 0;
+    uint32_t height = 0;
+    double x_dpi = 96;
+    double y_dpi = 96;
+    int fseek_err;
+
+    FILE *stream = image_options->stream;
+
+    /* Skip another 4 bytes to the end of the PNG header. */
+    fseek_err = fseek(stream, 4, SEEK_CUR);
+    if (fseek_err)
+        goto file_error;
+
+    while (!feof(stream)) {
+
+        /* Read the PNG length and type fields for the sub-section. */
+        if (fread(&length, sizeof(length), 1, stream) < 1)
+            break;
+
+        if (fread(&type, 1, 4, stream) < 4)
+            break;
+
+        /* Convert the length to network order. */
+        length = LXW_UINT32_NETWORK(length);
+
+        /* The offset for next fseek() is the field length + type length. */
+        offset = length + 4;
+
+        if (memcmp(type, "IHDR", 4) == 0) {
+            if (fread(&width, sizeof(width), 1, stream) < 1)
+                break;
+
+            if (fread(&height, sizeof(height), 1, stream) < 1)
+                break;
+
+            width = LXW_UINT32_NETWORK(width);
+            height = LXW_UINT32_NETWORK(height);
+
+            /* Reduce the offset by the length of previous freads(). */
+            offset -= 8;
+        }
+
+        if (memcmp(type, "pHYs", 4) == 0) {
+            uint32_t x_ppu = 0;
+            uint32_t y_ppu = 0;
+            uint8_t units = 1;
+
+            if (fread(&x_ppu, sizeof(x_ppu), 1, stream) < 1)
+                break;
+
+            if (fread(&y_ppu, sizeof(y_ppu), 1, stream) < 1)
+                break;
+
+            if (fread(&units, sizeof(units), 1, stream) < 1)
+                break;
+
+            if (units == 1) {
+                x_ppu = LXW_UINT32_NETWORK(x_ppu);
+                y_ppu = LXW_UINT32_NETWORK(y_ppu);
+
+                x_dpi = (double) x_ppu *0.0254;
+                y_dpi = (double) y_ppu *0.0254;
+            }
+
+            /* Reduce the offset by the length of previous freads(). */
+            offset -= 9;
+        }
+
+        if (memcmp(type, "IEND", 4) == 0)
+            break;
+
+        if (!feof(stream)) {
+            fseek_err = fseek(stream, offset, SEEK_CUR);
+            if (fseek_err)
+                goto file_error;
+        }
+    }
+
+    /* Ensure that we read some valid data from the file. */
+    if (width == 0)
+        goto file_error;
+
+    /* Set the image metadata. */
+    image_options->image_type = LXW_IMAGE_PNG;
+    image_options->width = width;
+    image_options->height = height;
+    image_options->x_dpi = x_dpi ? x_dpi : 96;
+    image_options->y_dpi = y_dpi ? x_dpi : 96;
+    image_options->extension = lxw_strdup("png");
+
+    return LXW_NO_ERROR;
+
+file_error:
+    LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                     "no size data found in file: %s.",
+                     image_options->filename);
+
+    return LXW_ERROR_IMAGE_DIMENSIONS;
+}
+
+/*
+ * Extract width and height information from a JPEG file.
+ */
+STATIC lxw_error
+_process_jpeg(lxw_image_options *image_options)
+{
+    uint16_t length;
+    uint16_t marker;
+    uint32_t offset;
+    uint16_t width = 0;
+    uint16_t height = 0;
+    double x_dpi = 96;
+    double y_dpi = 96;
+    int fseek_err;
+
+    FILE *stream = image_options->stream;
+
+    /* Read back 2 bytes to the end of the initial 0xFFD8 marker. */
+    fseek_err = fseek(stream, -2, SEEK_CUR);
+    if (fseek_err)
+        goto file_error;
+
+    /* Search through the image data and read the JPEG markers. */
+    while (!feof(stream)) {
+
+        /* Read the JPEG marker and length fields for the sub-section. */
+        if (fread(&marker, sizeof(marker), 1, stream) < 1)
+            break;
+
+        if (fread(&length, sizeof(length), 1, stream) < 1)
+            break;
+
+        /* Convert the marker and length to network order. */
+        marker = LXW_UINT16_NETWORK(marker);
+        length = LXW_UINT16_NETWORK(length);
+
+        /* The offset for next fseek() is the field length + type length. */
+        offset = length - 2;
+
+        /* Read the height and width in the 0xFFCn elements (except C4, C8 */
+        /* and CC which aren't SOF markers). */
+        if ((marker & 0xFFF0) == 0xFFC0 && marker != 0xFFC4
+            && marker != 0xFFC8 && marker != 0xFFCC) {
+            /* Skip 1 byte to height and width. */
+            fseek_err = fseek(stream, 1, SEEK_CUR);
+            if (fseek_err)
+                goto file_error;
+
+            if (fread(&height, sizeof(height), 1, stream) < 1)
+                break;
+
+            if (fread(&width, sizeof(width), 1, stream) < 1)
+                break;
+
+            height = LXW_UINT16_NETWORK(height);
+            width = LXW_UINT16_NETWORK(width);
+
+            offset -= 9;
+        }
+
+        /* Read the DPI in the 0xFFE0 element. */
+        if (marker == 0xFFE0) {
+            uint16_t x_density = 0;
+            uint16_t y_density = 0;
+            uint8_t units = 1;
+
+            fseek_err = fseek(stream, 7, SEEK_CUR);
+            if (fseek_err)
+                goto file_error;
+
+            if (fread(&units, sizeof(units), 1, stream) < 1)
+                break;
+
+            if (fread(&x_density, sizeof(x_density), 1, stream) < 1)
+                break;
+
+            if (fread(&y_density, sizeof(y_density), 1, stream) < 1)
+                break;
+
+            x_density = LXW_UINT16_NETWORK(x_density);
+            y_density = LXW_UINT16_NETWORK(y_density);
+
+            if (units == 1) {
+                x_dpi = x_density;
+                y_dpi = y_density;
+            }
+
+            if (units == 2) {
+                x_dpi = x_density * 2.54;
+                y_dpi = y_density * 2.54;
+            }
+
+            offset -= 12;
+        }
+
+        if (marker == 0xFFDA)
+            break;
+
+        if (!feof(stream)) {
+            fseek_err = fseek(stream, offset, SEEK_CUR);
+            if (fseek_err)
+                goto file_error;
+        }
+    }
+
+    /* Ensure that we read some valid data from the file. */
+    if (width == 0)
+        goto file_error;
+
+    /* Set the image metadata. */
+    image_options->image_type = LXW_IMAGE_JPEG;
+    image_options->width = width;
+    image_options->height = height;
+    image_options->x_dpi = x_dpi ? x_dpi : 96;
+    image_options->y_dpi = y_dpi ? x_dpi : 96;
+    image_options->extension = lxw_strdup("jpeg");
+
+    return LXW_NO_ERROR;
+
+file_error:
+    LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                     "no size data found in file: %s.",
+                     image_options->filename);
+
+    return LXW_ERROR_IMAGE_DIMENSIONS;
+}
+
+/*
+ * Extract width and height information from a BMP file.
+ */
+STATIC lxw_error
+_process_bmp(lxw_image_options *image_options)
+{
+    uint32_t width = 0;
+    uint32_t height = 0;
+    double x_dpi = 96;
+    double y_dpi = 96;
+    int fseek_err;
+
+    FILE *stream = image_options->stream;
+
+    /* Skip another 14 bytes to the start of the BMP height/width. */
+    fseek_err = fseek(stream, 14, SEEK_CUR);
+    if (fseek_err)
+        goto file_error;
+
+    if (fread(&width, sizeof(width), 1, stream) < 1)
+        width = 0;
+
+    if (fread(&height, sizeof(height), 1, stream) < 1)
+        height = 0;
+
+    /* Ensure that we read some valid data from the file. */
+    if (width == 0)
+        goto file_error;
+
+    height = LXW_UINT32_HOST(height);
+    width = LXW_UINT32_HOST(width);
+
+    /* Set the image metadata. */
+    image_options->image_type = LXW_IMAGE_BMP;
+    image_options->width = width;
+    image_options->height = height;
+    image_options->x_dpi = x_dpi;
+    image_options->y_dpi = y_dpi;
+    image_options->extension = lxw_strdup("bmp");
+
+    return LXW_NO_ERROR;
+
+file_error:
+    LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                     "no size data found in file: %s.",
+                     image_options->filename);
+
+    return LXW_ERROR_IMAGE_DIMENSIONS;
+}
+
+/*
+ * Extract information from the image file such as dimension, type, filename,
+ * and extension.
+ */
+STATIC lxw_error
+_get_image_properties(lxw_image_options *image_options)
+{
+    unsigned char signature[4];
+
+    /* Read 4 bytes to look for the file header/signature. */
+    if (fread(signature, 1, 4, image_options->stream) < 4) {
+        LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                         "couldn't read file type for file: %s.",
+                         image_options->filename);
+        return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+
+    if (memcmp(&signature[1], "PNG", 3) == 0) {
+        if (_process_png(image_options) != LXW_NO_ERROR)
+            return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+    else if (signature[0] == 0xFF && signature[1] == 0xD8) {
+        if (_process_jpeg(image_options) != LXW_NO_ERROR)
+            return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+    else if (memcmp(signature, "BM", 2) == 0) {
+        if (_process_bmp(image_options) != LXW_NO_ERROR)
+            return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+    else {
+        LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                         "unsupported image format for file: %s.",
+                         image_options->filename);
+        return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*****************************************************************************
+ *
+ * XML file assembly functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write out a number worksheet cell. Doesn't use the xml functions as an
+ * optimization in the inner cell writing loop.
+ */
+STATIC void
+_write_number_cell(lxw_worksheet *self, char *range,
+                   int32_t style_index, lxw_cell *cell)
+{
+#ifdef USE_DOUBLE_FUNCTION
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, cell->u.number);
+
+    if (style_index)
+        fprintf(self->file,
+                "<c r=\"%s\" s=\"%d\"><v>%s</v></c>",
+                range, style_index, data);
+    else
+        fprintf(self->file, "<c r=\"%s\"><v>%s</v></c>", range, data);
+#else
+    if (style_index)
+        fprintf(self->file,
+                "<c r=\"%s\" s=\"%d\"><v>%.16g</v></c>",
+                range, style_index, cell->u.number);
+    else
+        fprintf(self->file,
+                "<c r=\"%s\"><v>%.16g</v></c>", range, cell->u.number);
+
+#endif
+}
+
+/*
+ * Write out a string worksheet cell. Doesn't use the xml functions as an
+ * optimization in the inner cell writing loop.
+ */
+STATIC void
+_write_string_cell(lxw_worksheet *self, char *range,
+                   int32_t style_index, lxw_cell *cell)
+{
+
+    if (style_index)
+        fprintf(self->file,
+                "<c r=\"%s\" s=\"%d\" t=\"s\"><v>%d</v></c>",
+                range, style_index, cell->u.string_id);
+    else
+        fprintf(self->file,
+                "<c r=\"%s\" t=\"s\"><v>%d</v></c>",
+                range, cell->u.string_id);
+}
+
+/*
+ * Write out an inline string. Doesn't use the xml functions as an
+ * optimization in the inner cell writing loop.
+ */
+STATIC void
+_write_inline_string_cell(lxw_worksheet *self, char *range,
+                          int32_t style_index, lxw_cell *cell)
+{
+    char *string = lxw_escape_data(cell->u.string);
+
+    /* Add attribute to preserve leading or trailing whitespace. */
+    if (isspace((unsigned char) string[0])
+        || isspace((unsigned char) string[strlen(string) - 1])) {
+
+        if (style_index)
+            fprintf(self->file,
+                    "<c r=\"%s\" s=\"%d\" t=\"inlineStr\"><is>"
+                    "<t xml:space=\"preserve\">%s</t></is></c>",
+                    range, style_index, string);
+        else
+            fprintf(self->file,
+                    "<c r=\"%s\" t=\"inlineStr\"><is>"
+                    "<t xml:space=\"preserve\">%s</t></is></c>",
+                    range, string);
+    }
+    else {
+        if (style_index)
+            fprintf(self->file,
+                    "<c r=\"%s\" s=\"%d\" t=\"inlineStr\">"
+                    "<is><t>%s</t></is></c>", range, style_index, string);
+        else
+            fprintf(self->file,
+                    "<c r=\"%s\" t=\"inlineStr\">"
+                    "<is><t>%s</t></is></c>", range, string);
+    }
+
+    free(string);
+}
+
+/*
+ * Write out a formula worksheet cell with a numeric result.
+ */
+STATIC void
+_write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, cell->formula_result);
+    lxw_xml_data_element(self->file, "f", cell->u.string, NULL);
+    lxw_xml_data_element(self->file, "v", data, NULL);
+}
+
+/*
+ * Write out an array formula worksheet cell with a numeric result.
+ */
+STATIC void
+_write_array_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char data[LXW_ATTR_32];
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("t", "array");
+    LXW_PUSH_ATTRIBUTES_STR("ref", cell->user_data1);
+
+    lxw_sprintf_dbl(data, cell->formula_result);
+
+    lxw_xml_data_element(self->file, "f", cell->u.string, &attributes);
+    lxw_xml_data_element(self->file, "v", data, NULL);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write out a boolean worksheet cell.
+ */
+STATIC void
+_write_boolean_cell(lxw_worksheet *self, lxw_cell *cell)
+{
+    char data[LXW_ATTR_32];
+
+    if (cell->u.number)
+        data[0] = '1';
+    else
+        data[0] = '0';
+
+    data[1] = '\0';
+
+    lxw_xml_data_element(self->file, "v", data, NULL);
+}
+
+/*
+ * Calculate the "spans" attribute of the <row> tag. This is an XLSX
+ * optimization and isn't strictly required. However, it makes comparing
+ * files easier.
+ *
+ * The span is the same for each block of 16 rows.
+ */
+STATIC void
+_calculate_spans(struct lxw_row *row, char *span, int32_t *block_num)
+{
+    lxw_cell *cell_min = RB_MIN(lxw_table_cells, row->cells);
+    lxw_cell *cell_max = RB_MAX(lxw_table_cells, row->cells);
+    lxw_col_t span_col_min = cell_min->col_num;
+    lxw_col_t span_col_max = cell_max->col_num;
+    lxw_col_t col_min;
+    lxw_col_t col_max;
+    *block_num = row->row_num / 16;
+
+    row = RB_NEXT(lxw_table_rows, root, row);
+
+    while (row && (int32_t) (row->row_num / 16) == *block_num) {
+
+        if (!RB_EMPTY(row->cells)) {
+            cell_min = RB_MIN(lxw_table_cells, row->cells);
+            cell_max = RB_MAX(lxw_table_cells, row->cells);
+            col_min = cell_min->col_num;
+            col_max = cell_max->col_num;
+
+            if (col_min < span_col_min)
+                span_col_min = col_min;
+
+            if (col_max > span_col_max)
+                span_col_max = col_max;
+        }
+
+        row = RB_NEXT(lxw_table_rows, root, row);
+    }
+
+    lxw_snprintf(span, LXW_MAX_CELL_RANGE_LENGTH,
+                 "%d:%d", span_col_min + 1, span_col_max + 1);
+}
+
+/*
+ * Write out a generic worksheet cell.
+ */
+STATIC void
+_write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char range[LXW_MAX_CELL_NAME_LENGTH] = { 0 };
+    lxw_row_t row_num = cell->row_num;
+    lxw_col_t col_num = cell->col_num;
+    int32_t style_index = 0;
+
+    lxw_rowcol_to_cell(range, row_num, col_num);
+
+    if (cell->format) {
+        style_index = lxw_format_get_xf_index(cell->format);
+    }
+    else if (row_format) {
+        style_index = lxw_format_get_xf_index(row_format);
+    }
+    else if (col_num < self->col_formats_max && self->col_formats[col_num]) {
+        style_index = lxw_format_get_xf_index(self->col_formats[col_num]);
+    }
+
+    /* Unrolled optimization for most commonly written cell types. */
+    if (cell->type == NUMBER_CELL) {
+        _write_number_cell(self, range, style_index, cell);
+        return;
+    }
+
+    if (cell->type == STRING_CELL) {
+        _write_string_cell(self, range, style_index, cell);
+        return;
+    }
+
+    if (cell->type == INLINE_STRING_CELL) {
+        _write_inline_string_cell(self, range, style_index, cell);
+        return;
+    }
+
+    /* For other cell types use the general functions. */
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("r", range);
+
+    if (style_index)
+        LXW_PUSH_ATTRIBUTES_INT("s", style_index);
+
+    if (cell->type == FORMULA_CELL) {
+        lxw_xml_start_tag(self->file, "c", &attributes);
+        _write_formula_num_cell(self, cell);
+        lxw_xml_end_tag(self->file, "c");
+    }
+    else if (cell->type == BLANK_CELL) {
+        lxw_xml_empty_tag(self->file, "c", &attributes);
+    }
+    else if (cell->type == BOOLEAN_CELL) {
+        LXW_PUSH_ATTRIBUTES_STR("t", "b");
+        lxw_xml_start_tag(self->file, "c", &attributes);
+        _write_boolean_cell(self, cell);
+        lxw_xml_end_tag(self->file, "c");
+    }
+    else if (cell->type == ARRAY_FORMULA_CELL) {
+        lxw_xml_start_tag(self->file, "c", &attributes);
+        _write_array_formula_num_cell(self, cell);
+        lxw_xml_end_tag(self->file, "c");
+    }
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write out the worksheet data as a series of rows and cells.
+ */
+STATIC void
+_worksheet_write_rows(lxw_worksheet *self)
+{
+    lxw_row *row;
+    lxw_cell *cell;
+    int32_t block_num = -1;
+    char spans[LXW_MAX_CELL_RANGE_LENGTH] = { 0 };
+
+    RB_FOREACH(row, lxw_table_rows, self->table) {
+
+        if (RB_EMPTY(row->cells)) {
+            /* Row contains no cells but has height, format or other data. */
+
+            /* Write a default span for default rows. */
+            if (self->default_row_set)
+                _write_row(self, row, "1:1");
+            else
+                _write_row(self, row, NULL);
+        }
+        else {
+            /* Row and cell data. */
+            if ((int32_t) row->row_num / 16 > block_num)
+                _calculate_spans(row, spans, &block_num);
+
+            _write_row(self, row, spans);
+
+            RB_FOREACH(cell, lxw_table_cells, row->cells) {
+                _write_cell(self, cell, row->format);
+            }
+            lxw_xml_end_tag(self->file, "row");
+        }
+    }
+}
+
+/*
+ * Write out the worksheet data as a single row with cells. This method is
+ * used when memory optimization is on. A single row is written and the data
+ * array is reset. That way only one row of data is kept in memory at any one
+ * time. We don't write span data in the optimized case since it is optional.
+ */
+void
+lxw_worksheet_write_single_row(lxw_worksheet *self)
+{
+    lxw_row *row = self->optimize_row;
+    lxw_col_t col;
+
+    /* skip row if it doesn't contain row formatting, cell data or a comment. */
+    if (!(row->row_changed || row->data_changed))
+        return;
+
+    /* Write the cells if the row contains data. */
+    if (!row->data_changed) {
+        /* Row data only. No cells. */
+        _write_row(self, row, NULL);
+    }
+    else {
+        /* Row and cell data. */
+        _write_row(self, row, NULL);
+
+        for (col = self->dim_colmin; col <= self->dim_colmax; col++) {
+            if (self->array[col]) {
+                _write_cell(self, self->array[col], row->format);
+                _free_cell(self->array[col]);
+                self->array[col] = NULL;
+            }
+        }
+
+        lxw_xml_end_tag(self->file, "row");
+    }
+
+    /* Reset the row. */
+    row->height = LXW_DEF_ROW_HEIGHT;
+    row->format = NULL;
+    row->hidden = LXW_FALSE;
+    row->level = 0;
+    row->collapsed = LXW_FALSE;
+    row->data_changed = LXW_FALSE;
+    row->row_changed = LXW_FALSE;
+}
+
+/*
+ * Write the <col> element.
+ */
+STATIC void
+_worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    double width = options->width;
+    uint8_t has_custom_width = LXW_TRUE;
+    int32_t xf_index = 0;
+    double max_digit_width = 7.0;       /* For Calabri 11. */
+    double padding = 5.0;
+
+    /* Get the format index. */
+    if (options->format) {
+        xf_index = lxw_format_get_xf_index(options->format);
+    }
+
+    /* Check if width is the Excel default. */
+    if (width == LXW_DEF_COL_WIDTH) {
+
+        /* The default col width changes to 0 for hidden columns. */
+        if (options->hidden)
+            width = 0;
+        else
+            has_custom_width = LXW_FALSE;
+
+    }
+
+    /* Convert column width from user units to character width. */
+    if (width > 0) {
+        if (width < 1) {
+            width = (uint16_t) (((uint16_t)
+                                 (width * (max_digit_width + padding) + 0.5))
+                                / max_digit_width * 256.0) / 256.0;
+        }
+        else {
+            width = (uint16_t) (((uint16_t)
+                                 (width * max_digit_width + 0.5) + padding)
+                                / max_digit_width * 256.0) / 256.0;
+        }
+    }
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("min", 1 + options->firstcol);
+    LXW_PUSH_ATTRIBUTES_INT("max", 1 + options->lastcol);
+    LXW_PUSH_ATTRIBUTES_DBL("width", width);
+
+    if (xf_index)
+        LXW_PUSH_ATTRIBUTES_INT("style", xf_index);
+
+    if (options->hidden)
+        LXW_PUSH_ATTRIBUTES_STR("hidden", "1");
+
+    if (has_custom_width)
+        LXW_PUSH_ATTRIBUTES_STR("customWidth", "1");
+
+    if (options->level)
+        LXW_PUSH_ATTRIBUTES_INT("outlineLevel", options->level);
+
+    if (options->collapsed)
+        LXW_PUSH_ATTRIBUTES_STR("collapsed", "1");
+
+    lxw_xml_empty_tag(self->file, "col", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <cols> element and <col> sub elements.
+ */
+STATIC void
+_worksheet_write_cols(lxw_worksheet *self)
+{
+    lxw_col_t col;
+
+    if (!self->col_size_changed)
+        return;
+
+    lxw_xml_start_tag(self->file, "cols", NULL);
+
+    for (col = 0; col < self->col_options_max; col++) {
+        if (self->col_options[col])
+            _worksheet_write_col_info(self, self->col_options[col]);
+    }
+
+    lxw_xml_end_tag(self->file, "cols");
+}
+
+/*
+ * Write the <mergeCell> element.
+ */
+STATIC void
+_worksheet_write_merge_cell(lxw_worksheet *self,
+                            lxw_merged_range *merged_range)
+{
+
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char ref[LXW_MAX_CELL_RANGE_LENGTH];
+
+    LXW_INIT_ATTRIBUTES();
+
+    /* Convert the merge dimensions to a cell range. */
+    lxw_rowcol_to_range(ref, merged_range->first_row, merged_range->first_col,
+                        merged_range->last_row, merged_range->last_col);
+
+    LXW_PUSH_ATTRIBUTES_STR("ref", ref);
+
+    lxw_xml_empty_tag(self->file, "mergeCell", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <mergeCells> element.
+ */
+STATIC void
+_worksheet_write_merge_cells(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_merged_range *merged_range;
+
+    if (self->merged_range_count) {
+        LXW_INIT_ATTRIBUTES();
+
+        LXW_PUSH_ATTRIBUTES_INT("count", self->merged_range_count);
+
+        lxw_xml_start_tag(self->file, "mergeCells", &attributes);
+
+        STAILQ_FOREACH(merged_range, self->merged_ranges, list_pointers) {
+            _worksheet_write_merge_cell(self, merged_range);
+        }
+        lxw_xml_end_tag(self->file, "mergeCells");
+
+        LXW_FREE_ATTRIBUTES();
+    }
+}
+
+/*
+ * Write the <oddHeader> element.
+ */
+STATIC void
+_worksheet_write_odd_header(lxw_worksheet *self)
+{
+    lxw_xml_data_element(self->file, "oddHeader", self->header, NULL);
+}
+
+/*
+ * Write the <oddFooter> element.
+ */
+STATIC void
+_worksheet_write_odd_footer(lxw_worksheet *self)
+{
+    lxw_xml_data_element(self->file, "oddFooter", self->footer, NULL);
+}
+
+/*
+ * Write the <headerFooter> element.
+ */
+STATIC void
+_worksheet_write_header_footer(lxw_worksheet *self)
+{
+    if (!self->header_footer_changed)
+        return;
+
+    lxw_xml_start_tag(self->file, "headerFooter", NULL);
+
+    if (self->header[0] != '\0')
+        _worksheet_write_odd_header(self);
+
+    if (self->footer[0] != '\0')
+        _worksheet_write_odd_footer(self);
+
+    lxw_xml_end_tag(self->file, "headerFooter");
+}
+
+/*
+ * Write the <pageSetUpPr> element.
+ */
+STATIC void
+_worksheet_write_page_set_up_pr(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!self->fit_page)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("fitToPage", "1");
+
+    lxw_xml_empty_tag(self->file, "pageSetUpPr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+
+}
+
+/*
+ * Write the <tabColor> element.
+ */
+STATIC void
+_worksheet_write_tab_color(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char rgb_str[LXW_ATTR_32];
+
+    if (self->tab_color == LXW_COLOR_UNSET)
+        return;
+
+    lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X",
+                 self->tab_color & LXW_COLOR_MASK);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
+
+    lxw_xml_empty_tag(self->file, "tabColor", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <outlinePr> element.
+ */
+STATIC void
+_worksheet_write_outline_pr(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!self->outline_changed)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (self->outline_style)
+        LXW_PUSH_ATTRIBUTES_STR("applyStyles", "1");
+
+    if (!self->outline_below)
+        LXW_PUSH_ATTRIBUTES_STR("summaryBelow", "0");
+
+    if (!self->outline_right)
+        LXW_PUSH_ATTRIBUTES_STR("summaryRight", "0");
+
+    if (!self->outline_on)
+        LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0");
+
+    lxw_xml_empty_tag(self->file, "outlinePr", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <sheetPr> element for Sheet level properties.
+ */
+STATIC void
+_worksheet_write_sheet_pr(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    if (!self->fit_page
+        && !self->filter_on
+        && self->tab_color == LXW_COLOR_UNSET
+        && !self->outline_changed && !self->vba_codename) {
+        return;
+    }
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (self->vba_codename)
+        LXW_PUSH_ATTRIBUTES_INT("codeName", self->vba_codename);
+
+    if (self->filter_on)
+        LXW_PUSH_ATTRIBUTES_STR("filterMode", "1");
+
+    if (self->fit_page || self->tab_color != LXW_COLOR_UNSET
+        || self->outline_changed) {
+        lxw_xml_start_tag(self->file, "sheetPr", &attributes);
+        _worksheet_write_tab_color(self);
+        _worksheet_write_outline_pr(self);
+        _worksheet_write_page_set_up_pr(self);
+        lxw_xml_end_tag(self->file, "sheetPr");
+    }
+    else {
+        lxw_xml_empty_tag(self->file, "sheetPr", &attributes);
+    }
+
+    LXW_FREE_ATTRIBUTES();
+
+}
+
+/*
+ * Write the <brk> element.
+ */
+STATIC void
+_worksheet_write_brk(lxw_worksheet *self, uint32_t id, uint32_t max)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("id", id);
+    LXW_PUSH_ATTRIBUTES_INT("max", max);
+    LXW_PUSH_ATTRIBUTES_STR("man", "1");
+
+    lxw_xml_empty_tag(self->file, "brk", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <rowBreaks> element.
+ */
+STATIC void
+_worksheet_write_row_breaks(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint16_t count = self->hbreaks_count;
+    uint16_t i;
+
+    if (!count)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", count);
+    LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
+
+    lxw_xml_start_tag(self->file, "rowBreaks", &attributes);
+
+    for (i = 0; i < count; i++)
+        _worksheet_write_brk(self, self->hbreaks[i], LXW_COL_MAX - 1);
+
+    lxw_xml_end_tag(self->file, "rowBreaks");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <colBreaks> element.
+ */
+STATIC void
+_worksheet_write_col_breaks(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint16_t count = self->vbreaks_count;
+    uint16_t i;
+
+    if (!count)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", count);
+    LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
+
+    lxw_xml_start_tag(self->file, "colBreaks", &attributes);
+
+    for (i = 0; i < count; i++)
+        _worksheet_write_brk(self, self->vbreaks[i], LXW_ROW_MAX - 1);
+
+    lxw_xml_end_tag(self->file, "colBreaks");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <autoFilter> element.
+ */
+STATIC void
+_worksheet_write_auto_filter(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char range[LXW_MAX_CELL_RANGE_LENGTH];
+
+    if (!self->autofilter.in_use)
+        return;
+
+    lxw_rowcol_to_range(range,
+                        self->autofilter.first_row,
+                        self->autofilter.first_col,
+                        self->autofilter.last_row, self->autofilter.last_col);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("ref", range);
+
+    lxw_xml_empty_tag(self->file, "autoFilter", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <hyperlink> element for external links.
+ */
+STATIC void
+_worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num,
+                                    lxw_col_t col_num, const char *location,
+                                    const char *tooltip, uint16_t id)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char ref[LXW_MAX_CELL_NAME_LENGTH];
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
+
+    lxw_rowcol_to_cell(ref, row_num, col_num);
+
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("ref", ref);
+    LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
+
+    if (location)
+        LXW_PUSH_ATTRIBUTES_STR("location", location);
+
+    if (tooltip)
+        LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
+
+    lxw_xml_empty_tag(self->file, "hyperlink", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <hyperlink> element for internal links.
+ */
+STATIC void
+_worksheet_write_hyperlink_internal(lxw_worksheet *self, lxw_row_t row_num,
+                                    lxw_col_t col_num, const char *location,
+                                    const char *display, const char *tooltip)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char ref[LXW_MAX_CELL_NAME_LENGTH];
+
+    lxw_rowcol_to_cell(ref, row_num, col_num);
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_STR("ref", ref);
+
+    if (location)
+        LXW_PUSH_ATTRIBUTES_STR("location", location);
+
+    if (tooltip)
+        LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
+
+    if (display)
+        LXW_PUSH_ATTRIBUTES_STR("display", display);
+
+    lxw_xml_empty_tag(self->file, "hyperlink", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Process any stored hyperlinks in row/col order and write the <hyperlinks>
+ * element. The attributes are different for internal and external links.
+ */
+STATIC void
+_worksheet_write_hyperlinks(lxw_worksheet *self)
+{
+
+    lxw_row *row;
+    lxw_cell *link;
+    lxw_rel_tuple *relationship;
+
+    if (RB_EMPTY(self->hyperlinks))
+        return;
+
+    /* Write the hyperlink elements. */
+    lxw_xml_start_tag(self->file, "hyperlinks", NULL);
+
+    RB_FOREACH(row, lxw_table_rows, self->hyperlinks) {
+
+        RB_FOREACH(link, lxw_table_cells, row->cells) {
+
+            if (link->type == HYPERLINK_URL
+                || link->type == HYPERLINK_EXTERNAL) {
+
+                self->rel_count++;
+
+                relationship = calloc(1, sizeof(lxw_rel_tuple));
+                GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
+
+                relationship->type = lxw_strdup("/hyperlink");
+                GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
+
+                relationship->target = lxw_strdup(link->u.string);
+                GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
+
+                relationship->target_mode = lxw_strdup("External");
+                GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
+
+                STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship,
+                                   list_pointers);
+
+                _worksheet_write_hyperlink_external(self, link->row_num,
+                                                    link->col_num,
+                                                    link->user_data1,
+                                                    link->user_data2,
+                                                    self->rel_count);
+            }
+
+            if (link->type == HYPERLINK_INTERNAL) {
+
+                _worksheet_write_hyperlink_internal(self, link->row_num,
+                                                    link->col_num,
+                                                    link->u.string,
+                                                    link->user_data1,
+                                                    link->user_data2);
+            }
+
+        }
+
+    }
+
+    lxw_xml_end_tag(self->file, "hyperlinks");
+    return;
+
+mem_error:
+    if (relationship) {
+        free(relationship->type);
+        free(relationship->target);
+        free(relationship->target_mode);
+        free(relationship);
+    }
+    lxw_xml_end_tag(self->file, "hyperlinks");
+}
+
+/*
+ * Write the <sheetProtection> element.
+ */
+STATIC void
+_worksheet_write_sheet_protection(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+
+    struct lxw_protection *protect = &self->protection;
+
+    if (!protect->is_configured)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+
+    if (*protect->hash)
+        LXW_PUSH_ATTRIBUTES_STR("password", protect->hash);
+
+    if (!protect->no_sheet)
+        LXW_PUSH_ATTRIBUTES_INT("sheet", 1);
+
+    if (protect->content)
+        LXW_PUSH_ATTRIBUTES_INT("content", 1);
+
+    if (!protect->objects)
+        LXW_PUSH_ATTRIBUTES_INT("objects", 1);
+
+    if (!protect->scenarios)
+        LXW_PUSH_ATTRIBUTES_INT("scenarios", 1);
+
+    if (protect->format_cells)
+        LXW_PUSH_ATTRIBUTES_INT("formatCells", 0);
+
+    if (protect->format_columns)
+        LXW_PUSH_ATTRIBUTES_INT("formatColumns", 0);
+
+    if (protect->format_rows)
+        LXW_PUSH_ATTRIBUTES_INT("formatRows", 0);
+
+    if (protect->insert_columns)
+        LXW_PUSH_ATTRIBUTES_INT("insertColumns", 0);
+
+    if (protect->insert_rows)
+        LXW_PUSH_ATTRIBUTES_INT("insertRows", 0);
+
+    if (protect->insert_hyperlinks)
+        LXW_PUSH_ATTRIBUTES_INT("insertHyperlinks", 0);
+
+    if (protect->delete_columns)
+        LXW_PUSH_ATTRIBUTES_INT("deleteColumns", 0);
+
+    if (protect->delete_rows)
+        LXW_PUSH_ATTRIBUTES_INT("deleteRows", 0);
+
+    if (protect->no_select_locked_cells)
+        LXW_PUSH_ATTRIBUTES_INT("selectLockedCells", 1);
+
+    if (protect->sort)
+        LXW_PUSH_ATTRIBUTES_INT("sort", 0);
+
+    if (protect->autofilter)
+        LXW_PUSH_ATTRIBUTES_INT("autoFilter", 0);
+
+    if (protect->pivot_tables)
+        LXW_PUSH_ATTRIBUTES_INT("pivotTables", 0);
+
+    if (protect->no_select_unlocked_cells)
+        LXW_PUSH_ATTRIBUTES_INT("selectUnlockedCells", 1);
+
+    lxw_xml_empty_tag(self->file, "sheetProtection", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <drawing> element.
+ */
+STATIC void
+_write_drawing(lxw_worksheet *self, uint16_t id)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
+
+    lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id);
+
+    LXW_INIT_ATTRIBUTES();
+
+    LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
+
+    lxw_xml_empty_tag(self->file, "drawing", &attributes);
+
+    LXW_FREE_ATTRIBUTES();
+
+}
+
+/*
+ * Write the <drawing> elements.
+ */
+STATIC void
+_write_drawings(lxw_worksheet *self)
+{
+    if (!self->drawing)
+        return;
+
+    self->rel_count++;
+
+    _write_drawing(self, self->rel_count);
+}
+
+/*
+ * Write the <formula1> element for numbers.
+ */
+STATIC void
+_worksheet_write_formula1_num(lxw_worksheet *self, double number)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, number);
+
+    lxw_xml_data_element(self->file, "formula1", data, NULL);
+}
+
+/*
+ * Write the <formula1> element for strings/formulas.
+ */
+STATIC void
+_worksheet_write_formula1_str(lxw_worksheet *self, char *str)
+{
+    lxw_xml_data_element(self->file, "formula1", str, NULL);
+}
+
+/*
+ * Write the <formula2> element for numbers.
+ */
+STATIC void
+_worksheet_write_formula2_num(lxw_worksheet *self, double number)
+{
+    char data[LXW_ATTR_32];
+
+    lxw_sprintf_dbl(data, number);
+
+    lxw_xml_data_element(self->file, "formula2", data, NULL);
+}
+
+/*
+ * Write the <formula2> element for strings/formulas.
+ */
+STATIC void
+_worksheet_write_formula2_str(lxw_worksheet *self, char *str)
+{
+    lxw_xml_data_element(self->file, "formula2", str, NULL);
+}
+
+/*
+ * Write the <dataValidation> element.
+ */
+STATIC void
+_worksheet_write_data_validation(lxw_worksheet *self,
+                                 lxw_data_validation *validation)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    uint8_t is_between = 0;
+
+    LXW_INIT_ATTRIBUTES();
+
+    switch (validation->validate) {
+        case LXW_VALIDATION_TYPE_INTEGER:
+        case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
+            LXW_PUSH_ATTRIBUTES_STR("type", "whole");
+            break;
+        case LXW_VALIDATION_TYPE_DECIMAL:
+        case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
+            LXW_PUSH_ATTRIBUTES_STR("type", "decimal");
+            break;
+        case LXW_VALIDATION_TYPE_LIST:
+        case LXW_VALIDATION_TYPE_LIST_FORMULA:
+            LXW_PUSH_ATTRIBUTES_STR("type", "list");
+            break;
+        case LXW_VALIDATION_TYPE_DATE:
+        case LXW_VALIDATION_TYPE_DATE_FORMULA:
+        case LXW_VALIDATION_TYPE_DATE_NUMBER:
+            LXW_PUSH_ATTRIBUTES_STR("type", "date");
+            break;
+        case LXW_VALIDATION_TYPE_TIME:
+        case LXW_VALIDATION_TYPE_TIME_FORMULA:
+        case LXW_VALIDATION_TYPE_TIME_NUMBER:
+            LXW_PUSH_ATTRIBUTES_STR("type", "time");
+            break;
+        case LXW_VALIDATION_TYPE_LENGTH:
+        case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
+            LXW_PUSH_ATTRIBUTES_STR("type", "textLength");
+            break;
+        case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
+            LXW_PUSH_ATTRIBUTES_STR("type", "custom");
+            break;
+    }
+
+    switch (validation->criteria) {
+        case LXW_VALIDATION_CRITERIA_EQUAL_TO:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "equal");
+            break;
+        case LXW_VALIDATION_CRITERIA_NOT_EQUAL_TO:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual");
+            break;
+        case LXW_VALIDATION_CRITERIA_LESS_THAN:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan");
+            break;
+        case LXW_VALIDATION_CRITERIA_LESS_THAN_OR_EQUAL_TO:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual");
+            break;
+        case LXW_VALIDATION_CRITERIA_GREATER_THAN:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan");
+            break;
+        case LXW_VALIDATION_CRITERIA_GREATER_THAN_OR_EQUAL_TO:
+            LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual");
+            break;
+        case LXW_VALIDATION_CRITERIA_BETWEEN:
+            /* Between is the default for 2 formulas and isn't added. */
+            is_between = 1;
+            break;
+        case LXW_VALIDATION_CRITERIA_NOT_BETWEEN:
+            is_between = 1;
+            LXW_PUSH_ATTRIBUTES_STR("operator", "notBetween");
+            break;
+    }
+
+    if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_WARNING)
+        LXW_PUSH_ATTRIBUTES_STR("errorStyle", "warning");
+
+    if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_INFORMATION)
+        LXW_PUSH_ATTRIBUTES_STR("errorStyle", "information");
+
+    if (validation->ignore_blank)
+        LXW_PUSH_ATTRIBUTES_INT("allowBlank", 1);
+
+    if (validation->dropdown == LXW_VALIDATION_OFF)
+        LXW_PUSH_ATTRIBUTES_INT("showDropDown", 1);
+
+    if (validation->show_input)
+        LXW_PUSH_ATTRIBUTES_INT("showInputMessage", 1);
+
+    if (validation->show_error)
+        LXW_PUSH_ATTRIBUTES_INT("showErrorMessage", 1);
+
+    if (validation->error_title)
+        LXW_PUSH_ATTRIBUTES_STR("errorTitle", validation->error_title);
+
+    if (validation->error_message)
+        LXW_PUSH_ATTRIBUTES_STR("error", validation->error_message);
+
+    if (validation->input_title)
+        LXW_PUSH_ATTRIBUTES_STR("promptTitle", validation->input_title);
+
+    if (validation->input_message)
+        LXW_PUSH_ATTRIBUTES_STR("prompt", validation->input_message);
+
+    LXW_PUSH_ATTRIBUTES_STR("sqref", validation->sqref);
+
+    if (validation->validate == LXW_VALIDATION_TYPE_ANY)
+        lxw_xml_empty_tag(self->file, "dataValidation", &attributes);
+    else
+        lxw_xml_start_tag(self->file, "dataValidation", &attributes);
+
+    /* Write the formula1 and formula2 elements. */
+    switch (validation->validate) {
+        case LXW_VALIDATION_TYPE_INTEGER:
+        case LXW_VALIDATION_TYPE_DECIMAL:
+        case LXW_VALIDATION_TYPE_LENGTH:
+        case LXW_VALIDATION_TYPE_DATE:
+        case LXW_VALIDATION_TYPE_TIME:
+        case LXW_VALIDATION_TYPE_DATE_NUMBER:
+        case LXW_VALIDATION_TYPE_TIME_NUMBER:
+            _worksheet_write_formula1_num(self, validation->value_number);
+            if (is_between)
+                _worksheet_write_formula2_num(self,
+                                              validation->maximum_number);
+            break;
+        case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
+        case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
+        case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
+        case LXW_VALIDATION_TYPE_DATE_FORMULA:
+        case LXW_VALIDATION_TYPE_TIME_FORMULA:
+        case LXW_VALIDATION_TYPE_LIST:
+        case LXW_VALIDATION_TYPE_LIST_FORMULA:
+        case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
+            _worksheet_write_formula1_str(self, validation->value_formula);
+            if (is_between)
+                _worksheet_write_formula2_str(self,
+                                              validation->maximum_formula);
+            break;
+    }
+
+    if (validation->validate != LXW_VALIDATION_TYPE_ANY)
+        lxw_xml_end_tag(self->file, "dataValidation");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Write the <dataValidations> element.
+ */
+STATIC void
+_worksheet_write_data_validations(lxw_worksheet *self)
+{
+    struct xml_attribute_list attributes;
+    struct xml_attribute *attribute;
+    lxw_data_validation *data_validation;
+
+    if (self->num_validations == 0)
+        return;
+
+    LXW_INIT_ATTRIBUTES();
+    LXW_PUSH_ATTRIBUTES_INT("count", self->num_validations);
+
+    lxw_xml_start_tag(self->file, "dataValidations", &attributes);
+
+    STAILQ_FOREACH(data_validation, self->data_validations, list_pointers) {
+        /* Write the dataValidation element. */
+        _worksheet_write_data_validation(self, data_validation);
+    }
+
+    lxw_xml_end_tag(self->file, "dataValidations");
+
+    LXW_FREE_ATTRIBUTES();
+}
+
+/*
+ * Assemble and write the XML file.
+ */
+void
+lxw_worksheet_assemble_xml_file(lxw_worksheet *self)
+{
+    /* Write the XML declaration. */
+    _worksheet_xml_declaration(self);
+
+    /* Write the worksheet element. */
+    _worksheet_write_worksheet(self);
+
+    /* Write the worksheet properties. */
+    _worksheet_write_sheet_pr(self);
+
+    /* Write the worksheet dimensions. */
+    _worksheet_write_dimension(self);
+
+    /* Write the sheet view properties. */
+    _worksheet_write_sheet_views(self);
+
+    /* Write the sheet format properties. */
+    _worksheet_write_sheet_format_pr(self);
+
+    /* Write the sheet column info. */
+    _worksheet_write_cols(self);
+
+    /* Write the sheetData element. */
+    if (!self->optimize)
+        _worksheet_write_sheet_data(self);
+    else
+        _worksheet_write_optimized_sheet_data(self);
+
+    /* Write the sheetProtection element. */
+    _worksheet_write_sheet_protection(self);
+
+    /* Write the autoFilter element. */
+    _worksheet_write_auto_filter(self);
+
+    /* Write the mergeCells element. */
+    _worksheet_write_merge_cells(self);
+
+    /* Write the dataValidations element. */
+    _worksheet_write_data_validations(self);
+
+    /* Write the hyperlink element. */
+    _worksheet_write_hyperlinks(self);
+
+    /* Write the printOptions element. */
+    _worksheet_write_print_options(self);
+
+    /* Write the worksheet page_margins. */
+    _worksheet_write_page_margins(self);
+
+    /* Write the worksheet page setup. */
+    _worksheet_write_page_setup(self);
+
+    /* Write the headerFooter element. */
+    _worksheet_write_header_footer(self);
+
+    /* Write the rowBreaks element. */
+    _worksheet_write_row_breaks(self);
+
+    /* Write the colBreaks element. */
+    _worksheet_write_col_breaks(self);
+
+    /* Write the drawing element. */
+    _write_drawings(self);
+
+    /* Close the worksheet tag. */
+    lxw_xml_end_tag(self->file, "worksheet");
+}
+
+/*****************************************************************************
+ *
+ * Public functions.
+ *
+ ****************************************************************************/
+
+/*
+ * Write a number to a cell in Excel.
+ */
+lxw_error
+worksheet_write_number(lxw_worksheet *self,
+                       lxw_row_t row_num,
+                       lxw_col_t col_num, double value, lxw_format *format)
+{
+    lxw_cell *cell;
+    lxw_error err;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    cell = _new_number_cell(row_num, col_num, value, format);
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a string to an Excel file.
+ */
+lxw_error
+worksheet_write_string(lxw_worksheet *self,
+                       lxw_row_t row_num,
+                       lxw_col_t col_num, const char *string,
+                       lxw_format *format)
+{
+    lxw_cell *cell;
+    int32_t string_id;
+    char *string_copy;
+    struct sst_element *sst_element;
+    lxw_error err;
+
+    if (!string || !*string) {
+        /* Treat a NULL or empty string with formatting as a blank cell. */
+        /* Null strings without formats should be ignored.      */
+        if (format)
+            return worksheet_write_blank(self, row_num, col_num, format);
+        else
+            return LXW_NO_ERROR;
+    }
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    if (lxw_utf8_strlen(string) > LXW_STR_MAX)
+        return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED;
+
+    if (!self->optimize) {
+        /* Get the SST element and string id. */
+        sst_element = lxw_get_sst_index(self->sst, string);
+
+        if (!sst_element)
+            return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND;
+
+        string_id = sst_element->index;
+        cell = _new_string_cell(row_num, col_num, string_id,
+                                sst_element->string, format);
+    }
+    else {
+        /* Look for and escape control chars in the string. */
+        if (strpbrk(string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C"
+                    "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16"
+                    "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) {
+            string_copy = lxw_escape_control_characters(string);
+        }
+        else {
+            string_copy = lxw_strdup(string);
+        }
+        cell = _new_inline_string_cell(row_num, col_num, string_copy, format);
+    }
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a formula with a numerical result to a cell in Excel.
+ */
+lxw_error
+worksheet_write_formula_num(lxw_worksheet *self,
+                            lxw_row_t row_num,
+                            lxw_col_t col_num,
+                            const char *formula,
+                            lxw_format *format, double result)
+{
+    lxw_cell *cell;
+    char *formula_copy;
+    lxw_error err;
+
+    if (!formula)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    /* Strip leading "=" from formula. */
+    if (formula[0] == '=')
+        formula_copy = lxw_strdup(formula + 1);
+    else
+        formula_copy = lxw_strdup(formula);
+
+    cell = _new_formula_cell(row_num, col_num, formula_copy, format);
+    cell->formula_result = result;
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a formula with a default result to a cell in Excel .
+ */
+lxw_error
+worksheet_write_formula(lxw_worksheet *self,
+                        lxw_row_t row_num,
+                        lxw_col_t col_num, const char *formula,
+                        lxw_format *format)
+{
+    return worksheet_write_formula_num(self, row_num, col_num, formula,
+                                       format, 0);
+}
+
+/*
+ * Write a formula with a numerical result to a cell in Excel.
+ */
+lxw_error
+worksheet_write_array_formula_num(lxw_worksheet *self,
+                                  lxw_row_t first_row,
+                                  lxw_col_t first_col,
+                                  lxw_row_t last_row,
+                                  lxw_col_t last_col,
+                                  const char *formula,
+                                  lxw_format *format, double result)
+{
+    lxw_cell *cell;
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    char *formula_copy;
+    char *range;
+    lxw_error err;
+
+    /* Swap last row/col with first row/col as necessary */
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    if (!formula)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    /* Check that column number is valid and store the max value */
+    err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    /* Define the array range. */
+    range = calloc(1, LXW_MAX_CELL_RANGE_LENGTH);
+    RETURN_ON_MEM_ERROR(range, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    if (first_row == last_row && first_col == last_col)
+        lxw_rowcol_to_cell(range, first_row, last_col);
+    else
+        lxw_rowcol_to_range(range, first_row, first_col, last_row, last_col);
+
+    /* Copy and trip leading "{=" from formula. */
+    if (formula[0] == '{')
+        if (formula[1] == '=')
+            formula_copy = lxw_strdup(formula + 2);
+        else
+            formula_copy = lxw_strdup(formula + 1);
+    else
+        formula_copy = lxw_strdup(formula);
+
+    /* Strip trailing "}" from formula. */
+    if (formula_copy[strlen(formula_copy) - 1] == '}')
+        formula_copy[strlen(formula_copy) - 1] = '\0';
+
+    /* Create a new array formula cell object. */
+    cell = _new_array_formula_cell(first_row, first_col,
+                                   formula_copy, range, format);
+
+    cell->formula_result = result;
+
+    _insert_cell(self, first_row, first_col, cell);
+
+    /* Pad out the rest of the area with formatted zeroes. */
+    if (!self->optimize) {
+        for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
+            for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
+                if (tmp_row == first_row && tmp_col == first_col)
+                    continue;
+
+                worksheet_write_number(self, tmp_row, tmp_col, 0, format);
+            }
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write an array formula with a default result to a cell in Excel .
+ */
+lxw_error
+worksheet_write_array_formula(lxw_worksheet *self,
+                              lxw_row_t first_row,
+                              lxw_col_t first_col,
+                              lxw_row_t last_row,
+                              lxw_col_t last_col,
+                              const char *formula, lxw_format *format)
+{
+    return worksheet_write_array_formula_num(self, first_row, first_col,
+                                             last_row, last_col, formula,
+                                             format, 0);
+}
+
+/*
+ * Write a blank cell with a format to a cell in Excel.
+ */
+lxw_error
+worksheet_write_blank(lxw_worksheet *self,
+                      lxw_row_t row_num, lxw_col_t col_num,
+                      lxw_format *format)
+{
+    lxw_cell *cell;
+    lxw_error err;
+
+    /* Blank cells without formatting are ignored by Excel. */
+    if (!format)
+        return LXW_NO_ERROR;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    cell = _new_blank_cell(row_num, col_num, format);
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a boolean cell with a format to a cell in Excel.
+ */
+lxw_error
+worksheet_write_boolean(lxw_worksheet *self,
+                        lxw_row_t row_num, lxw_col_t col_num,
+                        int value, lxw_format *format)
+{
+    lxw_cell *cell;
+    lxw_error err;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+
+    if (err)
+        return err;
+
+    cell = _new_boolean_cell(row_num, col_num, value, format);
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a date and or time to a cell in Excel.
+ */
+lxw_error
+worksheet_write_datetime(lxw_worksheet *self,
+                         lxw_row_t row_num,
+                         lxw_col_t col_num, lxw_datetime *datetime,
+                         lxw_format *format)
+{
+    lxw_cell *cell;
+    double excel_date;
+    lxw_error err;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    excel_date = lxw_datetime_to_excel_date(datetime, LXW_EPOCH_1900);
+
+    cell = _new_number_cell(row_num, col_num, excel_date, format);
+
+    _insert_cell(self, row_num, col_num, cell);
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Write a hyperlink/url to an Excel file.
+ */
+lxw_error
+worksheet_write_url_opt(lxw_worksheet *self,
+                        lxw_row_t row_num,
+                        lxw_col_t col_num, const char *url,
+                        lxw_format *format, const char *string,
+                        const char *tooltip)
+{
+    lxw_cell *link;
+    char *string_copy = NULL;
+    char *url_copy = NULL;
+    char *url_external = NULL;
+    char *url_string = NULL;
+    char *tooltip_copy = NULL;
+    char *found_string;
+    lxw_error err;
+    size_t string_size;
+    size_t i;
+    enum cell_types link_type = HYPERLINK_URL;
+
+    if (!url || !*url)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    /* Check the Excel limit of URLS per worksheet. */
+    if (self->hlink_count > LXW_MAX_NUMBER_URLS)
+        return LXW_ERROR_WORKSHEET_MAX_NUMBER_URLS_EXCEEDED;
+
+    err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    /* Set the URI scheme from internal links. */
+    found_string = strstr(url, "internal:");
+    if (found_string)
+        link_type = HYPERLINK_INTERNAL;
+
+    /* Set the URI scheme from external links. */
+    found_string = strstr(url, "external:");
+    if (found_string)
+        link_type = HYPERLINK_EXTERNAL;
+
+    if (string) {
+        string_copy = lxw_strdup(string);
+        GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
+    }
+    else {
+        if (link_type == HYPERLINK_URL) {
+            /* Strip the mailto header. */
+            found_string = strstr(url, "mailto:");
+            if (found_string)
+                string_copy = lxw_strdup(url + sizeof("mailto"));
+            else
+                string_copy = lxw_strdup(url);
+        }
+        else {
+            string_copy = lxw_strdup(url + sizeof("__ternal"));
+        }
+        GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
+    }
+
+    if (url) {
+        if (link_type == HYPERLINK_URL)
+            url_copy = lxw_strdup(url);
+        else
+            url_copy = lxw_strdup(url + sizeof("__ternal"));
+
+        GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
+    }
+
+    if (tooltip) {
+        tooltip_copy = lxw_strdup(tooltip);
+        GOTO_LABEL_ON_MEM_ERROR(tooltip_copy, mem_error);
+    }
+
+    if (link_type == HYPERLINK_INTERNAL) {
+        url_string = lxw_strdup(string_copy);
+        GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
+    }
+
+    /* Escape the URL. */
+    if (link_type == HYPERLINK_URL && strlen(url_copy) >= 3) {
+        uint8_t not_escaped = 1;
+
+        /* First check if the URL is already escaped by the user. */
+        for (i = 0; i <= strlen(url_copy) - 3; i++) {
+            if (url_copy[i] == '%' && isxdigit(url_copy[i + 1])
+                && isxdigit(url_copy[i + 2])) {
+
+                not_escaped = 0;
+                break;
+            }
+        }
+
+        if (not_escaped) {
+            url_external = calloc(1, strlen(url_copy) * 3 + 1);
+            GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error);
+
+            for (i = 0; i <= strlen(url_copy); i++) {
+                switch (url_copy[i]) {
+                    case (' '):
+                    case ('"'):
+                    case ('%'):
+                    case ('<'):
+                    case ('>'):
+                    case ('['):
+                    case (']'):
+                    case ('`'):
+                    case ('^'):
+                    case ('{'):
+                    case ('}'):
+                        lxw_snprintf(url_external + strlen(url_external),
+                                     sizeof("%xx"), "%%%2x", url_copy[i]);
+                        break;
+                    default:
+                        url_external[strlen(url_external)] = url_copy[i];
+                }
+
+            }
+
+            free(url_copy);
+            url_copy = lxw_strdup(url_external);
+            GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
+
+            free(url_external);
+            url_external = NULL;
+        }
+    }
+
+    if (link_type == HYPERLINK_EXTERNAL) {
+        /* External Workbook links need to be modified into the right format.
+         * The URL will look something like "c:\temp\file.xlsx#Sheet!A1".
+         * We need the part to the left of the # as the URL and the part to
+         * the right as the "location" string (if it exists).
+         */
+
+        /* For external links change the dir separator from Unix to DOS. */
+        for (i = 0; i <= strlen(url_copy); i++)
+            if (url_copy[i] == '/')
+                url_copy[i] = '\\';
+
+        for (i = 0; i <= strlen(string_copy); i++)
+            if (string_copy[i] == '/')
+                string_copy[i] = '\\';
+
+        found_string = strchr(url_copy, '#');
+
+        if (found_string) {
+            url_string = lxw_strdup(found_string + 1);
+            GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
+
+            *found_string = '\0';
+        }
+
+        /* Look for Windows style "C:/" link or Windows share "\\" link. */
+        found_string = strchr(url_copy, ':');
+        if (!found_string)
+            found_string = strstr(url_copy, "\\\\");
+
+        if (found_string) {
+            /* Add the file:/// URI to the url if non-local. */
+            string_size = sizeof("file:///") + strlen(url_copy);
+            url_external = calloc(1, string_size);
+            GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error);
+
+            lxw_snprintf(url_external, string_size, "file:///%s", url_copy);
+
+        }
+
+        /* Convert a ./dir/file.xlsx link to dir/file.xlsx. */
+        found_string = strstr(url_copy, ".\\");
+        if (found_string == url_copy)
+            memmove(url_copy, url_copy + 2, strlen(url_copy) - 1);
+
+        if (url_external) {
+            free(url_copy);
+            url_copy = lxw_strdup(url_external);
+            GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
+
+            free(url_external);
+            url_external = NULL;
+        }
+
+    }
+
+    /* Excel limits escaped URL to 255 characters. */
+    if (lxw_utf8_strlen(url_copy) > 255)
+        goto mem_error;
+
+    err = worksheet_write_string(self, row_num, col_num, string_copy, format);
+    if (err)
+        goto mem_error;
+
+    link = _new_hyperlink_cell(row_num, col_num, link_type, url_copy,
+                               url_string, tooltip_copy);
+    GOTO_LABEL_ON_MEM_ERROR(link, mem_error);
+
+    _insert_hyperlink(self, row_num, col_num, link);
+
+    free(string_copy);
+    self->hlink_count++;
+    return LXW_NO_ERROR;
+
+mem_error:
+    free(string_copy);
+    free(url_copy);
+    free(url_external);
+    free(url_string);
+    free(tooltip_copy);
+    return LXW_ERROR_MEMORY_MALLOC_FAILED;
+}
+
+/*
+ * Write a hyperlink/url to an Excel file.
+ */
+lxw_error
+worksheet_write_url(lxw_worksheet *self,
+                    lxw_row_t row_num,
+                    lxw_col_t col_num, const char *url, lxw_format *format)
+{
+    return worksheet_write_url_opt(self, row_num, col_num, url, format, NULL,
+                                   NULL);
+}
+
+/*
+ * Set the properties of a single column or a range of columns with options.
+ */
+lxw_error
+worksheet_set_column_opt(lxw_worksheet *self,
+                         lxw_col_t firstcol,
+                         lxw_col_t lastcol,
+                         double width,
+                         lxw_format *format,
+                         lxw_row_col_options *user_options)
+{
+    lxw_col_options *copied_options;
+    uint8_t ignore_row = LXW_TRUE;
+    uint8_t ignore_col = LXW_TRUE;
+    uint8_t hidden = LXW_FALSE;
+    uint8_t level = 0;
+    uint8_t collapsed = LXW_FALSE;
+    lxw_col_t col;
+    lxw_error err;
+
+    if (user_options) {
+        hidden = user_options->hidden;
+        level = user_options->level;
+        collapsed = user_options->collapsed;
+    }
+
+    /* Ensure second col is larger than first. */
+    if (firstcol > lastcol) {
+        lxw_col_t tmp = firstcol;
+        firstcol = lastcol;
+        lastcol = tmp;
+    }
+
+    /* Ensure that the cols are valid and store max and min values.
+     * NOTE: The check shouldn't modify the row dimensions and should only
+     *       modify the column dimensions in certain cases. */
+    if (format != NULL || (width != LXW_DEF_COL_WIDTH && hidden))
+        ignore_col = LXW_FALSE;
+
+    err = _check_dimensions(self, 0, firstcol, ignore_row, ignore_col);
+
+    if (!err)
+        err = _check_dimensions(self, 0, lastcol, ignore_row, ignore_col);
+
+    if (err)
+        return err;
+
+    /* Resize the col_options array if required. */
+    if (firstcol >= self->col_options_max) {
+        lxw_col_t col;
+        lxw_col_t old_size = self->col_options_max;
+        lxw_col_t new_size = _next_power_of_two(firstcol + 1);
+        lxw_col_options **new_ptr = realloc(self->col_options,
+                                            new_size *
+                                            sizeof(lxw_col_options *));
+
+        if (new_ptr) {
+            for (col = old_size; col < new_size; col++)
+                new_ptr[col] = NULL;
+
+            self->col_options = new_ptr;
+            self->col_options_max = new_size;
+        }
+        else {
+            return LXW_ERROR_MEMORY_MALLOC_FAILED;
+        }
+    }
+
+    /* Resize the col_formats array if required. */
+    if (lastcol >= self->col_formats_max) {
+        lxw_col_t col;
+        lxw_col_t old_size = self->col_formats_max;
+        lxw_col_t new_size = _next_power_of_two(lastcol + 1);
+        lxw_format **new_ptr = realloc(self->col_formats,
+                                       new_size * sizeof(lxw_format *));
+
+        if (new_ptr) {
+            for (col = old_size; col < new_size; col++)
+                new_ptr[col] = NULL;
+
+            self->col_formats = new_ptr;
+            self->col_formats_max = new_size;
+        }
+        else {
+            return LXW_ERROR_MEMORY_MALLOC_FAILED;
+        }
+    }
+
+    /* Store the column options. */
+    copied_options = calloc(1, sizeof(lxw_col_options));
+    RETURN_ON_MEM_ERROR(copied_options, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    /* Ensure the level is <= 7). */
+    if (level > 7)
+        level = 7;
+
+    if (level > self->outline_col_level)
+        self->outline_col_level = level;
+
+    /* Set the column properties. */
+    copied_options->firstcol = firstcol;
+    copied_options->lastcol = lastcol;
+    copied_options->width = width;
+    copied_options->format = format;
+    copied_options->hidden = hidden;
+    copied_options->level = level;
+    copied_options->collapsed = collapsed;
+
+    self->col_options[firstcol] = copied_options;
+
+    /* Store the column formats for use when writing cell data. */
+    for (col = firstcol; col <= lastcol; col++) {
+        self->col_formats[col] = format;
+    }
+
+    /* Store the column change to allow optimizations. */
+    self->col_size_changed = LXW_TRUE;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the properties of a single column or a range of columns.
+ */
+lxw_error
+worksheet_set_column(lxw_worksheet *self,
+                     lxw_col_t firstcol,
+                     lxw_col_t lastcol, double width, lxw_format *format)
+{
+    return worksheet_set_column_opt(self, firstcol, lastcol, width, format,
+                                    NULL);
+}
+
+/*
+ * Set the properties of a row with options.
+ */
+lxw_error
+worksheet_set_row_opt(lxw_worksheet *self,
+                      lxw_row_t row_num,
+                      double height,
+                      lxw_format *format, lxw_row_col_options *user_options)
+{
+
+    lxw_col_t min_col;
+    uint8_t hidden = LXW_FALSE;
+    uint8_t level = 0;
+    uint8_t collapsed = LXW_FALSE;
+    lxw_row *row;
+    lxw_error err;
+
+    if (user_options) {
+        hidden = user_options->hidden;
+        level = user_options->level;
+        collapsed = user_options->collapsed;
+    }
+
+    /* Use minimum col in _check_dimensions(). */
+    if (self->dim_colmin != LXW_COL_MAX)
+        min_col = self->dim_colmin;
+    else
+        min_col = 0;
+
+    err = _check_dimensions(self, row_num, min_col, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    /* If the height is 0 the row is hidden and the height is the default. */
+    if (height == 0) {
+        hidden = LXW_TRUE;
+        height = self->default_row_height;
+    }
+
+    /* Ensure the level is <= 7). */
+    if (level > 7)
+        level = 7;
+
+    if (level > self->outline_row_level)
+        self->outline_row_level = level;
+
+    /* Store the row properties. */
+    row = _get_row(self, row_num);
+
+    row->height = height;
+    row->format = format;
+    row->hidden = hidden;
+    row->level = level;
+    row->collapsed = collapsed;
+    row->row_changed = LXW_TRUE;
+
+    if (height != self->default_row_height)
+        row->height_changed = LXW_TRUE;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the properties of a row.
+ */
+lxw_error
+worksheet_set_row(lxw_worksheet *self,
+                  lxw_row_t row_num, double height, lxw_format *format)
+{
+    return worksheet_set_row_opt(self, row_num, height, format, NULL);
+}
+
+/*
+ * Merge a range of cells. The first cell should contain the data and the others
+ * should be blank. All cells should contain the same format.
+ */
+lxw_error
+worksheet_merge_range(lxw_worksheet *self, lxw_row_t first_row,
+                      lxw_col_t first_col, lxw_row_t last_row,
+                      lxw_col_t last_col, const char *string,
+                      lxw_format *format)
+{
+    lxw_merged_range *merged_range;
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    lxw_error err;
+
+    /* Excel doesn't allow a single cell to be merged */
+    if (first_row == last_row && first_col == last_col)
+        return LXW_ERROR_PARAMETER_VALIDATION;
+
+    /* Swap last row/col with first row/col as necessary */
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    /* Check that column number is valid and store the max value */
+    err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    /* Store the merge range. */
+    merged_range = calloc(1, sizeof(lxw_merged_range));
+    RETURN_ON_MEM_ERROR(merged_range, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    merged_range->first_row = first_row;
+    merged_range->first_col = first_col;
+    merged_range->last_row = last_row;
+    merged_range->last_col = last_col;
+
+    STAILQ_INSERT_TAIL(self->merged_ranges, merged_range, list_pointers);
+    self->merged_range_count++;
+
+    /* Write the first cell */
+    worksheet_write_string(self, first_row, first_col, string, format);
+
+    /* Pad out the rest of the area with formatted blank cells. */
+    for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
+        for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
+            if (tmp_row == first_row && tmp_col == first_col)
+                continue;
+
+            worksheet_write_blank(self, tmp_row, tmp_col, format);
+        }
+    }
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the autofilter area in the worksheet.
+ */
+lxw_error
+worksheet_autofilter(lxw_worksheet *self, lxw_row_t first_row,
+                     lxw_col_t first_col, lxw_row_t last_row,
+                     lxw_col_t last_col)
+{
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    lxw_error err;
+
+    /* Excel doesn't allow a single cell to be merged */
+    if (first_row == last_row && first_col == last_col)
+        return LXW_ERROR_PARAMETER_VALIDATION;
+
+    /* Swap last row/col with first row/col as necessary */
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    /* Check that column number is valid and store the max value */
+    err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
+    if (err)
+        return err;
+
+    self->autofilter.in_use = LXW_TRUE;
+    self->autofilter.first_row = first_row;
+    self->autofilter.first_col = first_col;
+    self->autofilter.last_row = last_row;
+    self->autofilter.last_col = last_col;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
+ * highlighted.
+ */
+void
+worksheet_select(lxw_worksheet *self)
+{
+    self->selected = LXW_TRUE;
+
+    /* Selected worksheet can't be hidden. */
+    self->hidden = LXW_FALSE;
+}
+
+/*
+ * Set this worksheet as the active worksheet, i.e. the worksheet that is
+ * displayed when the workbook is opened. Also set it as selected.
+ */
+void
+worksheet_activate(lxw_worksheet *self)
+{
+    self->selected = LXW_TRUE;
+    self->active = LXW_TRUE;
+
+    /* Active worksheet can't be hidden. */
+    self->hidden = LXW_FALSE;
+
+    *self->active_sheet = self->index;
+}
+
+/*
+ * Set this worksheet as the first visible sheet. This is necessary
+ * when there are a large number of worksheets and the activated
+ * worksheet is not visible on the screen.
+ */
+void
+worksheet_set_first_sheet(lxw_worksheet *self)
+{
+    /* Active worksheet can't be hidden. */
+    self->hidden = LXW_FALSE;
+
+    *self->first_sheet = self->index;
+}
+
+/*
+ * Hide this worksheet.
+ */
+void
+worksheet_hide(lxw_worksheet *self)
+{
+    self->hidden = LXW_TRUE;
+
+    /* A hidden worksheet shouldn't be active or selected. */
+    self->selected = LXW_FALSE;
+
+    /* If this is active_sheet or first_sheet reset the workbook value. */
+    if (*self->first_sheet == self->index)
+        *self->first_sheet = 0;
+
+    if (*self->active_sheet == self->index)
+        *self->active_sheet = 0;
+}
+
+/*
+ * Set which cell or cells are selected in a worksheet.
+ */
+void
+worksheet_set_selection(lxw_worksheet *self,
+                        lxw_row_t first_row, lxw_col_t first_col,
+                        lxw_row_t last_row, lxw_col_t last_col)
+{
+    lxw_selection *selection;
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    char active_cell[LXW_MAX_CELL_RANGE_LENGTH];
+    char sqref[LXW_MAX_CELL_RANGE_LENGTH];
+
+    /* Only allow selection to be set once to avoid freeing/re-creating it. */
+    if (!STAILQ_EMPTY(self->selections))
+        return;
+
+    /* Excel doesn't set a selection for cell A1 since it is the default. */
+    if (first_row == 0 && first_col == 0 && last_row == 0 && last_col == 0)
+        return;
+
+    selection = calloc(1, sizeof(lxw_selection));
+    RETURN_VOID_ON_MEM_ERROR(selection);
+
+    /* Set the cell range selection. Do this before swapping max/min to  */
+    /* allow the selection direction to be reversed. */
+    lxw_rowcol_to_cell(active_cell, first_row, first_col);
+
+    /* Swap last row/col for first row/col if necessary. */
+    if (first_row > last_row) {
+        tmp_row = first_row;
+        first_row = last_row;
+        last_row = tmp_row;
+    }
+
+    if (first_col > last_col) {
+        tmp_col = first_col;
+        first_col = last_col;
+        last_col = tmp_col;
+    }
+
+    /* If the first and last cell are the same write a single cell. */
+    if ((first_row == last_row) && (first_col == last_col))
+        lxw_rowcol_to_cell(sqref, first_row, first_col);
+    else
+        lxw_rowcol_to_range(sqref, first_row, first_col, last_row, last_col);
+
+    lxw_strcpy(selection->pane, "");
+    lxw_strcpy(selection->active_cell, active_cell);
+    lxw_strcpy(selection->sqref, sqref);
+
+    STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
+}
+
+/*
+ * Set panes and mark them as frozen. With extra options.
+ */
+void
+worksheet_freeze_panes_opt(lxw_worksheet *self,
+                           lxw_row_t first_row, lxw_col_t first_col,
+                           lxw_row_t top_row, lxw_col_t left_col,
+                           uint8_t type)
+{
+    self->panes.first_row = first_row;
+    self->panes.first_col = first_col;
+    self->panes.top_row = top_row;
+    self->panes.left_col = left_col;
+    self->panes.x_split = 0.0;
+    self->panes.y_split = 0.0;
+
+    if (type)
+        self->panes.type = FREEZE_SPLIT_PANES;
+    else
+        self->panes.type = FREEZE_PANES;
+}
+
+/*
+ * Set panes and mark them as frozen.
+ */
+void
+worksheet_freeze_panes(lxw_worksheet *self,
+                       lxw_row_t first_row, lxw_col_t first_col)
+{
+    worksheet_freeze_panes_opt(self, first_row, first_col,
+                               first_row, first_col, 0);
+}
+
+/*
+ * Set panes and mark them as split.With extra options.
+ */
+void
+worksheet_split_panes_opt(lxw_worksheet *self,
+                          double y_split, double x_split,
+                          lxw_row_t top_row, lxw_col_t left_col)
+{
+    self->panes.first_row = 0;
+    self->panes.first_col = 0;
+    self->panes.top_row = top_row;
+    self->panes.left_col = left_col;
+    self->panes.x_split = x_split;
+    self->panes.y_split = y_split;
+    self->panes.type = SPLIT_PANES;
+}
+
+/*
+ * Set panes and mark them as split.
+ */
+void
+worksheet_split_panes(lxw_worksheet *self, double y_split, double x_split)
+{
+    worksheet_split_panes_opt(self, y_split, x_split, 0, 0);
+}
+
+/*
+ * Set the page orientation as portrait.
+ */
+void
+worksheet_set_portrait(lxw_worksheet *self)
+{
+    self->orientation = LXW_PORTRAIT;
+    self->page_setup_changed = LXW_TRUE;
+}
+
+/*
+ * Set the page orientation as landscape.
+ */
+void
+worksheet_set_landscape(lxw_worksheet *self)
+{
+    self->orientation = LXW_LANDSCAPE;
+    self->page_setup_changed = LXW_TRUE;
+}
+
+/*
+ * Set the page view mode for Mac Excel.
+ */
+void
+worksheet_set_page_view(lxw_worksheet *self)
+{
+    self->page_view = LXW_TRUE;
+}
+
+/*
+ * Set the paper type. Example. 1 = US Letter, 9 = A4
+ */
+void
+worksheet_set_paper(lxw_worksheet *self, uint8_t paper_size)
+{
+    self->paper_size = paper_size;
+    self->page_setup_changed = LXW_TRUE;
+}
+
+/*
+ * Set the order in which pages are printed.
+ */
+void
+worksheet_print_across(lxw_worksheet *self)
+{
+    self->page_order = LXW_PRINT_ACROSS;
+    self->page_setup_changed = LXW_TRUE;
+}
+
+/*
+ * Set all the page margins in inches.
+ */
+void
+worksheet_set_margins(lxw_worksheet *self, double left, double right,
+                      double top, double bottom)
+{
+
+    if (left >= 0)
+        self->margin_left = left;
+
+    if (right >= 0)
+        self->margin_right = right;
+
+    if (top >= 0)
+        self->margin_top = top;
+
+    if (bottom >= 0)
+        self->margin_bottom = bottom;
+}
+
+/*
+ * Set the page header caption and options.
+ */
+lxw_error
+worksheet_set_header_opt(lxw_worksheet *self, const char *string,
+                         lxw_header_footer_options *options)
+{
+    if (options) {
+        if (options->margin >= 0.0)
+            self->margin_header = options->margin;
+    }
+
+    if (!string)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX)
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+
+    lxw_strcpy(self->header, string);
+    self->header_footer_changed = 1;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the page footer caption and options.
+ */
+lxw_error
+worksheet_set_footer_opt(lxw_worksheet *self, const char *string,
+                         lxw_header_footer_options *options)
+{
+    if (options) {
+        if (options->margin >= 0.0)
+            self->margin_footer = options->margin;
+    }
+
+    if (!string)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX)
+        return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+
+    lxw_strcpy(self->footer, string);
+    self->header_footer_changed = 1;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the page header caption.
+ */
+lxw_error
+worksheet_set_header(lxw_worksheet *self, const char *string)
+{
+    return worksheet_set_header_opt(self, string, NULL);
+}
+
+/*
+ * Set the page footer caption.
+ */
+lxw_error
+worksheet_set_footer(lxw_worksheet *self, const char *string)
+{
+    return worksheet_set_footer_opt(self, string, NULL);
+}
+
+/*
+ * Set the option to show/hide gridlines on the screen and the printed page.
+ */
+void
+worksheet_gridlines(lxw_worksheet *self, uint8_t option)
+{
+    if (option == LXW_HIDE_ALL_GRIDLINES) {
+        self->print_gridlines = 0;
+        self->screen_gridlines = 0;
+    }
+
+    if (option & LXW_SHOW_SCREEN_GRIDLINES) {
+        self->screen_gridlines = 1;
+    }
+
+    if (option & LXW_SHOW_PRINT_GRIDLINES) {
+        self->print_gridlines = 1;
+        self->print_options_changed = 1;
+    }
+}
+
+/*
+ * Center the page horizontally.
+ */
+void
+worksheet_center_horizontally(lxw_worksheet *self)
+{
+    self->print_options_changed = 1;
+    self->hcenter = 1;
+}
+
+/*
+ * Center the page horizontally.
+ */
+void
+worksheet_center_vertically(lxw_worksheet *self)
+{
+    self->print_options_changed = 1;
+    self->vcenter = 1;
+}
+
+/*
+ * Set the option to print the row and column headers on the printed page.
+ */
+void
+worksheet_print_row_col_headers(lxw_worksheet *self)
+{
+    self->print_headers = 1;
+    self->print_options_changed = 1;
+}
+
+/*
+ * Set the rows to repeat at the top of each printed page.
+ */
+lxw_error
+worksheet_repeat_rows(lxw_worksheet *self, lxw_row_t first_row,
+                      lxw_row_t last_row)
+{
+    lxw_row_t tmp_row;
+    lxw_error err;
+
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+
+    err = _check_dimensions(self, last_row, 0, LXW_IGNORE, LXW_IGNORE);
+    if (err)
+        return err;
+
+    self->repeat_rows.in_use = LXW_TRUE;
+    self->repeat_rows.first_row = first_row;
+    self->repeat_rows.last_row = last_row;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the columns to repeat at the left hand side of each printed page.
+ */
+lxw_error
+worksheet_repeat_columns(lxw_worksheet *self, lxw_col_t first_col,
+                         lxw_col_t last_col)
+{
+    lxw_col_t tmp_col;
+    lxw_error err;
+
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    err = _check_dimensions(self, last_col, 0, LXW_IGNORE, LXW_IGNORE);
+    if (err)
+        return err;
+
+    self->repeat_cols.in_use = LXW_TRUE;
+    self->repeat_cols.first_col = first_col;
+    self->repeat_cols.last_col = last_col;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the print area in the current worksheet.
+ */
+lxw_error
+worksheet_print_area(lxw_worksheet *self, lxw_row_t first_row,
+                     lxw_col_t first_col, lxw_row_t last_row,
+                     lxw_col_t last_col)
+{
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    lxw_error err;
+
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    err = _check_dimensions(self, last_row, last_col, LXW_IGNORE, LXW_IGNORE);
+    if (err)
+        return err;
+
+    /* Ignore max area since it is the same as no print area in Excel. */
+    if (first_row == 0 && first_col == 0 && last_row == LXW_ROW_MAX - 1
+        && last_col == LXW_COL_MAX - 1) {
+        return LXW_NO_ERROR;
+    }
+
+    self->print_area.in_use = LXW_TRUE;
+    self->print_area.first_row = first_row;
+    self->print_area.last_row = last_row;
+    self->print_area.first_col = first_col;
+    self->print_area.last_col = last_col;
+
+    return LXW_NO_ERROR;
+}
+
+/* Store the vertical and horizontal number of pages that will define the
+ * maximum area printed.
+ */
+void
+worksheet_fit_to_pages(lxw_worksheet *self, uint16_t width, uint16_t height)
+{
+    self->fit_page = 1;
+    self->fit_width = width;
+    self->fit_height = height;
+    self->page_setup_changed = 1;
+}
+
+/*
+ * Set the start page number.
+ */
+void
+worksheet_set_start_page(lxw_worksheet *self, uint16_t start_page)
+{
+    self->page_start = start_page;
+}
+
+/*
+ * Set the scale factor for the printed page.
+ */
+void
+worksheet_set_print_scale(lxw_worksheet *self, uint16_t scale)
+{
+    /* Confine the scale to Excel"s range */
+    if (scale < 10 || scale > 400)
+        return;
+
+    /* Turn off "fit to page" option. */
+    self->fit_page = LXW_FALSE;
+
+    self->print_scale = scale;
+    self->page_setup_changed = LXW_TRUE;
+}
+
+/*
+ * Store the horizontal page breaks on a worksheet.
+ */
+lxw_error
+worksheet_set_h_pagebreaks(lxw_worksheet *self, lxw_row_t hbreaks[])
+{
+    uint16_t count = 0;
+
+    if (hbreaks == NULL)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    while (hbreaks[count])
+        count++;
+
+    /* The Excel 2007 specification says that the maximum number of page
+     * breaks is 1026. However, in practice it is actually 1023. */
+    if (count > LXW_BREAKS_MAX)
+        count = LXW_BREAKS_MAX;
+
+    self->hbreaks = calloc(count, sizeof(lxw_row_t));
+    RETURN_ON_MEM_ERROR(self->hbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED);
+    memcpy(self->hbreaks, hbreaks, count * sizeof(lxw_row_t));
+    self->hbreaks_count = count;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Store the vertical page breaks on a worksheet.
+ */
+lxw_error
+worksheet_set_v_pagebreaks(lxw_worksheet *self, lxw_col_t vbreaks[])
+{
+    uint16_t count = 0;
+
+    if (vbreaks == NULL)
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+
+    while (vbreaks[count])
+        count++;
+
+    /* The Excel 2007 specification says that the maximum number of page
+     * breaks is 1026. However, in practice it is actually 1023. */
+    if (count > LXW_BREAKS_MAX)
+        count = LXW_BREAKS_MAX;
+
+    self->vbreaks = calloc(count, sizeof(lxw_col_t));
+    RETURN_ON_MEM_ERROR(self->vbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED);
+    memcpy(self->vbreaks, vbreaks, count * sizeof(lxw_col_t));
+    self->vbreaks_count = count;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Set the worksheet zoom factor.
+ */
+void
+worksheet_set_zoom(lxw_worksheet *self, uint16_t scale)
+{
+    /* Confine the scale to Excel"s range */
+    if (scale < 10 || scale > 400) {
+        LXW_WARN("worksheet_set_zoom(): "
+                 "Zoom factor scale outside range: 10 <= zoom <= 400.");
+        return;
+    }
+
+    self->zoom = scale;
+}
+
+/*
+ * Hide cell zero values.
+ */
+void
+worksheet_hide_zero(lxw_worksheet *self)
+{
+    self->show_zeros = LXW_FALSE;
+}
+
+/*
+ * Display the worksheet right to left for some eastern versions of Excel.
+ */
+void
+worksheet_right_to_left(lxw_worksheet *self)
+{
+    self->right_to_left = LXW_TRUE;
+}
+
+/*
+ * Set the color of the worksheet tab.
+ */
+void
+worksheet_set_tab_color(lxw_worksheet *self, lxw_color_t color)
+{
+    self->tab_color = color;
+}
+
+/*
+ * Set the worksheet protection flags to prevent modification of worksheet
+ * objects.
+ */
+void
+worksheet_protect(lxw_worksheet *self, const char *password,
+                  lxw_protection *options)
+{
+    struct lxw_protection *protect = &self->protection;
+
+    /* Copy any user parameters to the internal structure. */
+    if (options) {
+        protect->no_select_locked_cells = options->no_select_locked_cells;
+        protect->no_select_unlocked_cells = options->no_select_unlocked_cells;
+        protect->format_cells = options->format_cells;
+        protect->format_columns = options->format_columns;
+        protect->format_rows = options->format_rows;
+        protect->insert_columns = options->insert_columns;
+        protect->insert_rows = options->insert_rows;
+        protect->insert_hyperlinks = options->insert_hyperlinks;
+        protect->delete_columns = options->delete_columns;
+        protect->delete_rows = options->delete_rows;
+        protect->sort = options->sort;
+        protect->autofilter = options->autofilter;
+        protect->pivot_tables = options->pivot_tables;
+        protect->scenarios = options->scenarios;
+        protect->objects = options->objects;
+    }
+
+    if (password) {
+        uint16_t hash = _hash_password(password);
+        lxw_snprintf(protect->hash, 5, "%X", hash);
+    }
+
+    protect->is_configured = LXW_TRUE;
+}
+
+/*
+ * Set the worksheet properties for outlines and grouping.
+ */
+void
+worksheet_outline_settings(lxw_worksheet *self,
+                           uint8_t visible,
+                           uint8_t symbols_below,
+                           uint8_t symbols_right, uint8_t auto_style)
+{
+    self->outline_on = visible;
+    self->outline_below = symbols_below;
+    self->outline_right = symbols_right;
+    self->outline_style = auto_style;
+
+    self->outline_changed = LXW_TRUE;
+}
+
+/*
+ * Set the default row properties
+ */
+void
+worksheet_set_default_row(lxw_worksheet *self, double height,
+                          uint8_t hide_unused_rows)
+{
+    if (height < 0)
+        height = self->default_row_height;
+
+    if (height != self->default_row_height) {
+        self->default_row_height = height;
+        self->row_size_changed = LXW_TRUE;
+    }
+
+    if (hide_unused_rows)
+        self->default_row_zeroed = LXW_TRUE;
+
+    self->default_row_set = LXW_TRUE;
+}
+
+/*
+ * Insert an image into the worksheet.
+ */
+lxw_error
+worksheet_insert_image_opt(lxw_worksheet *self,
+                           lxw_row_t row_num, lxw_col_t col_num,
+                           const char *filename,
+                           lxw_image_options *user_options)
+{
+    FILE *image_stream;
+    char *short_name;
+    lxw_image_options *options;
+
+    if (!filename) {
+        LXW_WARN("worksheet_insert_image()/_opt(): "
+                 "filename must be specified.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    /* Check that the image file exists and can be opened. */
+    image_stream = fopen(filename, "rb");
+    if (!image_stream) {
+        LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                         "file doesn't exist or can't be opened: %s.",
+                         filename);
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    /* Get the filename from the full path to add to the Drawing object. */
+    short_name = lxw_basename(filename);
+    if (!short_name) {
+        LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
+                         "couldn't get basename for file: %s.", filename);
+        fclose(image_stream);
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    /* Create a new object to hold the image options. */
+    options = calloc(1, sizeof(lxw_image_options));
+    if (!options) {
+        fclose(image_stream);
+        return LXW_ERROR_MEMORY_MALLOC_FAILED;
+    }
+
+    if (user_options) {
+        options->x_offset = user_options->x_offset;
+        options->y_offset = user_options->y_offset;
+        options->x_scale = user_options->x_scale;
+        options->y_scale = user_options->y_scale;
+    }
+
+    /* Copy other options or set defaults. */
+    options->filename = lxw_strdup(filename);
+    options->short_name = lxw_strdup(short_name);
+    options->stream = image_stream;
+    options->row = row_num;
+    options->col = col_num;
+
+    if (!options->x_scale)
+        options->x_scale = 1;
+
+    if (!options->y_scale)
+        options->y_scale = 1;
+
+    if (_get_image_properties(options) == LXW_NO_ERROR) {
+        STAILQ_INSERT_TAIL(self->image_data, options, list_pointers);
+        fclose(image_stream);
+        return LXW_NO_ERROR;
+    }
+    else {
+        free(options);
+        fclose(image_stream);
+        return LXW_ERROR_IMAGE_DIMENSIONS;
+    }
+}
+
+/*
+ * Insert an image into the worksheet.
+ */
+lxw_error
+worksheet_insert_image(lxw_worksheet *self,
+                       lxw_row_t row_num, lxw_col_t col_num,
+                       const char *filename)
+{
+    return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL);
+}
+
+/*
+ * Insert an chart into the worksheet.
+ */
+lxw_error
+worksheet_insert_chart_opt(lxw_worksheet *self,
+                           lxw_row_t row_num, lxw_col_t col_num,
+                           lxw_chart *chart, lxw_image_options *user_options)
+{
+    lxw_image_options *options;
+    lxw_chart_series *series;
+
+    if (!chart) {
+        LXW_WARN("worksheet_insert_chart()/_opt(): chart must be non-NULL.");
+        return LXW_ERROR_NULL_PARAMETER_IGNORED;
+    }
+
+    /* Check that the chart isn't being used more than once. */
+    if (chart->in_use) {
+        LXW_WARN("worksheet_insert_chart()/_opt(): the same chart object "
+                 "cannot be inserted in a worksheet more than once.");
+
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    /* Check that the chart has a data series. */
+    if (STAILQ_EMPTY(chart->series_list)) {
+        LXW_WARN
+            ("worksheet_insert_chart()/_opt(): chart must have a series.");
+
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    /* Check that the chart has a 'values' series. */
+    STAILQ_FOREACH(series, chart->series_list, list_pointers) {
+        if (!series->values->formula && !series->values->sheetname) {
+            LXW_WARN("worksheet_insert_chart()/_opt(): chart must have a "
+                     "'values' series.");
+
+            return LXW_ERROR_PARAMETER_VALIDATION;
+        }
+    }
+
+    /* Create a new object to hold the chart image options. */
+    options = calloc(1, sizeof(lxw_image_options));
+    RETURN_ON_MEM_ERROR(options, LXW_ERROR_MEMORY_MALLOC_FAILED);
+
+    if (user_options) {
+        options->x_offset = user_options->x_offset;
+        options->y_offset = user_options->y_offset;
+        options->x_scale = user_options->x_scale;
+        options->y_scale = user_options->y_scale;
+    }
+
+    /* Copy other options or set defaults. */
+    options->row = row_num;
+    options->col = col_num;
+
+    /* TODO. Read defaults from chart. */
+    options->width = 480;
+    options->height = 288;
+
+    if (!options->x_scale)
+        options->x_scale = 1;
+
+    if (!options->y_scale)
+        options->y_scale = 1;
+
+    /* Store chart references so they can be ordered in the workbook. */
+    options->chart = chart;
+
+    STAILQ_INSERT_TAIL(self->chart_data, options, list_pointers);
+
+    chart->in_use = LXW_TRUE;
+
+    return LXW_NO_ERROR;
+}
+
+/*
+ * Insert an image into the worksheet.
+ */
+lxw_error
+worksheet_insert_chart(lxw_worksheet *self,
+                       lxw_row_t row_num, lxw_col_t col_num, lxw_chart *chart)
+{
+    return worksheet_insert_chart_opt(self, row_num, col_num, chart, NULL);
+}
+
+/*
+ * Add a data validation to a worksheet, for a range. Ironically this requires
+ * a lot of validation of the user input.
+ */
+lxw_error
+worksheet_data_validation_range(lxw_worksheet *self, lxw_row_t first_row,
+                                lxw_col_t first_col,
+                                lxw_row_t last_row,
+                                lxw_col_t last_col,
+                                lxw_data_validation *validation)
+{
+    lxw_data_validation *copy;
+    uint8_t is_between = LXW_FALSE;
+    uint8_t is_formula = LXW_FALSE;
+    uint8_t has_criteria = LXW_TRUE;
+    lxw_error err;
+    lxw_row_t tmp_row;
+    lxw_col_t tmp_col;
+    size_t length;
+
+    /* No action is required for validation type 'any' unless there are
+     * input messages to display.*/
+    if (validation->validate == LXW_VALIDATION_TYPE_ANY
+        && !(validation->input_title || validation->input_message)) {
+
+        return LXW_NO_ERROR;
+    }
+
+    /* Check for formula types. */
+    switch (validation->validate) {
+        case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
+        case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
+        case LXW_VALIDATION_TYPE_LIST_FORMULA:
+        case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
+        case LXW_VALIDATION_TYPE_DATE_FORMULA:
+        case LXW_VALIDATION_TYPE_TIME_FORMULA:
+        case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
+            is_formula = LXW_TRUE;
+            break;
+    }
+
+    /* Check for types without a criteria. */
+    switch (validation->validate) {
+        case LXW_VALIDATION_TYPE_LIST:
+        case LXW_VALIDATION_TYPE_LIST_FORMULA:
+        case LXW_VALIDATION_TYPE_ANY:
+        case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
+            has_criteria = LXW_FALSE;
+            break;
+    }
+
+    /* Check that a validation parameter has been specified
+     * except for 'list', 'any' and 'custom'. */
+    if (has_criteria && validation->criteria == LXW_VALIDATION_CRITERIA_NONE) {
+
+        LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
+                        "criteria parameter must be specified.");
+        return LXW_ERROR_PARAMETER_VALIDATION;
+    }
+
+    /* Check for "between" criteria so we can do additional checks. */
+    if (has_criteria
+        && (validation->criteria == LXW_VALIDATION_CRITERIA_BETWEEN
+            || validation->criteria == LXW_VALIDATION_CRITERIA_NOT_BETWEEN)) {
+
+        is_between = LXW_TRUE;
+    }
+
+    /* Check that formula values are non NULL. */
+    if (is_formula) {
+        if (is_between) {
+            if (!validation->minimum_formula) {
+                LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
+                                "minimum_formula parameter cannot be NULL.");
+                return LXW_ERROR_PARAMETER_VALIDATION;
+            }
+            if (!validation->maximum_formula) {
+                LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
+                                "maximum_formula parameter cannot be NULL.");
+                return LXW_ERROR_PARAMETER_VALIDATION;
+            }
+        }
+        else {
+            if (!validation->value_formula) {
+                LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
+                                "formula parameter cannot be NULL.");
+                return LXW_ERROR_PARAMETER_VALIDATION;
+            }
+        }
+    }
+
+    /* Check Excel limitations on input strings. */
+    if (validation->input_title) {
+        length = lxw_utf8_strlen(validation->input_title);
+        if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) {
+            LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
+                             "input_title length > Excel limit of %d.",
+                             LXW_VALIDATION_MAX_TITLE_LENGTH);
+            return LXW_ERROR_32_STRING_LENGTH_EXCEEDED;
+        }
+    }
+
+    if (validation->error_title) {
+        length = lxw_utf8_strlen(validation->error_title);
+        if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) {
+            LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
+                             "error_title length > Excel limit of %d.",
+                             LXW_VALIDATION_MAX_TITLE_LENGTH);
+            return LXW_ERROR_32_STRING_LENGTH_EXCEEDED;
+        }
+    }
+
+    if (validation->input_message) {
+        length = lxw_utf8_strlen(validation->input_message);
+        if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
+            LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
+                             "input_message length > Excel limit of %d.",
+                             LXW_VALIDATION_MAX_STRING_LENGTH);
+            return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+        }
+    }
+
+    if (validation->error_message) {
+        length = lxw_utf8_strlen(validation->error_message);
+        if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
+            LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
+                             "error_message length > Excel limit of %d.",
+                             LXW_VALIDATION_MAX_STRING_LENGTH);
+            return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+        }
+    }
+
+    if (validation->validate == LXW_VALIDATION_TYPE_LIST) {
+        length = _validation_list_length(validation->value_list);
+
+        if (length == 0) {
+            LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
+                            "list parameters cannot be zero.");
+            return LXW_ERROR_PARAMETER_VALIDATION;
+        }
+
+        if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
+            LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
+                             "list length with commas > Excel limit of %d.",
+                             LXW_VALIDATION_MAX_STRING_LENGTH);
+            return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
+        }
+    }
+
+    /* Swap last row/col with first row/col as necessary */
+    if (first_row > last_row) {
+        tmp_row = last_row;
+        last_row = first_row;
+        first_row = tmp_row;
+    }
+    if (first_col > last_col) {
+        tmp_col = last_col;
+        last_col = first_col;
+        first_col = tmp_col;
+    }
+
+    /* Check that dimensions are valid but don't store them. */
+    err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE);
+    if (err)
+        return err;
+
+    /* Create a copy of the parameters from the user data validation. */
+    copy = calloc(1, sizeof(lxw_data_validation));
+    GOTO_LABEL_ON_MEM_ERROR(copy, mem_error);
+
+    /* Create the data validation range. */
+    if (first_row == last_row && first_col == last_col)
+        lxw_rowcol_to_cell(copy->sqref, first_row, last_col);
+    else
+        lxw_rowcol_to_range(copy->sqref, first_row, first_col, last_row,
+                            last_col);
+
+    /* Copy the parameters from the user data validation. */
+    copy->validate = validation->validate;
+    copy->value_number = validation->value_number;
+    copy->error_type = validation->error_type;
+    copy->dropdown = validation->dropdown;
+    copy->is_between = is_between;
+
+    if (has_criteria)
+        copy->criteria = validation->criteria;
+
+    if (is_between) {
+        copy->value_number = validation->minimum_number;
+        copy->maximum_number = validation->maximum_number;
+    }
+
+    /* Copy the input/error titles and messages. */
+    if (validation->input_title) {
+        copy->input_title = lxw_strdup_formula(validation->input_title);
+        GOTO_LABEL_ON_MEM_ERROR(copy->input_title, mem_error);
+    }
+
+    if (validation->input_message) {
+        copy->input_message = lxw_strdup_formula(validation->input_message);
+        GOTO_LABEL_ON_MEM_ERROR(copy->input_message, mem_error);
+    }
+
+    if (validation->error_title) {
+        copy->error_title = lxw_strdup_formula(validation->error_title);
+        GOTO_LABEL_ON_MEM_ERROR(copy->error_title, mem_error);
+    }
+
+    if (validation->error_message) {
+        copy->error_message = lxw_strdup_formula(validation->error_message);
+        GOTO_LABEL_ON_MEM_ERROR(copy->error_message, mem_error);
+    }
+
+    /* Copy the formula strings. */
+    if (is_formula) {
+        if (is_between) {
+            copy->value_formula =
+                lxw_strdup_formula(validation->minimum_formula);
+            GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
+            copy->maximum_formula =
+                lxw_strdup_formula(validation->maximum_formula);
+            GOTO_LABEL_ON_MEM_ERROR(copy->maximum_formula, mem_error);
+        }
+        else {
+            copy->value_formula =
+                lxw_strdup_formula(validation->value_formula);
+            GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
+        }
+    }
+
+    /* Copy the validation list as a csv string. */
+    if (validation->validate == LXW_VALIDATION_TYPE_LIST) {
+        copy->value_formula = _validation_list_to_csv(validation->value_list);
+        GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
+    }
+
+    if (validation->validate == LXW_VALIDATION_TYPE_LIST_FORMULA) {
+        copy->value_formula = lxw_strdup_formula(validation->value_formula);
+        GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
+    }
+
+    if (validation->validate == LXW_VALIDATION_TYPE_DATE
+        || validation->validate == LXW_VALIDATION_TYPE_TIME) {
+        if (is_between) {
+            copy->value_number =
+                lxw_datetime_to_excel_date(&validation->minimum_datetime,
+                                           LXW_EPOCH_1900);
+            copy->maximum_number =
+                lxw_datetime_to_excel_date(&validation->maximum_datetime,
+                                           LXW_EPOCH_1900);
+        }
+        else {
+            copy->value_number =
+                lxw_datetime_to_excel_date(&validation->value_datetime,
+                                           LXW_EPOCH_1900);
+        }
+    }
+
+    /* These options are on by default so we can't take plain booleans. */
+    copy->ignore_blank = validation->ignore_blank ^ 1;
+    copy->show_input = validation->show_input ^ 1;
+    copy->show_error = validation->show_error ^ 1;
+
+    STAILQ_INSERT_TAIL(self->data_validations, copy, list_pointers);
+
+    self->num_validations++;
+
+    return LXW_NO_ERROR;
+
+mem_error:
+    _free_data_validation(copy);
+    return LXW_ERROR_MEMORY_MALLOC_FAILED;
+}
+
+/*
+ * Add a data validation to a worksheet, for a cell.
+ */
+lxw_error
+worksheet_data_validation_cell(lxw_worksheet *self, lxw_row_t row,
+                               lxw_col_t col, lxw_data_validation *validation)
+{
+    return worksheet_data_validation_range(self, row, col,
+                                           row, col, validation);
+}

+ 355 - 0
library/src/xmlwriter.c

@@ -0,0 +1,355 @@
+/*****************************************************************************
+ * xmlwriter - A base library for libxlsxwriter libraries.
+ *
+ * Used in conjunction with the libxlsxwriter library.
+ *
+ * Copyright 2014-2018, John McNamara, [email protected]. See LICENSE.txt.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "xlsxwriter/xmlwriter.h"
+
+#define LXW_AMP  "&amp;"
+#define LXW_LT   "&lt;"
+#define LXW_GT   "&gt;"
+#define LXW_QUOT "&quot;"
+
+/* Defines. */
+#define LXW_MAX_ENCODED_ATTRIBUTE_LENGTH (LXW_MAX_ATTRIBUTE_LENGTH*6)
+
+/* Forward declarations. */
+STATIC char *_escape_attributes(struct xml_attribute *attribute);
+
+char *lxw_escape_data(const char *data);
+
+STATIC void _fprint_escaped_attributes(FILE * xmlfile,
+                                       struct xml_attribute_list *attributes);
+
+STATIC void _fprint_escaped_data(FILE * xmlfile, const char *data);
+
+/*
+ * Write the XML declaration.
+ */
+void
+lxw_xml_declaration(FILE * xmlfile)
+{
+    fprintf(xmlfile, "<?xml version=\"1.0\" "
+            "encoding=\"UTF-8\" standalone=\"yes\"?>\n");
+}
+
+/*
+ * Write an XML start tag with optional attributes.
+ */
+void
+lxw_xml_start_tag(FILE * xmlfile,
+                  const char *tag, struct xml_attribute_list *attributes)
+{
+    fprintf(xmlfile, "<%s", tag);
+
+    _fprint_escaped_attributes(xmlfile, attributes);
+
+    fprintf(xmlfile, ">");
+}
+
+/*
+ * Write an XML start tag with optional, unencoded, attributes.
+ * This is a minor speed optimization for elements that don't need encoding.
+ */
+void
+lxw_xml_start_tag_unencoded(FILE * xmlfile,
+                            const char *tag,
+                            struct xml_attribute_list *attributes)
+{
+    struct xml_attribute *attribute;
+
+    fprintf(xmlfile, "<%s", tag);
+
+    if (attributes) {
+        STAILQ_FOREACH(attribute, attributes, list_entries) {
+            fprintf(xmlfile, " %s=\"%s\"", attribute->key, attribute->value);
+        }
+    }
+
+    fprintf(xmlfile, ">");
+}
+
+/*
+ * Write an XML end tag.
+ */
+void
+lxw_xml_end_tag(FILE * xmlfile, const char *tag)
+{
+    fprintf(xmlfile, "</%s>", tag);
+}
+
+/*
+ * Write an empty XML tag with optional attributes.
+ */
+void
+lxw_xml_empty_tag(FILE * xmlfile,
+                  const char *tag, struct xml_attribute_list *attributes)
+{
+    fprintf(xmlfile, "<%s", tag);
+
+    _fprint_escaped_attributes(xmlfile, attributes);
+
+    fprintf(xmlfile, "/>");
+}
+
+/*
+ * Write an XML start tag with optional, unencoded, attributes.
+ * This is a minor speed optimization for elements that don't need encoding.
+ */
+void
+lxw_xml_empty_tag_unencoded(FILE * xmlfile,
+                            const char *tag,
+                            struct xml_attribute_list *attributes)
+{
+    struct xml_attribute *attribute;
+
+    fprintf(xmlfile, "<%s", tag);
+
+    if (attributes) {
+        STAILQ_FOREACH(attribute, attributes, list_entries) {
+            fprintf(xmlfile, " %s=\"%s\"", attribute->key, attribute->value);
+        }
+    }
+
+    fprintf(xmlfile, "/>");
+}
+
+/*
+ * Write an XML element containing data with optional attributes.
+ */
+void
+lxw_xml_data_element(FILE * xmlfile,
+                     const char *tag,
+                     const char *data, struct xml_attribute_list *attributes)
+{
+    fprintf(xmlfile, "<%s", tag);
+
+    _fprint_escaped_attributes(xmlfile, attributes);
+
+    fprintf(xmlfile, ">");
+
+    _fprint_escaped_data(xmlfile, data);
+
+    fprintf(xmlfile, "</%s>", tag);
+}
+
+/*
+ * Escape XML characters in attributes.
+ */
+STATIC char *
+_escape_attributes(struct xml_attribute *attribute)
+{
+    char *encoded = (char *) calloc(LXW_MAX_ENCODED_ATTRIBUTE_LENGTH, 1);
+    char *p_encoded = encoded;
+    char *p_attr = attribute->value;
+
+    while (*p_attr) {
+        switch (*p_attr) {
+            case '&':
+                strncat(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1);
+                p_encoded += sizeof(LXW_AMP) - 1;
+                break;
+            case '<':
+                strncat(p_encoded, LXW_LT, sizeof(LXW_LT) - 1);
+                p_encoded += sizeof(LXW_LT) - 1;
+                break;
+            case '>':
+                strncat(p_encoded, LXW_GT, sizeof(LXW_GT) - 1);
+                p_encoded += sizeof(LXW_GT) - 1;
+                break;
+            case '"':
+                strncat(p_encoded, LXW_QUOT, sizeof(LXW_QUOT) - 1);
+                p_encoded += sizeof(LXW_QUOT) - 1;
+                break;
+            default:
+                *p_encoded = *p_attr;
+                p_encoded++;
+                break;
+        }
+        p_attr++;
+    }
+
+    return encoded;
+}
+
+/*
+ * Escape XML characters in data sections of tags.
+ * Note, this is different from _escape_attributes()
+ * in that double quotes are not escaped by Excel.
+ */
+char *
+lxw_escape_data(const char *data)
+{
+    size_t encoded_len = (strlen(data) * 5 + 1);
+
+    char *encoded = (char *) calloc(encoded_len, 1);
+    char *p_encoded = encoded;
+
+    while (*data) {
+        switch (*data) {
+            case '&':
+                strncat(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1);
+                p_encoded += sizeof(LXW_AMP) - 1;
+                break;
+            case '<':
+                strncat(p_encoded, LXW_LT, sizeof(LXW_LT) - 1);
+                p_encoded += sizeof(LXW_LT) - 1;
+                break;
+            case '>':
+                strncat(p_encoded, LXW_GT, sizeof(LXW_GT) - 1);
+                p_encoded += sizeof(LXW_GT) - 1;
+                break;
+            default:
+                *p_encoded = *data;
+                p_encoded++;
+                break;
+        }
+        data++;
+    }
+
+    return encoded;
+}
+
+/*
+ * Escape control characters in strings with with _xHHHH_.
+ */
+char *
+lxw_escape_control_characters(const char *string)
+{
+    size_t escape_len = sizeof("_xHHHH_") - 1;
+    size_t encoded_len = (strlen(string) * escape_len + 1);
+
+    char *encoded = (char *) calloc(encoded_len, 1);
+    char *p_encoded = encoded;
+
+    while (*string) {
+        switch (*string) {
+            case '\x01':
+            case '\x02':
+            case '\x03':
+            case '\x04':
+            case '\x05':
+            case '\x06':
+            case '\x07':
+            case '\x08':
+            case '\x0B':
+            case '\x0C':
+            case '\x0D':
+            case '\x0E':
+            case '\x0F':
+            case '\x10':
+            case '\x11':
+            case '\x12':
+            case '\x13':
+            case '\x14':
+            case '\x15':
+            case '\x16':
+            case '\x17':
+            case '\x18':
+            case '\x19':
+            case '\x1A':
+            case '\x1B':
+            case '\x1C':
+            case '\x1D':
+            case '\x1E':
+            case '\x1F':
+                lxw_snprintf(p_encoded, escape_len + 1, "_x%04X_", *string);
+                p_encoded += escape_len;
+                break;
+            default:
+                *p_encoded = *string;
+                p_encoded++;
+                break;
+        }
+        string++;
+    }
+
+    return encoded;
+}
+
+/* Write out escaped attributes. */
+STATIC void
+_fprint_escaped_attributes(FILE * xmlfile,
+                           struct xml_attribute_list *attributes)
+{
+    struct xml_attribute *attribute;
+
+    if (attributes) {
+        STAILQ_FOREACH(attribute, attributes, list_entries) {
+            fprintf(xmlfile, " %s=", attribute->key);
+
+            if (!strpbrk(attribute->value, "&<>\"")) {
+                fprintf(xmlfile, "\"%s\"", attribute->value);
+            }
+            else {
+                char *encoded = _escape_attributes(attribute);
+
+                if (encoded) {
+                    fprintf(xmlfile, "\"%s\"", encoded);
+
+                    free(encoded);
+                }
+            }
+        }
+    }
+}
+
+/* Write out escaped XML data. */
+STATIC void
+_fprint_escaped_data(FILE * xmlfile, const char *data)
+{
+    /* Escape the data section of the XML element. */
+    if (!strpbrk(data, "&<>")) {
+        fprintf(xmlfile, "%s", data);
+    }
+    else {
+        char *encoded = lxw_escape_data(data);
+        if (encoded) {
+            fprintf(xmlfile, "%s", encoded);
+            free(encoded);
+        }
+    }
+}
+
+/* Create a new string XML attribute. */
+struct xml_attribute *
+lxw_new_attribute_str(const char *key, const char *value)
+{
+    struct xml_attribute *attribute = malloc(sizeof(struct xml_attribute));
+
+    LXW_ATTRIBUTE_COPY(attribute->key, key);
+    LXW_ATTRIBUTE_COPY(attribute->value, value);
+
+    return attribute;
+}
+
+/* Create a new integer XML attribute. */
+struct xml_attribute *
+lxw_new_attribute_int(const char *key, uint32_t value)
+{
+    struct xml_attribute *attribute = malloc(sizeof(struct xml_attribute));
+
+    LXW_ATTRIBUTE_COPY(attribute->key, key);
+    lxw_snprintf(attribute->value, LXW_MAX_ATTRIBUTE_LENGTH, "%d", value);
+
+    return attribute;
+}
+
+/* Create a new double XML attribute. */
+struct xml_attribute *
+lxw_new_attribute_dbl(const char *key, double value)
+{
+    struct xml_attribute *attribute = malloc(sizeof(struct xml_attribute));
+
+    LXW_ATTRIBUTE_COPY(attribute->key, key);
+    lxw_sprintf_dbl(attribute->value, value);
+
+    return attribute;
+}

+ 5 - 0
library/third_party/minizip/README.txt

@@ -0,0 +1,5 @@
+The souce files in this directory are included in libxlsxwriter from the
+contrib/minizip/ directory of zlib-1.2.8.
+
+The files zip.h and ioapi.h have had a small number of comments modifed from
+C++ to C style to avoid warnings with -pedantic -ansi.

+ 131 - 0
library/third_party/minizip/crypt.h

@@ -0,0 +1,131 @@
+/* crypt.h -- base code for crypt/uncrypt ZIPfile
+
+
+   Version 1.01e, February 12th, 2005
+
+   Copyright (C) 1998-2005 Gilles Vollant
+
+   This code is a modified version of crypting code in Infozip distribution
+
+   The encryption/decryption parts of this source code (as opposed to the
+   non-echoing password parts) were originally written in Europe.  The
+   whole source package can be freely distributed, including from the USA.
+   (Prior to January 2000, re-export from the US was a violation of US law.)
+
+   This encryption code is a direct transcription of the algorithm from
+   Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
+   file (appnote.txt) is distributed with the PKZIP program (even in the
+   version without encryption capabilities).
+
+   If you don't need crypting in your application, just define symbols
+   NOCRYPT and NOUNCRYPT.
+
+   This code support the "Traditional PKWARE Encryption".
+
+   The new AES encryption added on Zip format by Winzip (see the page
+   http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong
+   Encryption is not supported.
+*/
+
+#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
+
+/***********************************************************************
+ * Return the next byte in the pseudo-random sequence
+ */
+static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab)
+{
+    unsigned temp;  /* POTENTIAL BUG:  temp*(temp^1) may overflow in an
+                     * unpredictable manner on 16-bit systems; not a problem
+                     * with any known compiler so far, though */
+
+    temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2;
+    return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
+}
+
+/***********************************************************************
+ * Update the encryption keys with the next byte of plain text
+ */
+static int update_keys(unsigned long* pkeys,const z_crc_t* pcrc_32_tab,int c)
+{
+    (*(pkeys+0)) = CRC32((*(pkeys+0)), c);
+    (*(pkeys+1)) += (*(pkeys+0)) & 0xff;
+    (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
+    {
+      register int keyshift = (int)((*(pkeys+1)) >> 24);
+      (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift);
+    }
+    return c;
+}
+
+
+/***********************************************************************
+ * Initialize the encryption keys and the random header according to
+ * the given password.
+ */
+static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t* pcrc_32_tab)
+{
+    *(pkeys+0) = 305419896L;
+    *(pkeys+1) = 591751049L;
+    *(pkeys+2) = 878082192L;
+    while (*passwd != '\0') {
+        update_keys(pkeys,pcrc_32_tab,(int)*passwd);
+        passwd++;
+    }
+}
+
+#define zdecode(pkeys,pcrc_32_tab,c) \
+    (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab)))
+
+#define zencode(pkeys,pcrc_32_tab,c,t) \
+    (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c))
+
+#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+
+#define RAND_HEAD_LEN  12
+   /* "last resort" source for second part of crypt seed pattern */
+#  ifndef ZCR_SEED2
+#    define ZCR_SEED2 3141592654UL     /* use PI as default pattern */
+#  endif
+
+static int crypthead(const char* passwd,      /* password string */
+                     unsigned char* buf,      /* where to write header */
+                     int bufSize,
+                     unsigned long* pkeys,
+                     const z_crc_t* pcrc_32_tab,
+                     unsigned long crcForCrypting)
+{
+    int n;                       /* index in random header */
+    int t;                       /* temporary */
+    int c;                       /* random byte */
+    unsigned char header[RAND_HEAD_LEN-2]; /* random header */
+    static unsigned calls = 0;   /* ensure different random header each time */
+
+    if (bufSize<RAND_HEAD_LEN)
+      return 0;
+
+    /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
+     * output of rand() to get less predictability, since rand() is
+     * often poorly implemented.
+     */
+    if (++calls == 1)
+    {
+        srand((unsigned)(time(NULL) ^ ZCR_SEED2));
+    }
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        c = (rand() >> 7) & 0xff;
+        header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t);
+    }
+    /* Encrypt random header (last two bytes is high word of crc) */
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t);
+    }
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t);
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
+    return n;
+}
+
+#endif

+ 247 - 0
library/third_party/minizip/ioapi.c

@@ -0,0 +1,247 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+*/
+
+#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS)))
+        #define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#if defined(__APPLE__) || defined(IOAPI_NO_64)
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+
+#include "ioapi.h"
+
+voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
+{
+    if (pfilefunc->zfile_func64.zopen64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
+    else
+    {
+        return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
+    }
+}
+
+long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
+    else
+    {
+        uLong offsetTruncated = (uLong)offset;
+        if (offsetTruncated != offset)
+            return -1;
+        else
+            return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
+    }
+}
+
+ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
+    else
+    {
+        uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
+        if ((tell_uLong) == MAXU32)
+            return (ZPOS64_T)-1;
+        else
+            return tell_uLong;
+    }
+}
+
+void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
+{
+    p_filefunc64_32->zfile_func64.zopen64_file = NULL;
+    p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
+    p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
+    p_filefunc64_32->zfile_func64.ztell64_file = NULL;
+    p_filefunc64_32->zfile_func64.zseek64_file = NULL;
+    p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
+    p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
+    p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
+}
+
+
+
+static voidpf  ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
+static uLong   ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+static uLong   ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
+static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
+static long    ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+static int     ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
+static int     ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
+
+static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = fopen(filename, mode_fopen);
+    return file;
+}
+
+static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = FOPEN_FUNC((const char*)filename, mode_fopen);
+    return file;
+}
+
+
+static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
+{
+    long ret;
+    ret = ftell((FILE *)stream);
+    return ret;
+}
+
+
+static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
+{
+    ZPOS64_T ret;
+    ret = FTELLO_FUNC((FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK fseek_file_func (voidpf  opaque, voidpf stream, uLong offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+    if (fseek((FILE *)stream, offset, fseek_origin) != 0)
+        ret = -1;
+    return ret;
+}
+
+static long ZCALLBACK fseek64_file_func (voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+
+    if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
+                        ret = -1;
+
+    return ret;
+}
+
+
+static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = fclose((FILE *)stream);
+    return ret;
+}
+
+static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = ferror((FILE *)stream);
+    return ret;
+}
+
+void fill_fopen_filefunc (pzlib_filefunc_def)
+  zlib_filefunc_def* pzlib_filefunc_def;
+{
+    pzlib_filefunc_def->zopen_file = fopen_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell_file = ftell_file_func;
+    pzlib_filefunc_def->zseek_file = fseek_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+void fill_fopen64_filefunc (zlib_filefunc64_def*  pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = fopen64_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell64_file = ftell64_file_func;
+    pzlib_filefunc_def->zseek64_file = fseek64_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}

+ 208 - 0
library/third_party/minizip/ioapi.h

@@ -0,0 +1,208 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+
+    Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this)
+    Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux.
+               More if/def section may be needed to support other platforms
+    Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows.
+                          (but you should use iowin32.c for windows instead)
+
+*/
+
+#ifndef _ZLIBIOAPI64_H
+#define _ZLIBIOAPI64_H
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+
+  // Linux needs this to support file operation on files larger then 4+GB
+  // But might need better if/def to select just the platforms that needs them.
+
+        #ifndef __USE_FILE_OFFSET64
+                #define __USE_FILE_OFFSET64
+        #endif
+        #ifndef __USE_LARGEFILE64
+                #define __USE_LARGEFILE64
+        #endif
+        #ifndef _LARGEFILE64_SOURCE
+                #define _LARGEFILE64_SOURCE
+        #endif
+        #ifndef _FILE_OFFSET_BIT
+                #define _FILE_OFFSET_BIT 64
+        #endif
+
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "zlib.h"
+
+#if defined(USE_FILE32API)
+#define fopen64 fopen
+#define ftello64 ftell
+#define fseeko64 fseek
+#else
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+#define fopen64 fopen
+#define ftello64 ftello
+#define fseeko64 fseeko
+#endif
+#ifdef _MSC_VER
+ #define fopen64 fopen
+ #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
+  #define ftello64 _ftelli64
+  #define fseeko64 _fseeki64
+ #else // old MSC
+  #define ftello64 ftell
+  #define fseeko64 fseek
+ #endif
+#endif
+#endif
+
+/*
+#ifndef ZPOS64_T
+  #ifdef _WIN32
+                #define ZPOS64_T fpos_t
+  #else
+    #include <stdint.h>
+    #define ZPOS64_T uint64_t
+  #endif
+#endif
+*/
+
+#ifdef HAVE_MINIZIP64_CONF_H
+#include "mz64conf.h"
+#endif
+
+/* a type choosen by DEFINE */
+#ifdef HAVE_64BIT_INT_CUSTOM
+typedef  64BIT_INT_CUSTOM_TYPE ZPOS64_T;
+#else
+#ifdef HAS_STDINT_H
+#include "stdint.h"
+typedef uint64_t ZPOS64_T;
+#else
+
+/* Maximum unsigned 32-bit value used as placeholder for zip64 */
+#define MAXU32 0xffffffff
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+typedef unsigned __int64 ZPOS64_T;
+#else
+typedef unsigned long long int ZPOS64_T;
+#endif
+#endif
+#endif
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define ZLIB_FILEFUNC_SEEK_CUR (1)
+#define ZLIB_FILEFUNC_SEEK_END (2)
+#define ZLIB_FILEFUNC_SEEK_SET (0)
+
+#define ZLIB_FILEFUNC_MODE_READ      (1)
+#define ZLIB_FILEFUNC_MODE_WRITE     (2)
+#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
+
+#define ZLIB_FILEFUNC_MODE_EXISTING (4)
+#define ZLIB_FILEFUNC_MODE_CREATE   (8)
+
+
+#ifndef ZCALLBACK
+ #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
+   #define ZCALLBACK CALLBACK
+ #else
+   #define ZCALLBACK
+ #endif
+#endif
+
+
+
+
+typedef voidpf   (ZCALLBACK *open_file_func)      OF((voidpf opaque, const char* filename, int mode));
+typedef uLong    (ZCALLBACK *read_file_func)      OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+typedef uLong    (ZCALLBACK *write_file_func)     OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
+typedef int      (ZCALLBACK *close_file_func)     OF((voidpf opaque, voidpf stream));
+typedef int      (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
+
+typedef long     (ZCALLBACK *tell_file_func)      OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek_file_func)      OF((voidpf opaque, voidpf stream, uLong offset, int origin));
+
+
+/* here is the "old" 32 bits structure structure */
+typedef struct zlib_filefunc_def_s
+{
+    open_file_func      zopen_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell_file_func      ztell_file;
+    seek_file_func      zseek_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc_def;
+
+typedef ZPOS64_T (ZCALLBACK *tell64_file_func)    OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek64_file_func)    OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+typedef voidpf   (ZCALLBACK *open64_file_func)    OF((voidpf opaque, const void* filename, int mode));
+
+typedef struct zlib_filefunc64_def_s
+{
+    open64_file_func    zopen64_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell64_file_func    ztell64_file;
+    seek64_file_func    zseek64_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc64_def;
+
+void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
+void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
+
+/* now internal definition, only for zip.c and unzip.h */
+typedef struct zlib_filefunc64_32_def_s
+{
+    zlib_filefunc64_def zfile_func64;
+    open_file_func      zopen32_file;
+    tell_file_func      ztell32_file;
+    seek_file_func      zseek32_file;
+} zlib_filefunc64_32_def;
+
+
+#define ZREAD64(filefunc,filestream,buf,size)     ((*((filefunc).zfile_func64.zread_file))   ((filefunc).zfile_func64.opaque,filestream,buf,size))
+#define ZWRITE64(filefunc,filestream,buf,size)    ((*((filefunc).zfile_func64.zwrite_file))  ((filefunc).zfile_func64.opaque,filestream,buf,size))
+/* #define ZTELL64(filefunc,filestream)            ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) */
+/* #define ZSEEK64(filefunc,filestream,pos,mode)   ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) */
+#define ZCLOSE64(filefunc,filestream)             ((*((filefunc).zfile_func64.zclose_file))  ((filefunc).zfile_func64.opaque,filestream))
+#define ZERROR64(filefunc,filestream)             ((*((filefunc).zfile_func64.zerror_file))  ((filefunc).zfile_func64.opaque,filestream))
+
+voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
+long    call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
+ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
+
+void    fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32);
+
+#define ZOPEN64(filefunc,filename,mode)         (call_zopen64((&(filefunc)),(filename),(mode)))
+#define ZTELL64(filefunc,filestream)            (call_ztell64((&(filefunc)),(filestream)))
+#define ZSEEK64(filefunc,filestream,pos,mode)   (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 456 - 0
library/third_party/minizip/iowin32.c

@@ -0,0 +1,456 @@
+/* iowin32.c -- IO base function header for compress/uncompress .zip
+     Version 1.1, February 14h, 2010
+     part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+     For more info read MiniZip_info.txt
+
+*/
+
+#include <stdlib.h>
+
+#include "zlib.h"
+#include "ioapi.h"
+#include "iowin32.h"
+
+#ifndef INVALID_HANDLE_VALUE
+#define INVALID_HANDLE_VALUE (0xFFFFFFFF)
+#endif
+
+#ifndef INVALID_SET_FILE_POINTER
+#define INVALID_SET_FILE_POINTER ((DWORD)-1)
+#endif
+
+
+
+voidpf  ZCALLBACK win32_open_file_func  OF((voidpf opaque, const char* filename, int mode));
+uLong   ZCALLBACK win32_read_file_func  OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+uLong   ZCALLBACK win32_write_file_func OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
+ZPOS64_T ZCALLBACK win32_tell64_file_func  OF((voidpf opaque, voidpf stream));
+long    ZCALLBACK win32_seek64_file_func  OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+int     ZCALLBACK win32_close_file_func OF((voidpf opaque, voidpf stream));
+int     ZCALLBACK win32_error_file_func OF((voidpf opaque, voidpf stream));
+
+typedef struct
+{
+    HANDLE hf;
+    int error;
+} WIN32FILE_IOWIN;
+
+
+static void win32_translate_open_mode(int mode,
+                                      DWORD* lpdwDesiredAccess,
+                                      DWORD* lpdwCreationDisposition,
+                                      DWORD* lpdwShareMode,
+                                      DWORD* lpdwFlagsAndAttributes)
+{
+    *lpdwDesiredAccess = *lpdwShareMode = *lpdwFlagsAndAttributes = *lpdwCreationDisposition = 0;
+
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+    {
+        *lpdwDesiredAccess = GENERIC_READ;
+        *lpdwCreationDisposition = OPEN_EXISTING;
+        *lpdwShareMode = FILE_SHARE_READ;
+    }
+    else if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+    {
+        *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
+        *lpdwCreationDisposition = OPEN_EXISTING;
+    }
+    else if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+    {
+        *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ;
+        *lpdwCreationDisposition = CREATE_ALWAYS;
+    }
+}
+
+static voidpf win32_build_iowin(HANDLE hFile)
+{
+    voidpf ret=NULL;
+
+    if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
+    {
+        WIN32FILE_IOWIN w32fiow;
+        w32fiow.hf = hFile;
+        w32fiow.error = 0;
+        ret = malloc(sizeof(WIN32FILE_IOWIN));
+
+        if (ret==NULL)
+            CloseHandle(hFile);
+        else
+            *((WIN32FILE_IOWIN*)ret) = w32fiow;
+    }
+    return ret;
+}
+
+voidpf ZCALLBACK win32_open64_file_func (voidpf opaque,const void* filename,int mode)
+{
+    const char* mode_fopen = NULL;
+    DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ;
+    HANDLE hFile = NULL;
+
+    win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes);
+
+#ifdef IOWIN32_USING_WINRT_API
+#ifdef UNICODE
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+    {
+        WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
+        MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200);
+        hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
+    }
+#endif
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+#endif
+
+    return win32_build_iowin(hFile);
+}
+
+
+voidpf ZCALLBACK win32_open64_file_funcA (voidpf opaque,const void* filename,int mode)
+{
+    const char* mode_fopen = NULL;
+    DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ;
+    HANDLE hFile = NULL;
+
+    win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes);
+
+#ifdef IOWIN32_USING_WINRT_API
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+    {
+        WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
+        MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200);
+        hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
+    }
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFileA((LPCSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+#endif
+
+    return win32_build_iowin(hFile);
+}
+
+
+voidpf ZCALLBACK win32_open64_file_funcW (voidpf opaque,const void* filename,int mode)
+{
+    const char* mode_fopen = NULL;
+    DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ;
+    HANDLE hFile = NULL;
+
+    win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes);
+
+#ifdef IOWIN32_USING_WINRT_API
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFile2((LPCWSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition,NULL);
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFileW((LPCWSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+#endif
+
+    return win32_build_iowin(hFile);
+}
+
+
+voidpf ZCALLBACK win32_open_file_func (voidpf opaque,const char* filename,int mode)
+{
+    const char* mode_fopen = NULL;
+    DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ;
+    HANDLE hFile = NULL;
+
+    win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes);
+
+#ifdef IOWIN32_USING_WINRT_API
+#ifdef UNICODE
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+    {
+        WCHAR filenameW[FILENAME_MAX + 0x200 + 1];
+        MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200);
+        hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL);
+    }
+#endif
+#else
+    if ((filename!=NULL) && (dwDesiredAccess != 0))
+        hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL);
+#endif
+
+    return win32_build_iowin(hFile);
+}
+
+
+uLong ZCALLBACK win32_read_file_func (voidpf opaque, voidpf stream, void* buf,uLong size)
+{
+    uLong ret=0;
+    HANDLE hFile = NULL;
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream) -> hf;
+
+    if (hFile != NULL)
+    {
+        if (!ReadFile(hFile, buf, size, &ret, NULL))
+        {
+            DWORD dwErr = GetLastError();
+            if (dwErr == ERROR_HANDLE_EOF)
+                dwErr = 0;
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+        }
+    }
+
+    return ret;
+}
+
+
+uLong ZCALLBACK win32_write_file_func (voidpf opaque,voidpf stream,const void* buf,uLong size)
+{
+    uLong ret=0;
+    HANDLE hFile = NULL;
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream) -> hf;
+
+    if (hFile != NULL)
+    {
+        if (!WriteFile(hFile, buf, size, &ret, NULL))
+        {
+            DWORD dwErr = GetLastError();
+            if (dwErr == ERROR_HANDLE_EOF)
+                dwErr = 0;
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+        }
+    }
+
+    return ret;
+}
+
+static BOOL MySetFilePointerEx(HANDLE hFile, LARGE_INTEGER pos, LARGE_INTEGER *newPos,  DWORD dwMoveMethod)
+{
+#ifdef IOWIN32_USING_WINRT_API
+    return SetFilePointerEx(hFile, pos, newPos, dwMoveMethod);
+#else
+    LONG lHigh = pos.HighPart;
+    DWORD dwNewPos = SetFilePointer(hFile, pos.LowPart, &lHigh, dwMoveMethod);
+    BOOL fOk = TRUE;
+    if (dwNewPos == 0xFFFFFFFF)
+        if (GetLastError() != NO_ERROR)
+            fOk = FALSE;
+    if ((newPos != NULL) && (fOk))
+    {
+        newPos->LowPart = dwNewPos;
+        newPos->HighPart = lHigh;
+    }
+    return fOk;
+#endif
+}
+
+long ZCALLBACK win32_tell_file_func (voidpf opaque,voidpf stream)
+{
+    long ret=-1;
+    HANDLE hFile = NULL;
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream) -> hf;
+    if (hFile != NULL)
+    {
+        LARGE_INTEGER pos;
+        pos.QuadPart = 0;
+
+        if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT))
+        {
+            DWORD dwErr = GetLastError();
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+            ret = -1;
+        }
+        else
+            ret=(long)pos.LowPart;
+    }
+    return ret;
+}
+
+ZPOS64_T ZCALLBACK win32_tell64_file_func (voidpf opaque, voidpf stream)
+{
+    ZPOS64_T ret= (ZPOS64_T)-1;
+    HANDLE hFile = NULL;
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream)->hf;
+
+    if (hFile)
+    {
+        LARGE_INTEGER pos;
+        pos.QuadPart = 0;
+
+        if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT))
+        {
+            DWORD dwErr = GetLastError();
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+            ret = (ZPOS64_T)-1;
+        }
+        else
+            ret=pos.QuadPart;
+    }
+    return ret;
+}
+
+
+long ZCALLBACK win32_seek_file_func (voidpf opaque,voidpf stream,uLong offset,int origin)
+{
+    DWORD dwMoveMethod=0xFFFFFFFF;
+    HANDLE hFile = NULL;
+
+    long ret=-1;
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream) -> hf;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        dwMoveMethod = FILE_CURRENT;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        dwMoveMethod = FILE_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        dwMoveMethod = FILE_BEGIN;
+        break;
+    default: return -1;
+    }
+
+    if (hFile != NULL)
+    {
+        LARGE_INTEGER pos;
+        pos.QuadPart = offset;
+        if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod))
+        {
+            DWORD dwErr = GetLastError();
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+            ret = -1;
+        }
+        else
+            ret=0;
+    }
+    return ret;
+}
+
+long ZCALLBACK win32_seek64_file_func (voidpf opaque, voidpf stream,ZPOS64_T offset,int origin)
+{
+    DWORD dwMoveMethod=0xFFFFFFFF;
+    HANDLE hFile = NULL;
+    long ret=-1;
+
+    if (stream!=NULL)
+        hFile = ((WIN32FILE_IOWIN*)stream)->hf;
+
+    switch (origin)
+    {
+        case ZLIB_FILEFUNC_SEEK_CUR :
+            dwMoveMethod = FILE_CURRENT;
+            break;
+        case ZLIB_FILEFUNC_SEEK_END :
+            dwMoveMethod = FILE_END;
+            break;
+        case ZLIB_FILEFUNC_SEEK_SET :
+            dwMoveMethod = FILE_BEGIN;
+            break;
+        default: return -1;
+    }
+
+    if (hFile)
+    {
+        LARGE_INTEGER pos;
+        pos.QuadPart = offset;
+        if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod))
+        {
+            DWORD dwErr = GetLastError();
+            ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr;
+            ret = -1;
+        }
+        else
+            ret=0;
+    }
+    return ret;
+}
+
+int ZCALLBACK win32_close_file_func (voidpf opaque, voidpf stream)
+{
+    int ret=-1;
+
+    if (stream!=NULL)
+    {
+        HANDLE hFile;
+        hFile = ((WIN32FILE_IOWIN*)stream) -> hf;
+        if (hFile != NULL)
+        {
+            CloseHandle(hFile);
+            ret=0;
+        }
+        free(stream);
+    }
+    return ret;
+}
+
+int ZCALLBACK win32_error_file_func (voidpf opaque,voidpf stream)
+{
+    int ret=-1;
+    if (stream!=NULL)
+    {
+        ret = ((WIN32FILE_IOWIN*)stream) -> error;
+    }
+    return ret;
+}
+
+void fill_win32_filefunc (zlib_filefunc_def* pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen_file = win32_open_file_func;
+    pzlib_filefunc_def->zread_file = win32_read_file_func;
+    pzlib_filefunc_def->zwrite_file = win32_write_file_func;
+    pzlib_filefunc_def->ztell_file = win32_tell_file_func;
+    pzlib_filefunc_def->zseek_file = win32_seek_file_func;
+    pzlib_filefunc_def->zclose_file = win32_close_file_func;
+    pzlib_filefunc_def->zerror_file = win32_error_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+void fill_win32_filefunc64(zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = win32_open64_file_func;
+    pzlib_filefunc_def->zread_file = win32_read_file_func;
+    pzlib_filefunc_def->zwrite_file = win32_write_file_func;
+    pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
+    pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
+    pzlib_filefunc_def->zclose_file = win32_close_file_func;
+    pzlib_filefunc_def->zerror_file = win32_error_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+
+void fill_win32_filefunc64A(zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = win32_open64_file_funcA;
+    pzlib_filefunc_def->zread_file = win32_read_file_func;
+    pzlib_filefunc_def->zwrite_file = win32_write_file_func;
+    pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
+    pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
+    pzlib_filefunc_def->zclose_file = win32_close_file_func;
+    pzlib_filefunc_def->zerror_file = win32_error_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+
+void fill_win32_filefunc64W(zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = win32_open64_file_funcW;
+    pzlib_filefunc_def->zread_file = win32_read_file_func;
+    pzlib_filefunc_def->zwrite_file = win32_write_file_func;
+    pzlib_filefunc_def->ztell64_file = win32_tell64_file_func;
+    pzlib_filefunc_def->zseek64_file = win32_seek64_file_func;
+    pzlib_filefunc_def->zclose_file = win32_close_file_func;
+    pzlib_filefunc_def->zerror_file = win32_error_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}

+ 28 - 0
library/third_party/minizip/iowin32.h

@@ -0,0 +1,28 @@
+/* iowin32.h -- IO base function header for compress/uncompress .zip
+     Version 1.1, February 14h, 2010
+     part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+*/
+
+#include <windows.h>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void fill_win32_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
+void fill_win32_filefunc64 OF((zlib_filefunc64_def* pzlib_filefunc_def));
+void fill_win32_filefunc64A OF((zlib_filefunc64_def* pzlib_filefunc_def));
+void fill_win32_filefunc64W OF((zlib_filefunc64_def* pzlib_filefunc_def));
+
+#ifdef __cplusplus
+}
+#endif

+ 7 - 0
library/third_party/minizip/iowin32.loT

@@ -0,0 +1,7 @@
+# library/third_party/minizip/iowin32.lo - a libtool object file
+# Generated by ltmain.sh - GNU libtool 1.5.26 (1.1220.2.492 2008/01/30 06:40:56)
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.

+ 660 - 0
library/third_party/minizip/miniunz.c

@@ -0,0 +1,660 @@
+/*
+   miniunz.c
+   Version 1.1, February 14h, 2010
+   sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications of Unzip for Zip64
+         Copyright (C) 2007-2008 Even Rouault
+
+         Modifications for Zip64 support on both zip and unzip
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+*/
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+        #ifndef __USE_FILE_OFFSET64
+                #define __USE_FILE_OFFSET64
+        #endif
+        #ifndef __USE_LARGEFILE64
+                #define __USE_LARGEFILE64
+        #endif
+        #ifndef _LARGEFILE64_SOURCE
+                #define _LARGEFILE64_SOURCE
+        #endif
+        #ifndef _FILE_OFFSET_BIT
+                #define _FILE_OFFSET_BIT 64
+        #endif
+#endif
+
+#ifdef __APPLE__
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef _WIN32
+# include <direct.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <utime.h>
+#endif
+
+
+#include "unzip.h"
+
+#define CASESENSITIVITY (0)
+#define WRITEBUFFERSIZE (8192)
+#define MAXFILENAME (256)
+
+#ifdef _WIN32
+#define USEWIN32IOAPI
+#include "iowin32.h"
+#endif
+/*
+  mini unzip, demo of unzip package
+
+  usage :
+  Usage : miniunz [-exvlo] file.zip [file_to_extract] [-d extractdir]
+
+  list the file in the zipfile, and print the content of FILE_ID.ZIP or README.TXT
+    if it exists
+*/
+
+
+/* change_file_date : change the date/time of a file
+    filename : the filename of the file where date/time must be modified
+    dosdate : the new date at the MSDos format (4 bytes)
+    tmu_date : the SAME new date at the tm_unz format */
+void change_file_date(filename,dosdate,tmu_date)
+    const char *filename;
+    uLong dosdate;
+    tm_unz tmu_date;
+{
+#ifdef _WIN32
+  HANDLE hFile;
+  FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite;
+
+  hFile = CreateFileA(filename,GENERIC_READ | GENERIC_WRITE,
+                      0,NULL,OPEN_EXISTING,0,NULL);
+  GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite);
+  DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal);
+  LocalFileTimeToFileTime(&ftLocal,&ftm);
+  SetFileTime(hFile,&ftm,&ftLastAcc,&ftm);
+  CloseHandle(hFile);
+#else
+#ifdef unix || __APPLE__
+  struct utimbuf ut;
+  struct tm newdate;
+  newdate.tm_sec = tmu_date.tm_sec;
+  newdate.tm_min=tmu_date.tm_min;
+  newdate.tm_hour=tmu_date.tm_hour;
+  newdate.tm_mday=tmu_date.tm_mday;
+  newdate.tm_mon=tmu_date.tm_mon;
+  if (tmu_date.tm_year > 1900)
+      newdate.tm_year=tmu_date.tm_year - 1900;
+  else
+      newdate.tm_year=tmu_date.tm_year ;
+  newdate.tm_isdst=-1;
+
+  ut.actime=ut.modtime=mktime(&newdate);
+  utime(filename,&ut);
+#endif
+#endif
+}
+
+
+/* mymkdir and change_file_date are not 100 % portable
+   As I don't know well Unix, I wait feedback for the unix portion */
+
+int mymkdir(dirname)
+    const char* dirname;
+{
+    int ret=0;
+#ifdef _WIN32
+    ret = _mkdir(dirname);
+#elif unix
+    ret = mkdir (dirname,0775);
+#elif __APPLE__
+    ret = mkdir (dirname,0775);
+#endif
+    return ret;
+}
+
+int makedir (newdir)
+    char *newdir;
+{
+  char *buffer ;
+  char *p;
+  int  len = (int)strlen(newdir);
+
+  if (len <= 0)
+    return 0;
+
+  buffer = (char*)malloc(len+1);
+        if (buffer==NULL)
+        {
+                printf("Error allocating memory\n");
+                return UNZ_INTERNALERROR;
+        }
+  strcpy(buffer,newdir);
+
+  if (buffer[len-1] == '/') {
+    buffer[len-1] = '\0';
+  }
+  if (mymkdir(buffer) == 0)
+    {
+      free(buffer);
+      return 1;
+    }
+
+  p = buffer+1;
+  while (1)
+    {
+      char hold;
+
+      while(*p && *p != '\\' && *p != '/')
+        p++;
+      hold = *p;
+      *p = 0;
+      if ((mymkdir(buffer) == -1) && (errno == ENOENT))
+        {
+          printf("couldn't create directory %s\n",buffer);
+          free(buffer);
+          return 0;
+        }
+      if (hold == 0)
+        break;
+      *p++ = hold;
+    }
+  free(buffer);
+  return 1;
+}
+
+void do_banner()
+{
+    printf("MiniUnz 1.01b, demo of zLib + Unz package written by Gilles Vollant\n");
+    printf("more info at http://www.winimage.com/zLibDll/unzip.html\n\n");
+}
+
+void do_help()
+{
+    printf("Usage : miniunz [-e] [-x] [-v] [-l] [-o] [-p password] file.zip [file_to_extr.] [-d extractdir]\n\n" \
+           "  -e  Extract without pathname (junk paths)\n" \
+           "  -x  Extract with pathname\n" \
+           "  -v  list files\n" \
+           "  -l  list files\n" \
+           "  -d  directory to extract into\n" \
+           "  -o  overwrite files without prompting\n" \
+           "  -p  extract crypted file using password\n\n");
+}
+
+void Display64BitsSize(ZPOS64_T n, int size_char)
+{
+  /* to avoid compatibility problem , we do here the conversion */
+  char number[21];
+  int offset=19;
+  int pos_string = 19;
+  number[20]=0;
+  for (;;) {
+      number[offset]=(char)((n%10)+'0');
+      if (number[offset] != '0')
+          pos_string=offset;
+      n/=10;
+      if (offset==0)
+          break;
+      offset--;
+  }
+  {
+      int size_display_string = 19-pos_string;
+      while (size_char > size_display_string)
+      {
+          size_char--;
+          printf(" ");
+      }
+  }
+
+  printf("%s",&number[pos_string]);
+}
+
+int do_list(uf)
+    unzFile uf;
+{
+    uLong i;
+    unz_global_info64 gi;
+    int err;
+
+    err = unzGetGlobalInfo64(uf,&gi);
+    if (err!=UNZ_OK)
+        printf("error %d with zipfile in unzGetGlobalInfo \n",err);
+    printf("  Length  Method     Size Ratio   Date    Time   CRC-32     Name\n");
+    printf("  ------  ------     ---- -----   ----    ----   ------     ----\n");
+    for (i=0;i<gi.number_entry;i++)
+    {
+        char filename_inzip[256];
+        unz_file_info64 file_info;
+        uLong ratio=0;
+        const char *string_method;
+        char charCrypt=' ';
+        err = unzGetCurrentFileInfo64(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
+        if (err!=UNZ_OK)
+        {
+            printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
+            break;
+        }
+        if (file_info.uncompressed_size>0)
+            ratio = (uLong)((file_info.compressed_size*100)/file_info.uncompressed_size);
+
+        /* display a '*' if the file is crypted */
+        if ((file_info.flag & 1) != 0)
+            charCrypt='*';
+
+        if (file_info.compression_method==0)
+            string_method="Stored";
+        else
+        if (file_info.compression_method==Z_DEFLATED)
+        {
+            uInt iLevel=(uInt)((file_info.flag & 0x6)/2);
+            if (iLevel==0)
+              string_method="Defl:N";
+            else if (iLevel==1)
+              string_method="Defl:X";
+            else if ((iLevel==2) || (iLevel==3))
+              string_method="Defl:F"; /* 2:fast , 3 : extra fast*/
+        }
+        else
+        if (file_info.compression_method==Z_BZIP2ED)
+        {
+              string_method="BZip2 ";
+        }
+        else
+            string_method="Unkn. ";
+
+        Display64BitsSize(file_info.uncompressed_size,7);
+        printf("  %6s%c",string_method,charCrypt);
+        Display64BitsSize(file_info.compressed_size,7);
+        printf(" %3lu%%  %2.2lu-%2.2lu-%2.2lu  %2.2lu:%2.2lu  %8.8lx   %s\n",
+                ratio,
+                (uLong)file_info.tmu_date.tm_mon + 1,
+                (uLong)file_info.tmu_date.tm_mday,
+                (uLong)file_info.tmu_date.tm_year % 100,
+                (uLong)file_info.tmu_date.tm_hour,(uLong)file_info.tmu_date.tm_min,
+                (uLong)file_info.crc,filename_inzip);
+        if ((i+1)<gi.number_entry)
+        {
+            err = unzGoToNextFile(uf);
+            if (err!=UNZ_OK)
+            {
+                printf("error %d with zipfile in unzGoToNextFile\n",err);
+                break;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+int do_extract_currentfile(uf,popt_extract_without_path,popt_overwrite,password)
+    unzFile uf;
+    const int* popt_extract_without_path;
+    int* popt_overwrite;
+    const char* password;
+{
+    char filename_inzip[256];
+    char* filename_withoutpath;
+    char* p;
+    int err=UNZ_OK;
+    FILE *fout=NULL;
+    void* buf;
+    uInt size_buf;
+
+    unz_file_info64 file_info;
+    uLong ratio=0;
+    err = unzGetCurrentFileInfo64(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
+
+    if (err!=UNZ_OK)
+    {
+        printf("error %d with zipfile in unzGetCurrentFileInfo\n",err);
+        return err;
+    }
+
+    size_buf = WRITEBUFFERSIZE;
+    buf = (void*)malloc(size_buf);
+    if (buf==NULL)
+    {
+        printf("Error allocating memory\n");
+        return UNZ_INTERNALERROR;
+    }
+
+    p = filename_withoutpath = filename_inzip;
+    while ((*p) != '\0')
+    {
+        if (((*p)=='/') || ((*p)=='\\'))
+            filename_withoutpath = p+1;
+        p++;
+    }
+
+    if ((*filename_withoutpath)=='\0')
+    {
+        if ((*popt_extract_without_path)==0)
+        {
+            printf("creating directory: %s\n",filename_inzip);
+            mymkdir(filename_inzip);
+        }
+    }
+    else
+    {
+        const char* write_filename;
+        int skip=0;
+
+        if ((*popt_extract_without_path)==0)
+            write_filename = filename_inzip;
+        else
+            write_filename = filename_withoutpath;
+
+        err = unzOpenCurrentFilePassword(uf,password);
+        if (err!=UNZ_OK)
+        {
+            printf("error %d with zipfile in unzOpenCurrentFilePassword\n",err);
+        }
+
+        if (((*popt_overwrite)==0) && (err==UNZ_OK))
+        {
+            char rep=0;
+            FILE* ftestexist;
+            ftestexist = FOPEN_FUNC(write_filename,"rb");
+            if (ftestexist!=NULL)
+            {
+                fclose(ftestexist);
+                do
+                {
+                    char answer[128];
+                    int ret;
+
+                    printf("The file %s exists. Overwrite ? [y]es, [n]o, [A]ll: ",write_filename);
+                    ret = scanf("%1s",answer);
+                    if (ret != 1)
+                    {
+                       exit(EXIT_FAILURE);
+                    }
+                    rep = answer[0] ;
+                    if ((rep>='a') && (rep<='z'))
+                        rep -= 0x20;
+                }
+                while ((rep!='Y') && (rep!='N') && (rep!='A'));
+            }
+
+            if (rep == 'N')
+                skip = 1;
+
+            if (rep == 'A')
+                *popt_overwrite=1;
+        }
+
+        if ((skip==0) && (err==UNZ_OK))
+        {
+            fout=FOPEN_FUNC(write_filename,"wb");
+            /* some zipfile don't contain directory alone before file */
+            if ((fout==NULL) && ((*popt_extract_without_path)==0) &&
+                                (filename_withoutpath!=(char*)filename_inzip))
+            {
+                char c=*(filename_withoutpath-1);
+                *(filename_withoutpath-1)='\0';
+                makedir(write_filename);
+                *(filename_withoutpath-1)=c;
+                fout=FOPEN_FUNC(write_filename,"wb");
+            }
+
+            if (fout==NULL)
+            {
+                printf("error opening %s\n",write_filename);
+            }
+        }
+
+        if (fout!=NULL)
+        {
+            printf(" extracting: %s\n",write_filename);
+
+            do
+            {
+                err = unzReadCurrentFile(uf,buf,size_buf);
+                if (err<0)
+                {
+                    printf("error %d with zipfile in unzReadCurrentFile\n",err);
+                    break;
+                }
+                if (err>0)
+                    if (fwrite(buf,err,1,fout)!=1)
+                    {
+                        printf("error in writing extracted file\n");
+                        err=UNZ_ERRNO;
+                        break;
+                    }
+            }
+            while (err>0);
+            if (fout)
+                    fclose(fout);
+
+            if (err==0)
+                change_file_date(write_filename,file_info.dosDate,
+                                 file_info.tmu_date);
+        }
+
+        if (err==UNZ_OK)
+        {
+            err = unzCloseCurrentFile (uf);
+            if (err!=UNZ_OK)
+            {
+                printf("error %d with zipfile in unzCloseCurrentFile\n",err);
+            }
+        }
+        else
+            unzCloseCurrentFile(uf); /* don't lose the error */
+    }
+
+    free(buf);
+    return err;
+}
+
+
+int do_extract(uf,opt_extract_without_path,opt_overwrite,password)
+    unzFile uf;
+    int opt_extract_without_path;
+    int opt_overwrite;
+    const char* password;
+{
+    uLong i;
+    unz_global_info64 gi;
+    int err;
+    FILE* fout=NULL;
+
+    err = unzGetGlobalInfo64(uf,&gi);
+    if (err!=UNZ_OK)
+        printf("error %d with zipfile in unzGetGlobalInfo \n",err);
+
+    for (i=0;i<gi.number_entry;i++)
+    {
+        if (do_extract_currentfile(uf,&opt_extract_without_path,
+                                      &opt_overwrite,
+                                      password) != UNZ_OK)
+            break;
+
+        if ((i+1)<gi.number_entry)
+        {
+            err = unzGoToNextFile(uf);
+            if (err!=UNZ_OK)
+            {
+                printf("error %d with zipfile in unzGoToNextFile\n",err);
+                break;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int do_extract_onefile(uf,filename,opt_extract_without_path,opt_overwrite,password)
+    unzFile uf;
+    const char* filename;
+    int opt_extract_without_path;
+    int opt_overwrite;
+    const char* password;
+{
+    int err = UNZ_OK;
+    if (unzLocateFile(uf,filename,CASESENSITIVITY)!=UNZ_OK)
+    {
+        printf("file %s not found in the zipfile\n",filename);
+        return 2;
+    }
+
+    if (do_extract_currentfile(uf,&opt_extract_without_path,
+                                      &opt_overwrite,
+                                      password) == UNZ_OK)
+        return 0;
+    else
+        return 1;
+}
+
+
+int main(argc,argv)
+    int argc;
+    char *argv[];
+{
+    const char *zipfilename=NULL;
+    const char *filename_to_extract=NULL;
+    const char *password=NULL;
+    char filename_try[MAXFILENAME+16] = "";
+    int i;
+    int ret_value=0;
+    int opt_do_list=0;
+    int opt_do_extract=1;
+    int opt_do_extract_withoutpath=0;
+    int opt_overwrite=0;
+    int opt_extractdir=0;
+    const char *dirname=NULL;
+    unzFile uf=NULL;
+
+    do_banner();
+    if (argc==1)
+    {
+        do_help();
+        return 0;
+    }
+    else
+    {
+        for (i=1;i<argc;i++)
+        {
+            if ((*argv[i])=='-')
+            {
+                const char *p=argv[i]+1;
+
+                while ((*p)!='\0')
+                {
+                    char c=*(p++);;
+                    if ((c=='l') || (c=='L'))
+                        opt_do_list = 1;
+                    if ((c=='v') || (c=='V'))
+                        opt_do_list = 1;
+                    if ((c=='x') || (c=='X'))
+                        opt_do_extract = 1;
+                    if ((c=='e') || (c=='E'))
+                        opt_do_extract = opt_do_extract_withoutpath = 1;
+                    if ((c=='o') || (c=='O'))
+                        opt_overwrite=1;
+                    if ((c=='d') || (c=='D'))
+                    {
+                        opt_extractdir=1;
+                        dirname=argv[i+1];
+                    }
+
+                    if (((c=='p') || (c=='P')) && (i+1<argc))
+                    {
+                        password=argv[i+1];
+                        i++;
+                    }
+                }
+            }
+            else
+            {
+                if (zipfilename == NULL)
+                    zipfilename = argv[i];
+                else if ((filename_to_extract==NULL) && (!opt_extractdir))
+                        filename_to_extract = argv[i] ;
+            }
+        }
+    }
+
+    if (zipfilename!=NULL)
+    {
+
+#        ifdef USEWIN32IOAPI
+        zlib_filefunc64_def ffunc;
+#        endif
+
+        strncpy(filename_try, zipfilename,MAXFILENAME-1);
+        /* strncpy doesnt append the trailing NULL, of the string is too long. */
+        filename_try[ MAXFILENAME ] = '\0';
+
+#        ifdef USEWIN32IOAPI
+        fill_win32_filefunc64A(&ffunc);
+        uf = unzOpen2_64(zipfilename,&ffunc);
+#        else
+        uf = unzOpen64(zipfilename);
+#        endif
+        if (uf==NULL)
+        {
+            strcat(filename_try,".zip");
+#            ifdef USEWIN32IOAPI
+            uf = unzOpen2_64(filename_try,&ffunc);
+#            else
+            uf = unzOpen64(filename_try);
+#            endif
+        }
+    }
+
+    if (uf==NULL)
+    {
+        printf("Cannot open %s or %s.zip\n",zipfilename,zipfilename);
+        return 1;
+    }
+    printf("%s opened\n",filename_try);
+
+    if (opt_do_list==1)
+        ret_value = do_list(uf);
+    else if (opt_do_extract==1)
+    {
+#ifdef _WIN32
+        if (opt_extractdir && _chdir(dirname))
+#else
+        if (opt_extractdir && chdir(dirname))
+#endif
+        {
+          printf("Error changing into %s, aborting\n", dirname);
+          exit(-1);
+        }
+
+        if (filename_to_extract == NULL)
+            ret_value = do_extract(uf, opt_do_extract_withoutpath, opt_overwrite, password);
+        else
+            ret_value = do_extract_onefile(uf, filename_to_extract, opt_do_extract_withoutpath, opt_overwrite, password);
+    }
+
+    unzClose(uf);
+
+    return ret_value;
+}

+ 520 - 0
library/third_party/minizip/minizip.c

@@ -0,0 +1,520 @@
+/*
+   minizip.c
+   Version 1.1, February 14h, 2010
+   sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications of Unzip for Zip64
+         Copyright (C) 2007-2008 Even Rouault
+
+         Modifications for Zip64 support on both zip and unzip
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+*/
+
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+        #ifndef __USE_FILE_OFFSET64
+                #define __USE_FILE_OFFSET64
+        #endif
+        #ifndef __USE_LARGEFILE64
+                #define __USE_LARGEFILE64
+        #endif
+        #ifndef _LARGEFILE64_SOURCE
+                #define _LARGEFILE64_SOURCE
+        #endif
+        #ifndef _FILE_OFFSET_BIT
+                #define _FILE_OFFSET_BIT 64
+        #endif
+#endif
+
+#ifdef __APPLE__
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#ifdef _WIN32
+# include <direct.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <utime.h>
+# include <sys/types.h>
+# include <sys/stat.h>
+#endif
+
+#include "zip.h"
+
+#ifdef _WIN32
+        #define USEWIN32IOAPI
+        #include "iowin32.h"
+#endif
+
+
+
+#define WRITEBUFFERSIZE (16384)
+#define MAXFILENAME (256)
+
+#ifdef _WIN32
+uLong filetime(f, tmzip, dt)
+    char *f;                /* name of file to get info on */
+    tm_zip *tmzip;             /* return value: access, modific. and creation times */
+    uLong *dt;             /* dostime */
+{
+  int ret = 0;
+  {
+      FILETIME ftLocal;
+      HANDLE hFind;
+      WIN32_FIND_DATAA ff32;
+
+      hFind = FindFirstFileA(f,&ff32);
+      if (hFind != INVALID_HANDLE_VALUE)
+      {
+        FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal);
+        FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0);
+        FindClose(hFind);
+        ret = 1;
+      }
+  }
+  return ret;
+}
+#else
+#ifdef unix || __APPLE__
+uLong filetime(f, tmzip, dt)
+    char *f;               /* name of file to get info on */
+    tm_zip *tmzip;         /* return value: access, modific. and creation times */
+    uLong *dt;             /* dostime */
+{
+  int ret=0;
+  struct stat s;        /* results of stat() */
+  struct tm* filedate;
+  time_t tm_t=0;
+
+  if (strcmp(f,"-")!=0)
+  {
+    char name[MAXFILENAME+1];
+    int len = strlen(f);
+    if (len > MAXFILENAME)
+      len = MAXFILENAME;
+
+    strncpy(name, f,MAXFILENAME-1);
+    /* strncpy doesnt append the trailing NULL, of the string is too long. */
+    name[ MAXFILENAME ] = '\0';
+
+    if (name[len - 1] == '/')
+      name[len - 1] = '\0';
+    /* not all systems allow stat'ing a file with / appended */
+    if (stat(name,&s)==0)
+    {
+      tm_t = s.st_mtime;
+      ret = 1;
+    }
+  }
+  filedate = localtime(&tm_t);
+
+  tmzip->tm_sec  = filedate->tm_sec;
+  tmzip->tm_min  = filedate->tm_min;
+  tmzip->tm_hour = filedate->tm_hour;
+  tmzip->tm_mday = filedate->tm_mday;
+  tmzip->tm_mon  = filedate->tm_mon ;
+  tmzip->tm_year = filedate->tm_year;
+
+  return ret;
+}
+#else
+uLong filetime(f, tmzip, dt)
+    char *f;                /* name of file to get info on */
+    tm_zip *tmzip;             /* return value: access, modific. and creation times */
+    uLong *dt;             /* dostime */
+{
+    return 0;
+}
+#endif
+#endif
+
+
+
+
+int check_exist_file(filename)
+    const char* filename;
+{
+    FILE* ftestexist;
+    int ret = 1;
+    ftestexist = FOPEN_FUNC(filename,"rb");
+    if (ftestexist==NULL)
+        ret = 0;
+    else
+        fclose(ftestexist);
+    return ret;
+}
+
+void do_banner()
+{
+    printf("MiniZip 1.1, demo of zLib + MiniZip64 package, written by Gilles Vollant\n");
+    printf("more info on MiniZip at http://www.winimage.com/zLibDll/minizip.html\n\n");
+}
+
+void do_help()
+{
+    printf("Usage : minizip [-o] [-a] [-0 to -9] [-p password] [-j] file.zip [files_to_add]\n\n" \
+           "  -o  Overwrite existing file.zip\n" \
+           "  -a  Append to existing file.zip\n" \
+           "  -0  Store only\n" \
+           "  -1  Compress faster\n" \
+           "  -9  Compress better\n\n" \
+           "  -j  exclude path. store only the file name.\n\n");
+}
+
+/* calculate the CRC32 of a file,
+   because to encrypt a file, we need known the CRC32 of the file before */
+int getFileCrc(const char* filenameinzip,void*buf,unsigned long size_buf,unsigned long* result_crc)
+{
+   unsigned long calculate_crc=0;
+   int err=ZIP_OK;
+   FILE * fin = FOPEN_FUNC(filenameinzip,"rb");
+
+   unsigned long size_read = 0;
+   unsigned long total_read = 0;
+   if (fin==NULL)
+   {
+       err = ZIP_ERRNO;
+   }
+
+    if (err == ZIP_OK)
+        do
+        {
+            err = ZIP_OK;
+            size_read = (int)fread(buf,1,size_buf,fin);
+            if (size_read < size_buf)
+                if (feof(fin)==0)
+            {
+                printf("error in reading %s\n",filenameinzip);
+                err = ZIP_ERRNO;
+            }
+
+            if (size_read>0)
+                calculate_crc = crc32(calculate_crc,buf,size_read);
+            total_read += size_read;
+
+        } while ((err == ZIP_OK) && (size_read>0));
+
+    if (fin)
+        fclose(fin);
+
+    *result_crc=calculate_crc;
+    printf("file %s crc %lx\n", filenameinzip, calculate_crc);
+    return err;
+}
+
+int isLargeFile(const char* filename)
+{
+  int largeFile = 0;
+  ZPOS64_T pos = 0;
+  FILE* pFile = FOPEN_FUNC(filename, "rb");
+
+  if(pFile != NULL)
+  {
+    int n = FSEEKO_FUNC(pFile, 0, SEEK_END);
+    pos = FTELLO_FUNC(pFile);
+
+                printf("File : %s is %lld bytes\n", filename, pos);
+
+    if(pos >= 0xffffffff)
+     largeFile = 1;
+
+                fclose(pFile);
+  }
+
+ return largeFile;
+}
+
+int main(argc,argv)
+    int argc;
+    char *argv[];
+{
+    int i;
+    int opt_overwrite=0;
+    int opt_compress_level=Z_DEFAULT_COMPRESSION;
+    int opt_exclude_path=0;
+    int zipfilenamearg = 0;
+    char filename_try[MAXFILENAME+16];
+    int zipok;
+    int err=0;
+    int size_buf=0;
+    void* buf=NULL;
+    const char* password=NULL;
+
+
+    do_banner();
+    if (argc==1)
+    {
+        do_help();
+        return 0;
+    }
+    else
+    {
+        for (i=1;i<argc;i++)
+        {
+            if ((*argv[i])=='-')
+            {
+                const char *p=argv[i]+1;
+
+                while ((*p)!='\0')
+                {
+                    char c=*(p++);;
+                    if ((c=='o') || (c=='O'))
+                        opt_overwrite = 1;
+                    if ((c=='a') || (c=='A'))
+                        opt_overwrite = 2;
+                    if ((c>='0') && (c<='9'))
+                        opt_compress_level = c-'0';
+                    if ((c=='j') || (c=='J'))
+                        opt_exclude_path = 1;
+
+                    if (((c=='p') || (c=='P')) && (i+1<argc))
+                    {
+                        password=argv[i+1];
+                        i++;
+                    }
+                }
+            }
+            else
+            {
+                if (zipfilenamearg == 0)
+                {
+                    zipfilenamearg = i ;
+                }
+            }
+        }
+    }
+
+    size_buf = WRITEBUFFERSIZE;
+    buf = (void*)malloc(size_buf);
+    if (buf==NULL)
+    {
+        printf("Error allocating memory\n");
+        return ZIP_INTERNALERROR;
+    }
+
+    if (zipfilenamearg==0)
+    {
+        zipok=0;
+    }
+    else
+    {
+        int i,len;
+        int dot_found=0;
+
+        zipok = 1 ;
+        strncpy(filename_try, argv[zipfilenamearg],MAXFILENAME-1);
+        /* strncpy doesnt append the trailing NULL, of the string is too long. */
+        filename_try[ MAXFILENAME ] = '\0';
+
+        len=(int)strlen(filename_try);
+        for (i=0;i<len;i++)
+            if (filename_try[i]=='.')
+                dot_found=1;
+
+        if (dot_found==0)
+            strcat(filename_try,".zip");
+
+        if (opt_overwrite==2)
+        {
+            /* if the file don't exist, we not append file */
+            if (check_exist_file(filename_try)==0)
+                opt_overwrite=1;
+        }
+        else
+        if (opt_overwrite==0)
+            if (check_exist_file(filename_try)!=0)
+            {
+                char rep=0;
+                do
+                {
+                    char answer[128];
+                    int ret;
+                    printf("The file %s exists. Overwrite ? [y]es, [n]o, [a]ppend : ",filename_try);
+                    ret = scanf("%1s",answer);
+                    if (ret != 1)
+                    {
+                       exit(EXIT_FAILURE);
+                    }
+                    rep = answer[0] ;
+                    if ((rep>='a') && (rep<='z'))
+                        rep -= 0x20;
+                }
+                while ((rep!='Y') && (rep!='N') && (rep!='A'));
+                if (rep=='N')
+                    zipok = 0;
+                if (rep=='A')
+                    opt_overwrite = 2;
+            }
+    }
+
+    if (zipok==1)
+    {
+        zipFile zf;
+        int errclose;
+#        ifdef USEWIN32IOAPI
+        zlib_filefunc64_def ffunc;
+        fill_win32_filefunc64A(&ffunc);
+        zf = zipOpen2_64(filename_try,(opt_overwrite==2) ? 2 : 0,NULL,&ffunc);
+#        else
+        zf = zipOpen64(filename_try,(opt_overwrite==2) ? 2 : 0);
+#        endif
+
+        if (zf == NULL)
+        {
+            printf("error opening %s\n",filename_try);
+            err= ZIP_ERRNO;
+        }
+        else
+            printf("creating %s\n",filename_try);
+
+        for (i=zipfilenamearg+1;(i<argc) && (err==ZIP_OK);i++)
+        {
+            if (!((((*(argv[i]))=='-') || ((*(argv[i]))=='/')) &&
+                  ((argv[i][1]=='o') || (argv[i][1]=='O') ||
+                   (argv[i][1]=='a') || (argv[i][1]=='A') ||
+                   (argv[i][1]=='p') || (argv[i][1]=='P') ||
+                   ((argv[i][1]>='0') || (argv[i][1]<='9'))) &&
+                  (strlen(argv[i]) == 2)))
+            {
+                FILE * fin;
+                int size_read;
+                const char* filenameinzip = argv[i];
+                const char *savefilenameinzip;
+                zip_fileinfo zi;
+                unsigned long crcFile=0;
+                int zip64 = 0;
+
+                zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour =
+                zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0;
+                zi.dosDate = 0;
+                zi.internal_fa = 0;
+                zi.external_fa = 0;
+                filetime(filenameinzip,&zi.tmz_date,&zi.dosDate);
+
+/*
+                err = zipOpenNewFileInZip(zf,filenameinzip,&zi,
+                                 NULL,0,NULL,0,NULL / * comment * /,
+                                 (opt_compress_level != 0) ? Z_DEFLATED : 0,
+                                 opt_compress_level);
+*/
+                if ((password != NULL) && (err==ZIP_OK))
+                    err = getFileCrc(filenameinzip,buf,size_buf,&crcFile);
+
+                zip64 = isLargeFile(filenameinzip);
+
+                                                         /* The path name saved, should not include a leading slash. */
+               /*if it did, windows/xp and dynazip couldn't read the zip file. */
+                 savefilenameinzip = filenameinzip;
+                 while( savefilenameinzip[0] == '\\' || savefilenameinzip[0] == '/' )
+                 {
+                     savefilenameinzip++;
+                 }
+
+                 /*should the zip file contain any path at all?*/
+                 if( opt_exclude_path )
+                 {
+                     const char *tmpptr;
+                     const char *lastslash = 0;
+                     for( tmpptr = savefilenameinzip; *tmpptr; tmpptr++)
+                     {
+                         if( *tmpptr == '\\' || *tmpptr == '/')
+                         {
+                             lastslash = tmpptr;
+                         }
+                     }
+                     if( lastslash != NULL )
+                     {
+                         savefilenameinzip = lastslash+1; // base filename follows last slash.
+                     }
+                 }
+
+                 /**/
+                err = zipOpenNewFileInZip3_64(zf,savefilenameinzip,&zi,
+                                 NULL,0,NULL,0,NULL /* comment*/,
+                                 (opt_compress_level != 0) ? Z_DEFLATED : 0,
+                                 opt_compress_level,0,
+                                 /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 password,crcFile, zip64);
+
+                if (err != ZIP_OK)
+                    printf("error in opening %s in zipfile\n",filenameinzip);
+                else
+                {
+                    fin = FOPEN_FUNC(filenameinzip,"rb");
+                    if (fin==NULL)
+                    {
+                        err=ZIP_ERRNO;
+                        printf("error in opening %s for reading\n",filenameinzip);
+                    }
+                }
+
+                if (err == ZIP_OK)
+                    do
+                    {
+                        err = ZIP_OK;
+                        size_read = (int)fread(buf,1,size_buf,fin);
+                        if (size_read < size_buf)
+                            if (feof(fin)==0)
+                        {
+                            printf("error in reading %s\n",filenameinzip);
+                            err = ZIP_ERRNO;
+                        }
+
+                        if (size_read>0)
+                        {
+                            err = zipWriteInFileInZip (zf,buf,size_read);
+                            if (err<0)
+                            {
+                                printf("error in writing %s in the zipfile\n",
+                                                 filenameinzip);
+                            }
+
+                        }
+                    } while ((err == ZIP_OK) && (size_read>0));
+
+                if (fin)
+                    fclose(fin);
+
+                if (err<0)
+                    err=ZIP_ERRNO;
+                else
+                {
+                    err = zipCloseFileInZip(zf);
+                    if (err!=ZIP_OK)
+                        printf("error in closing %s in the zipfile\n",
+                                    filenameinzip);
+                }
+            }
+        }
+        errclose = zipClose(zf,NULL);
+        if (errclose != ZIP_OK)
+            printf("error in closing %s\n",filename_try);
+    }
+    else
+    {
+       do_help();
+    }
+
+    free(buf);
+    return 0;
+}

+ 291 - 0
library/third_party/minizip/mztools.c

@@ -0,0 +1,291 @@
+/*
+  Additional tools for Minizip
+  Code: Xavier Roche '2004
+  License: Same as ZLIB (www.gzip.org)
+*/
+
+/* Code */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "zlib.h"
+#include "unzip.h"
+
+#define READ_8(adr)  ((unsigned char)*(adr))
+#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) )
+#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) )
+
+#define WRITE_8(buff, n) do { \
+  *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \
+} while(0)
+#define WRITE_16(buff, n) do { \
+  WRITE_8((unsigned char*)(buff), n); \
+  WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \
+} while(0)
+#define WRITE_32(buff, n) do { \
+  WRITE_16((unsigned char*)(buff), (n) & 0xffff); \
+  WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \
+} while(0)
+
+extern int ZEXPORT unzRepair(file, fileOut, fileOutTmp, nRecovered, bytesRecovered)
+const char* file;
+const char* fileOut;
+const char* fileOutTmp;
+uLong* nRecovered;
+uLong* bytesRecovered;
+{
+  int err = Z_OK;
+  FILE* fpZip = fopen(file, "rb");
+  FILE* fpOut = fopen(fileOut, "wb");
+  FILE* fpOutCD = fopen(fileOutTmp, "wb");
+  if (fpZip != NULL &&  fpOut != NULL) {
+    int entries = 0;
+    uLong totalBytes = 0;
+    char header[30];
+    char filename[1024];
+    char extra[1024];
+    int offset = 0;
+    int offsetCD = 0;
+    while ( fread(header, 1, 30, fpZip) == 30 ) {
+      int currentOffset = offset;
+
+      /* File entry */
+      if (READ_32(header) == 0x04034b50) {
+        unsigned int version = READ_16(header + 4);
+        unsigned int gpflag = READ_16(header + 6);
+        unsigned int method = READ_16(header + 8);
+        unsigned int filetime = READ_16(header + 10);
+        unsigned int filedate = READ_16(header + 12);
+        unsigned int crc = READ_32(header + 14); /* crc */
+        unsigned int cpsize = READ_32(header + 18); /* compressed size */
+        unsigned int uncpsize = READ_32(header + 22); /* uncompressed sz */
+        unsigned int fnsize = READ_16(header + 26); /* file name length */
+        unsigned int extsize = READ_16(header + 28); /* extra field length */
+        filename[0] = extra[0] = '\0';
+
+        /* Header */
+        if (fwrite(header, 1, 30, fpOut) == 30) {
+          offset += 30;
+        } else {
+          err = Z_ERRNO;
+          break;
+        }
+
+        /* Filename */
+        if (fnsize > 0) {
+          if (fnsize < sizeof(filename)) {
+            if (fread(filename, 1, fnsize, fpZip) == fnsize) {
+                if (fwrite(filename, 1, fnsize, fpOut) == fnsize) {
+                offset += fnsize;
+              } else {
+                err = Z_ERRNO;
+                break;
+              }
+            } else {
+              err = Z_ERRNO;
+              break;
+            }
+          } else {
+            err = Z_ERRNO;
+            break;
+          }
+        } else {
+          err = Z_STREAM_ERROR;
+          break;
+        }
+
+        /* Extra field */
+        if (extsize > 0) {
+          if (extsize < sizeof(extra)) {
+            if (fread(extra, 1, extsize, fpZip) == extsize) {
+              if (fwrite(extra, 1, extsize, fpOut) == extsize) {
+                offset += extsize;
+                } else {
+                err = Z_ERRNO;
+                break;
+              }
+            } else {
+              err = Z_ERRNO;
+              break;
+            }
+          } else {
+            err = Z_ERRNO;
+            break;
+          }
+        }
+
+        /* Data */
+        {
+          int dataSize = cpsize;
+          if (dataSize == 0) {
+            dataSize = uncpsize;
+          }
+          if (dataSize > 0) {
+            char* data = malloc(dataSize);
+            if (data != NULL) {
+              if ((int)fread(data, 1, dataSize, fpZip) == dataSize) {
+                if ((int)fwrite(data, 1, dataSize, fpOut) == dataSize) {
+                  offset += dataSize;
+                  totalBytes += dataSize;
+                } else {
+                  err = Z_ERRNO;
+                }
+              } else {
+                err = Z_ERRNO;
+              }
+              free(data);
+              if (err != Z_OK) {
+                break;
+              }
+            } else {
+              err = Z_MEM_ERROR;
+              break;
+            }
+          }
+        }
+
+        /* Central directory entry */
+        {
+          char header[46];
+          char* comment = "";
+          int comsize = (int) strlen(comment);
+          WRITE_32(header, 0x02014b50);
+          WRITE_16(header + 4, version);
+          WRITE_16(header + 6, version);
+          WRITE_16(header + 8, gpflag);
+          WRITE_16(header + 10, method);
+          WRITE_16(header + 12, filetime);
+          WRITE_16(header + 14, filedate);
+          WRITE_32(header + 16, crc);
+          WRITE_32(header + 20, cpsize);
+          WRITE_32(header + 24, uncpsize);
+          WRITE_16(header + 28, fnsize);
+          WRITE_16(header + 30, extsize);
+          WRITE_16(header + 32, comsize);
+          WRITE_16(header + 34, 0);     /* disk # */
+          WRITE_16(header + 36, 0);     /* int attrb */
+          WRITE_32(header + 38, 0);     /* ext attrb */
+          WRITE_32(header + 42, currentOffset);
+          /* Header */
+          if (fwrite(header, 1, 46, fpOutCD) == 46) {
+            offsetCD += 46;
+
+            /* Filename */
+            if (fnsize > 0) {
+              if (fwrite(filename, 1, fnsize, fpOutCD) == fnsize) {
+                offsetCD += fnsize;
+              } else {
+                err = Z_ERRNO;
+                break;
+              }
+            } else {
+              err = Z_STREAM_ERROR;
+              break;
+            }
+
+            /* Extra field */
+            if (extsize > 0) {
+              if (fwrite(extra, 1, extsize, fpOutCD) == extsize) {
+                offsetCD += extsize;
+              } else {
+                err = Z_ERRNO;
+                break;
+              }
+            }
+
+            /* Comment field */
+            if (comsize > 0) {
+              if ((int)fwrite(comment, 1, comsize, fpOutCD) == comsize) {
+                offsetCD += comsize;
+              } else {
+                err = Z_ERRNO;
+                break;
+              }
+            }
+
+
+          } else {
+            err = Z_ERRNO;
+            break;
+          }
+        }
+
+        /* Success */
+        entries++;
+
+      } else {
+        break;
+      }
+    }
+
+    /* Final central directory  */
+    {
+      int entriesZip = entries;
+      char header[22];
+      char* comment = ""; // "ZIP File recovered by zlib/minizip/mztools";
+      int comsize = (int) strlen(comment);
+      if (entriesZip > 0xffff) {
+        entriesZip = 0xffff;
+      }
+      WRITE_32(header, 0x06054b50);
+      WRITE_16(header + 4, 0);    /* disk # */
+      WRITE_16(header + 6, 0);    /* disk # */
+      WRITE_16(header + 8, entriesZip);   /* hack */
+      WRITE_16(header + 10, entriesZip);  /* hack */
+      WRITE_32(header + 12, offsetCD);    /* size of CD */
+      WRITE_32(header + 16, offset);      /* offset to CD */
+      WRITE_16(header + 20, comsize);     /* comment */
+
+      /* Header */
+      if (fwrite(header, 1, 22, fpOutCD) == 22) {
+
+        /* Comment field */
+        if (comsize > 0) {
+          if ((int)fwrite(comment, 1, comsize, fpOutCD) != comsize) {
+            err = Z_ERRNO;
+          }
+        }
+
+      } else {
+        err = Z_ERRNO;
+      }
+    }
+
+    /* Final merge (file + central directory) */
+    fclose(fpOutCD);
+    if (err == Z_OK) {
+      fpOutCD = fopen(fileOutTmp, "rb");
+      if (fpOutCD != NULL) {
+        int nRead;
+        char buffer[8192];
+        while ( (nRead = (int)fread(buffer, 1, sizeof(buffer), fpOutCD)) > 0) {
+          if ((int)fwrite(buffer, 1, nRead, fpOut) != nRead) {
+            err = Z_ERRNO;
+            break;
+          }
+        }
+        fclose(fpOutCD);
+      }
+    }
+
+    /* Close */
+    fclose(fpZip);
+    fclose(fpOut);
+
+    /* Wipe temporary file */
+    (void)remove(fileOutTmp);
+
+    /* Number of recovered entries */
+    if (err == Z_OK) {
+      if (nRecovered != NULL) {
+        *nRecovered = entries;
+      }
+      if (bytesRecovered != NULL) {
+        *bytesRecovered = totalBytes;
+      }
+    }
+  } else {
+    err = Z_STREAM_ERROR;
+  }
+  return err;
+}

+ 37 - 0
library/third_party/minizip/mztools.h

@@ -0,0 +1,37 @@
+/*
+  Additional tools for Minizip
+  Code: Xavier Roche '2004
+  License: Same as ZLIB (www.gzip.org)
+*/
+
+#ifndef _zip_tools_H
+#define _zip_tools_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _ZLIB_H
+#include "zlib.h"
+#endif
+
+#include "unzip.h"
+
+/* Repair a ZIP file (missing central directory)
+   file: file to recover
+   fileOut: output file after recovery
+   fileOutTmp: temporary file name used for recovery
+*/
+extern int ZEXPORT unzRepair(const char* file,
+                             const char* fileOut,
+                             const char* fileOutTmp,
+                             uLong* nRecovered,
+                             uLong* bytesRecovered);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif

+ 2125 - 0
library/third_party/minizip/unzip.c

@@ -0,0 +1,2125 @@
+/* unzip.c -- IO for uncompress .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications of Unzip for Zip64
+         Copyright (C) 2007-2008 Even Rouault
+
+         Modifications for Zip64 support on both zip and unzip
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+
+  ------------------------------------------------------------------------------------
+  Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of
+  compatibility with older software. The following is from the original crypt.c.
+  Code woven in by Terry Thorsen 1/2003.
+
+  Copyright (c) 1990-2000 Info-ZIP.  All rights reserved.
+
+  See the accompanying file LICENSE, version 2000-Apr-09 or later
+  (the contents of which are also included in zip.h) for terms of use.
+  If, for some reason, all these files are missing, the Info-ZIP license
+  also may be found at:  ftp://ftp.info-zip.org/pub/infozip/license.html
+
+        crypt.c (full version) by Info-ZIP.      Last revised:  [see crypt.h]
+
+  The encryption/decryption parts of this source code (as opposed to the
+  non-echoing password parts) were originally written in Europe.  The
+  whole source package can be freely distributed, including from the USA.
+  (Prior to January 2000, re-export from the US was a violation of US law.)
+
+        This encryption code is a direct transcription of the algorithm from
+  Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
+  file (appnote.txt) is distributed with the PKZIP program (even in the
+  version without encryption capabilities).
+
+        ------------------------------------------------------------------------------------
+
+        Changes in unzip.c
+
+        2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos
+  2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz*
+  2007-2008 - Even Rouault - Remove old C style function prototypes
+  2007-2008 - Even Rouault - Add unzip support for ZIP64
+
+        Copyright (C) 2007-2008 Even Rouault
+
+
+        Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again).
+  Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G
+                                should only read the compressed/uncompressed size from the Zip64 format if
+                                the size from normal header was 0xFFFFFFFF
+  Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant
+        Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required)
+                                Patch created by Daniel Borca
+
+  Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer
+
+  Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson
+
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef NOUNCRYPT
+        #define NOUNCRYPT
+#endif
+
+#include "zlib.h"
+#include "unzip.h"
+
+#ifdef STDC
+#  include <stddef.h>
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+#ifdef NO_ERRNO_H
+    extern int errno;
+#else
+#   include <errno.h>
+#endif
+
+
+#ifndef local
+#  define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+
+#ifndef CASESENSITIVITYDEFAULT_NO
+#  if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES)
+#    define CASESENSITIVITYDEFAULT_NO
+#  endif
+#endif
+
+
+#ifndef UNZ_BUFSIZE
+#define UNZ_BUFSIZE (16384)
+#endif
+
+#ifndef UNZ_MAXFILENAMEINZIP
+#define UNZ_MAXFILENAMEINZIP (256)
+#endif
+
+#ifndef ALLOC
+# define ALLOC(size) (malloc(size))
+#endif
+#ifndef TRYFREE
+# define TRYFREE(p) {if (p) free(p);}
+#endif
+
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZEZIPLOCALHEADER (0x1e)
+
+
+const char unz_copyright[] =
+   " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
+
+/* unz_file_info_interntal contain internal info about a file in zipfile*/
+typedef struct unz_file_info64_internal_s
+{
+    ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */
+} unz_file_info64_internal;
+
+
+/* file_in_zip_read_info_s contain internal information about a file in zipfile,
+    when reading and decompress it */
+typedef struct
+{
+    char  *read_buffer;         /* internal buffer for compressed data */
+    z_stream stream;            /* zLib stream structure for inflate */
+
+#ifdef HAVE_BZIP2
+    bz_stream bstream;          /* bzLib stream structure for bziped */
+#endif
+
+    ZPOS64_T pos_in_zipfile;       /* position in byte on the zipfile, for fseek*/
+    uLong stream_initialised;   /* flag set if stream structure is initialised*/
+
+    ZPOS64_T offset_local_extrafield;/* offset of the local extra field */
+    uInt  size_local_extrafield;/* size of the local extra field */
+    ZPOS64_T pos_local_extrafield;   /* position in the local extra field in read*/
+    ZPOS64_T total_out_64;
+
+    uLong crc32;                /* crc32 of all data uncompressed */
+    uLong crc32_wait;           /* crc32 we must obtain after decompress all */
+    ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */
+    ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/
+    zlib_filefunc64_32_def z_filefunc;
+    voidpf filestream;        /* io structore of the zipfile */
+    uLong compression_method;   /* compression method (0==store) */
+    ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
+    int   raw;
+} file_in_zip64_read_info_s;
+
+
+/* unz64_s contain internal information about the zipfile
+*/
+typedef struct
+{
+    zlib_filefunc64_32_def z_filefunc;
+    int is64bitOpenFunction;
+    voidpf filestream;        /* io structore of the zipfile */
+    unz_global_info64 gi;       /* public global information */
+    ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
+    ZPOS64_T num_file;             /* number of the current file in the zipfile*/
+    ZPOS64_T pos_in_central_dir;   /* pos of the current file in the central dir*/
+    ZPOS64_T current_file_ok;      /* flag about the usability of the current file*/
+    ZPOS64_T central_pos;          /* position of the beginning of the central dir*/
+
+    ZPOS64_T size_central_dir;     /* size of the central directory  */
+    ZPOS64_T offset_central_dir;   /* offset of start of central directory with
+                                   respect to the starting disk number */
+
+    unz_file_info64 cur_file_info; /* public info about the current file in zip*/
+    unz_file_info64_internal cur_file_info_internal; /* private info about it*/
+    file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current
+                                        file if we are decompressing it */
+    int encrypted;
+
+    int isZip64;
+
+#    ifndef NOUNCRYPT
+    unsigned long keys[3];     /* keys defining the pseudo-random sequence */
+    const z_crc_t* pcrc_32_tab;
+#    endif
+} unz64_s;
+
+
+#ifndef NOUNCRYPT
+#include "crypt.h"
+#endif
+
+/* ===========================================================================
+     Read a byte from a gz_stream; update next_in and avail_in. Return EOF
+   for end of file.
+   IN assertion: the stream s has been sucessfully opened for reading.
+*/
+
+
+local int unz64local_getByte OF((
+    const zlib_filefunc64_32_def* pzlib_filefunc_def,
+    voidpf filestream,
+    int *pi));
+
+local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)
+{
+    unsigned char c;
+    int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1);
+    if (err==1)
+    {
+        *pi = (int)c;
+        return UNZ_OK;
+    }
+    else
+    {
+        if (ZERROR64(*pzlib_filefunc_def,filestream))
+            return UNZ_ERRNO;
+        else
+            return UNZ_EOF;
+    }
+}
+
+
+/* ===========================================================================
+   Reads a long in LSB order from the given gz_stream. Sets
+*/
+local int unz64local_getShort OF((
+    const zlib_filefunc64_32_def* pzlib_filefunc_def,
+    voidpf filestream,
+    uLong *pX));
+
+local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def,
+                             voidpf filestream,
+                             uLong *pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((uLong)i)<<8;
+
+    if (err==UNZ_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int unz64local_getLong OF((
+    const zlib_filefunc64_32_def* pzlib_filefunc_def,
+    voidpf filestream,
+    uLong *pX));
+
+local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def,
+                            voidpf filestream,
+                            uLong *pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((uLong)i)<<8;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((uLong)i)<<16;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<24;
+
+    if (err==UNZ_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int unz64local_getLong64 OF((
+    const zlib_filefunc64_32_def* pzlib_filefunc_def,
+    voidpf filestream,
+    ZPOS64_T *pX));
+
+
+local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def,
+                            voidpf filestream,
+                            ZPOS64_T *pX)
+{
+    ZPOS64_T x ;
+    int i = 0;
+    int err;
+
+    err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (ZPOS64_T)i;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<8;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<16;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<24;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<32;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<40;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<48;
+
+    if (err==UNZ_OK)
+        err = unz64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x |= ((ZPOS64_T)i)<<56;
+
+    if (err==UNZ_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+/* My own strcmpi / strcasecmp */
+local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2)
+{
+    for (;;)
+    {
+        char c1=*(fileName1++);
+        char c2=*(fileName2++);
+        if ((c1>='a') && (c1<='z'))
+            c1 -= 0x20;
+        if ((c2>='a') && (c2<='z'))
+            c2 -= 0x20;
+        if (c1=='\0')
+            return ((c2=='\0') ? 0 : -1);
+        if (c2=='\0')
+            return 1;
+        if (c1<c2)
+            return -1;
+        if (c1>c2)
+            return 1;
+    }
+}
+
+
+#ifdef  CASESENSITIVITYDEFAULT_NO
+#define CASESENSITIVITYDEFAULTVALUE 2
+#else
+#define CASESENSITIVITYDEFAULTVALUE 1
+#endif
+
+#ifndef STRCMPCASENOSENTIVEFUNCTION
+#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal
+#endif
+
+/*
+   Compare two filename (fileName1,fileName2).
+   If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
+   If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
+                                                                or strcasecmp)
+   If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
+        (like 1 on Unix, 2 on Windows)
+
+*/
+extern int ZEXPORT unzStringFileNameCompare (const char*  fileName1,
+                                                 const char*  fileName2,
+                                                 int iCaseSensitivity)
+
+{
+    if (iCaseSensitivity==0)
+        iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE;
+
+    if (iCaseSensitivity==1)
+        return strcmp(fileName1,fileName2);
+
+    return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2);
+}
+
+#ifndef BUFREADCOMMENT
+#define BUFREADCOMMENT (0x400)
+#endif
+
+/*
+  Locate the Central directory of a zipfile (at the end, just before
+    the global comment)
+*/
+local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+    unsigned char* buf;
+    ZPOS64_T uSizeFile;
+    ZPOS64_T uBackRead;
+    ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+    ZPOS64_T uPosFound=0;
+
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+        return 0;
+
+
+    uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+    if (uMaxBack>uSizeFile)
+        uMaxBack = uSizeFile;
+
+    buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+    if (buf==NULL)
+        return 0;
+
+    uBackRead = 4;
+    while (uBackRead<uMaxBack)
+    {
+        uLong uReadSize;
+        ZPOS64_T uReadPos ;
+        int i;
+        if (uBackRead+BUFREADCOMMENT>uMaxBack)
+            uBackRead = uMaxBack;
+        else
+            uBackRead+=BUFREADCOMMENT;
+        uReadPos = uSizeFile-uBackRead ;
+
+        uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+                     (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+        if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            break;
+
+        if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+            break;
+
+        for (i=(int)uReadSize-3; (i--)>0;)
+            if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+                ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
+            {
+                uPosFound = uReadPos+i;
+                break;
+            }
+
+        if (uPosFound!=0)
+            break;
+    }
+    TRYFREE(buf);
+    return uPosFound;
+}
+
+
+/*
+  Locate the Central directory 64 of a zipfile (at the end, just before
+    the global comment)
+*/
+local ZPOS64_T unz64local_SearchCentralDir64 OF((
+    const zlib_filefunc64_32_def* pzlib_filefunc_def,
+    voidpf filestream));
+
+local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def,
+                                      voidpf filestream)
+{
+    unsigned char* buf;
+    ZPOS64_T uSizeFile;
+    ZPOS64_T uBackRead;
+    ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+    ZPOS64_T uPosFound=0;
+    uLong uL;
+                ZPOS64_T relativeOffset;
+
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+        return 0;
+
+
+    uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+    if (uMaxBack>uSizeFile)
+        uMaxBack = uSizeFile;
+
+    buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+    if (buf==NULL)
+        return 0;
+
+    uBackRead = 4;
+    while (uBackRead<uMaxBack)
+    {
+        uLong uReadSize;
+        ZPOS64_T uReadPos;
+        int i;
+        if (uBackRead+BUFREADCOMMENT>uMaxBack)
+            uBackRead = uMaxBack;
+        else
+            uBackRead+=BUFREADCOMMENT;
+        uReadPos = uSizeFile-uBackRead ;
+
+        uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+                     (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+        if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            break;
+
+        if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+            break;
+
+        for (i=(int)uReadSize-3; (i--)>0;)
+            if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+                ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07))
+            {
+                uPosFound = uReadPos+i;
+                break;
+            }
+
+        if (uPosFound!=0)
+            break;
+    }
+    TRYFREE(buf);
+    if (uPosFound == 0)
+        return 0;
+
+    /* Zip64 end of central directory locator */
+    if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0)
+        return 0;
+
+    /* the signature, already checked */
+    if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK)
+        return 0;
+
+    /* number of the disk with the start of the zip64 end of  central directory */
+    if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK)
+        return 0;
+    if (uL != 0)
+        return 0;
+
+    /* relative offset of the zip64 end of central directory record */
+    if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK)
+        return 0;
+
+    /* total number of disks */
+    if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK)
+        return 0;
+    if (uL != 1)
+        return 0;
+
+    /* Goto end of central directory record */
+    if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0)
+        return 0;
+
+     /* the signature */
+    if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK)
+        return 0;
+
+    if (uL != 0x06064b50)
+        return 0;
+
+    return relativeOffset;
+}
+
+/*
+  Open a Zip file. path contain the full pathname (by example,
+     on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer
+     "zlib/zlib114.zip".
+     If the zipfile cannot be opened (file doesn't exist or in not valid), the
+       return value is NULL.
+     Else, the return value is a unzFile Handle, usable with other function
+       of this unzip package.
+*/
+local unzFile unzOpenInternal (const void *path,
+                               zlib_filefunc64_32_def* pzlib_filefunc64_32_def,
+                               int is64bitOpenFunction)
+{
+    unz64_s us;
+    unz64_s *s;
+    ZPOS64_T central_pos;
+    uLong   uL;
+
+    uLong number_disk;          /* number of the current dist, used for
+                                   spaning ZIP, unsupported, always 0*/
+    uLong number_disk_with_CD;  /* number the the disk with central dir, used
+                                   for spaning ZIP, unsupported, always 0*/
+    ZPOS64_T number_entry_CD;      /* total number of entries in
+                                   the central dir
+                                   (same than number_entry on nospan) */
+
+    int err=UNZ_OK;
+
+    if (unz_copyright[0]!=' ')
+        return NULL;
+
+    us.z_filefunc.zseek32_file = NULL;
+    us.z_filefunc.ztell32_file = NULL;
+    if (pzlib_filefunc64_32_def==NULL)
+        fill_fopen64_filefunc(&us.z_filefunc.zfile_func64);
+    else
+        us.z_filefunc = *pzlib_filefunc64_32_def;
+    us.is64bitOpenFunction = is64bitOpenFunction;
+
+
+
+    us.filestream = ZOPEN64(us.z_filefunc,
+                                                 path,
+                                                 ZLIB_FILEFUNC_MODE_READ |
+                                                 ZLIB_FILEFUNC_MODE_EXISTING);
+    if (us.filestream==NULL)
+        return NULL;
+
+    central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream);
+    if (central_pos)
+    {
+        uLong uS;
+        ZPOS64_T uL64;
+
+        us.isZip64 = 1;
+
+        if (ZSEEK64(us.z_filefunc, us.filestream,
+                                      central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+        err=UNZ_ERRNO;
+
+        /* the signature, already checked */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* size of zip64 end of central directory record */
+        if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* version made by */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* version needed to extract */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* number of this disk */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* number of the disk with the start of the central directory */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* total number of entries in the central directory on this disk */
+        if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* total number of entries in the central directory */
+        if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        if ((number_entry_CD!=us.gi.number_entry) ||
+            (number_disk_with_CD!=0) ||
+            (number_disk!=0))
+            err=UNZ_BADZIPFILE;
+
+        /* size of the central directory */
+        if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* offset of start of central directory with respect to the
+          starting disk number */
+        if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        us.gi.size_comment = 0;
+    }
+    else
+    {
+        central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream);
+        if (central_pos==0)
+            err=UNZ_ERRNO;
+
+        us.isZip64 = 0;
+
+        if (ZSEEK64(us.z_filefunc, us.filestream,
+                                        central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err=UNZ_ERRNO;
+
+        /* the signature, already checked */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* number of this disk */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* number of the disk with the start of the central directory */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK)
+            err=UNZ_ERRNO;
+
+        /* total number of entries in the central dir on this disk */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+        us.gi.number_entry = uL;
+
+        /* total number of entries in the central dir */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+        number_entry_CD = uL;
+
+        if ((number_entry_CD!=us.gi.number_entry) ||
+            (number_disk_with_CD!=0) ||
+            (number_disk!=0))
+            err=UNZ_BADZIPFILE;
+
+        /* size of the central directory */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+        us.size_central_dir = uL;
+
+        /* offset of start of central directory with respect to the
+            starting disk number */
+        if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK)
+            err=UNZ_ERRNO;
+        us.offset_central_dir = uL;
+
+        /* zipfile comment length */
+        if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK)
+            err=UNZ_ERRNO;
+    }
+
+    if ((central_pos<us.offset_central_dir+us.size_central_dir) &&
+        (err==UNZ_OK))
+        err=UNZ_BADZIPFILE;
+
+    if (err!=UNZ_OK)
+    {
+        ZCLOSE64(us.z_filefunc, us.filestream);
+        return NULL;
+    }
+
+    us.byte_before_the_zipfile = central_pos -
+                            (us.offset_central_dir+us.size_central_dir);
+    us.central_pos = central_pos;
+    us.pfile_in_zip_read = NULL;
+    us.encrypted = 0;
+
+
+    s=(unz64_s*)ALLOC(sizeof(unz64_s));
+    if( s != NULL)
+    {
+        *s=us;
+        unzGoToFirstFile((unzFile)s);
+    }
+    return (unzFile)s;
+}
+
+
+extern unzFile ZEXPORT unzOpen2 (const char *path,
+                                        zlib_filefunc_def* pzlib_filefunc32_def)
+{
+    if (pzlib_filefunc32_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def);
+        return unzOpenInternal(path, &zlib_filefunc64_32_def_fill, 0);
+    }
+    else
+        return unzOpenInternal(path, NULL, 0);
+}
+
+extern unzFile ZEXPORT unzOpen2_64 (const void *path,
+                                     zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    if (pzlib_filefunc_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
+        zlib_filefunc64_32_def_fill.ztell32_file = NULL;
+        zlib_filefunc64_32_def_fill.zseek32_file = NULL;
+        return unzOpenInternal(path, &zlib_filefunc64_32_def_fill, 1);
+    }
+    else
+        return unzOpenInternal(path, NULL, 1);
+}
+
+extern unzFile ZEXPORT unzOpen (const char *path)
+{
+    return unzOpenInternal(path, NULL, 0);
+}
+
+extern unzFile ZEXPORT unzOpen64 (const void *path)
+{
+    return unzOpenInternal(path, NULL, 1);
+}
+
+/*
+  Close a ZipFile opened with unzOpen.
+  If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
+    these files MUST be closed with unzCloseCurrentFile before call unzClose.
+  return UNZ_OK if there is no problem. */
+extern int ZEXPORT unzClose (unzFile file)
+{
+    unz64_s* s;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+
+    if (s->pfile_in_zip_read!=NULL)
+        unzCloseCurrentFile(file);
+
+    ZCLOSE64(s->z_filefunc, s->filestream);
+    TRYFREE(s);
+    return UNZ_OK;
+}
+
+
+/*
+  Write info about the ZipFile in the *pglobal_info structure.
+  No preparation of the structure is needed
+  return UNZ_OK if there is no problem. */
+extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info)
+{
+    unz64_s* s;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    *pglobal_info=s->gi;
+    return UNZ_OK;
+}
+
+extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32)
+{
+    unz64_s* s;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    /* to do : check if number_entry is not truncated */
+    pglobal_info32->number_entry = (uLong)s->gi.number_entry;
+    pglobal_info32->size_comment = s->gi.size_comment;
+    return UNZ_OK;
+}
+/*
+   Translate date/time from Dos format to tm_unz (readable more easilty)
+*/
+local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm)
+{
+    ZPOS64_T uDate;
+    uDate = (ZPOS64_T)(ulDosDate>>16);
+    ptm->tm_mday = (uInt)(uDate&0x1f) ;
+    ptm->tm_mon =  (uInt)((((uDate)&0x1E0)/0x20)-1) ;
+    ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ;
+
+    ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800);
+    ptm->tm_min =  (uInt) ((ulDosDate&0x7E0)/0x20) ;
+    ptm->tm_sec =  (uInt) (2*(ulDosDate&0x1f)) ;
+}
+
+/*
+  Get Info about the current file in the zipfile, with internal only info
+*/
+local int unz64local_GetCurrentFileInfoInternal OF((unzFile file,
+                                                  unz_file_info64 *pfile_info,
+                                                  unz_file_info64_internal
+                                                  *pfile_info_internal,
+                                                  char *szFileName,
+                                                  uLong fileNameBufferSize,
+                                                  void *extraField,
+                                                  uLong extraFieldBufferSize,
+                                                  char *szComment,
+                                                  uLong commentBufferSize));
+
+local int unz64local_GetCurrentFileInfoInternal (unzFile file,
+                                                  unz_file_info64 *pfile_info,
+                                                  unz_file_info64_internal
+                                                  *pfile_info_internal,
+                                                  char *szFileName,
+                                                  uLong fileNameBufferSize,
+                                                  void *extraField,
+                                                  uLong extraFieldBufferSize,
+                                                  char *szComment,
+                                                  uLong commentBufferSize)
+{
+    unz64_s* s;
+    unz_file_info64 file_info;
+    unz_file_info64_internal file_info_internal;
+    int err=UNZ_OK;
+    uLong uMagic;
+    long lSeek=0;
+    uLong uL;
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    if (ZSEEK64(s->z_filefunc, s->filestream,
+              s->pos_in_central_dir+s->byte_before_the_zipfile,
+              ZLIB_FILEFUNC_SEEK_SET)!=0)
+        err=UNZ_ERRNO;
+
+
+    /* we check the magic */
+    if (err==UNZ_OK)
+    {
+        if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK)
+            err=UNZ_ERRNO;
+        else if (uMagic!=0x02014b50)
+            err=UNZ_BADZIPFILE;
+    }
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date);
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
+        err=UNZ_ERRNO;
+    file_info.compressed_size = uL;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
+        err=UNZ_ERRNO;
+    file_info.uncompressed_size = uL;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+                // relative offset of local header
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
+        err=UNZ_ERRNO;
+    file_info_internal.offset_curfile = uL;
+
+    lSeek+=file_info.size_filename;
+    if ((err==UNZ_OK) && (szFileName!=NULL))
+    {
+        uLong uSizeRead ;
+        if (file_info.size_filename<fileNameBufferSize)
+        {
+            *(szFileName+file_info.size_filename)='\0';
+            uSizeRead = file_info.size_filename;
+        }
+        else
+            uSizeRead = fileNameBufferSize;
+
+        if ((file_info.size_filename>0) && (fileNameBufferSize>0))
+            if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead)
+                err=UNZ_ERRNO;
+        lSeek -= uSizeRead;
+    }
+
+    // Read extrafield
+    if ((err==UNZ_OK) && (extraField!=NULL))
+    {
+        ZPOS64_T uSizeRead ;
+        if (file_info.size_file_extra<extraFieldBufferSize)
+            uSizeRead = file_info.size_file_extra;
+        else
+            uSizeRead = extraFieldBufferSize;
+
+        if (lSeek!=0)
+        {
+            if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0)
+                lSeek=0;
+            else
+                err=UNZ_ERRNO;
+        }
+
+        if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0))
+            if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead)
+                err=UNZ_ERRNO;
+
+        lSeek += file_info.size_file_extra - (uLong)uSizeRead;
+    }
+    else
+        lSeek += file_info.size_file_extra;
+
+
+    if ((err==UNZ_OK) && (file_info.size_file_extra != 0))
+    {
+                                uLong acc = 0;
+
+        // since lSeek now points to after the extra field we need to move back
+        lSeek -= file_info.size_file_extra;
+
+        if (lSeek!=0)
+        {
+            if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0)
+                lSeek=0;
+            else
+                err=UNZ_ERRNO;
+        }
+
+        while(acc < file_info.size_file_extra)
+        {
+            uLong headerId;
+                                                uLong dataSize;
+
+            if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK)
+                err=UNZ_ERRNO;
+
+            if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK)
+                err=UNZ_ERRNO;
+
+            /* ZIP64 extra fields */
+            if (headerId == 0x0001)
+            {
+                                                        uLong uL;
+
+                                                                if(file_info.uncompressed_size == MAXU32)
+                                                                {
+                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK)
+                                                                                        err=UNZ_ERRNO;
+                                                                }
+
+                                                                if(file_info.compressed_size == MAXU32)
+                                                                {
+                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK)
+                                                                                  err=UNZ_ERRNO;
+                                                                }
+
+                                                                if(file_info_internal.offset_curfile == MAXU32)
+                                                                {
+                                                                        /* Relative Header offset */
+                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK)
+                                                                                err=UNZ_ERRNO;
+                                                                }
+
+                                                                if(file_info.disk_num_start == MAXU32)
+                                                                {
+                                                                        /* Disk Start Number */
+                                                                        if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
+                                                                                err=UNZ_ERRNO;
+                                                                }
+
+            }
+            else
+            {
+                if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0)
+                    err=UNZ_ERRNO;
+            }
+
+            acc += 2 + 2 + dataSize;
+        }
+    }
+
+    if ((err==UNZ_OK) && (szComment!=NULL))
+    {
+        uLong uSizeRead ;
+        if (file_info.size_file_comment<commentBufferSize)
+        {
+            *(szComment+file_info.size_file_comment)='\0';
+            uSizeRead = file_info.size_file_comment;
+        }
+        else
+            uSizeRead = commentBufferSize;
+
+        if (lSeek!=0)
+        {
+            if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0)
+                lSeek=0;
+            else
+                err=UNZ_ERRNO;
+        }
+
+        if ((file_info.size_file_comment>0) && (commentBufferSize>0))
+            if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead)
+                err=UNZ_ERRNO;
+        lSeek+=file_info.size_file_comment - uSizeRead;
+    }
+    else
+        lSeek+=file_info.size_file_comment;
+
+
+    if ((err==UNZ_OK) && (pfile_info!=NULL))
+        *pfile_info=file_info;
+
+    if ((err==UNZ_OK) && (pfile_info_internal!=NULL))
+        *pfile_info_internal=file_info_internal;
+
+    return err;
+}
+
+
+
+/*
+  Write info about the ZipFile in the *pglobal_info structure.
+  No preparation of the structure is needed
+  return UNZ_OK if there is no problem.
+*/
+extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file,
+                                          unz_file_info64 * pfile_info,
+                                          char * szFileName, uLong fileNameBufferSize,
+                                          void *extraField, uLong extraFieldBufferSize,
+                                          char* szComment,  uLong commentBufferSize)
+{
+    return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL,
+                                                szFileName,fileNameBufferSize,
+                                                extraField,extraFieldBufferSize,
+                                                szComment,commentBufferSize);
+}
+
+extern int ZEXPORT unzGetCurrentFileInfo (unzFile file,
+                                          unz_file_info * pfile_info,
+                                          char * szFileName, uLong fileNameBufferSize,
+                                          void *extraField, uLong extraFieldBufferSize,
+                                          char* szComment,  uLong commentBufferSize)
+{
+    int err;
+    unz_file_info64 file_info64;
+    err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL,
+                                                szFileName,fileNameBufferSize,
+                                                extraField,extraFieldBufferSize,
+                                                szComment,commentBufferSize);
+    if ((err==UNZ_OK) && (pfile_info != NULL))
+    {
+        pfile_info->version = file_info64.version;
+        pfile_info->version_needed = file_info64.version_needed;
+        pfile_info->flag = file_info64.flag;
+        pfile_info->compression_method = file_info64.compression_method;
+        pfile_info->dosDate = file_info64.dosDate;
+        pfile_info->crc = file_info64.crc;
+
+        pfile_info->size_filename = file_info64.size_filename;
+        pfile_info->size_file_extra = file_info64.size_file_extra;
+        pfile_info->size_file_comment = file_info64.size_file_comment;
+
+        pfile_info->disk_num_start = file_info64.disk_num_start;
+        pfile_info->internal_fa = file_info64.internal_fa;
+        pfile_info->external_fa = file_info64.external_fa;
+
+        pfile_info->tmu_date = file_info64.tmu_date,
+
+
+        pfile_info->compressed_size = (uLong)file_info64.compressed_size;
+        pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size;
+
+    }
+    return err;
+}
+/*
+  Set the current file of the zipfile to the first file.
+  return UNZ_OK if there is no problem
+*/
+extern int ZEXPORT unzGoToFirstFile (unzFile file)
+{
+    int err=UNZ_OK;
+    unz64_s* s;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    s->pos_in_central_dir=s->offset_central_dir;
+    s->num_file=0;
+    err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+                                             &s->cur_file_info_internal,
+                                             NULL,0,NULL,0,NULL,0);
+    s->current_file_ok = (err == UNZ_OK);
+    return err;
+}
+
+/*
+  Set the current file of the zipfile to the next file.
+  return UNZ_OK if there is no problem
+  return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
+*/
+extern int ZEXPORT unzGoToNextFile (unzFile  file)
+{
+    unz64_s* s;
+    int err;
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    if (!s->current_file_ok)
+        return UNZ_END_OF_LIST_OF_FILE;
+    if (s->gi.number_entry != 0xffff)    /* 2^16 files overflow hack */
+      if (s->num_file+1==s->gi.number_entry)
+        return UNZ_END_OF_LIST_OF_FILE;
+
+    s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename +
+            s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ;
+    s->num_file++;
+    err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+                                               &s->cur_file_info_internal,
+                                               NULL,0,NULL,0,NULL,0);
+    s->current_file_ok = (err == UNZ_OK);
+    return err;
+}
+
+
+/*
+  Try locate the file szFileName in the zipfile.
+  For the iCaseSensitivity signification, see unzStringFileNameCompare
+
+  return value :
+  UNZ_OK if the file is found. It becomes the current file.
+  UNZ_END_OF_LIST_OF_FILE if the file is not found
+*/
+extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity)
+{
+    unz64_s* s;
+    int err;
+
+    /* We remember the 'current' position in the file so that we can jump
+     * back there if we fail.
+     */
+    unz_file_info64 cur_file_infoSaved;
+    unz_file_info64_internal cur_file_info_internalSaved;
+    ZPOS64_T num_fileSaved;
+    ZPOS64_T pos_in_central_dirSaved;
+
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+
+    if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP)
+        return UNZ_PARAMERROR;
+
+    s=(unz64_s*)file;
+    if (!s->current_file_ok)
+        return UNZ_END_OF_LIST_OF_FILE;
+
+    /* Save the current state */
+    num_fileSaved = s->num_file;
+    pos_in_central_dirSaved = s->pos_in_central_dir;
+    cur_file_infoSaved = s->cur_file_info;
+    cur_file_info_internalSaved = s->cur_file_info_internal;
+
+    err = unzGoToFirstFile(file);
+
+    while (err == UNZ_OK)
+    {
+        char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1];
+        err = unzGetCurrentFileInfo64(file,NULL,
+                                    szCurrentFileName,sizeof(szCurrentFileName)-1,
+                                    NULL,0,NULL,0);
+        if (err == UNZ_OK)
+        {
+            if (unzStringFileNameCompare(szCurrentFileName,
+                                            szFileName,iCaseSensitivity)==0)
+                return UNZ_OK;
+            err = unzGoToNextFile(file);
+        }
+    }
+
+    /* We failed, so restore the state of the 'current file' to where we
+     * were.
+     */
+    s->num_file = num_fileSaved ;
+    s->pos_in_central_dir = pos_in_central_dirSaved ;
+    s->cur_file_info = cur_file_infoSaved;
+    s->cur_file_info_internal = cur_file_info_internalSaved;
+    return err;
+}
+
+
+/*
+///////////////////////////////////////////
+// Contributed by Ryan Haksi (mailto://[email protected])
+// I need random access
+//
+// Further optimization could be realized by adding an ability
+// to cache the directory in memory. The goal being a single
+// comprehensive file read to put the file I need in a memory.
+*/
+
+/*
+typedef struct unz_file_pos_s
+{
+    ZPOS64_T pos_in_zip_directory;   // offset in file
+    ZPOS64_T num_of_file;            // # of file
+} unz_file_pos;
+*/
+
+extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos*  file_pos)
+{
+    unz64_s* s;
+
+    if (file==NULL || file_pos==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    if (!s->current_file_ok)
+        return UNZ_END_OF_LIST_OF_FILE;
+
+    file_pos->pos_in_zip_directory  = s->pos_in_central_dir;
+    file_pos->num_of_file           = s->num_file;
+
+    return UNZ_OK;
+}
+
+extern int ZEXPORT unzGetFilePos(
+    unzFile file,
+    unz_file_pos* file_pos)
+{
+    unz64_file_pos file_pos64;
+    int err = unzGetFilePos64(file,&file_pos64);
+    if (err==UNZ_OK)
+    {
+        file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory;
+        file_pos->num_of_file = (uLong)file_pos64.num_of_file;
+    }
+    return err;
+}
+
+extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos)
+{
+    unz64_s* s;
+    int err;
+
+    if (file==NULL || file_pos==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+
+    /* jump to the right spot */
+    s->pos_in_central_dir = file_pos->pos_in_zip_directory;
+    s->num_file           = file_pos->num_of_file;
+
+    /* set the current file */
+    err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+                                               &s->cur_file_info_internal,
+                                               NULL,0,NULL,0,NULL,0);
+    /* return results */
+    s->current_file_ok = (err == UNZ_OK);
+    return err;
+}
+
+extern int ZEXPORT unzGoToFilePos(
+    unzFile file,
+    unz_file_pos* file_pos)
+{
+    unz64_file_pos file_pos64;
+    if (file_pos == NULL)
+        return UNZ_PARAMERROR;
+
+    file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory;
+    file_pos64.num_of_file = file_pos->num_of_file;
+    return unzGoToFilePos64(file,&file_pos64);
+}
+
+/*
+// Unzip Helper Functions - should be here?
+///////////////////////////////////////////
+*/
+
+/*
+  Read the local header of the current zipfile
+  Check the coherency of the local header and info in the end of central
+        directory about this file
+  store in *piSizeVar the size of extra info in local header
+        (filename and size of extra field data)
+*/
+local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar,
+                                                    ZPOS64_T * poffset_local_extrafield,
+                                                    uInt  * psize_local_extrafield)
+{
+    uLong uMagic,uData,uFlags;
+    uLong size_filename;
+    uLong size_extra_field;
+    int err=UNZ_OK;
+
+    *piSizeVar = 0;
+    *poffset_local_extrafield = 0;
+    *psize_local_extrafield = 0;
+
+    if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile +
+                                s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0)
+        return UNZ_ERRNO;
+
+
+    if (err==UNZ_OK)
+    {
+        if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK)
+            err=UNZ_ERRNO;
+        else if (uMagic!=0x04034b50)
+            err=UNZ_BADZIPFILE;
+    }
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK)
+        err=UNZ_ERRNO;
+/*
+    else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion))
+        err=UNZ_BADZIPFILE;
+*/
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK)
+        err=UNZ_ERRNO;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK)
+        err=UNZ_ERRNO;
+    else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method))
+        err=UNZ_BADZIPFILE;
+
+    if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) &&
+/* #ifdef HAVE_BZIP2 */
+                         (s->cur_file_info.compression_method!=Z_BZIP2ED) &&
+/* #endif */
+                         (s->cur_file_info.compression_method!=Z_DEFLATED))
+        err=UNZ_BADZIPFILE;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */
+        err=UNZ_ERRNO;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */
+        err=UNZ_ERRNO;
+    else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0))
+        err=UNZ_BADZIPFILE;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */
+        err=UNZ_ERRNO;
+    else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0))
+        err=UNZ_BADZIPFILE;
+
+    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */
+        err=UNZ_ERRNO;
+    else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0))
+        err=UNZ_BADZIPFILE;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK)
+        err=UNZ_ERRNO;
+    else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename))
+        err=UNZ_BADZIPFILE;
+
+    *piSizeVar += (uInt)size_filename;
+
+    if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK)
+        err=UNZ_ERRNO;
+    *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile +
+                                    SIZEZIPLOCALHEADER + size_filename;
+    *psize_local_extrafield = (uInt)size_extra_field;
+
+    *piSizeVar += (uInt)size_extra_field;
+
+    return err;
+}
+
+/*
+  Open for reading data the current file in the zipfile.
+  If there is no error and the file is opened, the return value is UNZ_OK.
+*/
+extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method,
+                                            int* level, int raw, const char* password)
+{
+    int err=UNZ_OK;
+    uInt iSizeVar;
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    ZPOS64_T offset_local_extrafield;  /* offset of the local extra field */
+    uInt  size_local_extrafield;    /* size of the local extra field */
+#    ifndef NOUNCRYPT
+    char source[12];
+#    else
+    if (password != NULL)
+        return UNZ_PARAMERROR;
+#    endif
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    if (!s->current_file_ok)
+        return UNZ_PARAMERROR;
+
+    if (s->pfile_in_zip_read != NULL)
+        unzCloseCurrentFile(file);
+
+    if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK)
+        return UNZ_BADZIPFILE;
+
+    pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s));
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_INTERNALERROR;
+
+    pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE);
+    pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield;
+    pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield;
+    pfile_in_zip_read_info->pos_local_extrafield=0;
+    pfile_in_zip_read_info->raw=raw;
+
+    if (pfile_in_zip_read_info->read_buffer==NULL)
+    {
+        TRYFREE(pfile_in_zip_read_info);
+        return UNZ_INTERNALERROR;
+    }
+
+    pfile_in_zip_read_info->stream_initialised=0;
+
+    if (method!=NULL)
+        *method = (int)s->cur_file_info.compression_method;
+
+    if (level!=NULL)
+    {
+        *level = 6;
+        switch (s->cur_file_info.flag & 0x06)
+        {
+          case 6 : *level = 1; break;
+          case 4 : *level = 2; break;
+          case 2 : *level = 9; break;
+        }
+    }
+
+    if ((s->cur_file_info.compression_method!=0) &&
+/* #ifdef HAVE_BZIP2 */
+        (s->cur_file_info.compression_method!=Z_BZIP2ED) &&
+/* #endif */
+        (s->cur_file_info.compression_method!=Z_DEFLATED))
+
+        err=UNZ_BADZIPFILE;
+
+    pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc;
+    pfile_in_zip_read_info->crc32=0;
+    pfile_in_zip_read_info->total_out_64=0;
+    pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method;
+    pfile_in_zip_read_info->filestream=s->filestream;
+    pfile_in_zip_read_info->z_filefunc=s->z_filefunc;
+    pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile;
+
+    pfile_in_zip_read_info->stream.total_out = 0;
+
+    if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw))
+    {
+#ifdef HAVE_BZIP2
+      pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0;
+      pfile_in_zip_read_info->bstream.bzfree = (free_func)0;
+      pfile_in_zip_read_info->bstream.opaque = (voidpf)0;
+      pfile_in_zip_read_info->bstream.state = (voidpf)0;
+
+      pfile_in_zip_read_info->stream.zalloc = (alloc_func)0;
+      pfile_in_zip_read_info->stream.zfree = (free_func)0;
+      pfile_in_zip_read_info->stream.opaque = (voidpf)0;
+      pfile_in_zip_read_info->stream.next_in = (voidpf)0;
+      pfile_in_zip_read_info->stream.avail_in = 0;
+
+      err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0);
+      if (err == Z_OK)
+        pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED;
+      else
+      {
+        TRYFREE(pfile_in_zip_read_info);
+        return err;
+      }
+#else
+      pfile_in_zip_read_info->raw=1;
+#endif
+    }
+    else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw))
+    {
+      pfile_in_zip_read_info->stream.zalloc = (alloc_func)0;
+      pfile_in_zip_read_info->stream.zfree = (free_func)0;
+      pfile_in_zip_read_info->stream.opaque = (voidpf)0;
+      pfile_in_zip_read_info->stream.next_in = 0;
+      pfile_in_zip_read_info->stream.avail_in = 0;
+
+      err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS);
+      if (err == Z_OK)
+        pfile_in_zip_read_info->stream_initialised=Z_DEFLATED;
+      else
+      {
+        TRYFREE(pfile_in_zip_read_info);
+        return err;
+      }
+        /* windowBits is passed < 0 to tell that there is no zlib header.
+         * Note that in this case inflate *requires* an extra "dummy" byte
+         * after the compressed stream in order to complete decompression and
+         * return Z_STREAM_END.
+         * In unzip, i don't wait absolutely Z_STREAM_END because I known the
+         * size of both compressed and uncompressed data
+         */
+    }
+    pfile_in_zip_read_info->rest_read_compressed =
+            s->cur_file_info.compressed_size ;
+    pfile_in_zip_read_info->rest_read_uncompressed =
+            s->cur_file_info.uncompressed_size ;
+
+
+    pfile_in_zip_read_info->pos_in_zipfile =
+            s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER +
+              iSizeVar;
+
+    pfile_in_zip_read_info->stream.avail_in = (uInt)0;
+
+    s->pfile_in_zip_read = pfile_in_zip_read_info;
+                s->encrypted = 0;
+
+#    ifndef NOUNCRYPT
+    if (password != NULL)
+    {
+        int i;
+        s->pcrc_32_tab = get_crc_table();
+        init_keys(password,s->keys,s->pcrc_32_tab);
+        if (ZSEEK64(s->z_filefunc, s->filestream,
+                  s->pfile_in_zip_read->pos_in_zipfile +
+                     s->pfile_in_zip_read->byte_before_the_zipfile,
+                  SEEK_SET)!=0)
+            return UNZ_INTERNALERROR;
+        if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12)
+            return UNZ_INTERNALERROR;
+
+        for (i = 0; i<12; i++)
+            zdecode(s->keys,s->pcrc_32_tab,source[i]);
+
+        s->pfile_in_zip_read->pos_in_zipfile+=12;
+        s->encrypted=1;
+    }
+#    endif
+
+
+    return UNZ_OK;
+}
+
+extern int ZEXPORT unzOpenCurrentFile (unzFile file)
+{
+    return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL);
+}
+
+extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char*  password)
+{
+    return unzOpenCurrentFile3(file, NULL, NULL, 0, password);
+}
+
+extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw)
+{
+    return unzOpenCurrentFile3(file, method, level, raw, NULL);
+}
+
+/** Addition for GDAL : START */
+
+extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file)
+{
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    s=(unz64_s*)file;
+    if (file==NULL)
+        return 0; //UNZ_PARAMERROR;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+    if (pfile_in_zip_read_info==NULL)
+        return 0; //UNZ_PARAMERROR;
+    return pfile_in_zip_read_info->pos_in_zipfile +
+                         pfile_in_zip_read_info->byte_before_the_zipfile;
+}
+
+/** Addition for GDAL : END */
+
+/*
+  Read bytes from the current file.
+  buf contain buffer where data must be copied
+  len the size of buf.
+
+  return the number of byte copied if somes bytes are copied
+  return 0 if the end of file was reached
+  return <0 with error code if there is an error
+    (UNZ_ERRNO for IO error, or zLib error for uncompress error)
+*/
+extern int ZEXPORT unzReadCurrentFile  (unzFile file, voidp buf, unsigned len)
+{
+    int err=UNZ_OK;
+    uInt iRead = 0;
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_PARAMERROR;
+
+
+    if (pfile_in_zip_read_info->read_buffer == NULL)
+        return UNZ_END_OF_LIST_OF_FILE;
+    if (len==0)
+        return 0;
+
+    pfile_in_zip_read_info->stream.next_out = (Bytef*)buf;
+
+    pfile_in_zip_read_info->stream.avail_out = (uInt)len;
+
+    if ((len>pfile_in_zip_read_info->rest_read_uncompressed) &&
+        (!(pfile_in_zip_read_info->raw)))
+        pfile_in_zip_read_info->stream.avail_out =
+            (uInt)pfile_in_zip_read_info->rest_read_uncompressed;
+
+    if ((len>pfile_in_zip_read_info->rest_read_compressed+
+           pfile_in_zip_read_info->stream.avail_in) &&
+         (pfile_in_zip_read_info->raw))
+        pfile_in_zip_read_info->stream.avail_out =
+            (uInt)pfile_in_zip_read_info->rest_read_compressed+
+            pfile_in_zip_read_info->stream.avail_in;
+
+    while (pfile_in_zip_read_info->stream.avail_out>0)
+    {
+        if ((pfile_in_zip_read_info->stream.avail_in==0) &&
+            (pfile_in_zip_read_info->rest_read_compressed>0))
+        {
+            uInt uReadThis = UNZ_BUFSIZE;
+            if (pfile_in_zip_read_info->rest_read_compressed<uReadThis)
+                uReadThis = (uInt)pfile_in_zip_read_info->rest_read_compressed;
+            if (uReadThis == 0)
+                return UNZ_EOF;
+            if (ZSEEK64(pfile_in_zip_read_info->z_filefunc,
+                      pfile_in_zip_read_info->filestream,
+                      pfile_in_zip_read_info->pos_in_zipfile +
+                         pfile_in_zip_read_info->byte_before_the_zipfile,
+                         ZLIB_FILEFUNC_SEEK_SET)!=0)
+                return UNZ_ERRNO;
+            if (ZREAD64(pfile_in_zip_read_info->z_filefunc,
+                      pfile_in_zip_read_info->filestream,
+                      pfile_in_zip_read_info->read_buffer,
+                      uReadThis)!=uReadThis)
+                return UNZ_ERRNO;
+
+
+#            ifndef NOUNCRYPT
+            if(s->encrypted)
+            {
+                uInt i;
+                for(i=0;i<uReadThis;i++)
+                  pfile_in_zip_read_info->read_buffer[i] =
+                      zdecode(s->keys,s->pcrc_32_tab,
+                              pfile_in_zip_read_info->read_buffer[i]);
+            }
+#            endif
+
+
+            pfile_in_zip_read_info->pos_in_zipfile += uReadThis;
+
+            pfile_in_zip_read_info->rest_read_compressed-=uReadThis;
+
+            pfile_in_zip_read_info->stream.next_in =
+                (Bytef*)pfile_in_zip_read_info->read_buffer;
+            pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis;
+        }
+
+        if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw))
+        {
+            uInt uDoCopy,i ;
+
+            if ((pfile_in_zip_read_info->stream.avail_in == 0) &&
+                (pfile_in_zip_read_info->rest_read_compressed == 0))
+                return (iRead==0) ? UNZ_EOF : iRead;
+
+            if (pfile_in_zip_read_info->stream.avail_out <
+                            pfile_in_zip_read_info->stream.avail_in)
+                uDoCopy = pfile_in_zip_read_info->stream.avail_out ;
+            else
+                uDoCopy = pfile_in_zip_read_info->stream.avail_in ;
+
+            for (i=0;i<uDoCopy;i++)
+                *(pfile_in_zip_read_info->stream.next_out+i) =
+                        *(pfile_in_zip_read_info->stream.next_in+i);
+
+            pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy;
+
+            pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,
+                                pfile_in_zip_read_info->stream.next_out,
+                                uDoCopy);
+            pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy;
+            pfile_in_zip_read_info->stream.avail_in -= uDoCopy;
+            pfile_in_zip_read_info->stream.avail_out -= uDoCopy;
+            pfile_in_zip_read_info->stream.next_out += uDoCopy;
+            pfile_in_zip_read_info->stream.next_in += uDoCopy;
+            pfile_in_zip_read_info->stream.total_out += uDoCopy;
+            iRead += uDoCopy;
+        }
+        else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED)
+        {
+#ifdef HAVE_BZIP2
+            uLong uTotalOutBefore,uTotalOutAfter;
+            const Bytef *bufBefore;
+            uLong uOutThis;
+
+            pfile_in_zip_read_info->bstream.next_in        = (char*)pfile_in_zip_read_info->stream.next_in;
+            pfile_in_zip_read_info->bstream.avail_in       = pfile_in_zip_read_info->stream.avail_in;
+            pfile_in_zip_read_info->bstream.total_in_lo32  = pfile_in_zip_read_info->stream.total_in;
+            pfile_in_zip_read_info->bstream.total_in_hi32  = 0;
+            pfile_in_zip_read_info->bstream.next_out       = (char*)pfile_in_zip_read_info->stream.next_out;
+            pfile_in_zip_read_info->bstream.avail_out      = pfile_in_zip_read_info->stream.avail_out;
+            pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out;
+            pfile_in_zip_read_info->bstream.total_out_hi32 = 0;
+
+            uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32;
+            bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out;
+
+            err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream);
+
+            uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32;
+            uOutThis = uTotalOutAfter-uTotalOutBefore;
+
+            pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis;
+
+            pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis));
+            pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis;
+            iRead += (uInt)(uTotalOutAfter - uTotalOutBefore);
+
+            pfile_in_zip_read_info->stream.next_in   = (Bytef*)pfile_in_zip_read_info->bstream.next_in;
+            pfile_in_zip_read_info->stream.avail_in  = pfile_in_zip_read_info->bstream.avail_in;
+            pfile_in_zip_read_info->stream.total_in  = pfile_in_zip_read_info->bstream.total_in_lo32;
+            pfile_in_zip_read_info->stream.next_out  = (Bytef*)pfile_in_zip_read_info->bstream.next_out;
+            pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out;
+            pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32;
+
+            if (err==BZ_STREAM_END)
+              return (iRead==0) ? UNZ_EOF : iRead;
+            if (err!=BZ_OK)
+              break;
+#endif
+        } // end Z_BZIP2ED
+        else
+        {
+            ZPOS64_T uTotalOutBefore,uTotalOutAfter;
+            const Bytef *bufBefore;
+            ZPOS64_T uOutThis;
+            int flush=Z_SYNC_FLUSH;
+
+            uTotalOutBefore = pfile_in_zip_read_info->stream.total_out;
+            bufBefore = pfile_in_zip_read_info->stream.next_out;
+
+            /*
+            if ((pfile_in_zip_read_info->rest_read_uncompressed ==
+                     pfile_in_zip_read_info->stream.avail_out) &&
+                (pfile_in_zip_read_info->rest_read_compressed == 0))
+                flush = Z_FINISH;
+            */
+            err=inflate(&pfile_in_zip_read_info->stream,flush);
+
+            if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL))
+              err = Z_DATA_ERROR;
+
+            uTotalOutAfter = pfile_in_zip_read_info->stream.total_out;
+            uOutThis = uTotalOutAfter-uTotalOutBefore;
+
+            pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis;
+
+            pfile_in_zip_read_info->crc32 =
+                crc32(pfile_in_zip_read_info->crc32,bufBefore,
+                        (uInt)(uOutThis));
+
+            pfile_in_zip_read_info->rest_read_uncompressed -=
+                uOutThis;
+
+            iRead += (uInt)(uTotalOutAfter - uTotalOutBefore);
+
+            if (err==Z_STREAM_END)
+                return (iRead==0) ? UNZ_EOF : iRead;
+            if (err!=Z_OK)
+                break;
+        }
+    }
+
+    if (err==Z_OK)
+        return iRead;
+    return err;
+}
+
+
+/*
+  Give the current position in uncompressed data
+*/
+extern z_off_t ZEXPORT unztell (unzFile file)
+{
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_PARAMERROR;
+
+    return (z_off_t)pfile_in_zip_read_info->stream.total_out;
+}
+
+extern ZPOS64_T ZEXPORT unztell64 (unzFile file)
+{
+
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    if (file==NULL)
+        return (ZPOS64_T)-1;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return (ZPOS64_T)-1;
+
+    return pfile_in_zip_read_info->total_out_64;
+}
+
+
+/*
+  return 1 if the end of file was reached, 0 elsewhere
+*/
+extern int ZEXPORT unzeof (unzFile file)
+{
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_PARAMERROR;
+
+    if (pfile_in_zip_read_info->rest_read_uncompressed == 0)
+        return 1;
+    else
+        return 0;
+}
+
+
+
+/*
+Read extra field from the current file (opened by unzOpenCurrentFile)
+This is the local-header version of the extra field (sometimes, there is
+more info in the local-header version than in the central-header)
+
+  if buf==NULL, it return the size of the local extra field that can be read
+
+  if buf!=NULL, len is the size of the buffer, the extra header is copied in
+    buf.
+  the return value is the number of bytes copied in buf, or (if <0)
+    the error code
+*/
+extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len)
+{
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    uInt read_now;
+    ZPOS64_T size_to_read;
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_PARAMERROR;
+
+    size_to_read = (pfile_in_zip_read_info->size_local_extrafield -
+                pfile_in_zip_read_info->pos_local_extrafield);
+
+    if (buf==NULL)
+        return (int)size_to_read;
+
+    if (len>size_to_read)
+        read_now = (uInt)size_to_read;
+    else
+        read_now = (uInt)len ;
+
+    if (read_now==0)
+        return 0;
+
+    if (ZSEEK64(pfile_in_zip_read_info->z_filefunc,
+              pfile_in_zip_read_info->filestream,
+              pfile_in_zip_read_info->offset_local_extrafield +
+              pfile_in_zip_read_info->pos_local_extrafield,
+              ZLIB_FILEFUNC_SEEK_SET)!=0)
+        return UNZ_ERRNO;
+
+    if (ZREAD64(pfile_in_zip_read_info->z_filefunc,
+              pfile_in_zip_read_info->filestream,
+              buf,read_now)!=read_now)
+        return UNZ_ERRNO;
+
+    return (int)read_now;
+}
+
+/*
+  Close the file in zip opened with unzOpenCurrentFile
+  Return UNZ_CRCERROR if all the file was read but the CRC is not good
+*/
+extern int ZEXPORT unzCloseCurrentFile (unzFile file)
+{
+    int err=UNZ_OK;
+
+    unz64_s* s;
+    file_in_zip64_read_info_s* pfile_in_zip_read_info;
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    pfile_in_zip_read_info=s->pfile_in_zip_read;
+
+    if (pfile_in_zip_read_info==NULL)
+        return UNZ_PARAMERROR;
+
+
+    if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) &&
+        (!pfile_in_zip_read_info->raw))
+    {
+        if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait)
+            err=UNZ_CRCERROR;
+    }
+
+
+    TRYFREE(pfile_in_zip_read_info->read_buffer);
+    pfile_in_zip_read_info->read_buffer = NULL;
+    if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED)
+        inflateEnd(&pfile_in_zip_read_info->stream);
+#ifdef HAVE_BZIP2
+    else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED)
+        BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream);
+#endif
+
+
+    pfile_in_zip_read_info->stream_initialised = 0;
+    TRYFREE(pfile_in_zip_read_info);
+
+    s->pfile_in_zip_read=NULL;
+
+    return err;
+}
+
+
+/*
+  Get the global comment string of the ZipFile, in the szComment buffer.
+  uSizeBuf is the size of the szComment buffer.
+  return the number of byte copied or an error code <0
+*/
+extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf)
+{
+    unz64_s* s;
+    uLong uReadThis ;
+    if (file==NULL)
+        return (int)UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+
+    uReadThis = uSizeBuf;
+    if (uReadThis>s->gi.size_comment)
+        uReadThis = s->gi.size_comment;
+
+    if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0)
+        return UNZ_ERRNO;
+
+    if (uReadThis>0)
+    {
+      *szComment='\0';
+      if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis)
+        return UNZ_ERRNO;
+    }
+
+    if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment))
+        *(szComment+s->gi.size_comment)='\0';
+    return (int)uReadThis;
+}
+
+/* Additions by RX '2004 */
+extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file)
+{
+    unz64_s* s;
+
+    if (file==NULL)
+          return 0; //UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+    if (!s->current_file_ok)
+      return 0;
+    if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff)
+      if (s->num_file==s->gi.number_entry)
+         return 0;
+    return s->pos_in_central_dir;
+}
+
+extern uLong ZEXPORT unzGetOffset (unzFile file)
+{
+    ZPOS64_T offset64;
+
+    if (file==NULL)
+          return 0; //UNZ_PARAMERROR;
+    offset64 = unzGetOffset64(file);
+    return (uLong)offset64;
+}
+
+extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos)
+{
+    unz64_s* s;
+    int err;
+
+    if (file==NULL)
+        return UNZ_PARAMERROR;
+    s=(unz64_s*)file;
+
+    s->pos_in_central_dir = pos;
+    s->num_file = s->gi.number_entry;      /* hack */
+    err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info,
+                                              &s->cur_file_info_internal,
+                                              NULL,0,NULL,0,NULL,0);
+    s->current_file_ok = (err == UNZ_OK);
+    return err;
+}
+
+extern int ZEXPORT unzSetOffset (unzFile file, uLong pos)
+{
+    return unzSetOffset64(file,pos);
+}

+ 437 - 0
library/third_party/minizip/unzip.h

@@ -0,0 +1,437 @@
+/* unzip.h -- IO for uncompress .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications of Unzip for Zip64
+         Copyright (C) 2007-2008 Even Rouault
+
+         Modifications for Zip64 support on both zip and unzip
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         ---------------------------------------------------------------------------------
+
+        Condition of use and distribution are the same than zlib :
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  ---------------------------------------------------------------------------------
+
+        Changes
+
+        See header of unzip64.c
+
+*/
+
+#ifndef _unz64_H
+#define _unz64_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _ZLIB_H
+#include "zlib.h"
+#endif
+
+#ifndef  _ZLIBIOAPI_H
+#include "ioapi.h"
+#endif
+
+#ifdef HAVE_BZIP2
+#include "bzlib.h"
+#endif
+
+#define Z_BZIP2ED 12
+
+#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+    from (void*) without cast */
+typedef struct TagunzFile__ { int unused; } unzFile__;
+typedef unzFile__ *unzFile;
+#else
+typedef voidp unzFile;
+#endif
+
+
+#define UNZ_OK                          (0)
+#define UNZ_END_OF_LIST_OF_FILE         (-100)
+#define UNZ_ERRNO                       (Z_ERRNO)
+#define UNZ_EOF                         (0)
+#define UNZ_PARAMERROR                  (-102)
+#define UNZ_BADZIPFILE                  (-103)
+#define UNZ_INTERNALERROR               (-104)
+#define UNZ_CRCERROR                    (-105)
+
+/* tm_unz contain date/time info */
+typedef struct tm_unz_s
+{
+    uInt tm_sec;            /* seconds after the minute - [0,59] */
+    uInt tm_min;            /* minutes after the hour - [0,59] */
+    uInt tm_hour;           /* hours since midnight - [0,23] */
+    uInt tm_mday;           /* day of the month - [1,31] */
+    uInt tm_mon;            /* months since January - [0,11] */
+    uInt tm_year;           /* years - [1980..2044] */
+} tm_unz;
+
+/* unz_global_info structure contain global data about the ZIPfile
+   These data comes from the end of central dir */
+typedef struct unz_global_info64_s
+{
+    ZPOS64_T number_entry;         /* total number of entries in
+                                     the central dir on this disk */
+    uLong size_comment;         /* size of the global comment of the zipfile */
+} unz_global_info64;
+
+typedef struct unz_global_info_s
+{
+    uLong number_entry;         /* total number of entries in
+                                     the central dir on this disk */
+    uLong size_comment;         /* size of the global comment of the zipfile */
+} unz_global_info;
+
+/* unz_file_info contain information about a file in the zipfile */
+typedef struct unz_file_info64_s
+{
+    uLong version;              /* version made by                 2 bytes */
+    uLong version_needed;       /* version needed to extract       2 bytes */
+    uLong flag;                 /* general purpose bit flag        2 bytes */
+    uLong compression_method;   /* compression method              2 bytes */
+    uLong dosDate;              /* last mod file date in Dos fmt   4 bytes */
+    uLong crc;                  /* crc-32                          4 bytes */
+    ZPOS64_T compressed_size;   /* compressed size                 8 bytes */
+    ZPOS64_T uncompressed_size; /* uncompressed size               8 bytes */
+    uLong size_filename;        /* filename length                 2 bytes */
+    uLong size_file_extra;      /* extra field length              2 bytes */
+    uLong size_file_comment;    /* file comment length             2 bytes */
+
+    uLong disk_num_start;       /* disk number start               2 bytes */
+    uLong internal_fa;          /* internal file attributes        2 bytes */
+    uLong external_fa;          /* external file attributes        4 bytes */
+
+    tm_unz tmu_date;
+} unz_file_info64;
+
+typedef struct unz_file_info_s
+{
+    uLong version;              /* version made by                 2 bytes */
+    uLong version_needed;       /* version needed to extract       2 bytes */
+    uLong flag;                 /* general purpose bit flag        2 bytes */
+    uLong compression_method;   /* compression method              2 bytes */
+    uLong dosDate;              /* last mod file date in Dos fmt   4 bytes */
+    uLong crc;                  /* crc-32                          4 bytes */
+    uLong compressed_size;      /* compressed size                 4 bytes */
+    uLong uncompressed_size;    /* uncompressed size               4 bytes */
+    uLong size_filename;        /* filename length                 2 bytes */
+    uLong size_file_extra;      /* extra field length              2 bytes */
+    uLong size_file_comment;    /* file comment length             2 bytes */
+
+    uLong disk_num_start;       /* disk number start               2 bytes */
+    uLong internal_fa;          /* internal file attributes        2 bytes */
+    uLong external_fa;          /* external file attributes        4 bytes */
+
+    tm_unz tmu_date;
+} unz_file_info;
+
+extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1,
+                                                 const char* fileName2,
+                                                 int iCaseSensitivity));
+/*
+   Compare two filename (fileName1,fileName2).
+   If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp)
+   If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi
+                                or strcasecmp)
+   If iCaseSenisivity = 0, case sensitivity is defaut of your operating system
+    (like 1 on Unix, 2 on Windows)
+*/
+
+
+extern unzFile ZEXPORT unzOpen OF((const char *path));
+extern unzFile ZEXPORT unzOpen64 OF((const void *path));
+/*
+  Open a Zip file. path contain the full pathname (by example,
+     on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer
+     "zlib/zlib113.zip".
+     If the zipfile cannot be opened (file don't exist or in not valid), the
+       return value is NULL.
+     Else, the return value is a unzFile Handle, usable with other function
+       of this unzip package.
+     the "64" function take a const void* pointer, because the path is just the
+       value passed to the open64_file_func callback.
+     Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path
+       is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char*
+       does not describe the reality
+*/
+
+
+extern unzFile ZEXPORT unzOpen2 OF((const char *path,
+                                    zlib_filefunc_def* pzlib_filefunc_def));
+/*
+   Open a Zip file, like unzOpen, but provide a set of file low level API
+      for read/write the zip file (see ioapi.h)
+*/
+
+extern unzFile ZEXPORT unzOpen2_64 OF((const void *path,
+                                    zlib_filefunc64_def* pzlib_filefunc_def));
+/*
+   Open a Zip file, like unz64Open, but provide a set of file low level API
+      for read/write the zip file (see ioapi.h)
+*/
+
+extern int ZEXPORT unzClose OF((unzFile file));
+/*
+  Close a ZipFile opened with unzOpen.
+  If there is files inside the .Zip opened with unzOpenCurrentFile (see later),
+    these files MUST be closed with unzCloseCurrentFile before call unzClose.
+  return UNZ_OK if there is no problem. */
+
+extern int ZEXPORT unzGetGlobalInfo OF((unzFile file,
+                                        unz_global_info *pglobal_info));
+
+extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file,
+                                        unz_global_info64 *pglobal_info));
+/*
+  Write info about the ZipFile in the *pglobal_info structure.
+  No preparation of the structure is needed
+  return UNZ_OK if there is no problem. */
+
+
+extern int ZEXPORT unzGetGlobalComment OF((unzFile file,
+                                           char *szComment,
+                                           uLong uSizeBuf));
+/*
+  Get the global comment string of the ZipFile, in the szComment buffer.
+  uSizeBuf is the size of the szComment buffer.
+  return the number of byte copied or an error code <0
+*/
+
+
+/***************************************************************************/
+/* Unzip package allow you browse the directory of the zipfile */
+
+extern int ZEXPORT unzGoToFirstFile OF((unzFile file));
+/*
+  Set the current file of the zipfile to the first file.
+  return UNZ_OK if there is no problem
+*/
+
+extern int ZEXPORT unzGoToNextFile OF((unzFile file));
+/*
+  Set the current file of the zipfile to the next file.
+  return UNZ_OK if there is no problem
+  return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest.
+*/
+
+extern int ZEXPORT unzLocateFile OF((unzFile file,
+                     const char *szFileName,
+                     int iCaseSensitivity));
+/*
+  Try locate the file szFileName in the zipfile.
+  For the iCaseSensitivity signification, see unzStringFileNameCompare
+
+  return value :
+  UNZ_OK if the file is found. It becomes the current file.
+  UNZ_END_OF_LIST_OF_FILE if the file is not found
+*/
+
+
+/* ****************************************** */
+/* Ryan supplied functions */
+/* unz_file_info contain information about a file in the zipfile */
+typedef struct unz_file_pos_s
+{
+    uLong pos_in_zip_directory;   /* offset in zip file directory */
+    uLong num_of_file;            /* # of file */
+} unz_file_pos;
+
+extern int ZEXPORT unzGetFilePos(
+    unzFile file,
+    unz_file_pos* file_pos);
+
+extern int ZEXPORT unzGoToFilePos(
+    unzFile file,
+    unz_file_pos* file_pos);
+
+typedef struct unz64_file_pos_s
+{
+    ZPOS64_T pos_in_zip_directory;   /* offset in zip file directory */
+    ZPOS64_T num_of_file;            /* # of file */
+} unz64_file_pos;
+
+extern int ZEXPORT unzGetFilePos64(
+    unzFile file,
+    unz64_file_pos* file_pos);
+
+extern int ZEXPORT unzGoToFilePos64(
+    unzFile file,
+    const unz64_file_pos* file_pos);
+
+/* ****************************************** */
+
+extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file,
+                         unz_file_info64 *pfile_info,
+                         char *szFileName,
+                         uLong fileNameBufferSize,
+                         void *extraField,
+                         uLong extraFieldBufferSize,
+                         char *szComment,
+                         uLong commentBufferSize));
+
+extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file,
+                         unz_file_info *pfile_info,
+                         char *szFileName,
+                         uLong fileNameBufferSize,
+                         void *extraField,
+                         uLong extraFieldBufferSize,
+                         char *szComment,
+                         uLong commentBufferSize));
+/*
+  Get Info about the current file
+  if pfile_info!=NULL, the *pfile_info structure will contain somes info about
+        the current file
+  if szFileName!=NULL, the filemane string will be copied in szFileName
+            (fileNameBufferSize is the size of the buffer)
+  if extraField!=NULL, the extra field information will be copied in extraField
+            (extraFieldBufferSize is the size of the buffer).
+            This is the Central-header version of the extra field
+  if szComment!=NULL, the comment string of the file will be copied in szComment
+            (commentBufferSize is the size of the buffer)
+*/
+
+
+/** Addition for GDAL : START */
+
+extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file));
+
+/** Addition for GDAL : END */
+
+
+/***************************************************************************/
+/* for reading the content of the current zipfile, you can open it, read data
+   from it, and close it (you can close it before reading all the file)
+   */
+
+extern int ZEXPORT unzOpenCurrentFile OF((unzFile file));
+/*
+  Open for reading data the current file in the zipfile.
+  If there is no error, the return value is UNZ_OK.
+*/
+
+extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file,
+                                                  const char* password));
+/*
+  Open for reading data the current file in the zipfile.
+  password is a crypting password
+  If there is no error, the return value is UNZ_OK.
+*/
+
+extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file,
+                                           int* method,
+                                           int* level,
+                                           int raw));
+/*
+  Same than unzOpenCurrentFile, but open for read raw the file (not uncompress)
+    if raw==1
+  *method will receive method of compression, *level will receive level of
+     compression
+  note : you can set level parameter as NULL (if you did not want known level,
+         but you CANNOT set method parameter as NULL
+*/
+
+extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file,
+                                           int* method,
+                                           int* level,
+                                           int raw,
+                                           const char* password));
+/*
+  Same than unzOpenCurrentFile, but open for read raw the file (not uncompress)
+    if raw==1
+  *method will receive method of compression, *level will receive level of
+     compression
+  note : you can set level parameter as NULL (if you did not want known level,
+         but you CANNOT set method parameter as NULL
+*/
+
+
+extern int ZEXPORT unzCloseCurrentFile OF((unzFile file));
+/*
+  Close the file in zip opened with unzOpenCurrentFile
+  Return UNZ_CRCERROR if all the file was read but the CRC is not good
+*/
+
+extern int ZEXPORT unzReadCurrentFile OF((unzFile file,
+                      voidp buf,
+                      unsigned len));
+/*
+  Read bytes from the current file (opened by unzOpenCurrentFile)
+  buf contain buffer where data must be copied
+  len the size of buf.
+
+  return the number of byte copied if somes bytes are copied
+  return 0 if the end of file was reached
+  return <0 with error code if there is an error
+    (UNZ_ERRNO for IO error, or zLib error for uncompress error)
+*/
+
+extern z_off_t ZEXPORT unztell OF((unzFile file));
+
+extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file));
+/*
+  Give the current position in uncompressed data
+*/
+
+extern int ZEXPORT unzeof OF((unzFile file));
+/*
+  return 1 if the end of file was reached, 0 elsewhere
+*/
+
+extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file,
+                                             voidp buf,
+                                             unsigned len));
+/*
+  Read extra field from the current file (opened by unzOpenCurrentFile)
+  This is the local-header version of the extra field (sometimes, there is
+    more info in the local-header version than in the central-header)
+
+  if buf==NULL, it return the size of the local extra field
+
+  if buf!=NULL, len is the size of the buffer, the extra header is copied in
+    buf.
+  the return value is the number of bytes copied in buf, or (if <0)
+    the error code
+*/
+
+/***************************************************************************/
+
+/* Get the current file offset */
+extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file);
+extern uLong ZEXPORT unzGetOffset (unzFile file);
+
+/* Set the current file offset */
+extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos);
+extern int ZEXPORT unzSetOffset (unzFile file, uLong pos);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _unz64_H */

+ 2007 - 0
library/third_party/minizip/zip.c

@@ -0,0 +1,2007 @@
+/* zip.c -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+   Oct-2009 - Mathias Svensson - Remove old C style function prototypes
+   Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives
+   Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions.
+   Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data
+                                 It is used when recreting zip archive with RAW when deleting items from a zip.
+                                 ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed.
+   Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required)
+   Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer
+
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "zlib.h"
+#include "zip.h"
+
+#ifdef STDC
+#  include <stddef.h>
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+#ifdef NO_ERRNO_H
+    extern int errno;
+#else
+#   include <errno.h>
+#endif
+
+
+#ifndef local
+#  define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+#ifndef VERSIONMADEBY
+# define VERSIONMADEBY   (0x0) /* platform depedent */
+#endif
+
+#ifndef Z_BUFSIZE
+#define Z_BUFSIZE (64*1024) //(16384)
+#endif
+
+#ifndef Z_MAXFILENAMEINZIP
+#define Z_MAXFILENAMEINZIP (256)
+#endif
+
+#ifndef ALLOC
+# define ALLOC(size) (malloc(size))
+#endif
+#ifndef TRYFREE
+# define TRYFREE(p) {if (p) free(p);}
+#endif
+
+/*
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZEZIPLOCALHEADER (0x1e)
+*/
+
+/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
+
+
+// NOT sure that this work on ALL platform
+#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32))
+
+#ifndef SEEK_CUR
+#define SEEK_CUR    1
+#endif
+
+#ifndef SEEK_END
+#define SEEK_END    2
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET    0
+#endif
+
+#ifndef DEF_MEM_LEVEL
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+#endif
+const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
+
+
+#define SIZEDATA_INDATABLOCK (4096-(4*4))
+
+#define LOCALHEADERMAGIC    (0x04034b50)
+#define CENTRALHEADERMAGIC  (0x02014b50)
+#define ENDHEADERMAGIC      (0x06054b50)
+#define ZIP64ENDHEADERMAGIC      (0x6064b50)
+#define ZIP64ENDLOCHEADERMAGIC   (0x7064b50)
+
+#define FLAG_LOCALHEADER_OFFSET (0x06)
+#define CRC_LOCALHEADER_OFFSET  (0x0e)
+
+#define SIZECENTRALHEADER (0x2e) /* 46 */
+
+typedef struct linkedlist_datablock_internal_s
+{
+  struct linkedlist_datablock_internal_s* next_datablock;
+  uLong  avail_in_this_block;
+  uLong  filled_in_this_block;
+  uLong  unused; /* for future use and alignement */
+  unsigned char data[SIZEDATA_INDATABLOCK];
+} linkedlist_datablock_internal;
+
+typedef struct linkedlist_data_s
+{
+    linkedlist_datablock_internal* first_block;
+    linkedlist_datablock_internal* last_block;
+} linkedlist_data;
+
+
+typedef struct
+{
+    z_stream stream;            /* zLib stream structure for inflate */
+#ifdef HAVE_BZIP2
+    bz_stream bstream;          /* bzLib stream structure for bziped */
+#endif
+
+    int  stream_initialised;    /* 1 is stream is initialised */
+    uInt pos_in_buffered_data;  /* last written byte in buffered_data */
+
+    ZPOS64_T pos_local_header;     /* offset of the local header of the file
+                                     currenty writing */
+    char* central_header;       /* central header data for the current file */
+    uLong size_centralExtra;
+    uLong size_centralheader;   /* size of the central header for cur file */
+    uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */
+    uLong flag;                 /* flag of the file currently writing */
+
+    int  method;                /* compression method of file currenty wr.*/
+    int  raw;                   /* 1 for directly writing raw data */
+    Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/
+    uLong dosDate;
+    uLong crc32;
+    int  encrypt;
+    int  zip64;               /* Add ZIP64 extened information in the extra field */
+    ZPOS64_T pos_zip64extrainfo;
+    ZPOS64_T totalCompressedData;
+    ZPOS64_T totalUncompressedData;
+#ifndef NOCRYPT
+    unsigned long keys[3];     /* keys defining the pseudo-random sequence */
+    const z_crc_t* pcrc_32_tab;
+    int crypt_header_size;
+#endif
+} curfile64_info;
+
+typedef struct
+{
+    zlib_filefunc64_32_def z_filefunc;
+    voidpf filestream;        /* io structore of the zipfile */
+    linkedlist_data central_dir;/* datablock with central dir in construction*/
+    int  in_opened_file_inzip;  /* 1 if a file in the zip is currently writ.*/
+    curfile64_info ci;            /* info on the file curretly writing */
+
+    ZPOS64_T begin_pos;            /* position of the beginning of the zipfile */
+    ZPOS64_T add_position_when_writting_offset;
+    ZPOS64_T number_entry;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    char *globalcomment;
+#endif
+
+} zip64_internal;
+
+
+#ifndef NOCRYPT
+#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+#include "crypt.h"
+#endif
+
+local linkedlist_datablock_internal* allocate_new_datablock()
+{
+    linkedlist_datablock_internal* ldi;
+    ldi = (linkedlist_datablock_internal*)
+                 ALLOC(sizeof(linkedlist_datablock_internal));
+    if (ldi!=NULL)
+    {
+        ldi->next_datablock = NULL ;
+        ldi->filled_in_this_block = 0 ;
+        ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ;
+    }
+    return ldi;
+}
+
+local void free_datablock(linkedlist_datablock_internal* ldi)
+{
+    while (ldi!=NULL)
+    {
+        linkedlist_datablock_internal* ldinext = ldi->next_datablock;
+        TRYFREE(ldi);
+        ldi = ldinext;
+    }
+}
+
+local void init_linkedlist(linkedlist_data* ll)
+{
+    ll->first_block = ll->last_block = NULL;
+}
+
+local void free_linkedlist(linkedlist_data* ll)
+{
+    free_datablock(ll->first_block);
+    ll->first_block = ll->last_block = NULL;
+}
+
+
+local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len)
+{
+    linkedlist_datablock_internal* ldi;
+    const unsigned char* from_copy;
+
+    if (ll==NULL)
+        return ZIP_INTERNALERROR;
+
+    if (ll->last_block == NULL)
+    {
+        ll->first_block = ll->last_block = allocate_new_datablock();
+        if (ll->first_block == NULL)
+            return ZIP_INTERNALERROR;
+    }
+
+    ldi = ll->last_block;
+    from_copy = (unsigned char*)buf;
+
+    while (len>0)
+    {
+        uInt copy_this;
+        uInt i;
+        unsigned char* to_copy;
+
+        if (ldi->avail_in_this_block==0)
+        {
+            ldi->next_datablock = allocate_new_datablock();
+            if (ldi->next_datablock == NULL)
+                return ZIP_INTERNALERROR;
+            ldi = ldi->next_datablock ;
+            ll->last_block = ldi;
+        }
+
+        if (ldi->avail_in_this_block < len)
+            copy_this = (uInt)ldi->avail_in_this_block;
+        else
+            copy_this = (uInt)len;
+
+        to_copy = &(ldi->data[ldi->filled_in_this_block]);
+
+        for (i=0;i<copy_this;i++)
+            *(to_copy+i)=*(from_copy+i);
+
+        ldi->filled_in_this_block += copy_this;
+        ldi->avail_in_this_block -= copy_this;
+        from_copy += copy_this ;
+        len -= copy_this;
+    }
+    return ZIP_OK;
+}
+
+
+
+/****************************************************************************/
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+/* ===========================================================================
+   Inputs a long in LSB order to the given file
+   nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T)
+*/
+
+local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte));
+local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)
+{
+    unsigned char buf[8];
+    int n;
+    for (n = 0; n < nbByte; n++)
+    {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+    if (x != 0)
+      {     /* data overflow - hack for ZIP64 (X Roche) */
+      for (n = 0; n < nbByte; n++)
+        {
+          buf[n] = 0xff;
+        }
+      }
+
+    if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte)
+        return ZIP_ERRNO;
+    else
+        return ZIP_OK;
+}
+
+local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte));
+local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte)
+{
+    unsigned char* buf=(unsigned char*)dest;
+    int n;
+    for (n = 0; n < nbByte; n++) {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+
+    if (x != 0)
+    {     /* data overflow - hack for ZIP64 */
+       for (n = 0; n < nbByte; n++)
+       {
+          buf[n] = 0xff;
+       }
+    }
+}
+
+/****************************************************************************/
+
+
+local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm)
+{
+    uLong year = (uLong)ptm->tm_year;
+    if (year>=1980)
+        year-=1980;
+    else if (year>=80)
+        year-=80;
+    return
+      (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) |
+        ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour));
+}
+
+
+/****************************************************************************/
+
+local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi));
+
+local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi)
+{
+    unsigned char c;
+    int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1);
+    if (err==1)
+    {
+        *pi = (int)c;
+        return ZIP_OK;
+    }
+    else
+    {
+        if (ZERROR64(*pzlib_filefunc_def,filestream))
+            return ZIP_ERRNO;
+        else
+            return ZIP_EOF;
+    }
+}
+
+
+/* ===========================================================================
+   Reads a long in LSB order from the given gz_stream. Sets
+*/
+local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<16;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<24;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX));
+
+
+local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)
+{
+  ZPOS64_T x;
+  int i = 0;
+  int err;
+
+  err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x = (ZPOS64_T)i;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<8;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<16;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<24;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<32;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<40;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<48;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<56;
+
+  if (err==ZIP_OK)
+    *pX = x;
+  else
+    *pX = 0;
+
+  return err;
+}
+
+#ifndef BUFREADCOMMENT
+#define BUFREADCOMMENT (0x400)
+#endif
+/*
+  Locate the Central directory of a zipfile (at the end, just before
+    the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos ;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+        ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+
+      if (uPosFound!=0)
+        break;
+  }
+  TRYFREE(buf);
+  return uPosFound;
+}
+
+/*
+Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before
+the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+  uLong uL;
+  ZPOS64_T relativeOffset;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+    {
+      // Signature "0x07064b50" Zip64 end of central directory locater
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+    }
+
+      if (uPosFound!=0)
+        break;
+  }
+
+  TRYFREE(buf);
+  if (uPosFound == 0)
+    return 0;
+
+  /* Zip64 end of central directory locator */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature, already checked */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  /* number of the disk with the start of the zip64 end of  central directory */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 0)
+    return 0;
+
+  /* relative offset of the zip64 end of central directory record */
+  if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK)
+    return 0;
+
+  /* total number of disks */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 1)
+    return 0;
+
+  /* Goto Zip64 end of central directory record */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  if (uL != 0x06064b50) // signature of 'Zip64 end of central directory'
+    return 0;
+
+  return relativeOffset;
+}
+
+int LoadCentralDirectoryRecord(zip64_internal* pziinit)
+{
+  int err=ZIP_OK;
+  ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
+
+  ZPOS64_T size_central_dir;     /* size of the central directory  */
+  ZPOS64_T offset_central_dir;   /* offset of start of central directory */
+  ZPOS64_T central_pos;
+  uLong uL;
+
+  uLong number_disk;          /* number of the current dist, used for
+                              spaning ZIP, unsupported, always 0*/
+  uLong number_disk_with_CD;  /* number the the disk with central dir, used
+                              for spaning ZIP, unsupported, always 0*/
+  ZPOS64_T number_entry;
+  ZPOS64_T number_entry_CD;      /* total number of entries in
+                                the central dir
+                                (same than number_entry on nospan) */
+  uLong VersionMadeBy;
+  uLong VersionNeeded;
+  uLong size_comment;
+
+  int hasZIP64Record = 0;
+
+  // check first if we find a ZIP64 record
+  central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream);
+  if(central_pos > 0)
+  {
+    hasZIP64Record = 1;
+  }
+  else if(central_pos == 0)
+  {
+    central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream);
+  }
+
+/* disable to allow appending to empty ZIP archive
+        if (central_pos==0)
+            err=ZIP_ERRNO;
+*/
+
+  if(hasZIP64Record)
+  {
+    ZPOS64_T sizeEndOfCentralDirectory;
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* size of zip64 end of central directory record */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version made by */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version needed to extract */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory on this disk */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* offset of start of central directory with respect to the
+    starting disk number */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    // TODO..
+    // read the comment from the standard central header.
+    size_comment = 0;
+  }
+  else
+  {
+    // Read End of central Directory info
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central dir on this disk */
+    number_entry = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry = uL;
+
+    /* total number of entries in the central dir */
+    number_entry_CD = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry_CD = uL;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    size_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      size_central_dir = uL;
+
+    /* offset of start of central directory with respect to the starting disk number */
+    offset_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      offset_central_dir = uL;
+
+
+    /* zipfile global comment length */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK)
+      err=ZIP_ERRNO;
+  }
+
+  if ((central_pos<offset_central_dir+size_central_dir) &&
+    (err==ZIP_OK))
+    err=ZIP_BADZIPFILE;
+
+  if (err!=ZIP_OK)
+  {
+    ZCLOSE64(pziinit->z_filefunc, pziinit->filestream);
+    return ZIP_ERRNO;
+  }
+
+  if (size_comment>0)
+  {
+    pziinit->globalcomment = (char*)ALLOC(size_comment+1);
+    if (pziinit->globalcomment)
+    {
+      size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment);
+      pziinit->globalcomment[size_comment]=0;
+    }
+  }
+
+  byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir);
+  pziinit->add_position_when_writting_offset = byte_before_the_zipfile;
+
+  {
+    ZPOS64_T size_central_dir_to_read = size_central_dir;
+    size_t buf_size = SIZEDATA_INDATABLOCK;
+    void* buf_read = (void*)ALLOC(buf_size);
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    while ((size_central_dir_to_read>0) && (err==ZIP_OK))
+    {
+      ZPOS64_T read_this = SIZEDATA_INDATABLOCK;
+      if (read_this > size_central_dir_to_read)
+        read_this = size_central_dir_to_read;
+
+      if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this)
+        err=ZIP_ERRNO;
+
+      if (err==ZIP_OK)
+        err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this);
+
+      size_central_dir_to_read-=read_this;
+    }
+    TRYFREE(buf_read);
+  }
+  pziinit->begin_pos = byte_before_the_zipfile;
+  pziinit->number_entry = number_entry_CD;
+
+  if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0)
+    err=ZIP_ERRNO;
+
+  return err;
+}
+
+
+#endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+
+/************************************************************/
+extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def)
+{
+    zip64_internal ziinit;
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    ziinit.z_filefunc.zseek32_file = NULL;
+    ziinit.z_filefunc.ztell32_file = NULL;
+    if (pzlib_filefunc64_32_def==NULL)
+        fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64);
+    else
+        ziinit.z_filefunc = *pzlib_filefunc64_32_def;
+
+    ziinit.filestream = ZOPEN64(ziinit.z_filefunc,
+                  pathname,
+                  (append == APPEND_STATUS_CREATE) ?
+                  (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) :
+                    (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING));
+
+    if (ziinit.filestream == NULL)
+        return NULL;
+
+    if (append == APPEND_STATUS_CREATEAFTER)
+        ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END);
+
+    ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream);
+    ziinit.in_opened_file_inzip = 0;
+    ziinit.ci.stream_initialised = 0;
+    ziinit.number_entry = 0;
+    ziinit.add_position_when_writting_offset = 0;
+    init_linkedlist(&(ziinit.central_dir));
+
+
+
+    zi = (zip64_internal*)ALLOC(sizeof(zip64_internal));
+    if (zi==NULL)
+    {
+        ZCLOSE64(ziinit.z_filefunc,ziinit.filestream);
+        return NULL;
+    }
+
+    /* now we add file in a zipfile */
+#    ifndef NO_ADDFILEINEXISTINGZIP
+    ziinit.globalcomment = NULL;
+    if (append == APPEND_STATUS_ADDINZIP)
+    {
+      // Read and Cache Central Directory Records
+      err = LoadCentralDirectoryRecord(&ziinit);
+    }
+
+    if (globalcomment)
+    {
+      *globalcomment = ziinit.globalcomment;
+    }
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+    if (err != ZIP_OK)
+    {
+#    ifndef NO_ADDFILEINEXISTINGZIP
+        TRYFREE(ziinit.globalcomment);
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+        TRYFREE(zi);
+        return NULL;
+    }
+    else
+    {
+        *zi = ziinit;
+        return (zipFile)zi;
+    }
+}
+
+extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def)
+{
+    if (pzlib_filefunc32_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def);
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    if (pzlib_filefunc_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
+        zlib_filefunc64_32_def_fill.ztell32_file = NULL;
+        zlib_filefunc64_32_def_fill.zseek32_file = NULL;
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+
+
+extern zipFile ZEXPORT zipOpen (const char* pathname, int append)
+{
+    return zipOpen3((const void*)pathname,append,NULL,NULL);
+}
+
+extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append)
+{
+    return zipOpen3(pathname,append,NULL,NULL);
+}
+
+int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local)
+{
+  /* write the local header */
+  int err;
+  uInt size_filename = (uInt)strlen(filename);
+  uInt size_extrafield = size_extrafield_local;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4);
+
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4);
+
+  // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */
+  }
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2);
+
+  if(zi->ci.zip64)
+  {
+    size_extrafield += 20;
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2);
+
+  if ((err==ZIP_OK) && (size_filename > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename)
+      err = ZIP_ERRNO;
+  }
+
+  if ((err==ZIP_OK) && (size_extrafield_local > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local)
+      err = ZIP_ERRNO;
+  }
+
+
+  if ((err==ZIP_OK) && (zi->ci.zip64))
+  {
+      // write the Zip64 extended info
+      short HeaderID = 1;
+      short DataSize = 16;
+      ZPOS64_T CompressedSize = 0;
+      ZPOS64_T UncompressedSize = 0;
+
+      // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file)
+      zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8);
+  }
+
+  return err;
+}
+
+/*
+ NOTE.
+ When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped
+ before calling this function it can be done with zipRemoveExtraInfoBlock
+
+ It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize
+ unnecessary allocations.
+ */
+extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase, int zip64)
+{
+    zip64_internal* zi;
+    uInt size_filename;
+    uInt size_comment;
+    uInt i;
+    int err = ZIP_OK;
+
+#    ifdef NOCRYPT
+    (void)(crcForCrypting);
+    if (password != NULL)
+        return ZIP_PARAMERROR;
+#    endif
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+#ifdef HAVE_BZIP2
+    if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED))
+      return ZIP_PARAMERROR;
+#else
+    if ((method!=0) && (method!=Z_DEFLATED))
+      return ZIP_PARAMERROR;
+#endif
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+        if (err != ZIP_OK)
+            return err;
+    }
+
+    if (filename==NULL)
+        filename="-";
+
+    if (comment==NULL)
+        size_comment = 0;
+    else
+        size_comment = (uInt)strlen(comment);
+
+    size_filename = (uInt)strlen(filename);
+
+    if (zipfi == NULL)
+        zi->ci.dosDate = 0;
+    else
+    {
+        if (zipfi->dosDate != 0)
+            zi->ci.dosDate = zipfi->dosDate;
+        else
+          zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date);
+    }
+
+    zi->ci.flag = flagBase;
+    if ((level==8) || (level==9))
+      zi->ci.flag |= 2;
+    if (level==2)
+      zi->ci.flag |= 4;
+    if (level==1)
+      zi->ci.flag |= 6;
+    if (password != NULL)
+      zi->ci.flag |= 1;
+
+    zi->ci.crc32 = 0;
+    zi->ci.method = method;
+    zi->ci.encrypt = 0;
+    zi->ci.stream_initialised = 0;
+    zi->ci.pos_in_buffered_data = 0;
+    zi->ci.raw = raw;
+    zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment;
+    zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data
+
+    zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree);
+
+    zi->ci.size_centralExtra = size_extrafield_global;
+    zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4);
+    /* version info */
+    zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4);
+    zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/
+    zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2);
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4);
+
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4);
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4);
+
+    for (i=0;i<size_filename;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i);
+
+    for (i=0;i<size_extrafield_global;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) =
+              *(((const char*)extrafield_global)+i);
+
+    for (i=0;i<size_comment;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+
+              size_extrafield_global+i) = *(comment+i);
+    if (zi->ci.central_header == NULL)
+        return ZIP_INTERNALERROR;
+
+    zi->ci.zip64 = zip64;
+    zi->ci.totalCompressedData = 0;
+    zi->ci.totalUncompressedData = 0;
+    zi->ci.pos_zip64extrainfo = 0;
+
+    err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local);
+
+#ifdef HAVE_BZIP2
+    zi->ci.bstream.avail_in = (uInt)0;
+    zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+    zi->ci.bstream.total_in_hi32 = 0;
+    zi->ci.bstream.total_in_lo32 = 0;
+    zi->ci.bstream.total_out_hi32 = 0;
+    zi->ci.bstream.total_out_lo32 = 0;
+#endif
+
+    zi->ci.stream.avail_in = (uInt)0;
+    zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.stream.next_out = zi->ci.buffered_data;
+    zi->ci.stream.total_in = 0;
+    zi->ci.stream.total_out = 0;
+    zi->ci.stream.data_type = Z_BINARY;
+
+#ifdef HAVE_BZIP2
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+#else
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+#endif
+    {
+        if(zi->ci.method == Z_DEFLATED)
+        {
+          zi->ci.stream.zalloc = (alloc_func)0;
+          zi->ci.stream.zfree = (free_func)0;
+          zi->ci.stream.opaque = (voidpf)0;
+
+          if (windowBits>0)
+              windowBits = -windowBits;
+
+          err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
+
+          if (err==Z_OK)
+              zi->ci.stream_initialised = Z_DEFLATED;
+        }
+        else if(zi->ci.method == Z_BZIP2ED)
+        {
+#ifdef HAVE_BZIP2
+            // Init BZip stuff here
+          zi->ci.bstream.bzalloc = 0;
+          zi->ci.bstream.bzfree = 0;
+          zi->ci.bstream.opaque = (voidpf)0;
+
+          err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35);
+          if(err == BZ_OK)
+            zi->ci.stream_initialised = Z_BZIP2ED;
+#endif
+        }
+
+    }
+
+#    ifndef NOCRYPT
+    zi->ci.crypt_header_size = 0;
+    if ((err==Z_OK) && (password != NULL))
+    {
+        unsigned char bufHead[RAND_HEAD_LEN];
+        unsigned int sizeHead;
+        zi->ci.encrypt = 1;
+        zi->ci.pcrc_32_tab = get_crc_table();
+        /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/
+
+        sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting);
+        zi->ci.crypt_header_size = sizeHead;
+
+        if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead)
+                err = ZIP_ERRNO;
+    }
+#    endif
+
+    if (err==Z_OK)
+        zi->in_opened_file_inzip = 1;
+    return err;
+}
+
+extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, versionMadeBy, flagBase, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+local int zip64FlushWriteBuffer(zip64_internal* zi)
+{
+    int err=ZIP_OK;
+
+    if (zi->ci.encrypt != 0)
+    {
+#ifndef NOCRYPT
+        uInt i;
+        int t;
+        for (i=0;i<zi->ci.pos_in_buffered_data;i++)
+            zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t);
+#endif
+    }
+
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data)
+      err = ZIP_ERRNO;
+
+    zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data;
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED)
+    {
+      zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32;
+      zi->ci.bstream.total_in_lo32 = 0;
+      zi->ci.bstream.total_in_hi32 = 0;
+    }
+    else
+#endif
+    {
+      zi->ci.totalUncompressedData += zi->ci.stream.total_in;
+      zi->ci.stream.total_in = 0;
+    }
+
+
+    zi->ci.pos_in_buffered_data = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len)
+{
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+
+    zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len);
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw))
+    {
+      zi->ci.bstream.next_in = (void*)buf;
+      zi->ci.bstream.avail_in = len;
+      err = BZ_RUN_OK;
+
+      while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0))
+      {
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+
+
+        if(err != BZ_RUN_OK)
+          break;
+
+        if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+        {
+          uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32;
+//          uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32;
+          err=BZ2_bzCompress(&zi->ci.bstream,  BZ_RUN);
+
+          zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ;
+        }
+      }
+
+      if(err == BZ_RUN_OK)
+        err = ZIP_OK;
+    }
+    else
+#endif
+    {
+      zi->ci.stream.next_in = (Bytef*)buf;
+      zi->ci.stream.avail_in = len;
+
+      while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0))
+      {
+          if (zi->ci.stream.avail_out == 0)
+          {
+              if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                  err = ZIP_ERRNO;
+              zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+              zi->ci.stream.next_out = zi->ci.buffered_data;
+          }
+
+
+          if(err != ZIP_OK)
+              break;
+
+          if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+          {
+              uLong uTotalOutBefore = zi->ci.stream.total_out;
+              err=deflate(&zi->ci.stream,  Z_NO_FLUSH);
+              if(uTotalOutBefore > zi->ci.stream.total_out)
+              {
+                int bBreak = 0;
+                bBreak++;
+              }
+
+              zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+          }
+          else
+          {
+              uInt copy_this,i;
+              if (zi->ci.stream.avail_in < zi->ci.stream.avail_out)
+                  copy_this = zi->ci.stream.avail_in;
+              else
+                  copy_this = zi->ci.stream.avail_out;
+
+              for (i = 0; i < copy_this; i++)
+                  *(((char*)zi->ci.stream.next_out)+i) =
+                      *(((const char*)zi->ci.stream.next_in)+i);
+              {
+                  zi->ci.stream.avail_in -= copy_this;
+                  zi->ci.stream.avail_out-= copy_this;
+                  zi->ci.stream.next_in+= copy_this;
+                  zi->ci.stream.next_out+= copy_this;
+                  zi->ci.stream.total_in+= copy_this;
+                  zi->ci.stream.total_out+= copy_this;
+                  zi->ci.pos_in_buffered_data += copy_this;
+              }
+          }
+      }// while(...)
+    }
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32)
+{
+    return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32);
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32)
+{
+    zip64_internal* zi;
+    ZPOS64_T compressed_size;
+    uLong invalidValue = 0xffffffff;
+    short datasize = 0;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+    zi->ci.stream.avail_in = 0;
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+                {
+                        while (err==ZIP_OK)
+                        {
+                                uLong uTotalOutBefore;
+                                if (zi->ci.stream.avail_out == 0)
+                                {
+                                        if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                                                err = ZIP_ERRNO;
+                                        zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+                                        zi->ci.stream.next_out = zi->ci.buffered_data;
+                                }
+                                uTotalOutBefore = zi->ci.stream.total_out;
+                                err=deflate(&zi->ci.stream,  Z_FINISH);
+                                zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+                        }
+                }
+    else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+#ifdef HAVE_BZIP2
+      err = BZ_FINISH_OK;
+      while (err==BZ_FINISH_OK)
+      {
+        uLong uTotalOutBefore;
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+        uTotalOutBefore = zi->ci.bstream.total_out_lo32;
+        err=BZ2_bzCompress(&zi->ci.bstream,  BZ_FINISH);
+        if(err == BZ_STREAM_END)
+          err = Z_STREAM_END;
+
+        zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore);
+      }
+
+      if(err == BZ_FINISH_OK)
+        err = ZIP_OK;
+#endif
+    }
+
+    if (err==Z_STREAM_END)
+        err=ZIP_OK; /* this is normal */
+
+    if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK))
+                {
+        if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO)
+            err = ZIP_ERRNO;
+                }
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+    {
+        int tmp_err = deflateEnd(&zi->ci.stream);
+        if (err == ZIP_OK)
+            err = tmp_err;
+        zi->ci.stream_initialised = 0;
+    }
+#ifdef HAVE_BZIP2
+    else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+      int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream);
+                        if (err==ZIP_OK)
+                                err = tmperr;
+                        zi->ci.stream_initialised = 0;
+    }
+#endif
+
+    if (!zi->ci.raw)
+    {
+        crc32 = (uLong)zi->ci.crc32;
+        uncompressed_size = zi->ci.totalUncompressedData;
+    }
+    compressed_size = zi->ci.totalCompressedData;
+
+#    ifndef NOCRYPT
+    compressed_size += zi->ci.crypt_header_size;
+#    endif
+
+    // update Current Item crc and sizes,
+    if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff)
+    {
+      /*version Made by*/
+      zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2);
+      /*version needed*/
+      zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2);
+
+    }
+
+    zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/
+
+
+    if(compressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/
+
+    /// set internal file attributes field
+    if (zi->ci.stream.data_type == Z_ASCII)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2);
+
+    if(uncompressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/
+
+    // Add ZIP64 extra info field for uncompressed size
+    if(uncompressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for compressed size
+    if(compressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for relative offset to local file header of current file
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      datasize += 8;
+
+    if(datasize > 0)
+    {
+      char* p = NULL;
+
+      if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree)
+      {
+        // we can not write more data to the buffer that we have room for.
+        return ZIP_BADZIPFILE;
+      }
+
+      p = zi->ci.central_header + zi->ci.size_centralheader;
+
+      // Add Extra Information Header for 'ZIP64 information'
+      zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID
+      p += 2;
+      zip64local_putValue_inmemory(p, datasize, 2); // DataSize
+      p += 2;
+
+      if(uncompressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, uncompressed_size, 8);
+        p += 8;
+      }
+
+      if(compressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, compressed_size, 8);
+        p += 8;
+      }
+
+      if(zi->ci.pos_local_header >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8);
+        p += 8;
+      }
+
+      // Update how much extra free space we got in the memory buffer
+      // and increase the centralheader size so the new ZIP64 fields are included
+      // ( 4 below is the size of HeaderID and DataSize field )
+      zi->ci.size_centralExtraFree -= datasize + 4;
+      zi->ci.size_centralheader += datasize + 4;
+
+      // Update the extra info size field
+      zi->ci.size_centralExtra += datasize + 4;
+      zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2);
+    }
+
+    if (err==ZIP_OK)
+        err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader);
+
+    free(zi->ci.central_header);
+
+    if (err==ZIP_OK)
+    {
+        // Update the LocalFileHeader with the new values.
+
+        ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+
+        if (err==ZIP_OK)
+            err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */
+
+        if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff )
+        {
+          if(zi->ci.pos_zip64extrainfo > 0)
+          {
+            // Update the size in the ZIP64 extended field.
+            if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0)
+              err = ZIP_ERRNO;
+
+            if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8);
+
+            if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8);
+          }
+          else
+              err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal
+        }
+        else
+        {
+          if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4);
+
+          if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4);
+        }
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+    }
+
+    zi->number_entry ++;
+    zi->in_opened_file_inzip = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZip (zipFile file)
+{
+    return zipCloseFileInZipRaw (file,0,0);
+}
+
+int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip)
+{
+  int err = ZIP_OK;
+  ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4);
+
+  /*num disks*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  /*relative offset*/
+    if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8);
+
+  /*total disks*/ /* Do not support spawning of disk so always say 1 here*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4);
+
+    return err;
+}
+
+int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  uLong Zip64DataSize = 44;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ?
+
+  if (err==ZIP_OK) /* version made by */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* version needed */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8);
+  }
+  return err;
+}
+int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  /*signature*/
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+  {
+    {
+      if(zi->number_entry >= 0xFFFF)
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+      else
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+    }
+  }
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+  {
+    if(zi->number_entry >= 0xFFFF)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+  }
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff)
+    {
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4);
+    }
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4);
+  }
+
+   return err;
+}
+
+int Write_GlobalComment(zip64_internal* zi, const char* global_comment)
+{
+  int err = ZIP_OK;
+  uInt size_global_comment = 0;
+
+  if(global_comment != NULL)
+    size_global_comment = (uInt)strlen(global_comment);
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2);
+
+  if (err == ZIP_OK && size_global_comment > 0)
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment)
+      err = ZIP_ERRNO;
+  }
+  return err;
+}
+
+extern int ZEXPORT zipClose (zipFile file, const char* global_comment)
+{
+    zip64_internal* zi;
+    int err = 0;
+    uLong size_centraldir = 0;
+    ZPOS64_T centraldir_pos_inzip;
+    ZPOS64_T pos;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+    }
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    if (global_comment==NULL)
+        global_comment = zi->globalcomment;
+#endif
+
+    centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    if (err==ZIP_OK)
+    {
+        linkedlist_datablock_internal* ldi = zi->central_dir.first_block;
+        while (ldi!=NULL)
+        {
+            if ((err==ZIP_OK) && (ldi->filled_in_this_block>0))
+            {
+                if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block)
+                    err = ZIP_ERRNO;
+            }
+
+            size_centraldir += ldi->filled_in_this_block;
+            ldi = ldi->next_datablock;
+        }
+    }
+    free_linkedlist(&(zi->central_dir));
+
+    pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff || zi->number_entry > 0xFFFF)
+    {
+      ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream);
+      Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+      Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos);
+    }
+
+    if (err==ZIP_OK)
+      err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+    if(err == ZIP_OK)
+      err = Write_GlobalComment(zi, global_comment);
+
+    if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0)
+        if (err == ZIP_OK)
+            err = ZIP_ERRNO;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    TRYFREE(zi->globalcomment);
+#endif
+    TRYFREE(zi);
+
+    return err;
+}
+
+extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader)
+{
+  char* p = pData;
+  int size = 0;
+  char* pNewHeader;
+  char* pTmp;
+  short header;
+  short dataSize;
+
+  int retVal = ZIP_OK;
+
+  if(pData == NULL || *dataLen < 4)
+    return ZIP_PARAMERROR;
+
+  pNewHeader = (char*)ALLOC(*dataLen);
+  pTmp = pNewHeader;
+
+  while(p < (pData + *dataLen))
+  {
+    header = *(short*)p;
+    dataSize = *(((short*)p)+1);
+
+    if( header == sHeader ) // Header found.
+    {
+      p += dataSize + 4; // skip it. do not copy to temp buffer
+    }
+    else
+    {
+      // Extra Info block should not be removed, So copy it to the temp buffer.
+      memcpy(pTmp, p, dataSize + 4);
+      p += dataSize + 4;
+      size += dataSize + 4;
+    }
+
+  }
+
+  if(size < *dataLen)
+  {
+    // clean old extra info block.
+    memset(pData,0, *dataLen);
+
+    // copy the new extra info block over the old
+    if(size > 0)
+      memcpy(pData, pNewHeader, size);
+
+    // set the new extra info size
+    *dataLen = size;
+
+    retVal = ZIP_OK;
+  }
+  else
+    retVal = ZIP_ERRNO;
+
+  TRYFREE(pNewHeader);
+
+  return retVal;
+}

+ 367 - 0
library/third_party/minizip/zip.h

@@ -0,0 +1,367 @@
+/* zip.h -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         ---------------------------------------------------------------------------
+
+   Condition of use and distribution are the same than zlib :
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+        ---------------------------------------------------------------------------
+
+        Changes
+
+        See header of zip.h
+
+*/
+
+/* Pragma added by libxlsxwriter project to avoid warnings with -pedantic -ansi. */
+#ifndef _WIN32
+#pragma GCC system_header
+#endif
+
+#ifndef _zip12_H
+#define _zip12_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* #define HAVE_BZIP2 */
+
+#ifndef _ZLIB_H
+#include "zlib.h"
+#endif
+
+#ifndef _ZLIBIOAPI_H
+#include "ioapi.h"
+#endif
+
+#ifdef HAVE_BZIP2
+#include "bzlib.h"
+#endif
+
+#define Z_BZIP2ED 12
+
+#if defined(STRICTZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+    from (void*) without cast */
+typedef struct TagzipFile__ { int unused; } zipFile__;
+typedef zipFile__ *zipFile;
+#else
+typedef voidp zipFile;
+#endif
+
+#define ZIP_OK                          (0)
+#define ZIP_EOF                         (0)
+#define ZIP_ERRNO                       (Z_ERRNO)
+#define ZIP_PARAMERROR                  (-102)
+#define ZIP_BADZIPFILE                  (-103)
+#define ZIP_INTERNALERROR               (-104)
+
+#ifndef DEF_MEM_LEVEL
+#  if MAX_MEM_LEVEL >= 8
+#    define DEF_MEM_LEVEL 8
+#  else
+#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#  endif
+#endif
+/* default memLevel */
+
+/* tm_zip contain date/time info */
+typedef struct tm_zip_s
+{
+    uInt tm_sec;            /* seconds after the minute - [0,59] */
+    uInt tm_min;            /* minutes after the hour - [0,59] */
+    uInt tm_hour;           /* hours since midnight - [0,23] */
+    uInt tm_mday;           /* day of the month - [1,31] */
+    uInt tm_mon;            /* months since January - [0,11] */
+    uInt tm_year;           /* years - [1980..2044] */
+} tm_zip;
+
+typedef struct
+{
+    tm_zip      tmz_date;       /* date in understandable format           */
+    uLong       dosDate;       /* if dos_date == 0, tmu_date is used      */
+/*    uLong       flag;        */   /* general purpose bit flag        2 bytes */
+
+    uLong       internal_fa;    /* internal file attributes        2 bytes */
+    uLong       external_fa;    /* external file attributes        4 bytes */
+} zip_fileinfo;
+
+typedef const char* zipcharpc;
+
+
+#define APPEND_STATUS_CREATE        (0)
+#define APPEND_STATUS_CREATEAFTER   (1)
+#define APPEND_STATUS_ADDINZIP      (2)
+
+extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append));
+extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append));
+/*
+  Create a zipfile.
+     pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on
+       an Unix computer "zlib/zlib113.zip".
+     if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip
+       will be created at the end of the file.
+         (useful if the file contain a self extractor code)
+     if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will
+       add files in existing zip (be sure you don't add file that doesn't exist)
+     If the zipfile cannot be opened, the return value is NULL.
+     Else, the return value is a zipFile Handle, usable with other function
+       of this zip package.
+*/
+
+/* Note : there is no delete function into a zipfile.
+   If you want delete file into a zipfile, you must open a zipfile, and create another
+   Of couse, you can use RAW reading and writing to copy the file you did not want delte
+*/
+
+extern zipFile ZEXPORT zipOpen2 OF((const char *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc_def* pzlib_filefunc_def));
+
+extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc64_def* pzlib_filefunc_def));
+
+extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level));
+
+extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level,
+                       int zip64));
+
+/*
+  Open a file in the ZIP for writing.
+  filename : the filename in zip (if NULL, '-' without quote will be used
+  *zipfi contain supplemental information
+  if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local
+    contains the extrafield data the the local header
+  if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global
+    contains the extrafield data the the local header
+  if comment != NULL, comment contain the comment string
+  method contain the compression method (0 for store, Z_DEFLATED for deflate)
+  level contain the level of compression (can be Z_DEFAULT_COMPRESSION)
+  zip64 is set to 1 if a zip64 extended information block should be added to the local file header.
+                    this MUST be '1' if the uncompressed size is >= 0xffffffff.
+
+*/
+
+
+extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw));
+
+
+extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int zip64));
+/*
+  Same than zipOpenNewFileInZip, except if raw=1, we write raw file
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting));
+
+extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            int zip64
+                                            ));
+
+/*
+  Same than zipOpenNewFileInZip2, except
+    windowBits,memLevel,,strategy : see parameter strategy in deflateInit2
+    password : crypting password (NULL for no crypting)
+    crcForCrypting : crc of file to compress (needed for crypting)
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase
+                                            ));
+
+
+extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase,
+                                            int zip64
+                                            ));
+/*
+  Same than zipOpenNewFileInZip4, except
+    versionMadeBy : value for Version made by field
+    flag : value for flag field (compression level info will be added)
+ */
+
+
+extern int ZEXPORT zipWriteInFileInZip OF((zipFile file,
+                       const void* buf,
+                       unsigned len));
+/*
+  Write data in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZip OF((zipFile file));
+/*
+  Close the current file in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file,
+                                            uLong uncompressed_size,
+                                            uLong crc32));
+
+extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file,
+                                            ZPOS64_T uncompressed_size,
+                                            uLong crc32));
+
+/*
+  Close the current file in the zipfile, for file opened with
+    parameter raw=1 in zipOpenNewFileInZip2
+  uncompressed_size and crc32 are value for the uncompressed size
+*/
+
+extern int ZEXPORT zipClose OF((zipFile file,
+                const char* global_comment));
+/*
+  Close the zipfile
+*/
+
+
+extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader));
+/*
+  zipRemoveExtraInfoBlock -  Added by Mathias Svensson
+
+  Remove extra information block from a extra information data for the local file header or central directory header
+
+  It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode.
+
+  0x0001 is the signature header for the ZIP64 extra information blocks
+
+  usage.
+                        Remove ZIP64 Extra information from a central director extra field data
+              zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001);
+
+                        Remove ZIP64 Extra information from a Local File Header extra field data
+        zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001);
+*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _zip64_H */

+ 342 - 0
library/third_party/tmpfileplus/tmpfileplus.c

@@ -0,0 +1,342 @@
+/* $Id: tmpfileplus.c $ */
+/*
+ * $Date: 2016-06-01 03:31Z $
+ * $Revision: 2.0.0 $
+ * $Author: dai $
+ */
+
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2012-16 David Ireland, DI Management Services Pty Ltd
+ * <http://www.di-mgt.com.au/contact/>.
+ */
+
+
+/*
+* NAME
+*        tmpfileplus - create a unique temporary file
+*
+* SYNOPSIS
+*        FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep)
+*
+* DESCRIPTION
+*        The tmpfileplus() function opens a unique temporary file in binary
+*        read/write (w+b) mode. The file is opened with the O_EXCL flag,
+*        guaranteeing that the caller is the only user. The filename will consist
+*        of the string given by `prefix` followed by 10 random characters. If
+*        `prefix` is NULL, then the string "tmp." will be used instead. The file
+*        will be created in an appropriate directory chosen by the first
+*        successful attempt in the following sequence:
+*
+*        a) The directory given by the `dir` argument (so the caller can specify
+*        a secure directory to take precedence).
+*
+*        b) The directory name in the environment variables:
+*
+*          (i)   "TMP" [Windows only]
+*          (ii)  "TEMP" [Windows only]
+*          (iii) "TMPDIR" [Unix only]
+*
+*        c) `P_tmpdir` as defined in <stdio.h> [Unix only] (in Windows, this is
+*        usually "\", which is no good).
+*
+*        d) The current working directory.
+*
+*        If a file cannot be created in any of the above directories, then the
+*        function fails and NULL is returned.
+*
+*        If the argument `pathname` is not a null pointer, then it will point to
+*        the full pathname of the file. The pathname is allocated using `malloc`
+*        and therefore should be freed by `free`.
+*
+*        If `keep` is nonzero and `pathname` is not a null pointer, then the file
+*        will be kept after it is closed. Otherwise the file will be
+*        automatically deleted when it is closed or the program terminates.
+*
+*
+* RETURN VALUE
+*        The tmpfileplus() function returns a pointer to the open file stream,
+*        or NULL if a unique file cannot be opened.
+*
+*
+* ERRORS
+*        ENOMEM Not enough memory to allocate filename.
+*
+*/
+
+/* ADDED IN v2.0 */
+
+/*
+* NAME
+*        tmpfileplus_f - create a unique temporary file with filename stored in a fixed-length buffer
+*
+* SYNOPSIS
+*        FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep);
+*
+* DESCRIPTION
+*        Same as tmpfileplus() except receives filename in a fixed-length buffer. No allocated memory to free.
+
+* ERRORS
+*        E2BIG Resulting filename is too big for the buffer `pathnamebuf`.
+
+*/
+
+#include "tmpfileplus.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+/* Non-ANSI include files that seem to work in both MSVC and Linux */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+/* MSVC nags to enforce ISO C++ conformant function names with leading "_",
+ * so we define our own function names to avoid whingeing compilers...
+ */
+#define OPEN_ _open
+#define FDOPEN_ _fdopen
+#else
+#define OPEN_ open
+#define FDOPEN_ fdopen
+#endif
+
+
+/* DEBUGGING STUFF */
+#if defined(_DEBUG) && defined(SHOW_DPRINTF)
+#define DPRINTF1(s, a1) printf(s, a1)
+#else
+#define DPRINTF1(s, a1)
+#endif
+
+
+#ifdef _WIN32
+#define FILE_SEPARATOR "\\"
+#else
+#define FILE_SEPARATOR "/"
+#endif
+
+#define RANDCHARS	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+#define NRANDCHARS	(sizeof(RANDCHARS) - 1)
+
+/** Replace each byte in string s with a random character from TEMPCHARS */
+static char *set_randpart(char *s)
+{
+	size_t i;
+	unsigned int r;
+	static unsigned int seed;	/* NB static */
+
+	if (seed == 0)
+	{	/* First time set our seed using current time and clock */
+		seed = ((unsigned)time(NULL)<<8) ^ (unsigned)clock();
+	}
+	srand(seed++);
+	for (i = 0; i < strlen(s); i++)
+	{
+		r = rand() % NRANDCHARS;
+		s[i] = (RANDCHARS)[r];
+	}
+	return s;
+}
+
+/** Return 1 if path is a valid directory otherwise 0 */
+static int is_valid_dir(const char *path)
+{
+	struct stat st;
+	if ((stat(path, &st) == 0) && (st.st_mode & S_IFDIR))
+		return 1;
+
+	return 0;
+}
+
+/** Call getenv and save a copy in buf */
+static char *getenv_save(const char *varname, char *buf, size_t bufsize)
+{
+	char *ptr = getenv(varname);
+	buf[0] = '\0';
+	if (ptr)
+	{
+		strncpy(buf, ptr, bufsize);
+		buf[bufsize-1] = '\0';
+		return buf;
+	}
+	return NULL;
+}
+
+/**
+ * Try and create a randomly-named file in directory `tmpdir`.
+ * If successful, allocate memory and set `tmpname_ptr` to full filepath, and return file pointer;
+ * otherwise return NULL.
+ * If `keep` is zero then create the file as temporary and it should not exist once closed.
+ */
+static FILE *mktempfile_internal(const char *tmpdir, const char *pfx, char **tmpname_ptr, int keep)
+/* PRE:
+ * pfx is not NULL and points to a valid null-terminated string
+ * tmpname_ptr is not NULL.
+ */
+{
+	FILE *fp;
+	int fd;
+	char randpart[] = "1234567890";
+	size_t lentempname;
+	int i;
+	char *tmpname = NULL;
+	int oflag, pmode;
+
+/* In Windows, we use the _O_TEMPORARY flag with `open` to ensure the file is deleted when closed.
+ * In Unix, we use the unlink function after opening the file. (This does not work in Windows,
+ * which does not allow an open file to be unlinked.)
+ */
+#ifdef _WIN32
+	/* MSVC flags */
+	oflag =  _O_BINARY|_O_CREAT|_O_EXCL|_O_RDWR;
+	if (!keep)
+		oflag |= _O_TEMPORARY;
+	pmode = _S_IREAD | _S_IWRITE;
+#else
+	/* Standard POSIX flags */
+	oflag = O_CREAT|O_EXCL|O_RDWR;
+	pmode = S_IRUSR|S_IWUSR;
+#endif
+
+	if (!tmpdir || !is_valid_dir(tmpdir)) {
+		errno = ENOENT;
+		return NULL;
+	}
+
+	lentempname = strlen(tmpdir) + strlen(FILE_SEPARATOR) + strlen(pfx) + strlen(randpart);
+	DPRINTF1("lentempname=%d\n", lentempname);
+	tmpname = malloc(lentempname + 1);
+	if (!tmpname)
+	{
+		errno = ENOMEM;
+		return NULL;
+	}
+	/* If we don't manage to create a file after 10 goes, there is something wrong... */
+	for (i = 0; i < 10; i++)
+	{
+		sprintf(tmpname, "%s%s%s%s", tmpdir, FILE_SEPARATOR, pfx, set_randpart(randpart));
+		DPRINTF1("[%s]\n", tmpname);
+		fd = OPEN_(tmpname, oflag, pmode);
+		if (fd != -1) break;
+	}
+	DPRINTF1("strlen(tmpname)=%d\n", strlen(tmpname));
+	if (fd != -1)
+	{	/* Success, so return user a proper ANSI C file pointer */
+		fp = FDOPEN_(fd, "w+b");
+		errno = 0;
+
+#ifndef _WIN32
+		/* [Unix only] And make sure the file will be deleted once closed */
+		if (!keep) unlink(tmpname);
+#endif
+
+	}
+	else
+	{	/* We failed */
+		fp = NULL;
+	}
+	if (!fp)
+	{
+		free(tmpname);
+		tmpname = NULL;
+	}
+
+	*tmpname_ptr = tmpname;
+	return fp;
+}
+
+/**********************/
+/* EXPORTED FUNCTIONS */
+/**********************/
+
+FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep)
+{
+	FILE *fp = NULL;
+	char *tmpname = NULL;
+	char *tmpdir = NULL;
+	const char *pfx = (prefix ? prefix : "tmp.");
+	char *tempdirs[12] = { 0 };
+#ifdef _WIN32
+	char env1[FILENAME_MAX+1] = { 0 };
+	char env2[FILENAME_MAX+1] = { 0 };
+#else
+	char env3[FILENAME_MAX+1] = { 0 };
+#endif
+	int ntempdirs = 0;
+	int i;
+
+	/* Set up a list of temp directories we will try in order */
+	i = 0;
+	tempdirs[i++] = (char *)dir;
+#ifdef _WIN32
+	tempdirs[i++] = getenv_save("TMP", env1, sizeof(env1));
+	tempdirs[i++] = getenv_save("TEMP", env2, sizeof(env2));
+#else
+	tempdirs[i++] = getenv_save("TMPDIR", env3, sizeof(env3));
+	tempdirs[i++] = P_tmpdir;
+#endif
+	tempdirs[i++] = ".";
+	ntempdirs = i;
+
+	errno = 0;
+
+	/* Work through list we set up before, and break once we are successful */
+	for (i = 0; i < ntempdirs; i++)
+	{
+		tmpdir = tempdirs[i];
+		DPRINTF1("Trying tmpdir=[%s]\n", tmpdir);
+		fp = mktempfile_internal(tmpdir, pfx, &tmpname, keep);
+		if (fp) break;
+	}
+	/* If we succeeded and the user passed a pointer, set it to the alloc'd pathname: the user must free this */
+	if (fp && pathname)
+		*pathname = tmpname;
+	else	/* Otherwise, free the alloc'd memory */
+		free(tmpname);
+
+	return fp;
+}
+
+/* Same as tmpfileplus() but with fixed length buffer for output filename and no memory allocation */
+FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep)
+{
+	char *tmpbuf = NULL;
+	FILE *fp;
+
+	/* If no buffer provided, do the normal way */
+	if (!pathnamebuf || (int)pathsize <= 0) {
+		return tmpfileplus(dir, prefix, NULL, keep);
+	}
+	/* Call with a temporary buffer */
+	fp = tmpfileplus(dir, prefix, &tmpbuf, keep);
+	if (fp && strlen(tmpbuf) > pathsize - 1) {
+		/* Succeeded but not enough room in output buffer, so clean up and return an error */
+		pathnamebuf[0] = 0;
+		fclose(fp);
+		if (keep) remove(tmpbuf);
+		free(tmpbuf);
+		errno = E2BIG;
+		return NULL;
+	}
+	/* Copy name into buffer */
+	strcpy(pathnamebuf, tmpbuf);
+	free(tmpbuf);
+
+	return fp;
+}
+
+

+ 53 - 0
library/third_party/tmpfileplus/tmpfileplus.h

@@ -0,0 +1,53 @@
+/* $Id: tmpfileplus.h $ */
+/*
+ * $Date: 2016-06-01 03:31Z $
+ * $Revision: 2.0.0 $
+ * $Author: dai $
+ */
+
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) 2012-16 David Ireland, DI Management Services Pty Ltd
+ * <http://www.di-mgt.com.au/contact/>.
+ */
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#ifndef TMPFILEPLUS_H_
+#define TMPFILEPLUS_H_
+
+#include <stdio.h>
+
+/** Create a unique temporary file.
+@param dir (optional) directory to create file. If NULL use default TMP directory.
+@param prefix (optional) prefix for file name. If NULL use "tmp.".
+@param pathname (optional) pointer to a buffer to receive the temp filename. 
+	Allocated using `malloc()`; user to free. Ignored if NULL.
+@param keep If `keep` is nonzero and `pathname` is not NULL, then keep the file after closing. 
+	Otherwise file is automatically deleted when closed.
+@return Pointer to stream opened in binary read/write (w+b) mode, or a null pointer on error.
+@exception ENOMEM Not enough memory to allocate filename.
+*/
+FILE *tmpfileplus(const char *dir, const char *prefix, char **pathname, int keep);
+
+
+/** Create a unique temporary file with filename stored in a fixed-length buffer.
+@param dir (optional) directory to create file. If NULL use default directory.
+@param prefix (optional) prefix for file name. If NULL use "tmp.".
+@param pathnamebuf (optional) buffer to receive full pathname of temporary file. Ignored if NULL.
+@param pathsize Size of buffer to receive filename and its terminating null character.
+@param keep If `keep` is nonzero and `pathname` is not NULL, then keep the file after closing.
+	Otherwise file is automatically deleted when closed.
+@return Pointer to stream opened in binary read/write (w+b) mode, or a null pointer on error.
+@exception E2BIG Resulting filename is too big for the buffer `pathnamebuf`.
+*/
+FILE *tmpfileplus_f(const char *dir, const char *prefix, char *pathnamebuf, size_t pathsize, int keep);
+
+#define TMPFILE_KEEP 1
+
+#endif /* end TMPFILEPLUS_H_ */