Import ismrmrd_1.3.3.orig.tar.gz
authorGhislain Antony Vaillant <ghisvail@gmail.com>
Sat, 26 Nov 2016 11:45:27 +0000 (11:45 +0000)
committerGhislain Antony Vaillant <ghisvail@gmail.com>
Sat, 26 Nov 2016 11:45:27 +0000 (11:45 +0000)
[dgit import orig ismrmrd_1.3.3.orig.tar.gz]

77 files changed:
.gitignore [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
_clang-format [new file with mode: 0644]
cmake/FindFFTW3.cmake [new file with mode: 0644]
cmake/FindPugiXML.cmake [new file with mode: 0644]
cmake/ISMRMRDConfig.cmake.in [new file with mode: 0644]
cmake/cpack_options.cmake.in [new file with mode: 0644]
cmake/ismrmrd_cpack.cmake [new file with mode: 0644]
doc/CMakeLists.txt [new file with mode: 0644]
doc/Doxyfile.in [new file with mode: 0644]
doc/Makefile.sphinx.in [new file with mode: 0644]
doc/WindowsISMRMRDInstallDependencies.ps1 [new file with mode: 0644]
doc/make.sphinx.bat.in [new file with mode: 0644]
doc/source/TODO.rst [new file with mode: 0644]
doc/source/conf.py [new file with mode: 0644]
doc/source/index.rst [new file with mode: 0644]
examples/c/CMakeLists.txt [new file with mode: 0644]
examples/c/README.md [new file with mode: 0644]
examples/c/main.c [new file with mode: 0644]
examples/matlab/simple_gridder.m [new file with mode: 0644]
examples/matlab/simple_spiral_recon.m [new file with mode: 0644]
examples/matlab/test_create_dataset.m [new file with mode: 0644]
examples/matlab/test_create_undersampled_dataset.m [new file with mode: 0644]
examples/matlab/test_recon_dataset.m [new file with mode: 0644]
include/ismrmrd/dataset.h [new file with mode: 0644]
include/ismrmrd/export.h [new file with mode: 0644]
include/ismrmrd/ismrmrd.h [new file with mode: 0644]
include/ismrmrd/meta.h [new file with mode: 0644]
include/ismrmrd/xml.h [new file with mode: 0644]
include/version.in [new file with mode: 0644]
libsrc/dataset.c [new file with mode: 0644]
libsrc/dataset.cpp [new file with mode: 0644]
libsrc/ismrmrd.c [new file with mode: 0644]
libsrc/ismrmrd.cpp [new file with mode: 0644]
libsrc/meta.cpp [new file with mode: 0644]
libsrc/pugiconfig.hpp [new file with mode: 0644]
libsrc/pugixml.cpp [new file with mode: 0644]
libsrc/pugixml.hpp [new file with mode: 0644]
libsrc/xml.cpp [new file with mode: 0644]
matlab/+ismrmrd/+util/AcquisitionHeaderFromBytes.m [new file with mode: 0644]
matlab/+ismrmrd/+util/AcquisitionHeaderToBytes.m [new file with mode: 0644]
matlab/+ismrmrd/+util/ImageHeaderFromBytes.m [new file with mode: 0644]
matlab/+ismrmrd/+util/ImageHeaderToBytes.m [new file with mode: 0644]
matlab/+ismrmrd/+util/hdf5_datatypes.m [new file with mode: 0644]
matlab/+ismrmrd/+util/isInt.m [new file with mode: 0644]
matlab/+ismrmrd/+xml/deserialize.m [new file with mode: 0644]
matlab/+ismrmrd/+xml/serialize.m [new file with mode: 0644]
matlab/+ismrmrd/+xml/validate.m [new file with mode: 0644]
matlab/+ismrmrd/Acquisition.m [new file with mode: 0644]
matlab/+ismrmrd/AcquisitionHeader.m [new file with mode: 0644]
matlab/+ismrmrd/Dataset.m [new file with mode: 0644]
matlab/+ismrmrd/Image.m [new file with mode: 0644]
matlab/+ismrmrd/ImageHeader.m [new file with mode: 0644]
schema/ismrmrd.xsd [new file with mode: 0644]
schema/ismrmrd_example.xml [new file with mode: 0644]
schema/ismrmrd_example_extended.xml [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/test_acquisitions.cpp [new file with mode: 0644]
tests/test_channels.cpp [new file with mode: 0644]
tests/test_flags.cpp [new file with mode: 0644]
tests/test_images.cpp [new file with mode: 0644]
tests/test_ismrmrd.h [new file with mode: 0644]
tests/test_main.cpp [new file with mode: 0644]
tests/test_ndarray.cpp [new file with mode: 0644]
tests/test_quaternions.cpp [new file with mode: 0644]
utilities/CMakeLists.txt [new file with mode: 0644]
utilities/generate_cartesian_shepp_logan.cpp [new file with mode: 0644]
utilities/ismrmrd_fftw.h [new file with mode: 0644]
utilities/ismrmrd_info.cpp [new file with mode: 0644]
utilities/ismrmrd_phantom.cpp [new file with mode: 0644]
utilities/ismrmrd_phantom.h [new file with mode: 0644]
utilities/ismrmrd_test_xml.cpp [new file with mode: 0644]
utilities/read_timing_test.cpp [new file with mode: 0644]
utilities/recon_cartesian_2d.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ea8261b
--- /dev/null
@@ -0,0 +1,8 @@
+*~
+*swp
+*.so
+build/
+.DS_Store
+
+# autogenerated
+cmake/cpack_options.cmake
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..c897037
--- /dev/null
@@ -0,0 +1,18 @@
+language: cpp
+
+compiler:
+    - gcc
+    - clang
+
+before_install:
+    - sudo apt-get update
+install:
+    - sudo apt-get install libboost-all-dev libhdf5-serial-dev h5utils doxygen
+
+before_script:
+    - mkdir build
+    - cd build
+    - cmake --version
+    - cmake ..
+
+script: make && make check
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..665e106
--- /dev/null
@@ -0,0 +1,285 @@
+cmake_minimum_required(VERSION 2.8)
+foreach(p
+    CMP0025 # CMake 3.0 Compiler id for Apple Clang is now ``AppleClang``.
+    CMP0042 # CMake 3.0 ``MACOSX_RPATH`` is enabled by default.
+    CMP0046 # CMake 3.0 Error on non-existent dependency in add_dependencies.
+    CMP0054 # CMake 3.1 Only interpret ``if()`` arguments as variables or keywords when unquoted.
+    CMP0056 # CMake 3.2 Honor link flags in ``try_compile()`` source-file signature.
+    CMP0058 # CMake 3.3 Ninja requires custom command byproducts to be explicit.
+    )
+  if(POLICY ${p})
+    cmake_policy(SET ${p} NEW)
+  endif()
+endforeach()
+project(ISMRMRD)
+
+# set project specific cmake module path
+set (ISMRMRD_CMAKE_DIR ${PROJECT_SOURCE_DIR}/cmake CACHE PATH
+  "Location of CMake scripts")
+
+# command line options
+option(USE_SYSTEM_PUGIXML "Use pugixml installed on the system" OFF)
+
+# and include it to the search list
+list(APPEND CMAKE_MODULE_PATH ${ISMRMRD_CMAKE_DIR})
+
+# whether to install dependencies
+if (WIN32)
+    option(ISMRMRD_INSTALL_DEPENDENCIES "Install ismrmrd dependencies in windows" Off)
+endif ()
+
+# set the build type to Release if not specified
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Release CACHE STRING
+      "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
+      FORCE)
+endif()
+
+# compiler flags
+if (WIN32)
+    add_definitions(-DWIN32 -D_WIN32 -D_WINDOWS)
+    add_definitions(-DUNICODE -D_UNICODE)
+    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3")
+    set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/debug /INCREMENTAL:NO")
+    set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/debug /INCREMENTAL:NO")
+    set(CMAKE_STATIC_LINKER_FLAGS_DEBUG "/debug /INCREMENTAL:NO")
+    set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "/debug /INCREMENTAL:NO")
+    add_definitions(-D__func__=__FUNCTION__)
+else ()
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+endif ()
+
+#  ---   VERSIONING  (begin) ----
+#The ISMRMRD convention is to use version numbers with the format:
+#   XX.YY.ZZ (major, minor, patch)
+#
+#The major number increments when the binary compatibility of
+#the fixed memory layout struts (e.g. AcquisitionHeader) is broken.
+#The minor number changes when there are changes to the XML schema for
+#the flexible header. The micro number changes when there are small changes
+#in the utility libraries, that don't affect the data format itself.
+# For more information see http://semver.org/
+set(ISMRMRD_VERSION_MAJOR 1)
+set(ISMRMRD_VERSION_MINOR 3)
+set(ISMRMRD_VERSION_PATCH 3)
+set(ISMRMRD_VERSION_STRING ${ISMRMRD_VERSION_MAJOR}.${ISMRMRD_VERSION_MINOR}.${ISMRMRD_VERSION_PATCH})
+set(ISMRMRD_SOVERSION ${ISMRMRD_VERSION_MAJOR}.${ISMRMRD_VERSION_MINOR})
+
+set(ISMRMRD_XML_SCHEMA_SHA1 "9b899c6ad806bc2388c70461d6e5affe5cc6d750")
+
+#Remove line breaks and white space that does not change the meaning of the schema
+file(STRINGS ${CMAKE_SOURCE_DIR}/schema/ismrmrd.xsd SCHEMA_STRINGS) #Read all strings from file
+string(REPLACE ";" "" SCHEMA_NO_BREAKS  ${SCHEMA_STRINGS}) #Concatenate the list of strings
+string(REGEX REPLACE ">[ \t]+<" "><" SCHEMA_NO_SPACE ${SCHEMA_NO_BREAKS}) #Remove spaces and tabs
+string(STRIP ${SCHEMA_NO_SPACE} SCHEMA_NO_SPACE) #Strip any leading/trailing whitespace
+set(ISMRMRD_SCHEMA_DIR ${CMAKE_CURRENT_BINARY_DIR})
+file(WRITE ${ISMRMRD_SCHEMA_DIR}/ismrmrd_no_white_space.xsd ${SCHEMA_NO_SPACE}) #Write to file
+
+#Now hash the cleaned up file
+file(SHA1 ${CMAKE_CURRENT_BINARY_DIR}/ismrmrd_no_white_space.xsd ISMRMRD_CURRENT_XML_SCHEMA_SHA1)
+
+#Compare to last known hash
+if (NOT (${ISMRMRD_XML_SCHEMA_SHA1} STREQUAL ${ISMRMRD_CURRENT_XML_SCHEMA_SHA1}))
+  message("")
+  message("-----------------------------------------------")
+  message("            !!VERSION ERROR!!                  ")
+  message("                                               ")
+  message(" Expected SHA1 hash:                           ")
+  message("    ${ISMRMRD_XML_SCHEMA_SHA1}")
+  message(" Actual SHA1 hash:                             ")
+  message("    ${ISMRMRD_CURRENT_XML_SCHEMA_SHA1}")
+  message("                                               ")
+  message(" The XML Schema (ismrmrmd.xsd) has changed and ")
+  message(" the MINOR version number should be increased  ")
+  message(" and the SHA1 has should be updated in the     ")
+  message(" CMakelists.txt file.                          ")
+  message("                                               ")
+  message(" If you don't know what this message means, you")
+  message(" probably shouldn't be changing anything       ")
+  message("-----------------------------------------------")
+  message("")
+  message(FATAL_ERROR "     FATAL XML VERSION ERROR")
+endif()
+
+
+# Find HDF5 for dataset support
+find_package(HDF5 1.8 COMPONENTS C)
+
+if (HDF5_FOUND)
+    set (ISMRMRD_DATASET_SUPPORT true)
+    set (ISMRMRD_DATASET_SOURCES libsrc/dataset.c libsrc/dataset.cpp)
+    set (ISMRMRD_DATASET_INCLUDE_DIR ${HDF5_C_INCLUDE_DIR})
+    set (ISMRMRD_DATASET_LIBRARIES ${HDF5_LIBRARIES})
+else ()
+    set (ISMRMRD_DATASET_SUPPORT false)
+    message (WARNING "HDF5 not found. Dataset and file support unavailable!")
+endif ()
+
+# Generate the version.h header file
+find_package(Git)
+if (GIT_FOUND)
+    execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY
+            ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE ISMRMRD_GIT_SHA1 ERROR_VARIABLE ISMRMRD_GIT_STDERR)
+    string(STRIP "${ISMRMRD_GIT_SHA1}" ISMRMRD_GIT_SHA1)
+    string(LENGTH "${ISMRMRD_GIT_SHA1}" ISMRMRD_GIT_SHA1_LEN)
+    if (${ISMRMRD_GIT_SHA1_LEN} LESS 40)
+        message(WARNING "Could not determine SHA-1 hash: ${ISMRMRD_GIT_STDERR}")
+        set(ISMRMRD_GIT_SHA1 "NA")
+    endif ()
+else()
+  set(ISMRMRD_GIT_SHA1 "NA")
+endif()
+configure_file(include/version.in ${CMAKE_BINARY_DIR}/include/ismrmrd/version.h)
+install(FILES ${CMAKE_BINARY_DIR}/include/ismrmrd/version.h DESTINATION include/ismrmrd COMPONENT Devel)
+# note: for the utilities in this project that need ismrmrd/version.h
+# remember to add ${CMAKE_BINARY_DIR}/include to the include path
+
+
+#  ---   VERSIONING  (end) ----
+
+#  ---   Main Library  (begin) ----
+# in windows, install the HDF5 dependencies
+if (HDF5_FOUND AND WIN32 AND ISMRMRD_INSTALL_DEPENDENCIES)
+    if(DEFINED ENV{HDF5_ROOT})
+        set(HDF5_BIN_DIR $ENV{HDF5_ROOT}/bin)
+    else ()
+        set(HDF5_BIN_DIR ${HDF5_C_INCLUDE_DIR}/../bin)
+    endif ()
+    message("Install hdf5 libraries from ${HDF5_BIN_DIR} ")
+    install( DIRECTORY ${HDF5_BIN_DIR} DESTINATION bin/.. FILES_MATCHING PATTERN "*.dll" )
+endif ()
+
+# include directories for main library
+set(ISMRMRD_TARGET_INCLUDE_DIRS
+  ${CMAKE_CURRENT_LIST_DIR}/include
+  ${CMAKE_BINARY_DIR}/include
+  ${ISMRMRD_DATASET_INCLUDE_DIR}
+)
+
+set(ISMRMRD_TARGET_SOURCES
+  libsrc/ismrmrd.c
+  libsrc/ismrmrd.cpp
+  libsrc/xml.cpp
+  libsrc/meta.cpp
+  ${ISMRMRD_DATASET_SOURCES}
+)
+
+set(ISMRMRD_TARGET_LINK_LIBS ${ISMRMRD_DATASET_LIBRARIES})
+
+# optional handling of system-installed pugixml
+if(USE_SYSTEM_PUGIXML)
+  find_package(PugiXML)
+  if(PugiXML_FOUND)
+    message("Found system pugixml: ${PugiXML_INCLUDE_DIR} ${PugiXML_LIBRARY}")
+    list(APPEND ISMRMRD_TARGET_INCLUDE_DIRS ${PugiXML_INCLUDE_DIR})
+    list(APPEND ISMRMRD_TARGET_LINK_LIBS ${PugiXML_LIBRARY})
+  else()
+    message(FATAL_ERROR "Pugixml library not found on the system, try without "
+    "setting USE_SYSTEM_PUGIXML to use the version provided in the source "
+    "tree.")
+  endif()
+  list(APPEND ISMRMRD_TARGET_INCLUDE_DIRS ${PugiXML_INCLUDE_DIR})
+  list(APPEND ISMRMRD_TARGET_LINK_LIBS ${PugiXML_LIBRARY})
+else()
+  list(APPEND ISMRMRD_TARGET_SOURCES libsrc/pugixml.cpp)
+endif()
+
+# main library
+include_directories(${ISMRMRD_TARGET_INCLUDE_DIRS})
+add_library(ismrmrd SHARED ${ISMRMRD_TARGET_SOURCES})
+set_target_properties(ismrmrd PROPERTIES
+  VERSION ${ISMRMRD_VERSION_STRING}
+  SOVERSION ${ISMRMRD_SOVERSION}
+)
+target_link_libraries(ismrmrd ${ISMRMRD_TARGET_LINK_LIBS})
+list(APPEND ISMRMRD_LIBRARIES ismrmrd) # Add to list of libraries to be found
+list(APPEND ISMRMRD_LIBRARY_DIRS ${CMAKE_BINARY_DIR} ) # Add to list of directories to find libaries
+
+# install the main library
+install(TARGETS ismrmrd EXPORT ISMRMRDTargets
+   LIBRARY DESTINATION lib
+   ARCHIVE DESTINATION lib
+   RUNTIME DESTINATION bin
+   COMPONENT Devel
+)
+
+# install the headers
+install(DIRECTORY include/ismrmrd  DESTINATION include COMPONENT Devel)
+
+# install the schema file
+install(FILES schema/ismrmrd.xsd DESTINATION share/ismrmrd/schema COMPONENT Devel)
+
+# install the cmake modules
+install(FILES cmake/FindFFTW3.cmake DESTINATION share/ismrmrd/cmake COMPONENT Devel)
+
+#  ---   Main Library  (end) ----
+
+# process subdirectories
+add_subdirectory(doc)
+
+add_subdirectory(utilities)
+if (HDF5_FOUND)
+    add_subdirectory(examples/c)
+endif ()
+
+# TODO: make this work on Windows
+if (NOT WIN32)
+    add_subdirectory(tests)
+endif ()
+
+# install the matlab api
+install(DIRECTORY matlab DESTINATION share/ismrmrd )
+
+#--- Create cmake package for downstream projects
+#
+##- include(CMakePackageConfigHelpers)
+##- write_basic_package_version_file(
+##-   "${CMAKE_CURRENT_BINARY_DIR}/ISMRMRDConfigVersion.cmake"
+##-   VERSION ${ISMRMRD_VERSION_STRING}
+##-   COMPATIBILITY AnyNewerVersion
+##- )
+
+##- export(EXPORT ISMRMRDTargets
+##-  FILE "${CMAKE_CURRENT_BINARY_DIR}/ISMRMRDTargets.cmake"
+##-  NAMESPACE ISMRMRD
+##-)
+
+set(CONFIG_ISMRMRD_SCHEMA_DIR   ${ISMRMRD_SCHEMA_DIR})
+set(CONFIG_ISMRMRD_TARGET_INCLUDE_DIRS ${ISMRMRD_TARGET_INCLUDE_DIRS})
+set(CONFIG_ISMRMRD_LIBRARY_DIRS ${ISMRMRD_LIBRARY_DIRS})
+configure_file(cmake/ISMRMRDConfig.cmake.in
+  "${CMAKE_CURRENT_BINARY_DIR}/ISMRMRDConfig.cmake"
+  @ONLY
+)
+
+set(CONFIG_ISMRMRD_SCHEMA_DIR   ${CMAKE_INSTALL_PREFIX}/share/ismrmrd/schema)
+set(CONFIG_ISMRMRD_TARGET_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include)
+set(CONFIG_ISMRMRD_LIBRARY_DIRS ${CMAKE_INSTALL_PREFIX}/lib)
+configure_file(cmake/ISMRMRDConfig.cmake.in
+  "${CMAKE_CURRENT_BINARY_DIR}/InstallFiles/ISMRMRDConfig.cmake"
+  @ONLY
+)
+
+set(ConfigPackageLocation lib/cmake/ISMRMRD)
+install(
+  FILES
+    "${CMAKE_CURRENT_BINARY_DIR}/InstallFiles/ISMRMRDConfig.cmake"
+#--    "${CMAKE_CURRENT_BINARY_DIR}/ISMRMRDConfigVersion.cmake"
+  DESTINATION
+    ${ConfigPackageLocation}
+  COMPONENT
+    Devel
+)
+
+# Create package
+string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER)
+include(${ISMRMRD_CMAKE_DIR}/ismrmrd_cpack.cmake)
+if(CPACK_GENERATOR)
+  message(STATUS "Found CPack generators: ${CPACK_GENERATOR}")
+  configure_file("${ISMRMRD_CMAKE_DIR}/cpack_options.cmake.in" ${ISMRMRD_CPACK_CFG_FILE} @ONLY)
+  set(CPACK_PROJECT_CONFIG_FILE ${ISMRMRD_CPACK_CFG_FILE})
+  include(CPack)
+endif()
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..c6fe879
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+ISMRMRD SOFTWARE LICENSE JULY 2013
+
+PERMISSION IS HEREBY GRANTED, FREE OF CHARGE, TO ANY PERSON OBTAINING
+A COPY OF THIS SOFTWARE AND ASSOCIATED DOCUMENTATION FILES (THE
+"SOFTWARE"), TO DEAL IN THE SOFTWARE WITHOUT RESTRICTION, INCLUDING
+WITHOUT LIMITATION THE RIGHTS TO USE, COPY, MODIFY, MERGE, PUBLISH,
+DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THE SOFTWARE, AND TO
+PERMIT PERSONS TO WHOM THE SOFTWARE IS FURNISHED TO DO SO, SUBJECT TO
+THE FOLLOWING CONDITIONS:
+
+THE ABOVE COPYRIGHT NOTICE, THIS PERMISSION NOTICE, AND THE LIMITATION
+OF LIABILITY BELOW SHALL BE INCLUDED IN ALL COPIES OR REDISTRIBUTIONS
+OF SUBSTANTIAL PORTIONS OF THE SOFTWARE.
+
+SOFTWARE IS BEING DEVELOPED IN PART AT THE NATIONAL HEART, LUNG, AND BLOOD
+INSTITUTE, NATIONAL INSTITUTES OF HEALTH BY AN EMPLOYEE OF THE FEDERAL
+GOVERNMENT IN THE COURSE OF HIS OFFICIAL DUTIES. PURSUANT TO TITLE 17, 
+SECTION 105 OF THE UNITED STATES CODE, THIS SOFTWARE IS NOT SUBJECT TO 
+COPYRIGHT PROTECTION AND IS IN THE PUBLIC DOMAIN. EXCEPT AS CONTAINED IN
+THIS NOTICE, THE NAME OF THE AUTHORS, THE NATIONAL HEART, LUNG, AND BLOOD
+INSTITUTE (NHLBI), OR THE NATIONAL INSTITUTES OF HEALTH (NIH) MAY NOT 
+BE USED TO ENDORSE OR PROMOTE PRODUCTS DERIVED FROM THIS SOFTWARE WITHOUT 
+SPECIFIC PRIOR WRITTEN PERMISSION FROM THE NHLBI OR THE NIH.THE SOFTWARE IS 
+PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..cfa6fbc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+[![Build Status](https://travis-ci.org/ismrmrd/ismrmrd.svg?branch=master)](https://travis-ci.org/ismrmrd/ismrmrd)
+
+ISMRM Raw Data Format (ISMRMRD)
+===============================
+
+A prerequisite for sharing magnetic resonance (imaging) reconstruction algorithms and code is a common raw data format. This document describes such a common raw data format and attempts to capture the data fields that are require to describe enough details about the magnetic resonance experiment to reconstruct images from the data. This standard was developed by a subcommittee of the ISMRM Sedona 2013 workshop. Please see the [ISMRMRD Documentation](https://ismrmrd.github.io) for more information.
+
+Obtaining and Installing
+-------------------------
+
+To download the source code, clone the git archive:
+
+    git clone https://github.com/ismrmrd/ismrmrd
+
+API Documentation can be found at https://ismrmrd.github.io/api/.
+
+You will need CMake, HDF5, and optionally Boost and FFTW to build the C/C++ code. To generate the API documentation you will need Doxygen. Please see the ISMRMRD documentation for specific installation instructions for [Linux](https://ismrmrd.github.io/index.html#linux-installation), [Mac OS X](https://ismrmrd.github.io/index.html#mac-osx-installation), and [Windows](https://ismrmrd.github.io/index.html#windows-installation).
+
+Overview
+---------
+
+The raw data format combines a mix of flexible data structures (XML header) and fixed structures (equivalent to C-structs).
+
+A raw data set consist mainly of 2 sections:
+
+1.  A flexible XML format document that can contain an arbitrary number of fields and accommodate everything from simple values (b-values, etc.) to entire vendor protocols, etc. The purpose of this XML document is to provide parameters that may be meaningful for some experiments but not for others.  This XML format is defined by an XML Schema Definition file (ismrmrd.xsd).  Please see the [example header](https://github.com/ismrmrd/ismrmrd/blob/master/schema/ismrmrd_example_extended.xml), [schema](https://github.com/ismrmrd/ismrmrd/blob/master/schema/ismrmrd.xsd), and the [documentation](https://ismrmrd.github.io/index.html#flexible-data-header) for more information.
+
+1.  Raw data section. This section contains all the acquired data in the experiment. Each data item is preceded by a C-struct with encoding numbers, etc. Following this data header is a channel header and data for each acquired channel. The raw data headers are defined in a C/C++ header file (ismrmrd.h).  Please see the [C header](https://github.com/ismrmrd/ismrmrd/blob/master/include/ismrmrd/ismrmrd.h) and the [documentation](https://ismrmrd.github.io/index.html#fixed-data-structures) for more information.
+
+In addition to these sections, the ISMRMRD format also specifies an image header for storing reconstructed images and the accompanying C/C++ library provides a convenient way of writing such images into HDF5 files along with generic arrays for storing less well defined data structures, e.g. coil sensitivity maps or other calibration data.
+
+Other Resources
+---------------
+
+- [Python implementation](https://www.github.com/ismrmrd/ismrmrd-python)
+
+External Use of ISMRMRD in other source packages
+------------------------------------------------
+Build and install ISMRMRD by setting 
+
+```
+   cmake -DCMAKE_INSTALL_PREFIX=<your install directory>
+```
+
+To use ISMRMRD for your externally developed projects, add the following to your CMakeLists.txt file:
+
+```
+  find_package( ISMRMRD REQUIRED )
+  link_directories( ${ISMRMRD_LIBRARY_DIRS} )
+  include_directories( ${ISMRMRD_INCLUDE_DIRS} )
+  target_link_libraries( mytarget ${ISMRMRD_LIBRARIES} )
+```
+
+then when configuring your package use set the following cmake variables (command line variant shown):
+
+```
+  cmake -DISMRMRD_DIR:PATH=<path to build/install tree of ISMRMRD> <path to my source tree>
+```
diff --git a/_clang-format b/_clang-format
new file mode 100644 (file)
index 0000000..384dfe2
--- /dev/null
@@ -0,0 +1,48 @@
+---
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -4
+ConstructorInitializerIndentWidth: 4
+AlignEscapedNewlinesLeft: false
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakTemplateDeclarations: false
+AlwaysBreakBeforeMultilineStrings: false
+BreakBeforeBinaryOperators: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BinPackParameters: true
+ColumnLimit:     0
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+DerivePointerBinding: false
+ExperimentalAutoDetectBinPacking: false
+IndentCaseLabels: false
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 60
+PenaltyBreakString: 1000
+PenaltyBreakFirstLessLess: 120
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerBindsToType: false
+SpaceBeforeAssignmentOperators: true
+SpacesBeforeTrailingComments: 1
+Cpp11BracedListStyle: false
+Standard:        Cpp03
+IndentWidth:     4
+TabWidth:        4
+UseTab:          Never
+BreakBeforeBraces: Attach
+IndentFunctionDeclarationAfterType: false
+SpacesInParentheses: false
+SpacesInAngles:  false
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+SpaceAfterControlStatementKeyword: true
+SpaceBeforeAssignmentOperators: true
+ContinuationIndentWidth: 4
+...
+
diff --git a/cmake/FindFFTW3.cmake b/cmake/FindFFTW3.cmake
new file mode 100644 (file)
index 0000000..ab54c0c
--- /dev/null
@@ -0,0 +1,114 @@
+# - Try to find FFTW3.
+# Usage: find_package(FFTW3 [COMPONENTS [single double long-double threads]])
+#
+# Variables used by this module:
+#  FFTW3_ROOT_DIR             - FFTW3 root directory
+# Variables defined by this module:
+#  FFTW3_FOUND                - system has FFTW3
+#  FFTW3_INCLUDE_DIR          - the FFTW3 include directory (cached)
+#  FFTW3_INCLUDE_DIRS         - the FFTW3 include directories
+#                               (identical to FFTW3_INCLUDE_DIR)
+#  FFTW3[FL]?_LIBRARY         - the FFTW3 library - double, single(F), 
+#                               long-double(L) precision (cached)
+#  FFTW3[FL]?_THREADS_LIBRARY - the threaded FFTW3 library - double, single(F), 
+#                               long-double(L) precision (cached)
+#  FFTW3_LIBRARIES            - list of all FFTW3 libraries found
+
+# Copyright (C) 2009-2010
+# ASTRON (Netherlands Institute for Radio Astronomy)
+# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
+#
+# This file is part of the LOFAR software suite.
+# The LOFAR software suite is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# The LOFAR software suite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: FindFFTW3.cmake 15918 2010-06-25 11:12:42Z loose $
+
+# Use double precision by default.
+if(FFTW3_FIND_COMPONENTS MATCHES "^$")
+  set(_components double)
+else()
+  set(_components ${FFTW3_FIND_COMPONENTS})
+endif()
+
+# Loop over each component.
+set(_libraries)
+foreach(_comp ${_components})
+  if(_comp STREQUAL "single")
+    list(APPEND _libraries fftw3f)
+  elseif(_comp STREQUAL "double")
+    list(APPEND _libraries fftw3)
+  elseif(_comp STREQUAL "long-double")
+    list(APPEND _libraries fftw3l)
+  elseif(_comp STREQUAL "threads")
+    set(_use_threads ON)
+  else()
+    message(FATAL_ERROR "FindFFTW3: unknown component `${_comp}' specified. "
+      "Valid components are `single', `double', `long-double', and `threads'.")
+  endif()
+endforeach()
+
+# If using threads, we need to link against threaded libraries as well.
+if(_use_threads)
+  set(_thread_libs)
+  foreach(_lib ${_libraries})
+    list(APPEND _thread_libs ${_lib}_threads)
+  endforeach()
+  set(_libraries ${_thread_libs} ${_libraries})
+endif()
+
+# Keep a list of variable names that we need to pass on to
+# find_package_handle_standard_args().
+set(_check_list)
+
+# Search for all requested libraries.
+if (WIN32)
+
+    foreach(_lib ${_libraries})
+
+      string(TOUPPER ${_lib} _LIB)
+
+      find_library(${_LIB}_LIBRARY lib${_lib}-3
+        HINTS $ENV{FFTW3_ROOT_DIR} PATH_SUFFIXES lib)
+      mark_as_advanced(${_LIB}_LIBRARY)
+      list(APPEND FFTW3_LIBRARIES ${${_LIB}_LIBRARY})
+      list(APPEND _check_list ${_LIB}_LIBRARY)
+    endforeach()
+
+    message("FFTW3 WINDOWS libraries: " ${FFTW3_LIBRARIES})
+
+else ()
+    foreach(_lib ${_libraries})
+
+      string(TOUPPER ${_lib} _LIB)
+
+      find_library(${_LIB}_LIBRARY ${_lib}
+        HINTS $ENV{FFTW3_ROOT_DIR} PATH_SUFFIXES lib)
+      mark_as_advanced(${_LIB}_LIBRARY)
+      list(APPEND FFTW3_LIBRARIES ${${_LIB}_LIBRARY})
+      list(APPEND _check_list ${_LIB}_LIBRARY)
+    endforeach()
+
+    message("FFTW3 UNIX libraries: " ${FFTW3_LIBRARIES})
+endif ()
+
+# Search for the header file.
+find_path(FFTW3_INCLUDE_DIR fftw3.h 
+  HINTS $ENV{FFTW3_ROOT_DIR} PATH_SUFFIXES include)
+mark_as_advanced(FFTW3_INCLUDE_DIR)
+list(APPEND _check_list FFTW3_INCLUDE_DIR)
+
+# Handle the QUIETLY and REQUIRED arguments and set FFTW3_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(FFTW3 DEFAULT_MSG ${_check_list})
diff --git a/cmake/FindPugiXML.cmake b/cmake/FindPugiXML.cmake
new file mode 100644 (file)
index 0000000..931d547
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyrigt 2012 Kamil Rytarowski <kamil AT mageia DOT org>
+#
+# 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/.
+
+find_path(PugiXML_INCLUDE_DIR pugixml.hpp /usr/include)
+
+find_library(PugiXML_LIBRARY
+  NAMES
+  "pugixml"
+  PATHS
+  /usr/lib
+  )
+
+if(PugiXML_LIBRARY AND PugiXML_INCLUDE_DIR)
+  set(PugiXML_FOUND 1)
+  mark_as_advanced(PugiXML_LIBRARY PugiXML_INCLUDE_DIR)
+else()
+  set(PugiXML_FOUND 0)
+endif()
+  
+if(NOT PugiXML_FOUND)
+  if(PugiXML_FIND_REQUIRED)
+    message(FATAL_ERROR "PugiXML was not found.")
+  endif()
+endif()
\ No newline at end of file
diff --git a/cmake/ISMRMRDConfig.cmake.in b/cmake/ISMRMRDConfig.cmake.in
new file mode 100644 (file)
index 0000000..f23a00e
--- /dev/null
@@ -0,0 +1,29 @@
+#-----------------------------------------------------------------------------
+#
+# ISMRMRDConfig.cmake - ISMRMRD CMake configuration file for external projects.
+#
+# This file is configured by ISMRMRD and used by the UseISMRMRD.cmake module
+# to load ISMRMRD's settings for an external project.
+@ISMRMRD_CONFIG_CODE@
+
+##- include(${CMAKE_CURRENT_LIST_DIR}/ISMRMRDConfigVersion.cmake)
+
+# The ISMRMRD version number
+set(ISMRMRD_VERSION_MAJOR "@ISMRMRD_VERSION_MAJOR@")
+set(ISMRMRD_VERSION_MINOR "@ISMRMRD_VERSION_MINOR@")
+set(ISMRMRD_VERSION_PATCH "@ISMRMRD_VERSION_PATCH@")
+
+#   ISMRMRD_SCHEMA_DIR   - where to find ismrmrd.xsd 
+set(ISMRMRD_SCHEMA_DIR   @CONFIG_ISMRMRD_SCHEMA_DIR@)
+#   ISMRMRD_INCLUDE_DIR  - where to find ismrmrd.h, etc.
+set(ISMRMRD_INCLUDE_DIRS @CONFIG_ISMRMRD_TARGET_INCLUDE_DIRS@)
+#   ISMRMRD_LIBRARY_DIRS - where to search for libraries
+set(ISMRMRD_LIBRARY_DIRS @CONFIG_ISMRMRD_LIBRARY_DIRS@)
+#   ISMRMRD_LIBRARIES    - i.e. ismrmrd
+set(ISMRMRD_LIBRARIES    @ISMRMRD_LIBRARIES@)
+
+## For backwards compatibility use existing variable name
+## Include directories can be lists, and should be plural
+## to conform with naming schemes in many other cmake packages
+set(ISMRMRD_INCLUDE_DIR  @CONFIG_ISMRMRD_TARGET_INCLUDE_DIRS@)
+set(ISMRMRD_LIB_DIR @CONFIG_ISMRMRD_LIBRARY_DIRS@)
diff --git a/cmake/cpack_options.cmake.in b/cmake/cpack_options.cmake.in
new file mode 100644 (file)
index 0000000..6a8f5ef
--- /dev/null
@@ -0,0 +1,33 @@
+################################################################################
+# Metadata for package generators
+################################################################################
+
+# Common options
+set(CPACK_PACKAGE_VERSION "@ISMRMRD_VERSION_STRING@")
+set(CPACK_PACKAGE_VERSION_MAJOR "@ISMRMRD_VERSION_MAJOR@")
+set(CPACK_PACKAGE_VERSION_MINOR "@ISMRMRD_VERSION_MINOR@")
+set(CPACK_PACKAGE_VERSION_PATCH "@ISMRMRD_VERSION_PATCH@")
+set(CPACK_PACKAGE_NAME "@PROJECT_NAME@")
+set(CPACK_PACKAGE_VENDOR "http://ismrmrd.github.io/")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "ISMRM Raw Data Format (ISMRMRD)")
+set(CPACK_PACKAGE_INSTALL_DIRECTORY "@PROJECT_NAME_LOWER@")
+set(CPACK_RESOURCE_FILE_LICENSE "@CMAKE_SOURCE_DIR@/LICENSE")
+set(CPACK_RESOURCE_FILE_README "@CMAKE_SOURCE_DIR@/README.html")
+set(CPACK_PACKAGE_DESCRIPTION_FILE "@CMAKE_SOURCE_DIR@/README.html")
+set(CPACK_PACKAGE_MAINTAINER "Michael S. Hansen <michael.hansen@nih.gov>")
+set(CPACK_PACKAGE_CONTACT "Michael S. Hansen <michael.hansen@nih.gov>")
+
+# DEB specific
+set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
+set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
+set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Implementation of the ISMRMRD format.")
+
+# NSIS specific
+set(CPACK_NSIS_HELP_LINK "http://ismrmrd.github.io")
+set(CPACK_NSIS_URL_INFO_ABOUT "http://ismrmrd.github.io")
+set(CPACK_NSIS_MODIFY_PATH ON)
+set(CPACK_NSIS_DISPLAY_NAME "ismrmrd")
+
+# Output filename of the generated tarball / package
+set(CPACK_PACKAGE_FILE_NAME "@PROJECT_NAME_LOWER@-@ISMRMRD_VERSION_STRING@")
+set(CPACK_SOURCE_PACKAGE_FILE_NAME "@PROJECT_NAME_LOWER@-@ISMRMRD_VERSION_STRING@")
diff --git a/cmake/ismrmrd_cpack.cmake b/cmake/ismrmrd_cpack.cmake
new file mode 100644 (file)
index 0000000..96d6bf3
--- /dev/null
@@ -0,0 +1,40 @@
+################################################################################
+# Find available package generators
+################################################################################
+
+if(UNIX)
+  # DEB
+  find_program(DPKG_PROGRAM dpkg)
+  if(EXISTS ${DPKG_PROGRAM})
+    list(APPEND CPACK_GENERATOR "DEB")
+  endif()
+endif()
+
+# Enable/Disable automatic search for dependencies:
+set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+
+# Enable/Disable component install for CPack generator DEB
+set(CPACK_DEB_COMPONENT_INSTALL OFF)
+set(CPACK_DEB_PACKAGE_COMPONENT OFF)
+
+# Set dependencies explicitly
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "libhdf5-7, libfftw3-3, libboost-program-options-dev")
+
+# Where the package metadata are
+set(ISMRMRD_CPACK_CFG_FILE "${PROJECT_BINARY_DIR}/cpack_options.cmake")
+
+# Where the package to be installed 
+set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
+message("CPACK_PACKAGING_INSTALL_PREFIX: " ${CPACK_PACKAGING_INSTALL_PREFIX})
+
+if(WIN32)
+  # NSLS
+  list(APPEND CPACK_GENERATOR "NSIS")    
+endif()
+
+list(APPEND CPACK_SOURCE_GENERATOR "TGZ")
+list(APPEND CPACK_SOURCE_GENERATOR "ZIP")
+list(APPEND CPACK_SOURCE_IGNORE_FILES ";.git;.gitignore;todo.txt;_clang-format;build/")
+
+# set dependencies explicitly
+include(InstallRequiredSystemLibraries)
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9771a0c
--- /dev/null
@@ -0,0 +1,39 @@
+add_custom_target(doc)
+find_package(Doxygen)
+if(DOXYGEN_FOUND)
+    set(API_DOC_DIR ${CMAKE_CURRENT_BINARY_DIR}/html/api)
+    set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+
+    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${DOXYFILE} @ONLY)
+
+    add_custom_target(apidoc
+        COMMAND ${CMAKE_COMMAND} -E make_directory ${API_DOC_DIR}
+        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE}
+        COMMENT "Generating API documentation using Doxygen" VERBATIM)
+    add_dependencies(doc apidoc)
+else()
+    message(STATUS "Doxygen not found. Not able to build API documentation")
+endif()
+
+find_program(SPHINXBUILD sphinx-build)
+if(SPHINXBUILD)
+    if(WIN32)
+        set(SPHINX_MAKEBAT ${CMAKE_CURRENT_BINARY_DIR}/make.sphinx.bat)
+        configure_file(make.sphinx.bat.in ${SPHINX_MAKEBAT} @ONLY)
+        add_custom_target(htmldoc
+            COMMAND ${SPHINX_MAKEBAT} html
+            COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/html/.nojekyll
+            COMMENT "Generating HTML docs using Sphinx" VERBATIM)
+    else()
+        set(SPHINX_MAKEFILE ${CMAKE_CURRENT_BINARY_DIR}/Makefile.sphinx)
+        configure_file(Makefile.sphinx.in ${SPHINX_MAKEFILE} @ONLY)
+        add_custom_target(htmldoc
+            COMMAND make -f ${SPHINX_MAKEFILE} html
+            COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/html/.nojekyll
+            COMMENT "Generating HTML docs using Sphinx" VERBATIM)
+    endif()
+    add_dependencies(doc htmldoc)
+else()
+    message(STATUS "sphinx-build not found. Not able to build HTML project documentation")
+endif()
+
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
new file mode 100644 (file)
index 0000000..ff07a28
--- /dev/null
@@ -0,0 +1,2333 @@
+# Doxyfile 1.8.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = ISMRMRD
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "ISMRM Raw Data Format"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = @CMAKE_CURRENT_BINARY_DIR@
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = NO
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @CMAKE_SOURCE_DIR@/README.md \
+                            @CMAKE_SOURCE_DIR@/include/ismrmrd \
+                            @CMAKE_SOURCE_DIR@/libsrc
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = pugi*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = @CMAKE_SOURCE_DIR@/README.md
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = @API_DOC_DIR@
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra stylesheet files is of importance (e.g. the last
+# stylesheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
+# for the replacement values of the other commands the user is refered to
+# HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = YES
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = __cplusplus
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+PLANTUML_JAR_PATH      =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/doc/Makefile.sphinx.in b/doc/Makefile.sphinx.in
new file mode 100644 (file)
index 0000000..f43540a
--- /dev/null
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = @CMAKE_CURRENT_BINARY_DIR@
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) @CMAKE_CURRENT_SOURCE_DIR@/source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) @CMAKE_CURRENT_SOURCE_DIR@/source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html       to make standalone HTML files"
+       @echo "  dirhtml    to make HTML files named index.html in directories"
+       @echo "  singlehtml to make a single large HTML file"
+       @echo "  pickle     to make pickle files"
+       @echo "  json       to make JSON files"
+       @echo "  htmlhelp   to make HTML files and a HTML help project"
+       @echo "  qthelp     to make HTML files and a qthelp project"
+       @echo "  devhelp    to make HTML files and a Devhelp project"
+       @echo "  epub       to make an epub"
+       @echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+       @echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+       @echo "  text       to make text files"
+       @echo "  man        to make manual pages"
+       @echo "  texinfo    to make Texinfo files"
+       @echo "  info       to make Texinfo files and run them through makeinfo"
+       @echo "  gettext    to make PO message catalogs"
+       @echo "  changes    to make an overview of all changed/added/deprecated items"
+       @echo "  xml        to make Docutils-native XML files"
+       @echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+       @echo "  linkcheck  to check all external links for integrity"
+       @echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       rm -rf $(BUILDDIR)/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+       $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+       @echo
+       @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ISMRMRD.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ISMRMRD.qhc"
+
+devhelp:
+       $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+       @echo
+       @echo "Build finished."
+       @echo "To view the help file:"
+       @echo "# mkdir -p $$HOME/.local/share/devhelp/ISMRMRD"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ISMRMRD"
+       @echo "# devhelp"
+
+epub:
+       $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+       @echo
+       @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make' in that directory to run these through (pdf)latex" \
+             "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through pdflatex..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through platex and dvipdfmx..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+       $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+       @echo
+       @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+       @echo
+       @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo
+       @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+       @echo "Run \`make' in that directory to run these through makeinfo" \
+             "(use \`make info' here to do that automatically)."
+
+info:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo "Running Texinfo files through makeinfo..."
+       make -C $(BUILDDIR)/texinfo info
+       @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+       $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+       @echo
+       @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+       $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+       @echo
+       @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+       $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+       @echo
+       @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/doc/WindowsISMRMRDInstallDependencies.ps1 b/doc/WindowsISMRMRDInstallDependencies.ps1
new file mode 100644 (file)
index 0000000..d50c91e
Binary files /dev/null and b/doc/WindowsISMRMRDInstallDependencies.ps1 differ
diff --git a/doc/make.sphinx.bat.in b/doc/make.sphinx.bat.in
new file mode 100644 (file)
index 0000000..30a33f1
--- /dev/null
@@ -0,0 +1,242 @@
+@ECHO OFF\r
+\r
+REM Command file for Sphinx documentation\r
+\r
+if "%SPHINXBUILD%" == "" (\r
+       set SPHINXBUILD=sphinx-build\r
+)\r
+set BUILDDIR=@CMAKE_CURRENT_BINARY_DIR@\r
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% @CMAKE_CURRENT_SOURCE_DIR@/source\r
+set I18NSPHINXOPTS=%SPHINXOPTS% @CMAKE_CURRENT_SOURCE_DIR@/source\r
+if NOT "%PAPER%" == "" (\r
+       set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\r
+       set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\r
+)\r
+\r
+if "%1" == "" goto help\r
+\r
+if "%1" == "help" (\r
+       :help\r
+       echo.Please use `make ^<target^>` where ^<target^> is one of\r
+       echo.  html       to make standalone HTML files\r
+       echo.  dirhtml    to make HTML files named index.html in directories\r
+       echo.  singlehtml to make a single large HTML file\r
+       echo.  pickle     to make pickle files\r
+       echo.  json       to make JSON files\r
+       echo.  htmlhelp   to make HTML files and a HTML help project\r
+       echo.  qthelp     to make HTML files and a qthelp project\r
+       echo.  devhelp    to make HTML files and a Devhelp project\r
+       echo.  epub       to make an epub\r
+       echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\r
+       echo.  text       to make text files\r
+       echo.  man        to make manual pages\r
+       echo.  texinfo    to make Texinfo files\r
+       echo.  gettext    to make PO message catalogs\r
+       echo.  changes    to make an overview over all changed/added/deprecated items\r
+       echo.  xml        to make Docutils-native XML files\r
+       echo.  pseudoxml  to make pseudoxml-XML files for display purposes\r
+       echo.  linkcheck  to check all external links for integrity\r
+       echo.  doctest    to run all doctests embedded in the documentation if enabled\r
+       goto end\r
+)\r
+\r
+if "%1" == "clean" (\r
+       for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i\r
+       del /q /s %BUILDDIR%\*\r
+       goto end\r
+)\r
+\r
+\r
+%SPHINXBUILD% 2> nul\r
+if errorlevel 9009 (\r
+       echo.\r
+       echo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r
+       echo.installed, then set the SPHINXBUILD environment variable to point\r
+       echo.to the full path of the 'sphinx-build' executable. Alternatively you\r
+       echo.may add the Sphinx directory to PATH.\r
+       echo.\r
+       echo.If you don't have Sphinx installed, grab it from\r
+       echo.http://sphinx-doc.org/\r
+       exit /b 1\r
+)\r
+\r
+if "%1" == "html" (\r
+       %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The HTML pages are in %BUILDDIR%/html.\r
+       goto end\r
+)\r
+\r
+if "%1" == "dirhtml" (\r
+       %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\r
+       goto end\r
+)\r
+\r
+if "%1" == "singlehtml" (\r
+       %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\r
+       goto end\r
+)\r
+\r
+if "%1" == "pickle" (\r
+       %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished; now you can process the pickle files.\r
+       goto end\r
+)\r
+\r
+if "%1" == "json" (\r
+       %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished; now you can process the JSON files.\r
+       goto end\r
+)\r
+\r
+if "%1" == "htmlhelp" (\r
+       %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished; now you can run HTML Help Workshop with the ^\r
+.hhp project file in %BUILDDIR%/htmlhelp.\r
+       goto end\r
+)\r
+\r
+if "%1" == "qthelp" (\r
+       %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished; now you can run "qcollectiongenerator" with the ^\r
+.qhcp project file in %BUILDDIR%/qthelp, like this:\r
+       echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ISMRMRD.qhcp\r
+       echo.To view the help file:\r
+       echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ISMRMRD.ghc\r
+       goto end\r
+)\r
+\r
+if "%1" == "devhelp" (\r
+       %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished.\r
+       goto end\r
+)\r
+\r
+if "%1" == "epub" (\r
+       %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The epub file is in %BUILDDIR%/epub.\r
+       goto end\r
+)\r
+\r
+if "%1" == "latex" (\r
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\r
+       goto end\r
+)\r
+\r
+if "%1" == "latexpdf" (\r
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r
+       cd %BUILDDIR%/latex\r
+       make all-pdf\r
+       cd %BUILDDIR%/..\r
+       echo.\r
+       echo.Build finished; the PDF files are in %BUILDDIR%/latex.\r
+       goto end\r
+)\r
+\r
+if "%1" == "latexpdfja" (\r
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\r
+       cd %BUILDDIR%/latex\r
+       make all-pdf-ja\r
+       cd %BUILDDIR%/..\r
+       echo.\r
+       echo.Build finished; the PDF files are in %BUILDDIR%/latex.\r
+       goto end\r
+)\r
+\r
+if "%1" == "text" (\r
+       %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The text files are in %BUILDDIR%/text.\r
+       goto end\r
+)\r
+\r
+if "%1" == "man" (\r
+       %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The manual pages are in %BUILDDIR%/man.\r
+       goto end\r
+)\r
+\r
+if "%1" == "texinfo" (\r
+       %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\r
+       goto end\r
+)\r
+\r
+if "%1" == "gettext" (\r
+       %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The message catalogs are in %BUILDDIR%/locale.\r
+       goto end\r
+)\r
+\r
+if "%1" == "changes" (\r
+       %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.The overview file is in %BUILDDIR%/changes.\r
+       goto end\r
+)\r
+\r
+if "%1" == "linkcheck" (\r
+       %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Link check complete; look for any errors in the above output ^\r
+or in %BUILDDIR%/linkcheck/output.txt.\r
+       goto end\r
+)\r
+\r
+if "%1" == "doctest" (\r
+       %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Testing of doctests in the sources finished, look at the ^\r
+results in %BUILDDIR%/doctest/output.txt.\r
+       goto end\r
+)\r
+\r
+if "%1" == "xml" (\r
+       %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The XML files are in %BUILDDIR%/xml.\r
+       goto end\r
+)\r
+\r
+if "%1" == "pseudoxml" (\r
+       %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\r
+       if errorlevel 1 exit /b 1\r
+       echo.\r
+       echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\r
+       goto end\r
+)\r
+\r
+:end\r
diff --git a/doc/source/TODO.rst b/doc/source/TODO.rst
new file mode 100644 (file)
index 0000000..29090cb
--- /dev/null
@@ -0,0 +1,19 @@
+TODO
+-------------------------
+
+* Make More Example Datasets
+
+       - Radial
+       - Diffusion
+       - Phase contrast flow
+
+* Flag definitions.
+
+       - Do we need more flags?
+
+* Converters for vendor raw data
+
+       - Siemens (Hansen)
+       - Philips (Kozerke? Boernert?)
+       - GE (Hargreaves?)
+       - Bruker (Hansen?)
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644 (file)
index 0000000..a62a228
--- /dev/null
@@ -0,0 +1,273 @@
+# -*- coding: utf-8 -*-
+#
+# ISMRMRD documentation build configuration file, created by
+# sphinx-quickstart on Thu Nov 13 10:11:39 2014.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+#import breathe
+import sphinx_bootstrap_theme
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.todo',
+    'sphinx.ext.mathjax',
+    #'breathe'
+]
+
+#breathe_projects = { 'ISMRMRD': '/Users/naegelejd/src/github.com/ismrmrd/ismrmrd/build/doc/api/xml' }
+#breathe_default_project = 'ISMRMRD'
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'ISMRMRD'
+copyright = u'2014, ISMRMRD Developers'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.1'
+# The full version, including alpha/beta/rc tags.
+release = '1.1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'bootstrap'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {
+    'navbar_links': [
+        ('API Reference', "api/index.html", True)
+    ]
+}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ISMRMRDdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+  ('index', 'ISMRMRD.tex', u'ISMRMRD Documentation',
+   u'ISMRMRD Developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'ismrmrd', u'ISMRMRD Documentation',
+     [u'ISMRMRD Developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'ISMRMRD', u'ISMRMRD Documentation',
+   u'ISMRMRD Developers', 'ISMRMRD', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644 (file)
index 0000000..23b7c3e
--- /dev/null
@@ -0,0 +1,459 @@
+.. ISMRMRD documentation master file, created by\r
+   sphinx-quickstart on Thu Nov 13 10:11:39 2014.\r
+   You can adapt this file completely to your liking, but it should at least\r
+   contain the root `toctree` directive.\r
+\r
+ISMRM Raw Data Format (ISMRMRD)\r
+===================================\r
+\r
+Contents:\r
+\r
+.. toctree::\r
+   :maxdepth: 1\r
+\r
+.. contents::\r
+\r
+Preamble\r
+---------\r
+\r
+A prerequisite for sharing magnetic resonance (imaging) reconstruction algorithms and code is a common raw data format. This document describes such a common raw data format and attempts to capture the data fields that are require to describe enough details about the magnetic resonance experiment to reconstruct images from the data.\r
+This standard was developed by a subcommittee of the ISMRM Sedona 2013 workshop. Comments and requests for additions/modifications can be sent to:\r
+\r
+* Michael S. Hansen (michael DOT hansen AT nih DOT gov)\r
+* Wally Block (wblock AT cae DOT wisc DOT edu)\r
+* Mark Griswold (mag46 AT case DOT edu)\r
+* Brian Hargreaves (bah AT stanford DOT edu)\r
+* Peter Boernert (peter DOT boernert AT philips DOT com)\r
+* Sebastian Kozerke (kozerke AT biomed DOT ee DOT ethz DOT ch)\r
+* Craig Meyer (cmeyer AT virginia DOT edu)\r
+* Doug Noll (dnoll AT umich DOT edu)\r
+* Jim Pipe (Jim.Pipe AT DignityHealth DOT org)\r
+\r
+Developers/Contributors\r
+------------------------\r
+\r
+* Michael S. Hansen, National Institutes of Health, USA\r
+* Nick Zwart, Barrow Neurological Institute, Phoenix, Arizona\r
+* Souheil Inati, National Institutes of Health, USA\r
+* Joe Naegele, National Institutes of Health, USA\r
+* Kaveh Vahedipour, Juelich Research Centre, Juelich, Germany\r
+\r
+Obtaining and Installing\r
+-------------------------\r
+\r
+The source code, examples, and example datasets can be found on the ISMRM Raw Data Github website_.\r
+\r
+.. _website: https://github.com/ismrmrd/ismrmrd\r
+\r
+To download the source code, clone the git archive::\r
+\r
+    git clone https://github.com/ismrmrd/ismrmrd\r
+\r
+API Documentation can be found at https://ismrmrd.github.io/api/.\r
+\r
+\r
+Dependencies\r
+.............\r
+\r
+The ISMRM Raw Data format is described by an XML schema_ and some C-style structs with fixed memory layout and as such does not have dependencies. However, it uses HDF5 files for storage and a C++ library for reading and writing the ISMRMRD files is included in this distribution. Furthermore, since the XML header is defined with an XML schema_, we encourage using XML data binding when writing software using the format. To compile all components of this distribution you need:\r
+\r
+\r
+* HDF5 (version 1.8 or higher) libraries. Available from http://www.hdfgroup.org/downloads/index.html.\r
+* Boost (http://www.boost.org/)\r
+* Cmake build tool (http://www.cmake.org/)\r
+* Git if you would like to use the source code archive (http://git-scm.com/)\r
+* FFTW if you would like to compile some of the example applications\r
+  (http://www.fftw.org)\r
+* Doxygen if you would like to generate API documentation (http://www.doxygen.org)\r
+* Sphinx (http://sphinx-doc.org/) and Sphinx Bootstrap Theme (https://pypi.python.org/pypi/sphinx-bootstrap-theme) to generate this project documentation\r
+\r
+.. note:: It is only necessary to install the dependencies if you wish to develop compiled C/C++ software, which uses the ISMRMRD format. The format can be read in Matlab without installing any additional software.\r
+\r
+\r
+Linux installation\r
+...................\r
+\r
+The dependencies mentioned above should be included in most linux distributions. On Ubuntu you can install all required dependencies with::\r
+\r
+  sudo apt-get install libhdf5-serial-dev h5utils cmake cmake-curses-gui libboost-all-dev doxygen git\r
+\r
+After installation of dependencies, the library can be installed with:\r
+\r
+.. code:: bash\r
+\r
+    git clone https://github.com/ismrmrd/ismrmrd\r
+    cd ismrmrd/\r
+    mkdir build\r
+    cd build\r
+    cmake ../\r
+    make\r
+    sudo make install\r
+\r
+This will install the library in ``/usr/local/`` by default. To specify\r
+an alternative installation directory, pass ``-D CMAKE_INSTALL_PREFIX=<install dir>`` to ``cmake``.\r
+\r
+Mac OSX Installation\r
+.....................\r
+\r
+There are numerous different package management systems for Mac. In this example, we have used Homebrew (http://brew.sh/). First install the dependencies::\r
+\r
+    brew tap homebrew/science\r
+    brew install wget hdf5 boost cmake doxygen fftw\r
+\r
+Then download and compile:\r
+\r
+.. code:: bash\r
+\r
+    git clone https://github.com/ismrmrd/ismrmrd\r
+    cd ismrmrd\r
+    mkdir build\r
+    cd build/\r
+    cmake ../\r
+    make\r
+    make install\r
+\r
+This will install the library in ``/usr/local/`` by default. To specify\r
+an alternative installation directory, pass ``-D CMAKE_INSTALL_PREFIX=<install dir>`` to ``cmake``.\r
+\r
+\r
+Windows Installation\r
+.....................\r
+\r
+Setting up a Windows development environment is usually a bit more challenging than working on Unix platforms where most library dependencies are easily installed with package management systems (see above). The general Windows installation instructions (you may have to make adjustments for your setup) is as follows:\r
+\r
+* Starting with a Windows 7 (64-bit) machine with Visual Studio 2010 installed.\r
+\r
+* Install CMake (http://www.cmake.org/files/v2.8/cmake-2.8.9-win32-x86.exe)\r
+\r
+* Install Git (http://msysgit.googlecode.com/files/Git-1.7.11-preview20120710.exe)\r
+\r
+* Install HDF5 (http://www.hdfgroup.org/ftp/HDF5/current/bin/windows/HDF5189-win64-vs10-shared.zip)\r
+\r
+* Install HDFView (http://www.hdfgroup.org/ftp/HDF5/hdf-java/hdfview/hdfview_install_win64.exe)\r
+\r
+* Install Boost (http://boostpro.com/download/x64/boost_1_51_setup.exe)\r
+\r
+  - Just install everything for VS2010 and worry about which versions you need later.\r
+\r
+* Install FFTW (ftp://ftp.fftw.org/pub/fftw/fftw-3.3.2-dll64.zip)\r
+\r
+  - You need to create ``.lib`` files manually after installing. See instructions at http://www.fftw.org/install/windows.html\r
+\r
+* Make sure the paths to your new libraries are in your PATH environment variable:\r
+\r
+  - Boost libraries  (typically ``C:\Program Files\boost\boost_1_51\lib``)\r
+  - FFTW libraries (typically ``C:\MRILibraries\fftw3``)\r
+  - HDF5 libraries (typically ``C:\Program Files\HDF Group\HDF5\1.8.9\bin``)\r
+  - ISMRMRD (typically ``C:\Program Files\ismrmrd\bin;C:\Program Files\ismrmrd\bin``)\r
+\r
+This can seem a bit daunting, we have included a Windows powershell_ script, which you can use to guide you through the installation process.\r
+\r
+.. _powershell: https://github.com/ismrmrd/ismrmrd/blob/master/doc/WindowsISMRMRDInstallDependencies.ps1\r
+\r
+After installing all dependencies, download the code, e.g. from a git bash shell:\r
+\r
+.. code:: bash\r
+\r
+    git clone https://github.com/ismrmrd/ismrmrd\r
+    cd ismrmrd/\r
+    mkdir build\r
+    cd build/\r
+    cmake-gui.exe\r
+\r
+Last command will open CMake's graphical user interface. Hit the configure button and deal with the dependencies that CMake is unable to find. Hit configure again and repeat the process until CMake has enough information to configure. Once the configuration is complete, you can hit generate to generate a Visual Studio project, which you can open and use to build ISMRMRD. There are step-by-step commands included in the powershell_ script below to guide you through the CMake configuration and build process from the command line. The command line CMake configuration line (assuming you have installed with the paths above), would look something like (backslashes are just there to break the command over multiple lines)::\r
+\r
+    cmake -G"Visual Studio 10 Win64" \\r
+        -DBOOST_ROOT=C:/Program Files/boost/boost_1_51 \\r
+        -DFFTW3_INCLUDE_DIR=C:/MRILibraries/fftw3 \\r
+        -DFFTW3F_LIBRARY=C:/MRILibraries/fftw3/libfftw3f-3.lib ../\r
+\r
+Again, you may have to adjust for your specific installation paths. After generating the Visual Studio project, you can build from a Visual Studio Command Prompt with::\r
+\r
+   msbuild .\ISMRMRD.sln /p:Configuration=Release\r
+\r
+\r
+Overview\r
+---------\r
+\r
+The raw data format combines a mix of flexible data structures (XML header) and fixed structures (equivalent to C-structs). A raw data set consist mainly of 2 sections:\r
+\r
+1. A flexible XML format document that can contain an arbitrary number of fields and accommodate everything from simple values (b-values, etc.) to entire vendor protocols, etc. This purpose of this XML document is to provide parameters that may be meaningful for some experiments but not for others. This XML format is defined by an XML Schema Definition file (ismrmrd.xsd).\r
+2. Raw data section. This section contains all the acquired data in the experiment. Each data item is preceded by a C-struct with encoding numbers, etc. Following this data header is a channel header and data for each acquired channel. The raw data headers are defined in a C/C++ header file (ismrmrd.h)\r
+\r
+In addition to these sections, the ISMRMRD format also specifies an image header for storing reconstructed images and the accompanying C++ library provides a convenient way of writing such images into HDF5 files along with generic arrays for storing less well defined data structures, e.g. coil sensitivity maps or other calibration data.\r
+\r
+Flexible Data Header\r
+.....................\r
+\r
+The flexible data structure is defined by the xml schema definition in ``schema/ismrmrd.xsd`` (schema_ is included in appendix below).\r
+\r
+An example of an XML file for a Cartesian 3D acquisition could look like:\r
+\r
+.. literalinclude:: ../../schema/ismrmrd_example.xml\r
+\r
+The most critical elements for image reconstruction are contained in the ``<encoding>`` section of the document, which describes the encoded spaced and also the target reconstructed space. Along with the ``<encodingLimits>`` this section allows the reconstruction program to determine matrix sizes, oversampling factors, partial Fourier, etc. In the example above, data is acquired with two-fold oversampling in the read-out (``x``) direction, which is reflected in the larger matrix size in the encoded space compared to the reconstruction space. The field of view is also twice as large in the encoded space. For the first phase encoding dimension (``y``), we have a combination of oversampling (20%), reduced phase resolution (only 83 lines of k-space acquired, and partial Fourier sampling, which is reflected in the asymmetric center of the encoding limits of the ``<kspace_encoding_step_1>``. Specifically, the data lines would be placed into the encoding space like this:\r
+\r
+::\r
+\r
+   0                                     70                                         139\r
+   |-------------------------------------|-------------------------------------------|\r
+                         ****************************************************\r
+                         ^               ^                                  ^\r
+                         0              28                                  83\r
+\r
+After FFT, only the central 116 lines are kept, i.e. there is a reduced field of view in the phase encoding direction. Center and encoding limits for the readout dimension is not given in the XML header. This is to accommodate sequences where the center of the readout may change from readout to readout (alternating directions of readout). There is a field on the individual data headers (see below) to indicate the center of the readout.\r
+\r
+An experiment can have multiple encoding spaces and it is possible to indicate on each acquired data readout, which encoding space the data belongs to (see below).\r
+\r
+In addition to the defined field in the xml header, it is possible to add an arbitrary number of user defined parameters to accommodate special sequence parameters. Please consult the xml schema_ to see how user parameters are defined. Briefly, the XML header can have a section at the end which looks like:\r
+\r
+.. code:: xml\r
+\r
+   <userParameters>\r
+     <userParameterLong>\r
+       <name>MyVar1</name><value>1003</value>\r
+     </userParameterLong>\r
+     <userParameterLong>\r
+       <name>MyVar2</name><value>1999</value>\r
+     </userParameterLong>\r
+     <userParameterDouble>\r
+       <name>MyDoubleVar</name><value>87.6676</value>\r
+     </userParameterDouble>\r
+   </userParameters>\r
+\r
+\r
+Fixed Data structures\r
+......................\r
+\r
+Each raw data acquisition is preceded by the following fixed layout structure:\r
+\r
+.. literalinclude:: ../../include/ismrmrd/ismrmrd.h\r
+   :start-after: typedef struct ISMRMRD_AcquisitionHeader {\r
+   :end-before: } ISMRMRD_AcquisitionHeader;\r
+\r
+Where EncodingCounters are defined as:\r
+\r
+.. literalinclude:: ../../include/ismrmrd/ismrmrd.h\r
+   :start-after: typedef struct ISMRMRD_EncodingCounters {\r
+   :end-before: } ISMRMRD_EncodingCounters;\r
+\r
+The interpretation of some of these fields may vary from sequence to sequence, i.e. for a Cartesian sequence, ``kspace_encode_step_1`` would be the phase encoding step, for a spiral sequence where phase encoding direction does not make sense, it would be the spiral interleave number. The ``encoding_space_ref`` enables the user to tie an acquisition to a specific encoding space (see above) in case there are multiple, e.g. in situations where a calibration scan may be integrated in the acquisition.\r
+\r
+The flags field is a bit mask, which in principle can be used freely by the user, but suggested flag values are given in ``ismrmrd.h``, it is recommended not to use already designated flag bits for custom purposes. There are a set of bits reserved for prototyping (bits 57-64), please see ``ismrmrd.h`` for details.\r
+\r
+The header contains a ``trajectory_dimensions`` field. If the value of this field is larger than 0, it means that trajectories are stored with each individual acquisition. For a 2D acquisition, the ``trajectory_dimensions`` would typically be 2 and the convention (for memory layout) is that the header is followed immediately by the trajectory before the complex data. There is an example of how this memory layout could be implemented with a C++ class in the ``ismrmrd.h`` file:\r
+\r
+.. code:: c++\r
+\r
+   class Acquisition\r
+   {\r
+\r
+   //....\r
+\r
+   AcquisitionHeader head_; //Header, see above\r
+\r
+   float* traj_;            //Trajectory, elements = head_.trajectory_dimensions*head_.number_of_samples\r
+                            //   [kx,ky,kx,ky.....]        (for head_.trajectory_dimensions = 2)\r
+                            //   [kx,ky,kz,kx,ky,kz,.....] (for head_.trajectory_dimensions = 3)\r
+\r
+   float* data_;            //Actual data, elements = head_.number_of_samples*head_.active_channels*2\r
+                            //   [re,im,re,im,.....,re,im,re,im,.....,re,im,re,im,.....]\r
+                            //    ---channel 1-------channel 2---------channel 3-----\r
+\r
+   };\r
+\r
+This suggested memory layout is only a suggestion. The HDF5 interface (see below) can be used to read the data into many different data structures. In fact, the user can choose to read only part of the header or not read the data, etc.\r
+\r
+As mentioned above, the ISMRMRD format also suggests a way to store reconstructed images (or maybe image data used for calibration). An ``ImageHeader`` structure is defined in ``ismrmrd.h``:\r
+\r
+.. literalinclude:: ../../include/ismrmrd/ismrmrd.h\r
+   :start-after: typedef struct ISMRMRD_ImageHeader {\r
+   :end-before: } ISMRMRD_ImageHeader;\r
+\r
+In a similar fashion to the raw data acquisition data, the intention is to store a header followed by the image data. Since image data can be in several different format (e.g. float, complex, etc.), the memory layout is less well defined but can be described as:\r
+\r
+.. code:: c++\r
+\r
+  template <typename T> class Image {\r
+\r
+  ImageHeader head_;     //ImageHeader as defined above\r
+  T* data_;              //Data, array of size (matrix_size[0]*matrix_size[1]*matrix_size[2]*channels),\r
+                         //first spatial dimension is fastest changing array index, channels outer most (slowest changing).\r
+  };\r
+\r
+\r
+File Storage\r
+-------------\r
+\r
+The ISMRM Raw Data format is stored in HDF5 format. Details on this format can be found at the HDF5_ website. Briefly it is a hierarchical data format (much like a file system), which can contain multiple variable organized in groups (like folders in a file system). The variables can contain arrays of data values, custom defined structs, or simple text fields. It is the convention (but not a requirement) that the ISMRMRD datasets are stored in a group called ``/dataset``. The XML configuration is stored in the variable ``/dataset/xml`` and the data is stored in ``/dataset/data``. HDF5 files can be viewed with the HDFView application which is available on the HDF5 website for multiple platforms. Files can also be read directly in Matlab, in fact Matlab uses (since file format v7.3) HDF5 as its internal data format in the ``.mat`` files. As an example the data from an ISMRMRD file with name ``myfile.h5`` can be read in matlab with a command like:\r
+\r
+.. code:: matlab\r
+\r
+   >> data = h5read('simple_gre.h5', '/dataset/data');\r
+   >> data\r
+\r
+   data =\r
+\r
+   head: [1x1 struct]\r
+   traj: {1x1281 cell}\r
+   data: {1x1281 cell}\r
+\r
+    >> data.head\r
+\r
+    ans =\r
+\r
+                   version: [1x1281 uint16]\r
+                     flags: [1x1281 uint64]\r
+           measurement_uid: [1x1281 uint32]\r
+              scan_counter: [1x1281 uint32]\r
+    acquisition_time_stamp: [1x1281 uint32]\r
+     physiology_time_stamp: [3x1281 uint32]\r
+         number_of_samples: [1x1281 uint16]\r
+        available_channels: [1x1281 uint16]\r
+           active_channels: [1x1281 uint16]\r
+              channel_mask: [16x1281 uint64]\r
+               discard_pre: [1x1281 uint16]\r
+              discard_post: [1x1281 uint16]\r
+             center_sample: [1x1281 uint16]\r
+        encoding_space_ref: [1x1281 uint16]\r
+     trajectory_dimensions: [1x1281 uint16]\r
+            sample_time_us: [1x1281 single]\r
+                  position: [3x1281 single]\r
+                  read_dir: [3x1281 single]\r
+                 phase_dir: [3x1281 single]\r
+                 slice_dir: [3x1281 single]\r
+    patient_table_position: [3x1281 single]\r
+                       idx: [1x1 struct]\r
+                  user_int: [8x1281 int32]\r
+                user_float: [8x1281 single]\r
+\r
+    >>\r
+\r
+The HDF5 file format can be access from C, C++, and java using the libraries provided on the HDF5 website. The ISMRMRD distribution also comes with some C++ wrappers that can be used for easy access (read and write) from C++ programs. See below.\r
+\r
+In addition to storing acquisition data and images as defined by the headers above, the HDF5 format also enables storage of generic multi-dimensional arrays. The ISMRMRD format does not explicitly define how such data should be stored, but leaves it open for the user to add variables and data as dictated by a given application.\r
+\r
+.. _HDF5: http://www.hdfgroup.org/HDF5/\r
+\r
+C++ Support Library\r
+--------------------\r
+\r
+To enable easy prototyping of C++ software using the ISMRMRD data format, a simple C++ wrapper class is provided (defined in ``dataset.h``):\r
+\r
+.. literalinclude:: ../../include/ismrmrd/dataset.h\r
+   :start-after: //  ISMRMRD Dataset C++ Interface\r
+   :end-before: };\r
+\r
+Using this wrapper, C++ applications can be programmed as:\r
+\r
+.. code:: c++\r
+\r
+    // Open dataset\r
+    ISMRMRD::Dataset d(datafile.c_str(), "dataset", false);\r
+\r
+    std::string xml;\r
+    d.readHeader(xml);\r
+    ISMRMRD::IsmrmrdHeader hdr;\r
+    ISMRMRD::deserialize(xml.c_str(),hdr);\r
+\r
+    // Do something with the header\r
+\r
+    unsigned int number_of_acquisitions = d.getNumberOfAcquisitions();\r
+    ISMRMRD::Acquisition acq;\r
+    for (unsigned int i = 0; i < number_of_acquisitions; i++) {\r
+        // Read one acquisition at a time\r
+        d.readAcquisition(i, acq);\r
+\r
+        // Do something with the data\r
+    }\r
+\r
+Since the XML header is defined in the ``schema/ismrmrd.xsd`` file, it can be\r
+parsed with numerous xml parsing libraries. The ISMRMRD library includes an API\r
+that allows for programmatically deserializing, manipulating, and serializing the\r
+XML header. See the code in the ``utilities`` directory for examples of how to\r
+use the XML API.\r
+\r
+C++ Example Applications\r
+..........................\r
+\r
+The distribution includes two example applications, one that creates a simple 2D single-channel dataset from scratch and one that reconstructs this dataset (you need FFTW installed to compile these test applications). The data generation application looks like this (``generate_cartesian_shepp_logan.cpp``):\r
+\r
+.. literalinclude:: ../../utilities/generate_cartesian_shepp_logan.cpp\r
+   :start-after: // MAIN APPLICATION\r
+\r
+To reconstruct this synthetic dataset, you can use the test reconstruction application (``recon_cartesian_2d.cpp``):\r
+\r
+.. literalinclude:: ../../utilities/recon_cartesian_2d.cpp\r
+   :start-after: // MAIN APPLICATION\r
+\r
+\r
+Matlab Example Code and Datasets\r
+--------------------------------\r
+\r
+The ``examples`` folder contains some matlab code to illustrate simple interaction with the ISMRMRD data format. The examples use test data sets, wich can be downloaded from the Github website_. Go to the ``examples/data`` folder and type the following to download the data::\r
+\r
+  wget https://sourceforge.net/projects/ismrmrd/files/data/3D_partial_fourier.h5\r
+  wget https://sourceforge.net/projects/ismrmrd/files/data/simple_gre.h5\r
+  wget https://sourceforge.net/projects/ismrmrd/files/data/simple_spiral.h5\r
+\r
+For instance, to reconstruct a 2D Cartesian acquisition (10 image repetitions), type (from the ``examples/matlab`` folder):\r
+\r
+.. code:: matlab\r
+\r
+   >> images = simple_cartesian_recon('../data/simple_gre.h5');\r
+   Reconstructing image 1....done\r
+   Reconstructing image 2....done\r
+   Reconstructing image 3....done\r
+   Reconstructing image 4....done\r
+   Reconstructing image 5....done\r
+   Reconstructing image 6....done\r
+   Reconstructing image 7....done\r
+   Reconstructing image 8....done\r
+   Reconstructing image 9....done\r
+   Reconstructing image 10....done\r
+   >>\r
+\r
+You should see one of the reconstructed images display. An example is also given of a 3D acquisition with partial Fourier, phase and slice oversampling, etc. Reconstruct this dataset with:\r
+\r
+.. code:: matlab\r
+\r
+   >> images = simple_cartesian_recon('../data/3D_partial_fourier.h5');\r
+   Reconstructing image 1....done\r
+\r
+The center slice of the volume should be displayed at the end of the reconstruction.\r
+\r
+Finally, there is also a spiral dataset. This dataset illustrates how the flexible section of the ``<trajectoryDescription>`` can be used to add user defined parameters and an identifier to describe the trajectory. This dataset is also an example of storing the trajectory with the data for direct reconstruction. Reconstruct this dataset with:\r
+\r
+.. code:: matlab\r
+\r
+   >> images = simple_spiral_recon('../data/simple_spiral.h5');\r
+   Reconstructing image 1....done\r
+   Reconstructing image 2....done\r
+   Reconstructing image 3....done\r
+   Reconstructing image 4....done\r
+   Reconstructing image 5....done\r
+   Reconstructing image 6....done\r
+   Reconstructing image 7....done\r
+   Reconstructing image 8....done\r
+   Reconstructing image 9....done\r
+   Reconstructing image 10....done\r
+   >>\r
+\r
+Appendix\r
+---------\r
+\r
+XML Schema Definition\r
+......................\r
+\r
+.. _schema:\r
+\r
+.. literalinclude:: ../../schema/ismrmrd.xsd\r
+\r
+\r
+Indices and tables\r
+------------------\r
+\r
+* :ref:`genindex`\r
+* :ref:`modindex`\r
+* :ref:`search`\r
+\r
diff --git a/examples/c/CMakeLists.txt b/examples/c/CMakeLists.txt
new file mode 100644 (file)
index 0000000..778ab5a
--- /dev/null
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 2.8)
+project(ISMRMRD-C-EXAMPLE)
+
+# if building this example as a standalone project
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
+    if(NOT DEFINED ENV{ISMRMRD_HOME})
+        message(FATAL_ERROR "ISMRMRD_HOME environment variable must be defined")
+    endif()
+
+    list(APPEND CMAKE_MODULE_PATH "$ENV{ISMRMRD_HOME}/share/ismrmrd/cmake")
+
+    find_package(Ismrmrd REQUIRED)
+
+# otherwise, building it as part of ISMRMRD itself
+else()
+    set(ISMRMRD_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include")
+    set(ISMRMRD_LIBRARIES ismrmrd)
+endif()
+
+include_directories(${ISMRMRD_INCLUDE_DIR})
+add_executable(ismrmrd_c_example main.c)
+target_link_libraries(ismrmrd_c_example ${ISMRMRD_LIBRARIES})
+install(TARGETS ismrmrd_c_example DESTINATION bin)
diff --git a/examples/c/README.md b/examples/c/README.md
new file mode 100644 (file)
index 0000000..a20d781
--- /dev/null
@@ -0,0 +1,13 @@
+This is an example of a simple C project that is built on ISMRMRD.
+
+Instructions for building:
+
+1. Install ISMRMRD and CMake
+2. Define the environment variable ISMRMRD_HOME,
+   e.g. `export ISMRMRD_HOME=/usr/local/ismrmrd`
+3. Compile the example:
+
+        mkdir build
+        cmake ..
+        make
+        ./ismrmrd_c_example
diff --git a/examples/c/main.c b/examples/c/main.c
new file mode 100644 (file)
index 0000000..a2f265e
--- /dev/null
@@ -0,0 +1,192 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/dataset.h"
+
+void myerror(const char *file, int line, const char *func, int code, const char *msg) {
+    char *msgtype = ismrmrd_strerror(code);
+    fprintf(stderr, "Whoa! ERROR: %s in %s, line %d: %s\n", msgtype, file, line, msg);
+    exit(-1);
+}
+
+int main(void)
+{
+
+    /* Declarations */
+    int nacq_write, n, k, c;
+    ISMRMRD_Dataset dataset1;
+    ISMRMRD_Acquisition acq, acq2, acq3;
+    ISMRMRD_Dataset dataset2;
+    char *xmlstring;
+    ISMRMRD_Image im, im2;
+    uint32_t index;
+    uint64_t loc;
+    uint32_t nacq_read;
+    uint32_t numim;
+    const char *filename = "myfile.h5";
+    const char *groupname = "/dataset";
+    const char *xmlhdr = "Yo! This is some text for the header.";
+    const char *attr_string = "Yo! This is some text for the attribute string.";
+    ISMRMRD_NDArray arr, arr2;
+
+    /* Set the error handler */
+    ismrmrd_set_error_handler(myerror);
+
+    /************/
+    /* New File */
+    /************/
+    /* Create a data set */
+    ismrmrd_init_dataset(&dataset1, filename, groupname);
+    ismrmrd_open_dataset(&dataset1, true);
+
+    /* Write an XML header */
+    ismrmrd_write_header(&dataset1, xmlhdr);
+
+    /* Append some acquisitions */
+    nacq_write = 5;
+    for (n=0; n < nacq_write; n++) {
+        /* must initialize an acquisition before you can use it */
+        ismrmrd_init_acquisition(&acq);
+        acq.head.number_of_samples = 128;
+        acq.head.active_channels = 4;
+        for (k=0; k<acq.head.active_channels; k++) {
+            ismrmrd_set_channel_on(acq.head.channel_mask, k);
+        }
+        ismrmrd_make_consistent_acquisition(&acq);
+        for (k=0; k<acq.head.number_of_samples; k++) {
+            for (c=0; c<acq.head.active_channels; c++) {
+#ifdef _MSC_VER
+                /* Windows C compilers don't have a good complex type */
+                acq.data[k*acq.head.active_channels + c].real = n;
+                acq.data[k*acq.head.active_channels + c].imag = n;
+#else
+                acq.data[k*acq.head.active_channels + c] = n + I*n;
+#endif
+            }
+        }
+        if (n == 0) {
+            ismrmrd_set_flag(&(acq.head.flags), ISMRMRD_ACQ_FIRST_IN_SLICE);
+        }
+        else if (n == nacq_write-1) {
+            ismrmrd_set_flag(&(acq.head.flags), ISMRMRD_ACQ_LAST_IN_SLICE);
+        }
+        ismrmrd_append_acquisition(&dataset1, &acq);
+        ismrmrd_cleanup_acquisition(&acq);
+    }
+    
+    /* Close the dataset */
+    ismrmrd_close_dataset(&dataset1);
+
+    /************/
+    /* Old File */
+    /************/
+    /* Reopen the file as a different dataset */
+    ismrmrd_init_dataset(&dataset2, filename, groupname);
+    ismrmrd_open_dataset(&dataset2, false);
+
+    /* Read the header */
+    xmlstring = ismrmrd_read_header(&dataset2);
+    printf("Header: %s\n", xmlstring);
+    free(xmlstring);
+
+    /* Get the number of acquisitions */
+    nacq_read = ismrmrd_get_number_of_acquisitions(&dataset2);
+    printf("Number of Acquisitions: %u\n", nacq_read);
+
+    /* read the next to last one */
+    ismrmrd_init_acquisition(&acq2);
+    index = 0;
+    if (nacq_read>1) {
+        index = nacq_read - 1;
+    }
+    else {
+        index = 0;
+    }
+    printf("Acquisition index: %u\n", index);
+    ismrmrd_read_acquisition(&dataset2, index, &acq2);
+    printf("Number of samples: %u\n", acq2.head.number_of_samples);
+    printf("Flags: %llu\n", (unsigned long long)acq2.head.flags);
+    printf("Channel Mask[0]: %llu\n", (unsigned long long)acq2.head.channel_mask[0]);
+    printf("Channel Mask[1]: %llu\n", (unsigned long long)acq2.head.channel_mask[1]);
+    printf("Channel 3 is %d\n", ismrmrd_is_channel_on(acq2.head.channel_mask, 3));
+    printf("Channel 5 is %d\n", ismrmrd_is_channel_on(acq2.head.channel_mask, 5));
+    
+#ifdef _MSC_VER
+    /* Windows C compilers don't have a good complex type */
+    printf("Data 3: %f\t 2: %f\n", acq2.data[4].real, acq2.data[4].imag);
+#else
+    printf("Data[4]: %f, %f\n", creal(acq2.data[4]), cimag(acq2.data[4]));
+#endif
+    
+    ismrmrd_init_acquisition(&acq3);
+    ismrmrd_copy_acquisition(&acq3, &acq2);
+    
+    printf("Pointers 3: %p\t 2: %p\n", (void *) acq3.data, (void *) acq2.data);
+#ifdef _MSC_VER
+    /* Windows C compilers don't have a good complex type */
+    printf("Data 3: %f\t 2: %f\n", acq3.data[4].real, acq2.data[4].real);
+#else
+    printf("Data 3: %f\t 2: %f\n", creal(acq3.data[4]), creal(acq2.data[4]));
+#endif
+    ismrmrd_cleanup_acquisition(&acq2);
+    ismrmrd_cleanup_acquisition(&acq3);
+
+    /* Create and store an image */
+    ismrmrd_init_image(&im);
+    im.head.data_type = ISMRMRD_FLOAT;
+    im.head.matrix_size[0] = 256;
+    im.head.matrix_size[1] = 256;
+    im.head.matrix_size[2] = 4;
+    im.head.channels = 8;
+    /* Add an attribute string */
+    im.head.attribute_string_len = strlen(attr_string);
+    ismrmrd_make_consistent_image(&im);
+    memcpy(im.attribute_string, attr_string, im.head.attribute_string_len);
+    memset(im.data, 0, 256*256*4*8*sizeof(float));
+    
+    printf("Image Version: %d\n", im.head.version);
+    printf("Image String: %s\n", im.attribute_string);
+    ismrmrd_append_image(&dataset2, "testimages", &im);
+    for (loc=0; loc < 256*256*4*8; loc++) {
+        ((float*)im.data)[loc] = 2.0;
+    }
+    ismrmrd_append_image(&dataset2, "testimages", &im);
+    ismrmrd_cleanup_image(&im);
+
+    numim = ismrmrd_get_number_of_images(&dataset2, "testimages");
+    printf("Number of images stored = %d\n", numim);
+    
+    ismrmrd_init_image(&im2);
+    ismrmrd_read_image(&dataset2, "testimages", 1, &im2);
+    printf("Image 1 attribute string = %s\n", im2.attribute_string);
+    printf("Image 1 at position 10 has value = %f\n", ((float*)im2.data)[10]);
+    ismrmrd_cleanup_image(&im2);
+
+    /* Create and store an array */
+    ismrmrd_init_ndarray(&arr);
+    arr.data_type = ISMRMRD_FLOAT;
+    arr.ndim = 3;
+    arr.dims[0] = 256;
+    arr.dims[1] = 128;
+    arr.dims[2] = 4;
+    ismrmrd_make_consistent_ndarray(&arr);
+    for (loc=0; loc < 256*128*4; loc++) {
+        ((float*)arr.data)[loc] = 2.0;
+    }
+    ismrmrd_append_array(&dataset2, "testarray", &arr);
+    printf("Number of arrays stored = %d\n", ismrmrd_get_number_of_arrays(&dataset2, "testarray"));
+    ismrmrd_cleanup_ndarray(&arr);
+
+    /* Read it back in */
+    ismrmrd_init_ndarray(&arr2);
+    ismrmrd_read_array(&dataset2, "testarray", 0, &arr2);
+    printf("Array 2 at position 10 has value = %f\n", ((float*)arr2.data)[10]);
+    ismrmrd_cleanup_ndarray(&arr2);
+    
+    /* Close the dataset */
+    ismrmrd_close_dataset(&dataset2);
+
+    return 0;
+}
diff --git a/examples/matlab/simple_gridder.m b/examples/matlab/simple_gridder.m
new file mode 100644 (file)
index 0000000..cb65fb5
--- /dev/null
@@ -0,0 +1,100 @@
+function img = simple_gridder(co,data,weight,matrix_size)
+%
+%   img = simple_gridder(co,data,weight,matrix_size)
+%
+%   A very simple gridder written purely in Matlab. 
+%   Only tested for 2D
+%
+%   Input arguments:
+%     - co [N,2], k-space coordinates in the range [-0.5:0.5]
+%     - data, vector of N complex data points
+%     - weight, vector of N data weights (density compensation)
+%     - matrix_size, 2-element vector, e.g. [128 128]
+%
+%   Michael S. Hansen (michael.hansen@nih.gov), 2012
+%
+%
+
+over_sampling = 2.0;
+kernel_width = 8;
+kernel_samples = 100000;
+kernel_beta = 18.5547;
+matrix_size_oversampled = matrix_size*over_sampling;
+
+kernel_table = 1.0-(2.0*[-bitshift(kernel_samples,-1):(bitshift(kernel_samples,-1)-1)]/kernel_samples).^2;
+
+kernel_table(kernel_table<0) = 0;
+kernel_table=sqrt(kernel_table);
+kernel_table = bessi0(kernel_beta .* kernel_table);
+knorm = sum(kernel_table(:))/numel(kernel_table);
+kernel_table = kernel_table ./ knorm;
+
+grid_cells = ceil(co .* repmat(matrix_size_oversampled,size(co,1),1)-(kernel_width/2));
+kernel_idx = grid_cells-(co .* repmat(matrix_size_oversampled,size(co,1),1));
+grid_cells = uint32(grid_cells+repmat(bitshift(matrix_size_oversampled+kernel_width,-1),size(co,1),1));
+kernel_idx = uint32(floor(((kernel_idx + kernel_width/2)/kernel_width)*kernel_samples));
+kernel_step = uint32(floor(kernel_samples/kernel_width));
+
+%Calculate deapodization
+kern = -bitshift(kernel_width,-1):(-bitshift(kernel_width,-1)+kernel_width-1);
+kern = 1.0-(2.0*kern/kernel_width).^2;
+kern = bessi0(kernel_beta .* kern);
+kern = repmat(kern,kernel_width,1) .* repmat(kern',1,kernel_width);
+deapodization = zeros(matrix_size_oversampled);
+deapodization((1:kernel_width) + bitshift(matrix_size_oversampled(1)-kernel_width,-1), ...
+              (1:kernel_width) + bitshift(matrix_size_oversampled(2)-kernel_width,-1)) = kern;  
+deapodization = fftshift(ifftn(ifftshift(deapodization))) ./ sqrt(numel(deapodization));
+
+%Do convolution
+oversampled_grid_padded = zeros(matrix_size_oversampled+kernel_width);
+for y=1:(kernel_width),
+    for x=1:(kernel_width),       
+        oversampled_grid_padded = oversampled_grid_padded + ...
+            accumarray([grid_cells(:,1)+(x-1),grid_cells(:,2)+(y-1)], ...
+            weight(:).*data(:).*(kernel_table(kernel_idx(:,1)+(x-1)*kernel_step+1).*kernel_table(kernel_idx(:,2)+(y-1).*kernel_step+1))', ...
+            matrix_size_oversampled+kernel_width);
+    end
+end
+
+osps = size(oversampled_grid_padded);
+oss = matrix_size_oversampled;
+
+%Let's just get rid of the padding, it should really be folded in
+oversampled_grid = oversampled_grid_padded(bitshift(osps(1)-oss(1),-1):bitshift(osps(1)-oss(1),-1)+matrix_size_oversampled(1)-1, ...
+                                           bitshift(osps(2)-oss(2),-1):bitshift(osps(1)-oss(2),-1)+matrix_size_oversampled(2)-1); 
+
+%FFT to image space                                       
+img = fftshift(ifftn(ifftshift(oversampled_grid))) ./ sqrt(numel(oversampled_grid));
+
+%Deapodization
+img = img ./ deapodization;
+
+%Remove oversampling
+img = img((1:matrix_size(1))+bitshift(matrix_size_oversampled(1)-matrix_size(1),-1), ...
+          (1:matrix_size(2))+bitshift(matrix_size_oversampled(2)-matrix_size(2),-1));
+
+%TODO
+% - fold padding back in
+
+return
+
+function ans = bessi0(x)
+
+ax = abs(x);
+ans = zeros(size(x));
+
+% ax<3.75
+k = find(ax<3.75);
+y=x(k)./3.75;
+y=y.^2;
+ans(k)=1.0+y.*(3.5156229+y.*(3.0899424+y.*(1.2067492 ...
+    +y.*(0.2659732+y.*(0.360768e-1+y.*0.45813e-2)))));
+
+% ax>=3.75
+k = find(ax>=3.75);
+y=3.75./ax(k);
+ans(k)=(exp(ax(k))./sqrt(ax(k))).*(0.39894228+y.*(0.1328592e-1 ...
+    +y.*(0.225319e-2+y.*(-0.157565e-2+y.*(0.916281e-2 ...
+    +y.*(-0.2057706e-1+y.*(0.2635537e-1+y.*(-0.1647633e-1 ...
+    +y.*0.392377e-2))))))));
+return
\ No newline at end of file
diff --git a/examples/matlab/simple_spiral_recon.m b/examples/matlab/simple_spiral_recon.m
new file mode 100644 (file)
index 0000000..42bd8b2
--- /dev/null
@@ -0,0 +1,82 @@
+function [images] = simple_spiral_recon(ismrmrdfile)
+%
+%   [images] = simple_spiral_recon(ismrmrdfile)
+%
+%   Simple example of how to reconstruct a 2D spiral dataset stored
+%   in ISMRMRD dataformat
+%
+%   Michael S. Hansen (michael.hansen@nih.gov), 2012
+%
+
+dset = ismrmrd.Dataset(ismrmrdfile);
+header = ismrmrd.xml.deserialize(dset.readxml());
+
+%Is this a spiral acquisition
+if (~strcmp(header.encoding.trajectory,'spiral')),
+   error('This is not a spiral dataset'); 
+end
+
+%Let's get the matrix size
+matrix_size = [header.encoding.encodedSpace.matrixSize.x, ...
+               header.encoding.encodedSpace.matrixSize.y];
+
+%Let's load the data
+raw_data = dset.readAcquisition();  % read all the acquisitions
+
+interleaves = max(raw_data.head.idx.kspace_encode_step_1)+1;
+repetitions = max(raw_data.head.idx.repetition)+1;
+samples = max(raw_data.head.number_of_samples);
+samples_to_skip_end = max(raw_data.head.discard_post);
+samples_to_skip_start = max(raw_data.head.discard_pre);
+net_samples = samples-samples_to_skip_end-samples_to_skip_start;
+channels = max(raw_data.head.active_channels);
+
+trajectory = zeros(2,net_samples,interleaves);
+data = zeros(net_samples,interleaves,channels);
+
+images = [];
+
+counter = 0;
+for p=1:length(raw_data.head.flags),
+
+   %if this is noise, we will skip it
+   if raw_data.head.flagIsSet('ACQ_IS_NOISE_MEASUREMENT',p) 
+      continue; 
+   end
+   
+   d = raw_data.data{p};
+   t = raw_data.traj{p};
+   current_interleave = raw_data.head.idx.kspace_encode_step_1(p)+1;
+   start_sample = samples_to_skip_start+1;
+   end_sample = samples-samples_to_skip_end;
+
+   data(:,current_interleave,:) = reshape(d(start_sample:end_sample,:), net_samples, 1, channels);
+   trajectory(:,:,current_interleave) = t(:,start_sample:end_sample);
+
+   %Is this the last in slice? We should make an image
+   if raw_data.head.flagIsSet('ACQ_LAST_IN_SLICE',p) 
+
+      fprintf('Reconstructing image %d....', counter+1); 
+      co = permute(trajectory(:,:),[2 1]);
+
+      %Some simple density compensation
+      k = complex(co(:,1),co(:,2));
+      g = complex(diff(co(:,1)),diff(co(:,2)));
+      g(end+1) = g(end);
+      weights = abs(g(:)) .* abs(sin(angle(g(:))-angle(k(:)))); %Estimating weights from Meyer et al. Magn Reson Med. 1992 Dec;28(2):202-13.
+      
+      %Loop over coils and grid images
+      for c=1:size(data,3), 
+           img(:,:,c) = simple_gridder(co,data(:,:,c),weights, matrix_size); 
+      end;
+      images(:,:,counter+1) = sqrt(sum(abs(img).^2,3)); %RMS coil combination
+      counter = counter + 1;
+      fprintf('done\n'); 
+   end
+   
+end
+
+%Let's just display the first image
+imagesc(images(:,:,1));colormap(gray);axis image;
+
+return
diff --git a/examples/matlab/test_create_dataset.m b/examples/matlab/test_create_dataset.m
new file mode 100644 (file)
index 0000000..4b230f7
--- /dev/null
@@ -0,0 +1,136 @@
+%% Generating a simple ISMRMRD data set
+
+% This is an example of how to construct a datset from synthetic data
+% simulating a fully sampled acquisition on a cartesian grid.
+% data from 4 coils from a single slice object that looks like a square
+
+% File Name
+filename = 'testdata.h5';
+
+% Create an empty ismrmrd dataset
+if exist(filename,'file')
+    error(['File ' filename ' already exists.  Please remove first'])
+end
+dset = ismrmrd.Dataset(filename);
+
+% Synthesize the object
+nX = 256;
+nY = 256;
+rho = zeros(nX,nY);
+indxstart = floor(nX/4)+1;
+indxend   = floor(3*nX/4);
+indystart = floor(nY/4)+1;
+indyend   = floor(3*nY/4);
+rho(indxstart:indxend,indystart:indyend) = 1;
+
+% Synthesize some coil sensitivities
+[X,Y] = ndgrid((0:nX-1)/nX/2.0 - 0.5, (0:nY-1)/nY/2.0 - 0.5);
+C = zeros(nX,nY,4);
+C(:,:,1) = exp(-((X-.5).^2 + (Y).^2)    + 1i*(X-.5));
+C(:,:,2) = exp(-((X+.5).^2 + (Y).^2)    - 1i*(X+.5));
+C(:,:,3) = exp(-((X).^2    + (Y-.5).^2) + 1i*(Y-.5));
+C(:,:,4) = exp(-((X).^2    + (Y+.5).^2) - 1i*(Y+.5));
+nCoils = size(C,3);
+
+% Synthesize the k-space data
+nReps = 5;
+noiselevel = 0.05;
+K = zeros(nX, nY, nCoils, nReps);
+for rep = 1:nReps
+    for coil = 1:nCoils
+        noise = noiselevel * (randn(nX,nY)+1j*randn(nX,nY));
+        K(:,:,coil,rep) = fftshift(fft2(fftshift( C(:,:,coil).*rho + noise)));
+    end
+end
+
+% It is very slow to append one acquisition at a time, so we're going
+% to append a block of acquisitions at a time.
+% In this case, we'll do it one repetition at a time to show off this
+% feature.  Each block has nY aquisitions
+acqblock = ismrmrd.Acquisition(nY);
+
+% Set the header elements that don't change
+acqblock.head.version(:) = 1;
+acqblock.head.number_of_samples(:) = nX;
+acqblock.head.center_sample(:) = floor(nX/2);
+acqblock.head.active_channels(:) = nCoils;
+acqblock.head.read_dir  = repmat([1 0 0]',[1 nY]);
+acqblock.head.phase_dir = repmat([0 1 0]',[1 nY]);
+acqblock.head.slice_dir = repmat([0 0 1]',[1 nY]);
+
+% Loop over the acquisitions, set the header, set the data and append
+for rep = 1:nReps
+    for acqno = 1:nY
+        
+        % Set the header elements that change from acquisition to the next
+        % c-style counting
+        acqblock.head.scan_counter(acqno) = (rep-1)*nY + acqno-1;
+        acqblock.head.idx.kspace_encode_step_1(acqno) = acqno-1; 
+        acqblock.head.idx.repetition(acqno) = rep - 1;
+        
+        % Set the flags
+        acqblock.head.flagClearAll(acqno);
+        if acqno == 1
+            acqblock.head.flagSet('ACQ_FIRST_IN_ENCODE_STEP1', acqno);
+            acqblock.head.flagSet('ACQ_FIRST_IN_SLICE', acqno);
+            acqblock.head.flagSet('ACQ_FIRST_IN_REPETITION', acqno);
+        elseif acqno==size(K,2)
+            acqblock.head.flagSet('ACQ_LAST_IN_ENCODE_STEP1', acqno);
+            acqblock.head.flagSet('ACQ_LAST_IN_SLICE', acqno);
+            acqblock.head.flagSet('ACQ_LAST_IN_REPETITION', acqno);
+        end
+        
+        % fill the data
+        acqblock.data{acqno} = squeeze(K(:,acqno,:,rep));
+    end
+
+    % Append the acquisition block
+    dset.appendAcquisition(acqblock);
+        
+end % rep loop
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%
+%% Fill the xml header %
+%%%%%%%%%%%%%%%%%%%%%%%%
+% We create a matlab struct and then serialize it to xml.
+% Look at the xml schema to see what the field names should be
+
+header = [];
+
+% Experimental Conditions (Required)
+header.experimentalConditions.H1resonanceFrequency_Hz = 128000000; % 3T
+
+% Acquisition System Information (Optional)
+header.acquisitionSystemInformation.systemVendor = 'ISMRMRD Labs';
+header.acquisitionSystemInformation.systemModel = 'Virtual Scanner';
+header.acquisitionSystemInformation.receiverChannels = nCoils;
+
+% The Encoding (Required)
+header.encoding.trajectory = 'cartesian';
+header.encoding.encodedSpace.fieldOfView_mm.x = 256;
+header.encoding.encodedSpace.fieldOfView_mm.y = 256;
+header.encoding.encodedSpace.fieldOfView_mm.z = 5;
+header.encoding.encodedSpace.matrixSize.x = size(K,1);
+header.encoding.encodedSpace.matrixSize.y = size(K,2);
+header.encoding.encodedSpace.matrixSize.z = 1;
+% Recon Space
+% (in this case same as encoding space)
+header.encoding.reconSpace = header.encoding.encodedSpace;
+% Encoding Limits
+header.encoding.encodingLimits.kspace_encoding_step_0.minimum = 0;
+header.encoding.encodingLimits.kspace_encoding_step_0.maximum = size(K,1)-1;
+header.encoding.encodingLimits.kspace_encoding_step_0.center = floor(size(K,1)/2);
+header.encoding.encodingLimits.kspace_encoding_step_1.minimum = 0;
+header.encoding.encodingLimits.kspace_encoding_step_1.maximum = size(K,2)-1;
+header.encoding.encodingLimits.kspace_encoding_step_1.center = floor(size(K,2)/2);
+header.encoding.encodingLimits.repetition.minimum = 0;
+header.encoding.encodingLimits.repetition.maximum = nReps-1;
+header.encoding.encodingLimits.repetition.center = 0;
+
+%% Serialize and write to the data set
+xmlstring = ismrmrd.xml.serialize(header);
+dset.writexml(xmlstring);
+
+%% Write the dataset
+dset.close();
diff --git a/examples/matlab/test_create_undersampled_dataset.m b/examples/matlab/test_create_undersampled_dataset.m
new file mode 100644 (file)
index 0000000..e55a60f
--- /dev/null
@@ -0,0 +1,192 @@
+%% Generating a simple ISMRMRD data set to simulate GRAPPA undersampling
+% Based on test_create_dataset
+%
+% This is an example of how to construct a datset from synthetic data
+% simulating an under sampled acquisition on a cartesian grid with a 
+% central fully sampled (ACS) region.
+% Data from 4 coils from a single slice object with 6 repetitions.
+%
+% Reconstruction using the gadgetron configuration file 
+% Generic_Cartesian_Grappa.xml may require changes to cope with the small 
+% number of coils:
+%  <property><name>upstream_coil_compression_thres</name><value>0.00001</value></property>
+%  <property><name>downstream_coil_compression_thres</name><value>0.0001</value></property>
+%  <property><name>use_constant_scalingFactor</name><value>false</value></property>
+
+% Output file Name
+def_filename = 'testusdatamri.h5';
+FilterSpec = '*.h5' ;
+DialogTitle = 'Output h5 filename' ;
+
+[FileName,PathName,FilterIndex] = uiputfile(FilterSpec,DialogTitle,def_filename);
+filename = fullfile(PathName,FileName) ;
+
+dset = ismrmrd.Dataset(filename);
+
+% Synthesize the object
+% nY here corresponds to fully sampled data (256), nYsamp is number of actually
+% sampled lines (128 + additional central ACS lines)
+nX = 256;
+nY = 256;
+rho = zeros(nX,nY);
+indxstart = floor(nX/4)+1;
+indxend   = floor(3*nX/4);
+indystart = floor(nY/4)+1;
+indyend   = floor(3*nY/4);
+% put an MR image in the centre
+load mri % loads D - an example 128 128 1 27 dataset
+rho(indxstart:indxend,indystart:indyend) = squeeze(double(D(:,:,1,12))) ;
+
+
+% Synthesize some coil sensitivities
+[X,Y] = ndgrid((0:nX-1)/nX/2.0 - 0.5, (0:nY-1)/nY/2.0 - 0.5);
+C = zeros(nX,nY,4);
+C(:,:,1) = exp(-((X-.5).^2 + (Y).^2)    + 1i*(X-.5));
+C(:,:,2) = exp(-((X+.5).^2 + (Y).^2)    - 1i*(X+.5));
+C(:,:,3) = exp(-((X).^2    + (Y-.5).^2) + 1i*(Y-.5));
+C(:,:,4) = exp(-((X).^2    + (Y+.5).^2) - 1i*(Y+.5));
+nCoils = size(C,3);
+if exist('eshow','file')
+    eshow(C)  % displays coil sensitivities
+end
+
+% set ACS lines for GRAPPA simulation (fully sampled central k-space
+% region)
+ACShw = 14 ; % GRAPPA ACS half width i.e. here 28 lines are ACS
+Ysamp_u = [1:2:nY] ; % undersampling by every alternate line
+Ysamp_ACS = [nY/2-ACShw+1 : nY/2+ACShw] ; % GRAPPA autocalibration lines
+Ysamp = union(Ysamp_u, Ysamp_ACS) ; % actually sampled lines
+nYsamp = length(Ysamp) ; % number of actually sampled
+
+% Ysamp indexes the actually sampled lines to the encoded k-space line number. 
+% For example, if there were just regular factor 2 undersampling 
+% (with no ACS lines), Ysamp would have length 128 and be [1 3 5 ... 255].
+% With ACS lines, the elements of Ysamp are separated by 2 near the k-space
+% edges, and by 1 in the central ACS region.
+
+
+% Synthesize the k-space data
+nReps = 6  % increased from 4 to 6 to avoid confusion with number of coils.
+noiselevel = 0.05;
+K = zeros(nX, nYsamp, nCoils, nReps);
+for rep = 1:nReps
+    for coil = 1:nCoils
+        noise = noiselevel * (randn(nX,nY)+1j*randn(nX,nY));
+        ksp = fftshift(fft2(fftshift( C(:,:,coil).*rho + noise))); 
+        K(:,:,coil,rep) = ksp(:,Ysamp);
+    end
+end
+
+% It is very slow to append one acquisition at a time, so we're going
+% to append a block of acquisitions at a time.
+% In this case, we'll do it one repetition at a time to show off this
+% feature.  Each block has nYsamp aquisitions
+acqblock = ismrmrd.Acquisition(nYsamp);
+
+% Set the header elements that don't change
+acqblock.head.version(:) = 1;
+acqblock.head.number_of_samples(:) = nX;
+acqblock.head.center_sample(:) = floor(nX/2);
+acqblock.head.active_channels(:) = nCoils;
+acqblock.head.read_dir  = repmat([1 0 0]',[1 nYsamp]);
+acqblock.head.phase_dir = repmat([0 1 0]',[1 nYsamp]);
+acqblock.head.slice_dir = repmat([0 0 1]',[1 nYsamp]);
+
+% Loop over the acquisitions, set the header, set the data and append
+for rep = 1:nReps
+    for acqno = 1:nYsamp
+        
+        % Set the header elements that change from acquisition to the next
+        % c-style counting
+        acqblock.head.scan_counter(acqno) = (rep-1)*nYsamp + acqno-1;
+        % Note next entry is k-space encoded line number (not acqno which
+        % is just the sequential acquisition number)
+        acqblock.head.idx.kspace_encode_step_1(acqno) = Ysamp(acqno)-1; 
+        acqblock.head.idx.repetition(acqno) = rep - 1;
+        
+        % Set the flags
+        acqblock.head.flagClearAll(acqno);
+        if acqno == 1
+            acqblock.head.flagSet('ACQ_FIRST_IN_ENCODE_STEP1', acqno);
+            acqblock.head.flagSet('ACQ_FIRST_IN_SLICE', acqno);
+            acqblock.head.flagSet('ACQ_FIRST_IN_REPETITION', acqno);
+        elseif acqno==size(K,2)
+            acqblock.head.flagSet('ACQ_LAST_IN_ENCODE_STEP1', acqno);
+            acqblock.head.flagSet('ACQ_LAST_IN_SLICE', acqno);
+            acqblock.head.flagSet('ACQ_LAST_IN_REPETITION', acqno);
+        end
+        
+        if ismember(Ysamp(acqno),Ysamp_ACS)
+            if ismember(Ysamp(acqno),Ysamp_u)
+                % both calibration and part of the undersampled pattern
+                acqblock.head.flagSet('ACQ_IS_PARALLEL_CALIBRATION_AND_IMAGING', acqno)
+            else
+                % in ACS block but not part of the regular undersampling
+                % pattern Ysamp_u
+                acqblock.head.flagSet('ACQ_IS_PARALLEL_CALIBRATION', acqno) ;
+            end
+        end
+        
+        % fill the data
+        acqblock.data{acqno} = squeeze(K(:,acqno,:,rep));
+    end
+
+    % Append the acquisition block
+    dset.appendAcquisition(acqblock);
+        
+end % rep loop
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%
+%% Fill the xml header %
+%%%%%%%%%%%%%%%%%%%%%%%%
+% We create a matlab struct and then serialize it to xml.
+% Look at the xml schema to see what the field names should be
+
+header = [];
+
+% Experimental Conditions (Required)
+header.experimentalConditions.H1resonanceFrequency_Hz = 128000000; % 3T
+
+% Acquisition System Information (Optional)
+header.acquisitionSystemInformation.systemVendor = 'ISMRMRD Labs';
+header.acquisitionSystemInformation.systemModel = 'Virtual Scanner';
+header.acquisitionSystemInformation.receiverChannels = nCoils;
+
+% The Encoding (Required)
+header.encoding.trajectory = 'cartesian';
+header.encoding.encodedSpace.fieldOfView_mm.x = 256;
+header.encoding.encodedSpace.fieldOfView_mm.y = 256;
+header.encoding.encodedSpace.fieldOfView_mm.z = 5;
+header.encoding.encodedSpace.matrixSize.x = size(K,1);
+header.encoding.encodedSpace.matrixSize.y = nY;
+header.encoding.encodedSpace.matrixSize.z = 1;
+% Recon Space
+% (in this case same as encoding space)
+header.encoding.reconSpace = header.encoding.encodedSpace;
+% Encoding Limits
+header.encoding.encodingLimits.kspace_encoding_step_0.minimum = 0;
+header.encoding.encodingLimits.kspace_encoding_step_0.maximum = size(K,1)-1;
+header.encoding.encodingLimits.kspace_encoding_step_0.center = floor(size(K,1)/2);
+header.encoding.encodingLimits.kspace_encoding_step_1.minimum = 0;
+header.encoding.encodingLimits.kspace_encoding_step_1.maximum = nY-1;
+header.encoding.encodingLimits.kspace_encoding_step_1.center = floor(nY/2);
+header.encoding.encodingLimits.repetition.minimum = 0;
+header.encoding.encodingLimits.repetition.maximum = nReps-1;
+header.encoding.encodingLimits.repetition.center = 0;
+
+header.encoding.parallelImaging.accelerationFactor.kspace_encoding_step_1 = 2 ;
+header.encoding.parallelImaging.accelerationFactor.kspace_encoding_step_2 = 1 ;
+header.encoding.parallelImaging.calibrationMode = 'embedded' ;
+
+% Commented code below appears not necessary - saw this parameter after converting
+% a scanner file using siemens_to_ismrmrd
+% header.userParameters.userParameterLong.name = 'EmbeddedRefLinesE1' ;
+% header.userParameters.userParameterLong.value = ACShw *2  ;
+
+%% Serialize and write to the data set
+xmlstring = ismrmrd.xml.serialize(header);
+dset.writexml(xmlstring);
+
+%% Write the dataset
+dset.close();
diff --git a/examples/matlab/test_recon_dataset.m b/examples/matlab/test_recon_dataset.m
new file mode 100644 (file)
index 0000000..6a6a6f7
--- /dev/null
@@ -0,0 +1,165 @@
+%% Working with an existing ISMRMRD data set
+
+% This is a simple example of how to reconstruct images from data
+% acquired on a fully sampled cartesian grid
+%
+% Capabilities:
+%   2D/3D
+%   multiple slices/slabs
+%   multiple contrasts, repetitions
+%   
+% Limitations:
+%   only works with a single encoded space
+%   fully sampled k-space (no partial fourier or undersampling)
+%   multiple repetitions
+%   doesn't handle averages, phases, segments and sets
+%   ignores noise scans (no pre-whitening)
+% 
+
+% We first create a data set using the example program like this:
+%   ismrmrd_generate_cartesian_shepp_logan -r 5 -C -o shepp-logan.h5
+% This will produce the file shepp-logan.h5 containing an ISMRMRD
+% dataset sampled evenly on the k-space grid -128:0.5:127.5 x -128:127
+% (i.e. oversampled by a factor of 2 in the readout direction)
+% with 8 coils, 5 repetitions and a noise level of 0.5
+% with a noise calibration scan at the beginning
+%
+%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
+% Loading an existing file %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%% 
+filename = 'testdata.h5';
+if exist(filename, 'file')
+    dset = ismrmrd.Dataset(filename, 'dataset');
+else
+    error(['File ' filename ' does not exist.  Please generate it.'])
+end
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read some fields from the XML header %%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% We need to check if optional fields exists before trying to read them
+
+hdr = ismrmrd.xml.deserialize(dset.readxml);
+
+%% Encoding and reconstruction information
+% Matrix size
+enc_Nx = hdr.encoding.encodedSpace.matrixSize.x;
+enc_Ny = hdr.encoding.encodedSpace.matrixSize.y;
+enc_Nz = hdr.encoding.encodedSpace.matrixSize.z;
+rec_Nx = hdr.encoding.reconSpace.matrixSize.x;
+rec_Ny = hdr.encoding.reconSpace.matrixSize.y;
+rec_Nz = hdr.encoding.reconSpace.matrixSize.z;
+
+% Field of View
+enc_FOVx = hdr.encoding.encodedSpace.fieldOfView_mm.x;
+enc_FOVy = hdr.encoding.encodedSpace.fieldOfView_mm.y;
+enc_FOVz = hdr.encoding.encodedSpace.fieldOfView_mm.z;
+rec_FOVx = hdr.encoding.reconSpace.fieldOfView_mm.x;
+rec_FOVy = hdr.encoding.reconSpace.fieldOfView_mm.y;
+rec_FOVz = hdr.encoding.reconSpace.fieldOfView_mm.z;
+
+% Number of slices, coils, repetitions, contrasts etc.
+% We have to wrap the following in a try/catch because a valid xml header may
+% not have an entry for some of the parameters
+
+try
+  nSlices = hdr.encoding.encodingLimits.slice.maximum + 1;
+catch
+    nSlices = 1;
+end
+
+try 
+    nCoils = hdr.acquisitionSystemInformation.receiverChannels;
+catch
+    nCoils = 1;
+end
+
+try
+    nReps = hdr.encoding.encodingLimits.repetition.maximum + 1;
+catch
+    nReps = 1;
+end
+
+try
+    nContrasts = hdr.encoding.encodingLimits.contrast.maximum + 1 + 1;
+catch
+    nContrasts = 1;
+end
+
+% TODO add the other possibilites
+
+%% Read all the data
+% Reading can be done one acquisition (or chunk) at a time, 
+% but this is much faster for data sets that fit into RAM.
+D = dset.readAcquisition();
+
+% Note: can select a single acquisition or header from the block, e.g.
+% acq = D.select(5);
+% hdr = D.head.select(5);
+% or you can work with them all together
+
+%% Ignore noise scans
+% TODO add a pre-whitening example
+% Find the first non-noise scan
+% This is how to check if a flag is set in the acquisition header
+isNoise = D.head.flagIsSet('ACQ_IS_NOISE_MEASUREMENT');
+firstScan = find(isNoise==0,1,'first');
+if firstScan > 1
+    noise = D.select(1:firstScan-1);
+else
+    noise = [];
+end
+meas  = D.select(firstScan:D.getNumber);
+clear D;
+
+%% Reconstruct images
+% Since the entire file is in memory we can use random access
+% Loop over repetitions, contrasts, slices
+reconImages = {};
+nimages = 0;
+for rep = 1:nReps
+    for contrast = 1:nContrasts
+        for slice = 1:nSlices
+            % Initialize the K-space storage array
+            K = zeros(enc_Nx, enc_Ny, enc_Nz, nCoils);
+            % Select the appropriate measurements from the data
+            acqs = find(  (meas.head.idx.contrast==(contrast-1)) ...
+                        & (meas.head.idx.repetition==(rep-1)) ...
+                        & (meas.head.idx.slice==(slice-1)));
+            for p = 1:length(acqs)
+                ky = meas.head.idx.kspace_encode_step_1(acqs(p)) + 1;
+                kz = meas.head.idx.kspace_encode_step_2(acqs(p)) + 1;
+                K(:,ky,kz,:) = meas.data{acqs(p)};
+            end
+            % Reconstruct in x
+            K = fftshift(ifft(fftshift(K,1),[],1),1);
+            % Chop if needed
+            if (enc_Nx == rec_Nx)
+                im = K;
+            else
+                ind1 = floor((enc_Nx - rec_Nx)/2)+1;
+                ind2 = floor((enc_Nx - rec_Nx)/2)+rec_Nx;
+                im = K(ind1:ind2,:,:,:);
+            end
+            % Reconstruct in y then z
+            im = fftshift(ifft(fftshift(im,2),[],2),2);
+            if size(im,3)>1
+                im = fftshift(ifft(fftshift(im,3),[],3),3);
+            end
+            
+            % Combine SOS across coils
+            im = sqrt(sum(abs(im).^2,4));
+            
+            % Append
+            nimages = nimages + 1;
+            reconImages{nimages} = im;
+        end
+    end
+end
+
+%% Display the first image
+figure
+colormap gray
+imagesc(reconImages{1}); axis image; axis off; colorbar;
diff --git a/include/ismrmrd/dataset.h b/include/ismrmrd/dataset.h
new file mode 100644 (file)
index 0000000..127a863
--- /dev/null
@@ -0,0 +1,176 @@
+/* ISMRMRD Data Set */
+
+/**
+ * @file dataset.h
+ */
+
+#pragma once
+#ifndef ISMRMRD_DATASET_H
+#define ISMRMRD_DATASET_H
+
+#include "ismrmrd/ismrmrd.h"
+#include <hdf5.h>
+
+#ifdef __cplusplus
+#include <string>
+namespace ISMRMRD {
+extern "C" {
+#endif
+
+/**
+ *   Interface for accessing an ISMRMRD Data Set stored on disk in HDF5 format.
+ *
+ *   A given ISMRMRD dataset if assumed to be stored under one group name in the
+ *   HDF5 file.  To make the datasets consistent, this library enforces that the
+ *   XML configuration is stored in the variable groupname/xml and the
+ *   Acquisitions are stored in the variable groupname/data.
+ *
+ */
+typedef struct ISMRMRD_Dataset {
+    char *filename;
+    char *groupname;
+    hid_t fileid;
+} ISMRMRD_Dataset;
+
+/**
+ * Initializes an ISMRMRD dataset structure
+ *
+ */
+EXPORTISMRMRD int ismrmrd_init_dataset(ISMRMRD_Dataset *dset, const char *filename, const char *groupname);
+            
+/**
+ * Opens an ISMRMRD dataset.
+ *
+ */
+EXPORTISMRMRD int ismrmrd_open_dataset(ISMRMRD_Dataset *dset, const bool create_if_neded);
+
+/**
+ * Closes all references to the underlying HDF5 file.
+ *
+ */
+EXPORTISMRMRD int ismrmrd_close_dataset(ISMRMRD_Dataset *dset);
+
+/**
+ *  Writes the XML header string to the dataset.
+ *
+ *  @warning There is no check of whether the string is a valid XML document at this point.
+ *
+ */
+EXPORTISMRMRD int ismrmrd_write_header(const ISMRMRD_Dataset *dset, const char *xmlstring);
+
+/**
+ *  Reads the XML configuration header from the dataset.
+ *
+ *  @warning There is no check of whether the string is a valid XML document at this point.
+ *
+ */
+EXPORTISMRMRD char * ismrmrd_read_header(const ISMRMRD_Dataset *dset);
+
+/**
+ *  Appends and NMR/MRI acquisition to the dataset.
+ *
+ *  Please consult @See ISMRMRD_Acquisition struct for details.
+ */
+EXPORTISMRMRD int ismrmrd_append_acquisition(const ISMRMRD_Dataset *dset, const ISMRMRD_Acquisition *acq);
+
+/**
+ *  Reads the acquisition with the specified index from the dataset.
+ */
+EXPORTISMRMRD int ismrmrd_read_acquisition(const ISMRMRD_Dataset *dset, uint32_t index, ISMRMRD_Acquisition *acq);
+
+/**
+ *  Return the number of acquisitions in the dataset.
+ */
+EXPORTISMRMRD uint32_t ismrmrd_get_number_of_acquisitions(const ISMRMRD_Dataset *dset);
+
+/**
+ *  Appends an Image to the variable named varname in the dataset.
+ *
+ *  Please consult @See ISMRMRD_Image struct for details.
+ *
+ *  Headers and attribute strings are stored separately from the data.
+ *  This allows for easy viewing and reading in other applications.
+ *
+ *  Images of the same size can be appended to "grow" an array.
+ *    e.g. 20 images of size (256, 256, 4, 16), i.e. 4 slice and 16 channels, can be appended
+ *    one at a time to make a (256, 256, 4, 16, 20) array in the file.
+ *
+ */
+EXPORTISMRMRD int ismrmrd_append_image(const ISMRMRD_Dataset *dset, const char *varname,
+                                       const ISMRMRD_Image *im);
+
+/**
+ *   Reads an image stored with appendImage.
+ *   The index indicates which image to read from the variable named varname.
+ */
+EXPORTISMRMRD int ismrmrd_read_image(const ISMRMRD_Dataset *dset, const char *varname,
+                                     const uint32_t index, ISMRMRD_Image *im);
+
+/**
+ *  Return the number of images in the variable varname in the dataset.
+ */
+EXPORTISMRMRD uint32_t ismrmrd_get_number_of_images(const ISMRMRD_Dataset *dset, const char *varname);
+
+/**
+ *  Appends an NDArray to the variable named varname in the dataset.
+ *
+ *  Please consult @See NDArray struct for details.
+ *
+ *  Arrays contain simple data elements such as float, double, etc.
+ *
+ *  Arrays of the same size can be appended to "grow" an array,
+ *    e.g. 3D arrays of size (K,L,M) can be appended to grow a 4D array of size (K,L,M,N).
+ *
+ */
+EXPORTISMRMRD int ismrmrd_append_array(const ISMRMRD_Dataset *dset, const char *varname,
+                                       const ISMRMRD_NDArray *arr);
+
+/**
+ *  Reads an array from the data file.
+ */
+EXPORTISMRMRD int ismrmrd_read_array(const ISMRMRD_Dataset *dataset, const char *varname,
+                                     const uint32_t index, ISMRMRD_NDArray *arr);
+
+/**
+ *  Return the number of arrays in the variable varname in the dataset.
+ */
+EXPORTISMRMRD uint32_t ismrmrd_get_number_of_arrays(const ISMRMRD_Dataset *dset, const char *varname);
+
+    
+#ifdef __cplusplus
+} /* extern "C" */
+
+//  ISMRMRD Dataset C++ Interface
+class EXPORTISMRMRD Dataset {
+public:
+    // Constructor and destructor
+    Dataset(const char* filename, const char* groupname, bool create_file_if_needed = true);
+    ~Dataset();
+    
+    // Methods
+    // XML Header
+    void writeHeader(const std::string &xmlstring);
+    void readHeader(std::string& xmlstring);
+    // Acquisitions
+    void appendAcquisition(const Acquisition &acq);
+    void readAcquisition(uint32_t index, Acquisition &acq);
+    uint32_t getNumberOfAcquisitions();
+    // Images
+    template <typename T> void appendImage(const std::string &var, const Image<T> &im);
+    void appendImage(const std::string &var, const ISMRMRD_Image *im);
+    template <typename T> void readImage(const std::string &var, uint32_t index, Image<T> &im);
+    uint32_t getNumberOfImages(const std::string &var);
+    // NDArrays
+    template <typename T> void appendNDArray(const std::string &var, const NDArray<T> &arr);
+    void appendNDArray(const std::string &var, const ISMRMRD_NDArray *arr);
+    template <typename T> void readNDArray(const std::string &var, uint32_t index, NDArray<T> &arr);
+    uint32_t getNumberOfNDArrays(const std::string &var);
+
+protected:
+    ISMRMRD_Dataset dset_;
+};
+
+} /* ISMRMRD namespace */
+#endif
+
+#endif /* ISMRMRD_DATASET_H */
diff --git a/include/ismrmrd/export.h b/include/ismrmrd/export.h
new file mode 100644 (file)
index 0000000..57d57bc
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef ISMRMRD_EXPORT_H_
+#define ISMRMRD_EXPORT_H_
+
+#if defined (WIN32)
+#if defined (ismrmrd_EXPORTS)
+#define EXPORTISMRMRD __declspec(dllexport)
+#else
+#define EXPORTISMRMRD __declspec(dllimport)
+#endif
+#else
+#define EXPORTISMRMRD
+#endif
+
+#endif /* ISMRMRD_EXPORT_H_ */
diff --git a/include/ismrmrd/ismrmrd.h b/include/ismrmrd/ismrmrd.h
new file mode 100644 (file)
index 0000000..5c9d76e
--- /dev/null
@@ -0,0 +1,831 @@
+/* ISMRMRD MR Raw Data Strutures                           */
+/* DRAFT                                                   */
+/* Authors:                                                */
+/*    Michael S. Hansen (michael.hansen@nih.gov)           */
+/*    Brian Hargreaves  (bah@stanford.edu)                 */
+/*    Sebastian Kozerke (kozerke@biomed.ee.ethz.ch)        */
+/*    Kaveh Vahedipour  (k.vahedipour@fz-juelich.de)       */
+/*    Hui Xue           (hui.xue@nih.gov)                  */
+/*    Souheil Inati     (souheil.inati@nih.gov)            */
+/*    Joseph Naegele    (joseph.naegele@nih.gov)           */
+
+/**
+ * @file ismrmrd.h
+ * @defgroup capi C API
+ * @defgroup cxxapi C++ API
+ */
+
+#pragma once
+#ifndef ISMRMRD_H
+#define ISMRMRD_H
+
+/* Language and cross platform section for defining types */
+/* integers */
+#ifdef _MSC_VER /* MS compiler */
+#ifndef HAS_INT_TYPE
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#endif
+#else /* non MS C or C++ compiler */
+#include <stdint.h>
+#include <stddef.h>     /* for size_t */
+#endif /* _MSC_VER */
+
+/* Complex numbers */
+#ifdef __cplusplus
+#include <complex>
+typedef std::complex<float> complex_float_t;
+typedef std::complex<double> complex_double_t;
+#else
+#ifdef _MSC_VER /* MS C compiler */
+typedef struct complex_float_t{
+    float real;
+    float imag;
+}complex_float_t;
+typedef struct complex_double_t{
+    double real;
+    double imag;
+}complex_double_t;
+#else /* C99 compiler */
+#include <complex.h>
+typedef float complex complex_float_t;
+typedef double complex complex_double_t;
+#endif /* _MSC_VER */
+#endif /* __cplusplus */
+
+/* Booleans - part of C++ */
+#ifndef __cplusplus
+#ifdef _MSC_VER /* MS C compiler */
+typedef int bool;
+#define false 0
+#define true 1
+#else /* C99 compiler */
+#include <stdbool.h>
+#endif /* _MSC_VER */
+#endif /* __cplusplus */
+
+/* Vectors */
+#ifdef __cplusplus
+#include <vector>
+#endif /* __cplusplus */
+
+/* Exports needed for MS C++ */
+#include "ismrmrd/export.h"
+
+#pragma pack(push, 2) /* Use 2 byte alignment */
+
+#ifdef __cplusplus
+namespace ISMRMRD {
+extern "C" {
+#endif
+
+/**
+ * Constants
+ */
+enum ISMRMRD_Constants {
+    ISMRMRD_USER_INTS = 8,
+    ISMRMRD_USER_FLOATS = 8,
+    ISMRMRD_PHYS_STAMPS = 3,
+    ISMRMRD_CHANNEL_MASKS = 16,
+    ISMRMRD_NDARRAY_MAXDIM = 7,
+    ISMRMRD_POSITION_LENGTH = 3,
+    ISMRMRD_DIRECTION_LENGTH = 3
+};
+
+
+/**
+ * Constants
+ */
+enum ISMRMRD_ErrorCodes {
+    ISMRMRD_BEGINERROR=-1,
+    ISMRMRD_NOERROR,
+    ISMRMRD_MEMORYERROR,
+    ISMRMRD_FILEERROR,
+    ISMRMRD_TYPEERROR,
+    ISMRMRD_RUNTIMEERROR,
+    ISMRMRD_HDF5ERROR,
+    ISMRMRD_ENDERROR
+};
+
+/**
+ * Data Types
+ */
+enum ISMRMRD_DataTypes {
+    ISMRMRD_USHORT   = 1, /**< corresponds to uint16_t */
+    ISMRMRD_SHORT    = 2, /**< corresponds to int16_t */
+    ISMRMRD_UINT     = 3, /**< corresponds to uint32_t */
+    ISMRMRD_INT      = 4, /**< corresponds to int32_t */
+    ISMRMRD_FLOAT    = 5, /**< corresponds to float */
+    ISMRMRD_DOUBLE   = 6, /**< corresponds to double */
+    ISMRMRD_CXFLOAT  = 7, /**< corresponds to complex float */
+    ISMRMRD_CXDOUBLE = 8  /**< corresponds to complex double */
+};
+
+/** Returns the size in bytes of an ISMRMRD_DataType */
+size_t ismrmrd_sizeof_data_type(int data_type);
+
+/**
+ * Acquisition Flags
+ */
+enum ISMRMRD_AcquisitionFlags {
+    ISMRMRD_ACQ_FIRST_IN_ENCODE_STEP1               =  1,
+    ISMRMRD_ACQ_LAST_IN_ENCODE_STEP1                =  2,
+    ISMRMRD_ACQ_FIRST_IN_ENCODE_STEP2               =  3,
+    ISMRMRD_ACQ_LAST_IN_ENCODE_STEP2                =  4,
+    ISMRMRD_ACQ_FIRST_IN_AVERAGE                    =  5,
+    ISMRMRD_ACQ_LAST_IN_AVERAGE                     =  6,
+    ISMRMRD_ACQ_FIRST_IN_SLICE                      =  7,
+    ISMRMRD_ACQ_LAST_IN_SLICE                       =  8,
+    ISMRMRD_ACQ_FIRST_IN_CONTRAST                   =  9,
+    ISMRMRD_ACQ_LAST_IN_CONTRAST                    = 10,
+    ISMRMRD_ACQ_FIRST_IN_PHASE                      = 11,
+    ISMRMRD_ACQ_LAST_IN_PHASE                       = 12,
+    ISMRMRD_ACQ_FIRST_IN_REPETITION                 = 13,
+    ISMRMRD_ACQ_LAST_IN_REPETITION                  = 14,
+    ISMRMRD_ACQ_FIRST_IN_SET                        = 15,
+    ISMRMRD_ACQ_LAST_IN_SET                         = 16,
+    ISMRMRD_ACQ_FIRST_IN_SEGMENT                    = 17,
+    ISMRMRD_ACQ_LAST_IN_SEGMENT                     = 18,
+    ISMRMRD_ACQ_IS_NOISE_MEASUREMENT                = 19,
+    ISMRMRD_ACQ_IS_PARALLEL_CALIBRATION             = 20,
+    ISMRMRD_ACQ_IS_PARALLEL_CALIBRATION_AND_IMAGING = 21,
+    ISMRMRD_ACQ_IS_REVERSE                          = 22,
+    ISMRMRD_ACQ_IS_NAVIGATION_DATA                  = 23,
+    ISMRMRD_ACQ_IS_PHASECORR_DATA                   = 24,
+    ISMRMRD_ACQ_LAST_IN_MEASUREMENT                 = 25,
+    ISMRMRD_ACQ_IS_HPFEEDBACK_DATA                  = 26,
+    ISMRMRD_ACQ_IS_DUMMYSCAN_DATA                   = 27,
+    ISMRMRD_ACQ_IS_RTFEEDBACK_DATA                  = 28,
+    ISMRMRD_ACQ_IS_SURFACECOILCORRECTIONSCAN_DATA   = 29,
+
+    ISMRMRD_ACQ_COMPRESSION1                        = 53,
+    ISMRMRD_ACQ_COMPRESSION2                        = 54,
+    ISMRMRD_ACQ_COMPRESSION3                        = 55,
+    ISMRMRD_ACQ_COMPRESSION4                        = 56,
+    ISMRMRD_ACQ_USER1                               = 57,
+    ISMRMRD_ACQ_USER2                               = 58,
+    ISMRMRD_ACQ_USER3                               = 59,
+    ISMRMRD_ACQ_USER4                               = 60,
+    ISMRMRD_ACQ_USER5                               = 61,
+    ISMRMRD_ACQ_USER6                               = 62,
+    ISMRMRD_ACQ_USER7                               = 63,
+    ISMRMRD_ACQ_USER8                               = 64
+};
+
+/**
+ * Image Types
+ */
+enum ISMRMRD_ImageTypes {
+    ISMRMRD_IMTYPE_MAGNITUDE = 1,
+    ISMRMRD_IMTYPE_PHASE     = 2,
+    ISMRMRD_IMTYPE_REAL      = 3,
+    ISMRMRD_IMTYPE_IMAG      = 4,
+    ISMRMRD_IMTYPE_COMPLEX   = 5
+};
+
+/**
+ * Image Flags
+ */
+enum ISMRMRD_ImageFlags {
+    ISMRMRD_IMAGE_IS_NAVIGATION_DATA =  1,
+    ISMRMRD_IMAGE_USER1              = 57,
+    ISMRMRD_IMAGE_USER2              = 58,
+    ISMRMRD_IMAGE_USER3              = 59,
+    ISMRMRD_IMAGE_USER4              = 60,
+    ISMRMRD_IMAGE_USER5              = 61,
+    ISMRMRD_IMAGE_USER6              = 62,
+    ISMRMRD_IMAGE_USER7              = 63,
+    ISMRMRD_IMAGE_USER8              = 64
+};
+
+/**
+ * Struct used for keeping track of typical loop counters in MR experiment.
+ */
+typedef struct ISMRMRD_EncodingCounters {
+    uint16_t kspace_encode_step_1;    /**< e.g. phase encoding line number */
+    uint16_t kspace_encode_step_2;    /**< e.g. partition encoding number */
+    uint16_t average;                 /**< e.g. signal average number */
+    uint16_t slice;                   /**< e.g. imaging slice number */
+    uint16_t contrast;                /**< e.g. echo number in multi-echo */
+    uint16_t phase;                   /**< e.g. cardiac phase number */
+    uint16_t repetition;              /**< e.g. dynamic number for dynamic scanning */
+    uint16_t set;                     /**< e.g. flow encoding set */
+    uint16_t segment;                 /**< e.g. segment number for segmented acquisition */
+    uint16_t user[ISMRMRD_USER_INTS]; /**< Free user parameters */
+} ISMRMRD_EncodingCounters;
+
+/**
+ * Header for each MR acquisition.
+ */
+typedef struct ISMRMRD_AcquisitionHeader {
+    uint16_t version;                                    /**< First unsigned int indicates the version */
+    uint64_t flags;                                      /**< bit field with flags */
+    uint32_t measurement_uid;                            /**< Unique ID for the measurement */
+    uint32_t scan_counter;                               /**< Current acquisition number in the measurement */
+    uint32_t acquisition_time_stamp;                     /**< Acquisition clock */
+    uint32_t physiology_time_stamp[ISMRMRD_PHYS_STAMPS]; /**< Physiology time stamps, e.g. ecg, breating, etc. */
+    uint16_t number_of_samples;                          /**< Number of samples acquired */
+    uint16_t available_channels;                         /**< Available coils */
+    uint16_t active_channels;                            /**< Active coils on current acquisiton */
+    uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS];        /**< Mask to indicate which channels are active. Support for 1024 channels */
+    uint16_t discard_pre;                                /**< Samples to be discarded at the beginning of  acquisition */
+    uint16_t discard_post;                               /**< Samples to be discarded at the end of acquisition */
+    uint16_t center_sample;                              /**< Sample at the center of k-space */
+    uint16_t encoding_space_ref;                         /**< Reference to an encoding space, typically only one per acquisition */
+    uint16_t trajectory_dimensions;                      /**< Indicates the dimensionality of the trajectory vector (0 means no trajectory) */
+    float sample_time_us;                                /**< Time between samples in micro seconds, sampling BW */
+    float position[3];                                   /**< Three-dimensional spatial offsets from isocenter */
+    float read_dir[3];                                   /**< Directional cosines of the readout/frequency encoding */
+    float phase_dir[3];                                  /**< Directional cosines of the phase */
+    float slice_dir[3];                                  /**< Directional cosines of the slice direction */
+    float patient_table_position[3];                     /**< Patient table off-center */
+    ISMRMRD_EncodingCounters idx;                        /**< Encoding loop counters, see above */
+    int32_t user_int[ISMRMRD_USER_INTS];                 /**< Free user parameters */
+    float user_float[ISMRMRD_USER_FLOATS];               /**< Free user parameters */
+} ISMRMRD_AcquisitionHeader;
+
+/**
+ * Initialize an Acquisition Header
+ * @ingroup capi
+ *
+ */
+EXPORTISMRMRD int ismrmrd_init_acquisition_header(ISMRMRD_AcquisitionHeader *hdr);
+
+/** Individual MR acquisition. */
+typedef struct ISMRMRD_Acquisition {
+    ISMRMRD_AcquisitionHeader head; /**< Header, see above */
+    float *traj;
+    complex_float_t *data;
+} ISMRMRD_Acquisition;
+
+/** @addtogroup capi
+ *  @{
+ */
+EXPORTISMRMRD ISMRMRD_Acquisition * ismrmrd_create_acquisition();
+EXPORTISMRMRD int ismrmrd_free_acquisition(ISMRMRD_Acquisition *acq);
+EXPORTISMRMRD int ismrmrd_init_acquisition(ISMRMRD_Acquisition *acq);
+EXPORTISMRMRD int ismrmrd_cleanup_acquisition(ISMRMRD_Acquisition *acq);
+EXPORTISMRMRD int ismrmrd_copy_acquisition(ISMRMRD_Acquisition *acqdest, const ISMRMRD_Acquisition *acqsource);
+EXPORTISMRMRD int ismrmrd_make_consistent_acquisition(ISMRMRD_Acquisition *acq);
+EXPORTISMRMRD size_t ismrmrd_size_of_acquisition_traj(const ISMRMRD_Acquisition *acq);
+EXPORTISMRMRD size_t ismrmrd_size_of_acquisition_data(const ISMRMRD_Acquisition *acq);
+/** @} */
+
+/**********/
+/* Images */
+/**********/
+
+/**
+ *  Header for each Image
+ */
+typedef struct ISMRMRD_ImageHeader {
+    uint16_t version;                                    /**< First unsigned int indicates the version */
+    uint16_t data_type;                                  /**< e.g. unsigned short, float, complex float, etc. */
+    uint64_t flags;                                      /**< bit field with flags */
+    uint32_t measurement_uid;                            /**< Unique ID for the measurement  */
+    uint16_t matrix_size[3];                             /**< Pixels in the 3 spatial dimensions */
+    float field_of_view[3];                              /**< Size (in mm) of the 3 spatial dimensions */
+    uint16_t channels;                                   /**< Number of receive channels */
+    float position[3];                                   /**< Three-dimensional spatial offsets from isocenter */
+    float read_dir[3];                                   /**< Directional cosines of the readout/frequency encoding */
+    float phase_dir[3];                                  /**< Directional cosines of the phase */
+    float slice_dir[3];                                  /**< Directional cosines of the slice direction */
+    float patient_table_position[3];                     /**< Patient table off-center */
+    uint16_t average;                                    /**< e.g. signal average number */
+    uint16_t slice;                                      /**< e.g. imaging slice number */
+    uint16_t contrast;                                   /**< e.g. echo number in multi-echo */
+    uint16_t phase;                                      /**< e.g. cardiac phase number */
+    uint16_t repetition;                                 /**< e.g. dynamic number for dynamic scanning */
+    uint16_t set;                                        /**< e.g. flow encodning set */
+    uint32_t acquisition_time_stamp;                     /**< Acquisition clock */
+    uint32_t physiology_time_stamp[ISMRMRD_PHYS_STAMPS]; /**< Physiology time stamps, e.g. ecg, breathing, etc. */
+    uint16_t image_type;                                 /**< e.g. magnitude, phase, complex, real, imag, etc. */
+    uint16_t image_index;                                /**< e.g. image number in series of images  */
+    uint16_t image_series_index;                         /**< e.g. series number */
+    int32_t user_int[ISMRMRD_USER_INTS];                 /**< Free user parameters */
+    float user_float[ISMRMRD_USER_FLOATS];               /**< Free user parameters */
+    uint32_t attribute_string_len;                       /**< Length of attributes string */
+} ISMRMRD_ImageHeader;
+
+/** @ingroup capi */
+EXPORTISMRMRD int ismrmrd_init_image_header(ISMRMRD_ImageHeader *hdr);
+
+/**
+ *  An individual Image
+ *  @ingroup capi
+ */
+typedef struct ISMRMRD_Image {
+    ISMRMRD_ImageHeader head;
+    char *attribute_string;
+    void *data;
+} ISMRMRD_Image;
+
+
+/** @addtogroup capi
+ *  @{
+ */
+EXPORTISMRMRD ISMRMRD_Image * ismrmrd_create_image();
+EXPORTISMRMRD int ismrmrd_free_image(ISMRMRD_Image *im);
+EXPORTISMRMRD int ismrmrd_init_image(ISMRMRD_Image *im);
+EXPORTISMRMRD int ismrmrd_cleanup_image(ISMRMRD_Image *im);
+EXPORTISMRMRD int ismrmrd_copy_image(ISMRMRD_Image *imdest, const ISMRMRD_Image *imsource);
+EXPORTISMRMRD int ismrmrd_make_consistent_image(ISMRMRD_Image *im);
+EXPORTISMRMRD size_t ismrmrd_size_of_image_attribute_string(const ISMRMRD_Image *im);
+EXPORTISMRMRD size_t ismrmrd_size_of_image_data(const ISMRMRD_Image *im);
+/** @} */
+
+/************/
+/* NDArrays */
+/************/
+
+/**
+ *  A simple N dimensional array
+ */
+typedef struct ISMRMRD_NDArray {
+    uint16_t version;                      /**< First unsigned int indicates the version */
+    uint16_t data_type;                    /**< e.g. unsigned short, float, complex float, etc. */
+    uint16_t ndim;                         /**< Number of dimensions */
+    size_t   dims[ISMRMRD_NDARRAY_MAXDIM]; /**< Dimensions */
+    void     *data;                        /**< Pointer to data */
+} ISMRMRD_NDArray;
+
+/** @addtogroup capi
+ *  @{
+ */
+EXPORTISMRMRD ISMRMRD_NDArray * ismrmrd_create_ndarray();
+EXPORTISMRMRD int ismrmrd_free_ndarray(ISMRMRD_NDArray *arr);
+EXPORTISMRMRD int ismrmrd_init_ndarray(ISMRMRD_NDArray *arr);
+EXPORTISMRMRD int ismrmrd_cleanup_ndarray(ISMRMRD_NDArray *arr);
+EXPORTISMRMRD int ismrmrd_copy_ndarray(ISMRMRD_NDArray *arrdest, const ISMRMRD_NDArray *arrsource);
+EXPORTISMRMRD int ismrmrd_make_consistent_ndarray(ISMRMRD_NDArray *arr);
+EXPORTISMRMRD size_t ismrmrd_size_of_ndarray_data(const ISMRMRD_NDArray *arr);
+/** @} */
+
+/*********/
+/* Flags */
+/*********/
+/** @addtogroup capi
+ *  @{
+ */
+EXPORTISMRMRD bool ismrmrd_is_flag_set(const uint64_t flags, const uint64_t val);
+EXPORTISMRMRD int ismrmrd_set_flag(uint64_t *flags, const uint64_t val);
+EXPORTISMRMRD int ismrmrd_set_flags(uint64_t *flags, const uint64_t val);
+EXPORTISMRMRD int ismrmrd_clear_flag(uint64_t *flags, const uint64_t val);
+EXPORTISMRMRD int ismrmrd_clear_all_flags(uint64_t *flags);
+/** @} */
+
+/*****************/
+/* Channel Masks */
+/*****************/
+/** @addtogroup capi
+ *  @{
+ */
+EXPORTISMRMRD bool ismrmrd_is_channel_on(const uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan);
+EXPORTISMRMRD int ismrmrd_set_channel_on(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan);
+EXPORTISMRMRD int ismrmrd_set_channel_off(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan);
+EXPORTISMRMRD int ismrmrd_set_all_channels_off(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS]);
+/** @} */
+
+/******************/
+/* Error Handling */
+/******************/
+/** @addtogroup capi
+ *  @{
+ */
+typedef void (*ismrmrd_error_handler_t)(const char *file, int line,
+        const char *function, int code, const char *msg);
+#define ISMRMRD_PUSH_ERR(code, msg) ismrmrd_push_error(__FILE__, __LINE__, \
+        __func__, (code), (msg))
+int ismrmrd_push_error(const char *file, const int line, const char *func,
+        const int code, const char *msg);
+/** Sets a custom error handler */
+EXPORTISMRMRD void ismrmrd_set_error_handler(ismrmrd_error_handler_t);
+/** Returns message for corresponding error code */
+EXPORTISMRMRD char *ismrmrd_strerror(int code);
+/** @} */
+
+/** Populates parameters (if non-NULL) with error information
+ * @returns true if there was error information to return, false otherwise */
+bool ismrmrd_pop_error(char **file, int *line, char **func,
+        int *code, char **msg);
+
+/*****************************/
+/* Rotations and Quaternions */
+/*****************************/
+/** @addtogroup capi
+ *  @{
+ */
+/** Calculates the determinant of the matrix and return the sign */
+EXPORTISMRMRD int ismrmrd_sign_of_directions(float read_dir[3], float phase_dir[3], float slice_dir[3]);
+
+/** Creates a normalized quaternion from a 3x3 rotation matrix */
+EXPORTISMRMRD void ismrmrd_directions_to_quaternion(float read_dir[3], float phase_dir[3], float slice_dir[3], float quat[4]);
+
+/** Converts a quaternion of the form | a b c d | to a 3x3 rotation matrix */
+EXPORTISMRMRD void ismrmrd_quaternion_to_directions(float quat[4], float read_dir[3], float phase_dir[3], float slice_dir[3]);
+/** @} */
+
+#pragma pack(pop) /* Restore old alignment */
+
+#ifdef __cplusplus
+} // extern "C"
+
+///  ISMRMRD C++ Interface
+
+/// Construct exception message from ISMRMRD error stack
+std::string build_exception_string(void);
+
+/// Some typedefs to beautify the namespace
+typedef  ISMRMRD_EncodingCounters EncodingCounters;
+
+/** @addtogroup cxxapi
+ *  @{
+ */
+
+/// Allowed data types for Images and NDArrays
+template <typename T> EXPORTISMRMRD ISMRMRD_DataTypes get_data_type();
+
+/// Convenience class for flags
+class EXPORTISMRMRD FlagBit
+{
+public:
+ FlagBit(unsigned short b)
+   : bitmask_(0)
+    {
+      if (b > 0) {
+    bitmask_ = 1;
+    bitmask_ = (bitmask_ << (b-1));
+      }
+    }
+
+  bool isSet(const uint64_t& m) const {
+    return ((m & bitmask_)>0);
+  }
+
+  uint64_t bitmask_;
+
+};
+
+/// Header for MR Acquisition type
+class EXPORTISMRMRD AcquisitionHeader: public ISMRMRD_AcquisitionHeader {
+public:
+    // Constructors
+    AcquisitionHeader();
+
+    // Flag methods
+    bool isFlagSet(const ISMRMRD_AcquisitionFlags val);
+    void setFlag(const ISMRMRD_AcquisitionFlags val);
+    void clearFlag(const ISMRMRD_AcquisitionFlags val);
+    void clearAllFlags();
+
+    // Channel mask methods
+    bool isChannelActive(uint16_t channel_id);
+    void setChannelActive(uint16_t channel_id);
+    void setChannelNotActive(uint16_t channel_id);
+    void setAllChannelsNotActive();
+
+};
+
+/// MR Acquisition type
+class EXPORTISMRMRD Acquisition {
+    friend class Dataset;
+public:
+    // Constructors, assignment, destructor
+    Acquisition();
+    Acquisition(uint16_t num_samples, uint16_t active_channels=1, uint16_t trajectory_dimensions=0);
+    Acquisition(const Acquisition &other);
+    Acquisition & operator= (const Acquisition &other);
+    ~Acquisition();
+
+    // Accessors and mutators
+    const uint16_t &version();
+    const uint64_t &flags();
+    uint32_t &measurement_uid();
+    uint32_t &scan_counter();
+    uint32_t &acquisition_time_stamp();
+    uint32_t (&physiology_time_stamp())[ISMRMRD_PHYS_STAMPS];
+    const uint16_t &number_of_samples();
+    uint16_t &available_channels();
+    const uint16_t &active_channels();
+    const uint64_t (&channel_mask())[ISMRMRD_CHANNEL_MASKS];
+    uint16_t &discard_pre();
+    uint16_t &discard_post();
+    uint16_t &center_sample();
+    uint16_t &encoding_space_ref();
+    const uint16_t &trajectory_dimensions();
+    float &sample_time_us();
+    float (&position())[3];
+    float (&read_dir())[3];
+    float (&phase_dir())[3];
+    float (&slice_dir())[3];
+    float (&patient_table_position())[3];
+    ISMRMRD_EncodingCounters &idx();
+    int32_t (&user_int())[ISMRMRD_USER_INTS];
+    float (&user_float())[ISMRMRD_USER_FLOATS];
+
+    // Sizes
+    void resize(uint16_t num_samples, uint16_t active_channels=1, uint16_t trajectory_dimensions=0);
+    size_t getNumberOfDataElements() const;
+    size_t getNumberOfTrajElements() const;
+    size_t getDataSize() const;
+    size_t getTrajSize() const;
+
+    // Header, data and trajectory accessors
+    const AcquisitionHeader &getHead() const;
+    void setHead(const AcquisitionHeader &other);
+    
+    /**
+     * Returns a pointer to the data
+     */
+    const complex_float_t * getDataPtr() const;
+    complex_float_t * getDataPtr();
+
+    /**
+     * Returns a reference to the data
+     */    
+    complex_float_t & data(uint16_t sample, uint16_t channel);
+
+    /**
+     * Sets the datay.  Must set sizes properly first
+     */    
+    void setData(complex_float_t * data);
+
+    /**
+     * Returns an iterator to the beginning of the data
+     */
+    complex_float_t * data_begin() const;
+    
+    /**
+     * Returns an iterator of the end of the data
+     */
+    complex_float_t * data_end() const;
+    
+    /**
+     * Returns a pointer to the trajectory
+     */
+    const float * getTrajPtr() const;
+    float * getTrajPtr();
+    
+    /**
+     * Returns a reference to the trajectory
+     */
+    float & traj(uint16_t dimension, uint16_t sample);
+    
+    /**
+     * Sets the trajectory.  Must set sizes properly first
+     */
+    void setTraj(float * traj);
+    
+    /**
+     * Returns an iterator to the beginning of the trajectories
+     */
+    float * traj_begin() const;
+    
+    /**
+     * Returns an iterator to the end of the trajectories
+     */
+    float * traj_end() const;
+
+    // Flag methods
+    bool isFlagSet(const uint64_t val);
+    void setFlag(const uint64_t val);
+    void clearFlag(const uint64_t val);
+    void clearAllFlags();
+
+    bool isFlagSet(const FlagBit &val)  { return isFlagSet(val.bitmask_); }
+    void setFlag(const FlagBit &val)    { setFlag(val.bitmask_); }
+    void clearFlag(const FlagBit &val)  { clearFlag(val.bitmask_); }
+
+    // Channel mask methods
+    bool isChannelActive(uint16_t channel_id);
+    void setChannelActive(uint16_t channel_id);
+    void setChannelNotActive(uint16_t channel_id);
+    void setAllChannelsNotActive();
+
+protected:
+    ISMRMRD_Acquisition acq;
+};
+
+/// Header for MR Image type
+class EXPORTISMRMRD ImageHeader: public ISMRMRD_ImageHeader {
+public:
+    // Constructor
+    ImageHeader();
+
+    // Flag methods
+    bool isFlagSet(const uint64_t val);
+    void setFlag(const uint64_t val);
+    void clearFlag(const uint64_t val);
+    void clearAllFlags();
+
+};
+
+/// MR Image type
+template <typename T> class EXPORTISMRMRD Image {
+    friend class Dataset;
+public:
+    // Constructors
+    Image(uint16_t matrix_size_x = 0, uint16_t matrix_size_y = 1,
+          uint16_t matrix_size_z = 1, uint16_t channels = 1);
+    Image(const Image &other);
+    Image & operator= (const Image &other);
+    ~Image();
+
+    // Image dimensions
+    void resize(uint16_t matrix_size_x, uint16_t matrix_size_y, uint16_t matrix_size_z, uint16_t channels);
+    uint16_t getMatrixSizeX() const;
+    void setMatrixSizeX(uint16_t matrix_size_x);
+    uint16_t getMatrixSizeY() const;
+    void setMatrixSizeY(uint16_t matrix_size_y);
+    uint16_t getMatrixSizeZ() const;
+    void setMatrixSizeZ(uint16_t matrix_size_z);
+    uint16_t getNumberOfChannels() const;
+    void setNumberOfChannels(uint16_t channels);
+
+    // Field of view
+    void setFieldOfView(float fov_x, float fov_y, float fov_z);
+    float getFieldOfViewX() const;
+    void setFieldOfViewX(float f);
+    float getFieldOfViewY() const;
+    void setFieldOfViewY(float f);
+    float getFieldOfViewZ() const;
+    void setFieldOfViewZ(float f);
+
+    // Positions and orientations
+    void setPosition(float x, float y, float z);    
+    float getPositionX() const;
+    void setPositionX(float x);
+    float getPositionY() const;
+    void setPositionY(float y);
+    float getPositionZ() const;
+    void setPositionZ(float z);
+
+    void setReadDirection(float x, float y, float z);
+    float getReadDirectionX() const;
+    void setReadDirectionX(float x);
+    float getReadDirectionY() const;
+    void setReadDirectionY(float y);
+    float getReadDirectionZ() const;
+    void setReadDirectionZ(float z);
+    
+    void setPhaseDirection(float x, float y, float z);
+    float getPhaseDirectionX() const;
+    void setPhaseDirectionX(float x);
+    float getPhaseDirectionY() const;
+    void setPhaseDirectionY(float y);
+    float getPhaseDirectionZ() const;
+    void setPhaseDirectionZ(float z);
+
+    void setSliceDirection(float x, float y, float z);
+    float getSliceDirectionX() const;
+    void setSliceDirectionX(float x);
+    float getSliceDirectionY() const;
+    void setSliceDirectionY(float y);
+    float getSliceDirectionZ() const;
+    void setSliceDirectionZ(float z);
+    
+    void setPatientTablePosition(float x, float y, float z);
+    float getPatientTablePositionX() const;
+    void setPatientTablePositionX(float x);
+    float getPatientTablePositionY() const;
+    void setPatientTablePositionY(float y);
+    float getPatientTablePositionZ() const;
+    void setPatientTablePositionZ(float z);
+
+    
+    // Attributes
+    uint16_t getVersion() const;
+    ISMRMRD_DataTypes getDataType() const;
+
+    // Counters and labels
+    uint32_t getMeasurementUid() const;
+    void setMeasurementUid(uint32_t measurement_uid);
+
+    uint16_t getAverage() const;
+    void setAverage(uint16_t average);
+
+    uint16_t getSlice() const;
+    void setSlice(uint16_t slice);
+    
+    uint16_t getContrast() const;
+    void setContrast(uint16_t contrast);
+
+    uint16_t getPhase() const;
+    void setPhase(uint16_t phase);
+    
+    uint16_t getRepetition() const;
+    void setRepetition(uint16_t repetition);
+
+    uint16_t getSet() const;
+    void setSet(uint16_t set);
+
+    uint32_t getAcquisitionTimeStamp() const;
+    void setAcquisitionTimeStamp(uint32_t acquisition_time_stamp);
+
+    uint32_t getPhysiologyTimeStamp(unsigned int stamp_id) const;
+    void setPhysiologyTimeStamp(unsigned int stamp_id, uint32_t value);
+    
+    uint16_t getImageType() const;
+    void setImageType(uint16_t image_type);
+
+    uint16_t getImageIndex() const;
+    void setImageIndex(uint16_t image_index);
+
+    uint16_t getImageSeriesIndex() const;
+    void setImageSeriesIndex(uint16_t image_series_index);
+    
+    // User parameters
+    float getUserFloat(unsigned int index) const;
+    void setUserFloat(unsigned int index, float value);
+
+    int32_t getUserInt(unsigned int index) const;
+    void setUserInt(unsigned int index, int32_t value);
+
+    // Flags
+    uint64_t getFlags() const;
+    void setFlags(const uint64_t flags);
+    bool isFlagSet(const uint64_t val) const;
+    void setFlag(const uint64_t val);
+    void clearFlag(const uint64_t val);
+    void clearAllFlags();
+
+    // Header
+    ImageHeader & getHead();
+    const ImageHeader & getHead() const;
+    void setHead(const ImageHeader& head);
+    
+    // Attribute string
+    void getAttributeString(std::string &attr) const;
+    const char *getAttributeString() const;
+    void setAttributeString(const std::string &attr);
+    void setAttributeString(const char *attr);
+    size_t getAttributeStringLength() const;
+    
+    // Data
+    T * getDataPtr();
+    const T * getDataPtr() const;
+    /** Returns the number of elements in the image data **/
+    size_t getNumberOfDataElements() const;
+    /** Returns the size of the image data in bytes **/
+    size_t getDataSize() const;
+
+    /** Returns iterator to the beginning of the image data **/
+    T* begin();
+
+    /** Returns iterator to the end of the image data **/
+    T* end();
+
+    /** Returns a reference to the image data **/
+    T & operator () (uint16_t x, uint16_t y=0, uint16_t z=0 , uint16_t channel =0);
+
+protected:
+    ISMRMRD_Image im;
+};
+
+/// N-Dimensional array type
+template <typename T> class EXPORTISMRMRD NDArray {
+    friend class Dataset;
+public:
+    // Constructors, destructor and copy
+    NDArray();
+    NDArray(const std::vector<size_t> dimvec);
+    NDArray(const NDArray<T> &other);
+    ~NDArray();
+    NDArray<T> & operator= (const NDArray<T> &other);
+
+    // Accessors and mutators
+    uint16_t getVersion() const;
+    ISMRMRD_DataTypes getDataType() const;
+    uint16_t getNDim() const;
+    const size_t (&getDims())[ISMRMRD_NDARRAY_MAXDIM];
+    size_t getDataSize() const;
+    void resize(const std::vector<size_t> dimvec);
+    size_t getNumberOfElements() const;
+    T * getDataPtr();
+    const T * getDataPtr() const;
+    
+    /** Returns iterator to the beginning of the array **/
+    T * begin();
+
+    /** Returns iterator to the end of the array **/
+    T* end();
+
+    /** Returns a reference to the image data **/
+    T & operator () (uint16_t x, uint16_t y=0, uint16_t z=0, uint16_t w=0, uint16_t n=0, uint16_t m=0, uint16_t l=0);
+
+protected:
+    ISMRMRD_NDArray arr;
+};
+
+
+/** @} */
+
+} // namespace ISMRMRD
+
+#endif
+
+#endif /* ISMRMRD_H */
diff --git a/include/ismrmrd/meta.h b/include/ismrmrd/meta.h
new file mode 100644 (file)
index 0000000..ef62d06
--- /dev/null
@@ -0,0 +1,264 @@
+/**
+ * @file meta.h
+ * @defgroup meta Meta Attributes API
+ * @{
+ */
+
+#ifndef ISMRMRDMETA_H
+#define ISMRMRDMETA_H
+
+#include "ismrmrd/export.h"
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <map>
+#include <stdexcept>
+#include <stdio.h>
+
+namespace ISMRMRD
+{
+  /*
+    The serialized version of the structues would look like this
+    
+    <?xml version="1.0"?>
+    <ismrmrdMeta>
+       <!-- String value type -->
+       <meta>
+          <name>parameter1</name>
+         <value>value_string</value>
+       </meta>
+
+       <!-- Integer value type -->
+       <meta>
+          <name>parameter1</name>
+         <value>677797</value>
+       </meta>
+
+       <!-- Arrays can have mixed value types -->
+       <meta>
+          <name>parameter1</name>
+         <value>1.456</value>
+         <value>66797</value>
+         <value>hsjdhaks</value>
+       </meta>
+    </ismrmrdMeta>
+   */
+
+
+  /**
+     This class can represent a meta data value of any
+     type and it guarantees that any value will have a
+     representation as any type.
+
+     The class uses std::string internally to store the 
+     string representation of the value but this std::string
+     is never exposed on the class interface and so it should not 
+     need to be exported in Windows. For now, this class can be header only.
+   */
+  class MetaValue
+  {
+
+  public:
+    /** Default construtor */
+    MetaValue()
+    {
+      set(0L);
+    }
+
+    ///Null terminated string constructor
+    MetaValue(const char* s)
+    {
+      set(s);
+    }
+
+    ///Long constructor
+    MetaValue(long l)
+    {
+      set(l);
+    }
+
+    ///Long constructor
+    MetaValue(double d)
+    {
+      set(d);
+    }
+
+
+    ///Assignment operator for string
+    MetaValue& operator=(const char * s) 
+    {
+      set(s);
+      return *this;
+    }
+
+    ///Assignment operator for long
+    MetaValue& operator=(long l) 
+    {
+      set(l);
+      return *this;
+    }
+
+    ///Assignment operator for double
+    MetaValue& operator=(double d) 
+    {
+      set(d);
+      return *this;
+    }
+
+    ///Get the ingeter representation of the value
+    long as_long() const
+    {
+      return l_;
+    }
+
+    ///Get the floating point representation of the value
+    double as_double() const
+    {
+      return d_;
+    }
+    
+    ///get the C string representation of the value
+    const char* as_str() const
+    {
+      return s_.c_str();
+    }
+
+
+  protected:
+    long l_;
+    double d_;
+    std::string s_;
+
+    void set(const char* s)
+    {
+      s_ = std::string(s);
+      sscanf(s_.c_str(),"%ld",&l_);
+      sscanf(s_.c_str(),"%lf",&d_);      
+    }
+
+    void set(long l)
+    {
+      l_ = l;
+      d_ = static_cast<double>(l_);
+      std::stringstream strstream;
+      strstream << l_;
+      strstream >> s_;
+    }
+
+    void set(double d)
+    {
+      d_ = d;
+      l_ = static_cast<long>(d_);
+      std::stringstream strstream;
+      strstream << d_;
+      strstream >> s_;
+    }
+  };
+
+  class MetaContainer;
+
+  EXPORTISMRMRD void deserialize(const char* xml, MetaContainer& h);
+  EXPORTISMRMRD void serialize(MetaContainer& h, std::ostream& o);
+
+  /// Meta Container
+  class MetaContainer
+  {
+    typedef std::map< std::string, std::vector<MetaValue> > map_t;
+
+    friend void serialize(MetaContainer& h, std::ostream& o);
+
+  public:
+    MetaContainer()
+    {
+
+    }
+
+    /**
+       This function sets the parameter with name @name to 
+       value @value. The set function assumes the parameter
+       will have a single value and wipes out any array that might be there.
+       
+       There is an @append function for appending to an existing array.
+     */
+    template <class T> void set(const char* name, T value)
+    {
+      MetaValue v(value);
+      map_[std::string(name)] = std::vector<MetaValue>(1,value);
+    }
+
+   
+    template <class T> void append(const char* name, T value)
+    {
+      map_t::iterator it = map_.find(std::string(name));      
+      if (it == map_.end()) {
+       set(name, value);
+      } else {
+       MetaValue v(value);
+       it->second.push_back(v);
+      }
+    }
+
+    /// Return number of values of a particular parameter
+    size_t length(const char* name) const
+    {
+      map_t::const_iterator it = map_.find(std::string(name));
+      if (it != map_.end()) {
+       return it->second.size();
+      }
+      return 0;
+    }
+
+    /// Return value number @index of the parameter @name as long
+    long as_long(const char* name, size_t index = 0) const
+    {
+      return value(name,index).as_long();
+    }
+
+    /// Return value number @index of the parameter @name as double
+    double as_double(const char* name, size_t index = 0) const
+    {
+      return value(name,index).as_double();
+    }
+    
+    /// Return value number @index of the parameter @name as string
+    const char* as_str(const char* name, size_t index = 0) const
+    {
+      return value(name,index).as_str();
+    }
+
+    const MetaValue& value(const char* name, size_t index = 0) const
+    {
+      map_t::const_iterator it = map_.find(std::string(name));
+      if (it == map_.end()) {
+       throw std::runtime_error("Attempting to access unkown parameter");
+      }
+      if (index >= it->second.size()) {
+       throw std::runtime_error("Attempting to access indexed value out of bounds");
+      }
+      return it->second[index];
+    }
+
+    bool empty()
+    {
+        return map_.empty();
+    }
+
+  protected:
+    map_t map_; 
+  };
+
+  //Template function instantiations
+  /*
+  template void MetaContainer::set<const char*>(const char* name, const char* value);
+  template void MetaContainer::set<long>(const char* name, long value);
+  template void MetaContainer::set<double>(const char* name, double);
+  template void MetaContainer::append<const char*>(const char* name, const char* value);
+  template void MetaContainer::append<long>(const char* name, long value);
+  template void MetaContainer::append<double>(const char* name, double);
+  */
+}
+
+/** @} */
+
+#endif //ISMRMRDMETA_H
diff --git a/include/ismrmrd/xml.h b/include/ismrmrd/xml.h
new file mode 100644 (file)
index 0000000..1e4b782
--- /dev/null
@@ -0,0 +1,333 @@
+/**
+ * @file xml.h
+ * @defgroup xml XML API
+ * @{
+ */
+
+#ifndef ISMRMRDXML_H
+#define ISMRMRDXML_H
+
+#include "ismrmrd/export.h"
+
+#include <cstddef>
+#include <new> //For std::badalloc
+#include <stdexcept> //For std::length_error
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+/**
+  TODO LIST
+
+  - Add a date time class
+  - Add enumeration class
+
+ */
+
+
+namespace ISMRMRD
+{
+
+  template <typename T> class Optional
+  {
+  public:
+    Optional()
+      : present_(false)
+    {
+
+    }
+
+    Optional(const T&v) {
+      present_ = true;
+      value_ = v;      
+    }
+
+    const Optional& operator=(const T& v) {
+      present_ = true;
+      value_ = v;
+      return *this;
+    }
+
+    const T* operator->() const {
+      return &value_;
+    }
+
+    const T& operator*() const {
+      return value_;
+    }
+
+    operator bool() const {
+      return present_;
+    }
+
+    bool is_present() const {
+      return present_;
+    }
+
+    T& get() {
+      if (!present_) {
+       throw std::runtime_error("Access optional value, which has not been set");
+      }
+      return value_;
+    }
+    
+    T& operator()() {
+      return get();
+    }
+
+    void set(const T& v) {
+      present_ = true;
+      value_ = v;
+    }
+
+  protected:
+    bool present_;
+    T value_;
+
+  }; 
+
+  struct SubjectInformation 
+  {
+    Optional<std::string> patientName;
+    Optional<float> patientWeight_kg;
+    Optional<std::string> patientID;
+    Optional<std::string> patientBirthdate;
+    Optional<std::string> patientGender;
+  };
+
+  struct StudyInformation
+  {
+    Optional<std::string> studyDate;
+    Optional<std::string> studyTime;
+    Optional<std::string> studyID;
+    Optional<long> accessionNumber;
+    Optional<std::string> referringPhysicianName;
+    Optional<std::string> studyDescription;
+    Optional<std::string> studyInstanceUID;
+  };
+
+  struct MeasurementDependency
+  {
+    std::string dependencyType;
+    std::string measurementID;
+  };
+
+  struct ReferencedImageSequence
+  {
+    std::string referencedSOPInstanceUID;
+  };
+  
+  struct MeasurementInformation
+  {
+    Optional<std::string> measurementID;
+    Optional<std::string> seriesDate;
+    Optional<std::string> seriesTime;
+    std::string patientPosition;
+    Optional<long int> initialSeriesNumber;
+    Optional<std::string> protocolName;
+    Optional<std::string> seriesDescription;
+    std::vector<MeasurementDependency> measurementDependency;
+    Optional<std::string> seriesInstanceUIDRoot;
+    Optional<std::string> frameOfReferenceUID;
+    std::vector<ReferencedImageSequence> referencedImageSequence;
+  };
+
+  struct CoilLabel
+  {
+    unsigned short coilNumber;
+    std::string coilName;
+  };
+  
+  struct AcquisitionSystemInformation
+  {
+    Optional<std::string> systemVendor;
+    Optional<std::string> systemModel;
+    Optional<float> systemFieldStrength_T;
+    Optional<float> relativeReceiverNoiseBandwidth;
+    Optional<unsigned short> receiverChannels;
+    std::vector<CoilLabel> coilLabel;
+    Optional<std::string> institutionName;
+    Optional<std::string> stationName;
+  };
+
+
+  struct ExperimentalConditions
+  {
+    long int H1resonanceFrequency_Hz;
+  };
+
+  struct MatrixSize
+  {
+    MatrixSize()
+    : x(1)
+    , y(1)
+    , z(1)
+    {
+
+    }
+    
+    MatrixSize(unsigned short x, unsigned short y)
+    : x(x)
+    , y(y)
+    , z(1)
+    {
+
+    }
+    
+    MatrixSize(unsigned short x, unsigned short y, unsigned short z)
+    : x(x)
+    , y(y)
+    , z(z)
+    {
+
+    }
+    
+    unsigned short x;
+    unsigned short y;
+    unsigned short z;
+  };
+
+  struct FieldOfView_mm
+  {
+    float x;
+    float y;
+    float z;
+  };
+
+  struct EncodingSpace
+  {
+    MatrixSize matrixSize;
+    FieldOfView_mm fieldOfView_mm;
+  };
+
+
+  struct Limit
+  {
+    Limit() 
+    : minimum(0)
+    , maximum(0)
+    , center(0)
+    {
+
+    }
+    
+    Limit(unsigned short minimum, unsigned short maximum, unsigned short center) 
+    : minimum(minimum)
+    , maximum(maximum)
+    , center(center)
+    {
+
+    }
+    
+    unsigned short minimum;
+    unsigned short maximum;
+    unsigned short center;
+  };
+
+  struct EncodingLimits
+  {
+    Optional<Limit> kspace_encoding_step_0;
+    Optional<Limit> kspace_encoding_step_1;
+    Optional<Limit> kspace_encoding_step_2;
+    Optional<Limit> average;
+    Optional<Limit> slice;
+    Optional<Limit> contrast;
+    Optional<Limit> phase;
+    Optional<Limit> repetition;
+    Optional<Limit> set;
+    Optional<Limit> segment;
+  };
+
+
+  struct UserParameterLong
+  {
+    std::string name;
+    long value;
+  };
+
+  struct UserParameterDouble
+  {
+    std::string name;
+    double value;
+  };
+  
+  struct UserParameterString
+
+  {
+    std::string name;
+    std::string value;
+  };
+
+  struct UserParameters
+  {
+    std::vector<UserParameterLong> userParameterLong;
+    std::vector<UserParameterDouble> userParameterDouble;
+    std::vector<UserParameterString> userParameterString;
+    std::vector<UserParameterString> userParameterBase64;
+  };
+
+  struct TrajectoryDescription
+  {
+    std::string identifier;
+    std::vector<UserParameterLong> userParameterLong;
+    std::vector<UserParameterDouble> userParameterDouble;
+    Optional<std::string> comment; 
+  };
+
+  struct AccelerationFactor
+  {
+    unsigned short kspace_encoding_step_1;
+    unsigned short kspace_encoding_step_2;
+  };
+
+  struct ParallelImaging
+  {
+    AccelerationFactor accelerationFactor;
+    Optional<std::string> calibrationMode;
+    Optional<std::string> interleavingDimension;
+  };
+
+  struct Encoding
+  {
+    EncodingSpace encodedSpace;
+    EncodingSpace reconSpace;
+    EncodingLimits encodingLimits;
+    std::string trajectory;
+    Optional<TrajectoryDescription> trajectoryDescription;
+    Optional<ParallelImaging> parallelImaging;
+    Optional<long> echoTrainLength;
+  };
+
+  struct SequenceParameters
+  {
+    Optional<std::vector<float> > TR;
+    Optional<std::vector<float> > TE;
+    Optional<std::vector<float> > TI;
+    Optional<std::vector<float> > flipAngle_deg;
+    Optional<std::string> sequence_type;
+    Optional<std::vector<float> > echo_spacing;
+  };
+
+  struct IsmrmrdHeader
+  {
+    Optional<long> version;
+    Optional<SubjectInformation> subjectInformation;
+    Optional<StudyInformation> studyInformation;
+    Optional<MeasurementInformation> measurementInformation;
+    Optional<AcquisitionSystemInformation> acquisitionSystemInformation;
+    ExperimentalConditions experimentalConditions;
+    std::vector<Encoding> encoding;
+    Optional<SequenceParameters> sequenceParameters;
+    Optional<UserParameters> userParameters;    
+  };
+
+
+
+  EXPORTISMRMRD void deserialize(const char* xml, IsmrmrdHeader& h);
+  EXPORTISMRMRD void serialize(const IsmrmrdHeader& h, std::ostream& o);
+}
+
+/** @} */
+#endif //ISMRMRDXML_H
diff --git a/include/version.in b/include/version.in
new file mode 100644 (file)
index 0000000..43378ae
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef ISMRMRD_VERSION_H
+#define ISMRMRD_VERSION_H
+
+#define ISMRMRD_VERSION_MAJOR @ISMRMRD_VERSION_MAJOR@
+#define ISMRMRD_VERSION_MINOR @ISMRMRD_VERSION_MINOR@
+#define ISMRMRD_VERSION_PATCH @ISMRMRD_VERSION_PATCH@
+#define ISMRMRD_XMLHDR_VERSION @ISMRMRD_VERSION_MINOR@
+#define ISMRMRD_GIT_SHA1_HASH "@ISMRMRD_GIT_SHA1@"
+#define ISMRMRD_DATASET_SUPPORT @ISMRMRD_DATASET_SUPPORT@
+
+#endif /* ISMRMRD_VERSION_H */
diff --git a/libsrc/dataset.c b/libsrc/dataset.c
new file mode 100644 (file)
index 0000000..1379e4d
--- /dev/null
@@ -0,0 +1,1457 @@
+/* Language and Cross platform section for defining types */
+#ifdef __cplusplus
+#include <cstring>
+#include <cstdlib>
+#include <cstdio>
+#else
+/* C99 compiler */
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#endif /* __cplusplus */
+
+#include <hdf5.h>
+#include "ismrmrd/dataset.h"
+
+#ifdef __cplusplus
+namespace ISMRMRD {
+extern "C" {
+#endif
+
+/******************************/
+/* Private (Static) Functions */
+/******************************/
+
+static herr_t walk_hdf5_errors(unsigned int n, const H5E_error2_t *desc, void *client_data)
+{
+    (void)n;
+    (void)client_data;
+    ismrmrd_push_error(desc->file_name, desc->line, desc->func_name, ISMRMRD_HDF5ERROR, desc->desc);
+    return 0;
+}
+
+static bool link_exists(const ISMRMRD_Dataset *dset, const char *link_path) {
+    htri_t val = H5Lexists(dset->fileid, link_path, H5P_DEFAULT);
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return false;
+    }
+
+    if (val < 0 ) {
+        return false;
+    }
+    else if (val) {
+        return true;
+    }
+    else {
+        return false;
+    }
+}
+
+static int create_link(const ISMRMRD_Dataset *dset, const char *link_path) {
+    hid_t lcpl_id, gid;
+
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+    }
+
+    if (link_exists(dset, link_path)) {
+        return ISMRMRD_NOERROR;
+    }
+    else {
+        lcpl_id = H5Pcreate(H5P_LINK_CREATE);
+        H5Pset_create_intermediate_group(lcpl_id, 1);
+        gid = H5Gcreate2(dset->fileid, link_path, lcpl_id, H5P_DEFAULT, H5P_DEFAULT);
+        H5Gclose(gid);
+        H5Pclose(lcpl_id);
+        /* TODO can this thing ever return an error? */
+        return ISMRMRD_NOERROR;
+    }
+}
+
+static char * make_path(const ISMRMRD_Dataset *dset, const char * var) {
+    char *path = NULL;
+    size_t len = 0;
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return NULL;
+    }
+    if (NULL == var) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL var parameter");
+        return NULL;
+    }
+
+    len = strlen(dset->groupname) + strlen(var) + 2;
+    path = (char *)malloc(len);
+    if (path == NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to malloc path");
+        return NULL;
+    }
+    memset(path, 0, len);
+    strcat(path, dset->groupname);
+    strcat(path, "/");
+    strcat(path, var);
+    return path;
+}
+
+static char * append_to_path(const ISMRMRD_Dataset *dset,
+        const char * path, const char * var)
+{
+    size_t len = 0;
+    char *newpath = NULL;
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return NULL;
+    }
+    if (NULL == path) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL path parameter");
+        return NULL;
+    }
+    if (NULL == var) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL var parameter");
+        return NULL;
+    }
+
+    len = strlen(path) + strlen(var) + 2;
+    newpath = (char *) malloc(len);
+    if (newpath == NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to realloc newpath");
+        return NULL;
+    }
+    memset(newpath, 0, len);
+    strcat(newpath, path);
+    strcat(newpath, "/");
+    strcat(newpath, var);
+    return newpath;
+}
+
+static int delete_var(const ISMRMRD_Dataset *dset, const char *var) {
+    int status = ISMRMRD_NOERROR;
+    herr_t h5status;
+    char *path = NULL;
+
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+    }
+
+    path = make_path(dset, var);
+    if (link_exists(dset, path)) {
+        h5status = H5Ldelete(dset->fileid, path, H5P_DEFAULT);
+        if (h5status < 0) {
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            status = ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to delete H5 path");
+        }
+    }
+    free(path);
+    return status;
+}
+
+/*********************************************/
+/* Private (Static) Functions for HDF5 Types */
+/*********************************************/
+typedef struct HDF5_Acquisition
+{
+    ISMRMRD_AcquisitionHeader head;
+    hvl_t traj;
+    hvl_t data;
+} HDF5_Acquisition;
+
+static hid_t get_hdf5type_uint16(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_UINT16);
+    return datatype;
+}
+
+static hid_t get_hdf5type_int16(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_INT16);
+    return datatype;
+}
+
+static hid_t get_hdf5type_uint32(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_UINT32);
+    return datatype;
+}
+    
+static hid_t get_hdf5type_int32(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_INT32);
+    return datatype;
+}
+
+static hid_t get_hdf5type_float(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_FLOAT);
+    return datatype;
+}
+
+static hid_t get_hdf5type_double(void) {
+    hid_t datatype = H5Tcopy(H5T_NATIVE_DOUBLE);
+    return datatype;
+}
+
+/* TODO for all get_hdf5type_xxx functions:
+ *      Check return code of each H5Tinsert call */
+
+static hid_t get_hdf5type_complexfloat(void) {
+    hid_t datatype;
+    herr_t h5status;
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(complex_float_t));
+    h5status = H5Tinsert(datatype, "real", 0, H5T_NATIVE_FLOAT);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get complex float data type");
+    }
+    h5status = H5Tinsert(datatype, "imag", sizeof(float), H5T_NATIVE_FLOAT);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get complex float data type");
+    }
+    return datatype;
+}
+    
+static hid_t get_hdf5type_complexdouble(void) {
+    hid_t datatype;
+    herr_t h5status;
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(complex_double_t));
+    h5status = H5Tinsert(datatype, "real", 0, H5T_NATIVE_DOUBLE);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get complex double data type");
+    }
+    h5status = H5Tinsert(datatype, "imag", sizeof(double), H5T_NATIVE_DOUBLE);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get complex double data type");
+    }
+    return datatype;
+}
+
+static hid_t get_hdf5type_xmlheader(void) {
+    hid_t datatype = H5Tcopy(H5T_C_S1);
+    herr_t h5status = H5Tset_size(datatype, H5T_VARIABLE);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get XML header data type");
+    }
+    return datatype;
+}
+
+static hid_t get_hdf5type_encoding(void) {
+    hid_t datatype;
+    herr_t h5status;
+    hsize_t arraydims[] = {ISMRMRD_USER_INTS};
+    hid_t arraytype;
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(ISMRMRD_EncodingCounters));
+    h5status = H5Tinsert(datatype, "kspace_encode_step_1", HOFFSET(ISMRMRD_EncodingCounters, kspace_encode_step_1),  H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "kspace_encode_step_2", HOFFSET(ISMRMRD_EncodingCounters, kspace_encode_step_2), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "average", HOFFSET(ISMRMRD_EncodingCounters, average), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "slice", HOFFSET(ISMRMRD_EncodingCounters, slice), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "contrast", HOFFSET(ISMRMRD_EncodingCounters, contrast), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "phase", HOFFSET(ISMRMRD_EncodingCounters, phase), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "repetition", HOFFSET(ISMRMRD_EncodingCounters, repetition), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "set", HOFFSET(ISMRMRD_EncodingCounters, set), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "segment", HOFFSET(ISMRMRD_EncodingCounters, segment), H5T_NATIVE_UINT16);
+    arraytype = H5Tarray_create2(H5T_NATIVE_UINT16, 1, arraydims);
+    h5status = H5Tinsert(datatype, "user", HOFFSET(ISMRMRD_EncodingCounters, user), arraytype);
+    if (h5status < 0) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get endoding data type");
+    }
+    H5Tclose(arraytype);
+    return datatype;
+}
+
+static hid_t get_hdf5type_acquisitionheader(void) {
+    hid_t datatype;
+    herr_t h5status;
+    hsize_t arraydims[1];
+    hid_t vartype;
+    
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(ISMRMRD_AcquisitionHeader));
+    h5status = H5Tinsert(datatype, "version", HOFFSET(ISMRMRD_AcquisitionHeader, version), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "flags", HOFFSET(ISMRMRD_AcquisitionHeader, flags), H5T_NATIVE_UINT64);
+    h5status = H5Tinsert(datatype, "measurement_uid", HOFFSET(ISMRMRD_AcquisitionHeader,  measurement_uid), H5T_NATIVE_UINT32);
+    h5status = H5Tinsert(datatype, "scan_counter", HOFFSET(ISMRMRD_AcquisitionHeader, scan_counter), H5T_NATIVE_UINT32);
+    h5status = H5Tinsert(datatype, "acquisition_time_stamp", HOFFSET(ISMRMRD_AcquisitionHeader, acquisition_time_stamp), H5T_NATIVE_UINT32);
+    
+    arraydims[0] = ISMRMRD_PHYS_STAMPS;
+    vartype = H5Tarray_create2(H5T_NATIVE_UINT32, 1, arraydims);
+    h5status = H5Tinsert(datatype, "physiology_time_stamp", HOFFSET(ISMRMRD_AcquisitionHeader, physiology_time_stamp), vartype);
+    H5Tclose(vartype);
+    
+    h5status = H5Tinsert(datatype, "number_of_samples", HOFFSET(ISMRMRD_AcquisitionHeader, number_of_samples), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "available_channels", HOFFSET(ISMRMRD_AcquisitionHeader, available_channels), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "active_channels", HOFFSET(ISMRMRD_AcquisitionHeader, active_channels), H5T_NATIVE_UINT16);
+    
+    arraydims[0] = ISMRMRD_CHANNEL_MASKS;
+    vartype = H5Tarray_create2(H5T_NATIVE_UINT64, 1, arraydims);
+    h5status = H5Tinsert(datatype, "channel_mask", HOFFSET(ISMRMRD_AcquisitionHeader, channel_mask), vartype);
+    H5Tclose(vartype);
+    
+    h5status = H5Tinsert(datatype, "discard_pre", HOFFSET(ISMRMRD_AcquisitionHeader, discard_pre), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "discard_post", HOFFSET(ISMRMRD_AcquisitionHeader, discard_post), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "center_sample", HOFFSET(ISMRMRD_AcquisitionHeader, center_sample), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "encoding_space_ref", HOFFSET(ISMRMRD_AcquisitionHeader, encoding_space_ref), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "trajectory_dimensions", HOFFSET(ISMRMRD_AcquisitionHeader, trajectory_dimensions), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "sample_time_us", HOFFSET(ISMRMRD_AcquisitionHeader, sample_time_us), H5T_NATIVE_FLOAT);
+    
+    arraydims[0] = 3;
+    vartype = H5Tarray_create2(H5T_NATIVE_FLOAT, 1, arraydims);
+    h5status = H5Tinsert(datatype, "position", HOFFSET(ISMRMRD_AcquisitionHeader, position), vartype);
+    h5status = H5Tinsert(datatype, "read_dir", HOFFSET(ISMRMRD_AcquisitionHeader, read_dir), vartype);
+    h5status = H5Tinsert(datatype, "phase_dir", HOFFSET(ISMRMRD_AcquisitionHeader, phase_dir), vartype);
+    h5status = H5Tinsert(datatype, "slice_dir", HOFFSET(ISMRMRD_AcquisitionHeader, slice_dir), vartype);
+    h5status = H5Tinsert(datatype, "patient_table_position", HOFFSET(ISMRMRD_AcquisitionHeader, patient_table_position), vartype);
+    H5Tclose(vartype);
+    
+    vartype = get_hdf5type_encoding();
+    h5status = H5Tinsert(datatype, "idx", HOFFSET(ISMRMRD_AcquisitionHeader, idx), vartype);
+    H5Tclose(vartype);
+    
+    arraydims[0] = ISMRMRD_USER_INTS;
+    vartype = H5Tarray_create2(H5T_NATIVE_INT32, 1, arraydims);
+    h5status = H5Tinsert(datatype, "user_int", HOFFSET(ISMRMRD_AcquisitionHeader, user_int), vartype);
+    H5Tclose(vartype);
+    
+    arraydims[0] = ISMRMRD_USER_FLOATS;
+    vartype = H5Tarray_create2(H5T_NATIVE_FLOAT, 1, arraydims);
+    h5status = H5Tinsert(datatype, "user_float", HOFFSET(ISMRMRD_AcquisitionHeader, user_float), vartype);
+    H5Tclose(vartype);
+    
+    /* Clean up */
+    if (h5status < 0) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get acquisitionheader data type");
+    }
+    
+    return datatype;   
+}
+
+static hid_t get_hdf5type_acquisition(void) {
+    hid_t datatype, vartype, vlvartype;
+    herr_t h5status;
+    
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(HDF5_Acquisition));
+    vartype = get_hdf5type_acquisitionheader();
+    h5status = H5Tinsert(datatype, "head", HOFFSET(HDF5_Acquisition, head), vartype);
+    H5Tclose(vartype);
+    vartype =  get_hdf5type_float();
+    vlvartype = H5Tvlen_create(vartype);
+    h5status = H5Tinsert(datatype, "traj", HOFFSET(HDF5_Acquisition, traj), vlvartype);
+    H5Tclose(vartype);
+    H5Tclose(vlvartype);
+    
+    /* Store acquisition data as an array of floats */
+    vartype = get_hdf5type_float();
+    vlvartype = H5Tvlen_create(vartype);
+    h5status = H5Tinsert(datatype, "data", HOFFSET(HDF5_Acquisition, data), vlvartype);
+    H5Tclose(vartype);
+    H5Tclose(vlvartype);
+    
+    if (h5status < 0) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get acquisition data type");
+    }
+    
+    return datatype;
+}
+
+static hid_t get_hdf5type_imageheader(void) {
+    hid_t datatype;
+    herr_t h5status;
+    hsize_t arraydims[1];
+    hid_t vartype;
+    
+    datatype = H5Tcreate(H5T_COMPOUND, sizeof(ISMRMRD_ImageHeader));
+    h5status = H5Tinsert(datatype, "version", HOFFSET(ISMRMRD_ImageHeader, version), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "data_type", HOFFSET(ISMRMRD_ImageHeader, data_type), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "flags", HOFFSET(ISMRMRD_ImageHeader, flags), H5T_NATIVE_UINT64);
+    h5status = H5Tinsert(datatype, "measurement_uid", HOFFSET(ISMRMRD_ImageHeader,  measurement_uid), H5T_NATIVE_UINT32);
+    arraydims[0] = 3;
+    vartype = H5Tarray_create2(H5T_NATIVE_UINT16, 1, arraydims);
+    h5status = H5Tinsert(datatype, "matrix_size", HOFFSET(ISMRMRD_ImageHeader, matrix_size), vartype);
+    H5Tclose(vartype);
+
+    vartype = H5Tarray_create2(H5T_NATIVE_FLOAT, 1, arraydims);
+    h5status = H5Tinsert(datatype, "field_of_view", HOFFSET(ISMRMRD_ImageHeader, field_of_view), vartype);
+    h5status = H5Tinsert(datatype, "channels", HOFFSET(ISMRMRD_ImageHeader, channels), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "position", HOFFSET(ISMRMRD_ImageHeader, position), vartype);
+    h5status = H5Tinsert(datatype, "read_dir", HOFFSET(ISMRMRD_ImageHeader, read_dir), vartype);
+    h5status = H5Tinsert(datatype, "phase_dir", HOFFSET(ISMRMRD_ImageHeader, phase_dir), vartype);
+    h5status = H5Tinsert(datatype, "slice_dir", HOFFSET(ISMRMRD_ImageHeader, slice_dir), vartype);
+    h5status = H5Tinsert(datatype, "patient_table_position", HOFFSET(ISMRMRD_ImageHeader, patient_table_position), vartype);
+    H5Tclose(vartype);
+
+    h5status = H5Tinsert(datatype, "average", HOFFSET(ISMRMRD_ImageHeader, average), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "slice", HOFFSET(ISMRMRD_ImageHeader, slice), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "contrast", HOFFSET(ISMRMRD_ImageHeader, contrast), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "phase", HOFFSET(ISMRMRD_ImageHeader, phase), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "repetition", HOFFSET(ISMRMRD_ImageHeader, repetition), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "set", HOFFSET(ISMRMRD_ImageHeader, set), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "acquisition_time_stamp", HOFFSET(ISMRMRD_ImageHeader, acquisition_time_stamp), H5T_NATIVE_UINT32);
+    arraydims[0] = ISMRMRD_PHYS_STAMPS;
+    vartype = H5Tarray_create2(H5T_NATIVE_UINT32, 1, arraydims);
+    h5status = H5Tinsert(datatype, "physiology_time_stamp", HOFFSET(ISMRMRD_ImageHeader, physiology_time_stamp), vartype);
+    H5Tclose(vartype);
+
+    h5status = H5Tinsert(datatype, "image_type", HOFFSET(ISMRMRD_ImageHeader, image_type), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "image_index", HOFFSET(ISMRMRD_ImageHeader, image_index), H5T_NATIVE_UINT16);
+    h5status = H5Tinsert(datatype, "image_series_index", HOFFSET(ISMRMRD_ImageHeader, image_series_index), H5T_NATIVE_UINT16);
+    
+    arraydims[0] = ISMRMRD_USER_INTS;
+    vartype = H5Tarray_create2(H5T_NATIVE_INT32, 1, arraydims);
+    h5status = H5Tinsert(datatype, "user_int", HOFFSET(ISMRMRD_ImageHeader, user_int), vartype);
+    H5Tclose(vartype);
+
+    arraydims[0] = ISMRMRD_USER_FLOATS;
+    vartype = H5Tarray_create2(H5T_NATIVE_FLOAT, 1, arraydims);
+    h5status = H5Tinsert(datatype, "user_float", HOFFSET(ISMRMRD_ImageHeader, user_float), vartype);
+    H5Tclose(vartype);
+
+    h5status = H5Tinsert(datatype, "attribute_string_len", HOFFSET(ISMRMRD_ImageHeader, attribute_string_len), H5T_NATIVE_UINT32);
+    
+    /* Clean up */
+    if (h5status < 0) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get imageheader data type");
+    }
+    
+    return datatype;   
+}
+
+static hid_t get_hdf5type_image_attribute_string(void) {
+    hid_t datatype = H5Tcopy(H5T_C_S1);
+    herr_t h5status = H5Tset_size(datatype, H5T_VARIABLE);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed get image attribute string data type");
+    }
+    return datatype;
+}
+    
+static hid_t get_hdf5type_ndarray(uint16_t data_type) {
+    
+    hid_t hdfdatatype = -1;
+    switch (data_type) {
+        case ISMRMRD_USHORT:
+            hdfdatatype = get_hdf5type_uint16();
+            break;
+        case ISMRMRD_SHORT:
+            hdfdatatype = get_hdf5type_int16();
+            break;
+        case ISMRMRD_UINT:
+            hdfdatatype = get_hdf5type_uint32();
+            break;
+        case ISMRMRD_INT:
+            hdfdatatype = get_hdf5type_int32();
+            break;
+        case ISMRMRD_FLOAT:
+            hdfdatatype = get_hdf5type_float();
+            break;
+        case ISMRMRD_DOUBLE:
+            hdfdatatype = get_hdf5type_double();
+            break;
+        case ISMRMRD_CXFLOAT:
+            hdfdatatype = get_hdf5type_complexfloat();
+            break;
+        case ISMRMRD_CXDOUBLE:
+            hdfdatatype = get_hdf5type_complexdouble();
+            break;
+        default :
+            ISMRMRD_PUSH_ERR(ISMRMRD_TYPEERROR, "Failed to get HDF5 data type.");
+    }
+    return hdfdatatype;
+}
+
+static uint16_t get_ndarray_data_type(hid_t hdf5type) {
+
+    uint16_t dtype = 0;
+    hid_t t;
+    
+    t = get_hdf5type_uint16();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_USHORT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_int16();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_SHORT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_uint32();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_UINT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_int32();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_INT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_float();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_FLOAT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_double();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_DOUBLE;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_complexfloat();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_CXFLOAT;
+    }
+    H5Tclose(t);
+
+    t = get_hdf5type_complexdouble();
+    if (H5Tequal(hdf5type, t)) {
+        dtype = ISMRMRD_CXDOUBLE;
+    }
+    H5Tclose(t);
+
+    if (dtype == 0) {
+        //ISMRMRD_PUSH_ERR(ISMRMRD_TYPEERROR, "Failed to get data type from HDF5 data type.");
+    }
+    
+    return dtype;
+}
+
+static uint32_t get_number_of_elements(const ISMRMRD_Dataset *dset, const char * path)
+{
+    herr_t h5status;
+    uint32_t num;
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return 0;
+    }
+
+    if (link_exists(dset, path)) {
+        hid_t dataset, dataspace;
+        hsize_t rank, *dims, *maxdims;
+        dataset = H5Dopen2(dset->fileid, path, H5P_DEFAULT);
+        dataspace = H5Dget_space(dataset);
+        rank = H5Sget_simple_extent_ndims(dataspace);
+        dims = (hsize_t *) malloc(rank*sizeof(hsize_t));
+        maxdims = (hsize_t *) malloc(rank*sizeof(hsize_t));
+        h5status = H5Sget_simple_extent_dims(dataspace, dims, maxdims);
+        num = dims[0];
+        free(dims);
+        free(maxdims);
+        h5status = H5Sclose(dataspace);
+        h5status= H5Dclose(dataset);
+        if (h5status < 0) {
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR,
+                    "Failed to get number of elements in vector.");
+        }
+    }
+    else {
+        /* none */
+        num = 0;
+    }
+    
+    return num;
+}
+
+static int append_element(const ISMRMRD_Dataset * dset, const char * path,
+        void * elem, const hid_t datatype,
+        const uint16_t ndim, const size_t *dims)
+{
+    hid_t dataset, dataspace, props, filespace, memspace;
+    herr_t h5status = 0;
+    hsize_t *hdfdims = NULL, *ext_dims = NULL, *offset = NULL, *maxdims = NULL, *chunk_dims = NULL;
+    int n = 0, rank = 0;
+    
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+    }
+
+    /* Check the path and find rank */
+    if (link_exists(dset, path)) {
+        /* open dataset */
+        dataset = H5Dopen2(dset->fileid, path, H5P_DEFAULT);
+        /* TODO check that the header dataset's datatype is correct */
+        dataspace = H5Dget_space(dataset);
+        rank = H5Sget_simple_extent_ndims(dataspace);
+        if (rank != ndim + 1) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Dimensions are incorrect.");
+        }
+    } else {
+        dataset   = -1; /* will be initialized below */
+        dataspace = -1; /* will be initialized below */
+        rank = ndim + 1;
+    }
+
+    hdfdims = (hsize_t *) malloc(rank * sizeof(hsize_t));
+    maxdims = (hsize_t *) malloc(rank * sizeof(hsize_t));
+    offset = (hsize_t *) malloc(rank * sizeof(hsize_t));
+    ext_dims = (hsize_t *) malloc(rank * sizeof(hsize_t));
+    chunk_dims = (hsize_t *) malloc(rank * sizeof(hsize_t));
+
+    /* extend or create if needed, and select the last block */
+    if (link_exists(dset, path)) {
+        h5status = H5Sget_simple_extent_dims(dataspace, hdfdims, maxdims);
+        for (n = 0; n<ndim; n++) {
+            if (dims[n] != hdfdims[n+1]) {
+                free(hdfdims);
+                free(ext_dims);
+                free(offset);
+                free(maxdims);
+                free(chunk_dims);
+                return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Dimensions are incorrect.");
+            }
+        }
+        /* extend it by one */
+        hdfdims[0] += 1;
+        h5status = H5Dset_extent(dataset, hdfdims);
+        /* Select the last block */
+        ext_dims[0] = 1;
+        for (n = 0; n < ndim; n++) {
+            offset[n + 1] = 0;
+            ext_dims[n + 1] = dims[n];
+        }
+    } else {
+        hdfdims[0] = 1;
+        maxdims[0] = H5S_UNLIMITED;
+        ext_dims[0] = 1;
+        chunk_dims[0] = 1;
+        for (n = 0; n < ndim; n++) {
+            hdfdims[n + 1] = dims[n];
+            maxdims[n + 1] = dims[n];
+            offset[n + 1] = 0;
+            ext_dims[n + 1] = dims[n];
+            chunk_dims[n + 1] = dims[n];
+        }
+        dataspace = H5Screate_simple(rank, hdfdims, maxdims);
+        props = H5Pcreate(H5P_DATASET_CREATE);
+        /* enable chunking so that the dataset is extensible */
+        h5status = H5Pset_chunk (props, rank, chunk_dims);
+        /* create */
+        dataset = H5Dcreate2(dset->fileid, path, datatype, dataspace, H5P_DEFAULT, props,  H5P_DEFAULT);
+        if (dataset < 0) {
+            free(hdfdims);
+            free(ext_dims);
+            free(offset);
+            free(maxdims);
+            free(chunk_dims);
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to create dataset");
+        }
+        h5status = H5Pclose(props);
+        if (h5status < 0) {
+            free(hdfdims);
+            free(ext_dims);
+            free(offset);
+            free(maxdims);
+            free(chunk_dims);
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close property list");
+        }
+    }
+
+    /* Select the last block */
+    offset[0] = hdfdims[0]-1;
+    filespace = H5Dget_space(dataset);
+    h5status  = H5Sselect_hyperslab (filespace, H5S_SELECT_SET, offset, NULL, ext_dims, NULL);
+    memspace = H5Screate_simple(rank, ext_dims, NULL);
+
+    free(hdfdims);
+    free(ext_dims);
+    free(offset);
+    free(maxdims);
+    free(chunk_dims);
+
+    /* Write it */
+    /* since this is a 1 element array we can just pass the pointer to the header */
+    h5status = H5Dwrite(dataset, datatype, memspace, filespace, H5P_DEFAULT, elem);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to write dataset");
+    }
+
+    /* Clean up */
+    h5status = H5Sclose(dataspace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close dataspace");
+    }
+    h5status = H5Sclose(filespace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close filespace");
+    }
+    h5status = H5Sclose(memspace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close memspace");
+    }
+    h5status = H5Dclose(dataset);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close dataset");
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+static int get_array_properties(const ISMRMRD_Dataset *dset, const char *path,
+        uint16_t *ndim, size_t dims[ISMRMRD_NDARRAY_MAXDIM],
+        uint16_t *data_type)
+{
+    hid_t dataset, filespace, hdf5type;
+    hsize_t *hdfdims = NULL;
+    herr_t h5status = 0;
+    int rank, n;
+
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+
+    /* Check path existence */
+    if (!link_exists(dset, path)) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Path to element not found.");
+    }
+
+    /* open dataset */
+    dataset = H5Dopen2(dset->fileid, path, H5P_DEFAULT);
+
+    /* get the data type */
+    hdf5type = H5Dget_type(dataset);
+
+    /* get the file space */
+    filespace = H5Dget_space(dataset);
+
+    /* get the rank */
+    rank = H5Sget_simple_extent_ndims(filespace);
+
+    /* get the dimensions */
+    hdfdims = (hsize_t *)malloc(rank * sizeof(*hdfdims));
+    h5status = H5Sget_simple_extent_dims(filespace, hdfdims, NULL);
+
+    /* set the return values - permute dimensions */
+    *data_type = get_ndarray_data_type(hdf5type);
+    *ndim = rank;
+    for (n=0; n<rank; n++) {
+        dims[n] = hdfdims[rank-n-1];
+    }
+
+    free(hdfdims);
+
+    /* clean up */
+    h5status = H5Tclose(hdf5type);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close datatype.");
+    }
+    h5status = H5Sclose(filespace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close filespace");
+    }
+    h5status = H5Dclose(dataset);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close dataset.");
+    }
+
+    return ISMRMRD_NOERROR;
+
+}
+
+int read_element(const ISMRMRD_Dataset *dset, const char *path, void *elem,
+        const hid_t datatype, const uint32_t index)
+{
+    hid_t dataset, filespace, memspace;
+    hsize_t *hdfdims = NULL, *offset = NULL, *count = NULL;
+    herr_t h5status = 0;
+    int rank = 0;
+    int n;
+    int ret_code = ISMRMRD_NOERROR;
+
+
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+
+    /* Check path existence */
+    if (!link_exists(dset, path)) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Path to element not found.");
+    }
+
+    /* open dataset */
+    dataset = H5Dopen2(dset->fileid, path, H5P_DEFAULT);
+
+    /* TODO check that the dataset's datatype is correct */
+    filespace = H5Dget_space(dataset);
+
+    rank = H5Sget_simple_extent_ndims(filespace);
+
+    hdfdims = (hsize_t *)malloc(rank * sizeof(*hdfdims));
+    offset = (hsize_t *)malloc(rank * sizeof(*offset));
+    count = (hsize_t *)malloc(rank * sizeof(*count));
+
+    h5status = H5Sget_simple_extent_dims(filespace, hdfdims, NULL);
+
+    if (index >= hdfdims[0]) {
+        ret_code = ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Index out of range.");
+        goto cleanup;
+    }
+
+    offset[0] = index;
+    count[0] = 1;
+    for (n=1; n< rank; n++) {
+        offset[n] = 0;
+        count[n] = hdfdims[n];
+    }
+
+    h5status = H5Sselect_hyperslab(filespace, H5S_SELECT_SET, offset, NULL, count, NULL);
+
+    /* create space for one */
+    memspace = H5Screate_simple(rank, count, NULL);
+
+    h5status = H5Dread(dataset, datatype, memspace, filespace, H5P_DEFAULT, elem);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ret_code = ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to read from dataset.");
+        goto cleanup;
+    }
+
+    h5status = H5Sclose(filespace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ret_code = ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close filespace.");
+        goto cleanup;
+    }
+    h5status = H5Sclose(memspace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ret_code = ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close memspace.");
+        goto cleanup;
+    }
+    h5status = H5Dclose(dataset);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ret_code = ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close dataset.");
+        goto cleanup;
+    }
+
+cleanup:
+    free(count);
+    free(offset);
+    free(hdfdims);
+    return ret_code;
+}
+
+/********************/
+/* Public functions */
+/********************/
+int ismrmrd_init_dataset(ISMRMRD_Dataset *dset, const char *filename,
+        const char *groupname)
+{
+    if (NULL == dset) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+    }
+
+    /* Disable HDF5 automatic error printing */
+    H5Eset_auto2(H5E_DEFAULT, NULL, NULL);
+
+    dset->filename = (char *) malloc(strlen(filename) + 1);
+    if (dset->filename == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to malloc dataset filename");
+    }
+    strcpy(dset->filename, filename);
+
+    dset->groupname = (char *) malloc(strlen(groupname) + 1);
+    if (dset->groupname == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to malloc dataset groupname");
+    }
+    strcpy(dset->groupname, groupname);
+
+    dset->fileid = 0;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_open_dataset(ISMRMRD_Dataset *dset, const bool create_if_needed) {
+    /* TODO add a mode for clobbering the dataset if it exists. */
+    hid_t fileid;
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return false;
+    }
+
+    /* Try opening the file */
+    /* Note the is_hdf5 function doesn't work well when trying to open multiple files */
+    fileid = H5Fopen(dset->filename, H5F_ACC_RDWR, H5P_DEFAULT);
+
+    if (fileid > 0) {
+        dset->fileid = fileid;
+    }
+    else if (create_if_needed == false) {
+        /*Try opening the file as read-only*/
+        fileid = H5Fopen(dset->filename, H5F_ACC_RDONLY, H5P_DEFAULT);
+        if (fileid > 0) {
+            dset->fileid = fileid;
+        }
+        else{
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            /* Some sort of error opening the file - Maybe it doesn't exist? */
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to open file.");
+        }
+    }
+    else {
+        /* Try creating a new file using the default properties. */
+        /* this will be readwrite */
+        fileid = H5Fcreate(dset->filename, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
+        if (fileid > 0) {
+            dset->fileid = fileid;
+        }
+        else {
+            /* Error opening the file */
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to open file.");
+        }
+    }
+    /* Open the existing dataset */
+    /* ensure that /groupname exists */
+    create_link(dset, dset->groupname);
+
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_close_dataset(ISMRMRD_Dataset *dset) {
+    herr_t h5status;
+
+    if (NULL == dset) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "NULL Dataset parameter");
+        return false;
+    }
+
+    if (dset->filename != NULL) {
+        free(dset->filename);
+        dset->filename = NULL;
+    }
+
+    if (dset->groupname != NULL) {
+        free(dset->groupname);
+        dset->groupname = NULL;
+    }
+
+    /* Check for a valid fileid before trying to close the file */
+    if (dset->fileid > 0) {
+        h5status = H5Fclose (dset->fileid);
+        dset->fileid = 0;
+        if (h5status < 0) {
+            H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+            return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close dataset.");
+        }
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_write_header(const ISMRMRD_Dataset *dset, const char *xmlstring) {
+    hid_t dataset, dataspace, datatype, props;
+    hsize_t dims[] = {1};
+    herr_t h5status;
+    void *buff[1];
+    char * path;
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+
+    if (xmlstring==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "xmlstring should not be NULL.");
+    }
+
+    /* The path to the xml header */
+    path = make_path(dset, "xml");
+
+    /* Delete the old header if it exists */
+    h5status = delete_var(dset, "xml");
+
+    /* Create a new dataset for the xmlstring */
+    /* i.e. create the memory type, data space, and data set */
+    dataspace = H5Screate_simple(1, dims, NULL);
+    datatype = get_hdf5type_xmlheader();
+    props = H5Pcreate (H5P_DATASET_CREATE);
+    dataset = H5Dcreate2(dset->fileid, path, datatype, dataspace, H5P_DEFAULT, props,  H5P_DEFAULT);
+    free(path);
+
+    /* Write it out */
+    /* We have to wrap the xmlstring in an array */
+    buff[0] = (void *) xmlstring;  /* safe to get rid of const the type */
+    h5status = H5Dwrite(dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, buff);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to write xml string to dataset");
+    }
+
+    /* Clean up */
+    h5status = H5Pclose(props);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close property list.");
+    }
+    h5status = H5Tclose(datatype);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close datatype.");
+    }
+    h5status = H5Sclose(dataspace);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close dataspace.");
+    }
+    h5status = H5Dclose(dataset);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close dataset.");
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+char * ismrmrd_read_header(const ISMRMRD_Dataset *dset) {
+    hid_t dataset, datatype;
+    herr_t h5status;
+    char* xmlstring = NULL;
+    char* path = NULL;
+
+    if (dset==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+        return NULL;
+    }
+
+    /* The path to the xml header */
+    path = make_path(dset, "xml");
+
+    if (!link_exists(dset, path)) {
+        /* No XML String found */
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "No XML Header found.");
+        goto cleanup_path;
+    }
+
+    dataset = H5Dopen2(dset->fileid, path, H5P_DEFAULT);
+    datatype = get_hdf5type_xmlheader();
+    /* Read it into a 1D buffer*/
+    h5status = H5Dread(dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &xmlstring);
+    if (h5status < 0 || xmlstring == NULL) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to read header.");
+        goto cleanup_path;
+    }
+
+    /* Clean up */
+    h5status = H5Tclose(datatype);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close XML header HDF5 datatype.");
+        goto cleanup_xmlstring;
+    }
+    h5status = H5Dclose(dataset);
+    if (h5status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to close XML header HDF5 dataset.");
+        goto cleanup_xmlstring;
+    }
+
+    goto cleanup_path;
+
+cleanup_xmlstring:
+    free(xmlstring);
+    xmlstring = NULL;
+cleanup_path:
+    free(path);
+    return xmlstring;
+}
+
+uint32_t ismrmrd_get_number_of_acquisitions(const ISMRMRD_Dataset *dset) {
+    char *path;
+    uint32_t numacq;
+
+    if (dset==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+        return 0;
+    }
+    /* The path to the acqusition data */    
+    path = make_path(dset, "data");
+    numacq = get_number_of_elements(dset, path);
+    free(path);
+    return numacq;
+}
+
+int ismrmrd_append_acquisition(const ISMRMRD_Dataset *dset, const ISMRMRD_Acquisition *acq) {
+    int status;
+    char *path;
+    hid_t datatype;
+    HDF5_Acquisition hdf5acq[1];
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (acq==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Acquisition pointer should not be NULL.");
+    }
+
+    /* The path to the acqusition data */    
+    path = make_path(dset, "data");
+
+    /* The acquisition datatype */
+    datatype = get_hdf5type_acquisition();
+
+    /* Create the HDF5 version of the acquisition */
+    hdf5acq[0].head = acq->head;
+    hdf5acq[0].traj.len = acq->head.number_of_samples * acq->head.trajectory_dimensions;
+    hdf5acq[0].traj.p = acq->traj;
+    hdf5acq[0].data.len = 2 * acq->head.number_of_samples * acq->head.active_channels;
+    hdf5acq[0].data.p = acq->data;
+
+    /* Write it */
+    status = append_element(dset, path, hdf5acq, datatype, 0, NULL);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append acquisition.");
+    }
+
+    free(path);
+
+    /* Clean up */
+    status = H5Tclose(datatype);
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_read_acquisition(const ISMRMRD_Dataset *dset, uint32_t index, ISMRMRD_Acquisition *acq)
+{
+    hid_t datatype;
+    herr_t status;
+    HDF5_Acquisition hdf5acq;
+    char *path;
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (acq==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Acquisition pointer should not be NULL.");
+    }
+
+    /* The path to the acquisition data */
+    path = make_path(dset, "data");
+
+    /* The acquisition datatype */
+    datatype = get_hdf5type_acquisition();
+
+    status = read_element(dset, path, &hdf5acq, datatype, index);
+    memcpy(&acq->head, &hdf5acq.head, sizeof(ISMRMRD_AcquisitionHeader));
+    ismrmrd_make_consistent_acquisition(acq);
+    memcpy(acq->traj, hdf5acq.traj.p, ismrmrd_size_of_acquisition_traj(acq));
+    memcpy(acq->data, hdf5acq.data.p, ismrmrd_size_of_acquisition_data(acq));
+
+    /* clean up */
+    free(path);
+    free(hdf5acq.traj.p);
+    free(hdf5acq.data.p);
+
+    status = H5Tclose(datatype);
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_append_image(const ISMRMRD_Dataset *dset, const char *varname, const ISMRMRD_Image *im) {
+    int status;
+    hid_t datatype;
+    char *path, *headerpath, *attrpath, *datapath;
+    size_t dims[4];
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (varname==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+    }
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Image pointer should not be NULL.");
+    }
+
+    /* The group for this set of images */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+    /* Make sure the path exists */
+    create_link(dset, path);        
+
+    /* Handle the header */
+    headerpath = append_to_path(dset, path, "header");
+    datatype = get_hdf5type_imageheader();
+    status = append_element(dset, headerpath, (void *) &im->head, datatype, 0, NULL);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append image header.");
+    }
+    status = H5Tclose(datatype);
+    free(headerpath);
+
+    /* Handle the attribute string */
+    attrpath = append_to_path(dset, path, "attributes");
+    datatype = get_hdf5type_image_attribute_string();
+    status = append_element(dset, attrpath, (void *) &im->attribute_string, datatype, 0, NULL);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append image attribute string.");
+    }
+    status = H5Tclose(datatype);
+    free(attrpath);
+
+    /* Handle the data */
+    datapath = append_to_path(dset, path, "data");
+    datatype = get_hdf5type_ndarray(im->head.data_type);
+    /* permute the dimensions in the hdf5 file */
+    dims[3] = im->head.matrix_size[0];
+    dims[2] = im->head.matrix_size[1];
+    dims[1] = im->head.matrix_size[2];
+    dims[0] = im->head.channels;
+    status = append_element(dset, datapath, im->data, datatype, 4, dims);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append image data.");
+    }
+    status = H5Tclose(datatype);
+    free(datapath);
+
+    /* Final cleanup */
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+    free(path);
+
+    return ISMRMRD_NOERROR;
+}
+
+uint32_t ismrmrd_get_number_of_images(const ISMRMRD_Dataset *dset, const char *varname)
+{
+    char *path, *headerpath;
+    uint32_t numimages;
+
+    if (dset==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+        return 0;
+    }
+    if (varname==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+        return 0;
+    }
+    /* The group for this set of images */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+    /* The path to the acqusition image headers */
+    headerpath = append_to_path(dset, path, "header");
+    numimages = get_number_of_elements(dset, headerpath);
+    free(headerpath);
+    free(path);
+    return numimages;
+}
+
+
+int ismrmrd_read_image(const ISMRMRD_Dataset *dset, const char *varname,
+        const uint32_t index, ISMRMRD_Image *im) {
+
+    int status;
+    hid_t datatype;
+    char *path, *headerpath, *attrpath, *datapath, *attr_string;
+    uint32_t numims;
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (varname==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+    }
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Image pointer should not be NULL.");
+    }
+
+    numims = ismrmrd_get_number_of_images(dset, varname);
+
+    if (index > numims) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Index requested exceeds number of images in the dataset.");
+    }
+
+    /* The group for this set of images */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+
+    /* Handle the header */
+    headerpath = append_to_path(dset, path, "header");
+    datatype = get_hdf5type_imageheader();
+    status = read_element(dset, headerpath, (void *) &im->head, datatype, index);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to read image header.");
+    }
+    free(headerpath);
+    H5Tclose(datatype);
+
+    /* Allocate the memory for the attribute string and the data */
+    ismrmrd_make_consistent_image(im);
+
+    /* Handle the attribute string */
+    attrpath = append_to_path(dset, path, "attributes");
+    datatype = get_hdf5type_image_attribute_string();
+    status = read_element(dset, attrpath, (void *) &attr_string, datatype, index);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to read image attribute string.");
+    }
+    free(attrpath);
+    H5Tclose(datatype);
+
+    /* copy the attribute string read from the file into the Image */
+    memcpy(im->attribute_string, attr_string, ismrmrd_size_of_image_attribute_string(im));
+    free(attr_string);
+
+    /* Handle the data */
+    datapath = append_to_path(dset, path, "data");
+    datatype = get_hdf5type_ndarray(im->head.data_type);
+    status = read_element(dset, datapath, im->data, datatype, index);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to read image data.");
+    }
+    free(datapath);
+
+    /* Final cleanup */
+    status = H5Tclose(datatype);
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+    free(path);
+
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_append_array(const ISMRMRD_Dataset *dset, const char *varname, const ISMRMRD_NDArray *arr) {
+    int status;
+    hid_t datatype;
+    uint16_t ndim;
+    size_t *dims;
+    int n;
+    char *path;
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (varname==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+    }
+    if (arr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Array pointer should not be NULL.");
+    }
+
+    /* The group for this set */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+
+    /* Handle the data */
+    datatype = get_hdf5type_ndarray(arr->data_type);
+    ndim = arr->ndim;
+    dims = (size_t *) malloc(ndim*sizeof(size_t));
+    /* permute the dimensions in the hdf5 file */
+    for (n=0; n<ndim; n++) {
+        dims[ndim-n-1] = arr->dims[n];
+    }
+    status = append_element(dset, path, arr->data, datatype, ndim, dims);
+    if (status != ISMRMRD_NOERROR) {
+        free(dims);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append array.");
+    }
+
+    /* Final cleanup */
+    free(dims);
+    status = H5Tclose(datatype);
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+    free(path);
+
+    return ISMRMRD_NOERROR;
+}
+
+uint32_t ismrmrd_get_number_of_arrays(const ISMRMRD_Dataset *dset, const char *varname) {
+    char *path;
+    uint32_t numarrays;
+
+    if (dset==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+        return 0;
+    }
+    if (varname==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+        return 0;
+    }
+
+    /* The group for this set */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+    numarrays = get_number_of_elements(dset, path);
+    free(path);
+    return numarrays;
+}
+
+int ismrmrd_read_array(const ISMRMRD_Dataset *dset, const char *varname,
+        const uint32_t index, ISMRMRD_NDArray *arr) {    
+    int status;
+    hid_t datatype;
+    char *path;
+
+    if (dset==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Dataset pointer should not be NULL.");
+    }
+    if (varname==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Varname should not be NULL.");
+    }
+    if (arr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Array pointer should not be NULL.");
+    }
+
+    /* The group for this set */
+    /* /groupname/varname */
+    path = make_path(dset, varname);
+
+    /* get the array properties */
+    get_array_properties(dset, path, &arr->ndim, arr->dims, &arr->data_type);
+    datatype = get_hdf5type_ndarray(arr->data_type);
+
+    /* allocate the memory */
+    ismrmrd_make_consistent_ndarray(arr);
+
+    /* read the data */
+    status = read_element(dset, path, arr->data, datatype, index);
+    if (status != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_FILEERROR, "Failed to append array.");
+    }
+
+    /* Final cleanup */
+    status = H5Tclose(datatype);
+    if (status < 0) {
+        H5Ewalk2(H5E_DEFAULT, H5E_WALK_UPWARD, walk_hdf5_errors, NULL);
+        return ISMRMRD_PUSH_ERR(ISMRMRD_HDF5ERROR, "Failed to close datatype.");
+    }
+    free(path);
+
+    return ISMRMRD_NOERROR;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+} /* ISMRMRD namespace */
+#endif
diff --git a/libsrc/dataset.cpp b/libsrc/dataset.cpp
new file mode 100644 (file)
index 0000000..a87bdfc
--- /dev/null
@@ -0,0 +1,181 @@
+#include "ismrmrd/dataset.h"
+
+// for memcpy and free in older compilers
+#include <string.h>
+#include <stdlib.h>
+#include <stdexcept>
+
+namespace ISMRMRD {
+//
+// Dataset class implementation
+//
+// Constructor
+Dataset::Dataset(const char* filename, const char* groupname, bool create_file_if_needed)
+{
+    // TODO error checking and exception throwing
+    // Initialize the dataset
+    int status;
+    status = ismrmrd_init_dataset(&dset_, filename, groupname);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+    // Open the file
+    status = ismrmrd_open_dataset(&dset_, create_file_if_needed);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Destructor
+Dataset::~Dataset()
+{
+    int status = ismrmrd_close_dataset(&dset_);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// XML Header
+void Dataset::writeHeader(const std::string &xmlstring)
+{
+    int status = ismrmrd_write_header(&dset_, xmlstring.c_str());
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+void Dataset::readHeader(std::string& xmlstring){
+    char * temp = ismrmrd_read_header(&dset_);
+    if (NULL == temp) {
+        throw std::runtime_error(build_exception_string());
+    } else {
+        xmlstring = std::string(temp);
+        free(temp);
+    }
+}
+
+// Acquisitions
+void Dataset::appendAcquisition(const Acquisition &acq)
+{
+    int status = ismrmrd_append_acquisition(&dset_, reinterpret_cast<const ISMRMRD_Acquisition*>(&acq));
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+void Dataset::readAcquisition(uint32_t index, Acquisition & acq) {
+    int status = ismrmrd_read_acquisition(&dset_, index, reinterpret_cast<ISMRMRD_Acquisition*>(&acq));
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+
+uint32_t Dataset::getNumberOfAcquisitions()
+{
+    uint32_t num = ismrmrd_get_number_of_acquisitions(&dset_);
+    return num;
+}
+
+// Images
+template <typename T>void Dataset::appendImage(const std::string &var, const Image<T> &im)
+{
+    int status = ismrmrd_append_image(&dset_, var.c_str(), &im.im);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+void Dataset::appendImage(const std::string &var, const ISMRMRD_Image *im)
+{
+    int status = ismrmrd_append_image(&dset_, var.c_str(), im);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+// Specific instantiations
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<uint16_t> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<int16_t> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<uint32_t> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<int32_t> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<float> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<double> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<complex_float_t> &im);
+template EXPORTISMRMRD void Dataset::appendImage(const std::string &var, const Image<complex_double_t> &im);
+
+
+template <typename T> void Dataset::readImage(const std::string &var, uint32_t index, Image<T> &im) {
+    int status = ismrmrd_read_image(&dset_, var.c_str(), index, &im.im);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Specific instantiations
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<uint16_t> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<int16_t> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<uint32_t> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<int32_t> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<float> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<double> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<complex_float_t> &im);
+template EXPORTISMRMRD void Dataset::readImage(const std::string &var, uint32_t index, Image<complex_double_t> &im);
+
+uint32_t Dataset::getNumberOfImages(const std::string &var)
+{
+    uint32_t num =  ismrmrd_get_number_of_images(&dset_, var.c_str());
+    return num;
+}
+
+
+// NDArrays
+template <typename T> void Dataset::appendNDArray(const std::string &var, const NDArray<T> &arr)
+{
+    int status = ismrmrd_append_array(&dset_, var.c_str(), &arr.arr);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Specific instantiations
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<uint16_t> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<int16_t> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<uint32_t> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<int32_t> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<float> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<double> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<complex_float_t> &arr);
+template EXPORTISMRMRD void Dataset::appendNDArray(const std::string &var, const NDArray<complex_double_t> &arr);
+
+void Dataset::appendNDArray(const std::string &var, const ISMRMRD_NDArray *arr)
+{
+    int status = ismrmrd_append_array(&dset_, var.c_str(), arr);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<T> &arr) {
+    int status = ismrmrd_read_array(&dset_, var.c_str(), index, &arr.arr);
+    if (status != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Specific instantiations
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<uint16_t> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<int16_t> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<uint32_t> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<int32_t> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<float> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<double> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<complex_float_t> &arr);
+template EXPORTISMRMRD void Dataset::readNDArray(const std::string &var, uint32_t index, NDArray<complex_double_t> &arr);
+
+uint32_t Dataset::getNumberOfNDArrays(const std::string &var)
+{
+    uint32_t num = ismrmrd_get_number_of_arrays(&dset_, var.c_str());
+    return num;
+}
+
+} // namespace ISMRMRD
diff --git a/libsrc/ismrmrd.c b/libsrc/ismrmrd.c
new file mode 100644 (file)
index 0000000..081b33e
--- /dev/null
@@ -0,0 +1,767 @@
+#include <string.h>
+#include <stdlib.h>
+
+/* Language and Cross platform section for defining types */
+#ifdef __cplusplus
+#include <cmath>
+#include <cstdio>
+
+#else
+/* C99 compiler */
+#include <math.h>
+#include <stdio.h>
+
+#endif /* __cplusplus */
+
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+
+#ifdef __cplusplus
+namespace ISMRMRD {
+extern "C" {
+#endif
+
+/* Error handling prototypes */
+typedef struct ISMRMRD_error_node {
+    struct ISMRMRD_error_node *next;
+    char *file;
+    char *func;
+    char *msg;
+    int line;
+    int code;
+} ISMRMRD_error_node_t;
+
+static void ismrmrd_error_default(const char *file, int line,
+        const char *func, int code, const char *msg);
+static ISMRMRD_error_node_t *error_stack_head = NULL;
+static ismrmrd_error_handler_t ismrmrd_error_handler = ismrmrd_error_default;
+
+
+/* Acquisition functions */
+int ismrmrd_init_acquisition_header(ISMRMRD_AcquisitionHeader *hdr) {
+    if (hdr == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+
+    memset(hdr, 0, sizeof(ISMRMRD_AcquisitionHeader));
+    hdr->version = ISMRMRD_VERSION_MAJOR;
+    hdr->number_of_samples = 0;
+    hdr->available_channels = 1;
+    hdr->active_channels = 1;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_init_acquisition(ISMRMRD_Acquisition *acq) {
+    if (acq == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    ismrmrd_init_acquisition_header(&acq->head);
+    acq->traj = NULL;
+    acq->data = NULL;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_cleanup_acquisition(ISMRMRD_Acquisition *acq) {
+    if (acq == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    
+    free(acq->data);
+    acq->data = NULL;
+    free(acq->traj);
+    acq->traj = NULL;
+    return ISMRMRD_NOERROR;
+}
+    
+ISMRMRD_Acquisition * ismrmrd_create_acquisition() {
+    ISMRMRD_Acquisition *acq = (ISMRMRD_Acquisition *) malloc(sizeof(ISMRMRD_Acquisition));
+    if (acq == NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to malloc new ISMRMRD_Acquistion.");
+        return NULL;
+    }
+    if (ismrmrd_init_acquisition(acq) != ISMRMRD_NOERROR)
+    {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to initialize acquistion.");
+        return NULL;
+    }
+    return acq;
+}
+
+int ismrmrd_free_acquisition(ISMRMRD_Acquisition *acq) {
+
+    if (acq == NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+    
+    if (ismrmrd_cleanup_acquisition(acq)!=ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to cleanup acquisition.");
+    }
+    free(acq);
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_copy_acquisition(ISMRMRD_Acquisition *acqdest, const ISMRMRD_Acquisition *acqsource) {
+
+    if (acqsource==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Source pointer should not NULL.");
+    }
+    if (acqdest==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Destination pointer should not NULL.");
+    }
+
+    /* Copy the header */
+    memcpy(&acqdest->head, &acqsource->head, sizeof(ISMRMRD_AcquisitionHeader));
+    /* Reallocate memory for the trajectory and the data*/
+    ismrmrd_make_consistent_acquisition(acqdest);
+    /* Copy the trajectory and the data */
+    memcpy(acqdest->traj, acqsource->traj, ismrmrd_size_of_acquisition_traj(acqsource));
+    memcpy(acqdest->data, acqsource->data, ismrmrd_size_of_acquisition_data(acqsource));
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_make_consistent_acquisition(ISMRMRD_Acquisition *acq) {
+
+    size_t traj_size, data_size;
+        
+    if (acq==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+
+    if (acq->head.available_channels < acq->head.active_channels) {
+        acq->head.available_channels = acq->head.active_channels;
+    }
+    
+    traj_size = ismrmrd_size_of_acquisition_traj(acq);
+    if (traj_size > 0) {
+        float *newPtr = (float *)realloc(acq->traj, traj_size);
+        if (newPtr == NULL) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR,
+                          "Failed to realloc acquisition trajectory array");
+        }
+        acq->traj = newPtr;
+    }
+        
+    data_size = ismrmrd_size_of_acquisition_data(acq);
+    if (data_size > 0) {
+        complex_float_t *newPtr = (complex_float_t *)realloc(acq->data, data_size);
+        if (newPtr == NULL) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR,
+                          "Failed to realloc acquisition data array");
+        }
+        acq->data = newPtr;
+    }
+
+    return ISMRMRD_NOERROR;
+}
+
+size_t ismrmrd_size_of_acquisition_traj(const ISMRMRD_Acquisition *acq) {
+
+    int num_traj;
+    
+    if (acq==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+        return 0;
+    }
+
+    num_traj = acq->head.number_of_samples * acq->head.trajectory_dimensions;
+    return num_traj * sizeof(*acq->traj);
+
+}
+
+size_t ismrmrd_size_of_acquisition_data(const ISMRMRD_Acquisition *acq) {
+    int num_data;
+    
+    if (acq==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+        return 0;
+    }
+
+    num_data = acq->head.number_of_samples * acq->head.active_channels;
+    return num_data * sizeof(*acq->data);
+
+}
+
+/* Image functions */
+int ismrmrd_init_image_header(ISMRMRD_ImageHeader *hdr) {
+    if (hdr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+    memset(hdr, 0, sizeof(ISMRMRD_ImageHeader));
+    hdr->version = ISMRMRD_VERSION_MAJOR;
+    hdr->matrix_size[0] = 0;
+    hdr->matrix_size[1] = 1;
+    hdr->matrix_size[2] = 1;
+    hdr->channels = 1;
+    return ISMRMRD_NOERROR;
+}
+
+/* ImageHeader functions */
+int ismrmrd_init_image(ISMRMRD_Image *im) {
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+
+    if (ismrmrd_init_image_header(&im->head) != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to initialize image header.");
+    }
+    im->attribute_string = NULL;
+    im->data = NULL;
+    return ISMRMRD_NOERROR;
+}
+
+ISMRMRD_Image * ismrmrd_create_image() {
+    ISMRMRD_Image *im = (ISMRMRD_Image *) malloc(sizeof(ISMRMRD_Image));
+    if (im==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to allocate new Image.");
+        return NULL;
+    }
+    
+    if (ismrmrd_init_image(im) != ISMRMRD_NOERROR) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to initialize image.");
+        return NULL;
+    }
+    return im;
+}
+
+int ismrmrd_cleanup_image(ISMRMRD_Image *im) {
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+    free(im->attribute_string);
+    im->attribute_string = NULL;
+    free(im->data);
+    im->data = NULL;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_free_image(ISMRMRD_Image *im) {
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }        
+    if (ismrmrd_cleanup_image(im) != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to clean up image.");
+    }
+    free(im);
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_copy_image(ISMRMRD_Image *imdest, const ISMRMRD_Image *imsource) {
+    if (imsource==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Source pointer should not NULL.");
+    }
+    if (imdest==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Destination pointer should not NULL.");
+    }
+    memcpy(&imdest->head, &imsource->head, sizeof(ISMRMRD_ImageHeader));
+    if (ismrmrd_make_consistent_image(imdest) != ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to make image consistent.");
+    }
+    memcpy(imdest->attribute_string, imsource->attribute_string,
+           ismrmrd_size_of_image_attribute_string(imdest));
+    memcpy(imdest->data, imsource->data, ismrmrd_size_of_image_data(imdest));
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_make_consistent_image(ISMRMRD_Image *im) {
+    size_t attr_size, data_size;
+    if (im==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+    }
+   
+    attr_size = ismrmrd_size_of_image_attribute_string(im);
+    if (attr_size > 0) {
+        // Allocate space plus a null-terminating character
+        char *newPtr = (char *)realloc(im->attribute_string, attr_size+sizeof(*im->attribute_string));
+        if (newPtr == NULL) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to realloc image attribute string");
+        }
+        im->attribute_string = newPtr;
+        // Set the null terminating character
+        im->attribute_string[im->head.attribute_string_len] = '\0';
+    }
+        
+    data_size = ismrmrd_size_of_image_data(im);
+    if (data_size > 0) {
+        void *newPtr = realloc(im->data, data_size);
+        if (newPtr == NULL) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to realloc image data array");
+        }
+        im->data = newPtr;
+    }
+    return ISMRMRD_NOERROR;
+}
+
+size_t ismrmrd_size_of_image_data(const ISMRMRD_Image *im) {
+    size_t data_size = 0;
+    int num_data;
+    if (im==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not NULL.");
+        return 0;
+    }
+
+    if (ismrmrd_sizeof_data_type(im->head.data_type) == 0) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_TYPEERROR, "Invalid image data type");
+        return 0;
+    }
+
+    num_data = im->head.matrix_size[0] * im->head.matrix_size[1] *
+            im->head.matrix_size[2] * im->head.channels;
+        
+    data_size = num_data * ismrmrd_sizeof_data_type(im->head.data_type);
+    
+    return data_size;
+}
+
+size_t ismrmrd_size_of_image_attribute_string(const ISMRMRD_Image *im) {
+    if (im==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+        return 0;
+    }
+    return im->head.attribute_string_len * sizeof(*im->attribute_string);
+}
+
+/* NDArray functions */
+int ismrmrd_init_ndarray(ISMRMRD_NDArray *arr) {
+    int n;
+
+    if (arr==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+        return ISMRMRD_RUNTIMEERROR;
+    }
+
+    arr->version = ISMRMRD_VERSION_MAJOR;
+    arr->data_type = 0; /* no default data type */
+    arr->ndim = 0;
+    
+    for (n = 0; n < ISMRMRD_NDARRAY_MAXDIM; n++) {
+        arr->dims[n] = 0;
+    }
+    arr->data = NULL;
+    return ISMRMRD_NOERROR;
+}
+
+ISMRMRD_NDArray * ismrmrd_create_ndarray() {
+    ISMRMRD_NDArray *arr = (ISMRMRD_NDArray *) malloc(sizeof(ISMRMRD_NDArray));
+    if (arr==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to malloc new ISMRMRD_NDArray.");
+        return NULL;
+    }
+        
+    if (ismrmrd_init_ndarray(arr)!=ISMRMRD_NOERROR) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to initialize ndarray.");
+        return NULL;
+    }
+    return arr;
+}
+
+int ismrmrd_cleanup_ndarray(ISMRMRD_NDArray *arr) {
+    if (arr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+
+    free(arr->data);
+    arr->data = NULL;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_free_ndarray(ISMRMRD_NDArray *arr) {
+    if (arr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+
+    if (ismrmrd_cleanup_ndarray(arr)!=ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to cleanup ndarray.");
+    }
+    free(arr);
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_copy_ndarray(ISMRMRD_NDArray *arrdest, const ISMRMRD_NDArray *arrsource) {
+    int n;
+
+    if (arrsource==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Source pointer should not be NULL.");
+    }
+    if (arrdest==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Destination pointer should not be NULL.");
+    }
+            
+    arrdest->version = arrsource->version;
+    arrdest->data_type = arrsource->data_type;
+    arrdest->ndim = arrsource->ndim;
+            
+    for (n = 0; n < ISMRMRD_NDARRAY_MAXDIM; n++) {
+        arrdest->dims[n] = arrsource->dims[n];
+    }
+    if (ismrmrd_make_consistent_ndarray(arrdest)!=ISMRMRD_NOERROR) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Failed to make ndarray consistent.");
+    }
+    memcpy(arrdest->data, arrsource->data, ismrmrd_size_of_ndarray_data(arrdest));
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_make_consistent_ndarray(ISMRMRD_NDArray *arr) {
+    size_t data_size;
+    
+    if (arr==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+
+    data_size = ismrmrd_size_of_ndarray_data(arr);
+    if (data_size > 0) {
+        void *newPtr = realloc(arr->data, data_size);
+        if (newPtr == NULL) {
+            return ISMRMRD_PUSH_ERR(ISMRMRD_MEMORYERROR, "Failed to realloc NDArray data array");
+        }
+        arr->data = newPtr;
+    }
+    return ISMRMRD_NOERROR;
+}
+
+size_t ismrmrd_size_of_ndarray_data(const ISMRMRD_NDArray *arr) {
+    size_t data_size = 0;
+    int num_data = 1;
+    int n;
+    
+    if (arr==NULL) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+        return 0;
+    }
+
+    if (ismrmrd_sizeof_data_type(arr->data_type) == 0 ) {
+        ISMRMRD_PUSH_ERR(ISMRMRD_TYPEERROR, "Invalid NDArray data type");
+        return 0;
+    }
+    
+    for (n = 0; n < arr->ndim; n++) {
+        num_data *= (int) (arr->dims[n]);
+    }
+
+    data_size = num_data * ismrmrd_sizeof_data_type(arr->data_type);
+
+    return data_size;
+}
+
+size_t ismrmrd_sizeof_data_type(int data_type)
+{
+    size_t size = 0;
+
+    switch (data_type) {
+        case ISMRMRD_USHORT:
+            size = sizeof(uint16_t);
+            break;
+        case ISMRMRD_SHORT:
+            size = sizeof(int16_t);
+            break;
+        case ISMRMRD_UINT:
+            size = sizeof(uint32_t);
+            break;
+        case ISMRMRD_INT:
+            size = sizeof(int32_t);
+            break;
+        case ISMRMRD_FLOAT:
+            size = sizeof(float);
+            break;
+        case ISMRMRD_DOUBLE:
+            size = sizeof(double);
+            break;
+        case ISMRMRD_CXFLOAT:
+            size = sizeof(complex_float_t);
+            break;
+        case ISMRMRD_CXDOUBLE:
+            size = sizeof(complex_double_t);
+            break;
+        default:
+            size = 0;
+    }
+    return size;
+}
+
+/* Misc. functions */
+bool ismrmrd_is_flag_set(const uint64_t flags, const uint64_t val) {
+    uint64_t bitmask = (uint64_t)(1) << (val - 1);
+    return (flags & bitmask) > 0;
+}
+
+int ismrmrd_set_flag(uint64_t *flags, const uint64_t val) {
+    uint64_t bitmask;
+    if (flags==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    bitmask = (uint64_t)(1) << (val - 1);
+    *flags |= bitmask;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_set_flags(uint64_t *flags, const uint64_t val) {
+    if (flags==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    *flags = val;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_clear_flag(uint64_t *flags, const uint64_t val) {
+    uint64_t bitmask;
+    if (flags==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    bitmask = (uint64_t)(1) << (val - 1);
+    *flags &= ~bitmask;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_clear_all_flags(uint64_t *flags) {
+    if (flags==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer should not be NULL.");
+    }
+    *flags = 0;
+    return ISMRMRD_NOERROR;
+}
+
+bool ismrmrd_is_channel_on(const uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan) {
+    uint64_t bitmask;
+    size_t offset;
+    if (channel_mask==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer to channel_mask should not be NULL.");
+    }
+    bitmask = (uint64_t)(1) << (chan % 64);
+    offset = chan / 64;
+    return (channel_mask[offset] & bitmask) > 0;
+}
+
+int ismrmrd_set_channel_on(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan) {
+    uint64_t bitmask;
+    size_t offset;
+    if (channel_mask==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer to channel_mask should not be NULL.");
+    }
+    bitmask = (uint64_t)(1) << (chan % 64);
+    offset = chan / 64;
+    channel_mask[offset] |= bitmask;
+    return ISMRMRD_NOERROR;
+}
+
+int ismrmrd_set_channel_off(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS], const uint16_t chan) {
+    uint64_t bitmask;
+    size_t offset;
+    if (channel_mask==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer to channel_mask should not be NULL.");
+    }
+    bitmask = 1 << (chan % 64);
+    offset = chan / 64;
+    channel_mask[offset] &= ~bitmask;
+    return ISMRMRD_NOERROR;
+}
+    
+int ismrmrd_set_all_channels_off(uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS]) {
+    size_t offset;
+    if (channel_mask==NULL) {
+        return ISMRMRD_PUSH_ERR(ISMRMRD_RUNTIMEERROR, "Pointer to channel_mask should not be NULL.");
+    }
+    for (offset = 0; offset<ISMRMRD_CHANNEL_MASKS; offset++) {
+        channel_mask[offset] = 0;
+    }
+    return ISMRMRD_NOERROR;
+}
+    
+int ismrmrd_sign_of_directions(float read_dir[3], float phase_dir[3], float slice_dir[3]) {
+    float r11 = read_dir[0], r12 = phase_dir[0], r13 = slice_dir[0];
+    float r21 = read_dir[1], r22 = phase_dir[1], r23 = slice_dir[1];
+    float r31 = read_dir[2], r32 = phase_dir[2], r33 = slice_dir[2];
+    
+    /* Determinant should be 1 or -1 */
+    float deti = (r11 * r22 * r33) + (r12 * r23 * r31) + (r21 * r32 * r13) -
+                 (r13 * r22 * r31) - (r12 * r21 * r33) - (r11 * r23 * r32);
+    
+    if (deti < 0) {
+        return -1;
+    } else {
+        return 1;
+    }
+}
+
+void ismrmrd_directions_to_quaternion(float read_dir[3], float phase_dir[3],
+                                      float slice_dir[3], float quat[4]) {
+    float r11 = read_dir[0], r12 = phase_dir[0], r13 = slice_dir[0];
+    float r21 = read_dir[1], r22 = phase_dir[1], r23 = slice_dir[1];
+    float r31 = read_dir[2], r32 = phase_dir[2], r33 = slice_dir[2];
+    
+    double a = 1, b = 0, c = 0, d = 0, s = 0;
+    double trace = 0;
+    double xd, yd, zd;
+    
+    /* verify the sign of the rotation*/
+    if (ismrmrd_sign_of_directions(read_dir, phase_dir, slice_dir) < 0) {
+        /* flip 3rd column */
+        r13 = -r13;
+        r23 = -r23;
+        r33 = -r33;
+    }
+    
+    /* Compute quaternion parameters */
+    /* http://www.cs.princeton.edu/~gewang/projects/darth/stuff/quat_faq.html#Q55 */
+    trace = 1.0 + r11 + r22 + r33;
+    if (trace > 0.00001) { /* simplest case */
+        s = sqrt(trace) * 2;
+        a = (r32 - r23) / s;
+        b = (r13 - r31) / s;
+        c = (r21 - r12) / s;
+        d = 0.25 * s;
+    } else {
+        /* trickier case...
+         * determine which major diagonal element has
+         * the greatest value... */
+        xd = 1.0 + r11 - (r22 + r33); /* 4**b**b */
+        yd = 1.0 + r22 - (r11 + r33); /* 4**c**c */
+        zd = 1.0 + r33 - (r11 + r22); /* 4**d**d */
+        /* if r11 is the greatest */
+        if (xd > 1.0) {
+            s = 2.0 * sqrt(xd);
+            a = 0.25 * s;
+            b = (r21 + r12) / s;
+            c = (r31 + r13) / s;
+            d = (r32 - r23) / s;
+        }
+        /* else if r22 is the greatest */
+        else if (yd > 1.0) {
+            s = 2.0 * sqrt(yd);
+            a = (r21 + r12) / s;
+            b = 0.25 * s;
+            c = (r32 + r23) / s;
+            d = (r13 - r31) / s;
+        }
+        /* else, r33 must be the greatest */
+        else {
+            s = 2.0 * sqrt(zd);
+            a = (r13 + r31) / s;
+            b = (r23 + r32) / s;
+            c = 0.25 * s;
+            d = (r21 - r12) / s;
+        }
+
+        if (a < 0.0) {
+            b = -b;
+            c = -c;
+            d = -d;
+            a = -a;
+        }
+    }
+    
+    quat[0] = (float)a;
+    quat[1] = (float)b;
+    quat[2] = (float)c;
+    quat[3] = (float)d;
+}
+
+/* http://www.cs.princeton.edu/~gewang/projects/darth/stuff/quat_faq.html#Q54 */
+void ismrmrd_quaternion_to_directions(float quat[4], float read_dir[3],
+                                      float phase_dir[3], float slice_dir[3]) {
+    float a = quat[0], b = quat[1], c = quat[2], d = quat[3];
+    
+    read_dir[0] = 1.0f - 2.0f * (b * b + c * c);
+    phase_dir[0] = 2.0f * (a * b - c * d);
+    slice_dir[0] = 2.0f * (a * c + b * d);
+    
+    read_dir[1] = 2.0f * (a * b + c * d);
+    phase_dir[1] = 1.0f - 2.0f * (a * a + c * c);
+    slice_dir[1] = 2.0f * (b * c - a * d);
+    
+    read_dir[2] = 2.0f * (a * c - b * d);
+    phase_dir[2] = 2.0f * (b * c + a * d);
+    slice_dir[2] = 1.0f - 2.0f * (a * a + b * b);
+}
+
+/**
+ * Saves error information on the error stack
+ * @returns error code
+ */
+int ismrmrd_push_error(const char *file, const int line, const char *func,
+        const int code, const char *msg)
+{
+    ISMRMRD_error_node_t *node = NULL;
+
+    /* Call user-defined error handler if it exists */
+    if (ismrmrd_error_handler != NULL) {
+        ismrmrd_error_handler(file, line, func, code, msg);
+    }
+
+    /* Save error information on error stack */
+    node = (ISMRMRD_error_node_t*)calloc(1, sizeof(*node));
+    if (node == NULL) {
+        /* TODO: how to handle this? */
+        return ISMRMRD_MEMORYERROR;
+    }
+
+    node->next = error_stack_head;
+    error_stack_head = node;
+
+    node->file = (char*)file;
+    node->line = line;
+    node->func = (char*)func;
+    node->code = code;
+    node->msg = (char*)msg;
+
+    return code;
+}
+
+bool ismrmrd_pop_error(char **file, int *line, char **func,
+        int *code, char **msg)
+{
+    ISMRMRD_error_node_t *node = error_stack_head;
+    if (node == NULL) {
+        /* nothing to pop */
+        return false;
+    }
+
+    /* pop head off stack */
+    error_stack_head = node->next;
+
+    if (file != NULL) {
+        *file = node->file;
+    }
+    if (line != NULL) {
+        *line = node->line;
+    }
+    if (func != NULL) {
+        *func = node->func;
+    }
+    if (code != NULL) {
+        *code = node->code;
+    }
+    if (msg != NULL) {
+        *msg = node->msg;
+    }
+
+    free(node);
+    return true;
+}
+
+void ismrmrd_set_error_handler(ismrmrd_error_handler_t handler) {
+    ismrmrd_error_handler = handler;
+}
+
+char *ismrmrd_strerror(int code) {
+    /* Match the ISMRMRD_ErrorCodes */
+    static char * const error_messages []= {
+        "No Error",
+        "Memory Error",
+        "File Error",
+        "Type Error",
+        "Runtime Error",
+        "HDF5 Error",
+    };
+    return error_messages[code];
+}
+
+static void ismrmrd_error_default(const char *file, int line,
+        const char *func, int code, const char *msg)
+{
+    char *msgtype = ismrmrd_strerror(code);
+    fprintf(stderr, "ERROR: %s in %s, line %d: %s\n", msgtype, file, line, msg);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+} // namespace ISMRMRD
+#endif
diff --git a/libsrc/ismrmrd.cpp b/libsrc/ismrmrd.cpp
new file mode 100644 (file)
index 0000000..d33c1b1
--- /dev/null
@@ -0,0 +1,1218 @@
+#include <string.h>
+#include <stdlib.h>
+#include <sstream>
+#include <stdexcept>
+
+#include <iostream>
+#include "ismrmrd/ismrmrd.h"
+
+namespace ISMRMRD {
+
+//
+// AcquisitionHeader class implementation
+//
+// Constructors
+AcquisitionHeader::AcquisitionHeader() {
+    if (ismrmrd_init_acquisition_header(this) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Flag methods
+bool AcquisitionHeader::isFlagSet(const ISMRMRD_AcquisitionFlags val) {
+    return ismrmrd_is_flag_set(flags, val);
+}
+
+void AcquisitionHeader::setFlag(const ISMRMRD_AcquisitionFlags val) {
+    ismrmrd_set_flag(&flags, val);
+}
+
+void AcquisitionHeader::clearFlag(const ISMRMRD_AcquisitionFlags val) {
+    ismrmrd_clear_flag(&flags, val);
+}
+
+void AcquisitionHeader::clearAllFlags() {
+    ismrmrd_clear_all_flags(&flags);
+}
+
+// Channel mask methods
+bool AcquisitionHeader::isChannelActive(uint16_t channel_id) {
+    return ismrmrd_is_channel_on(channel_mask, channel_id);
+}
+void AcquisitionHeader::setChannelActive(uint16_t channel_id) {
+    ismrmrd_set_channel_on(channel_mask, channel_id);
+}
+void AcquisitionHeader::setChannelNotActive(uint16_t channel_id) {
+    ismrmrd_set_channel_off(channel_mask, channel_id);
+}
+void AcquisitionHeader::setAllChannelsNotActive() {
+    ismrmrd_set_all_channels_off(channel_mask);
+}
+
+
+//
+// Acquisition class Implementation
+//
+// Constructors, assignment operator, destructor
+Acquisition::Acquisition() {
+    if (ismrmrd_init_acquisition(&acq) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+
+Acquisition::Acquisition(uint16_t num_samples, uint16_t active_channels, uint16_t trajectory_dimensions){
+    if (ismrmrd_init_acquisition(&acq) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+    this->resize(num_samples,active_channels,trajectory_dimensions);
+}
+
+Acquisition::Acquisition(const Acquisition &other) {
+    int err = 0;
+    // This is a deep copy
+    err = ismrmrd_init_acquisition(&acq);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+    err = ismrmrd_copy_acquisition(&acq, &other.acq);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+Acquisition & Acquisition::operator= (const Acquisition &other) {
+    // Assignment makes a copy
+    int err = 0;
+    if (this != &other )
+    {
+        err = ismrmrd_init_acquisition(&acq);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+        err = ismrmrd_copy_acquisition(&acq, &other.acq);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+    }
+    return *this;
+}
+
+Acquisition::~Acquisition() {
+    if (ismrmrd_cleanup_acquisition(&acq) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Accessors and mutators
+const uint16_t &Acquisition::version() {
+    return acq.head.version;
+}
+
+const uint64_t &Acquisition::flags() {
+    return acq.head.flags;
+}
+
+uint32_t &Acquisition::measurement_uid() {
+    return acq.head.measurement_uid;
+}
+
+uint32_t &Acquisition::scan_counter() {
+    return acq.head.scan_counter;
+}
+
+uint32_t &Acquisition::acquisition_time_stamp() {
+    return acq.head.acquisition_time_stamp;
+}
+
+uint32_t (&Acquisition::physiology_time_stamp()) [ISMRMRD_PHYS_STAMPS] {
+    return acq.head.physiology_time_stamp;
+}
+
+const uint16_t &Acquisition::number_of_samples() {
+    return acq.head.number_of_samples;
+}
+
+uint16_t &Acquisition::available_channels() {
+    return acq.head.available_channels;
+}
+
+const uint16_t &Acquisition::active_channels() {
+    return acq.head.active_channels;
+}
+
+const uint64_t (&Acquisition::channel_mask()) [ISMRMRD_CHANNEL_MASKS] {
+    return acq.head.channel_mask;
+}
+
+uint16_t &Acquisition::discard_pre() {
+    return acq.head.discard_pre;
+}
+
+uint16_t &Acquisition::discard_post() {
+    return acq.head.discard_post;
+}
+
+uint16_t &Acquisition::center_sample() {
+    return acq.head.center_sample;
+}
+
+uint16_t &Acquisition::encoding_space_ref() {
+    return acq.head.encoding_space_ref;
+}
+
+const uint16_t &Acquisition::trajectory_dimensions() {
+    return acq.head.trajectory_dimensions;
+}
+
+float &Acquisition::sample_time_us() {
+    return acq.head.sample_time_us;
+}
+
+float (&Acquisition::position())[3] {
+    return acq.head.position;
+}
+
+float (&Acquisition::read_dir())[3] {
+    return acq.head.read_dir;
+}
+
+float (&Acquisition::phase_dir())[3] {
+    return acq.head.phase_dir;
+}
+
+float (&Acquisition::slice_dir())[3] {
+    return acq.head.slice_dir;
+}
+
+float (&Acquisition::patient_table_position())[3] {
+    return acq.head.patient_table_position;
+}
+
+ISMRMRD_EncodingCounters &Acquisition::idx() {
+    return acq.head.idx;
+}
+
+int32_t (&Acquisition::user_int()) [ISMRMRD_USER_INTS] {
+    return acq.head.user_int;
+}
+
+float (&Acquisition::user_float()) [ISMRMRD_USER_FLOATS] {
+    return acq.head.user_float;
+}
+
+// Sizes
+size_t Acquisition::getNumberOfDataElements() const {
+    size_t num = acq.head.number_of_samples * acq.head.active_channels;
+    return num;
+}
+
+size_t Acquisition::getDataSize() const {
+    size_t num = acq.head.number_of_samples * acq.head.active_channels;
+    return num*sizeof(complex_float_t);
+}
+
+size_t Acquisition::getNumberOfTrajElements() const {
+    size_t num = acq.head.number_of_samples * acq.head.trajectory_dimensions;
+    return num;
+}
+
+size_t Acquisition::getTrajSize() const {
+    size_t num = acq.head.number_of_samples * acq.head.trajectory_dimensions;
+    return num*sizeof(float);
+}
+
+// Data and Trajectory accessors
+const AcquisitionHeader & Acquisition::getHead() const {
+    // This returns a reference
+    return *static_cast<const AcquisitionHeader *>(&acq.head);
+}
+
+void Acquisition::setHead(const AcquisitionHeader &other) {
+    memcpy(&acq.head, &other, sizeof(AcquisitionHeader));
+    if (ismrmrd_make_consistent_acquisition(&acq) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+void Acquisition::resize(uint16_t num_samples, uint16_t active_channels, uint16_t trajectory_dimensions){
+       acq.head.number_of_samples = num_samples;
+       acq.head.active_channels = active_channels;
+       acq.head.trajectory_dimensions = trajectory_dimensions;
+       if (ismrmrd_make_consistent_acquisition(&acq) != ISMRMRD_NOERROR) {
+           throw std::runtime_error(build_exception_string());
+       }
+}
+
+const complex_float_t * Acquisition::getDataPtr() const {
+    return acq.data;
+}
+
+complex_float_t * Acquisition::getDataPtr() {
+    return acq.data;
+}
+
+void Acquisition::setData(complex_float_t * data) {
+    memcpy(acq.data,data,this->getNumberOfDataElements()*sizeof(complex_float_t));
+}
+
+complex_float_t & Acquisition::data(uint16_t sample, uint16_t channel){
+       size_t index = size_t(sample)+size_t(channel)*size_t(acq.head.number_of_samples);
+       return acq.data[index];
+}
+
+const float * Acquisition::getTrajPtr() const {
+    return acq.traj;
+}
+
+float * Acquisition::getTrajPtr() {
+    return acq.traj;
+}
+
+void Acquisition::setTraj(float* traj) {
+       memcpy(acq.traj,traj,this->getNumberOfTrajElements()*sizeof(float));
+}
+
+float & Acquisition::traj(uint16_t dimension, uint16_t sample){
+    size_t index = size_t(sample)*size_t(acq.head.trajectory_dimensions)+size_t(dimension);
+    return acq.traj[index];
+}
+
+complex_float_t * Acquisition::data_begin() const{
+       return acq.data;
+}
+
+complex_float_t * Acquisition::data_end() const {
+       return acq.data+size_t(acq.head.number_of_samples)*size_t(acq.head.active_channels);
+}
+
+float * Acquisition::traj_begin() const {
+       return acq.traj;
+}
+
+float * Acquisition::traj_end() const {
+       return acq.traj+size_t(acq.head.number_of_samples)*size_t(acq.head.trajectory_dimensions);
+}
+
+// Flag methods
+bool Acquisition::isFlagSet(const uint64_t val) {
+    return ismrmrd_is_flag_set(acq.head.flags, val);
+}
+void Acquisition::setFlag(const uint64_t val) {
+    ismrmrd_set_flag(&acq.head.flags, val);
+}
+void Acquisition::clearFlag(const uint64_t val) {
+    ismrmrd_clear_flag(&acq.head.flags, val);
+}
+void Acquisition::clearAllFlags() {
+    ismrmrd_clear_all_flags(&acq.head.flags);
+}
+
+// Channel mask methods
+bool Acquisition::isChannelActive(uint16_t channel_id) {
+    return ismrmrd_is_channel_on(acq.head.channel_mask, channel_id);
+}
+void Acquisition::setChannelActive(uint16_t channel_id) {
+    ismrmrd_set_channel_on(acq.head.channel_mask, channel_id);
+}
+void Acquisition::setChannelNotActive(uint16_t channel_id) {
+    ismrmrd_set_channel_off(acq.head.channel_mask, channel_id);
+}
+void Acquisition::setAllChannelsNotActive() {
+    ismrmrd_set_all_channels_off(acq.head.channel_mask);
+}
+
+
+//
+// ImageHeader class Implementation
+//
+
+// Constructor
+ImageHeader::ImageHeader() {
+    if (ismrmrd_init_image_header(this) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+};
+
+// Flag methods
+bool ImageHeader::isFlagSet(const uint64_t val) {
+    return ismrmrd_is_flag_set(flags, val);
+};
+
+void ImageHeader::setFlag(const uint64_t val) {
+    ismrmrd_set_flag(&flags, val);
+};
+
+void ImageHeader::clearFlag(const uint64_t val) {
+    ismrmrd_clear_flag(&flags, val);
+};
+
+void ImageHeader::clearAllFlags() {
+    ismrmrd_clear_all_flags(&flags);
+};
+
+//
+// Image class Implementation
+//
+
+// Constructors
+template <typename T> Image<T>::Image(uint16_t matrix_size_x,
+                                      uint16_t matrix_size_y,
+                                      uint16_t matrix_size_z,
+                                      uint16_t channels)
+{
+    if (ismrmrd_init_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+    im.head.data_type = static_cast<uint16_t>(get_data_type<T>());
+    resize(matrix_size_x, matrix_size_y, matrix_size_z, channels);
+}
+
+template <typename T> Image<T>::Image(const Image<T> &other) {
+    int err = 0;
+    // This is a deep copy
+    err = ismrmrd_init_image(&im);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+    err = ismrmrd_copy_image(&im, &other.im);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> Image<T> & Image<T>::operator= (const Image<T> &other)
+{
+    int err = 0;
+    // Assignment makes a copy
+    if (this != &other )
+    {
+        err = ismrmrd_init_image(&im);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+        err = ismrmrd_copy_image(&im, &other.im);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+    }
+    return *this;
+}
+
+template <typename T> Image<T>::~Image() {
+    if (ismrmrd_cleanup_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Image dimensions
+template <typename T> void Image<T>::resize(uint16_t matrix_size_x,
+                                            uint16_t matrix_size_y,
+                                            uint16_t matrix_size_z,
+                                            uint16_t channels)
+{
+    // TODO what if matrix_size_x = 0?
+    if (matrix_size_y == 0) {
+        matrix_size_y = 1;
+    }
+    if (matrix_size_z == 0) {
+        matrix_size_z = 1;
+    }
+    if (channels == 0) {
+        channels = 1;
+    }
+    
+    im.head.matrix_size[0] = matrix_size_x;
+    im.head.matrix_size[1] = matrix_size_y;
+    im.head.matrix_size[2] = matrix_size_z;
+    im.head.channels = channels;
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> uint16_t Image<T>::getMatrixSizeX() const
+{
+    return im.head.matrix_size[0];
+}
+
+template <typename T> void Image<T>::setMatrixSizeX(uint16_t matrix_size_x)
+{
+    // TODO what if matrix_size_x = 0?
+    im.head.matrix_size[0] = matrix_size_x;
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> uint16_t Image<T>::getMatrixSizeY() const
+{
+    return im.head.matrix_size[1];
+}
+
+template <typename T> void Image<T>::setMatrixSizeY(uint16_t matrix_size_y)
+{
+    if (matrix_size_y == 0) {
+        matrix_size_y = 1;
+    }
+    im.head.matrix_size[1] = matrix_size_y;
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> uint16_t Image<T>::getMatrixSizeZ() const
+{
+    return im.head.matrix_size[2];
+}
+
+template <typename T> void Image<T>::setMatrixSizeZ(uint16_t matrix_size_z)
+{
+    if (matrix_size_z == 0) {
+        matrix_size_z = 1;
+    }
+    im.head.matrix_size[2] = matrix_size_z;
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> uint16_t Image<T>::getNumberOfChannels() const
+{
+    return im.head.channels;
+}
+
+template <typename T> void Image<T>::setNumberOfChannels(uint16_t channels)
+{
+    if (channels == 0) {
+        channels = 1;
+    }
+
+    im.head.channels = channels;
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+
+template <typename T> void Image<T>::setFieldOfView(float fov_x, float fov_y, float fov_z)
+{
+    im.head.field_of_view[0] = fov_x;
+    im.head.field_of_view[1] = fov_y;
+    im.head.field_of_view[2] = fov_z;
+}
+
+template <typename T> void Image<T>::setFieldOfViewX(float fov_x)
+{
+    im.head.field_of_view[0] = fov_x;
+}
+
+template <typename T> float Image<T>::getFieldOfViewX() const
+{
+    return im.head.field_of_view[0];
+}
+
+template <typename T> void Image<T>::setFieldOfViewY(float fov_y)
+{
+    im.head.field_of_view[1] = fov_y;
+}
+
+template <typename T> float Image<T>::getFieldOfViewY() const
+{
+    return im.head.field_of_view[1];
+}
+
+template <typename T> void Image<T>::setFieldOfViewZ(float fov_z)
+{
+    im.head.field_of_view[2] = fov_z;
+}
+
+template <typename T> float Image<T>::getFieldOfViewZ() const
+{
+    return im.head.field_of_view[2];
+}
+
+
+// Positions and orientations
+template <typename T> void Image<T>::setPosition(float x, float y, float z)
+{
+    im.head.position[0] = x;
+    im.head.position[1] = y;
+    im.head.position[2] = z;
+}
+
+template <typename T> float Image<T>::getPositionX() const
+{
+    return im.head.position[0];
+}
+
+template <typename T> void Image<T>::setPositionX(float x)
+{
+    im.head.position[0] = x;
+}
+
+template <typename T> float Image<T>::getPositionY() const
+{
+    return im.head.position[1];
+}
+
+template <typename T> void Image<T>::setPositionY(float y)
+{
+    im.head.position[1] = y;
+}
+
+template <typename T> float Image<T>::getPositionZ() const
+{
+    return im.head.position[2];
+}
+
+template <typename T> void Image<T>::setPositionZ(float z)
+{
+    im.head.position[2] = z;
+}
+
+
+template <typename T> void Image<T>::setReadDirection(float x, float y, float z)
+{
+    im.head.read_dir[0] = x;
+    im.head.read_dir[1] = y;
+    im.head.read_dir[2] = z;
+}
+
+template <typename T> float Image<T>::getReadDirectionX() const
+{
+    return im.head.read_dir[0];
+}
+
+template <typename T> void Image<T>::setReadDirectionX(float x)
+{
+    im.head.read_dir[0] = x;
+}
+
+template <typename T> float Image<T>::getReadDirectionY() const
+{
+    return im.head.read_dir[1];
+}
+
+template <typename T> void Image<T>::setReadDirectionY(float y)
+{
+    im.head.read_dir[1] = y;
+}
+
+template <typename T> float Image<T>::getReadDirectionZ() const
+{
+    return im.head.read_dir[2];
+}
+
+template <typename T> void Image<T>::setReadDirectionZ(float z)
+{
+    im.head.read_dir[2] = z;    
+}
+
+    
+template <typename T> void Image<T>::setPhaseDirection(float x, float y, float z)
+{
+    im.head.phase_dir[0] = x;
+    im.head.phase_dir[1] = y;
+    im.head.phase_dir[2] = z;
+}
+
+template <typename T> float Image<T>::getPhaseDirectionX() const
+{
+    return im.head.phase_dir[0];
+}
+
+template <typename T> void Image<T>::setPhaseDirectionX(float x)
+{
+    im.head.phase_dir[0] = x;
+}
+
+template <typename T> float Image<T>::getPhaseDirectionY() const
+{
+    return im.head.phase_dir[1];
+}
+
+template <typename T> void Image<T>::setPhaseDirectionY(float y)
+{
+    im.head.phase_dir[1] = y;
+}
+
+template <typename T> float Image<T>::getPhaseDirectionZ() const
+{
+    return im.head.phase_dir[2];
+}
+
+template <typename T> void Image<T>::setPhaseDirectionZ(float z)
+{
+    im.head.phase_dir[2] = z;
+}
+
+template <typename T> void Image<T>::setSliceDirection(float x, float y, float z)
+{
+    im.head.slice_dir[0] = x;
+    im.head.slice_dir[1] = y;
+    im.head.slice_dir[2] = z;
+}
+
+template <typename T> float Image<T>::getSliceDirectionX() const
+{
+    return im.head.slice_dir[0];
+}
+
+template <typename T> void Image<T>::setSliceDirectionX(float x)
+{
+    im.head.slice_dir[0] = x;
+}
+
+template <typename T> float Image<T>::getSliceDirectionY() const
+{
+    return im.head.slice_dir[1];
+}
+
+template <typename T> void Image<T>::setSliceDirectionY(float y)
+{
+    im.head.slice_dir[1] = y;
+}
+
+template <typename T> float Image<T>::getSliceDirectionZ() const
+{
+    return im.head.slice_dir[2];
+}
+
+template <typename T> void Image<T>::setSliceDirectionZ(float z)
+{
+    im.head.slice_dir[2] = z;
+}
+    
+template <typename T> void Image<T>::setPatientTablePosition(float x, float y, float z)
+{
+    im.head.patient_table_position[0] = x;
+    im.head.patient_table_position[1] = y;
+    im.head.patient_table_position[2] = z;
+}
+
+template <typename T> float Image<T>::getPatientTablePositionX() const
+{
+    return im.head.patient_table_position[0];
+}
+
+template <typename T> void Image<T>::setPatientTablePositionX(float x)
+{
+    im.head.patient_table_position[0] = x;
+}
+
+template <typename T> float Image<T>::getPatientTablePositionY() const
+{
+    return im.head.patient_table_position[1];
+}
+
+template <typename T> void Image<T>::setPatientTablePositionY(float y)
+{
+    im.head.patient_table_position[1] = y;
+}
+
+template <typename T> float Image<T>::getPatientTablePositionZ() const
+{
+    return im.head.patient_table_position[2];
+}
+
+template <typename T> void Image<T>::setPatientTablePositionZ(float z)
+{
+    im.head.patient_table_position[2] = z;
+}
+
+template <typename T> uint16_t Image<T>::getVersion() const
+{
+    return im.head.version;
+}
+
+template <typename T> ISMRMRD_DataTypes Image<T>::getDataType() const
+{
+    return static_cast<ISMRMRD_DataTypes>(im.head.data_type);
+}
+
+template <typename T> uint32_t Image<T>::getMeasurementUid() const
+{
+    return im.head.measurement_uid;
+}
+
+template <typename T> void Image<T>::setMeasurementUid(uint32_t measurement_uid)
+{
+    im.head.measurement_uid = measurement_uid;
+}
+
+
+template <typename T> uint16_t Image<T>::getAverage() const
+{
+    return im.head.average;
+}
+
+template <typename T> void  Image<T>::setAverage(uint16_t average)
+{
+    im.head.average = average;
+}
+
+template <typename T> uint16_t Image<T>::getSlice() const
+{
+    return im.head.slice;
+}
+
+template <typename T> void  Image<T>::setSlice(uint16_t slice)
+{
+    im.head.slice = slice;
+}
+
+template <typename T> uint16_t Image<T>::getContrast() const
+{
+    return im.head.contrast;
+}
+
+template <typename T> void  Image<T>::setContrast(uint16_t contrast)
+{
+    im.head.contrast = contrast;
+}
+
+template <typename T> uint16_t Image<T>::getPhase() const
+{
+    return im.head.phase;
+}
+
+template <typename T> void  Image<T>::setPhase(uint16_t phase)
+{
+    im.head.phase = phase;
+}
+    
+template <typename T> uint16_t Image<T>::getRepetition() const
+{
+    return im.head.repetition;
+}
+
+template <typename T> void  Image<T>::setRepetition(uint16_t repetition)
+{
+    im.head.repetition = repetition;
+}
+
+template <typename T> uint16_t Image<T>::getSet() const
+{
+    return im.head.set;
+}
+
+template <typename T> void  Image<T>::setSet(uint16_t set)
+{
+    im.head.set = set;
+}
+
+template <typename T> uint32_t Image<T>::getAcquisitionTimeStamp() const
+{
+    return im.head.acquisition_time_stamp;
+}
+
+template <typename T> void  Image<T>::setAcquisitionTimeStamp(uint32_t acquisition_time_stamp)
+{
+    im.head.acquisition_time_stamp = acquisition_time_stamp;
+}
+
+template <typename T> uint32_t Image<T>::getPhysiologyTimeStamp(unsigned int stamp_id) const
+{
+    return im.head.physiology_time_stamp[stamp_id];
+}
+
+template <typename T> void  Image<T>::setPhysiologyTimeStamp(unsigned int stamp_id, uint32_t value)
+{
+    im.head.physiology_time_stamp[stamp_id] = value;
+}
+
+template <typename T> uint16_t Image<T>::getImageType() const
+{
+    return im.head.image_type;
+}
+
+template <typename T> void Image<T>::setImageType(uint16_t image_type)
+{
+    im.head.image_type = image_type;
+}
+
+template <typename T> uint16_t Image<T>::getImageIndex() const
+{
+    return im.head.image_index;
+}
+
+template <typename T> void Image<T>::setImageIndex(uint16_t image_index)
+{
+    im.head.image_index = image_index;
+}
+
+template <typename T> uint16_t Image<T>::getImageSeriesIndex() const
+{
+    return im.head.image_series_index;
+}
+
+template <typename T> void Image<T>::setImageSeriesIndex(uint16_t image_series_index)
+{
+    im.head.image_series_index = image_series_index;
+}
+
+    
+// User parameters
+template <typename T> float Image<T>::getUserFloat(unsigned int index) const
+{
+    return im.head.user_float[index];
+}
+
+template <typename T> void Image<T>::setUserFloat(unsigned int index, float value)
+{
+    im.head.user_float[index] = value;
+}
+
+template <typename T> int32_t Image<T>::getUserInt(unsigned int index) const
+{
+    return im.head.user_int[index];
+}
+
+template <typename T> void Image<T>::setUserInt(unsigned int index, int32_t value)
+{
+    im.head.user_int[index] = value;
+}
+
+
+// Flag methods
+template <typename T> uint64_t Image<T>::getFlags() const {
+    return im.head.flags;
+}
+
+template <typename T> bool Image<T>::isFlagSet(const uint64_t val) const {
+    return ismrmrd_is_flag_set(im.head.flags, val);
+}
+
+template <typename T> void Image<T>::setFlag(const uint64_t val) {
+    ismrmrd_set_flag(&(im.head.flags), val);
+}
+
+template <typename T> void Image<T>::setFlags(const uint64_t val) {
+    ismrmrd_set_flags(&(im.head.flags), val);
+}
+
+template <typename T> void Image<T>::clearFlag(const uint64_t val) {
+    ismrmrd_clear_flag(&(im.head.flags), val);
+}
+
+template <typename T> void Image<T>::clearAllFlags() {
+    ismrmrd_clear_all_flags(&(im.head.flags));
+}
+
+// Header
+template <typename T> ImageHeader &Image<T>::getHead() {
+    // This returns a reference
+    return *static_cast<ImageHeader *>(&im.head);
+}
+
+template <typename T> const ImageHeader &Image<T>::getHead() const {
+    // This returns a reference
+    return *static_cast<const ImageHeader *>(&im.head);
+}
+
+template <typename T> void Image<T>::setHead(const ImageHeader &other) {
+    if (other.data_type != im.head.data_type) {
+        throw std::runtime_error("Cannot assign a header of a different data type.");
+    }
+    
+    memcpy(&im.head, &other, sizeof(ImageHeader));
+    if (ismrmrd_make_consistent_image(&im) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+// Attribute string
+template <typename T> void Image<T>::getAttributeString(std::string &attr) const
+{
+   if (im.attribute_string)
+      attr.assign(im.attribute_string);
+   else
+      attr.assign("");
+}
+
+template <typename T> const char *Image<T>::getAttributeString() const
+{
+   return im.attribute_string;
+}
+
+template <typename T> void Image<T>::setAttributeString(const std::string &attr)
+{
+    this->setAttributeString(attr.c_str());
+}
+
+template <typename T> void Image<T>::setAttributeString(const char *attr)
+{
+    // Get the string length
+    size_t length = strlen(attr);
+
+    // Allocate space plus a null terminator and check for success
+    char *newPointer = (char *)realloc(im.attribute_string, (length+1) * sizeof(*im.attribute_string));
+    if (NULL==newPointer) {
+        throw std::runtime_error(build_exception_string());
+    }
+
+    // Make changes only if reallocation was successful
+    im.attribute_string = newPointer;
+    im.head.attribute_string_len = static_cast<uint32_t>(length);
+
+    // Set the null terminator and copy the string
+    im.attribute_string[length] = '\0';
+    strncpy(im.attribute_string, attr, length);
+}
+
+template <typename T> size_t Image<T>::getAttributeStringLength() const
+{
+    return im.head.attribute_string_len;
+}
+
+// Data
+template <typename T> T * Image<T>::getDataPtr() {
+     return static_cast<T*>(im.data);
+}
+
+template <typename T> const T * Image<T>::getDataPtr() const {
+     return static_cast<const T*>(im.data);
+}
+
+template <typename T> size_t Image<T>::getNumberOfDataElements() const {
+    size_t num = 1;
+    num *= im.head.matrix_size[0];
+    num *= im.head.matrix_size[1];
+    num *= im.head.matrix_size[2];
+    num *= im.head.channels;
+    return num;
+}
+
+template <typename T> size_t Image<T>::getDataSize() const {
+    return ismrmrd_size_of_image_data(&im);
+}
+
+template <typename T> T * Image<T>::begin() {
+     return static_cast<T*>(im.data);
+}
+
+template <typename T> T * Image<T>::end() {
+     return static_cast<T*>(im.data)+this->getNumberOfDataElements();
+}
+
+template <typename T> T & Image<T>::operator () (uint16_t ix, uint16_t iy, uint16_t iz, uint16_t channel) {
+     size_t index = ix \
+             + size_t(im.head.matrix_size[0])*iy \
+             + size_t(im.head.matrix_size[1])*size_t(im.head.matrix_size[0])*iz \
+             + size_t(im.head.matrix_size[1])*size_t(im.head.matrix_size[0])*size_t(im.head.matrix_size[2])*channel;
+     return static_cast<T*>(im.data)[index];
+}
+
+//
+// Array class Implementation
+//
+template <typename T> NDArray<T>::NDArray()
+{
+    if (ismrmrd_init_ndarray(&arr) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+    arr.data_type = static_cast<uint16_t>(get_data_type<T>());
+}
+
+template <typename T> NDArray<T>::NDArray(const std::vector<size_t> dimvec)
+{
+    if (ismrmrd_init_ndarray(&arr) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+    arr.data_type = static_cast<uint16_t>(get_data_type<T>());
+    resize(dimvec);
+}
+
+template <typename T> NDArray<T>::NDArray(const NDArray<T> &other)
+{
+    int err = 0;
+    err = ismrmrd_init_ndarray(&arr);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+    err = ismrmrd_copy_ndarray(&arr, &other.arr);
+    if (err) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> NDArray<T>::~NDArray()
+{
+    if (ismrmrd_cleanup_ndarray(&arr) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> NDArray<T> & NDArray<T>::operator= (const NDArray<T> &other)
+{
+    int err = 0;
+    // Assignment makes a copy
+    if (this != &other )
+    {
+        err = ismrmrd_init_ndarray(&arr);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+        err = ismrmrd_copy_ndarray(&arr, &other.arr);
+        if (err) {
+            throw std::runtime_error(build_exception_string());
+        }
+    }
+    return *this;
+}
+
+template <typename T> uint16_t NDArray<T>::getVersion() const {
+    return arr.version;
+};
+
+template <typename T> ISMRMRD_DataTypes NDArray<T>::getDataType() const {
+    return static_cast<ISMRMRD_DataTypes>( arr.data_type );
+}
+
+template <typename T> uint16_t NDArray<T>::getNDim() const {
+    return  arr.ndim;
+};
+    
+template <typename T> const size_t (&NDArray<T>::getDims())[ISMRMRD_NDARRAY_MAXDIM] {
+    return arr.dims;
+};
+
+template <typename T> void NDArray<T>::resize(const std::vector<size_t> dimvec) {
+    if (dimvec.size() > ISMRMRD_NDARRAY_MAXDIM) {
+        throw std::runtime_error("Input vector dimvec is too long.");
+    }
+    arr.ndim = static_cast<uint16_t>(dimvec.size());
+    for (int n=0; n<arr.ndim; n++) {
+        arr.dims[n] = dimvec[n];
+    }
+    if (ismrmrd_make_consistent_ndarray(&arr) != ISMRMRD_NOERROR) {
+        throw std::runtime_error(build_exception_string());
+    }
+}
+
+template <typename T> T * NDArray<T>::getDataPtr() {
+    return static_cast<T*>(arr.data);
+}
+
+template <typename T> const T * NDArray<T>::getDataPtr() const {
+    return static_cast<T*>(arr.data);
+}
+
+template <typename T> size_t NDArray<T>::getDataSize() const {
+    return ismrmrd_size_of_ndarray_data(&arr);
+}
+
+template <typename T> size_t NDArray<T>::getNumberOfElements() const {
+    size_t num = 1;
+    for (int n = 0; n < arr.ndim; n++) {
+        size_t v = arr.dims[n];
+        // This is necessary to prevent bad GCC loop optimization!
+        if (v > 0) {
+            num *= v;
+        }
+    }
+    return num;
+}
+
+template <typename T> T * NDArray<T>::begin() {
+    return static_cast<T*>(arr.data);
+}
+
+template <typename T> T * NDArray<T>::end() {
+    return static_cast<T*>(arr.data)+this->getNumberOfElements();
+}
+
+template <typename T> T & NDArray<T>::operator () (uint16_t x, uint16_t y, uint16_t z, uint16_t w, uint16_t n, uint16_t m, uint16_t l){
+       size_t index = 0;
+       uint16_t indices[ISMRMRD_NDARRAY_MAXDIM] = {x,y,z,w,n,m,l};
+       size_t stride = 1;
+       for (uint16_t i = 0; i < arr.ndim; i++){
+               index += indices[i]*stride;
+               stride *= arr.dims[i];
+       }
+
+       return static_cast<T*>(arr.data)[index];
+}
+
+// Specializations
+// Allowed data types for Images and NDArrays
+template <> EXPORTISMRMRD ISMRMRD_DataTypes get_data_type<uint16_t>()
+{
+    return ISMRMRD_USHORT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<int16_t>()
+{
+    return ISMRMRD_SHORT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<uint32_t>()
+{
+    return ISMRMRD_UINT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<int32_t>()
+{
+    return ISMRMRD_INT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<float>()
+{
+    return ISMRMRD_FLOAT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<double>()
+{
+    return ISMRMRD_DOUBLE;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<complex_float_t>()
+{
+    return ISMRMRD_CXFLOAT;
+}
+
+template <> EXPORTISMRMRD inline ISMRMRD_DataTypes get_data_type<complex_double_t>()
+{
+    return ISMRMRD_CXDOUBLE;
+}
+
+// Images
+template EXPORTISMRMRD class Image<uint16_t>;
+template EXPORTISMRMRD class Image<int16_t>;
+template EXPORTISMRMRD class Image<uint32_t>;
+template EXPORTISMRMRD class Image<int32_t>;
+template EXPORTISMRMRD class Image<float>;
+template EXPORTISMRMRD class Image<double>;
+template EXPORTISMRMRD class Image<complex_float_t>;
+template EXPORTISMRMRD class Image<complex_double_t>;
+
+// NDArrays
+template EXPORTISMRMRD class NDArray<uint16_t>;
+template EXPORTISMRMRD class NDArray<int16_t>;
+template EXPORTISMRMRD class NDArray<uint32_t>;
+template EXPORTISMRMRD class NDArray<int32_t>;
+template EXPORTISMRMRD class NDArray<float>;
+template EXPORTISMRMRD class NDArray<double>;
+template EXPORTISMRMRD class NDArray<complex_float_t>;
+template EXPORTISMRMRD class NDArray<complex_double_t>;
+
+
+// Helper function for generating exception message from ISMRMRD error stack
+std::string build_exception_string(void)
+{
+    char *file = NULL, *func = NULL, *msg = NULL;
+    int line = 0, code = 0;
+    std::stringstream stream;
+    for (int i = 0; ismrmrd_pop_error(&file, &line, &func, &code, &msg); ++i) {
+        if (i > 0) {
+            stream << std::endl;
+        }
+        stream << "ISMRMRD " << ismrmrd_strerror(code) << " in " << func <<
+                " (" << file << ":" << line << ": " << msg;
+    }
+    return stream.str();
+}
+
+
+} // namespace ISMRMRD
diff --git a/libsrc/meta.cpp b/libsrc/meta.cpp
new file mode 100644 (file)
index 0000000..6642442
--- /dev/null
@@ -0,0 +1,59 @@
+#include "ismrmrd/meta.h"
+#include "pugixml.hpp"
+
+
+namespace ISMRMRD
+{
+  void deserialize(const char* xml, MetaContainer& h)
+  {
+    pugi::xml_document doc;
+    pugi::xml_parse_result result = doc.load(xml);
+    
+    if (!result) {
+      throw std::runtime_error("Unable to load ISMRMRD Meta XML document");
+    }
+
+    pugi::xml_node root = doc.child("ismrmrdMeta");
+
+    if (!root) {
+      throw std::runtime_error("ismrmrdMeta tag not found in meta data header");
+    }
+    
+    pugi::xml_node meta = root.child("meta");
+    while (meta) {
+      pugi::xml_node name = meta.child("name");
+      pugi::xml_node value = meta.child("value");
+
+      if (!name || !value) {
+       std::runtime_error("Malformed metadata value");
+      }
+
+      while (value) {
+       h.append(name.child_value(),value.child_value());
+       value = value.next_sibling("value");
+      }
+
+      meta = meta.next_sibling("meta");
+    }
+  }
+
+  void serialize(MetaContainer& h, std::ostream& o)
+  {
+    pugi::xml_document doc;
+    pugi::xml_node root = doc.append_child("ismrmrdMeta");
+    
+    MetaContainer::map_t::iterator it = h.map_.begin();
+    while (it != h.map_.end()) {
+      pugi::xml_node meta = root.append_child("meta");
+      pugi::xml_node name = meta.append_child("name");
+      name.append_child(pugi::node_pcdata).set_value(it->first.c_str());
+      for (unsigned int i = 0; i < it->second.size(); i++) {
+       pugi::xml_node name = meta.append_child("value");
+       name.append_child(pugi::node_pcdata).set_value(it->second[i].as_str());
+      }
+      it++;
+    }
+    doc.save(o);
+  }
+
+}
diff --git a/libsrc/pugiconfig.hpp b/libsrc/pugiconfig.hpp
new file mode 100644 (file)
index 0000000..56f1d22
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * pugixml parser - version 1.4
+ * --------------------------------------------------------
+ * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * Report bugs and download new versions at http://pugixml.org/
+ *
+ * This library is distributed under the MIT License. See notice at the end
+ * of this file.
+ *
+ * This work is based on the pugxml parser, which is:
+ * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
+ */
+
+#ifndef HEADER_PUGICONFIG_HPP
+#define HEADER_PUGICONFIG_HPP
+
+// Uncomment this to enable wchar_t mode
+// #define PUGIXML_WCHAR_MODE
+
+// Uncomment this to disable XPath
+// #define PUGIXML_NO_XPATH
+
+// Uncomment this to disable STL
+// #define PUGIXML_NO_STL
+
+// Uncomment this to disable exceptions
+// #define PUGIXML_NO_EXCEPTIONS
+
+// Set this to control attributes for public classes/functions, i.e.:
+// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
+// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
+// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
+// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
+
+// Tune these constants to adjust memory-related behavior
+// #define PUGIXML_MEMORY_PAGE_SIZE 32768
+// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
+// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
+
+// Uncomment this to switch to header-only version
+// #define PUGIXML_HEADER_ONLY
+// #include "pugixml.cpp"
+
+// Uncomment this to enable long long support
+// #define PUGIXML_HAS_LONG_LONG
+
+#endif
+
+/**
+ * Copyright (c) 2006-2014 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/libsrc/pugixml.cpp b/libsrc/pugixml.cpp
new file mode 100644 (file)
index 0000000..754f92f
--- /dev/null
@@ -0,0 +1,10639 @@
+/**
+ * pugixml parser - version 1.4
+ * --------------------------------------------------------
+ * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * Report bugs and download new versions at http://pugixml.org/
+ *
+ * This library is distributed under the MIT License. See notice at the end
+ * of this file.
+ *
+ * This work is based on the pugxml parser, which is:
+ * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
+ */
+
+#ifndef SOURCE_PUGIXML_CPP
+#define SOURCE_PUGIXML_CPP
+
+#include "pugixml.hpp"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef PUGIXML_WCHAR_MODE
+#      include <wchar.h>
+#endif
+
+#ifndef PUGIXML_NO_XPATH
+#      include <math.h>
+#      include <float.h>
+#      ifdef PUGIXML_NO_EXCEPTIONS
+#              include <setjmp.h>
+#      endif
+#endif
+
+#ifndef PUGIXML_NO_STL
+#      include <istream>
+#      include <ostream>
+#      include <string>
+#endif
+
+// For placement new
+#include <new>
+
+#ifdef _MSC_VER
+#      pragma warning(push)
+#      pragma warning(disable: 4127) // conditional expression is constant
+#      pragma warning(disable: 4324) // structure was padded due to __declspec(align())
+#      pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable
+#      pragma warning(disable: 4702) // unreachable code
+#      pragma warning(disable: 4996) // this function or variable may be unsafe
+#      pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged
+#endif
+
+#ifdef __INTEL_COMPILER
+#      pragma warning(disable: 177) // function was declared but never referenced 
+#      pragma warning(disable: 279) // controlling expression is constant
+#      pragma warning(disable: 1478 1786) // function was declared "deprecated"
+#      pragma warning(disable: 1684) // conversion from pointer to same-sized integral type
+#endif
+
+#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY)
+#      pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away
+#endif
+
+#ifdef __BORLANDC__
+#      pragma option push
+#      pragma warn -8008 // condition is always false
+#      pragma warn -8066 // unreachable code
+#endif
+
+#ifdef __SNC__
+// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug
+#      pragma diag_suppress=178 // function was declared but never referenced
+#      pragma diag_suppress=237 // controlling expression is constant
+#endif
+
+// Inlining controls
+#if defined(_MSC_VER) && _MSC_VER >= 1300
+#      define PUGI__NO_INLINE __declspec(noinline)
+#elif defined(__GNUC__)
+#      define PUGI__NO_INLINE __attribute__((noinline))
+#else
+#      define PUGI__NO_INLINE 
+#endif
+
+// Simple static assertion
+#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; }
+
+// Digital Mars C++ bug workaround for passing char loaded from memory via stack
+#ifdef __DMC__
+#      define PUGI__DMC_VOLATILE volatile
+#else
+#      define PUGI__DMC_VOLATILE
+#endif
+
+// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all)
+#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST)
+using std::memcpy;
+using std::memmove;
+#endif
+
+// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features
+#if defined(_MSC_VER) && !defined(__S3E__)
+#      define PUGI__MSVC_CRT_VERSION _MSC_VER
+#endif
+
+#ifdef PUGIXML_HEADER_ONLY
+#      define PUGI__NS_BEGIN namespace pugi { namespace impl {
+#      define PUGI__NS_END } }
+#      define PUGI__FN inline
+#      define PUGI__FN_NO_INLINE inline
+#else
+#      if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces
+#              define PUGI__NS_BEGIN namespace pugi { namespace impl {
+#              define PUGI__NS_END } }
+#      else
+#              define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace {
+#              define PUGI__NS_END } } }
+#      endif
+#      define PUGI__FN
+#      define PUGI__FN_NO_INLINE PUGI__NO_INLINE
+#endif
+
+// uintptr_t
+#if !defined(_MSC_VER) || _MSC_VER >= 1600
+#      include <stdint.h>
+#else
+#      ifndef _UINTPTR_T_DEFINED
+// No native uintptr_t in MSVC6 and in some WinCE versions
+typedef size_t uintptr_t;
+#define _UINTPTR_T_DEFINED
+#      endif
+PUGI__NS_BEGIN
+       typedef unsigned __int8 uint8_t;
+       typedef unsigned __int16 uint16_t;
+       typedef unsigned __int32 uint32_t;
+PUGI__NS_END
+#endif
+
+// Memory allocation
+PUGI__NS_BEGIN
+       PUGI__FN void* default_allocate(size_t size)
+       {
+               return malloc(size);
+       }
+
+       PUGI__FN void default_deallocate(void* ptr)
+       {
+               free(ptr);
+       }
+
+       template <typename T>
+       struct xml_memory_management_function_storage
+       {
+               static allocation_function allocate;
+               static deallocation_function deallocate;
+       };
+
+       template <typename T> allocation_function xml_memory_management_function_storage<T>::allocate = default_allocate;
+       template <typename T> deallocation_function xml_memory_management_function_storage<T>::deallocate = default_deallocate;
+
+       typedef xml_memory_management_function_storage<int> xml_memory;
+PUGI__NS_END
+
+// String utilities
+PUGI__NS_BEGIN
+       // Get string length
+       PUGI__FN size_t strlength(const char_t* s)
+       {
+               assert(s);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcslen(s);
+       #else
+               return strlen(s);
+       #endif
+       }
+
+       // Compare two strings
+       PUGI__FN bool strequal(const char_t* src, const char_t* dst)
+       {
+               assert(src && dst);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcscmp(src, dst) == 0;
+       #else
+               return strcmp(src, dst) == 0;
+       #endif
+       }
+
+       // Compare lhs with [rhs_begin, rhs_end)
+       PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count)
+       {
+               for (size_t i = 0; i < count; ++i)
+                       if (lhs[i] != rhs[i])
+                               return false;
+       
+               return lhs[count] == 0;
+       }
+
+       // Get length of wide string, even if CRT lacks wide character support
+       PUGI__FN size_t strlength_wide(const wchar_t* s)
+       {
+               assert(s);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcslen(s);
+       #else
+               const wchar_t* end = s;
+               while (*end) end++;
+               return static_cast<size_t>(end - s);
+       #endif
+       }
+
+#ifdef PUGIXML_WCHAR_MODE
+       // Convert string to wide string, assuming all symbols are ASCII
+       PUGI__FN void widen_ascii(wchar_t* dest, const char* source)
+       {
+               for (const char* i = source; *i; ++i) *dest++ = *i;
+               *dest = 0;
+       }
+#endif
+PUGI__NS_END
+
+#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH)
+// auto_ptr-like buffer holder for exception recovery
+PUGI__NS_BEGIN
+       struct buffer_holder
+       {
+               void* data;
+               void (*deleter)(void*);
+
+               buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_)
+               {
+               }
+
+               ~buffer_holder()
+               {
+                       if (data) deleter(data);
+               }
+
+               void* release()
+               {
+                       void* result = data;
+                       data = 0;
+                       return result;
+               }
+       };
+PUGI__NS_END
+#endif
+
+PUGI__NS_BEGIN
+       static const size_t xml_memory_page_size =
+       #ifdef PUGIXML_MEMORY_PAGE_SIZE
+               PUGIXML_MEMORY_PAGE_SIZE
+       #else
+               32768
+       #endif
+               ;
+
+       static const uintptr_t xml_memory_page_alignment = 32;
+       static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1);
+       static const uintptr_t xml_memory_page_name_allocated_mask = 16;
+       static const uintptr_t xml_memory_page_value_allocated_mask = 8;
+       static const uintptr_t xml_memory_page_type_mask = 7;
+
+       struct xml_allocator;
+
+       struct xml_memory_page
+       {
+               static xml_memory_page* construct(void* memory)
+               {
+                       if (!memory) return 0; //$ redundant, left for performance
+
+                       xml_memory_page* result = static_cast<xml_memory_page*>(memory);
+
+                       result->allocator = 0;
+                       result->memory = 0;
+                       result->prev = 0;
+                       result->next = 0;
+                       result->busy_size = 0;
+                       result->freed_size = 0;
+
+                       return result;
+               }
+
+               xml_allocator* allocator;
+
+               void* memory;
+
+               xml_memory_page* prev;
+               xml_memory_page* next;
+
+               size_t busy_size;
+               size_t freed_size;
+
+               char data[1];
+       };
+
+       struct xml_memory_string_header
+       {
+               uint16_t page_offset; // offset from page->data
+               uint16_t full_size; // 0 if string occupies whole page
+       };
+
+       struct xml_allocator
+       {
+               xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size)
+               {
+               }
+
+               xml_memory_page* allocate_page(size_t data_size)
+               {
+                       size_t size = offsetof(xml_memory_page, data) + data_size;
+
+                       // allocate block with some alignment, leaving memory for worst-case padding
+                       void* memory = xml_memory::allocate(size + xml_memory_page_alignment);
+                       if (!memory) return 0;
+
+                       // align upwards to page boundary
+                       void* page_memory = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1));
+
+                       // prepare page structure
+                       xml_memory_page* page = xml_memory_page::construct(page_memory);
+                       assert(page);
+
+                       page->memory = memory;
+                       page->allocator = _root->allocator;
+
+                       return page;
+               }
+
+               static void deallocate_page(xml_memory_page* page)
+               {
+                       xml_memory::deallocate(page->memory);
+               }
+
+               void* allocate_memory_oob(size_t size, xml_memory_page*& out_page);
+
+               void* allocate_memory(size_t size, xml_memory_page*& out_page)
+               {
+                       if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page);
+
+                       void* buf = _root->data + _busy_size;
+
+                       _busy_size += size;
+
+                       out_page = _root;
+
+                       return buf;
+               }
+
+               void deallocate_memory(void* ptr, size_t size, xml_memory_page* page)
+               {
+                       if (page == _root) page->busy_size = _busy_size;
+
+                       assert(ptr >= page->data && ptr < page->data + page->busy_size);
+                       (void)!ptr;
+
+                       page->freed_size += size;
+                       assert(page->freed_size <= page->busy_size);
+
+                       if (page->freed_size == page->busy_size)
+                       {
+                               if (page->next == 0)
+                               {
+                                       assert(_root == page);
+
+                                       // top page freed, just reset sizes
+                                       page->busy_size = page->freed_size = 0;
+                                       _busy_size = 0;
+                               }
+                               else
+                               {
+                                       assert(_root != page);
+                                       assert(page->prev);
+
+                                       // remove from the list
+                                       page->prev->next = page->next;
+                                       page->next->prev = page->prev;
+
+                                       // deallocate
+                                       deallocate_page(page);
+                               }
+                       }
+               }
+
+               char_t* allocate_string(size_t length)
+               {
+                       // allocate memory for string and header block
+                       size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t);
+                       
+                       // round size up to pointer alignment boundary
+                       size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1);
+
+                       xml_memory_page* page;
+                       xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page));
+
+                       if (!header) return 0;
+
+                       // setup header
+                       ptrdiff_t page_offset = reinterpret_cast<char*>(header) - page->data;
+
+                       assert(page_offset >= 0 && page_offset < (1 << 16));
+                       header->page_offset = static_cast<uint16_t>(page_offset);
+
+                       // full_size == 0 for large strings that occupy the whole page
+                       assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0));
+                       header->full_size = static_cast<uint16_t>(full_size < (1 << 16) ? full_size : 0);
+
+                       // round-trip through void* to avoid 'cast increases required alignment of target type' warning
+                       // header is guaranteed a pointer-sized alignment, which should be enough for char_t
+                       return static_cast<char_t*>(static_cast<void*>(header + 1));
+               }
+
+               void deallocate_string(char_t* string)
+               {
+                       // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings
+                       // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string
+
+                       // get header
+                       xml_memory_string_header* header = static_cast<xml_memory_string_header*>(static_cast<void*>(string)) - 1;
+
+                       // deallocate
+                       size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset;
+                       xml_memory_page* page = reinterpret_cast<xml_memory_page*>(static_cast<void*>(reinterpret_cast<char*>(header) - page_offset));
+
+                       // if full_size == 0 then this string occupies the whole page
+                       size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size;
+
+                       deallocate_memory(header, full_size, page);
+               }
+
+               xml_memory_page* _root;
+               size_t _busy_size;
+       };
+
+       PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page)
+       {
+               const size_t large_allocation_threshold = xml_memory_page_size / 4;
+
+               xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size);
+               out_page = page;
+
+               if (!page) return 0;
+
+               if (size <= large_allocation_threshold)
+               {
+                       _root->busy_size = _busy_size;
+
+                       // insert page at the end of linked list
+                       page->prev = _root;
+                       _root->next = page;
+                       _root = page;
+
+                       _busy_size = size;
+               }
+               else
+               {
+                       // insert page before the end of linked list, so that it is deleted as soon as possible
+                       // the last page is not deleted even if it's empty (see deallocate_memory)
+                       assert(_root->prev);
+
+                       page->prev = _root->prev;
+                       page->next = _root;
+
+                       _root->prev->next = page;
+                       _root->prev = page;
+               }
+
+               // allocate inside page
+               page->busy_size = size;
+
+               return page->data;
+       }
+PUGI__NS_END
+
+namespace pugi
+{
+       /// A 'name=value' XML attribute structure.
+       struct xml_attribute_struct
+       {
+               /// Default ctor
+               xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast<uintptr_t>(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0)
+               {
+               }
+
+               uintptr_t header;
+
+               char_t* name;   ///< Pointer to attribute name.
+               char_t* value;  ///< Pointer to attribute value.
+
+               xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list)
+               xml_attribute_struct* next_attribute;   ///< Next attribute
+       };
+
+       /// An XML document tree node.
+       struct xml_node_struct
+       {
+               /// Default ctor
+               /// \param type - node type
+               xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast<uintptr_t>(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0)
+               {
+               }
+
+               uintptr_t header;
+
+               xml_node_struct*                parent;                                 ///< Pointer to parent
+
+               char_t*                                 name;                                   ///< Pointer to element name.
+               char_t*                                 value;                                  ///< Pointer to any associated string data.
+
+               xml_node_struct*                first_child;                    ///< First child
+               
+               xml_node_struct*                prev_sibling_c;                 ///< Left brother (cyclic list)
+               xml_node_struct*                next_sibling;                   ///< Right brother
+               
+               xml_attribute_struct*   first_attribute;                ///< First attribute
+       };
+}
+
+PUGI__NS_BEGIN
+       struct xml_extra_buffer
+       {
+               char_t* buffer;
+               xml_extra_buffer* next;
+       };
+
+       struct xml_document_struct: public xml_node_struct, public xml_allocator
+       {
+               xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0)
+               {
+               }
+
+               const char_t* buffer;
+
+               xml_extra_buffer* extra_buffers;
+       };
+
+       inline xml_allocator& get_allocator(const xml_node_struct* node)
+       {
+               assert(node);
+
+               return *reinterpret_cast<xml_memory_page*>(node->header & xml_memory_page_pointer_mask)->allocator;
+       }
+PUGI__NS_END
+
+// Low-level DOM operations
+PUGI__NS_BEGIN
+       inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc)
+       {
+               xml_memory_page* page;
+               void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page);
+
+               return new (memory) xml_attribute_struct(page);
+       }
+
+       inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type)
+       {
+               xml_memory_page* page;
+               void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page);
+
+               return new (memory) xml_node_struct(page, type);
+       }
+
+       inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc)
+       {
+               uintptr_t header = a->header;
+
+               if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name);
+               if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value);
+
+               alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask));
+       }
+
+       inline void destroy_node(xml_node_struct* n, xml_allocator& alloc)
+       {
+               uintptr_t header = n->header;
+
+               if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name);
+               if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value);
+
+               for (xml_attribute_struct* attr = n->first_attribute; attr; )
+               {
+                       xml_attribute_struct* next = attr->next_attribute;
+
+                       destroy_attribute(attr, alloc);
+
+                       attr = next;
+               }
+
+               for (xml_node_struct* child = n->first_child; child; )
+               {
+                       xml_node_struct* next = child->next_sibling;
+
+                       destroy_node(child, alloc);
+
+                       child = next;
+               }
+
+               alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask));
+       }
+
+       PUGI__FN_NO_INLINE xml_node_struct* append_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element)
+       {
+               xml_node_struct* child = allocate_node(alloc, type);
+               if (!child) return 0;
+
+               child->parent = node;
+
+               xml_node_struct* first_child = node->first_child;
+                       
+               if (first_child)
+               {
+                       xml_node_struct* last_child = first_child->prev_sibling_c;
+
+                       last_child->next_sibling = child;
+                       child->prev_sibling_c = last_child;
+                       first_child->prev_sibling_c = child;
+               }
+               else
+               {
+                       node->first_child = child;
+                       child->prev_sibling_c = child;
+               }
+                       
+               return child;
+       }
+
+       PUGI__FN_NO_INLINE xml_attribute_struct* append_attribute_ll(xml_node_struct* node, xml_allocator& alloc)
+       {
+               xml_attribute_struct* a = allocate_attribute(alloc);
+               if (!a) return 0;
+
+               xml_attribute_struct* first_attribute = node->first_attribute;
+
+               if (first_attribute)
+               {
+                       xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c;
+
+                       last_attribute->next_attribute = a;
+                       a->prev_attribute_c = last_attribute;
+                       first_attribute->prev_attribute_c = a;
+               }
+               else
+               {
+                       node->first_attribute = a;
+                       a->prev_attribute_c = a;
+               }
+                       
+               return a;
+       }
+PUGI__NS_END
+
+// Helper classes for code generation
+PUGI__NS_BEGIN
+       struct opt_false
+       {
+               enum { value = 0 };
+       };
+
+       struct opt_true
+       {
+               enum { value = 1 };
+       };
+PUGI__NS_END
+
+// Unicode utilities
+PUGI__NS_BEGIN
+       inline uint16_t endian_swap(uint16_t value)
+       {
+               return static_cast<uint16_t>(((value & 0xff) << 8) | (value >> 8));
+       }
+
+       inline uint32_t endian_swap(uint32_t value)
+       {
+               return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24);
+       }
+
+       struct utf8_counter
+       {
+               typedef size_t value_type;
+
+               static value_type low(value_type result, uint32_t ch)
+               {
+                       // U+0000..U+007F
+                       if (ch < 0x80) return result + 1;
+                       // U+0080..U+07FF
+                       else if (ch < 0x800) return result + 2;
+                       // U+0800..U+FFFF
+                       else return result + 3;
+               }
+
+               static value_type high(value_type result, uint32_t)
+               {
+                       // U+10000..U+10FFFF
+                       return result + 4;
+               }
+       };
+
+       struct utf8_writer
+       {
+               typedef uint8_t* value_type;
+
+               static value_type low(value_type result, uint32_t ch)
+               {
+                       // U+0000..U+007F
+                       if (ch < 0x80)
+                       {
+                               *result = static_cast<uint8_t>(ch);
+                               return result + 1;
+                       }
+                       // U+0080..U+07FF
+                       else if (ch < 0x800)
+                       {
+                               result[0] = static_cast<uint8_t>(0xC0 | (ch >> 6));
+                               result[1] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+                               return result + 2;
+                       }
+                       // U+0800..U+FFFF
+                       else
+                       {
+                               result[0] = static_cast<uint8_t>(0xE0 | (ch >> 12));
+                               result[1] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F));
+                               result[2] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+                               return result + 3;
+                       }
+               }
+
+               static value_type high(value_type result, uint32_t ch)
+               {
+                       // U+10000..U+10FFFF
+                       result[0] = static_cast<uint8_t>(0xF0 | (ch >> 18));
+                       result[1] = static_cast<uint8_t>(0x80 | ((ch >> 12) & 0x3F));
+                       result[2] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F));
+                       result[3] = static_cast<uint8_t>(0x80 | (ch & 0x3F));
+                       return result + 4;
+               }
+
+               static value_type any(value_type result, uint32_t ch)
+               {
+                       return (ch < 0x10000) ? low(result, ch) : high(result, ch);
+               }
+       };
+
+       struct utf16_counter
+       {
+               typedef size_t value_type;
+
+               static value_type low(value_type result, uint32_t)
+               {
+                       return result + 1;
+               }
+
+               static value_type high(value_type result, uint32_t)
+               {
+                       return result + 2;
+               }
+       };
+
+       struct utf16_writer
+       {
+               typedef uint16_t* value_type;
+
+               static value_type low(value_type result, uint32_t ch)
+               {
+                       *result = static_cast<uint16_t>(ch);
+
+                       return result + 1;
+               }
+
+               static value_type high(value_type result, uint32_t ch)
+               {
+                       uint32_t msh = static_cast<uint32_t>(ch - 0x10000) >> 10;
+                       uint32_t lsh = static_cast<uint32_t>(ch - 0x10000) & 0x3ff;
+
+                       result[0] = static_cast<uint16_t>(0xD800 + msh);
+                       result[1] = static_cast<uint16_t>(0xDC00 + lsh);
+
+                       return result + 2;
+               }
+
+               static value_type any(value_type result, uint32_t ch)
+               {
+                       return (ch < 0x10000) ? low(result, ch) : high(result, ch);
+               }
+       };
+
+       struct utf32_counter
+       {
+               typedef size_t value_type;
+
+               static value_type low(value_type result, uint32_t)
+               {
+                       return result + 1;
+               }
+
+               static value_type high(value_type result, uint32_t)
+               {
+                       return result + 1;
+               }
+       };
+
+       struct utf32_writer
+       {
+               typedef uint32_t* value_type;
+
+               static value_type low(value_type result, uint32_t ch)
+               {
+                       *result = ch;
+
+                       return result + 1;
+               }
+
+               static value_type high(value_type result, uint32_t ch)
+               {
+                       *result = ch;
+
+                       return result + 1;
+               }
+
+               static value_type any(value_type result, uint32_t ch)
+               {
+                       *result = ch;
+
+                       return result + 1;
+               }
+       };
+
+       struct latin1_writer
+       {
+               typedef uint8_t* value_type;
+
+               static value_type low(value_type result, uint32_t ch)
+               {
+                       *result = static_cast<uint8_t>(ch > 255 ? '?' : ch);
+
+                       return result + 1;
+               }
+
+               static value_type high(value_type result, uint32_t ch)
+               {
+                       (void)ch;
+
+                       *result = '?';
+
+                       return result + 1;
+               }
+       };
+
+       template <size_t size> struct wchar_selector;
+
+       template <> struct wchar_selector<2>
+       {
+               typedef uint16_t type;
+               typedef utf16_counter counter;
+               typedef utf16_writer writer;
+       };
+
+       template <> struct wchar_selector<4>
+       {
+               typedef uint32_t type;
+               typedef utf32_counter counter;
+               typedef utf32_writer writer;
+       };
+
+       typedef wchar_selector<sizeof(wchar_t)>::counter wchar_counter;
+       typedef wchar_selector<sizeof(wchar_t)>::writer wchar_writer;
+
+       template <typename Traits, typename opt_swap = opt_false> struct utf_decoder
+       {
+               static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result)
+               {
+                       const uint8_t utf8_byte_mask = 0x3f;
+
+                       while (size)
+                       {
+                               uint8_t lead = *data;
+
+                               // 0xxxxxxx -> U+0000..U+007F
+                               if (lead < 0x80)
+                               {
+                                       result = Traits::low(result, lead);
+                                       data += 1;
+                                       size -= 1;
+
+                                       // process aligned single-byte (ascii) blocks
+                                       if ((reinterpret_cast<uintptr_t>(data) & 3) == 0)
+                                       {
+                                               // round-trip through void* to silence 'cast increases required alignment of target type' warnings
+                                               while (size >= 4 && (*static_cast<const uint32_t*>(static_cast<const void*>(data)) & 0x80808080) == 0)
+                                               {
+                                                       result = Traits::low(result, data[0]);
+                                                       result = Traits::low(result, data[1]);
+                                                       result = Traits::low(result, data[2]);
+                                                       result = Traits::low(result, data[3]);
+                                                       data += 4;
+                                                       size -= 4;
+                                               }
+                                       }
+                               }
+                               // 110xxxxx -> U+0080..U+07FF
+                               else if (static_cast<unsigned int>(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80)
+                               {
+                                       result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask));
+                                       data += 2;
+                                       size -= 2;
+                               }
+                               // 1110xxxx -> U+0800-U+FFFF
+                               else if (static_cast<unsigned int>(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80)
+                               {
+                                       result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask));
+                                       data += 3;
+                                       size -= 3;
+                               }
+                               // 11110xxx -> U+10000..U+10FFFF
+                               else if (static_cast<unsigned int>(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80)
+                               {
+                                       result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask));
+                                       data += 4;
+                                       size -= 4;
+                               }
+                               // 10xxxxxx or 11111xxx -> invalid
+                               else
+                               {
+                                       data += 1;
+                                       size -= 1;
+                               }
+                       }
+
+                       return result;
+               }
+
+               static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result)
+               {
+                       const uint16_t* end = data + size;
+
+                       while (data < end)
+                       {
+                               unsigned int lead = opt_swap::value ? endian_swap(*data) : *data;
+
+                               // U+0000..U+D7FF
+                               if (lead < 0xD800)
+                               {
+                                       result = Traits::low(result, lead);
+                                       data += 1;
+                               }
+                               // U+E000..U+FFFF
+                               else if (static_cast<unsigned int>(lead - 0xE000) < 0x2000)
+                               {
+                                       result = Traits::low(result, lead);
+                                       data += 1;
+                               }
+                               // surrogate pair lead
+                               else if (static_cast<unsigned int>(lead - 0xD800) < 0x400 && data + 1 < end)
+                               {
+                                       uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1];
+
+                                       if (static_cast<unsigned int>(next - 0xDC00) < 0x400)
+                                       {
+                                               result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff));
+                                               data += 2;
+                                       }
+                                       else
+                                       {
+                                               data += 1;
+                                       }
+                               }
+                               else
+                               {
+                                       data += 1;
+                               }
+                       }
+
+                       return result;
+               }
+
+               static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result)
+               {
+                       const uint32_t* end = data + size;
+
+                       while (data < end)
+                       {
+                               uint32_t lead = opt_swap::value ? endian_swap(*data) : *data;
+
+                               // U+0000..U+FFFF
+                               if (lead < 0x10000)
+                               {
+                                       result = Traits::low(result, lead);
+                                       data += 1;
+                               }
+                               // U+10000..U+10FFFF
+                               else
+                               {
+                                       result = Traits::high(result, lead);
+                                       data += 1;
+                               }
+                       }
+
+                       return result;
+               }
+
+               static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result)
+               {
+                       for (size_t i = 0; i < size; ++i)
+                       {
+                               result = Traits::low(result, data[i]);
+                       }
+
+                       return result;
+               }
+
+               static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result)
+               {
+                       return decode_utf16_block(data, size, result);
+               }
+
+               static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result)
+               {
+                       return decode_utf32_block(data, size, result);
+               }
+
+               static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result)
+               {
+                       return decode_wchar_block_impl(reinterpret_cast<const wchar_selector<sizeof(wchar_t)>::type*>(data), size, result);
+               }
+       };
+
+       template <typename T> PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length)
+       {
+               for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]);
+       }
+
+#ifdef PUGIXML_WCHAR_MODE
+       PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length)
+       {
+               for (size_t i = 0; i < length; ++i) result[i] = static_cast<wchar_t>(endian_swap(static_cast<wchar_selector<sizeof(wchar_t)>::type>(data[i])));
+       }
+#endif
+PUGI__NS_END
+
+PUGI__NS_BEGIN
+       enum chartype_t
+       {
+               ct_parse_pcdata = 1,    // \0, &, \r, <
+               ct_parse_attr = 2,              // \0, &, \r, ', "
+               ct_parse_attr_ws = 4,   // \0, &, \r, ', ", \n, tab
+               ct_space = 8,                   // \r, \n, space, tab
+               ct_parse_cdata = 16,    // \0, ], >, \r
+               ct_parse_comment = 32,  // \0, -, >, \r
+               ct_symbol = 64,                 // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, .
+               ct_start_symbol = 128   // Any symbol > 127, a-z, A-Z, _, :
+       };
+
+       static const unsigned char chartype_table[256] =
+       {
+               55,  0,   0,   0,   0,   0,   0,   0,      0,   12,  12,  0,   0,   63,  0,   0,   // 0-15
+               0,   0,   0,   0,   0,   0,   0,   0,      0,   0,   0,   0,   0,   0,   0,   0,   // 16-31
+               8,   0,   6,   0,   0,   0,   7,   6,      0,   0,   0,   0,   0,   96,  64,  0,   // 32-47
+               64,  64,  64,  64,  64,  64,  64,  64,     64,  64,  192, 0,   1,   0,   48,  0,   // 48-63
+               0,   192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192, // 64-79
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 0,   0,   16,  0,   192, // 80-95
+               0,   192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192, // 96-111
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 0, 0, 0, 0, 0,           // 112-127
+
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192, // 128+
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192,
+               192, 192, 192, 192, 192, 192, 192, 192,    192, 192, 192, 192, 192, 192, 192, 192
+       };
+
+       enum chartypex_t
+       {
+               ctx_special_pcdata = 1,   // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, >
+               ctx_special_attr = 2,     // Any symbol >= 0 and < 32 (except \t), &, <, >, "
+               ctx_start_symbol = 4,     // Any symbol > 127, a-z, A-Z, _
+               ctx_digit = 8,                    // 0-9
+               ctx_symbol = 16                   // Any symbol > 127, a-z, A-Z, 0-9, _, -, .
+       };
+       
+       static const unsigned char chartypex_table[256] =
+       {
+               3,  3,  3,  3,  3,  3,  3,  3,     3,  0,  2,  3,  3,  2,  3,  3,     // 0-15
+               3,  3,  3,  3,  3,  3,  3,  3,     3,  3,  3,  3,  3,  3,  3,  3,     // 16-31
+               0,  0,  2,  0,  0,  0,  3,  0,     0,  0,  0,  0,  0, 16, 16,  0,     // 32-47
+               24, 24, 24, 24, 24, 24, 24, 24,    24, 24, 0,  0,  3,  0,  3,  0,     // 48-63
+
+               0,  20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,    // 64-79
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 0,  0,  0,  0,  20,    // 80-95
+               0,  20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,    // 96-111
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 0,  0,  0,  0,  0,     // 112-127
+
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,    // 128+
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20,
+               20, 20, 20, 20, 20, 20, 20, 20,    20, 20, 20, 20, 20, 20, 20, 20
+       };
+       
+#ifdef PUGIXML_WCHAR_MODE
+       #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast<unsigned int>(c) < 128 ? table[static_cast<unsigned int>(c)] : table[128]) & (ct))
+#else
+       #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast<unsigned char>(c)] & (ct))
+#endif
+
+       #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table)
+       #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table)
+
+       PUGI__FN bool is_little_endian()
+       {
+               unsigned int ui = 1;
+
+               return *reinterpret_cast<unsigned char*>(&ui) == 1;
+       }
+
+       PUGI__FN xml_encoding get_wchar_encoding()
+       {
+               PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4);
+
+               if (sizeof(wchar_t) == 2)
+                       return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+               else 
+                       return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+       }
+
+       PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
+       {
+               // look for BOM in first few bytes
+               if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be;
+               if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le;
+               if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be;
+               if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le;
+               if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8;
+
+               // look for <, <? or <?xm in various encodings
+               if (d0 == 0 && d1 == 0 && d2 == 0 && d3 == 0x3c) return encoding_utf32_be;
+               if (d0 == 0x3c && d1 == 0 && d2 == 0 && d3 == 0) return encoding_utf32_le;
+               if (d0 == 0 && d1 == 0x3c && d2 == 0 && d3 == 0x3f) return encoding_utf16_be;
+               if (d0 == 0x3c && d1 == 0 && d2 == 0x3f && d3 == 0) return encoding_utf16_le;
+               if (d0 == 0x3c && d1 == 0x3f && d2 == 0x78 && d3 == 0x6d) return encoding_utf8;
+
+               // look for utf16 < followed by node name (this may fail, but is better than utf8 since it's zero terminated so early)
+               if (d0 == 0 && d1 == 0x3c) return encoding_utf16_be;
+               if (d0 == 0x3c && d1 == 0) return encoding_utf16_le;
+
+               // no known BOM detected, assume utf8
+               return encoding_utf8;
+       }
+
+       PUGI__FN xml_encoding get_buffer_encoding(xml_encoding encoding, const void* contents, size_t size)
+       {
+               // replace wchar encoding with utf implementation
+               if (encoding == encoding_wchar) return get_wchar_encoding();
+
+               // replace utf16 encoding with utf16 with specific endianness
+               if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+               // replace utf32 encoding with utf32 with specific endianness
+               if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+               // only do autodetection if no explicit encoding is requested
+               if (encoding != encoding_auto) return encoding;
+
+               // skip encoding autodetection if input buffer is too small
+               if (size < 4) return encoding_utf8;
+
+               // try to guess encoding (based on XML specification, Appendix F.1)
+               const uint8_t* data = static_cast<const uint8_t*>(contents);
+
+               PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3];
+
+               return guess_buffer_encoding(d0, d1, d2, d3);
+       }
+
+       PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+       {
+               size_t length = size / sizeof(char_t);
+
+               if (is_mutable)
+               {
+                       out_buffer = static_cast<char_t*>(const_cast<void*>(contents));
+                       out_length = length;
+               }
+               else
+               {
+                       char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+                       if (!buffer) return false;
+
+                       memcpy(buffer, contents, length * sizeof(char_t));
+                       buffer[length] = 0;
+
+                       out_buffer = buffer;
+                       out_length = length + 1;
+               }
+
+               return true;
+       }
+
+#ifdef PUGIXML_WCHAR_MODE
+       PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re)
+       {
+               return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) ||
+                          (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be);
+       }
+
+       PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+       {
+               const char_t* data = static_cast<const char_t*>(contents);
+               size_t length = size / sizeof(char_t);
+
+               if (is_mutable)
+               {
+                       char_t* buffer = const_cast<char_t*>(data);
+
+                       convert_wchar_endian_swap(buffer, data, length);
+
+                       out_buffer = buffer;
+                       out_length = length;
+               }
+               else
+               {
+                       char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+                       if (!buffer) return false;
+
+                       convert_wchar_endian_swap(buffer, data, length);
+                       buffer[length] = 0;
+
+                       out_buffer = buffer;
+                       out_length = length + 1;
+               }
+
+               return true;
+       }
+
+       PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size)
+       {
+               const uint8_t* data = static_cast<const uint8_t*>(contents);
+               size_t data_length = size;
+
+               // first pass: get length in wchar_t units
+               size_t length = utf_decoder<wchar_counter>::decode_utf8_block(data, data_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert utf8 input to wchar_t
+               wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer);
+               wchar_writer::value_type oend = utf_decoder<wchar_writer>::decode_utf8_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       template <typename opt_swap> PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap)
+       {
+               const uint16_t* data = static_cast<const uint16_t*>(contents);
+               size_t data_length = size / sizeof(uint16_t);
+
+               // first pass: get length in wchar_t units
+               size_t length = utf_decoder<wchar_counter, opt_swap>::decode_utf16_block(data, data_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert utf16 input to wchar_t
+               wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer);
+               wchar_writer::value_type oend = utf_decoder<wchar_writer, opt_swap>::decode_utf16_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       template <typename opt_swap> PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap)
+       {
+               const uint32_t* data = static_cast<const uint32_t*>(contents);
+               size_t data_length = size / sizeof(uint32_t);
+
+               // first pass: get length in wchar_t units
+               size_t length = utf_decoder<wchar_counter, opt_swap>::decode_utf32_block(data, data_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert utf32 input to wchar_t
+               wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer);
+               wchar_writer::value_type oend = utf_decoder<wchar_writer, opt_swap>::decode_utf32_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size)
+       {
+               const uint8_t* data = static_cast<const uint8_t*>(contents);
+               size_t data_length = size;
+
+               // get length in wchar_t units
+               size_t length = data_length;
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // convert latin1 input to wchar_t
+               wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer);
+               wchar_writer::value_type oend = utf_decoder<wchar_writer>::decode_latin1_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
+       {
+               // get native encoding
+               xml_encoding wchar_encoding = get_wchar_encoding();
+
+               // fast path: no conversion required
+               if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+               // only endian-swapping is required
+               if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable);
+
+               // source encoding is utf8
+               if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size);
+
+               // source encoding is utf16
+               if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+               {
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+                       return (native_encoding == encoding) ?
+                               convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) :
+                               convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true());
+               }
+
+               // source encoding is utf32
+               if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+               {
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+                       return (native_encoding == encoding) ?
+                               convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) :
+                               convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true());
+               }
+
+               // source encoding is latin1
+               if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size);
+
+               assert(!"Invalid encoding");
+               return false;
+       }
+#else
+       template <typename opt_swap> PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap)
+       {
+               const uint16_t* data = static_cast<const uint16_t*>(contents);
+               size_t data_length = size / sizeof(uint16_t);
+
+               // first pass: get length in utf8 units
+               size_t length = utf_decoder<utf8_counter, opt_swap>::decode_utf16_block(data, data_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert utf16 input to utf8
+               uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer);
+               uint8_t* oend = utf_decoder<utf8_writer, opt_swap>::decode_utf16_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       template <typename opt_swap> PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap)
+       {
+               const uint32_t* data = static_cast<const uint32_t*>(contents);
+               size_t data_length = size / sizeof(uint32_t);
+
+               // first pass: get length in utf8 units
+               size_t length = utf_decoder<utf8_counter, opt_swap>::decode_utf32_block(data, data_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert utf32 input to utf8
+               uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer);
+               uint8_t* oend = utf_decoder<utf8_writer, opt_swap>::decode_utf32_block(data, data_length, obegin);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size)
+       {
+               for (size_t i = 0; i < size; ++i)
+                       if (data[i] > 127)
+                               return i;
+
+               return size;
+       }
+
+       PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
+       {
+               const uint8_t* data = static_cast<const uint8_t*>(contents);
+               size_t data_length = size;
+
+               // get size of prefix that does not need utf8 conversion
+               size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length);
+               assert(prefix_length <= data_length);
+
+               const uint8_t* postfix = data + prefix_length;
+               size_t postfix_length = data_length - prefix_length;
+
+               // if no conversion is needed, just return the original buffer
+               if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+               // first pass: get length in utf8 units
+               size_t length = prefix_length + utf_decoder<utf8_counter>::decode_latin1_block(postfix, postfix_length, 0);
+
+               // allocate buffer of suitable length
+               char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+               if (!buffer) return false;
+
+               // second pass: convert latin1 input to utf8
+               memcpy(buffer, data, prefix_length);
+
+               uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer);
+               uint8_t* oend = utf_decoder<utf8_writer>::decode_latin1_block(postfix, postfix_length, obegin + prefix_length);
+
+               assert(oend == obegin + length);
+               *oend = 0;
+
+               out_buffer = buffer;
+               out_length = length + 1;
+
+               return true;
+       }
+
+       PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
+       {
+               // fast path: no conversion required
+               if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
+
+               // source encoding is utf16
+               if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+               {
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+                       return (native_encoding == encoding) ?
+                               convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) :
+                               convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true());
+               }
+
+               // source encoding is utf32
+               if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+               {
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+                       return (native_encoding == encoding) ?
+                               convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) :
+                               convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true());
+               }
+
+               // source encoding is latin1
+               if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable);
+
+               assert(!"Invalid encoding");
+               return false;
+       }
+#endif
+
+       PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length)
+       {
+               // get length in utf8 characters
+               return utf_decoder<utf8_counter>::decode_wchar_block(str, length, 0);
+       }
+
+       PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length)
+       {
+               // convert to utf8
+               uint8_t* begin = reinterpret_cast<uint8_t*>(buffer);
+               uint8_t* end = utf_decoder<utf8_writer>::decode_wchar_block(str, length, begin);
+       
+               assert(begin + size == end);
+               (void)!end;
+
+               // zero-terminate
+               buffer[size] = 0;
+       }
+       
+#ifndef PUGIXML_NO_STL
+       PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length)
+       {
+               // first pass: get length in utf8 characters
+               size_t size = as_utf8_begin(str, length);
+
+               // allocate resulting string
+               std::string result;
+               result.resize(size);
+
+               // second pass: convert to utf8
+               if (size > 0) as_utf8_end(&result[0], size, str, length);
+
+               return result;
+       }
+
+       PUGI__FN std::basic_string<wchar_t> as_wide_impl(const char* str, size_t size)
+       {
+               const uint8_t* data = reinterpret_cast<const uint8_t*>(str);
+
+               // first pass: get length in wchar_t units
+               size_t length = utf_decoder<wchar_counter>::decode_utf8_block(data, size, 0);
+
+               // allocate resulting string
+               std::basic_string<wchar_t> result;
+               result.resize(length);
+
+               // second pass: convert to wchar_t
+               if (length > 0)
+               {
+                       wchar_writer::value_type begin = reinterpret_cast<wchar_writer::value_type>(&result[0]);
+                       wchar_writer::value_type end = utf_decoder<wchar_writer>::decode_utf8_block(data, size, begin);
+
+                       assert(begin + length == end);
+                       (void)!end;
+               }
+
+               return result;
+       }
+#endif
+
+       inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target)
+       {
+               assert(target);
+               size_t target_length = strlength(target);
+
+               // always reuse document buffer memory if possible
+               if (!allocated) return target_length >= length;
+
+               // reuse heap memory if waste is not too great
+               const size_t reuse_threshold = 32;
+
+               return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2);
+       }
+
+       PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source)
+       {
+               assert(header);
+
+               size_t source_length = strlength(source);
+
+               if (source_length == 0)
+               {
+                       // empty string and null pointer are equivalent, so just deallocate old memory
+                       xml_allocator* alloc = reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)->allocator;
+
+                       if (header & header_mask) alloc->deallocate_string(dest);
+                       
+                       // mark the string as not allocated
+                       dest = 0;
+                       header &= ~header_mask;
+
+                       return true;
+               }
+               else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest))
+               {
+                       // we can reuse old buffer, so just copy the new data (including zero terminator)
+                       memcpy(dest, source, (source_length + 1) * sizeof(char_t));
+                       
+                       return true;
+               }
+               else
+               {
+                       xml_allocator* alloc = reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)->allocator;
+
+                       // allocate new buffer
+                       char_t* buf = alloc->allocate_string(source_length + 1);
+                       if (!buf) return false;
+
+                       // copy the string (including zero terminator)
+                       memcpy(buf, source, (source_length + 1) * sizeof(char_t));
+
+                       // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures)
+                       if (header & header_mask) alloc->deallocate_string(dest);
+                       
+                       // the string is now allocated, so set the flag
+                       dest = buf;
+                       header |= header_mask;
+
+                       return true;
+               }
+       }
+
+       struct gap
+       {
+               char_t* end;
+               size_t size;
+                       
+               gap(): end(0), size(0)
+               {
+               }
+                       
+               // Push new gap, move s count bytes further (skipping the gap).
+               // Collapse previous gap.
+               void push(char_t*& s, size_t count)
+               {
+                       if (end) // there was a gap already; collapse it
+                       {
+                               // Move [old_gap_end, new_gap_start) to [old_gap_start, ...)
+                               assert(s >= end);
+                               memmove(end - size, end, reinterpret_cast<char*>(s) - reinterpret_cast<char*>(end));
+                       }
+                               
+                       s += count; // end of current gap
+                               
+                       // "merge" two gaps
+                       end = s;
+                       size += count;
+               }
+                       
+               // Collapse all gaps, return past-the-end pointer
+               char_t* flush(char_t* s)
+               {
+                       if (end)
+                       {
+                               // Move [old_gap_end, current_pos) to [old_gap_start, ...)
+                               assert(s >= end);
+                               memmove(end - size, end, reinterpret_cast<char*>(s) - reinterpret_cast<char*>(end));
+
+                               return s - size;
+                       }
+                       else return s;
+               }
+       };
+       
+       PUGI__FN char_t* strconv_escape(char_t* s, gap& g)
+       {
+               char_t* stre = s + 1;
+
+               switch (*stre)
+               {
+                       case '#':       // &#...
+                       {
+                               unsigned int ucsc = 0;
+
+                               if (stre[1] == 'x') // &#x... (hex code)
+                               {
+                                       stre += 2;
+
+                                       char_t ch = *stre;
+
+                                       if (ch == ';') return stre;
+
+                                       for (;;)
+                                       {
+                                               if (static_cast<unsigned int>(ch - '0') <= 9)
+                                                       ucsc = 16 * ucsc + (ch - '0');
+                                               else if (static_cast<unsigned int>((ch | ' ') - 'a') <= 5)
+                                                       ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10);
+                                               else if (ch == ';')
+                                                       break;
+                                               else // cancel
+                                                       return stre;
+
+                                               ch = *++stre;
+                                       }
+                                       
+                                       ++stre;
+                               }
+                               else    // &#... (dec code)
+                               {
+                                       char_t ch = *++stre;
+
+                                       if (ch == ';') return stre;
+
+                                       for (;;)
+                                       {
+                                               if (static_cast<unsigned int>(static_cast<unsigned int>(ch) - '0') <= 9)
+                                                       ucsc = 10 * ucsc + (ch - '0');
+                                               else if (ch == ';')
+                                                       break;
+                                               else // cancel
+                                                       return stre;
+
+                                               ch = *++stre;
+                                       }
+                                       
+                                       ++stre;
+                               }
+
+                       #ifdef PUGIXML_WCHAR_MODE
+                               s = reinterpret_cast<char_t*>(wchar_writer::any(reinterpret_cast<wchar_writer::value_type>(s), ucsc));
+                       #else
+                               s = reinterpret_cast<char_t*>(utf8_writer::any(reinterpret_cast<uint8_t*>(s), ucsc));
+                       #endif
+                                       
+                               g.push(s, stre - s);
+                               return stre;
+                       }
+
+                       case 'a':       // &a
+                       {
+                               ++stre;
+
+                               if (*stre == 'm') // &am
+                               {
+                                       if (*++stre == 'p' && *++stre == ';') // &amp;
+                                       {
+                                               *s++ = '&';
+                                               ++stre;
+                                                       
+                                               g.push(s, stre - s);
+                                               return stre;
+                                       }
+                               }
+                               else if (*stre == 'p') // &ap
+                               {
+                                       if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // &apos;
+                                       {
+                                               *s++ = '\'';
+                                               ++stre;
+
+                                               g.push(s, stre - s);
+                                               return stre;
+                                       }
+                               }
+                               break;
+                       }
+
+                       case 'g': // &g
+                       {
+                               if (*++stre == 't' && *++stre == ';') // &gt;
+                               {
+                                       *s++ = '>';
+                                       ++stre;
+                                       
+                                       g.push(s, stre - s);
+                                       return stre;
+                               }
+                               break;
+                       }
+
+                       case 'l': // &l
+                       {
+                               if (*++stre == 't' && *++stre == ';') // &lt;
+                               {
+                                       *s++ = '<';
+                                       ++stre;
+                                               
+                                       g.push(s, stre - s);
+                                       return stre;
+                               }
+                               break;
+                       }
+
+                       case 'q': // &q
+                       {
+                               if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // &quot;
+                               {
+                                       *s++ = '"';
+                                       ++stre;
+                                       
+                                       g.push(s, stre - s);
+                                       return stre;
+                               }
+                               break;
+                       }
+
+                       default:
+                               break;
+               }
+               
+               return stre;
+       }
+
+       // Utility macro for last character handling
+       #define ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e)))
+
+       PUGI__FN char_t* strconv_comment(char_t* s, char_t endch)
+       {
+               gap g;
+               
+               while (true)
+               {
+                       while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) ++s;
+               
+                       if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+                       {
+                               *s++ = '\n'; // replace first one with 0x0a
+                               
+                               if (*s == '\n') g.push(s, 1);
+                       }
+                       else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here
+                       {
+                               *g.flush(s) = 0;
+                               
+                               return s + (s[2] == '>' ? 3 : 2);
+                       }
+                       else if (*s == 0)
+                       {
+                               return 0;
+                       }
+                       else ++s;
+               }
+       }
+
+       PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch)
+       {
+               gap g;
+                       
+               while (true)
+               {
+                       while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) ++s;
+                       
+                       if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+                       {
+                               *s++ = '\n'; // replace first one with 0x0a
+                               
+                               if (*s == '\n') g.push(s, 1);
+                       }
+                       else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here
+                       {
+                               *g.flush(s) = 0;
+                               
+                               return s + 1;
+                       }
+                       else if (*s == 0)
+                       {
+                               return 0;
+                       }
+                       else ++s;
+               }
+       }
+       
+       typedef char_t* (*strconv_pcdata_t)(char_t*);
+               
+       template <typename opt_trim, typename opt_eol, typename opt_escape> struct strconv_pcdata_impl
+       {
+               static char_t* parse(char_t* s)
+               {
+                       gap g;
+
+                       char_t* begin = s;
+
+                       while (true)
+                       {
+                               while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s;
+                                       
+                               if (*s == '<') // PCDATA ends here
+                               {
+                                       char_t* end = g.flush(s);
+
+                                       if (opt_trim::value)
+                                               while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space))
+                                                       --end;
+
+                                       *end = 0;
+                                       
+                                       return s + 1;
+                               }
+                               else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair
+                               {
+                                       *s++ = '\n'; // replace first one with 0x0a
+                                       
+                                       if (*s == '\n') g.push(s, 1);
+                               }
+                               else if (opt_escape::value && *s == '&')
+                               {
+                                       s = strconv_escape(s, g);
+                               }
+                               else if (*s == 0)
+                               {
+                                       char_t* end = g.flush(s);
+
+                                       if (opt_trim::value)
+                                               while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space))
+                                                       --end;
+
+                                       *end = 0;
+
+                                       return s;
+                               }
+                               else ++s;
+                       }
+               }
+       };
+       
+       PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask)
+       {
+               PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800);
+
+               switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (eol escapes trim)
+               {
+               case 0: return strconv_pcdata_impl<opt_false, opt_false, opt_false>::parse;
+               case 1: return strconv_pcdata_impl<opt_false, opt_false, opt_true>::parse;
+               case 2: return strconv_pcdata_impl<opt_false, opt_true, opt_false>::parse;
+               case 3: return strconv_pcdata_impl<opt_false, opt_true, opt_true>::parse;
+               case 4: return strconv_pcdata_impl<opt_true, opt_false, opt_false>::parse;
+               case 5: return strconv_pcdata_impl<opt_true, opt_false, opt_true>::parse;
+               case 6: return strconv_pcdata_impl<opt_true, opt_true, opt_false>::parse;
+               case 7: return strconv_pcdata_impl<opt_true, opt_true, opt_true>::parse;
+               default: assert(false); return 0; // should not get here
+               }
+       }
+
+       typedef char_t* (*strconv_attribute_t)(char_t*, char_t);
+       
+       template <typename opt_escape> struct strconv_attribute_impl
+       {
+               static char_t* parse_wnorm(char_t* s, char_t end_quote)
+               {
+                       gap g;
+
+                       // trim leading whitespaces
+                       if (PUGI__IS_CHARTYPE(*s, ct_space))
+                       {
+                               char_t* str = s;
+                               
+                               do ++str;
+                               while (PUGI__IS_CHARTYPE(*str, ct_space));
+                               
+                               g.push(s, str - s);
+                       }
+
+                       while (true)
+                       {
+                               while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) ++s;
+                               
+                               if (*s == end_quote)
+                               {
+                                       char_t* str = g.flush(s);
+                                       
+                                       do *str-- = 0;
+                                       while (PUGI__IS_CHARTYPE(*str, ct_space));
+                               
+                                       return s + 1;
+                               }
+                               else if (PUGI__IS_CHARTYPE(*s, ct_space))
+                               {
+                                       *s++ = ' ';
+               
+                                       if (PUGI__IS_CHARTYPE(*s, ct_space))
+                                       {
+                                               char_t* str = s + 1;
+                                               while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str;
+                                               
+                                               g.push(s, str - s);
+                                       }
+                               }
+                               else if (opt_escape::value && *s == '&')
+                               {
+                                       s = strconv_escape(s, g);
+                               }
+                               else if (!*s)
+                               {
+                                       return 0;
+                               }
+                               else ++s;
+                       }
+               }
+
+               static char_t* parse_wconv(char_t* s, char_t end_quote)
+               {
+                       gap g;
+
+                       while (true)
+                       {
+                               while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) ++s;
+                               
+                               if (*s == end_quote)
+                               {
+                                       *g.flush(s) = 0;
+                               
+                                       return s + 1;
+                               }
+                               else if (PUGI__IS_CHARTYPE(*s, ct_space))
+                               {
+                                       if (*s == '\r')
+                                       {
+                                               *s++ = ' ';
+                               
+                                               if (*s == '\n') g.push(s, 1);
+                                       }
+                                       else *s++ = ' ';
+                               }
+                               else if (opt_escape::value && *s == '&')
+                               {
+                                       s = strconv_escape(s, g);
+                               }
+                               else if (!*s)
+                               {
+                                       return 0;
+                               }
+                               else ++s;
+                       }
+               }
+
+               static char_t* parse_eol(char_t* s, char_t end_quote)
+               {
+                       gap g;
+
+                       while (true)
+                       {
+                               while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s;
+                               
+                               if (*s == end_quote)
+                               {
+                                       *g.flush(s) = 0;
+                               
+                                       return s + 1;
+                               }
+                               else if (*s == '\r')
+                               {
+                                       *s++ = '\n';
+                                       
+                                       if (*s == '\n') g.push(s, 1);
+                               }
+                               else if (opt_escape::value && *s == '&')
+                               {
+                                       s = strconv_escape(s, g);
+                               }
+                               else if (!*s)
+                               {
+                                       return 0;
+                               }
+                               else ++s;
+                       }
+               }
+
+               static char_t* parse_simple(char_t* s, char_t end_quote)
+               {
+                       gap g;
+
+                       while (true)
+                       {
+                               while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s;
+                               
+                               if (*s == end_quote)
+                               {
+                                       *g.flush(s) = 0;
+                               
+                                       return s + 1;
+                               }
+                               else if (opt_escape::value && *s == '&')
+                               {
+                                       s = strconv_escape(s, g);
+                               }
+                               else if (!*s)
+                               {
+                                       return 0;
+                               }
+                               else ++s;
+                       }
+               }
+       };
+
+       PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask)
+       {
+               PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80);
+               
+               switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes)
+               {
+               case 0:  return strconv_attribute_impl<opt_false>::parse_simple;
+               case 1:  return strconv_attribute_impl<opt_true>::parse_simple;
+               case 2:  return strconv_attribute_impl<opt_false>::parse_eol;
+               case 3:  return strconv_attribute_impl<opt_true>::parse_eol;
+               case 4:  return strconv_attribute_impl<opt_false>::parse_wconv;
+               case 5:  return strconv_attribute_impl<opt_true>::parse_wconv;
+               case 6:  return strconv_attribute_impl<opt_false>::parse_wconv;
+               case 7:  return strconv_attribute_impl<opt_true>::parse_wconv;
+               case 8:  return strconv_attribute_impl<opt_false>::parse_wnorm;
+               case 9:  return strconv_attribute_impl<opt_true>::parse_wnorm;
+               case 10: return strconv_attribute_impl<opt_false>::parse_wnorm;
+               case 11: return strconv_attribute_impl<opt_true>::parse_wnorm;
+               case 12: return strconv_attribute_impl<opt_false>::parse_wnorm;
+               case 13: return strconv_attribute_impl<opt_true>::parse_wnorm;
+               case 14: return strconv_attribute_impl<opt_false>::parse_wnorm;
+               case 15: return strconv_attribute_impl<opt_true>::parse_wnorm;
+               default: assert(false); return 0; // should not get here
+               }
+       }
+
+       inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0)
+       {
+               xml_parse_result result;
+               result.status = status;
+               result.offset = offset;
+
+               return result;
+       }
+
+       struct xml_parser
+       {
+               xml_allocator alloc;
+               char_t* error_offset;
+               xml_parse_status error_status;
+               
+               // Parser utilities.
+               #define PUGI__SKIPWS()                  { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; }
+               #define PUGI__OPTSET(OPT)                       ( optmsk & (OPT) )
+               #define PUGI__PUSHNODE(TYPE)            { cursor = append_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); }
+               #define PUGI__POPNODE()                 { cursor = cursor->parent; }
+               #define PUGI__SCANFOR(X)                        { while (*s != 0 && !(X)) ++s; }
+               #define PUGI__SCANWHILE(X)              { while ((X)) ++s; }
+               #define PUGI__ENDSEG()                  { ch = *s; *s = 0; ++s; }
+               #define PUGI__THROW_ERROR(err, m)       return error_offset = m, error_status = err, static_cast<char_t*>(0)
+               #define PUGI__CHECK_ERROR(err, m)       { if (*s == 0) PUGI__THROW_ERROR(err, m); }
+               
+               xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok)
+               {
+               }
+
+               // DOCTYPE consists of nested sections of the following possible types:
+               // <!-- ... -->, <? ... ?>, "...", '...'
+               // <![...]]>
+               // <!...>
+               // First group can not contain nested groups
+               // Second group can contain nested groups of the same type
+               // Third group can contain all other groups
+               char_t* parse_doctype_primitive(char_t* s)
+               {
+                       if (*s == '"' || *s == '\'')
+                       {
+                               // quoted string
+                               char_t ch = *s++;
+                               PUGI__SCANFOR(*s == ch);
+                               if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                               s++;
+                       }
+                       else if (s[0] == '<' && s[1] == '?')
+                       {
+                               // <? ... ?>
+                               s += 2;
+                               PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype
+                               if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                               s += 2;
+                       }
+                       else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-')
+                       {
+                               s += 4;
+                               PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype
+                               if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                               s += 4;
+                       }
+                       else PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                       return s;
+               }
+
+               char_t* parse_doctype_ignore(char_t* s)
+               {
+                       assert(s[0] == '<' && s[1] == '!' && s[2] == '[');
+                       s++;
+
+                       while (*s)
+                       {
+                               if (s[0] == '<' && s[1] == '!' && s[2] == '[')
+                               {
+                                       // nested ignore section
+                                       s = parse_doctype_ignore(s);
+                                       if (!s) return s;
+                               }
+                               else if (s[0] == ']' && s[1] == ']' && s[2] == '>')
+                               {
+                                       // ignore section end
+                                       s += 3;
+
+                                       return s;
+                               }
+                               else s++;
+                       }
+
+                       PUGI__THROW_ERROR(status_bad_doctype, s);
+               }
+
+               char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel)
+               {
+                       assert((s[0] == '<' || s[0] == 0) && s[1] == '!');
+                       s++;
+
+                       while (*s)
+                       {
+                               if (s[0] == '<' && s[1] == '!' && s[2] != '-')
+                               {
+                                       if (s[2] == '[')
+                                       {
+                                               // ignore
+                                               s = parse_doctype_ignore(s);
+                                               if (!s) return s;
+                                       }
+                                       else
+                                       {
+                                               // some control group
+                                               s = parse_doctype_group(s, endch, false);
+                                               if (!s) return s;
+
+                                               // skip >
+                                               assert(*s == '>');
+                                               s++;
+                                       }
+                               }
+                               else if (s[0] == '<' || s[0] == '"' || s[0] == '\'')
+                               {
+                                       // unknown tag (forbidden), or some primitive group
+                                       s = parse_doctype_primitive(s);
+                                       if (!s) return s;
+                               }
+                               else if (*s == '>')
+                               {
+                                       return s;
+                               }
+                               else s++;
+                       }
+
+                       if (!toplevel || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                       return s;
+               }
+
+               char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch)
+               {
+                       // parse node contents, starting with exclamation mark
+                       ++s;
+
+                       if (*s == '-') // '<!-...'
+                       {
+                               ++s;
+
+                               if (*s == '-') // '<!--...'
+                               {
+                                       ++s;
+
+                                       if (PUGI__OPTSET(parse_comments))
+                                       {
+                                               PUGI__PUSHNODE(node_comment); // Append a new node on the tree.
+                                               cursor->value = s; // Save the offset.
+                                       }
+
+                                       if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments))
+                                       {
+                                               s = strconv_comment(s, endch);
+
+                                               if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value);
+                                       }
+                                       else
+                                       {
+                                               // Scan for terminating '-->'.
+                                               PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>'));
+                                               PUGI__CHECK_ERROR(status_bad_comment, s);
+
+                                               if (PUGI__OPTSET(parse_comments))
+                                                       *s = 0; // Zero-terminate this segment at the first terminating '-'.
+
+                                               s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'.
+                                       }
+                               }
+                               else PUGI__THROW_ERROR(status_bad_comment, s);
+                       }
+                       else if (*s == '[')
+                       {
+                               // '<![CDATA[...'
+                               if (*++s=='C' && *++s=='D' && *++s=='A' && *++s=='T' && *++s=='A' && *++s == '[')
+                               {
+                                       ++s;
+
+                                       if (PUGI__OPTSET(parse_cdata))
+                                       {
+                                               PUGI__PUSHNODE(node_cdata); // Append a new node on the tree.
+                                               cursor->value = s; // Save the offset.
+
+                                               if (PUGI__OPTSET(parse_eol))
+                                               {
+                                                       s = strconv_cdata(s, endch);
+
+                                                       if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value);
+                                               }
+                                               else
+                                               {
+                                                       // Scan for terminating ']]>'.
+                                                       PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>'));
+                                                       PUGI__CHECK_ERROR(status_bad_cdata, s);
+
+                                                       *s++ = 0; // Zero-terminate this segment.
+                                               }
+                                       }
+                                       else // Flagged for discard, but we still have to scan for the terminator.
+                                       {
+                                               // Scan for terminating ']]>'.
+                                               PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>'));
+                                               PUGI__CHECK_ERROR(status_bad_cdata, s);
+
+                                               ++s;
+                                       }
+
+                                       s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'.
+                               }
+                               else PUGI__THROW_ERROR(status_bad_cdata, s);
+                       }
+                       else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E'))
+                       {
+                               s -= 2;
+
+                               if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s);
+
+                               char_t* mark = s + 9;
+
+                               s = parse_doctype_group(s, endch, true);
+                               if (!s) return s;
+
+                               assert((*s == 0 && endch == '>') || *s == '>');
+                               if (*s) *s++ = 0;
+
+                               if (PUGI__OPTSET(parse_doctype))
+                               {
+                                       while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark;
+
+                                       PUGI__PUSHNODE(node_doctype);
+
+                                       cursor->value = mark;
+
+                                       PUGI__POPNODE();
+                               }
+                       }
+                       else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s);
+                       else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s);
+                       else PUGI__THROW_ERROR(status_unrecognized_tag, s);
+
+                       return s;
+               }
+
+               char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch)
+               {
+                       // load into registers
+                       xml_node_struct* cursor = ref_cursor;
+                       char_t ch = 0;
+
+                       // parse node contents, starting with question mark
+                       ++s;
+
+                       // read PI target
+                       char_t* target = s;
+
+                       if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s);
+
+                       PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol));
+                       PUGI__CHECK_ERROR(status_bad_pi, s);
+
+                       // determine node type; stricmp / strcasecmp is not portable
+                       bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s;
+
+                       if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi))
+                       {
+                               if (declaration)
+                               {
+                                       // disallow non top-level declarations
+                                       if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s);
+
+                                       PUGI__PUSHNODE(node_declaration);
+                               }
+                               else
+                               {
+                                       PUGI__PUSHNODE(node_pi);
+                               }
+
+                               cursor->name = target;
+
+                               PUGI__ENDSEG();
+
+                               // parse value/attributes
+                               if (ch == '?')
+                               {
+                                       // empty node
+                                       if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s);
+                                       s += (*s == '>');
+
+                                       PUGI__POPNODE();
+                               }
+                               else if (PUGI__IS_CHARTYPE(ch, ct_space))
+                               {
+                                       PUGI__SKIPWS();
+
+                                       // scan for tag end
+                                       char_t* value = s;
+
+                                       PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>'));
+                                       PUGI__CHECK_ERROR(status_bad_pi, s);
+
+                                       if (declaration)
+                                       {
+                                               // replace ending ? with / so that 'element' terminates properly
+                                               *s = '/';
+
+                                               // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES
+                                               s = value;
+                                       }
+                                       else
+                                       {
+                                               // store value and step over >
+                                               cursor->value = value;
+                                               PUGI__POPNODE();
+
+                                               PUGI__ENDSEG();
+
+                                               s += (*s == '>');
+                                       }
+                               }
+                               else PUGI__THROW_ERROR(status_bad_pi, s);
+                       }
+                       else
+                       {
+                               // scan for tag end
+                               PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>'));
+                               PUGI__CHECK_ERROR(status_bad_pi, s);
+
+                               s += (s[1] == '>' ? 2 : 1);
+                       }
+
+                       // store from registers
+                       ref_cursor = cursor;
+
+                       return s;
+               }
+
+               char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch)
+               {
+                       strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk);
+                       strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk);
+                       
+                       char_t ch = 0;
+                       xml_node_struct* cursor = root;
+                       char_t* mark = s;
+
+                       while (*s != 0)
+                       {
+                               if (*s == '<')
+                               {
+                                       ++s;
+
+                               LOC_TAG:
+                                       if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...'
+                                       {
+                                               PUGI__PUSHNODE(node_element); // Append a new node to the tree.
+
+                                               cursor->name = s;
+
+                                               PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator.
+                                               PUGI__ENDSEG(); // Save char in 'ch', terminate & step over.
+
+                                               if (ch == '>')
+                                               {
+                                                       // end of tag
+                                               }
+                                               else if (PUGI__IS_CHARTYPE(ch, ct_space))
+                                               {
+                                               LOC_ATTRIBUTES:
+                                                       while (true)
+                                                       {
+                                                               PUGI__SKIPWS(); // Eat any whitespace.
+                                               
+                                                               if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #...
+                                                               {
+                                                                       xml_attribute_struct* a = append_attribute_ll(cursor, alloc); // Make space for this attribute.
+                                                                       if (!a) PUGI__THROW_ERROR(status_out_of_memory, s);
+
+                                                                       a->name = s; // Save the offset.
+
+                                                                       PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator.
+                                                                       PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance
+
+                                                                       PUGI__ENDSEG(); // Save char in 'ch', terminate & step over.
+                                                                       PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance
+
+                                                                       if (PUGI__IS_CHARTYPE(ch, ct_space))
+                                                                       {
+                                                                               PUGI__SKIPWS(); // Eat any whitespace.
+                                                                               PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance
+
+                                                                               ch = *s;
+                                                                               ++s;
+                                                                       }
+                                                                       
+                                                                       if (ch == '=') // '<... #=...'
+                                                                       {
+                                                                               PUGI__SKIPWS(); // Eat any whitespace.
+
+                                                                               if (*s == '"' || *s == '\'') // '<... #="...'
+                                                                               {
+                                                                                       ch = *s; // Save quote char to avoid breaking on "''" -or- '""'.
+                                                                                       ++s; // Step over the quote.
+                                                                                       a->value = s; // Save the offset.
+
+                                                                                       s = strconv_attribute(s, ch);
+                                                                               
+                                                                                       if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value);
+
+                                                                                       // After this line the loop continues from the start;
+                                                                                       // Whitespaces, / and > are ok, symbols and EOF are wrong,
+                                                                                       // everything else will be detected
+                                                                                       if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s);
+                                                                               }
+                                                                               else PUGI__THROW_ERROR(status_bad_attribute, s);
+                                                                       }
+                                                                       else PUGI__THROW_ERROR(status_bad_attribute, s);
+                                                               }
+                                                               else if (*s == '/')
+                                                               {
+                                                                       ++s;
+                                                                       
+                                                                       if (*s == '>')
+                                                                       {
+                                                                               PUGI__POPNODE();
+                                                                               s++;
+                                                                               break;
+                                                                       }
+                                                                       else if (*s == 0 && endch == '>')
+                                                                       {
+                                                                               PUGI__POPNODE();
+                                                                               break;
+                                                                       }
+                                                                       else PUGI__THROW_ERROR(status_bad_start_element, s);
+                                                               }
+                                                               else if (*s == '>')
+                                                               {
+                                                                       ++s;
+
+                                                                       break;
+                                                               }
+                                                               else if (*s == 0 && endch == '>')
+                                                               {
+                                                                       break;
+                                                               }
+                                                               else PUGI__THROW_ERROR(status_bad_start_element, s);
+                                                       }
+
+                                                       // !!!
+                                               }
+                                               else if (ch == '/') // '<#.../'
+                                               {
+                                                       if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s);
+
+                                                       PUGI__POPNODE(); // Pop.
+
+                                                       s += (*s == '>');
+                                               }
+                                               else if (ch == 0)
+                                               {
+                                                       // we stepped over null terminator, backtrack & handle closing tag
+                                                       --s;
+                                                       
+                                                       if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s);
+                                               }
+                                               else PUGI__THROW_ERROR(status_bad_start_element, s);
+                                       }
+                                       else if (*s == '/')
+                                       {
+                                               ++s;
+
+                                               char_t* name = cursor->name;
+                                               if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s);
+                                               
+                                               while (PUGI__IS_CHARTYPE(*s, ct_symbol))
+                                               {
+                                                       if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s);
+                                               }
+
+                                               if (*name)
+                                               {
+                                                       if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s);
+                                                       else PUGI__THROW_ERROR(status_end_element_mismatch, s);
+                                               }
+                                                       
+                                               PUGI__POPNODE(); // Pop.
+
+                                               PUGI__SKIPWS();
+
+                                               if (*s == 0)
+                                               {
+                                                       if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s);
+                                               }
+                                               else
+                                               {
+                                                       if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s);
+                                                       ++s;
+                                               }
+                                       }
+                                       else if (*s == '?') // '<?...'
+                                       {
+                                               s = parse_question(s, cursor, optmsk, endch);
+                                               if (!s) return s;
+
+                                               assert(cursor);
+                                               if ((cursor->header & xml_memory_page_type_mask) + 1 == node_declaration) goto LOC_ATTRIBUTES;
+                                       }
+                                       else if (*s == '!') // '<!...'
+                                       {
+                                               s = parse_exclamation(s, cursor, optmsk, endch);
+                                               if (!s) return s;
+                                       }
+                                       else if (*s == 0 && endch == '?') PUGI__THROW_ERROR(status_bad_pi, s);
+                                       else PUGI__THROW_ERROR(status_unrecognized_tag, s);
+                               }
+                               else
+                               {
+                                       mark = s; // Save this offset while searching for a terminator.
+
+                                       PUGI__SKIPWS(); // Eat whitespace if no genuine PCDATA here.
+
+                                       if (*s == '<' || !*s)
+                                       {
+                                               // We skipped some whitespace characters because otherwise we would take the tag branch instead of PCDATA one
+                                               assert(mark != s);
+
+                                               if (!PUGI__OPTSET(parse_ws_pcdata | parse_ws_pcdata_single) || PUGI__OPTSET(parse_trim_pcdata))
+                                               {
+                                                       continue;
+                                               }
+                                               else if (PUGI__OPTSET(parse_ws_pcdata_single))
+                                               {
+                                                       if (s[0] != '<' || s[1] != '/' || cursor->first_child) continue;
+                                               }
+                                       }
+
+                                       if (!PUGI__OPTSET(parse_trim_pcdata))
+                                               s = mark;
+                                                       
+                                       if (cursor->parent || PUGI__OPTSET(parse_fragment))
+                                       {
+                                               PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree.
+                                               cursor->value = s; // Save the offset.
+
+                                               s = strconv_pcdata(s);
+                                                               
+                                               PUGI__POPNODE(); // Pop since this is a standalone.
+                                               
+                                               if (!*s) break;
+                                       }
+                                       else
+                                       {
+                                               PUGI__SCANFOR(*s == '<'); // '...<'
+                                               if (!*s) break;
+                                               
+                                               ++s;
+                                       }
+
+                                       // We're after '<'
+                                       goto LOC_TAG;
+                               }
+                       }
+
+                       // check that last tag is closed
+                       if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s);
+
+                       return s;
+               }
+
+       #ifdef PUGIXML_WCHAR_MODE
+               static char_t* parse_skip_bom(char_t* s)
+               {
+                       unsigned int bom = 0xfeff;
+                       return (s[0] == static_cast<wchar_t>(bom)) ? s + 1 : s;
+               }
+       #else
+               static char_t* parse_skip_bom(char_t* s)
+               {
+                       return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s;
+               }
+       #endif
+
+               static bool has_element_node_siblings(xml_node_struct* node)
+               {
+                       while (node)
+                       {
+                               xml_node_type type = static_cast<xml_node_type>((node->header & impl::xml_memory_page_type_mask) + 1);
+                               if (type == node_element) return true;
+
+                               node = node->next_sibling;
+                       }
+
+                       return false;
+               }
+
+               static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk)
+               {
+                       // allocator object is a part of document object
+                       xml_allocator& alloc = *static_cast<xml_allocator*>(xmldoc);
+
+                       // early-out for empty documents
+                       if (length == 0)
+                               return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element);
+
+                       // get last child of the root before parsing
+                       xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c : 0;
+       
+                       // create parser on stack
+                       xml_parser parser(alloc);
+
+                       // save last character and make buffer zero-terminated (speeds up parsing)
+                       char_t endch = buffer[length - 1];
+                       buffer[length - 1] = 0;
+                       
+                       // skip BOM to make sure it does not end up as part of parse output
+                       char_t* buffer_data = parse_skip_bom(buffer);
+
+                       // perform actual parsing
+                       parser.parse_tree(buffer_data, root, optmsk, endch);
+
+                       // update allocator state
+                       alloc = parser.alloc;
+
+                       xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0);
+                       assert(result.offset >= 0 && static_cast<size_t>(result.offset) <= length);
+
+                       if (result)
+                       {
+                               // since we removed last character, we have to handle the only possible false positive (stray <)
+                               if (endch == '<')
+                                       return make_parse_result(status_unrecognized_tag, length - 1);
+
+                               // check if there are any element nodes parsed
+                               xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling : root->first_child;
+
+                               if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed))
+                                       return make_parse_result(status_no_document_element, length - 1);
+                       }
+                       else
+                       {
+                               // roll back offset if it occurs on a null terminator in the source buffer
+                               if (result.offset > 0 && static_cast<size_t>(result.offset) == length - 1 && endch == 0)
+                                       result.offset--;
+                       }
+
+                       return result;
+               }
+       };
+
+       // Output facilities
+       PUGI__FN xml_encoding get_write_native_encoding()
+       {
+       #ifdef PUGIXML_WCHAR_MODE
+               return get_wchar_encoding();
+       #else
+               return encoding_utf8;
+       #endif
+       }
+
+       PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding)
+       {
+               // replace wchar encoding with utf implementation
+               if (encoding == encoding_wchar) return get_wchar_encoding();
+
+               // replace utf16 encoding with utf16 with specific endianness
+               if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+               // replace utf32 encoding with utf32 with specific endianness
+               if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+               // only do autodetection if no explicit encoding is requested
+               if (encoding != encoding_auto) return encoding;
+
+               // assume utf8 encoding
+               return encoding_utf8;
+       }
+
+#ifdef PUGIXML_WCHAR_MODE
+       PUGI__FN size_t get_valid_length(const char_t* data, size_t length)
+       {
+               assert(length > 0);
+
+               // discard last character if it's the lead of a surrogate pair 
+               return (sizeof(wchar_t) == 2 && static_cast<unsigned int>(static_cast<uint16_t>(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length;
+       }
+
+       PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding)
+       {
+               // only endian-swapping is required
+               if (need_endian_swap_utf(encoding, get_wchar_encoding()))
+               {
+                       convert_wchar_endian_swap(r_char, data, length);
+
+                       return length * sizeof(char_t);
+               }
+       
+               // convert to utf8
+               if (encoding == encoding_utf8)
+               {
+                       uint8_t* dest = r_u8;
+                       uint8_t* end = utf_decoder<utf8_writer>::decode_wchar_block(data, length, dest);
+
+                       return static_cast<size_t>(end - dest);
+               }
+
+               // convert to utf16
+               if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+               {
+                       uint16_t* dest = r_u16;
+
+                       // convert to native utf16
+                       uint16_t* end = utf_decoder<utf16_writer>::decode_wchar_block(data, length, dest);
+
+                       // swap if necessary
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+                       if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest));
+
+                       return static_cast<size_t>(end - dest) * sizeof(uint16_t);
+               }
+
+               // convert to utf32
+               if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+               {
+                       uint32_t* dest = r_u32;
+
+                       // convert to native utf32
+                       uint32_t* end = utf_decoder<utf32_writer>::decode_wchar_block(data, length, dest);
+
+                       // swap if necessary
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+                       if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest));
+
+                       return static_cast<size_t>(end - dest) * sizeof(uint32_t);
+               }
+
+               // convert to latin1
+               if (encoding == encoding_latin1)
+               {
+                       uint8_t* dest = r_u8;
+                       uint8_t* end = utf_decoder<latin1_writer>::decode_wchar_block(data, length, dest);
+
+                       return static_cast<size_t>(end - dest);
+               }
+
+               assert(!"Invalid encoding");
+               return 0;
+       }
+#else
+       PUGI__FN size_t get_valid_length(const char_t* data, size_t length)
+       {
+               assert(length > 4);
+
+               for (size_t i = 1; i <= 4; ++i)
+               {
+                       uint8_t ch = static_cast<uint8_t>(data[length - i]);
+
+                       // either a standalone character or a leading one
+                       if ((ch & 0xc0) != 0x80) return length - i;
+               }
+
+               // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk
+               return length;
+       }
+
+       PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding)
+       {
+               if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
+               {
+                       uint16_t* dest = r_u16;
+
+                       // convert to native utf16
+                       uint16_t* end = utf_decoder<utf16_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest);
+
+                       // swap if necessary
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
+
+                       if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest));
+
+                       return static_cast<size_t>(end - dest) * sizeof(uint16_t);
+               }
+
+               if (encoding == encoding_utf32_be || encoding == encoding_utf32_le)
+               {
+                       uint32_t* dest = r_u32;
+
+                       // convert to native utf32
+                       uint32_t* end = utf_decoder<utf32_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest);
+
+                       // swap if necessary
+                       xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
+
+                       if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest));
+
+                       return static_cast<size_t>(end - dest) * sizeof(uint32_t);
+               }
+
+               if (encoding == encoding_latin1)
+               {
+                       uint8_t* dest = r_u8;
+                       uint8_t* end = utf_decoder<latin1_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest);
+
+                       return static_cast<size_t>(end - dest);
+               }
+
+               assert(!"Invalid encoding");
+               return 0;
+       }
+#endif
+
+       class xml_buffered_writer
+       {
+               xml_buffered_writer(const xml_buffered_writer&);
+               xml_buffered_writer& operator=(const xml_buffered_writer&);
+
+       public:
+               xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding))
+               {
+                       PUGI__STATIC_ASSERT(bufcapacity >= 8);
+               }
+
+               ~xml_buffered_writer()
+               {
+                       flush();
+               }
+
+               void flush()
+               {
+                       flush(buffer, bufsize);
+                       bufsize = 0;
+               }
+
+               void flush(const char_t* data, size_t size)
+               {
+                       if (size == 0) return;
+
+                       // fast path, just write data
+                       if (encoding == get_write_native_encoding())
+                               writer.write(data, size * sizeof(char_t));
+                       else
+                       {
+                               // convert chunk
+                               size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding);
+                               assert(result <= sizeof(scratch));
+
+                               // write data
+                               writer.write(scratch.data_u8, result);
+                       }
+               }
+
+               void write(const char_t* data, size_t length)
+               {
+                       if (bufsize + length > bufcapacity)
+                       {
+                               // flush the remaining buffer contents
+                               flush();
+
+                               // handle large chunks
+                               if (length > bufcapacity)
+                               {
+                                       if (encoding == get_write_native_encoding())
+                                       {
+                                               // fast path, can just write data chunk
+                                               writer.write(data, length * sizeof(char_t));
+                                               return;
+                                       }
+
+                                       // need to convert in suitable chunks
+                                       while (length > bufcapacity)
+                                       {
+                                               // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer
+                                               // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary)
+                                               size_t chunk_size = get_valid_length(data, bufcapacity);
+
+                                               // convert chunk and write
+                                               flush(data, chunk_size);
+
+                                               // iterate
+                                               data += chunk_size;
+                                               length -= chunk_size;
+                                       }
+
+                                       // small tail is copied below
+                                       bufsize = 0;
+                               }
+                       }
+
+                       memcpy(buffer + bufsize, data, length * sizeof(char_t));
+                       bufsize += length;
+               }
+
+               void write(const char_t* data)
+               {
+                       write(data, strlength(data));
+               }
+
+               void write(char_t d0)
+               {
+                       if (bufsize + 1 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       bufsize += 1;
+               }
+
+               void write(char_t d0, char_t d1)
+               {
+                       if (bufsize + 2 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       buffer[bufsize + 1] = d1;
+                       bufsize += 2;
+               }
+
+               void write(char_t d0, char_t d1, char_t d2)
+               {
+                       if (bufsize + 3 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       buffer[bufsize + 1] = d1;
+                       buffer[bufsize + 2] = d2;
+                       bufsize += 3;
+               }
+
+               void write(char_t d0, char_t d1, char_t d2, char_t d3)
+               {
+                       if (bufsize + 4 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       buffer[bufsize + 1] = d1;
+                       buffer[bufsize + 2] = d2;
+                       buffer[bufsize + 3] = d3;
+                       bufsize += 4;
+               }
+
+               void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4)
+               {
+                       if (bufsize + 5 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       buffer[bufsize + 1] = d1;
+                       buffer[bufsize + 2] = d2;
+                       buffer[bufsize + 3] = d3;
+                       buffer[bufsize + 4] = d4;
+                       bufsize += 5;
+               }
+
+               void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5)
+               {
+                       if (bufsize + 6 > bufcapacity) flush();
+
+                       buffer[bufsize + 0] = d0;
+                       buffer[bufsize + 1] = d1;
+                       buffer[bufsize + 2] = d2;
+                       buffer[bufsize + 3] = d3;
+                       buffer[bufsize + 4] = d4;
+                       buffer[bufsize + 5] = d5;
+                       bufsize += 6;
+               }
+
+               // utf8 maximum expansion: x4 (-> utf32)
+               // utf16 maximum expansion: x2 (-> utf32)
+               // utf32 maximum expansion: x1
+               enum
+               {
+                       bufcapacitybytes =
+                       #ifdef PUGIXML_MEMORY_OUTPUT_STACK
+                               PUGIXML_MEMORY_OUTPUT_STACK
+                       #else
+                               10240
+                       #endif
+                       ,
+                       bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4)
+               };
+
+               char_t buffer[bufcapacity];
+
+               union
+               {
+                       uint8_t data_u8[4 * bufcapacity];
+                       uint16_t data_u16[2 * bufcapacity];
+                       uint32_t data_u32[bufcapacity];
+                       char_t data_char[bufcapacity];
+               } scratch;
+
+               xml_writer& writer;
+               size_t bufsize;
+               xml_encoding encoding;
+       };
+
+       PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type)
+       {
+               while (*s)
+               {
+                       const char_t* prev = s;
+                       
+                       // While *s is a usual symbol
+                       while (!PUGI__IS_CHARTYPEX(*s, type)) ++s;
+               
+                       writer.write(prev, static_cast<size_t>(s - prev));
+
+                       switch (*s)
+                       {
+                               case 0: break;
+                               case '&':
+                                       writer.write('&', 'a', 'm', 'p', ';');
+                                       ++s;
+                                       break;
+                               case '<':
+                                       writer.write('&', 'l', 't', ';');
+                                       ++s;
+                                       break;
+                               case '>':
+                                       writer.write('&', 'g', 't', ';');
+                                       ++s;
+                                       break;
+                               case '"':
+                                       writer.write('&', 'q', 'u', 'o', 't', ';');
+                                       ++s;
+                                       break;
+                               default: // s is not a usual symbol
+                               {
+                                       unsigned int ch = static_cast<unsigned int>(*s++);
+                                       assert(ch < 32);
+
+                                       writer.write('&', '#', static_cast<char_t>((ch / 10) + '0'), static_cast<char_t>((ch % 10) + '0'), ';');
+                               }
+                       }
+               }
+       }
+
+       PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags)
+       {
+               if (flags & format_no_escapes)
+                       writer.write(s);
+               else
+                       text_output_escaped(writer, s, type);
+       }
+
+       PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s)
+       {
+               do
+               {
+                       writer.write('<', '!', '[', 'C', 'D');
+                       writer.write('A', 'T', 'A', '[');
+
+                       const char_t* prev = s;
+
+                       // look for ]]> sequence - we can't output it as is since it terminates CDATA
+                       while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s;
+
+                       // skip ]] if we stopped at ]]>, > will go to the next CDATA section
+                       if (*s) s += 2;
+
+                       writer.write(prev, static_cast<size_t>(s - prev));
+
+                       writer.write(']', ']', '>');
+               }
+               while (*s);
+       }
+
+       PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags)
+       {
+               const char_t* default_name = PUGIXML_TEXT(":anonymous");
+
+               for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute())
+               {
+                       writer.write(' ');
+                       writer.write(a.name()[0] ? a.name() : default_name);
+                       writer.write('=', '"');
+
+                       text_output(writer, a.value(), ctx_special_attr, flags);
+
+                       writer.write('"');
+               }
+       }
+
+       PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth)
+       {
+               const char_t* default_name = PUGIXML_TEXT(":anonymous");
+
+               if ((flags & format_indent) != 0 && (flags & format_raw) == 0)
+                       for (unsigned int i = 0; i < depth; ++i) writer.write(indent);
+
+               switch (node.type())
+               {
+               case node_document:
+               {
+                       for (xml_node n = node.first_child(); n; n = n.next_sibling())
+                               node_output(writer, n, indent, flags, depth);
+                       break;
+               }
+                       
+               case node_element:
+               {
+                       const char_t* name = node.name()[0] ? node.name() : default_name;
+
+                       writer.write('<');
+                       writer.write(name);
+
+                       node_output_attributes(writer, node, flags);
+
+                       if (flags & format_raw)
+                       {
+                               if (!node.first_child())
+                                       writer.write(' ', '/', '>');
+                               else
+                               {
+                                       writer.write('>');
+
+                                       for (xml_node n = node.first_child(); n; n = n.next_sibling())
+                                               node_output(writer, n, indent, flags, depth + 1);
+
+                                       writer.write('<', '/');
+                                       writer.write(name);
+                                       writer.write('>');
+                               }
+                       }
+                       else if (!node.first_child())
+                               writer.write(' ', '/', '>', '\n');
+                       else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata))
+                       {
+                               writer.write('>');
+
+                               if (node.first_child().type() == node_pcdata)
+                                       text_output(writer, node.first_child().value(), ctx_special_pcdata, flags);
+                               else
+                                       text_output_cdata(writer, node.first_child().value());
+
+                               writer.write('<', '/');
+                               writer.write(name);
+                               writer.write('>', '\n');
+                       }
+                       else
+                       {
+                               writer.write('>', '\n');
+                               
+                               for (xml_node n = node.first_child(); n; n = n.next_sibling())
+                                       node_output(writer, n, indent, flags, depth + 1);
+
+                               if ((flags & format_indent) != 0 && (flags & format_raw) == 0)
+                                       for (unsigned int i = 0; i < depth; ++i) writer.write(indent);
+                               
+                               writer.write('<', '/');
+                               writer.write(name);
+                               writer.write('>', '\n');
+                       }
+
+                       break;
+               }
+               
+               case node_pcdata:
+                       text_output(writer, node.value(), ctx_special_pcdata, flags);
+                       if ((flags & format_raw) == 0) writer.write('\n');
+                       break;
+
+               case node_cdata:
+                       text_output_cdata(writer, node.value());
+                       if ((flags & format_raw) == 0) writer.write('\n');
+                       break;
+
+               case node_comment:
+                       writer.write('<', '!', '-', '-');
+                       writer.write(node.value());
+                       writer.write('-', '-', '>');
+                       if ((flags & format_raw) == 0) writer.write('\n');
+                       break;
+
+               case node_pi:
+               case node_declaration:
+                       writer.write('<', '?');
+                       writer.write(node.name()[0] ? node.name() : default_name);
+
+                       if (node.type() == node_declaration)
+                       {
+                               node_output_attributes(writer, node, flags);
+                       }
+                       else if (node.value()[0])
+                       {
+                               writer.write(' ');
+                               writer.write(node.value());
+                       }
+
+                       writer.write('?', '>');
+                       if ((flags & format_raw) == 0) writer.write('\n');
+                       break;
+
+               case node_doctype:
+                       writer.write('<', '!', 'D', 'O', 'C');
+                       writer.write('T', 'Y', 'P', 'E');
+
+                       if (node.value()[0])
+                       {
+                               writer.write(' ');
+                               writer.write(node.value());
+                       }
+
+                       writer.write('>');
+                       if ((flags & format_raw) == 0) writer.write('\n');
+                       break;
+
+               default:
+                       assert(!"Invalid node type");
+               }
+       }
+
+       inline bool has_declaration(const xml_node& node)
+       {
+               for (xml_node child = node.first_child(); child; child = child.next_sibling())
+               {
+                       xml_node_type type = child.type();
+
+                       if (type == node_declaration) return true;
+                       if (type == node_element) return false;
+               }
+
+               return false;
+       }
+
+       inline bool allow_insert_child(xml_node_type parent, xml_node_type child)
+       {
+               if (parent != node_document && parent != node_element) return false;
+               if (child == node_document || child == node_null) return false;
+               if (parent != node_document && (child == node_declaration || child == node_doctype)) return false;
+
+               return true;
+       }
+
+       PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip)
+       {
+               assert(dest.type() == source.type());
+
+               switch (source.type())
+               {
+               case node_element:
+               {
+                       dest.set_name(source.name());
+
+                       for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute())
+                               dest.append_attribute(a.name()).set_value(a.value());
+
+                       for (xml_node c = source.first_child(); c; c = c.next_sibling())
+                       {
+                               if (c == skip) continue;
+
+                               xml_node cc = dest.append_child(c.type());
+                               assert(cc);
+
+                               recursive_copy_skip(cc, c, skip);
+                       }
+
+                       break;
+               }
+
+               case node_pcdata:
+               case node_cdata:
+               case node_comment:
+               case node_doctype:
+                       dest.set_value(source.value());
+                       break;
+
+               case node_pi:
+                       dest.set_name(source.name());
+                       dest.set_value(source.value());
+                       break;
+
+               case node_declaration:
+               {
+                       dest.set_name(source.name());
+
+                       for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute())
+                               dest.append_attribute(a.name()).set_value(a.value());
+
+                       break;
+               }
+
+               default:
+                       assert(!"Invalid node type");
+               }
+       }
+
+       inline bool is_text_node(xml_node_struct* node)
+       {
+               xml_node_type type = static_cast<xml_node_type>((node->header & impl::xml_memory_page_type_mask) + 1);
+
+               return type == node_pcdata || type == node_cdata;
+       }
+
+       // get value with conversion functions
+       PUGI__FN int get_integer_base(const char_t* value)
+       {
+               const char_t* s = value;
+
+               while (PUGI__IS_CHARTYPE(*s, ct_space))
+                       s++;
+
+               if (*s == '-')
+                       s++;
+
+               return (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
+       }
+
+       PUGI__FN int get_value_int(const char_t* value, int def)
+       {
+               if (!value) return def;
+
+               int base = get_integer_base(value);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return static_cast<int>(wcstol(value, 0, base));
+       #else
+               return static_cast<int>(strtol(value, 0, base));
+       #endif
+       }
+
+       PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def)
+       {
+               if (!value) return def;
+
+               int base = get_integer_base(value);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return static_cast<unsigned int>(wcstoul(value, 0, base));
+       #else
+               return static_cast<unsigned int>(strtoul(value, 0, base));
+       #endif
+       }
+
+       PUGI__FN double get_value_double(const char_t* value, double def)
+       {
+               if (!value) return def;
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcstod(value, 0);
+       #else
+               return strtod(value, 0);
+       #endif
+       }
+
+       PUGI__FN float get_value_float(const char_t* value, float def)
+       {
+               if (!value) return def;
+
+       #ifdef PUGIXML_WCHAR_MODE
+               return static_cast<float>(wcstod(value, 0));
+       #else
+               return static_cast<float>(strtod(value, 0));
+       #endif
+       }
+
+       PUGI__FN bool get_value_bool(const char_t* value, bool def)
+       {
+               if (!value) return def;
+
+               // only look at first char
+               char_t first = *value;
+
+               // 1*, t* (true), T* (True), y* (yes), Y* (YES)
+               return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y');
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN long long get_value_llong(const char_t* value, long long def)
+       {
+               if (!value) return def;
+
+               int base = get_integer_base(value);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               #ifdef PUGI__MSVC_CRT_VERSION
+                       return _wcstoi64(value, 0, base);
+               #else
+                       return wcstoll(value, 0, base);
+               #endif
+       #else
+               #ifdef PUGI__MSVC_CRT_VERSION
+                       return _strtoi64(value, 0, base);
+               #else
+                       return strtoll(value, 0, base);
+               #endif
+       #endif
+       }
+
+       PUGI__FN unsigned long long get_value_ullong(const char_t* value, unsigned long long def)
+       {
+               if (!value) return def;
+
+               int base = get_integer_base(value);
+
+       #ifdef PUGIXML_WCHAR_MODE
+               #ifdef PUGI__MSVC_CRT_VERSION
+                       return _wcstoui64(value, 0, base);
+               #else
+                       return wcstoull(value, 0, base);
+               #endif
+       #else
+               #ifdef PUGI__MSVC_CRT_VERSION
+                       return _strtoui64(value, 0, base);
+               #else
+                       return strtoull(value, 0, base);
+               #endif
+       #endif
+       }
+#endif
+
+       // set value with conversion functions
+       PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128])
+       {
+       #ifdef PUGIXML_WCHAR_MODE
+               char_t wbuf[128];
+               impl::widen_ascii(wbuf, buf);
+
+               return strcpy_insitu(dest, header, header_mask, wbuf);
+       #else
+               return strcpy_insitu(dest, header, header_mask, buf);
+       #endif
+       }
+
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value)
+       {
+               char buf[128];
+               sprintf(buf, "%d", value);
+       
+               return set_value_buffer(dest, header, header_mask, buf);
+       }
+
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value)
+       {
+               char buf[128];
+               sprintf(buf, "%u", value);
+
+               return set_value_buffer(dest, header, header_mask, buf);
+       }
+
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value)
+       {
+               char buf[128];
+               sprintf(buf, "%g", value);
+
+               return set_value_buffer(dest, header, header_mask, buf);
+       }
+       
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value)
+       {
+               return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"));
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, long long value)
+       {
+               char buf[128];
+               sprintf(buf, "%lld", value);
+       
+               return set_value_buffer(dest, header, header_mask, buf);
+       }
+
+       PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned long long value)
+       {
+               char buf[128];
+               sprintf(buf, "%llu", value);
+       
+               return set_value_buffer(dest, header, header_mask, buf);
+       }
+#endif
+
+       // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick
+       PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result)
+       {
+       #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE)
+               // there are 64-bit versions of fseek/ftell, let's use them
+               typedef __int64 length_type;
+
+               _fseeki64(file, 0, SEEK_END);
+               length_type length = _ftelli64(file);
+               _fseeki64(file, 0, SEEK_SET);
+       #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && !defined(__STRICT_ANSI__)
+               // there are 64-bit versions of fseek/ftell, let's use them
+               typedef off64_t length_type;
+
+               fseeko64(file, 0, SEEK_END);
+               length_type length = ftello64(file);
+               fseeko64(file, 0, SEEK_SET);
+       #else
+               // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway.
+               typedef long length_type;
+
+               fseek(file, 0, SEEK_END);
+               length_type length = ftell(file);
+               fseek(file, 0, SEEK_SET);
+       #endif
+
+               // check for I/O errors
+               if (length < 0) return status_io_error;
+               
+               // check for overflow
+               size_t result = static_cast<size_t>(length);
+
+               if (static_cast<length_type>(result) != length) return status_out_of_memory;
+
+               // finalize
+               out_result = result;
+
+               return status_ok;
+       }
+
+       PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) 
+       {
+               // We only need to zero-terminate if encoding conversion does not do it for us
+       #ifdef PUGIXML_WCHAR_MODE
+               xml_encoding wchar_encoding = get_wchar_encoding();
+
+               if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding))
+               {
+                       size_t length = size / sizeof(char_t);
+
+                       static_cast<char_t*>(buffer)[length] = 0;
+                       return (length + 1) * sizeof(char_t);
+               }
+       #else
+               if (encoding == encoding_utf8)
+               {
+                       static_cast<char*>(buffer)[size] = 0;
+                       return size + 1;
+               }
+       #endif
+
+               return size;
+       }
+
+       PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding)
+       {
+               if (!file) return make_parse_result(status_file_not_found);
+
+               // get file size (can result in I/O errors)
+               size_t size = 0;
+               xml_parse_status size_status = get_file_size(file, size);
+
+               if (size_status != status_ok)
+               {
+                       fclose(file);
+                       return make_parse_result(size_status);
+               }
+               
+               size_t max_suffix_size = sizeof(char_t);
+
+               // allocate buffer for the whole file
+               char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size));
+
+               if (!contents)
+               {
+                       fclose(file);
+                       return make_parse_result(status_out_of_memory);
+               }
+
+               // read file in memory
+               size_t read_size = fread(contents, 1, size, file);
+               fclose(file);
+
+               if (read_size != size)
+               {
+                       xml_memory::deallocate(contents);
+                       return make_parse_result(status_io_error);
+               }
+
+               xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size);
+               
+               return doc.load_buffer_inplace_own(contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding);
+       }
+
+#ifndef PUGIXML_NO_STL
+       template <typename T> struct xml_stream_chunk
+       {
+               static xml_stream_chunk* create()
+               {
+                       void* memory = xml_memory::allocate(sizeof(xml_stream_chunk));
+                       
+                       return new (memory) xml_stream_chunk();
+               }
+
+               static void destroy(void* ptr)
+               {
+                       xml_stream_chunk* chunk = static_cast<xml_stream_chunk*>(ptr);
+
+                       // free chunk chain
+                       while (chunk)
+                       {
+                               xml_stream_chunk* next = chunk->next;
+                               xml_memory::deallocate(chunk);
+                               chunk = next;
+                       }
+               }
+
+               xml_stream_chunk(): next(0), size(0)
+               {
+               }
+
+               xml_stream_chunk* next;
+               size_t size;
+
+               T data[xml_memory_page_size / sizeof(T)];
+       };
+
+       template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
+       {
+               buffer_holder chunks(0, xml_stream_chunk<T>::destroy);
+
+               // read file to a chunk list
+               size_t total = 0;
+               xml_stream_chunk<T>* last = 0;
+
+               while (!stream.eof())
+               {
+                       // allocate new chunk
+                       xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create();
+                       if (!chunk) return status_out_of_memory;
+
+                       // append chunk to list
+                       if (last) last = last->next = chunk;
+                       else chunks.data = last = chunk;
+
+                       // read data to chunk
+                       stream.read(chunk->data, static_cast<std::streamsize>(sizeof(chunk->data) / sizeof(T)));
+                       chunk->size = static_cast<size_t>(stream.gcount()) * sizeof(T);
+
+                       // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors
+                       if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error;
+
+                       // guard against huge files (chunk size is small enough to make this overflow check work)
+                       if (total + chunk->size < total) return status_out_of_memory;
+                       total += chunk->size;
+               }
+
+               size_t max_suffix_size = sizeof(char_t);
+
+               // copy chunk list to a contiguous buffer
+               char* buffer = static_cast<char*>(xml_memory::allocate(total + max_suffix_size));
+               if (!buffer) return status_out_of_memory;
+
+               char* write = buffer;
+
+               for (xml_stream_chunk<T>* chunk = static_cast<xml_stream_chunk<T>*>(chunks.data); chunk; chunk = chunk->next)
+               {
+                       assert(write + chunk->size <= buffer + total);
+                       memcpy(write, chunk->data, chunk->size);
+                       write += chunk->size;
+               }
+
+               assert(write == buffer + total);
+
+               // return buffer
+               *out_buffer = buffer;
+               *out_size = total;
+
+               return status_ok;
+       }
+
+       template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
+       {
+               // get length of remaining data in stream
+               typename std::basic_istream<T>::pos_type pos = stream.tellg();
+               stream.seekg(0, std::ios::end);
+               std::streamoff length = stream.tellg() - pos;
+               stream.seekg(pos);
+
+               if (stream.fail() || pos < 0) return status_io_error;
+
+               // guard against huge files
+               size_t read_length = static_cast<size_t>(length);
+
+               if (static_cast<std::streamsize>(read_length) != length || length < 0) return status_out_of_memory;
+
+               size_t max_suffix_size = sizeof(char_t);
+
+               // read stream data into memory (guard against stream exceptions with buffer holder)
+               buffer_holder buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate);
+               if (!buffer.data) return status_out_of_memory;
+
+               stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length));
+
+               // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors
+               if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error;
+
+               // return buffer
+               size_t actual_length = static_cast<size_t>(stream.gcount());
+               assert(actual_length <= read_length);
+               
+               *out_buffer = buffer.release();
+               *out_size = actual_length * sizeof(T);
+
+               return status_ok;
+       }
+
+       template <typename T> PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream<T>& stream, unsigned int options, xml_encoding encoding)
+       {
+               void* buffer = 0;
+               size_t size = 0;
+               xml_parse_status status = status_ok;
+
+               // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits)
+               if (stream.fail()) return make_parse_result(status_io_error);
+
+               // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory)
+               if (stream.tellg() < 0)
+               {
+                       stream.clear(); // clear error flags that could be set by a failing tellg
+                       status = load_stream_data_noseek(stream, &buffer, &size);
+               }
+               else
+                       status = load_stream_data_seek(stream, &buffer, &size);
+
+               if (status != status_ok) return make_parse_result(status);
+
+               xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size);
+               
+               return doc.load_buffer_inplace_own(buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding);
+       }
+#endif
+
+#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__))
+       PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+       {
+               return _wfopen(path, mode);
+       }
+#else
+       PUGI__FN char* convert_path_heap(const wchar_t* str)
+       {
+               assert(str);
+
+               // first pass: get length in utf8 characters
+               size_t length = strlength_wide(str);
+               size_t size = as_utf8_begin(str, length);
+
+               // allocate resulting string
+               char* result = static_cast<char*>(xml_memory::allocate(size + 1));
+               if (!result) return 0;
+
+               // second pass: convert to utf8
+               as_utf8_end(result, size, str, length);
+
+               return result;
+       }
+
+       PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode)
+       {
+               // there is no standard function to open wide paths, so our best bet is to try utf8 path
+               char* path_utf8 = convert_path_heap(path);
+               if (!path_utf8) return 0;
+
+               // convert mode to ASCII (we mirror _wfopen interface)
+               char mode_ascii[4] = {0};
+               for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast<char>(mode[i]);
+
+               // try to open the utf8 path
+               FILE* result = fopen(path_utf8, mode_ascii);
+
+               // free dummy buffer
+               xml_memory::deallocate(path_utf8);
+
+               return result;
+       }
+#endif
+
+       PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding)
+       {
+               if (!file) return false;
+
+               xml_writer_file writer(file);
+               doc.save(writer, indent, flags, encoding);
+
+               int result = ferror(file);
+
+               fclose(file);
+
+               return result == 0;
+       }
+
+       PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer)
+       {
+               // check input buffer
+               assert(contents || size == 0);
+
+               // get actual encoding
+               xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size);
+
+               // get private buffer
+               char_t* buffer = 0;
+               size_t length = 0;
+
+               if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory);
+               
+               // delete original buffer if we performed a conversion
+               if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents);
+
+               // store buffer for offset_debug
+               doc->buffer = buffer;
+
+               // parse
+               xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options);
+
+               // remember encoding
+               res.encoding = buffer_encoding;
+
+               // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
+               if (own || buffer != contents) *out_buffer = buffer;
+
+               return res;
+       }
+PUGI__NS_END
+
+namespace pugi
+{
+       PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_)
+       {
+       }
+
+       PUGI__FN void xml_writer_file::write(const void* data, size_t size)
+       {
+               size_t result = fwrite(data, 1, size, static_cast<FILE*>(file));
+               (void)!result; // unfortunately we can't do proper error handling here
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream<char, std::char_traits<char> >& stream): narrow_stream(&stream), wide_stream(0)
+       {
+       }
+
+       PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream): narrow_stream(0), wide_stream(&stream)
+       {
+       }
+
+       PUGI__FN void xml_writer_stream::write(const void* data, size_t size)
+       {
+               if (narrow_stream)
+               {
+                       assert(!wide_stream);
+                       narrow_stream->write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
+               }
+               else
+               {
+                       assert(wide_stream);
+                       assert(size % sizeof(wchar_t) == 0);
+
+                       wide_stream->write(reinterpret_cast<const wchar_t*>(data), static_cast<std::streamsize>(size / sizeof(wchar_t)));
+               }
+       }
+#endif
+
+       PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0)
+       {
+       }
+       
+       PUGI__FN xml_tree_walker::~xml_tree_walker()
+       {
+       }
+
+       PUGI__FN int xml_tree_walker::depth() const
+       {
+               return _depth;
+       }
+
+       PUGI__FN bool xml_tree_walker::begin(xml_node&)
+       {
+               return true;
+       }
+
+       PUGI__FN bool xml_tree_walker::end(xml_node&)
+       {
+               return true;
+       }
+
+       PUGI__FN xml_attribute::xml_attribute(): _attr(0)
+       {
+       }
+
+       PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr)
+       {
+       }
+
+       PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***)
+       {
+       }
+
+       PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const
+       {
+               return _attr ? unspecified_bool_xml_attribute : 0;
+       }
+
+       PUGI__FN bool xml_attribute::operator!() const
+       {
+               return !_attr;
+       }
+
+       PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const
+       {
+               return (_attr == r._attr);
+       }
+       
+       PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const
+       {
+               return (_attr != r._attr);
+       }
+
+       PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const
+       {
+               return (_attr < r._attr);
+       }
+       
+       PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const
+       {
+               return (_attr > r._attr);
+       }
+       
+       PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const
+       {
+               return (_attr <= r._attr);
+       }
+       
+       PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const
+       {
+               return (_attr >= r._attr);
+       }
+
+       PUGI__FN xml_attribute xml_attribute::next_attribute() const
+       {
+               return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute();
+       }
+
+       PUGI__FN xml_attribute xml_attribute::previous_attribute() const
+       {
+               return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute();
+       }
+
+       PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const
+       {
+               return (_attr && _attr->value) ? _attr->value : def;
+       }
+
+       PUGI__FN int xml_attribute::as_int(int def) const
+       {
+               return impl::get_value_int(_attr ? _attr->value : 0, def);
+       }
+
+       PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const
+       {
+               return impl::get_value_uint(_attr ? _attr->value : 0, def);
+       }
+
+       PUGI__FN double xml_attribute::as_double(double def) const
+       {
+               return impl::get_value_double(_attr ? _attr->value : 0, def);
+       }
+
+       PUGI__FN float xml_attribute::as_float(float def) const
+       {
+               return impl::get_value_float(_attr ? _attr->value : 0, def);
+       }
+
+       PUGI__FN bool xml_attribute::as_bool(bool def) const
+       {
+               return impl::get_value_bool(_attr ? _attr->value : 0, def);
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN long long xml_attribute::as_llong(long long def) const
+       {
+               return impl::get_value_llong(_attr ? _attr->value : 0, def);
+       }
+
+       PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const
+       {
+               return impl::get_value_ullong(_attr ? _attr->value : 0, def);
+       }
+#endif
+
+       PUGI__FN bool xml_attribute::empty() const
+       {
+               return !_attr;
+       }
+
+       PUGI__FN const char_t* xml_attribute::name() const
+       {
+               return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const char_t* xml_attribute::value() const
+       {
+               return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT("");
+       }
+
+       PUGI__FN size_t xml_attribute::hash_value() const
+       {
+               return static_cast<size_t>(reinterpret_cast<uintptr_t>(_attr) / sizeof(xml_attribute_struct));
+       }
+
+       PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const
+       {
+               return _attr;
+       }
+
+       PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+       
+       PUGI__FN xml_attribute& xml_attribute::operator=(int rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_attribute& xml_attribute::operator=(double rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+       
+       PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs)
+       {
+               set_value(rhs);
+               return *this;
+       }
+#endif
+
+       PUGI__FN bool xml_attribute::set_name(const char_t* rhs)
+       {
+               if (!_attr) return false;
+               
+               return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs);
+       }
+               
+       PUGI__FN bool xml_attribute::set_value(const char_t* rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+
+       PUGI__FN bool xml_attribute::set_value(int rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+
+       PUGI__FN bool xml_attribute::set_value(unsigned int rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+
+       PUGI__FN bool xml_attribute::set_value(double rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+       
+       PUGI__FN bool xml_attribute::set_value(bool rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN bool xml_attribute::set_value(long long rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+
+       PUGI__FN bool xml_attribute::set_value(unsigned long long rhs)
+       {
+               if (!_attr) return false;
+
+               return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs);
+       }
+#endif
+
+#ifdef __BORLANDC__
+       PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs)
+       {
+               return (bool)lhs && rhs;
+       }
+
+       PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs)
+       {
+               return (bool)lhs || rhs;
+       }
+#endif
+
+       PUGI__FN xml_node::xml_node(): _root(0)
+       {
+       }
+
+       PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p)
+       {
+       }
+       
+       PUGI__FN static void unspecified_bool_xml_node(xml_node***)
+       {
+       }
+
+       PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const
+       {
+               return _root ? unspecified_bool_xml_node : 0;
+       }
+
+       PUGI__FN bool xml_node::operator!() const
+       {
+               return !_root;
+       }
+
+       PUGI__FN xml_node::iterator xml_node::begin() const
+       {
+               return iterator(_root ? _root->first_child : 0, _root);
+       }
+
+       PUGI__FN xml_node::iterator xml_node::end() const
+       {
+               return iterator(0, _root);
+       }
+       
+       PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const
+       {
+               return attribute_iterator(_root ? _root->first_attribute : 0, _root);
+       }
+
+       PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const
+       {
+               return attribute_iterator(0, _root);
+       }
+       
+       PUGI__FN xml_object_range<xml_node_iterator> xml_node::children() const
+       {
+               return xml_object_range<xml_node_iterator>(begin(), end());
+       }
+
+       PUGI__FN xml_object_range<xml_named_node_iterator> xml_node::children(const char_t* name_) const
+       {
+               return xml_object_range<xml_named_node_iterator>(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_));
+       }
+
+       PUGI__FN xml_object_range<xml_attribute_iterator> xml_node::attributes() const
+       {
+               return xml_object_range<xml_attribute_iterator>(attributes_begin(), attributes_end());
+       }
+
+       PUGI__FN bool xml_node::operator==(const xml_node& r) const
+       {
+               return (_root == r._root);
+       }
+
+       PUGI__FN bool xml_node::operator!=(const xml_node& r) const
+       {
+               return (_root != r._root);
+       }
+
+       PUGI__FN bool xml_node::operator<(const xml_node& r) const
+       {
+               return (_root < r._root);
+       }
+       
+       PUGI__FN bool xml_node::operator>(const xml_node& r) const
+       {
+               return (_root > r._root);
+       }
+       
+       PUGI__FN bool xml_node::operator<=(const xml_node& r) const
+       {
+               return (_root <= r._root);
+       }
+       
+       PUGI__FN bool xml_node::operator>=(const xml_node& r) const
+       {
+               return (_root >= r._root);
+       }
+
+       PUGI__FN bool xml_node::empty() const
+       {
+               return !_root;
+       }
+       
+       PUGI__FN const char_t* xml_node::name() const
+       {
+               return (_root && _root->name) ? _root->name : PUGIXML_TEXT("");
+       }
+
+       PUGI__FN xml_node_type xml_node::type() const
+       {
+               return _root ? static_cast<xml_node_type>((_root->header & impl::xml_memory_page_type_mask) + 1) : node_null;
+       }
+       
+       PUGI__FN const char_t* xml_node::value() const
+       {
+               return (_root && _root->value) ? _root->value : PUGIXML_TEXT("");
+       }
+       
+       PUGI__FN xml_node xml_node::child(const char_t* name_) const
+       {
+               if (!_root) return xml_node();
+
+               for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+                       if (i->name && impl::strequal(name_, i->name)) return xml_node(i);
+
+               return xml_node();
+       }
+
+       PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const
+       {
+               if (!_root) return xml_attribute();
+
+               for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute)
+                       if (i->name && impl::strequal(name_, i->name))
+                               return xml_attribute(i);
+               
+               return xml_attribute();
+       }
+       
+       PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const
+       {
+               if (!_root) return xml_node();
+               
+               for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling)
+                       if (i->name && impl::strequal(name_, i->name)) return xml_node(i);
+
+               return xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::next_sibling() const
+       {
+               if (!_root) return xml_node();
+               
+               if (_root->next_sibling) return xml_node(_root->next_sibling);
+               else return xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const
+       {
+               if (!_root) return xml_node();
+               
+               for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c)
+                       if (i->name && impl::strequal(name_, i->name)) return xml_node(i);
+
+               return xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::previous_sibling() const
+       {
+               if (!_root) return xml_node();
+               
+               if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c);
+               else return xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::parent() const
+       {
+               return _root ? xml_node(_root->parent) : xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::root() const
+       {
+               if (!_root) return xml_node();
+
+               impl::xml_memory_page* page = reinterpret_cast<impl::xml_memory_page*>(_root->header & impl::xml_memory_page_pointer_mask);
+
+               return xml_node(static_cast<impl::xml_document_struct*>(page->allocator));
+       }
+
+       PUGI__FN xml_text xml_node::text() const
+       {
+               return xml_text(_root);
+       }
+
+       PUGI__FN const char_t* xml_node::child_value() const
+       {
+               if (!_root) return PUGIXML_TEXT("");
+               
+               for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+                       if (i->value && impl::is_text_node(i))
+                               return i->value;
+
+               return PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const
+       {
+               return child(name_).child_value();
+       }
+
+       PUGI__FN xml_attribute xml_node::first_attribute() const
+       {
+               return _root ? xml_attribute(_root->first_attribute) : xml_attribute();
+       }
+
+       PUGI__FN xml_attribute xml_node::last_attribute() const
+       {
+               return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute();
+       }
+
+       PUGI__FN xml_node xml_node::first_child() const
+       {
+               return _root ? xml_node(_root->first_child) : xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::last_child() const
+       {
+               return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node();
+       }
+
+       PUGI__FN bool xml_node::set_name(const char_t* rhs)
+       {
+               switch (type())
+               {
+               case node_pi:
+               case node_declaration:
+               case node_element:
+                       return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs);
+
+               default:
+                       return false;
+               }
+       }
+               
+       PUGI__FN bool xml_node::set_value(const char_t* rhs)
+       {
+               switch (type())
+               {
+               case node_pi:
+               case node_cdata:
+               case node_pcdata:
+               case node_comment:
+               case node_doctype:
+                       return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs);
+
+               default:
+                       return false;
+               }
+       }
+
+       PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_)
+       {
+               if (type() != node_element && type() != node_declaration) return xml_attribute();
+               
+               xml_attribute a(impl::append_attribute_ll(_root, impl::get_allocator(_root)));
+               a.set_name(name_);
+               
+               return a;
+       }
+
+       PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_)
+       {
+               if (type() != node_element && type() != node_declaration) return xml_attribute();
+               
+               xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root)));
+               if (!a) return xml_attribute();
+
+               a.set_name(name_);
+               
+               xml_attribute_struct* head = _root->first_attribute;
+
+               if (head)
+               {
+                       a._attr->prev_attribute_c = head->prev_attribute_c;
+                       head->prev_attribute_c = a._attr;
+               }
+               else
+                       a._attr->prev_attribute_c = a._attr;
+               
+               a._attr->next_attribute = head;
+               _root->first_attribute = a._attr;
+                               
+               return a;
+       }
+
+       PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr)
+       {
+               if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute();
+               
+               // check that attribute belongs to *this
+               xml_attribute_struct* cur = attr._attr;
+
+               while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c;
+
+               if (cur != _root->first_attribute) return xml_attribute();
+
+               xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root)));
+               if (!a) return xml_attribute();
+
+               a.set_name(name_);
+
+               if (attr._attr->prev_attribute_c->next_attribute)
+                       attr._attr->prev_attribute_c->next_attribute = a._attr;
+               else
+                       _root->first_attribute = a._attr;
+               
+               a._attr->prev_attribute_c = attr._attr->prev_attribute_c;
+               a._attr->next_attribute = attr._attr;
+               attr._attr->prev_attribute_c = a._attr;
+                               
+               return a;
+       }
+
+       PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr)
+       {
+               if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute();
+               
+               // check that attribute belongs to *this
+               xml_attribute_struct* cur = attr._attr;
+
+               while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c;
+
+               if (cur != _root->first_attribute) return xml_attribute();
+
+               xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root)));
+               if (!a) return xml_attribute();
+
+               a.set_name(name_);
+
+               if (attr._attr->next_attribute)
+                       attr._attr->next_attribute->prev_attribute_c = a._attr;
+               else
+                       _root->first_attribute->prev_attribute_c = a._attr;
+               
+               a._attr->next_attribute = attr._attr->next_attribute;
+               a._attr->prev_attribute_c = attr._attr;
+               attr._attr->next_attribute = a._attr;
+
+               return a;
+       }
+
+       PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto)
+       {
+               if (!proto) return xml_attribute();
+
+               xml_attribute result = append_attribute(proto.name());
+               result.set_value(proto.value());
+
+               return result;
+       }
+
+       PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto)
+       {
+               if (!proto) return xml_attribute();
+
+               xml_attribute result = prepend_attribute(proto.name());
+               result.set_value(proto.value());
+
+               return result;
+       }
+
+       PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr)
+       {
+               if (!proto) return xml_attribute();
+
+               xml_attribute result = insert_attribute_after(proto.name(), attr);
+               result.set_value(proto.value());
+
+               return result;
+       }
+
+       PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr)
+       {
+               if (!proto) return xml_attribute();
+
+               xml_attribute result = insert_attribute_before(proto.name(), attr);
+               result.set_value(proto.value());
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::append_child(xml_node_type type_)
+       {
+               if (!impl::allow_insert_child(this->type(), type_)) return xml_node();
+               
+               xml_node n(impl::append_node(_root, impl::get_allocator(_root), type_));
+
+               if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+               return n;
+       }
+
+       PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_)
+       {
+               if (!impl::allow_insert_child(this->type(), type_)) return xml_node();
+               
+               xml_node n(impl::allocate_node(impl::get_allocator(_root), type_));
+               if (!n) return xml_node();
+
+               n._root->parent = _root;
+
+               xml_node_struct* head = _root->first_child;
+
+               if (head)
+               {
+                       n._root->prev_sibling_c = head->prev_sibling_c;
+                       head->prev_sibling_c = n._root;
+               }
+               else
+                       n._root->prev_sibling_c = n._root;
+               
+               n._root->next_sibling = head;
+               _root->first_child = n._root;
+                               
+               if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+               return n;
+       }
+
+       PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node)
+       {
+               if (!impl::allow_insert_child(this->type(), type_)) return xml_node();
+               if (!node._root || node._root->parent != _root) return xml_node();
+       
+               xml_node n(impl::allocate_node(impl::get_allocator(_root), type_));
+               if (!n) return xml_node();
+
+               n._root->parent = _root;
+               
+               if (node._root->prev_sibling_c->next_sibling)
+                       node._root->prev_sibling_c->next_sibling = n._root;
+               else
+                       _root->first_child = n._root;
+               
+               n._root->prev_sibling_c = node._root->prev_sibling_c;
+               n._root->next_sibling = node._root;
+               node._root->prev_sibling_c = n._root;
+
+               if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+               return n;
+       }
+
+       PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node)
+       {
+               if (!impl::allow_insert_child(this->type(), type_)) return xml_node();
+               if (!node._root || node._root->parent != _root) return xml_node();
+       
+               xml_node n(impl::allocate_node(impl::get_allocator(_root), type_));
+               if (!n) return xml_node();
+
+               n._root->parent = _root;
+       
+               if (node._root->next_sibling)
+                       node._root->next_sibling->prev_sibling_c = n._root;
+               else
+                       _root->first_child->prev_sibling_c = n._root;
+               
+               n._root->next_sibling = node._root->next_sibling;
+               n._root->prev_sibling_c = node._root;
+               node._root->next_sibling = n._root;
+
+               if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml"));
+
+               return n;
+       }
+
+       PUGI__FN xml_node xml_node::append_child(const char_t* name_)
+       {
+               xml_node result = append_child(node_element);
+
+               result.set_name(name_);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::prepend_child(const char_t* name_)
+       {
+               xml_node result = prepend_child(node_element);
+
+               result.set_name(name_);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node)
+       {
+               xml_node result = insert_child_after(node_element, node);
+
+               result.set_name(name_);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node)
+       {
+               xml_node result = insert_child_before(node_element, node);
+
+               result.set_name(name_);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::append_copy(const xml_node& proto)
+       {
+               xml_node result = append_child(proto.type());
+
+               if (result) impl::recursive_copy_skip(result, proto, result);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto)
+       {
+               xml_node result = prepend_child(proto.type());
+
+               if (result) impl::recursive_copy_skip(result, proto, result);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node)
+       {
+               xml_node result = insert_child_after(proto.type(), node);
+
+               if (result) impl::recursive_copy_skip(result, proto, result);
+
+               return result;
+       }
+
+       PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node)
+       {
+               xml_node result = insert_child_before(proto.type(), node);
+
+               if (result) impl::recursive_copy_skip(result, proto, result);
+
+               return result;
+       }
+
+       PUGI__FN bool xml_node::remove_attribute(const char_t* name_)
+       {
+               return remove_attribute(attribute(name_));
+       }
+
+       PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a)
+       {
+               if (!_root || !a._attr) return false;
+
+               // check that attribute belongs to *this
+               xml_attribute_struct* attr = a._attr;
+
+               while (attr->prev_attribute_c->next_attribute) attr = attr->prev_attribute_c;
+
+               if (attr != _root->first_attribute) return false;
+
+               if (a._attr->next_attribute) a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c;
+               else if (_root->first_attribute) _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c;
+               
+               if (a._attr->prev_attribute_c->next_attribute) a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute;
+               else _root->first_attribute = a._attr->next_attribute;
+
+               impl::destroy_attribute(a._attr, impl::get_allocator(_root));
+
+               return true;
+       }
+
+       PUGI__FN bool xml_node::remove_child(const char_t* name_)
+       {
+               return remove_child(child(name_));
+       }
+
+       PUGI__FN bool xml_node::remove_child(const xml_node& n)
+       {
+               if (!_root || !n._root || n._root->parent != _root) return false;
+
+               if (n._root->next_sibling) n._root->next_sibling->prev_sibling_c = n._root->prev_sibling_c;
+               else if (_root->first_child) _root->first_child->prev_sibling_c = n._root->prev_sibling_c;
+               
+               if (n._root->prev_sibling_c->next_sibling) n._root->prev_sibling_c->next_sibling = n._root->next_sibling;
+               else _root->first_child = n._root->next_sibling;
+               
+               impl::destroy_node(n._root, impl::get_allocator(_root));
+
+               return true;
+       }
+
+       PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding)
+       {
+               // append_buffer is only valid for elements/documents
+               if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root);
+
+               // get document node
+               impl::xml_document_struct* doc = static_cast<impl::xml_document_struct*>(root()._root);
+               assert(doc);
+               
+               // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later)
+               impl::xml_memory_page* page = 0;
+               impl::xml_extra_buffer* extra = static_cast<impl::xml_extra_buffer*>(doc->allocate_memory(sizeof(impl::xml_extra_buffer), page));
+               (void)page;
+
+               if (!extra) return impl::make_parse_result(status_out_of_memory);
+
+               // save name; name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level
+               char_t* rootname = _root->name;
+               _root->name = 0;
+
+               // parse
+               char_t* buffer = 0;
+               xml_parse_result res = impl::load_buffer_impl(doc, _root, const_cast<void*>(contents), size, options, encoding, false, false, &buffer);
+
+               // restore name
+               _root->name = rootname;
+
+               // add extra buffer to the list
+               extra->buffer = buffer;
+               extra->next = doc->extra_buffers;
+               doc->extra_buffers = extra;
+
+               return res;
+       }
+
+       PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const
+       {
+               if (!_root) return xml_node();
+               
+               for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+                       if (i->name && impl::strequal(name_, i->name))
+                       {
+                               for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute)
+                                       if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT("")))
+                                               return xml_node(i);
+                       }
+
+               return xml_node();
+       }
+
+       PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const
+       {
+               if (!_root) return xml_node();
+               
+               for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+                       for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute)
+                               if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT("")))
+                                       return xml_node(i);
+
+               return xml_node();
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN string_t xml_node::path(char_t delimiter) const
+       {
+               xml_node cursor = *this; // Make a copy.
+               
+               string_t result = cursor.name();
+
+               while (cursor.parent())
+               {
+                       cursor = cursor.parent();
+                       
+                       string_t temp = cursor.name();
+                       temp += delimiter;
+                       temp += result;
+                       result.swap(temp);
+               }
+
+               return result;
+       }
+#endif
+
+       PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const
+       {
+               xml_node found = *this; // Current search context.
+
+               if (!_root || !path_ || !path_[0]) return found;
+
+               if (path_[0] == delimiter)
+               {
+                       // Absolute path; e.g. '/foo/bar'
+                       found = found.root();
+                       ++path_;
+               }
+
+               const char_t* path_segment = path_;
+
+               while (*path_segment == delimiter) ++path_segment;
+
+               const char_t* path_segment_end = path_segment;
+
+               while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end;
+
+               if (path_segment == path_segment_end) return found;
+
+               const char_t* next_segment = path_segment_end;
+
+               while (*next_segment == delimiter) ++next_segment;
+
+               if (*path_segment == '.' && path_segment + 1 == path_segment_end)
+                       return found.first_element_by_path(next_segment, delimiter);
+               else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end)
+                       return found.parent().first_element_by_path(next_segment, delimiter);
+               else
+               {
+                       for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling)
+                       {
+                               if (j->name && impl::strequalrange(j->name, path_segment, static_cast<size_t>(path_segment_end - path_segment)))
+                               {
+                                       xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter);
+
+                                       if (subsearch) return subsearch;
+                               }
+                       }
+
+                       return xml_node();
+               }
+       }
+
+       PUGI__FN bool xml_node::traverse(xml_tree_walker& walker)
+       {
+               walker._depth = -1;
+               
+               xml_node arg_begin = *this;
+               if (!walker.begin(arg_begin)) return false;
+
+               xml_node cur = first_child();
+                               
+               if (cur)
+               {
+                       ++walker._depth;
+
+                       do 
+                       {
+                               xml_node arg_for_each = cur;
+                               if (!walker.for_each(arg_for_each))
+                                       return false;
+                                               
+                               if (cur.first_child())
+                               {
+                                       ++walker._depth;
+                                       cur = cur.first_child();
+                               }
+                               else if (cur.next_sibling())
+                                       cur = cur.next_sibling();
+                               else
+                               {
+                                       // Borland C++ workaround
+                                       while (!cur.next_sibling() && cur != *this && !cur.parent().empty())
+                                       {
+                                               --walker._depth;
+                                               cur = cur.parent();
+                                       }
+                                               
+                                       if (cur != *this)
+                                               cur = cur.next_sibling();
+                               }
+                       }
+                       while (cur && cur != *this);
+               }
+
+               assert(walker._depth == -1);
+
+               xml_node arg_end = *this;
+               return walker.end(arg_end);
+       }
+
+       PUGI__FN size_t xml_node::hash_value() const
+       {
+               return static_cast<size_t>(reinterpret_cast<uintptr_t>(_root) / sizeof(xml_node_struct));
+       }
+
+       PUGI__FN xml_node_struct* xml_node::internal_object() const
+       {
+               return _root;
+       }
+
+       PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const
+       {
+               if (!_root) return;
+
+               impl::xml_buffered_writer buffered_writer(writer, encoding);
+
+               impl::node_output(buffered_writer, *this, indent, flags, depth);
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN void xml_node::print(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const
+       {
+               xml_writer_stream writer(stream);
+
+               print(writer, indent, flags, encoding, depth);
+       }
+
+       PUGI__FN void xml_node::print(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const
+       {
+               xml_writer_stream writer(stream);
+
+               print(writer, indent, flags, encoding_wchar, depth);
+       }
+#endif
+
+       PUGI__FN ptrdiff_t xml_node::offset_debug() const
+       {
+               xml_node_struct* r = root()._root;
+
+               if (!r) return -1;
+
+               const char_t* buffer = static_cast<impl::xml_document_struct*>(r)->buffer;
+
+               if (!buffer) return -1;
+
+               switch (type())
+               {
+               case node_document:
+                       return 0;
+
+               case node_element:
+               case node_declaration:
+               case node_pi:
+                       return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer;
+
+               case node_pcdata:
+               case node_cdata:
+               case node_comment:
+               case node_doctype:
+                       return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer;
+
+               default:
+                       return -1;
+               }
+       }
+
+#ifdef __BORLANDC__
+       PUGI__FN bool operator&&(const xml_node& lhs, bool rhs)
+       {
+               return (bool)lhs && rhs;
+       }
+
+       PUGI__FN bool operator||(const xml_node& lhs, bool rhs)
+       {
+               return (bool)lhs || rhs;
+       }
+#endif
+
+       PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root)
+       {
+       }
+
+       PUGI__FN xml_node_struct* xml_text::_data() const
+       {
+               if (!_root || impl::is_text_node(_root)) return _root;
+
+               for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling)
+                       if (impl::is_text_node(node))
+                               return node;
+
+               return 0;
+       }
+
+       PUGI__FN xml_node_struct* xml_text::_data_new()
+       {
+               xml_node_struct* d = _data();
+               if (d) return d;
+
+               return xml_node(_root).append_child(node_pcdata).internal_object();
+       }
+
+       PUGI__FN xml_text::xml_text(): _root(0)
+       {
+       }
+
+       PUGI__FN static void unspecified_bool_xml_text(xml_text***)
+       {
+       }
+
+       PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const
+       {
+               return _data() ? unspecified_bool_xml_text : 0;
+       }
+
+       PUGI__FN bool xml_text::operator!() const
+       {
+               return !_data();
+       }
+
+       PUGI__FN bool xml_text::empty() const
+       {
+               return _data() == 0;
+       }
+
+       PUGI__FN const char_t* xml_text::get() const
+       {
+               xml_node_struct* d = _data();
+
+               return (d && d->value) ? d->value : PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const char_t* xml_text::as_string(const char_t* def) const
+       {
+               xml_node_struct* d = _data();
+
+               return (d && d->value) ? d->value : def;
+       }
+
+       PUGI__FN int xml_text::as_int(int def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_int(d ? d->value : 0, def);
+       }
+
+       PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_uint(d ? d->value : 0, def);
+       }
+
+       PUGI__FN double xml_text::as_double(double def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_double(d ? d->value : 0, def);
+       }
+
+       PUGI__FN float xml_text::as_float(float def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_float(d ? d->value : 0, def);
+       }
+
+       PUGI__FN bool xml_text::as_bool(bool def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_bool(d ? d->value : 0, def);
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN long long xml_text::as_llong(long long def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_llong(d ? d->value : 0, def);
+       }
+
+       PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const
+       {
+               xml_node_struct* d = _data();
+
+               return impl::get_value_ullong(d ? d->value : 0, def);
+       }
+#endif
+
+       PUGI__FN bool xml_text::set(const char_t* rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+       PUGI__FN bool xml_text::set(int rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+       PUGI__FN bool xml_text::set(unsigned int rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+       PUGI__FN bool xml_text::set(double rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+       PUGI__FN bool xml_text::set(bool rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN bool xml_text::set(long long rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+
+       PUGI__FN bool xml_text::set(unsigned long long rhs)
+       {
+               xml_node_struct* dn = _data_new();
+
+               return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false;
+       }
+#endif
+
+       PUGI__FN xml_text& xml_text::operator=(const char_t* rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_text& xml_text::operator=(int rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_text& xml_text::operator=(unsigned int rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_text& xml_text::operator=(double rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_text& xml_text::operator=(bool rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+#ifdef PUGIXML_HAS_LONG_LONG
+       PUGI__FN xml_text& xml_text::operator=(long long rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+
+       PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs)
+       {
+               set(rhs);
+               return *this;
+       }
+#endif
+
+       PUGI__FN xml_node xml_text::data() const
+       {
+               return xml_node(_data());
+       }
+
+#ifdef __BORLANDC__
+       PUGI__FN bool operator&&(const xml_text& lhs, bool rhs)
+       {
+               return (bool)lhs && rhs;
+       }
+
+       PUGI__FN bool operator||(const xml_text& lhs, bool rhs)
+       {
+               return (bool)lhs || rhs;
+       }
+#endif
+
+       PUGI__FN xml_node_iterator::xml_node_iterator()
+       {
+       }
+
+       PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent())
+       {
+       }
+
+       PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent)
+       {
+       }
+
+       PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const
+       {
+               return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root;
+       }
+       
+       PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const
+       {
+               return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root;
+       }
+
+       PUGI__FN xml_node& xml_node_iterator::operator*() const
+       {
+               assert(_wrap._root);
+               return _wrap;
+       }
+
+       PUGI__FN xml_node* xml_node_iterator::operator->() const
+       {
+               assert(_wrap._root);
+               return const_cast<xml_node*>(&_wrap); // BCC32 workaround
+       }
+
+       PUGI__FN const xml_node_iterator& xml_node_iterator::operator++()
+       {
+               assert(_wrap._root);
+               _wrap._root = _wrap._root->next_sibling;
+               return *this;
+       }
+
+       PUGI__FN xml_node_iterator xml_node_iterator::operator++(int)
+       {
+               xml_node_iterator temp = *this;
+               ++*this;
+               return temp;
+       }
+
+       PUGI__FN const xml_node_iterator& xml_node_iterator::operator--()
+       {
+               _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child();
+               return *this;
+       }
+
+       PUGI__FN xml_node_iterator xml_node_iterator::operator--(int)
+       {
+               xml_node_iterator temp = *this;
+               --*this;
+               return temp;
+       }
+
+       PUGI__FN xml_attribute_iterator::xml_attribute_iterator()
+       {
+       }
+
+       PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent)
+       {
+       }
+
+       PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent)
+       {
+       }
+
+       PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const
+       {
+               return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root;
+       }
+       
+       PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const
+       {
+               return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root;
+       }
+
+       PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const
+       {
+               assert(_wrap._attr);
+               return _wrap;
+       }
+
+       PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const
+       {
+               assert(_wrap._attr);
+               return const_cast<xml_attribute*>(&_wrap); // BCC32 workaround
+       }
+
+       PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++()
+       {
+               assert(_wrap._attr);
+               _wrap._attr = _wrap._attr->next_attribute;
+               return *this;
+       }
+
+       PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int)
+       {
+               xml_attribute_iterator temp = *this;
+               ++*this;
+               return temp;
+       }
+
+       PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--()
+       {
+               _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute();
+               return *this;
+       }
+
+       PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int)
+       {
+               xml_attribute_iterator temp = *this;
+               --*this;
+               return temp;
+       }
+
+       PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0)
+       {
+       }
+
+       PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name)
+       {
+       }
+
+       PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name)
+       {
+       }
+
+       PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const
+       {
+               return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root;
+       }
+
+       PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const
+       {
+               return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root;
+       }
+
+       PUGI__FN xml_node& xml_named_node_iterator::operator*() const
+       {
+               assert(_wrap._root);
+               return _wrap;
+       }
+
+       PUGI__FN xml_node* xml_named_node_iterator::operator->() const
+       {
+               assert(_wrap._root);
+               return const_cast<xml_node*>(&_wrap); // BCC32 workaround
+       }
+
+       PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++()
+       {
+               assert(_wrap._root);
+               _wrap = _wrap.next_sibling(_name);
+               return *this;
+       }
+
+       PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int)
+       {
+               xml_named_node_iterator temp = *this;
+               ++*this;
+               return temp;
+       }
+
+       PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator--()
+       {
+               if (_wrap._root)
+                       _wrap = _wrap.previous_sibling(_name);
+               else
+               {
+                       _wrap = _parent.last_child();
+
+                       if (!impl::strequal(_wrap.name(), _name))
+                               _wrap = _wrap.previous_sibling(_name);
+               }
+
+               return *this;
+       }
+
+       PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int)
+       {
+               xml_named_node_iterator temp = *this;
+               --*this;
+               return temp;
+       }
+
+       PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto)
+       {
+       }
+
+       PUGI__FN xml_parse_result::operator bool() const
+       {
+               return status == status_ok;
+       }
+
+       PUGI__FN const char* xml_parse_result::description() const
+       {
+               switch (status)
+               {
+               case status_ok: return "No error";
+
+               case status_file_not_found: return "File was not found";
+               case status_io_error: return "Error reading from file/stream";
+               case status_out_of_memory: return "Could not allocate memory";
+               case status_internal_error: return "Internal error occurred";
+
+               case status_unrecognized_tag: return "Could not determine tag type";
+
+               case status_bad_pi: return "Error parsing document declaration/processing instruction";
+               case status_bad_comment: return "Error parsing comment";
+               case status_bad_cdata: return "Error parsing CDATA section";
+               case status_bad_doctype: return "Error parsing document type declaration";
+               case status_bad_pcdata: return "Error parsing PCDATA section";
+               case status_bad_start_element: return "Error parsing start element tag";
+               case status_bad_attribute: return "Error parsing element attribute";
+               case status_bad_end_element: return "Error parsing end element tag";
+               case status_end_element_mismatch: return "Start-end tags mismatch";
+
+               case status_append_invalid_root: return "Unable to append nodes: root is not an element or document";
+
+               case status_no_document_element: return "No document element found";
+
+               default: return "Unknown error";
+               }
+       }
+
+       PUGI__FN xml_document::xml_document(): _buffer(0)
+       {
+               create();
+       }
+
+       PUGI__FN xml_document::~xml_document()
+       {
+               destroy();
+       }
+
+       PUGI__FN void xml_document::reset()
+       {
+               destroy();
+               create();
+       }
+
+       PUGI__FN void xml_document::reset(const xml_document& proto)
+       {
+               reset();
+
+               for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling())
+                       append_copy(cur);
+       }
+
+       PUGI__FN void xml_document::create()
+       {
+        assert(!_root);
+
+               // initialize sentinel page
+               PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment <= sizeof(_memory));
+
+               // align upwards to page boundary
+               void* page_memory = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1));
+
+               // prepare page structure
+               impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory);
+               assert(page);
+
+               page->busy_size = impl::xml_memory_page_size;
+
+               // allocate new root
+               _root = new (page->data) impl::xml_document_struct(page);
+               _root->prev_sibling_c = _root;
+
+               // setup sentinel page
+               page->allocator = static_cast<impl::xml_document_struct*>(_root);
+       }
+
+       PUGI__FN void xml_document::destroy()
+       {
+        assert(_root);
+
+               // destroy static storage
+               if (_buffer)
+               {
+                       impl::xml_memory::deallocate(_buffer);
+                       _buffer = 0;
+               }
+
+               // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator)
+               for (impl::xml_extra_buffer* extra = static_cast<impl::xml_document_struct*>(_root)->extra_buffers; extra; extra = extra->next)
+               {
+                       if (extra->buffer) impl::xml_memory::deallocate(extra->buffer);
+               }
+
+               // destroy dynamic storage, leave sentinel page (it's in static memory)
+        impl::xml_memory_page* root_page = reinterpret_cast<impl::xml_memory_page*>(_root->header & impl::xml_memory_page_pointer_mask);
+        assert(root_page && !root_page->prev && !root_page->memory);
+
+        for (impl::xml_memory_page* page = root_page->next; page; )
+        {
+            impl::xml_memory_page* next = page->next;
+
+            impl::xml_allocator::deallocate_page(page);
+
+            page = next;
+        }
+
+        _root = 0;
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN xml_parse_result xml_document::load(std::basic_istream<char, std::char_traits<char> >& stream, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               return impl::load_stream_impl(*this, stream, options, encoding);
+       }
+
+       PUGI__FN xml_parse_result xml_document::load(std::basic_istream<wchar_t, std::char_traits<wchar_t> >& stream, unsigned int options)
+       {
+               reset();
+
+               return impl::load_stream_impl(*this, stream, options, encoding_wchar);
+       }
+#endif
+
+       PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options)
+       {
+               // Force native encoding (skip autodetection)
+       #ifdef PUGIXML_WCHAR_MODE
+               xml_encoding encoding = encoding_wchar;
+       #else
+               xml_encoding encoding = encoding_utf8;
+       #endif
+
+               return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding);
+       }
+
+       PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               FILE* file = fopen(path_, "rb");
+
+               return impl::load_file_impl(*this, file, options, encoding);
+       }
+
+       PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               FILE* file = impl::open_file_wide(path_, L"rb");
+
+               return impl::load_file_impl(*this, file, options, encoding);
+       }
+
+       PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, const_cast<void*>(contents), size, options, encoding, false, false, &_buffer);
+       }
+
+       PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, false, &_buffer);
+       }
+               
+       PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding)
+       {
+               reset();
+
+               return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, true, &_buffer);
+       }
+
+       PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+       {
+               impl::xml_buffered_writer buffered_writer(writer, encoding);
+
+               if ((flags & format_write_bom) && encoding != encoding_latin1)
+               {
+                       // BOM always represents the codepoint U+FEFF, so just write it in native encoding
+               #ifdef PUGIXML_WCHAR_MODE
+                       unsigned int bom = 0xfeff;
+                       buffered_writer.write(static_cast<wchar_t>(bom));
+               #else
+                       buffered_writer.write('\xef', '\xbb', '\xbf');
+               #endif
+               }
+
+               if (!(flags & format_no_declaration) && !impl::has_declaration(*this))
+               {
+                       buffered_writer.write(PUGIXML_TEXT("<?xml version=\"1.0\""));
+                       if (encoding == encoding_latin1) buffered_writer.write(PUGIXML_TEXT(" encoding=\"ISO-8859-1\""));
+                       buffered_writer.write('?', '>');
+                       if (!(flags & format_raw)) buffered_writer.write('\n');
+               }
+
+               impl::node_output(buffered_writer, *this, indent, flags, 0);
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN void xml_document::save(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+       {
+               xml_writer_stream writer(stream);
+
+               save(writer, indent, flags, encoding);
+       }
+
+       PUGI__FN void xml_document::save(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent, unsigned int flags) const
+       {
+               xml_writer_stream writer(stream);
+
+               save(writer, indent, flags, encoding_wchar);
+       }
+#endif
+
+       PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+       {
+               FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb");
+               return impl::save_file_impl(*this, file, indent, flags, encoding);
+       }
+
+       PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const
+       {
+               FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb");
+               return impl::save_file_impl(*this, file, indent, flags, encoding);
+       }
+
+       PUGI__FN xml_node xml_document::document_element() const
+       {
+        assert(_root);
+
+               for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling)
+                       if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element)
+                               return xml_node(i);
+
+               return xml_node();
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str)
+       {
+               assert(str);
+
+               return impl::as_utf8_impl(str, impl::strlength_wide(str));
+       }
+
+       PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t>& str)
+       {
+               return impl::as_utf8_impl(str.c_str(), str.size());
+       }
+       
+       PUGI__FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const char* str)
+       {
+               assert(str);
+
+               return impl::as_wide_impl(str, strlen(str));
+       }
+       
+       PUGI__FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const std::string& str)
+       {
+               return impl::as_wide_impl(str.c_str(), str.size());
+       }
+#endif
+
+       PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate)
+       {
+               impl::xml_memory::allocate = allocate;
+               impl::xml_memory::deallocate = deallocate;
+       }
+
+       PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function()
+       {
+               return impl::xml_memory::allocate;
+       }
+
+       PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function()
+       {
+               return impl::xml_memory::deallocate;
+       }
+}
+
+#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC))
+namespace std
+{
+       // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier)
+       PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+
+       PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+
+       PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+}
+#endif
+
+#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC)
+namespace std
+{
+       // Workarounds for (non-standard) iterator category detection
+       PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+
+       PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+
+       PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&)
+       {
+               return std::bidirectional_iterator_tag();
+       }
+}
+#endif
+
+#ifndef PUGIXML_NO_XPATH
+
+// STL replacements
+PUGI__NS_BEGIN
+       struct equal_to
+       {
+               template <typename T> bool operator()(const T& lhs, const T& rhs) const
+               {
+                       return lhs == rhs;
+               }
+       };
+
+       struct not_equal_to
+       {
+               template <typename T> bool operator()(const T& lhs, const T& rhs) const
+               {
+                       return lhs != rhs;
+               }
+       };
+
+       struct less
+       {
+               template <typename T> bool operator()(const T& lhs, const T& rhs) const
+               {
+                       return lhs < rhs;
+               }
+       };
+
+       struct less_equal
+       {
+               template <typename T> bool operator()(const T& lhs, const T& rhs) const
+               {
+                       return lhs <= rhs;
+               }
+       };
+
+       template <typename T> void swap(T& lhs, T& rhs)
+       {
+               T temp = lhs;
+               lhs = rhs;
+               rhs = temp;
+       }
+
+       template <typename I, typename Pred> I min_element(I begin, I end, const Pred& pred)
+       {
+               I result = begin;
+
+               for (I it = begin + 1; it != end; ++it)
+                       if (pred(*it, *result))
+                               result = it;
+
+               return result;
+       }
+
+       template <typename I> void reverse(I begin, I end)
+       {
+               while (end - begin > 1) swap(*begin++, *--end);
+       }
+
+       template <typename I> I unique(I begin, I end)
+       {
+               // fast skip head
+               while (end - begin > 1 && *begin != *(begin + 1)) begin++;
+
+               if (begin == end) return begin;
+
+               // last written element
+               I write = begin++; 
+
+               // merge unique elements
+               while (begin != end)
+               {
+                       if (*begin != *write)
+                               *++write = *begin++;
+                       else
+                               begin++;
+               }
+
+               // past-the-end (write points to live element)
+               return write + 1;
+       }
+
+       template <typename I> void copy_backwards(I begin, I end, I target)
+       {
+               while (begin != end) *--target = *--end;
+       }
+
+       template <typename I, typename Pred, typename T> void insertion_sort(I begin, I end, const Pred& pred, T*)
+       {
+               assert(begin != end);
+
+               for (I it = begin + 1; it != end; ++it)
+               {
+                       T val = *it;
+
+                       if (pred(val, *begin))
+                       {
+                               // move to front
+                               copy_backwards(begin, it, it + 1);
+                               *begin = val;
+                       }
+                       else
+                       {
+                               I hole = it;
+
+                               // move hole backwards
+                               while (pred(val, *(hole - 1)))
+                               {
+                                       *hole = *(hole - 1);
+                                       hole--;
+                               }
+
+                               // fill hole with element
+                               *hole = val;
+                       }
+               }
+       }
+
+       // std variant for elements with ==
+       template <typename I, typename Pred> void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend)
+       {
+               I eqbeg = middle, eqend = middle + 1;
+
+               // expand equal range
+               while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg;
+               while (eqend != end && *eqend == *eqbeg) ++eqend;
+
+               // process outer elements
+               I ltend = eqbeg, gtbeg = eqend;
+
+               for (;;)
+               {
+                       // find the element from the right side that belongs to the left one
+                       for (; gtbeg != end; ++gtbeg)
+                               if (!pred(*eqbeg, *gtbeg))
+                               {
+                                       if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++);
+                                       else break;
+                               }
+
+                       // find the element from the left side that belongs to the right one
+                       for (; ltend != begin; --ltend)
+                               if (!pred(*(ltend - 1), *eqbeg))
+                               {
+                                       if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg);
+                                       else break;
+                               }
+
+                       // scanned all elements
+                       if (gtbeg == end && ltend == begin)
+                       {
+                               *out_eqbeg = eqbeg;
+                               *out_eqend = eqend;
+                               return;
+                       }
+
+                       // make room for elements by moving equal area
+                       if (gtbeg == end)
+                       {
+                               if (--ltend != --eqbeg) swap(*ltend, *eqbeg);
+                               swap(*eqbeg, *--eqend);
+                       }
+                       else if (ltend == begin)
+                       {
+                               if (eqend != gtbeg) swap(*eqbeg, *eqend);
+                               ++eqend;
+                               swap(*gtbeg++, *eqbeg++);
+                       }
+                       else swap(*gtbeg++, *--ltend);
+               }
+       }
+
+       template <typename I, typename Pred> void median3(I first, I middle, I last, const Pred& pred)
+       {
+               if (pred(*middle, *first)) swap(*middle, *first);
+               if (pred(*last, *middle)) swap(*last, *middle);
+               if (pred(*middle, *first)) swap(*middle, *first);
+       }
+
+       template <typename I, typename Pred> void median(I first, I middle, I last, const Pred& pred)
+       {
+               if (last - first <= 40)
+               {
+                       // median of three for small chunks
+                       median3(first, middle, last, pred);
+               }
+               else
+               {
+                       // median of nine
+                       size_t step = (last - first + 1) / 8;
+
+                       median3(first, first + step, first + 2 * step, pred);
+                       median3(middle - step, middle, middle + step, pred);
+                       median3(last - 2 * step, last - step, last, pred);
+                       median3(first + step, middle, last - step, pred);
+               }
+       }
+
+       template <typename I, typename Pred> void sort(I begin, I end, const Pred& pred)
+       {
+               // sort large chunks
+               while (end - begin > 32)
+               {
+                       // find median element
+                       I middle = begin + (end - begin) / 2;
+                       median(begin, middle, end - 1, pred);
+
+                       // partition in three chunks (< = >)
+                       I eqbeg, eqend;
+                       partition(begin, middle, end, pred, &eqbeg, &eqend);
+
+                       // loop on larger half
+                       if (eqbeg - begin > end - eqend)
+                       {
+                               sort(eqend, end, pred);
+                               end = eqbeg;
+                       }
+                       else
+                       {
+                               sort(begin, eqbeg, pred);
+                               begin = eqend;
+                       }
+               }
+
+               // insertion sort small chunk
+               if (begin != end) insertion_sort(begin, end, pred, &*begin);
+       }
+PUGI__NS_END
+
+// Allocator used for AST and evaluation stacks
+PUGI__NS_BEGIN
+       struct xpath_memory_block
+       {       
+               xpath_memory_block* next;
+
+               char data[
+       #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE
+                       PUGIXML_MEMORY_XPATH_PAGE_SIZE
+       #else
+                       4096
+       #endif
+               ];
+       };
+               
+       class xpath_allocator
+       {
+               xpath_memory_block* _root;
+               size_t _root_size;
+
+       public:
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               jmp_buf* error_handler;
+       #endif
+
+               xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size)
+               {
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       error_handler = 0;
+               #endif
+               }
+               
+               void* allocate_nothrow(size_t size)
+               {
+                       const size_t block_capacity = sizeof(_root->data);
+
+                       // align size so that we're able to store pointers in subsequent blocks
+                       size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
+
+                       if (_root_size + size <= block_capacity)
+                       {
+                               void* buf = _root->data + _root_size;
+                               _root_size += size;
+                               return buf;
+                       }
+                       else
+                       {
+                               size_t block_data_size = (size > block_capacity) ? size : block_capacity;
+                               size_t block_size = block_data_size + offsetof(xpath_memory_block, data);
+
+                               xpath_memory_block* block = static_cast<xpath_memory_block*>(xml_memory::allocate(block_size));
+                               if (!block) return 0;
+                               
+                               block->next = _root;
+                               
+                               _root = block;
+                               _root_size = size;
+                               
+                               return block->data;
+                       }
+               }
+
+               void* allocate(size_t size)
+               {
+                       void* result = allocate_nothrow(size);
+
+                       if (!result)
+                       {
+                       #ifdef PUGIXML_NO_EXCEPTIONS
+                               assert(error_handler);
+                               longjmp(*error_handler, 1);
+                       #else
+                               throw std::bad_alloc();
+                       #endif
+                       }
+
+                       return result;
+               }
+
+               void* reallocate(void* ptr, size_t old_size, size_t new_size)
+               {
+                       // align size so that we're able to store pointers in subsequent blocks
+                       old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
+                       new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1);
+
+                       // we can only reallocate the last object
+                       assert(ptr == 0 || static_cast<char*>(ptr) + old_size == _root->data + _root_size);
+
+                       // adjust root size so that we have not allocated the object at all
+                       bool only_object = (_root_size == old_size);
+
+                       if (ptr) _root_size -= old_size;
+
+                       // allocate a new version (this will obviously reuse the memory if possible)
+                       void* result = allocate(new_size);
+                       assert(result);
+
+                       // we have a new block
+                       if (result != ptr && ptr)
+                       {
+                               // copy old data
+                               assert(new_size >= old_size);
+                               memcpy(result, ptr, old_size);
+
+                               // free the previous page if it had no other objects
+                               if (only_object)
+                               {
+                                       assert(_root->data == result);
+                                       assert(_root->next);
+
+                                       xpath_memory_block* next = _root->next->next;
+
+                                       if (next)
+                                       {
+                                               // deallocate the whole page, unless it was the first one
+                                               xml_memory::deallocate(_root->next);
+                                               _root->next = next;
+                                       }
+                               }
+                       }
+
+                       return result;
+               }
+
+               void revert(const xpath_allocator& state)
+               {
+                       // free all new pages
+                       xpath_memory_block* cur = _root;
+
+                       while (cur != state._root)
+                       {
+                               xpath_memory_block* next = cur->next;
+
+                               xml_memory::deallocate(cur);
+
+                               cur = next;
+                       }
+
+                       // restore state
+                       _root = state._root;
+                       _root_size = state._root_size;
+               }
+
+               void release()
+               {
+                       xpath_memory_block* cur = _root;
+                       assert(cur);
+
+                       while (cur->next)
+                       {
+                               xpath_memory_block* next = cur->next;
+
+                               xml_memory::deallocate(cur);
+
+                               cur = next;
+                       }
+               }
+       };
+
+       struct xpath_allocator_capture
+       {
+               xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc)
+               {
+               }
+
+               ~xpath_allocator_capture()
+               {
+                       _target->revert(_state);
+               }
+
+               xpath_allocator* _target;
+               xpath_allocator _state;
+       };
+
+       struct xpath_stack
+       {
+               xpath_allocator* result;
+               xpath_allocator* temp;
+       };
+
+       struct xpath_stack_data
+       {
+               xpath_memory_block blocks[2];
+               xpath_allocator result;
+               xpath_allocator temp;
+               xpath_stack stack;
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               jmp_buf error_handler;
+       #endif
+
+               xpath_stack_data(): result(blocks + 0), temp(blocks + 1)
+               {
+                       blocks[0].next = blocks[1].next = 0;
+
+                       stack.result = &result;
+                       stack.temp = &temp;
+
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       result.error_handler = temp.error_handler = &error_handler;
+               #endif
+               }
+
+               ~xpath_stack_data()
+               {
+                       result.release();
+                       temp.release();
+               }
+       };
+PUGI__NS_END
+
+// String class
+PUGI__NS_BEGIN
+       class xpath_string
+       {
+               const char_t* _buffer;
+               bool _uses_heap;
+
+               static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc)
+               {
+                       char_t* result = static_cast<char_t*>(alloc->allocate((length + 1) * sizeof(char_t)));
+                       assert(result);
+
+                       memcpy(result, string, length * sizeof(char_t));
+                       result[length] = 0;
+
+                       return result;
+               }
+
+               static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc)
+               {
+                       return duplicate_string(string, strlength(string), alloc);
+               }
+
+       public:
+               xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false)
+               {
+               }
+
+               explicit xpath_string(const char_t* str, xpath_allocator* alloc)
+               {
+                       bool empty_ = (*str == 0);
+
+                       _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc);
+                       _uses_heap = !empty_;
+               }
+
+               explicit xpath_string(const char_t* str, bool use_heap): _buffer(str), _uses_heap(use_heap)
+               {
+               }
+
+               xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc)
+               {
+                       assert(begin <= end);
+
+                       bool empty_ = (begin == end);
+
+                       _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast<size_t>(end - begin), alloc);
+                       _uses_heap = !empty_;
+               }
+
+               void append(const xpath_string& o, xpath_allocator* alloc)
+               {
+                       // skip empty sources
+                       if (!*o._buffer) return;
+
+                       // fast append for constant empty target and constant source
+                       if (!*_buffer && !_uses_heap && !o._uses_heap)
+                       {
+                               _buffer = o._buffer;
+                       }
+                       else
+                       {
+                               // need to make heap copy
+                               size_t target_length = strlength(_buffer);
+                               size_t source_length = strlength(o._buffer);
+                               size_t result_length = target_length + source_length;
+
+                               // allocate new buffer
+                               char_t* result = static_cast<char_t*>(alloc->reallocate(_uses_heap ? const_cast<char_t*>(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t)));
+                               assert(result);
+
+                               // append first string to the new buffer in case there was no reallocation
+                               if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t));
+
+                               // append second string to the new buffer
+                               memcpy(result + target_length, o._buffer, source_length * sizeof(char_t));
+                               result[result_length] = 0;
+
+                               // finalize
+                               _buffer = result;
+                               _uses_heap = true;
+                       }
+               }
+
+               const char_t* c_str() const
+               {
+                       return _buffer;
+               }
+
+               size_t length() const
+               {
+                       return strlength(_buffer);
+               }
+               
+               char_t* data(xpath_allocator* alloc)
+               {
+                       // make private heap copy
+                       if (!_uses_heap)
+                       {
+                               _buffer = duplicate_string(_buffer, alloc);
+                               _uses_heap = true;
+                       }
+
+                       return const_cast<char_t*>(_buffer);
+               }
+
+               bool empty() const
+               {
+                       return *_buffer == 0;
+               }
+
+               bool operator==(const xpath_string& o) const
+               {
+                       return strequal(_buffer, o._buffer);
+               }
+
+               bool operator!=(const xpath_string& o) const
+               {
+                       return !strequal(_buffer, o._buffer);
+               }
+
+               bool uses_heap() const
+               {
+                       return _uses_heap;
+               }
+       };
+
+       PUGI__FN xpath_string xpath_string_const(const char_t* str)
+       {
+               return xpath_string(str, false);
+       }
+PUGI__NS_END
+
+PUGI__NS_BEGIN
+       PUGI__FN bool starts_with(const char_t* string, const char_t* pattern)
+       {
+               while (*pattern && *string == *pattern)
+               {
+                       string++;
+                       pattern++;
+               }
+
+               return *pattern == 0;
+       }
+
+       PUGI__FN const char_t* find_char(const char_t* s, char_t c)
+       {
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcschr(s, c);
+       #else
+               return strchr(s, c);
+       #endif
+       }
+
+       PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p)
+       {
+       #ifdef PUGIXML_WCHAR_MODE
+               // MSVC6 wcsstr bug workaround (if s is empty it always returns 0)
+               return (*p == 0) ? s : wcsstr(s, p);
+       #else
+               return strstr(s, p);
+       #endif
+       }
+
+       // Converts symbol to lower case, if it is an ASCII one
+       PUGI__FN char_t tolower_ascii(char_t ch)
+       {
+               return static_cast<unsigned int>(ch - 'A') < 26 ? static_cast<char_t>(ch | ' ') : ch;
+       }
+
+       PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc)
+       {
+               if (na.attribute())
+                       return xpath_string_const(na.attribute().value());
+               else
+               {
+                       const xml_node& n = na.node();
+
+                       switch (n.type())
+                       {
+                       case node_pcdata:
+                       case node_cdata:
+                       case node_comment:
+                       case node_pi:
+                               return xpath_string_const(n.value());
+                       
+                       case node_document:
+                       case node_element:
+                       {
+                               xpath_string result;
+
+                               xml_node cur = n.first_child();
+                               
+                               while (cur && cur != n)
+                               {
+                                       if (cur.type() == node_pcdata || cur.type() == node_cdata)
+                                               result.append(xpath_string_const(cur.value()), alloc);
+
+                                       if (cur.first_child())
+                                               cur = cur.first_child();
+                                       else if (cur.next_sibling())
+                                               cur = cur.next_sibling();
+                                       else
+                                       {
+                                               while (!cur.next_sibling() && cur != n)
+                                                       cur = cur.parent();
+
+                                               if (cur != n) cur = cur.next_sibling();
+                                       }
+                               }
+                               
+                               return result;
+                       }
+                       
+                       default:
+                               return xpath_string();
+                       }
+               }
+       }
+       
+       PUGI__FN unsigned int node_height(xml_node n)
+       {
+               unsigned int result = 0;
+               
+               while (n)
+               {
+                       ++result;
+                       n = n.parent();
+               }
+               
+               return result;
+       }
+       
+       PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh)
+       {
+               // normalize heights
+               for (unsigned int i = rh; i < lh; i++) ln = ln.parent();
+               for (unsigned int j = lh; j < rh; j++) rn = rn.parent();
+               
+               // one node is the ancestor of the other
+               if (ln == rn) return lh < rh;
+               
+               // find common ancestor
+               while (ln.parent() != rn.parent())
+               {
+                       ln = ln.parent();
+                       rn = rn.parent();
+               }
+
+               // there is no common ancestor (the shared parent is null), nodes are from different documents
+               if (!ln.parent()) return ln < rn;
+
+               // determine sibling order
+               for (; ln; ln = ln.next_sibling())
+                       if (ln == rn)
+                               return true;
+                               
+               return false;
+       }
+
+       PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node)
+       {
+               while (node && node != parent) node = node.parent();
+
+               return parent && node == parent;
+       }
+
+       PUGI__FN const void* document_order(const xpath_node& xnode)
+       {
+               xml_node_struct* node = xnode.node().internal_object();
+
+               if (node)
+               {
+                       if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) return node->name;
+                       if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) return node->value;
+                       return 0;
+               }
+
+               xml_attribute_struct* attr = xnode.attribute().internal_object();
+
+               if (attr)
+               {
+                       if ((attr->header & xml_memory_page_name_allocated_mask) == 0) return attr->name;
+                       if ((attr->header & xml_memory_page_value_allocated_mask) == 0) return attr->value;
+                       return 0;
+               }
+
+               return 0;
+       }
+       
+       struct document_order_comparator
+       {
+               bool operator()(const xpath_node& lhs, const xpath_node& rhs) const
+               {
+                       // optimized document order based check
+                       const void* lo = document_order(lhs);
+                       const void* ro = document_order(rhs);
+
+                       if (lo && ro) return lo < ro;
+
+                       // slow comparison
+                       xml_node ln = lhs.node(), rn = rhs.node();
+
+                       // compare attributes
+                       if (lhs.attribute() && rhs.attribute())
+                       {
+                               // shared parent
+                               if (lhs.parent() == rhs.parent())
+                               {
+                                       // determine sibling order
+                                       for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute())
+                                               if (a == rhs.attribute())
+                                                       return true;
+                                       
+                                       return false;
+                               }
+                               
+                               // compare attribute parents
+                               ln = lhs.parent();
+                               rn = rhs.parent();
+                       }
+                       else if (lhs.attribute())
+                       {
+                               // attributes go after the parent element
+                               if (lhs.parent() == rhs.node()) return false;
+                               
+                               ln = lhs.parent();
+                       }
+                       else if (rhs.attribute())
+                       {
+                               // attributes go after the parent element
+                               if (rhs.parent() == lhs.node()) return true;
+                               
+                               rn = rhs.parent();
+                       }
+
+                       if (ln == rn) return false;
+                       
+                       unsigned int lh = node_height(ln);
+                       unsigned int rh = node_height(rn);
+                       
+                       return node_is_before(ln, lh, rn, rh);
+               }
+       };
+
+       struct duplicate_comparator
+       {
+               bool operator()(const xpath_node& lhs, const xpath_node& rhs) const
+               {
+                       if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true;
+                       else return rhs.attribute() ? false : lhs.node() < rhs.node();
+               }
+       };
+       
+       PUGI__FN double gen_nan()
+       {
+       #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24))
+               union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1];
+               u[0].i = 0x7fc00000;
+               return u[0].f;
+       #else
+               // fallback
+               const volatile double zero = 0.0;
+               return zero / zero;
+       #endif
+       }
+       
+       PUGI__FN bool is_nan(double value)
+       {
+       #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__)
+               return !!_isnan(value);
+       #elif defined(fpclassify) && defined(FP_NAN)
+               return fpclassify(value) == FP_NAN;
+       #else
+               // fallback
+               const volatile double v = value;
+               return v != v;
+       #endif
+       }
+       
+       PUGI__FN const char_t* convert_number_to_string_special(double value)
+       {
+       #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__)
+               if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0;
+               if (_isnan(value)) return PUGIXML_TEXT("NaN");
+               return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+       #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO)
+               switch (fpclassify(value))
+               {
+               case FP_NAN:
+                       return PUGIXML_TEXT("NaN");
+
+               case FP_INFINITE:
+                       return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+
+               case FP_ZERO:
+                       return PUGIXML_TEXT("0");
+
+               default:
+                       return 0;
+               }
+       #else
+               // fallback
+               const volatile double v = value;
+
+               if (v == 0) return PUGIXML_TEXT("0");
+               if (v != v) return PUGIXML_TEXT("NaN");
+               if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity");
+               return 0;
+       #endif
+       }
+       
+       PUGI__FN bool convert_number_to_boolean(double value)
+       {
+               return (value != 0 && !is_nan(value));
+       }
+       
+       PUGI__FN void truncate_zeros(char* begin, char* end)
+       {
+               while (begin != end && end[-1] == '0') end--;
+
+               *end = 0;
+       }
+
+       // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent
+#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE)
+       PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent)
+       {
+               // get base values
+               int sign, exponent;
+               _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign);
+
+               // truncate redundant zeros
+               truncate_zeros(buffer, buffer + strlen(buffer));
+
+               // fill results
+               *out_mantissa = buffer;
+               *out_exponent = exponent;
+       }
+#else
+       PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent)
+       {
+               // get a scientific notation value with IEEE DBL_DIG decimals
+               sprintf(buffer, "%.*e", DBL_DIG, value);
+               assert(strlen(buffer) < buffer_size);
+               (void)!buffer_size;
+
+               // get the exponent (possibly negative)
+               char* exponent_string = strchr(buffer, 'e');
+               assert(exponent_string);
+
+               int exponent = atoi(exponent_string + 1);
+
+               // extract mantissa string: skip sign
+               char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer;
+               assert(mantissa[0] != '0' && mantissa[1] == '.');
+
+               // divide mantissa by 10 to eliminate integer part
+               mantissa[1] = mantissa[0];
+               mantissa++;
+               exponent++;
+
+               // remove extra mantissa digits and zero-terminate mantissa
+               truncate_zeros(mantissa, exponent_string);
+
+               // fill results
+               *out_mantissa = mantissa;
+               *out_exponent = exponent;
+       }
+#endif
+
+       PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc)
+       {
+               // try special number conversion
+               const char_t* special = convert_number_to_string_special(value);
+               if (special) return xpath_string_const(special);
+
+               // get mantissa + exponent form
+               char mantissa_buffer[32];
+
+               char* mantissa;
+               int exponent;
+               convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent);
+
+               // allocate a buffer of suitable length for the number
+               size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4;
+               char_t* result = static_cast<char_t*>(alloc->allocate(sizeof(char_t) * result_size));
+               assert(result);
+
+               // make the number!
+               char_t* s = result;
+
+               // sign
+               if (value < 0) *s++ = '-';
+
+               // integer part
+               if (exponent <= 0)
+               {
+                       *s++ = '0';
+               }
+               else
+               {
+                       while (exponent > 0)
+                       {
+                               assert(*mantissa == 0 || static_cast<unsigned int>(static_cast<unsigned int>(*mantissa) - '0') <= 9);
+                               *s++ = *mantissa ? *mantissa++ : '0';
+                               exponent--;
+                       }
+               }
+
+               // fractional part
+               if (*mantissa)
+               {
+                       // decimal point
+                       *s++ = '.';
+
+                       // extra zeroes from negative exponent
+                       while (exponent < 0)
+                       {
+                               *s++ = '0';
+                               exponent++;
+                       }
+
+                       // extra mantissa digits
+                       while (*mantissa)
+                       {
+                               assert(static_cast<unsigned int>(*mantissa - '0') <= 9);
+                               *s++ = *mantissa++;
+                       }
+               }
+
+               // zero-terminate
+               assert(s < result + result_size);
+               *s = 0;
+
+               return xpath_string(result, true);
+       }
+       
+       PUGI__FN bool check_string_to_number_format(const char_t* string)
+       {
+               // parse leading whitespace
+               while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string;
+
+               // parse sign
+               if (*string == '-') ++string;
+
+               if (!*string) return false;
+
+               // if there is no integer part, there should be a decimal part with at least one digit
+               if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false;
+
+               // parse integer part
+               while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string;
+
+               // parse decimal part
+               if (*string == '.')
+               {
+                       ++string;
+
+                       while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string;
+               }
+
+               // parse trailing whitespace
+               while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string;
+
+               return *string == 0;
+       }
+
+       PUGI__FN double convert_string_to_number(const char_t* string)
+       {
+               // check string format
+               if (!check_string_to_number_format(string)) return gen_nan();
+
+               // parse string
+       #ifdef PUGIXML_WCHAR_MODE
+               return wcstod(string, 0);
+       #else
+               return atof(string);
+       #endif
+       }
+
+       PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result)
+       {
+               size_t length = static_cast<size_t>(end - begin);
+               char_t* scratch = buffer;
+
+               if (length >= sizeof(buffer) / sizeof(buffer[0]))
+               {
+                       // need to make dummy on-heap copy
+                       scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+                       if (!scratch) return false;
+               }
+
+               // copy string to zero-terminated buffer and perform conversion
+               memcpy(scratch, begin, length * sizeof(char_t));
+               scratch[length] = 0;
+
+               *out_result = convert_string_to_number(scratch);
+
+               // free dummy buffer
+               if (scratch != buffer) xml_memory::deallocate(scratch);
+
+               return true;
+       }
+       
+       PUGI__FN double round_nearest(double value)
+       {
+               return floor(value + 0.5);
+       }
+
+       PUGI__FN double round_nearest_nzero(double value)
+       {
+               // same as round_nearest, but returns -0 for [-0.5, -0]
+               // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0)
+               return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5);
+       }
+       
+       PUGI__FN const char_t* qualified_name(const xpath_node& node)
+       {
+               return node.attribute() ? node.attribute().name() : node.node().name();
+       }
+       
+       PUGI__FN const char_t* local_name(const xpath_node& node)
+       {
+               const char_t* name = qualified_name(node);
+               const char_t* p = find_char(name, ':');
+               
+               return p ? p + 1 : name;
+       }
+
+       struct namespace_uri_predicate
+       {
+               const char_t* prefix;
+               size_t prefix_length;
+
+               namespace_uri_predicate(const char_t* name)
+               {
+                       const char_t* pos = find_char(name, ':');
+
+                       prefix = pos ? name : 0;
+                       prefix_length = pos ? static_cast<size_t>(pos - name) : 0;
+               }
+
+               bool operator()(const xml_attribute& a) const
+               {
+                       const char_t* name = a.name();
+
+                       if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false;
+
+                       return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0;
+               }
+       };
+
+       PUGI__FN const char_t* namespace_uri(const xml_node& node)
+       {
+               namespace_uri_predicate pred = node.name();
+               
+               xml_node p = node;
+               
+               while (p)
+               {
+                       xml_attribute a = p.find_attribute(pred);
+                       
+                       if (a) return a.value();
+                       
+                       p = p.parent();
+               }
+               
+               return PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent)
+       {
+               namespace_uri_predicate pred = attr.name();
+               
+               // Default namespace does not apply to attributes
+               if (!pred.prefix) return PUGIXML_TEXT("");
+               
+               xml_node p = parent;
+               
+               while (p)
+               {
+                       xml_attribute a = p.find_attribute(pred);
+                       
+                       if (a) return a.value();
+                       
+                       p = p.parent();
+               }
+               
+               return PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const char_t* namespace_uri(const xpath_node& node)
+       {
+               return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node());
+       }
+
+       PUGI__FN void normalize_space(char_t* buffer)
+       {
+               char_t* write = buffer;
+
+               for (char_t* it = buffer; *it; )
+               {
+                       char_t ch = *it++;
+
+                       if (PUGI__IS_CHARTYPE(ch, ct_space))
+                       {
+                               // replace whitespace sequence with single space
+                               while (PUGI__IS_CHARTYPE(*it, ct_space)) it++;
+
+                               // avoid leading spaces
+                               if (write != buffer) *write++ = ' ';
+                       }
+                       else *write++ = ch;
+               }
+
+               // remove trailing space
+               if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--;
+
+               // zero-terminate
+               *write = 0;
+       }
+
+       PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to)
+       {
+               size_t to_length = strlength(to);
+
+               char_t* write = buffer;
+
+               while (*buffer)
+               {
+                       PUGI__DMC_VOLATILE char_t ch = *buffer++;
+
+                       const char_t* pos = find_char(from, ch);
+
+                       if (!pos)
+                               *write++ = ch; // do not process
+                       else if (static_cast<size_t>(pos - from) < to_length)
+                               *write++ = to[pos - from]; // replace
+               }
+
+               // zero-terminate
+               *write = 0;
+       }
+
+       struct xpath_variable_boolean: xpath_variable
+       {
+               xpath_variable_boolean(): value(false)
+               {
+               }
+
+               bool value;
+               char_t name[1];
+       };
+
+       struct xpath_variable_number: xpath_variable
+       {
+               xpath_variable_number(): value(0)
+               {
+               }
+
+               double value;
+               char_t name[1];
+       };
+
+       struct xpath_variable_string: xpath_variable
+       {
+               xpath_variable_string(): value(0)
+               {
+               }
+
+               ~xpath_variable_string()
+               {
+                       if (value) xml_memory::deallocate(value);
+               }
+
+               char_t* value;
+               char_t name[1];
+       };
+
+       struct xpath_variable_node_set: xpath_variable
+       {
+               xpath_node_set value;
+               char_t name[1];
+       };
+
+       static const xpath_node_set dummy_node_set;
+
+       PUGI__FN unsigned int hash_string(const char_t* str)
+       {
+               // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time)
+               unsigned int result = 0;
+
+               while (*str)
+               {
+                       result += static_cast<unsigned int>(*str++);
+                       result += result << 10;
+                       result ^= result >> 6;
+               }
+       
+               result += result << 3;
+               result ^= result >> 11;
+               result += result << 15;
+       
+               return result;
+       }
+
+       template <typename T> PUGI__FN T* new_xpath_variable(const char_t* name)
+       {
+               size_t length = strlength(name);
+               if (length == 0) return 0; // empty variable names are invalid
+
+               // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters
+               void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t));
+               if (!memory) return 0;
+
+               T* result = new (memory) T();
+
+               memcpy(result->name, name, (length + 1) * sizeof(char_t));
+
+               return result;
+       }
+
+       PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name)
+       {
+               switch (type)
+               {
+               case xpath_type_node_set:
+                       return new_xpath_variable<xpath_variable_node_set>(name);
+
+               case xpath_type_number:
+                       return new_xpath_variable<xpath_variable_number>(name);
+
+               case xpath_type_string:
+                       return new_xpath_variable<xpath_variable_string>(name);
+
+               case xpath_type_boolean:
+                       return new_xpath_variable<xpath_variable_boolean>(name);
+
+               default:
+                       return 0;
+               }
+       }
+
+       template <typename T> PUGI__FN void delete_xpath_variable(T* var)
+       {
+               var->~T();
+               xml_memory::deallocate(var);
+       }
+
+       PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var)
+       {
+               switch (type)
+               {
+               case xpath_type_node_set:
+                       delete_xpath_variable(static_cast<xpath_variable_node_set*>(var));
+                       break;
+
+               case xpath_type_number:
+                       delete_xpath_variable(static_cast<xpath_variable_number*>(var));
+                       break;
+
+               case xpath_type_string:
+                       delete_xpath_variable(static_cast<xpath_variable_string*>(var));
+                       break;
+
+               case xpath_type_boolean:
+                       delete_xpath_variable(static_cast<xpath_variable_boolean*>(var));
+                       break;
+
+               default:
+                       assert(!"Invalid variable type");
+               }
+       }
+
+       PUGI__FN xpath_variable* get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end)
+       {
+               size_t length = static_cast<size_t>(end - begin);
+               char_t* scratch = buffer;
+
+               if (length >= sizeof(buffer) / sizeof(buffer[0]))
+               {
+                       // need to make dummy on-heap copy
+                       scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
+                       if (!scratch) return 0;
+               }
+
+               // copy string to zero-terminated buffer and perform lookup
+               memcpy(scratch, begin, length * sizeof(char_t));
+               scratch[length] = 0;
+
+               xpath_variable* result = set->get(scratch);
+
+               // free dummy buffer
+               if (scratch != buffer) xml_memory::deallocate(scratch);
+
+               return result;
+       }
+PUGI__NS_END
+
+// Internal node set class
+PUGI__NS_BEGIN
+       PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev)
+       {
+               xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted;
+
+               if (type == xpath_node_set::type_unsorted)
+               {
+                       sort(begin, end, document_order_comparator());
+
+                       type = xpath_node_set::type_sorted;
+               }
+               
+               if (type != order) reverse(begin, end);
+                       
+               return order;
+       }
+
+       PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type)
+       {
+               if (begin == end) return xpath_node();
+
+               switch (type)
+               {
+               case xpath_node_set::type_sorted:
+                       return *begin;
+
+               case xpath_node_set::type_sorted_reverse:
+                       return *(end - 1);
+
+               case xpath_node_set::type_unsorted:
+                       return *min_element(begin, end, document_order_comparator());
+
+               default:
+                       assert(!"Invalid node set type");
+                       return xpath_node();
+               }
+       }
+
+       class xpath_node_set_raw
+       {
+               xpath_node_set::type_t _type;
+
+               xpath_node* _begin;
+               xpath_node* _end;
+               xpath_node* _eos;
+
+       public:
+               xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0)
+               {
+               }
+
+               xpath_node* begin() const
+               {
+                       return _begin;
+               }
+
+               xpath_node* end() const
+               {
+                       return _end;
+               }
+
+               bool empty() const
+               {
+                       return _begin == _end;
+               }
+
+               size_t size() const
+               {
+                       return static_cast<size_t>(_end - _begin);
+               }
+
+               xpath_node first() const
+               {
+                       return xpath_first(_begin, _end, _type);
+               }
+
+               void push_back(const xpath_node& node, xpath_allocator* alloc)
+               {
+                       if (_end == _eos)
+                       {
+                               size_t capacity = static_cast<size_t>(_eos - _begin);
+
+                               // get new capacity (1.5x rule)
+                               size_t new_capacity = capacity + capacity / 2 + 1;
+
+                               // reallocate the old array or allocate a new one
+                               xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node)));
+                               assert(data);
+
+                               // finalize
+                               _begin = data;
+                               _end = data + capacity;
+                               _eos = data + new_capacity;
+                       }
+
+                       *_end++ = node;
+               }
+
+               void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc)
+               {
+                       size_t size_ = static_cast<size_t>(_end - _begin);
+                       size_t capacity = static_cast<size_t>(_eos - _begin);
+                       size_t count = static_cast<size_t>(end_ - begin_);
+
+                       if (size_ + count > capacity)
+                       {
+                               // reallocate the old array or allocate a new one
+                               xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node)));
+                               assert(data);
+
+                               // finalize
+                               _begin = data;
+                               _end = data + size_;
+                               _eos = data + size_ + count;
+                       }
+
+                       memcpy(_end, begin_, count * sizeof(xpath_node));
+                       _end += count;
+               }
+
+               void sort_do()
+               {
+                       _type = xpath_sort(_begin, _end, _type, false);
+               }
+
+               void truncate(xpath_node* pos)
+               {
+                       assert(_begin <= pos && pos <= _end);
+
+                       _end = pos;
+               }
+
+               void remove_duplicates()
+               {
+                       if (_type == xpath_node_set::type_unsorted)
+                               sort(_begin, _end, duplicate_comparator());
+               
+                       _end = unique(_begin, _end);
+               }
+
+               xpath_node_set::type_t type() const
+               {
+                       return _type;
+               }
+
+               void set_type(xpath_node_set::type_t value)
+               {
+                       _type = value;
+               }
+       };
+PUGI__NS_END
+
+PUGI__NS_BEGIN
+       struct xpath_context
+       {
+               xpath_node n;
+               size_t position, size;
+
+               xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_)
+               {
+               }
+       };
+
+       enum lexeme_t
+       {
+               lex_none = 0,
+               lex_equal,
+               lex_not_equal,
+               lex_less,
+               lex_greater,
+               lex_less_or_equal,
+               lex_greater_or_equal,
+               lex_plus,
+               lex_minus,
+               lex_multiply,
+               lex_union,
+               lex_var_ref,
+               lex_open_brace,
+               lex_close_brace,
+               lex_quoted_string,
+               lex_number,
+               lex_slash,
+               lex_double_slash,
+               lex_open_square_brace,
+               lex_close_square_brace,
+               lex_string,
+               lex_comma,
+               lex_axis_attribute,
+               lex_dot,
+               lex_double_dot,
+               lex_double_colon,
+               lex_eof
+       };
+
+       struct xpath_lexer_string
+       {
+               const char_t* begin;
+               const char_t* end;
+
+               xpath_lexer_string(): begin(0), end(0)
+               {
+               }
+
+               bool operator==(const char_t* other) const
+               {
+                       size_t length = static_cast<size_t>(end - begin);
+
+                       return strequalrange(other, begin, length);
+               }
+       };
+
+       class xpath_lexer
+       {
+               const char_t* _cur;
+               const char_t* _cur_lexeme_pos;
+               xpath_lexer_string _cur_lexeme_contents;
+
+               lexeme_t _cur_lexeme;
+
+       public:
+               explicit xpath_lexer(const char_t* query): _cur(query)
+               {
+                       next();
+               }
+               
+               const char_t* state() const
+               {
+                       return _cur;
+               }
+               
+               void next()
+               {
+                       const char_t* cur = _cur;
+
+                       while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur;
+
+                       // save lexeme position for error reporting
+                       _cur_lexeme_pos = cur;
+
+                       switch (*cur)
+                       {
+                       case 0:
+                               _cur_lexeme = lex_eof;
+                               break;
+                       
+                       case '>':
+                               if (*(cur+1) == '=')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_greater_or_equal;
+                               }
+                               else
+                               {
+                                       cur += 1;
+                                       _cur_lexeme = lex_greater;
+                               }
+                               break;
+
+                       case '<':
+                               if (*(cur+1) == '=')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_less_or_equal;
+                               }
+                               else
+                               {
+                                       cur += 1;
+                                       _cur_lexeme = lex_less;
+                               }
+                               break;
+
+                       case '!':
+                               if (*(cur+1) == '=')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_not_equal;
+                               }
+                               else
+                               {
+                                       _cur_lexeme = lex_none;
+                               }
+                               break;
+
+                       case '=':
+                               cur += 1;
+                               _cur_lexeme = lex_equal;
+
+                               break;
+                       
+                       case '+':
+                               cur += 1;
+                               _cur_lexeme = lex_plus;
+
+                               break;
+
+                       case '-':
+                               cur += 1;
+                               _cur_lexeme = lex_minus;
+
+                               break;
+
+                       case '*':
+                               cur += 1;
+                               _cur_lexeme = lex_multiply;
+
+                               break;
+
+                       case '|':
+                               cur += 1;
+                               _cur_lexeme = lex_union;
+
+                               break;
+                       
+                       case '$':
+                               cur += 1;
+
+                               if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol))
+                               {
+                                       _cur_lexeme_contents.begin = cur;
+
+                                       while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+
+                                       if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname
+                                       {
+                                               cur++; // :
+
+                                               while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+                                       }
+
+                                       _cur_lexeme_contents.end = cur;
+                               
+                                       _cur_lexeme = lex_var_ref;
+                               }
+                               else
+                               {
+                                       _cur_lexeme = lex_none;
+                               }
+
+                               break;
+
+                       case '(':
+                               cur += 1;
+                               _cur_lexeme = lex_open_brace;
+
+                               break;
+
+                       case ')':
+                               cur += 1;
+                               _cur_lexeme = lex_close_brace;
+
+                               break;
+                       
+                       case '[':
+                               cur += 1;
+                               _cur_lexeme = lex_open_square_brace;
+
+                               break;
+
+                       case ']':
+                               cur += 1;
+                               _cur_lexeme = lex_close_square_brace;
+
+                               break;
+
+                       case ',':
+                               cur += 1;
+                               _cur_lexeme = lex_comma;
+
+                               break;
+
+                       case '/':
+                               if (*(cur+1) == '/')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_double_slash;
+                               }
+                               else
+                               {
+                                       cur += 1;
+                                       _cur_lexeme = lex_slash;
+                               }
+                               break;
+               
+                       case '.':
+                               if (*(cur+1) == '.')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_double_dot;
+                               }
+                               else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit))
+                               {
+                                       _cur_lexeme_contents.begin = cur; // .
+
+                                       ++cur;
+
+                                       while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+
+                                       _cur_lexeme_contents.end = cur;
+                                       
+                                       _cur_lexeme = lex_number;
+                               }
+                               else
+                               {
+                                       cur += 1;
+                                       _cur_lexeme = lex_dot;
+                               }
+                               break;
+
+                       case '@':
+                               cur += 1;
+                               _cur_lexeme = lex_axis_attribute;
+
+                               break;
+
+                       case '"':
+                       case '\'':
+                       {
+                               char_t terminator = *cur;
+
+                               ++cur;
+
+                               _cur_lexeme_contents.begin = cur;
+                               while (*cur && *cur != terminator) cur++;
+                               _cur_lexeme_contents.end = cur;
+                               
+                               if (!*cur)
+                                       _cur_lexeme = lex_none;
+                               else
+                               {
+                                       cur += 1;
+                                       _cur_lexeme = lex_quoted_string;
+                               }
+
+                               break;
+                       }
+
+                       case ':':
+                               if (*(cur+1) == ':')
+                               {
+                                       cur += 2;
+                                       _cur_lexeme = lex_double_colon;
+                               }
+                               else
+                               {
+                                       _cur_lexeme = lex_none;
+                               }
+                               break;
+
+                       default:
+                               if (PUGI__IS_CHARTYPEX(*cur, ctx_digit))
+                               {
+                                       _cur_lexeme_contents.begin = cur;
+
+                                       while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+                               
+                                       if (*cur == '.')
+                                       {
+                                               cur++;
+
+                                               while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++;
+                                       }
+
+                                       _cur_lexeme_contents.end = cur;
+
+                                       _cur_lexeme = lex_number;
+                               }
+                               else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol))
+                               {
+                                       _cur_lexeme_contents.begin = cur;
+
+                                       while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+
+                                       if (cur[0] == ':')
+                                       {
+                                               if (cur[1] == '*') // namespace test ncname:*
+                                               {
+                                                       cur += 2; // :*
+                                               }
+                                               else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname
+                                               {
+                                                       cur++; // :
+
+                                                       while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++;
+                                               }
+                                       }
+
+                                       _cur_lexeme_contents.end = cur;
+                               
+                                       _cur_lexeme = lex_string;
+                               }
+                               else
+                               {
+                                       _cur_lexeme = lex_none;
+                               }
+                       }
+
+                       _cur = cur;
+               }
+
+               lexeme_t current() const
+               {
+                       return _cur_lexeme;
+               }
+
+               const char_t* current_pos() const
+               {
+                       return _cur_lexeme_pos;
+               }
+
+               const xpath_lexer_string& contents() const
+               {
+                       assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string);
+
+                       return _cur_lexeme_contents;
+               }
+       };
+
+       enum ast_type_t
+       {
+               ast_unknown,
+               ast_op_or,                                              // left or right
+               ast_op_and,                                             // left and right
+               ast_op_equal,                                   // left = right
+               ast_op_not_equal,                               // left != right
+               ast_op_less,                                    // left < right
+               ast_op_greater,                                 // left > right
+               ast_op_less_or_equal,                   // left <= right
+               ast_op_greater_or_equal,                // left >= right
+               ast_op_add,                                             // left + right
+               ast_op_subtract,                                // left - right
+               ast_op_multiply,                                // left * right
+               ast_op_divide,                                  // left / right
+               ast_op_mod,                                             // left % right
+               ast_op_negate,                                  // left - right
+               ast_op_union,                                   // left | right
+               ast_predicate,                                  // apply predicate to set; next points to next predicate
+               ast_filter,                                             // select * from left where right
+               ast_filter_posinv,                              // select * from left where right; proximity position invariant
+               ast_string_constant,                    // string constant
+               ast_number_constant,                    // number constant
+               ast_variable,                                   // variable
+               ast_func_last,                                  // last()
+               ast_func_position,                              // position()
+               ast_func_count,                                 // count(left)
+               ast_func_id,                                    // id(left)
+               ast_func_local_name_0,                  // local-name()
+               ast_func_local_name_1,                  // local-name(left)
+               ast_func_namespace_uri_0,               // namespace-uri()
+               ast_func_namespace_uri_1,               // namespace-uri(left)
+               ast_func_name_0,                                // name()
+               ast_func_name_1,                                // name(left)
+               ast_func_string_0,                              // string()
+               ast_func_string_1,                              // string(left)
+               ast_func_concat,                                // concat(left, right, siblings)
+               ast_func_starts_with,                   // starts_with(left, right)
+               ast_func_contains,                              // contains(left, right)
+               ast_func_substring_before,              // substring-before(left, right)
+               ast_func_substring_after,               // substring-after(left, right)
+               ast_func_substring_2,                   // substring(left, right)
+               ast_func_substring_3,                   // substring(left, right, third)
+               ast_func_string_length_0,               // string-length()
+               ast_func_string_length_1,               // string-length(left)
+               ast_func_normalize_space_0,             // normalize-space()
+               ast_func_normalize_space_1,             // normalize-space(left)
+               ast_func_translate,                             // translate(left, right, third)
+               ast_func_boolean,                               // boolean(left)
+               ast_func_not,                                   // not(left)
+               ast_func_true,                                  // true()
+               ast_func_false,                                 // false()
+               ast_func_lang,                                  // lang(left)
+               ast_func_number_0,                              // number()
+               ast_func_number_1,                              // number(left)
+               ast_func_sum,                                   // sum(left)
+               ast_func_floor,                                 // floor(left)
+               ast_func_ceiling,                               // ceiling(left)
+               ast_func_round,                                 // round(left)
+               ast_step,                                               // process set left with step
+               ast_step_root                                   // select root node
+       };
+
+       enum axis_t
+       {
+               axis_ancestor,
+               axis_ancestor_or_self,
+               axis_attribute,
+               axis_child,
+               axis_descendant,
+               axis_descendant_or_self,
+               axis_following,
+               axis_following_sibling,
+               axis_namespace,
+               axis_parent,
+               axis_preceding,
+               axis_preceding_sibling,
+               axis_self
+       };
+       
+       enum nodetest_t
+       {
+               nodetest_none,
+               nodetest_name,
+               nodetest_type_node,
+               nodetest_type_comment,
+               nodetest_type_pi,
+               nodetest_type_text,
+               nodetest_pi,
+               nodetest_all,
+               nodetest_all_in_namespace
+       };
+
+       template <axis_t N> struct axis_to_type
+       {
+               static const axis_t axis;
+       };
+
+       template <axis_t N> const axis_t axis_to_type<N>::axis = N;
+               
+       class xpath_ast_node
+       {
+       private:
+               // node type
+               char _type;
+               char _rettype;
+
+               // for ast_step / ast_predicate
+               char _axis;
+               char _test;
+
+               // tree node structure
+               xpath_ast_node* _left;
+               xpath_ast_node* _right;
+               xpath_ast_node* _next;
+
+               union
+               {
+                       // value for ast_string_constant
+                       const char_t* string;
+                       // value for ast_number_constant
+                       double number;
+                       // variable for ast_variable
+                       xpath_variable* variable;
+                       // node test for ast_step (node name/namespace/node type/pi target)
+                       const char_t* nodetest;
+               } _data;
+
+               xpath_ast_node(const xpath_ast_node&);
+               xpath_ast_node& operator=(const xpath_ast_node&);
+
+               template <class Comp> static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp)
+               {
+                       xpath_value_type lt = lhs->rettype(), rt = rhs->rettype();
+
+                       if (lt != xpath_type_node_set && rt != xpath_type_node_set)
+                       {
+                               if (lt == xpath_type_boolean || rt == xpath_type_boolean)
+                                       return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack));
+                               else if (lt == xpath_type_number || rt == xpath_type_number)
+                                       return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack));
+                               else if (lt == xpath_type_string || rt == xpath_type_string)
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       xpath_string ls = lhs->eval_string(c, stack);
+                                       xpath_string rs = rhs->eval_string(c, stack);
+
+                                       return comp(ls, rs);
+                               }
+                       }
+                       else if (lt == xpath_type_node_set && rt == xpath_type_node_set)
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
+                               xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+
+                               for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+                                       for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+                                       {
+                                               xpath_allocator_capture cri(stack.result);
+
+                                               if (comp(string_value(*li, stack.result), string_value(*ri, stack.result)))
+                                                       return true;
+                                       }
+
+                               return false;
+                       }
+                       else
+                       {
+                               if (lt == xpath_type_node_set)
+                               {
+                                       swap(lhs, rhs);
+                                       swap(lt, rt);
+                               }
+
+                               if (lt == xpath_type_boolean)
+                                       return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack));
+                               else if (lt == xpath_type_number)
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       double l = lhs->eval_number(c, stack);
+                                       xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+
+                                       for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+                                       {
+                                               xpath_allocator_capture cri(stack.result);
+
+                                               if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+                                                       return true;
+                                       }
+
+                                       return false;
+                               }
+                               else if (lt == xpath_type_string)
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       xpath_string l = lhs->eval_string(c, stack);
+                                       xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+
+                                       for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+                                       {
+                                               xpath_allocator_capture cri(stack.result);
+
+                                               if (comp(l, string_value(*ri, stack.result)))
+                                                       return true;
+                                       }
+
+                                       return false;
+                               }
+                       }
+
+                       assert(!"Wrong types");
+                       return false;
+               }
+
+               template <class Comp> static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp)
+               {
+                       xpath_value_type lt = lhs->rettype(), rt = rhs->rettype();
+
+                       if (lt != xpath_type_node_set && rt != xpath_type_node_set)
+                               return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack));
+                       else if (lt == xpath_type_node_set && rt == xpath_type_node_set)
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
+                               xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+
+                               for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+                               {
+                                       xpath_allocator_capture cri(stack.result);
+
+                                       double l = convert_string_to_number(string_value(*li, stack.result).c_str());
+
+                                       for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+                                       {
+                                               xpath_allocator_capture crii(stack.result);
+
+                                               if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+                                                       return true;
+                                       }
+                               }
+
+                               return false;
+                       }
+                       else if (lt != xpath_type_node_set && rt == xpath_type_node_set)
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               double l = lhs->eval_number(c, stack);
+                               xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+
+                               for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
+                               {
+                                       xpath_allocator_capture cri(stack.result);
+
+                                       if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str())))
+                                               return true;
+                               }
+
+                               return false;
+                       }
+                       else if (lt == xpath_type_node_set && rt != xpath_type_node_set)
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
+                               double r = rhs->eval_number(c, stack);
+
+                               for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
+                               {
+                                       xpath_allocator_capture cri(stack.result);
+
+                                       if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r))
+                                               return true;
+                               }
+
+                               return false;
+                       }
+                       else
+                       {
+                               assert(!"Wrong types");
+                               return false;
+                       }
+               }
+
+               void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack)
+               {
+                       assert(ns.size() >= first);
+
+                       size_t i = 1;
+                       size_t size = ns.size() - first;
+                               
+                       xpath_node* last = ns.begin() + first;
+                               
+                       // remove_if... or well, sort of
+                       for (xpath_node* it = last; it != ns.end(); ++it, ++i)
+                       {
+                               xpath_context c(*it, i, size);
+                       
+                               if (expr->rettype() == xpath_type_number)
+                               {
+                                       if (expr->eval_number(c, stack) == i)
+                                               *last++ = *it;
+                               }
+                               else if (expr->eval_boolean(c, stack))
+                                       *last++ = *it;
+                       }
+                       
+                       ns.truncate(last);
+               }
+
+               void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack)
+               {
+                       if (ns.size() == first) return;
+                       
+                       for (xpath_ast_node* pred = _right; pred; pred = pred->_next)
+                       {
+                               apply_predicate(ns, first, pred->_left, stack);
+                       }
+               }
+
+               void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc)
+               {
+                       if (!a) return;
+
+                       const char_t* name = a.name();
+
+                       // There are no attribute nodes corresponding to attributes that declare namespaces
+                       // That is, "xmlns:..." or "xmlns"
+                       if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return;
+                       
+                       switch (_test)
+                       {
+                       case nodetest_name:
+                               if (strequal(name, _data.nodetest)) ns.push_back(xpath_node(a, parent), alloc);
+                               break;
+                               
+                       case nodetest_type_node:
+                       case nodetest_all:
+                               ns.push_back(xpath_node(a, parent), alloc);
+                               break;
+                               
+                       case nodetest_all_in_namespace:
+                               if (starts_with(name, _data.nodetest))
+                                       ns.push_back(xpath_node(a, parent), alloc);
+                               break;
+                       
+                       default:
+                               ;
+                       }
+               }
+               
+               void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc)
+               {
+                       if (!n) return;
+
+                       switch (_test)
+                       {
+                       case nodetest_name:
+                               if (n.type() == node_element && strequal(n.name(), _data.nodetest)) ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_type_node:
+                               ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_type_comment:
+                               if (n.type() == node_comment)
+                                       ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_type_text:
+                               if (n.type() == node_pcdata || n.type() == node_cdata)
+                                       ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_type_pi:
+                               if (n.type() == node_pi)
+                                       ns.push_back(n, alloc);
+                               break;
+                                                                       
+                       case nodetest_pi:
+                               if (n.type() == node_pi && strequal(n.name(), _data.nodetest))
+                                       ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_all:
+                               if (n.type() == node_element)
+                                       ns.push_back(n, alloc);
+                               break;
+                               
+                       case nodetest_all_in_namespace:
+                               if (n.type() == node_element && starts_with(n.name(), _data.nodetest))
+                                       ns.push_back(n, alloc);
+                               break;
+
+                       default:
+                               assert(!"Unknown axis");
+                       } 
+               }
+
+               template <class T> void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T)
+               {
+                       const axis_t axis = T::axis;
+
+                       switch (axis)
+                       {
+                       case axis_attribute:
+                       {
+                               for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute())
+                                       step_push(ns, a, n, alloc);
+                               
+                               break;
+                       }
+                       
+                       case axis_child:
+                       {
+                               for (xml_node c = n.first_child(); c; c = c.next_sibling())
+                                       step_push(ns, c, alloc);
+                                       
+                               break;
+                       }
+                       
+                       case axis_descendant:
+                       case axis_descendant_or_self:
+                       {
+                               if (axis == axis_descendant_or_self)
+                                       step_push(ns, n, alloc);
+                                       
+                               xml_node cur = n.first_child();
+                               
+                               while (cur && cur != n)
+                               {
+                                       step_push(ns, cur, alloc);
+                                       
+                                       if (cur.first_child())
+                                               cur = cur.first_child();
+                                       else if (cur.next_sibling())
+                                               cur = cur.next_sibling();
+                                       else
+                                       {
+                                               while (!cur.next_sibling() && cur != n)
+                                                       cur = cur.parent();
+                                       
+                                               if (cur != n) cur = cur.next_sibling();
+                                       }
+                               }
+                               
+                               break;
+                       }
+                       
+                       case axis_following_sibling:
+                       {
+                               for (xml_node c = n.next_sibling(); c; c = c.next_sibling())
+                                       step_push(ns, c, alloc);
+                               
+                               break;
+                       }
+                       
+                       case axis_preceding_sibling:
+                       {
+                               for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling())
+                                       step_push(ns, c, alloc);
+                               
+                               break;
+                       }
+                       
+                       case axis_following:
+                       {
+                               xml_node cur = n;
+
+                               // exit from this node so that we don't include descendants
+                               while (cur && !cur.next_sibling()) cur = cur.parent();
+                               cur = cur.next_sibling();
+
+                               for (;;)
+                               {
+                                       step_push(ns, cur, alloc);
+
+                                       if (cur.first_child())
+                                               cur = cur.first_child();
+                                       else if (cur.next_sibling())
+                                               cur = cur.next_sibling();
+                                       else
+                                       {
+                                               while (cur && !cur.next_sibling()) cur = cur.parent();
+                                               cur = cur.next_sibling();
+
+                                               if (!cur) break;
+                                       }
+                               }
+
+                               break;
+                       }
+
+                       case axis_preceding:
+                       {
+                               xml_node cur = n;
+
+                               while (cur && !cur.previous_sibling()) cur = cur.parent();
+                               cur = cur.previous_sibling();
+
+                               for (;;)
+                               {
+                                       if (cur.last_child())
+                                               cur = cur.last_child();
+                                       else
+                                       {
+                                               // leaf node, can't be ancestor
+                                               step_push(ns, cur, alloc);
+
+                                               if (cur.previous_sibling())
+                                                       cur = cur.previous_sibling();
+                                               else
+                                               {
+                                                       do 
+                                                       {
+                                                               cur = cur.parent();
+                                                               if (!cur) break;
+
+                                                               if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc);
+                                                       }
+                                                       while (!cur.previous_sibling());
+
+                                                       cur = cur.previous_sibling();
+
+                                                       if (!cur) break;
+                                               }
+                                       }
+                               }
+
+                               break;
+                       }
+                       
+                       case axis_ancestor:
+                       case axis_ancestor_or_self:
+                       {
+                               if (axis == axis_ancestor_or_self)
+                                       step_push(ns, n, alloc);
+
+                               xml_node cur = n.parent();
+                               
+                               while (cur)
+                               {
+                                       step_push(ns, cur, alloc);
+                                       
+                                       cur = cur.parent();
+                               }
+                               
+                               break;
+                       }
+
+                       case axis_self:
+                       {
+                               step_push(ns, n, alloc);
+
+                               break;
+                       }
+
+                       case axis_parent:
+                       {
+                               if (n.parent()) step_push(ns, n.parent(), alloc);
+
+                               break;
+                       }
+                               
+                       default:
+                               assert(!"Unimplemented axis");
+                       }
+               }
+               
+               template <class T> void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v)
+               {
+                       const axis_t axis = T::axis;
+
+                       switch (axis)
+                       {
+                       case axis_ancestor:
+                       case axis_ancestor_or_self:
+                       {
+                               if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test
+                                       step_push(ns, a, p, alloc);
+
+                               xml_node cur = p;
+                               
+                               while (cur)
+                               {
+                                       step_push(ns, cur, alloc);
+                                       
+                                       cur = cur.parent();
+                               }
+                               
+                               break;
+                       }
+
+                       case axis_descendant_or_self:
+                       case axis_self:
+                       {
+                               if (_test == nodetest_type_node) // reject attributes based on principal node type test
+                                       step_push(ns, a, p, alloc);
+
+                               break;
+                       }
+
+                       case axis_following:
+                       {
+                               xml_node cur = p;
+                               
+                               for (;;)
+                               {
+                                       if (cur.first_child())
+                                               cur = cur.first_child();
+                                       else if (cur.next_sibling())
+                                               cur = cur.next_sibling();
+                                       else
+                                       {
+                                               while (cur && !cur.next_sibling()) cur = cur.parent();
+                                               cur = cur.next_sibling();
+                                               
+                                               if (!cur) break;
+                                       }
+
+                                       step_push(ns, cur, alloc);
+                               }
+
+                               break;
+                       }
+
+                       case axis_parent:
+                       {
+                               step_push(ns, p, alloc);
+
+                               break;
+                       }
+
+                       case axis_preceding:
+                       {
+                               // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding
+                               step_fill(ns, p, alloc, v);
+                               break;
+                       }
+                       
+                       default:
+                               assert(!"Unimplemented axis");
+                       }
+               }
+               
+               template <class T> xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v)
+               {
+                       const axis_t axis = T::axis;
+                       bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self);
+
+                       xpath_node_set_raw ns;
+                       ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted);
+
+                       if (_left)
+                       {
+                               xpath_node_set_raw s = _left->eval_node_set(c, stack);
+
+                               // self axis preserves the original order
+                               if (axis == axis_self) ns.set_type(s.type());
+
+                               for (const xpath_node* it = s.begin(); it != s.end(); ++it)
+                               {
+                                       size_t size = ns.size();
+
+                                       // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes
+                                       if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted);
+                                       
+                                       if (it->node())
+                                               step_fill(ns, it->node(), stack.result, v);
+                                       else if (attributes)
+                                               step_fill(ns, it->attribute(), it->parent(), stack.result, v);
+                                               
+                                       apply_predicates(ns, size, stack);
+                               }
+                       }
+                       else
+                       {
+                               if (c.n.node())
+                                       step_fill(ns, c.n.node(), stack.result, v);
+                               else if (attributes)
+                                       step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v);
+                               
+                               apply_predicates(ns, 0, stack);
+                       }
+
+                       // child, attribute and self axes always generate unique set of nodes
+                       // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice
+                       if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted)
+                               ns.remove_duplicates();
+
+                       return ns;
+               }
+               
+       public:
+               xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value):
+                       _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0)
+               {
+                       assert(type == ast_string_constant);
+                       _data.string = value;
+               }
+
+               xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value):
+                       _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0)
+               {
+                       assert(type == ast_number_constant);
+                       _data.number = value;
+               }
+               
+               xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value):
+                       _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0)
+               {
+                       assert(type == ast_variable);
+                       _data.variable = value;
+               }
+               
+               xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0):
+                       _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0)
+               {
+               }
+
+               xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents):
+                       _type(static_cast<char>(type)), _rettype(xpath_type_node_set), _axis(static_cast<char>(axis)), _test(static_cast<char>(test)), _left(left), _right(0), _next(0)
+               {
+                       _data.nodetest = contents;
+               }
+
+               void set_next(xpath_ast_node* value)
+               {
+                       _next = value;
+               }
+
+               void set_right(xpath_ast_node* value)
+               {
+                       _right = value;
+               }
+
+               bool eval_boolean(const xpath_context& c, const xpath_stack& stack)
+               {
+                       switch (_type)
+                       {
+                       case ast_op_or:
+                               return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack);
+                               
+                       case ast_op_and:
+                               return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack);
+                               
+                       case ast_op_equal:
+                               return compare_eq(_left, _right, c, stack, equal_to());
+
+                       case ast_op_not_equal:
+                               return compare_eq(_left, _right, c, stack, not_equal_to());
+       
+                       case ast_op_less:
+                               return compare_rel(_left, _right, c, stack, less());
+                       
+                       case ast_op_greater:
+                               return compare_rel(_right, _left, c, stack, less());
+
+                       case ast_op_less_or_equal:
+                               return compare_rel(_left, _right, c, stack, less_equal());
+                       
+                       case ast_op_greater_or_equal:
+                               return compare_rel(_right, _left, c, stack, less_equal());
+
+                       case ast_func_starts_with:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_string lr = _left->eval_string(c, stack);
+                               xpath_string rr = _right->eval_string(c, stack);
+
+                               return starts_with(lr.c_str(), rr.c_str());
+                       }
+
+                       case ast_func_contains:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_string lr = _left->eval_string(c, stack);
+                               xpath_string rr = _right->eval_string(c, stack);
+
+                               return find_substring(lr.c_str(), rr.c_str()) != 0;
+                       }
+
+                       case ast_func_boolean:
+                               return _left->eval_boolean(c, stack);
+                               
+                       case ast_func_not:
+                               return !_left->eval_boolean(c, stack);
+                               
+                       case ast_func_true:
+                               return true;
+                               
+                       case ast_func_false:
+                               return false;
+
+                       case ast_func_lang:
+                       {
+                               if (c.n.attribute()) return false;
+                               
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_string lang = _left->eval_string(c, stack);
+                               
+                               for (xml_node n = c.n.node(); n; n = n.parent())
+                               {
+                                       xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang"));
+                                       
+                                       if (a)
+                                       {
+                                               const char_t* value = a.value();
+                                               
+                                               // strnicmp / strncasecmp is not portable
+                                               for (const char_t* lit = lang.c_str(); *lit; ++lit)
+                                               {
+                                                       if (tolower_ascii(*lit) != tolower_ascii(*value)) return false;
+                                                       ++value;
+                                               }
+                                               
+                                               return *value == 0 || *value == '-';
+                                       }
+                               }
+                               
+                               return false;
+                       }
+
+                       case ast_variable:
+                       {
+                               assert(_rettype == _data.variable->type());
+
+                               if (_rettype == xpath_type_boolean)
+                                       return _data.variable->get_boolean();
+
+                               // fallthrough to type conversion
+                       }
+
+                       default:
+                       {
+                               switch (_rettype)
+                               {
+                               case xpath_type_number:
+                                       return convert_number_to_boolean(eval_number(c, stack));
+                                       
+                               case xpath_type_string:
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       return !eval_string(c, stack).empty();
+                               }
+                                       
+                               case xpath_type_node_set:                               
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       return !eval_node_set(c, stack).empty();
+                               }
+
+                               default:
+                                       assert(!"Wrong expression for return type boolean");
+                                       return false;
+                               }
+                       }
+                       }
+               }
+
+               double eval_number(const xpath_context& c, const xpath_stack& stack)
+               {
+                       switch (_type)
+                       {
+                       case ast_op_add:
+                               return _left->eval_number(c, stack) + _right->eval_number(c, stack);
+                               
+                       case ast_op_subtract:
+                               return _left->eval_number(c, stack) - _right->eval_number(c, stack);
+
+                       case ast_op_multiply:
+                               return _left->eval_number(c, stack) * _right->eval_number(c, stack);
+
+                       case ast_op_divide:
+                               return _left->eval_number(c, stack) / _right->eval_number(c, stack);
+
+                       case ast_op_mod:
+                               return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack));
+
+                       case ast_op_negate:
+                               return -_left->eval_number(c, stack);
+
+                       case ast_number_constant:
+                               return _data.number;
+
+                       case ast_func_last:
+                               return static_cast<double>(c.size);
+                       
+                       case ast_func_position:
+                               return static_cast<double>(c.position);
+
+                       case ast_func_count:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               return static_cast<double>(_left->eval_node_set(c, stack).size());
+                       }
+                       
+                       case ast_func_string_length_0:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               return static_cast<double>(string_value(c.n, stack.result).length());
+                       }
+                       
+                       case ast_func_string_length_1:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               return static_cast<double>(_left->eval_string(c, stack).length());
+                       }
+                       
+                       case ast_func_number_0:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               return convert_string_to_number(string_value(c.n, stack.result).c_str());
+                       }
+                       
+                       case ast_func_number_1:
+                               return _left->eval_number(c, stack);
+
+                       case ast_func_sum:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               double r = 0;
+                               
+                               xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+                               
+                               for (const xpath_node* it = ns.begin(); it != ns.end(); ++it)
+                               {
+                                       xpath_allocator_capture cri(stack.result);
+
+                                       r += convert_string_to_number(string_value(*it, stack.result).c_str());
+                               }
+                       
+                               return r;
+                       }
+
+                       case ast_func_floor:
+                       {
+                               double r = _left->eval_number(c, stack);
+                               
+                               return r == r ? floor(r) : r;
+                       }
+
+                       case ast_func_ceiling:
+                       {
+                               double r = _left->eval_number(c, stack);
+                               
+                               return r == r ? ceil(r) : r;
+                       }
+
+                       case ast_func_round:
+                               return round_nearest_nzero(_left->eval_number(c, stack));
+                       
+                       case ast_variable:
+                       {
+                               assert(_rettype == _data.variable->type());
+
+                               if (_rettype == xpath_type_number)
+                                       return _data.variable->get_number();
+
+                               // fallthrough to type conversion
+                       }
+
+                       default:
+                       {
+                               switch (_rettype)
+                               {
+                               case xpath_type_boolean:
+                                       return eval_boolean(c, stack) ? 1 : 0;
+                                       
+                               case xpath_type_string:
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       return convert_string_to_number(eval_string(c, stack).c_str());
+                               }
+                                       
+                               case xpath_type_node_set:
+                               {
+                                       xpath_allocator_capture cr(stack.result);
+
+                                       return convert_string_to_number(eval_string(c, stack).c_str());
+                               }
+                                       
+                               default:
+                                       assert(!"Wrong expression for return type number");
+                                       return 0;
+                               }
+                               
+                       }
+                       }
+               }
+               
+               xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack)
+               {
+                       assert(_type == ast_func_concat);
+
+                       xpath_allocator_capture ct(stack.temp);
+
+                       // count the string number
+                       size_t count = 1;
+                       for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++;
+
+                       // gather all strings
+                       xpath_string static_buffer[4];
+                       xpath_string* buffer = static_buffer;
+
+                       // allocate on-heap for large concats
+                       if (count > sizeof(static_buffer) / sizeof(static_buffer[0]))
+                       {
+                               buffer = static_cast<xpath_string*>(stack.temp->allocate(count * sizeof(xpath_string)));
+                               assert(buffer);
+                       }
+
+                       // evaluate all strings to temporary stack
+                       xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                       buffer[0] = _left->eval_string(c, swapped_stack);
+
+                       size_t pos = 1;
+                       for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack);
+                       assert(pos == count);
+
+                       // get total length
+                       size_t length = 0;
+                       for (size_t i = 0; i < count; ++i) length += buffer[i].length();
+
+                       // create final string
+                       char_t* result = static_cast<char_t*>(stack.result->allocate((length + 1) * sizeof(char_t)));
+                       assert(result);
+
+                       char_t* ri = result;
+
+                       for (size_t j = 0; j < count; ++j)
+                               for (const char_t* bi = buffer[j].c_str(); *bi; ++bi)
+                                       *ri++ = *bi;
+
+                       *ri = 0;
+
+                       return xpath_string(result, true);
+               }
+
+               xpath_string eval_string(const xpath_context& c, const xpath_stack& stack)
+               {
+                       switch (_type)
+                       {
+                       case ast_string_constant:
+                               return xpath_string_const(_data.string);
+                       
+                       case ast_func_local_name_0:
+                       {
+                               xpath_node na = c.n;
+                               
+                               return xpath_string_const(local_name(na));
+                       }
+
+                       case ast_func_local_name_1:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+                               xpath_node na = ns.first();
+                               
+                               return xpath_string_const(local_name(na));
+                       }
+
+                       case ast_func_name_0:
+                       {
+                               xpath_node na = c.n;
+                               
+                               return xpath_string_const(qualified_name(na));
+                       }
+
+                       case ast_func_name_1:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+                               xpath_node na = ns.first();
+                               
+                               return xpath_string_const(qualified_name(na));
+                       }
+
+                       case ast_func_namespace_uri_0:
+                       {
+                               xpath_node na = c.n;
+                               
+                               return xpath_string_const(namespace_uri(na));
+                       }
+
+                       case ast_func_namespace_uri_1:
+                       {
+                               xpath_allocator_capture cr(stack.result);
+
+                               xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+                               xpath_node na = ns.first();
+                               
+                               return xpath_string_const(namespace_uri(na));
+                       }
+
+                       case ast_func_string_0:
+                               return string_value(c.n, stack.result);
+
+                       case ast_func_string_1:
+                               return _left->eval_string(c, stack);
+
+                       case ast_func_concat:
+                               return eval_string_concat(c, stack);
+
+                       case ast_func_substring_before:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_string s = _left->eval_string(c, swapped_stack);
+                               xpath_string p = _right->eval_string(c, swapped_stack);
+
+                               const char_t* pos = find_substring(s.c_str(), p.c_str());
+                               
+                               return pos ? xpath_string(s.c_str(), pos, stack.result) : xpath_string();
+                       }
+                       
+                       case ast_func_substring_after:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_string s = _left->eval_string(c, swapped_stack);
+                               xpath_string p = _right->eval_string(c, swapped_stack);
+                               
+                               const char_t* pos = find_substring(s.c_str(), p.c_str());
+                               if (!pos) return xpath_string();
+
+                               const char_t* result = pos + p.length();
+
+                               return s.uses_heap() ? xpath_string(result, stack.result) : xpath_string_const(result);
+                       }
+
+                       case ast_func_substring_2:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_string s = _left->eval_string(c, swapped_stack);
+                               size_t s_length = s.length();
+
+                               double first = round_nearest(_right->eval_number(c, stack));
+                               
+                               if (is_nan(first)) return xpath_string(); // NaN
+                               else if (first >= s_length + 1) return xpath_string();
+                               
+                               size_t pos = first < 1 ? 1 : static_cast<size_t>(first);
+                               assert(1 <= pos && pos <= s_length + 1);
+
+                               const char_t* rbegin = s.c_str() + (pos - 1);
+                               
+                               return s.uses_heap() ? xpath_string(rbegin, stack.result) : xpath_string_const(rbegin);
+                       }
+                       
+                       case ast_func_substring_3:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_string s = _left->eval_string(c, swapped_stack);
+                               size_t s_length = s.length();
+
+                               double first = round_nearest(_right->eval_number(c, stack));
+                               double last = first + round_nearest(_right->_next->eval_number(c, stack));
+                               
+                               if (is_nan(first) || is_nan(last)) return xpath_string();
+                               else if (first >= s_length + 1) return xpath_string();
+                               else if (first >= last) return xpath_string();
+                               else if (last < 1) return xpath_string();
+                               
+                               size_t pos = first < 1 ? 1 : static_cast<size_t>(first);
+                               size_t end = last >= s_length + 1 ? s_length + 1 : static_cast<size_t>(last);
+
+                               assert(1 <= pos && pos <= end && end <= s_length + 1);
+                               const char_t* rbegin = s.c_str() + (pos - 1);
+                               const char_t* rend = s.c_str() + (end - 1);
+
+                               return (end == s_length + 1 && !s.uses_heap()) ? xpath_string_const(rbegin) : xpath_string(rbegin, rend, stack.result);
+                       }
+
+                       case ast_func_normalize_space_0:
+                       {
+                               xpath_string s = string_value(c.n, stack.result);
+
+                               normalize_space(s.data(stack.result));
+
+                               return s;
+                       }
+
+                       case ast_func_normalize_space_1:
+                       {
+                               xpath_string s = _left->eval_string(c, stack);
+
+                               normalize_space(s.data(stack.result));
+                       
+                               return s;
+                       }
+
+                       case ast_func_translate:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_string s = _left->eval_string(c, stack);
+                               xpath_string from = _right->eval_string(c, swapped_stack);
+                               xpath_string to = _right->_next->eval_string(c, swapped_stack);
+
+                               translate(s.data(stack.result), from.c_str(), to.c_str());
+
+                               return s;
+                       }
+
+                       case ast_variable:
+                       {
+                               assert(_rettype == _data.variable->type());
+
+                               if (_rettype == xpath_type_string)
+                                       return xpath_string_const(_data.variable->get_string());
+
+                               // fallthrough to type conversion
+                       }
+
+                       default:
+                       {
+                               switch (_rettype)
+                               {
+                               case xpath_type_boolean:
+                                       return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"));
+                                       
+                               case xpath_type_number:
+                                       return convert_number_to_string(eval_number(c, stack), stack.result);
+                                       
+                               case xpath_type_node_set:
+                               {
+                                       xpath_allocator_capture cr(stack.temp);
+
+                                       xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                                       xpath_node_set_raw ns = eval_node_set(c, swapped_stack);
+                                       return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result);
+                               }
+                               
+                               default:
+                                       assert(!"Wrong expression for return type string");
+                                       return xpath_string();
+                               }
+                       }
+                       }
+               }
+
+               xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack)
+               {
+                       switch (_type)
+                       {
+                       case ast_op_union:
+                       {
+                               xpath_allocator_capture cr(stack.temp);
+
+                               xpath_stack swapped_stack = {stack.temp, stack.result};
+
+                               xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack);
+                               xpath_node_set_raw rs = _right->eval_node_set(c, stack);
+                               
+                               // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother
+                               rs.set_type(xpath_node_set::type_unsorted);
+
+                               rs.append(ls.begin(), ls.end(), stack.result);
+                               rs.remove_duplicates();
+                               
+                               return rs;
+                       }
+
+                       case ast_filter:
+                       case ast_filter_posinv:
+                       {
+                               xpath_node_set_raw set = _left->eval_node_set(c, stack);
+
+                               // either expression is a number or it contains position() call; sort by document order
+                               if (_type == ast_filter) set.sort_do();
+
+                               apply_predicate(set, 0, _right, stack);
+                       
+                               return set;
+                       }
+                       
+                       case ast_func_id:
+                               return xpath_node_set_raw();
+                       
+                       case ast_step:
+                       {
+                               switch (_axis)
+                               {
+                               case axis_ancestor:
+                                       return step_do(c, stack, axis_to_type<axis_ancestor>());
+                                       
+                               case axis_ancestor_or_self:
+                                       return step_do(c, stack, axis_to_type<axis_ancestor_or_self>());
+
+                               case axis_attribute:
+                                       return step_do(c, stack, axis_to_type<axis_attribute>());
+
+                               case axis_child:
+                                       return step_do(c, stack, axis_to_type<axis_child>());
+                               
+                               case axis_descendant:
+                                       return step_do(c, stack, axis_to_type<axis_descendant>());
+
+                               case axis_descendant_or_self:
+                                       return step_do(c, stack, axis_to_type<axis_descendant_or_self>());
+
+                               case axis_following:
+                                       return step_do(c, stack, axis_to_type<axis_following>());
+                               
+                               case axis_following_sibling:
+                                       return step_do(c, stack, axis_to_type<axis_following_sibling>());
+                               
+                               case axis_namespace:
+                                       // namespaced axis is not supported
+                                       return xpath_node_set_raw();
+                               
+                               case axis_parent:
+                                       return step_do(c, stack, axis_to_type<axis_parent>());
+                               
+                               case axis_preceding:
+                                       return step_do(c, stack, axis_to_type<axis_preceding>());
+
+                               case axis_preceding_sibling:
+                                       return step_do(c, stack, axis_to_type<axis_preceding_sibling>());
+                               
+                               case axis_self:
+                                       return step_do(c, stack, axis_to_type<axis_self>());
+
+                               default:
+                                       assert(!"Unknown axis");
+                                       return xpath_node_set_raw();
+                               }
+                       }
+
+                       case ast_step_root:
+                       {
+                               assert(!_right); // root step can't have any predicates
+
+                               xpath_node_set_raw ns;
+
+                               ns.set_type(xpath_node_set::type_sorted);
+
+                               if (c.n.node()) ns.push_back(c.n.node().root(), stack.result);
+                               else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result);
+
+                               return ns;
+                       }
+
+                       case ast_variable:
+                       {
+                               assert(_rettype == _data.variable->type());
+
+                               if (_rettype == xpath_type_node_set)
+                               {
+                                       const xpath_node_set& s = _data.variable->get_node_set();
+
+                                       xpath_node_set_raw ns;
+
+                                       ns.set_type(s.type());
+                                       ns.append(s.begin(), s.end(), stack.result);
+
+                                       return ns;
+                               }
+
+                               // fallthrough to type conversion
+                       }
+
+                       default:
+                               assert(!"Wrong expression for return type node set");
+                               return xpath_node_set_raw();
+                       }
+               }
+               
+               bool is_posinv()
+               {
+                       switch (_type)
+                       {
+                       case ast_func_position:
+                               return false;
+
+                       case ast_string_constant:
+                       case ast_number_constant:
+                       case ast_variable:
+                               return true;
+
+                       case ast_step:
+                       case ast_step_root:
+                               return true;
+
+                       case ast_predicate:
+                       case ast_filter:
+                       case ast_filter_posinv:
+                               return true;
+
+                       default:
+                               if (_left && !_left->is_posinv()) return false;
+                               
+                               for (xpath_ast_node* n = _right; n; n = n->_next)
+                                       if (!n->is_posinv()) return false;
+                                       
+                               return true;
+                       }
+               }
+
+               xpath_value_type rettype() const
+               {
+                       return static_cast<xpath_value_type>(_rettype);
+               }
+       };
+
+       struct xpath_parser
+       {
+               xpath_allocator* _alloc;
+               xpath_lexer _lexer;
+
+               const char_t* _query;
+               xpath_variable_set* _variables;
+
+               xpath_parse_result* _result;
+
+               char_t _scratch[32];
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               jmp_buf _error_handler;
+       #endif
+
+               void throw_error(const char* message)
+               {
+                       _result->error = message;
+                       _result->offset = _lexer.current_pos() - _query;
+
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       longjmp(_error_handler, 1);
+               #else
+                       throw xpath_exception(*_result);
+               #endif
+               }
+
+               void throw_error_oom()
+               {
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       throw_error("Out of memory");
+               #else
+                       throw std::bad_alloc();
+               #endif
+               }
+
+               void* alloc_node()
+               {
+                       void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node));
+
+                       if (!result) throw_error_oom();
+
+                       return result;
+               }
+
+               const char_t* alloc_string(const xpath_lexer_string& value)
+               {
+                       if (value.begin)
+                       {
+                               size_t length = static_cast<size_t>(value.end - value.begin);
+
+                               char_t* c = static_cast<char_t*>(_alloc->allocate_nothrow((length + 1) * sizeof(char_t)));
+                               if (!c) throw_error_oom();
+                               assert(c); // workaround for clang static analysis
+
+                               memcpy(c, value.begin, length * sizeof(char_t));
+                               c[length] = 0;
+
+                               return c;
+                       }
+                       else return 0;
+               }
+
+               xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2])
+               {
+                       assert(argc <= 1);
+
+                       if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set");
+
+                       return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]);
+               }
+
+               xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2])
+               {
+                       switch (name.begin[0])
+                       {
+                       case 'b':
+                               if (name == PUGIXML_TEXT("boolean") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]);
+                                       
+                               break;
+                       
+                       case 'c':
+                               if (name == PUGIXML_TEXT("count") && argc == 1)
+                               {
+                                       if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set");
+                                       return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]);
+                               }
+                               else if (name == PUGIXML_TEXT("contains") && argc == 2)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_boolean, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("concat") && argc >= 2)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("ceiling") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]);
+                                       
+                               break;
+                       
+                       case 'f':
+                               if (name == PUGIXML_TEXT("false") && argc == 0)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean);
+                               else if (name == PUGIXML_TEXT("floor") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]);
+                                       
+                               break;
+                       
+                       case 'i':
+                               if (name == PUGIXML_TEXT("id") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]);
+                                       
+                               break;
+                       
+                       case 'l':
+                               if (name == PUGIXML_TEXT("last") && argc == 0)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number);
+                               else if (name == PUGIXML_TEXT("lang") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]);
+                               else if (name == PUGIXML_TEXT("local-name") && argc <= 1)
+                                       return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args);
+                       
+                               break;
+                       
+                       case 'n':
+                               if (name == PUGIXML_TEXT("name") && argc <= 1)
+                                       return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args);
+                               else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1)
+                                       return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args);
+                               else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1)
+                                       return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("not") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]);
+                               else if (name == PUGIXML_TEXT("number") && argc <= 1)
+                                       return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]);
+                       
+                               break;
+                       
+                       case 'p':
+                               if (name == PUGIXML_TEXT("position") && argc == 0)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number);
+                               
+                               break;
+                       
+                       case 'r':
+                               if (name == PUGIXML_TEXT("round") && argc == 1)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]);
+
+                               break;
+                       
+                       case 's':
+                               if (name == PUGIXML_TEXT("string") && argc <= 1)
+                                       return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]);
+                               else if (name == PUGIXML_TEXT("string-length") && argc <= 1)
+                                       return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]);
+                               else if (name == PUGIXML_TEXT("starts-with") && argc == 2)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("substring-before") && argc == 2)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("substring-after") && argc == 2)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3))
+                                       return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("sum") && argc == 1)
+                               {
+                                       if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set");
+                                       return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]);
+                               }
+
+                               break;
+                       
+                       case 't':
+                               if (name == PUGIXML_TEXT("translate") && argc == 3)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]);
+                               else if (name == PUGIXML_TEXT("true") && argc == 0)
+                                       return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean);
+                                       
+                               break;
+
+                       default:
+                               break;
+                       }
+
+                       throw_error("Unrecognized function or wrong parameter count");
+
+                       return 0;
+               }
+
+               axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified)
+               {
+                       specified = true;
+
+                       switch (name.begin[0])
+                       {
+                       case 'a':
+                               if (name == PUGIXML_TEXT("ancestor"))
+                                       return axis_ancestor;
+                               else if (name == PUGIXML_TEXT("ancestor-or-self"))
+                                       return axis_ancestor_or_self;
+                               else if (name == PUGIXML_TEXT("attribute"))
+                                       return axis_attribute;
+                               
+                               break;
+                       
+                       case 'c':
+                               if (name == PUGIXML_TEXT("child"))
+                                       return axis_child;
+                               
+                               break;
+                       
+                       case 'd':
+                               if (name == PUGIXML_TEXT("descendant"))
+                                       return axis_descendant;
+                               else if (name == PUGIXML_TEXT("descendant-or-self"))
+                                       return axis_descendant_or_self;
+                               
+                               break;
+                       
+                       case 'f':
+                               if (name == PUGIXML_TEXT("following"))
+                                       return axis_following;
+                               else if (name == PUGIXML_TEXT("following-sibling"))
+                                       return axis_following_sibling;
+                               
+                               break;
+                       
+                       case 'n':
+                               if (name == PUGIXML_TEXT("namespace"))
+                                       return axis_namespace;
+                               
+                               break;
+                       
+                       case 'p':
+                               if (name == PUGIXML_TEXT("parent"))
+                                       return axis_parent;
+                               else if (name == PUGIXML_TEXT("preceding"))
+                                       return axis_preceding;
+                               else if (name == PUGIXML_TEXT("preceding-sibling"))
+                                       return axis_preceding_sibling;
+                               
+                               break;
+                       
+                       case 's':
+                               if (name == PUGIXML_TEXT("self"))
+                                       return axis_self;
+                               
+                               break;
+
+                       default:
+                               break;
+                       }
+
+                       specified = false;
+                       return axis_child;
+               }
+
+               nodetest_t parse_node_test_type(const xpath_lexer_string& name)
+               {
+                       switch (name.begin[0])
+                       {
+                       case 'c':
+                               if (name == PUGIXML_TEXT("comment"))
+                                       return nodetest_type_comment;
+
+                               break;
+
+                       case 'n':
+                               if (name == PUGIXML_TEXT("node"))
+                                       return nodetest_type_node;
+
+                               break;
+
+                       case 'p':
+                               if (name == PUGIXML_TEXT("processing-instruction"))
+                                       return nodetest_type_pi;
+
+                               break;
+
+                       case 't':
+                               if (name == PUGIXML_TEXT("text"))
+                                       return nodetest_type_text;
+
+                               break;
+                       
+                       default:
+                               break;
+                       }
+
+                       return nodetest_none;
+               }
+
+               // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
+               xpath_ast_node* parse_primary_expression()
+               {
+                       switch (_lexer.current())
+                       {
+                       case lex_var_ref:
+                       {
+                               xpath_lexer_string name = _lexer.contents();
+
+                               if (!_variables)
+                                       throw_error("Unknown variable: variable set is not provided");
+
+                               xpath_variable* var = get_variable_scratch(_scratch, _variables, name.begin, name.end);
+
+                               if (!var)
+                                       throw_error("Unknown variable: variable set does not contain the given name");
+
+                               _lexer.next();
+
+                               return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var);
+                       }
+
+                       case lex_open_brace:
+                       {
+                               _lexer.next();
+
+                               xpath_ast_node* n = parse_expression();
+
+                               if (_lexer.current() != lex_close_brace)
+                                       throw_error("Unmatched braces");
+
+                               _lexer.next();
+
+                               return n;
+                       }
+
+                       case lex_quoted_string:
+                       {
+                               const char_t* value = alloc_string(_lexer.contents());
+
+                               xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value);
+                               _lexer.next();
+
+                               return n;
+                       }
+
+                       case lex_number:
+                       {
+                               double value = 0;
+
+                               if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value))
+                                       throw_error_oom();
+
+                               xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value);
+                               _lexer.next();
+
+                               return n;
+                       }
+
+                       case lex_string:
+                       {
+                               xpath_ast_node* args[2] = {0};
+                               size_t argc = 0;
+                               
+                               xpath_lexer_string function = _lexer.contents();
+                               _lexer.next();
+                               
+                               xpath_ast_node* last_arg = 0;
+                               
+                               if (_lexer.current() != lex_open_brace)
+                                       throw_error("Unrecognized function call");
+                               _lexer.next();
+
+                               if (_lexer.current() != lex_close_brace)
+                                       args[argc++] = parse_expression();
+
+                               while (_lexer.current() != lex_close_brace)
+                               {
+                                       if (_lexer.current() != lex_comma)
+                                               throw_error("No comma between function arguments");
+                                       _lexer.next();
+                                       
+                                       xpath_ast_node* n = parse_expression();
+                                       
+                                       if (argc < 2) args[argc] = n;
+                                       else last_arg->set_next(n);
+
+                                       argc++;
+                                       last_arg = n;
+                               }
+                               
+                               _lexer.next();
+
+                               return parse_function(function, argc, args);
+                       }
+
+                       default:
+                               throw_error("Unrecognizable primary expression");
+
+                               return 0;
+                       }
+               }
+               
+               // FilterExpr ::= PrimaryExpr | FilterExpr Predicate
+               // Predicate ::= '[' PredicateExpr ']'
+               // PredicateExpr ::= Expr
+               xpath_ast_node* parse_filter_expression()
+               {
+                       xpath_ast_node* n = parse_primary_expression();
+
+                       while (_lexer.current() == lex_open_square_brace)
+                       {
+                               _lexer.next();
+
+                               xpath_ast_node* expr = parse_expression();
+
+                               if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set");
+
+                               bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv();
+
+                               n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr);
+
+                               if (_lexer.current() != lex_close_square_brace)
+                                       throw_error("Unmatched square brace");
+                       
+                               _lexer.next();
+                       }
+                       
+                       return n;
+               }
+               
+               // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
+               // AxisSpecifier ::= AxisName '::' | '@'?
+               // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
+               // NameTest ::= '*' | NCName ':' '*' | QName
+               // AbbreviatedStep ::= '.' | '..'
+               xpath_ast_node* parse_step(xpath_ast_node* set)
+               {
+                       if (set && set->rettype() != xpath_type_node_set)
+                               throw_error("Step has to be applied to node set");
+
+                       bool axis_specified = false;
+                       axis_t axis = axis_child; // implied child axis
+
+                       if (_lexer.current() == lex_axis_attribute)
+                       {
+                               axis = axis_attribute;
+                               axis_specified = true;
+                               
+                               _lexer.next();
+                       }
+                       else if (_lexer.current() == lex_dot)
+                       {
+                               _lexer.next();
+                               
+                               return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0);
+                       }
+                       else if (_lexer.current() == lex_double_dot)
+                       {
+                               _lexer.next();
+                               
+                               return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0);
+                       }
+               
+                       nodetest_t nt_type = nodetest_none;
+                       xpath_lexer_string nt_name;
+                       
+                       if (_lexer.current() == lex_string)
+                       {
+                               // node name test
+                               nt_name = _lexer.contents();
+                               _lexer.next();
+
+                               // was it an axis name?
+                               if (_lexer.current() == lex_double_colon)
+                               {
+                                       // parse axis name
+                                       if (axis_specified) throw_error("Two axis specifiers in one step");
+
+                                       axis = parse_axis_name(nt_name, axis_specified);
+
+                                       if (!axis_specified) throw_error("Unknown axis");
+
+                                       // read actual node test
+                                       _lexer.next();
+
+                                       if (_lexer.current() == lex_multiply)
+                                       {
+                                               nt_type = nodetest_all;
+                                               nt_name = xpath_lexer_string();
+                                               _lexer.next();
+                                       }
+                                       else if (_lexer.current() == lex_string)
+                                       {
+                                               nt_name = _lexer.contents();
+                                               _lexer.next();
+                                       }
+                                       else throw_error("Unrecognized node test");
+                               }
+                               
+                               if (nt_type == nodetest_none)
+                               {
+                                       // node type test or processing-instruction
+                                       if (_lexer.current() == lex_open_brace)
+                                       {
+                                               _lexer.next();
+                                               
+                                               if (_lexer.current() == lex_close_brace)
+                                               {
+                                                       _lexer.next();
+
+                                                       nt_type = parse_node_test_type(nt_name);
+
+                                                       if (nt_type == nodetest_none) throw_error("Unrecognized node type");
+                                                       
+                                                       nt_name = xpath_lexer_string();
+                                               }
+                                               else if (nt_name == PUGIXML_TEXT("processing-instruction"))
+                                               {
+                                                       if (_lexer.current() != lex_quoted_string)
+                                                               throw_error("Only literals are allowed as arguments to processing-instruction()");
+                                               
+                                                       nt_type = nodetest_pi;
+                                                       nt_name = _lexer.contents();
+                                                       _lexer.next();
+                                                       
+                                                       if (_lexer.current() != lex_close_brace)
+                                                               throw_error("Unmatched brace near processing-instruction()");
+                                                       _lexer.next();
+                                               }
+                                               else
+                                                       throw_error("Unmatched brace near node type test");
+
+                                       }
+                                       // QName or NCName:*
+                                       else
+                                       {
+                                               if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:*
+                                               {
+                                                       nt_name.end--; // erase *
+                                                       
+                                                       nt_type = nodetest_all_in_namespace;
+                                               }
+                                               else nt_type = nodetest_name;
+                                       }
+                               }
+                       }
+                       else if (_lexer.current() == lex_multiply)
+                       {
+                               nt_type = nodetest_all;
+                               _lexer.next();
+                       }
+                       else throw_error("Unrecognized node test");
+                       
+                       xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name));
+                       
+                       xpath_ast_node* last = 0;
+                       
+                       while (_lexer.current() == lex_open_square_brace)
+                       {
+                               _lexer.next();
+                               
+                               xpath_ast_node* expr = parse_expression();
+
+                               xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr);
+                               
+                               if (_lexer.current() != lex_close_square_brace)
+                                       throw_error("Unmatched square brace");
+                               _lexer.next();
+                               
+                               if (last) last->set_next(pred);
+                               else n->set_right(pred);
+                               
+                               last = pred;
+                       }
+                       
+                       return n;
+               }
+               
+               // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step
+               xpath_ast_node* parse_relative_location_path(xpath_ast_node* set)
+               {
+                       xpath_ast_node* n = parse_step(set);
+                       
+                       while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash)
+                       {
+                               lexeme_t l = _lexer.current();
+                               _lexer.next();
+
+                               if (l == lex_double_slash)
+                                       n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0);
+                               
+                               n = parse_step(n);
+                       }
+                       
+                       return n;
+               }
+               
+               // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath
+               // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath
+               xpath_ast_node* parse_location_path()
+               {
+                       if (_lexer.current() == lex_slash)
+                       {
+                               _lexer.next();
+                               
+                               xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set);
+
+                               // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path
+                               lexeme_t l = _lexer.current();
+
+                               if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply)
+                                       return parse_relative_location_path(n);
+                               else
+                                       return n;
+                       }
+                       else if (_lexer.current() == lex_double_slash)
+                       {
+                               _lexer.next();
+                               
+                               xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set);
+                               n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0);
+                               
+                               return parse_relative_location_path(n);
+                       }
+
+                       // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1
+                       return parse_relative_location_path(0);
+               }
+               
+               // PathExpr ::= LocationPath
+               //                              | FilterExpr
+               //                              | FilterExpr '/' RelativeLocationPath
+               //                              | FilterExpr '//' RelativeLocationPath
+               // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
+               // UnaryExpr ::= UnionExpr | '-' UnaryExpr
+               xpath_ast_node* parse_path_or_unary_expression()
+               {
+                       // Clarification.
+                       // PathExpr begins with either LocationPath or FilterExpr.
+                       // FilterExpr begins with PrimaryExpr
+                       // PrimaryExpr begins with '$' in case of it being a variable reference,
+                       // '(' in case of it being an expression, string literal, number constant or
+                       // function call.
+
+                       if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || 
+                               _lexer.current() == lex_quoted_string || _lexer.current() == lex_number ||
+                               _lexer.current() == lex_string)
+                       {
+                               if (_lexer.current() == lex_string)
+                               {
+                                       // This is either a function call, or not - if not, we shall proceed with location path
+                                       const char_t* state = _lexer.state();
+                                       
+                                       while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state;
+                                       
+                                       if (*state != '(') return parse_location_path();
+
+                                       // This looks like a function call; however this still can be a node-test. Check it.
+                                       if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path();
+                               }
+                               
+                               xpath_ast_node* n = parse_filter_expression();
+
+                               if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash)
+                               {
+                                       lexeme_t l = _lexer.current();
+                                       _lexer.next();
+                                       
+                                       if (l == lex_double_slash)
+                                       {
+                                               if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set");
+
+                                               n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0);
+                                       }
+       
+                                       // select from location path
+                                       return parse_relative_location_path(n);
+                               }
+
+                               return n;
+                       }
+                       else if (_lexer.current() == lex_minus)
+                       {
+                               _lexer.next();
+
+                               // precedence 7+ - only parses union expressions
+                               xpath_ast_node* expr = parse_expression_rec(parse_path_or_unary_expression(), 7);
+
+                               return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr);
+                       }
+                       else
+                               return parse_location_path();
+               }
+
+               struct binary_op_t
+               {
+                       ast_type_t asttype;
+                       xpath_value_type rettype;
+                       int precedence;
+
+                       binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0)
+                       {
+                       }
+
+                       binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_)
+                       {
+                       }
+
+                       static binary_op_t parse(xpath_lexer& lexer)
+                       {
+                               switch (lexer.current())
+                               {
+                               case lex_string:
+                                       if (lexer.contents() == PUGIXML_TEXT("or"))
+                                               return binary_op_t(ast_op_or, xpath_type_boolean, 1);
+                                       else if (lexer.contents() == PUGIXML_TEXT("and"))
+                                               return binary_op_t(ast_op_and, xpath_type_boolean, 2);
+                                       else if (lexer.contents() == PUGIXML_TEXT("div"))
+                                               return binary_op_t(ast_op_divide, xpath_type_number, 6);
+                                       else if (lexer.contents() == PUGIXML_TEXT("mod"))
+                                               return binary_op_t(ast_op_mod, xpath_type_number, 6);
+                                       else
+                                               return binary_op_t();
+
+                               case lex_equal:
+                                       return binary_op_t(ast_op_equal, xpath_type_boolean, 3);
+
+                               case lex_not_equal:
+                                       return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3);
+
+                               case lex_less:
+                                       return binary_op_t(ast_op_less, xpath_type_boolean, 4);
+
+                               case lex_greater:
+                                       return binary_op_t(ast_op_greater, xpath_type_boolean, 4);
+
+                               case lex_less_or_equal:
+                                       return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4);
+
+                               case lex_greater_or_equal:
+                                       return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4);
+
+                               case lex_plus:
+                                       return binary_op_t(ast_op_add, xpath_type_number, 5);
+
+                               case lex_minus:
+                                       return binary_op_t(ast_op_subtract, xpath_type_number, 5);
+
+                               case lex_multiply:
+                                       return binary_op_t(ast_op_multiply, xpath_type_number, 6);
+
+                               case lex_union:
+                                       return binary_op_t(ast_op_union, xpath_type_node_set, 7);
+
+                               default:
+                                       return binary_op_t();
+                               }
+                       }
+               };
+
+               xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit)
+               {
+                       binary_op_t op = binary_op_t::parse(_lexer);
+
+                       while (op.asttype != ast_unknown && op.precedence >= limit)
+                       {
+                               _lexer.next();
+
+                               xpath_ast_node* rhs = parse_path_or_unary_expression();
+
+                               binary_op_t nextop = binary_op_t::parse(_lexer);
+
+                               while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence)
+                               {
+                                       rhs = parse_expression_rec(rhs, nextop.precedence);
+
+                                       nextop = binary_op_t::parse(_lexer);
+                               }
+
+                               if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set))
+                                       throw_error("Union operator has to be applied to node sets");
+
+                               lhs = new (alloc_node()) xpath_ast_node(op.asttype, op.rettype, lhs, rhs);
+
+                               op = binary_op_t::parse(_lexer);
+                       }
+
+                       return lhs;
+               }
+
+               // Expr ::= OrExpr
+               // OrExpr ::= AndExpr | OrExpr 'or' AndExpr
+               // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
+               // EqualityExpr ::= RelationalExpr
+               //                                      | EqualityExpr '=' RelationalExpr
+               //                                      | EqualityExpr '!=' RelationalExpr
+               // RelationalExpr ::= AdditiveExpr
+               //                                        | RelationalExpr '<' AdditiveExpr
+               //                                        | RelationalExpr '>' AdditiveExpr
+               //                                        | RelationalExpr '<=' AdditiveExpr
+               //                                        | RelationalExpr '>=' AdditiveExpr
+               // AdditiveExpr ::= MultiplicativeExpr
+               //                                      | AdditiveExpr '+' MultiplicativeExpr
+               //                                      | AdditiveExpr '-' MultiplicativeExpr
+               // MultiplicativeExpr ::= UnaryExpr
+               //                                                | MultiplicativeExpr '*' UnaryExpr
+               //                                                | MultiplicativeExpr 'div' UnaryExpr
+               //                                                | MultiplicativeExpr 'mod' UnaryExpr
+               xpath_ast_node* parse_expression()
+               {
+                       return parse_expression_rec(parse_path_or_unary_expression(), 0);
+               }
+
+               xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result)
+               {
+               }
+
+               xpath_ast_node* parse()
+               {
+                       xpath_ast_node* result = parse_expression();
+                       
+                       if (_lexer.current() != lex_eof)
+                       {
+                               // there are still unparsed tokens left, error
+                               throw_error("Incorrect query");
+                       }
+                       
+                       return result;
+               }
+
+               static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result)
+               {
+                       xpath_parser parser(query, variables, alloc, result);
+
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       int error = setjmp(parser._error_handler);
+
+                       return (error == 0) ? parser.parse() : 0;
+               #else
+                       return parser.parse();
+               #endif
+               }
+       };
+
+       struct xpath_query_impl
+       {
+               static xpath_query_impl* create()
+               {
+                       void* memory = xml_memory::allocate(sizeof(xpath_query_impl));
+
+                       return new (memory) xpath_query_impl();
+               }
+
+               static void destroy(void* ptr)
+               {
+                       if (!ptr) return;
+                       
+                       // free all allocated pages
+                       static_cast<xpath_query_impl*>(ptr)->alloc.release();
+
+                       // free allocator memory (with the first page)
+                       xml_memory::deallocate(ptr);
+               }
+
+               xpath_query_impl(): root(0), alloc(&block)
+               {
+                       block.next = 0;
+               }
+
+               xpath_ast_node* root;
+               xpath_allocator alloc;
+               xpath_memory_block block;
+       };
+
+       PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd)
+       {
+               if (!impl) return xpath_string();
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               if (setjmp(sd.error_handler)) return xpath_string();
+       #endif
+
+               xpath_context c(n, 1, 1);
+
+               return impl->root->eval_string(c, sd.stack);
+       }
+PUGI__NS_END
+
+namespace pugi
+{
+#ifndef PUGIXML_NO_EXCEPTIONS
+       PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_)
+       {
+               assert(_result.error);
+       }
+       
+       PUGI__FN const char* xpath_exception::what() const throw()
+       {
+               return _result.error;
+       }
+
+       PUGI__FN const xpath_parse_result& xpath_exception::result() const
+       {
+               return _result;
+       }
+#endif
+       
+       PUGI__FN xpath_node::xpath_node()
+       {
+       }
+               
+       PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_)
+       {
+       }
+               
+       PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_)
+       {
+       }
+
+       PUGI__FN xml_node xpath_node::node() const
+       {
+               return _attribute ? xml_node() : _node;
+       }
+               
+       PUGI__FN xml_attribute xpath_node::attribute() const
+       {
+               return _attribute;
+       }
+       
+       PUGI__FN xml_node xpath_node::parent() const
+       {
+               return _attribute ? _node : _node.parent();
+       }
+
+       PUGI__FN static void unspecified_bool_xpath_node(xpath_node***)
+       {
+       }
+
+       PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const
+       {
+               return (_node || _attribute) ? unspecified_bool_xpath_node : 0;
+       }
+       
+       PUGI__FN bool xpath_node::operator!() const
+       {
+               return !(_node || _attribute);
+       }
+
+       PUGI__FN bool xpath_node::operator==(const xpath_node& n) const
+       {
+               return _node == n._node && _attribute == n._attribute;
+       }
+       
+       PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const
+       {
+               return _node != n._node || _attribute != n._attribute;
+       }
+
+#ifdef __BORLANDC__
+       PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs)
+       {
+               return (bool)lhs && rhs;
+       }
+
+       PUGI__FN bool operator||(const xpath_node& lhs, bool rhs)
+       {
+               return (bool)lhs || rhs;
+       }
+#endif
+
+       PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_)
+       {
+               assert(begin_ <= end_);
+
+               size_t size_ = static_cast<size_t>(end_ - begin_);
+
+               if (size_ <= 1)
+               {
+                       // deallocate old buffer
+                       if (_begin != &_storage) impl::xml_memory::deallocate(_begin);
+
+                       // use internal buffer
+                       if (begin_ != end_) _storage = *begin_;
+
+                       _begin = &_storage;
+                       _end = &_storage + size_;
+               }
+               else
+               {
+                       // make heap copy
+                       xpath_node* storage = static_cast<xpath_node*>(impl::xml_memory::allocate(size_ * sizeof(xpath_node)));
+
+                       if (!storage)
+                       {
+                       #ifdef PUGIXML_NO_EXCEPTIONS
+                               return;
+                       #else
+                               throw std::bad_alloc();
+                       #endif
+                       }
+
+                       memcpy(storage, begin_, size_ * sizeof(xpath_node));
+                       
+                       // deallocate old buffer
+                       if (_begin != &_storage) impl::xml_memory::deallocate(_begin);
+
+                       // finalize
+                       _begin = storage;
+                       _end = storage + size_;
+               }
+       }
+
+       PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage)
+       {
+       }
+
+       PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage)
+       {
+               _assign(begin_, end_);
+       }
+
+       PUGI__FN xpath_node_set::~xpath_node_set()
+       {
+               if (_begin != &_storage) impl::xml_memory::deallocate(_begin);
+       }
+               
+       PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage)
+       {
+               _assign(ns._begin, ns._end);
+       }
+       
+       PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns)
+       {
+               if (this == &ns) return *this;
+               
+               _type = ns._type;
+               _assign(ns._begin, ns._end);
+
+               return *this;
+       }
+
+       PUGI__FN xpath_node_set::type_t xpath_node_set::type() const
+       {
+               return _type;
+       }
+               
+       PUGI__FN size_t xpath_node_set::size() const
+       {
+               return _end - _begin;
+       }
+               
+       PUGI__FN bool xpath_node_set::empty() const
+       {
+               return _begin == _end;
+       }
+               
+       PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const
+       {
+               assert(index < size());
+               return _begin[index];
+       }
+
+       PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const
+       {
+               return _begin;
+       }
+               
+       PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const
+       {
+               return _end;
+       }
+       
+       PUGI__FN void xpath_node_set::sort(bool reverse)
+       {
+               _type = impl::xpath_sort(_begin, _end, _type, reverse);
+       }
+
+       PUGI__FN xpath_node xpath_node_set::first() const
+       {
+               return impl::xpath_first(_begin, _end, _type);
+       }
+
+       PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0)
+       {
+       }
+
+       PUGI__FN xpath_parse_result::operator bool() const
+       {
+               return error == 0;
+       }
+
+       PUGI__FN const char* xpath_parse_result::description() const
+       {
+               return error ? error : "No error";
+       }
+
+       PUGI__FN xpath_variable::xpath_variable(): _type(xpath_type_none), _next(0)
+       {
+       }
+
+       PUGI__FN const char_t* xpath_variable::name() const
+       {
+               switch (_type)
+               {
+               case xpath_type_node_set:
+                       return static_cast<const impl::xpath_variable_node_set*>(this)->name;
+
+               case xpath_type_number:
+                       return static_cast<const impl::xpath_variable_number*>(this)->name;
+
+               case xpath_type_string:
+                       return static_cast<const impl::xpath_variable_string*>(this)->name;
+
+               case xpath_type_boolean:
+                       return static_cast<const impl::xpath_variable_boolean*>(this)->name;
+
+               default:
+                       assert(!"Invalid variable type");
+                       return 0;
+               }
+       }
+
+       PUGI__FN xpath_value_type xpath_variable::type() const
+       {
+               return _type;
+       }
+
+       PUGI__FN bool xpath_variable::get_boolean() const
+       {
+               return (_type == xpath_type_boolean) ? static_cast<const impl::xpath_variable_boolean*>(this)->value : false;
+       }
+
+       PUGI__FN double xpath_variable::get_number() const
+       {
+               return (_type == xpath_type_number) ? static_cast<const impl::xpath_variable_number*>(this)->value : impl::gen_nan();
+       }
+
+       PUGI__FN const char_t* xpath_variable::get_string() const
+       {
+               const char_t* value = (_type == xpath_type_string) ? static_cast<const impl::xpath_variable_string*>(this)->value : 0;
+               return value ? value : PUGIXML_TEXT("");
+       }
+
+       PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const
+       {
+               return (_type == xpath_type_node_set) ? static_cast<const impl::xpath_variable_node_set*>(this)->value : impl::dummy_node_set;
+       }
+
+       PUGI__FN bool xpath_variable::set(bool value)
+       {
+               if (_type != xpath_type_boolean) return false;
+
+               static_cast<impl::xpath_variable_boolean*>(this)->value = value;
+               return true;
+       }
+
+       PUGI__FN bool xpath_variable::set(double value)
+       {
+               if (_type != xpath_type_number) return false;
+
+               static_cast<impl::xpath_variable_number*>(this)->value = value;
+               return true;
+       }
+
+       PUGI__FN bool xpath_variable::set(const char_t* value)
+       {
+               if (_type != xpath_type_string) return false;
+
+               impl::xpath_variable_string* var = static_cast<impl::xpath_variable_string*>(this);
+
+               // duplicate string
+               size_t size = (impl::strlength(value) + 1) * sizeof(char_t);
+
+               char_t* copy = static_cast<char_t*>(impl::xml_memory::allocate(size));
+               if (!copy) return false;
+
+               memcpy(copy, value, size);
+
+               // replace old string
+               if (var->value) impl::xml_memory::deallocate(var->value);
+               var->value = copy;
+
+               return true;
+       }
+
+       PUGI__FN bool xpath_variable::set(const xpath_node_set& value)
+       {
+               if (_type != xpath_type_node_set) return false;
+
+               static_cast<impl::xpath_variable_node_set*>(this)->value = value;
+               return true;
+       }
+
+       PUGI__FN xpath_variable_set::xpath_variable_set()
+       {
+               for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0;
+       }
+
+       PUGI__FN xpath_variable_set::~xpath_variable_set()
+       {
+               for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i)
+               {
+                       xpath_variable* var = _data[i];
+
+                       while (var)
+                       {
+                               xpath_variable* next = var->_next;
+
+                               impl::delete_xpath_variable(var->_type, var);
+
+                               var = next;
+                       }
+               }
+       }
+
+       PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const
+       {
+               const size_t hash_size = sizeof(_data) / sizeof(_data[0]);
+               size_t hash = impl::hash_string(name) % hash_size;
+
+               // look for existing variable
+               for (xpath_variable* var = _data[hash]; var; var = var->_next)
+                       if (impl::strequal(var->name(), name))
+                               return var;
+
+               return 0;
+       }
+
+       PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type)
+       {
+               const size_t hash_size = sizeof(_data) / sizeof(_data[0]);
+               size_t hash = impl::hash_string(name) % hash_size;
+
+               // look for existing variable
+               for (xpath_variable* var = _data[hash]; var; var = var->_next)
+                       if (impl::strequal(var->name(), name))
+                               return var->type() == type ? var : 0;
+
+               // add new variable
+               xpath_variable* result = impl::new_xpath_variable(type, name);
+
+               if (result)
+               {
+                       result->_type = type;
+                       result->_next = _data[hash];
+
+                       _data[hash] = result;
+               }
+
+               return result;
+       }
+
+       PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value)
+       {
+               xpath_variable* var = add(name, xpath_type_boolean);
+               return var ? var->set(value) : false;
+       }
+
+       PUGI__FN bool xpath_variable_set::set(const char_t* name, double value)
+       {
+               xpath_variable* var = add(name, xpath_type_number);
+               return var ? var->set(value) : false;
+       }
+
+       PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value)
+       {
+               xpath_variable* var = add(name, xpath_type_string);
+               return var ? var->set(value) : false;
+       }
+
+       PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value)
+       {
+               xpath_variable* var = add(name, xpath_type_node_set);
+               return var ? var->set(value) : false;
+       }
+
+       PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name)
+       {
+               return find(name);
+       }
+
+       PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const
+       {
+               return find(name);
+       }
+
+       PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0)
+       {
+               impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create();
+
+               if (!qimpl)
+               {
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       _result.error = "Out of memory";
+               #else
+                       throw std::bad_alloc();
+               #endif
+               }
+               else
+               {
+                       impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy);
+
+                       qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result);
+
+                       if (qimpl->root)
+                       {
+                               _impl = static_cast<impl::xpath_query_impl*>(impl_holder.release());
+                               _result.error = 0;
+                       }
+               }
+       }
+
+       PUGI__FN xpath_query::~xpath_query()
+       {
+               impl::xpath_query_impl::destroy(_impl);
+       }
+
+       PUGI__FN xpath_value_type xpath_query::return_type() const
+       {
+               if (!_impl) return xpath_type_none;
+
+               return static_cast<impl::xpath_query_impl*>(_impl)->root->rettype();
+       }
+
+       PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const
+       {
+               if (!_impl) return false;
+               
+               impl::xpath_context c(n, 1, 1);
+               impl::xpath_stack_data sd;
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               if (setjmp(sd.error_handler)) return false;
+       #endif
+               
+               return static_cast<impl::xpath_query_impl*>(_impl)->root->eval_boolean(c, sd.stack);
+       }
+       
+       PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const
+       {
+               if (!_impl) return impl::gen_nan();
+               
+               impl::xpath_context c(n, 1, 1);
+               impl::xpath_stack_data sd;
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               if (setjmp(sd.error_handler)) return impl::gen_nan();
+       #endif
+
+               return static_cast<impl::xpath_query_impl*>(_impl)->root->eval_number(c, sd.stack);
+       }
+
+#ifndef PUGIXML_NO_STL
+       PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const
+       {
+               impl::xpath_stack_data sd;
+
+               return impl::evaluate_string_impl(static_cast<impl::xpath_query_impl*>(_impl), n, sd).c_str();
+       }
+#endif
+
+       PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const
+       {
+               impl::xpath_stack_data sd;
+
+               impl::xpath_string r = impl::evaluate_string_impl(static_cast<impl::xpath_query_impl*>(_impl), n, sd);
+
+               size_t full_size = r.length() + 1;
+               
+               if (capacity > 0)
+               {
+                       size_t size = (full_size < capacity) ? full_size : capacity;
+                       assert(size > 0);
+
+                       memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t));
+                       buffer[size - 1] = 0;
+               }
+               
+               return full_size;
+       }
+
+       PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const
+       {
+               if (!_impl) return xpath_node_set();
+
+               impl::xpath_ast_node* root = static_cast<impl::xpath_query_impl*>(_impl)->root;
+
+               if (root->rettype() != xpath_type_node_set)
+               {
+               #ifdef PUGIXML_NO_EXCEPTIONS
+                       return xpath_node_set();
+               #else
+                       xpath_parse_result res;
+                       res.error = "Expression does not evaluate to node set";
+
+                       throw xpath_exception(res);
+               #endif
+               }
+               
+               impl::xpath_context c(n, 1, 1);
+               impl::xpath_stack_data sd;
+
+       #ifdef PUGIXML_NO_EXCEPTIONS
+               if (setjmp(sd.error_handler)) return xpath_node_set();
+       #endif
+
+               impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack);
+
+               return xpath_node_set(r.begin(), r.end(), r.type());
+       }
+
+       PUGI__FN const xpath_parse_result& xpath_query::result() const
+       {
+               return _result;
+       }
+
+       PUGI__FN static void unspecified_bool_xpath_query(xpath_query***)
+       {
+       }
+
+       PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const
+       {
+               return _impl ? unspecified_bool_xpath_query : 0;
+       }
+
+       PUGI__FN bool xpath_query::operator!() const
+       {
+               return !_impl;
+       }
+
+       PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const
+       {
+               xpath_query q(query, variables);
+               return select_single_node(q);
+       }
+
+       PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const
+       {
+               xpath_node_set s = query.evaluate_node_set(*this);
+               return s.empty() ? xpath_node() : s.first();
+       }
+
+       PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const
+       {
+               xpath_query q(query, variables);
+               return select_nodes(q);
+       }
+
+       PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const
+       {
+               return query.evaluate_node_set(*this);
+       }
+}
+
+#endif
+
+#ifdef __BORLANDC__
+#      pragma option pop
+#endif
+
+// Intel C++ does not properly keep warning state for function templates,
+// so popping warning state at the end of translation unit leads to warnings in the middle.
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
+#      pragma warning(pop)
+#endif
+
+// Undefine all local macros (makes sure we're not leaking macros in header-only mode)
+#undef PUGI__NO_INLINE
+#undef PUGI__STATIC_ASSERT
+#undef PUGI__DMC_VOLATILE
+#undef PUGI__MSVC_CRT_VERSION
+#undef PUGI__NS_BEGIN
+#undef PUGI__NS_END
+#undef PUGI__FN
+#undef PUGI__FN_NO_INLINE
+#undef PUGI__IS_CHARTYPE_IMPL
+#undef PUGI__IS_CHARTYPE
+#undef PUGI__IS_CHARTYPEX
+#undef PUGI__SKIPWS
+#undef PUGI__OPTSET
+#undef PUGI__PUSHNODE
+#undef PUGI__POPNODE
+#undef PUGI__SCANFOR
+#undef PUGI__SCANWHILE
+#undef PUGI__ENDSEG
+#undef PUGI__THROW_ERROR
+#undef PUGI__CHECK_ERROR
+
+#endif
+
+/**
+ * Copyright (c) 2006-2014 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/libsrc/pugixml.hpp b/libsrc/pugixml.hpp
new file mode 100644 (file)
index 0000000..6fb99be
--- /dev/null
@@ -0,0 +1,1332 @@
+/**
+ * pugixml parser - version 1.4
+ * --------------------------------------------------------
+ * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
+ * Report bugs and download new versions at http://pugixml.org/
+ *
+ * This library is distributed under the MIT License. See notice at the end
+ * of this file.
+ *
+ * This work is based on the pugxml parser, which is:
+ * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
+ */
+
+#ifndef PUGIXML_VERSION
+// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons
+#      define PUGIXML_VERSION 140
+#endif
+
+// Include user configuration file (this can define various configuration macros)
+#include "pugiconfig.hpp"
+
+#ifndef HEADER_PUGIXML_HPP
+#define HEADER_PUGIXML_HPP
+
+// Include stddef.h for size_t and ptrdiff_t
+#include <stddef.h>
+
+// Include exception header for XPath
+#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS)
+#      include <exception>
+#endif
+
+// Include STL headers
+#ifndef PUGIXML_NO_STL
+#      include <iterator>
+#      include <iosfwd>
+#      include <string>
+#endif
+
+// Macro for deprecated features
+#ifndef PUGIXML_DEPRECATED
+#      if defined(__GNUC__)
+#              define PUGIXML_DEPRECATED __attribute__((deprecated))
+#      elif defined(_MSC_VER) && _MSC_VER >= 1300
+#              define PUGIXML_DEPRECATED __declspec(deprecated)
+#      else
+#              define PUGIXML_DEPRECATED
+#      endif
+#endif
+
+// If no API is defined, assume default
+#ifndef PUGIXML_API
+#      define PUGIXML_API
+#endif
+
+// If no API for classes is defined, assume default
+#ifndef PUGIXML_CLASS
+#      define PUGIXML_CLASS PUGIXML_API
+#endif
+
+// If no API for functions is defined, assume default
+#ifndef PUGIXML_FUNCTION
+#      define PUGIXML_FUNCTION PUGIXML_API
+#endif
+
+// If the platform is known to have long long support, enable long long functions
+#ifndef PUGIXML_HAS_LONG_LONG
+#      if defined(__cplusplus) && __cplusplus >= 201103
+#              define PUGIXML_HAS_LONG_LONG
+#      elif defined(_MSC_VER) && _MSC_VER >= 1400
+#              define PUGIXML_HAS_LONG_LONG
+#      endif
+#endif
+
+// Character interface macros
+#ifdef PUGIXML_WCHAR_MODE
+#      define PUGIXML_TEXT(t) L ## t
+#      define PUGIXML_CHAR wchar_t
+#else
+#      define PUGIXML_TEXT(t) t
+#      define PUGIXML_CHAR char
+#endif
+
+namespace pugi
+{
+       // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE
+       typedef PUGIXML_CHAR char_t;
+
+#ifndef PUGIXML_NO_STL
+       // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE
+       typedef std::basic_string<PUGIXML_CHAR, std::char_traits<PUGIXML_CHAR>, std::allocator<PUGIXML_CHAR> > string_t;
+#endif
+}
+
+// The PugiXML namespace
+namespace pugi
+{
+       // Tree node types
+       enum xml_node_type
+       {
+               node_null,                      // Empty (null) node handle
+               node_document,          // A document tree's absolute root
+               node_element,           // Element tag, i.e. '<node/>'
+               node_pcdata,            // Plain character data, i.e. 'text'
+               node_cdata,                     // Character data, i.e. '<![CDATA[text]]>'
+               node_comment,           // Comment tag, i.e. '<!-- text -->'
+               node_pi,                        // Processing instruction, i.e. '<?name?>'
+               node_declaration,       // Document declaration, i.e. '<?xml version="1.0"?>'
+               node_doctype            // Document type declaration, i.e. '<!DOCTYPE doc>'
+       };
+
+       // Parsing options
+
+       // Minimal parsing mode (equivalent to turning all other flags off).
+       // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed.
+       const unsigned int parse_minimal = 0x0000;
+
+       // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default.
+       const unsigned int parse_pi = 0x0001;
+
+       // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default.
+       const unsigned int parse_comments = 0x0002;
+
+       // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default.
+       const unsigned int parse_cdata = 0x0004;
+
+       // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree.
+       // This flag is off by default; turning it on usually results in slower parsing and more memory consumption.
+       const unsigned int parse_ws_pcdata = 0x0008;
+
+       // This flag determines if character and entity references are expanded during parsing. This flag is on by default.
+       const unsigned int parse_escapes = 0x0010;
+
+       // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default.
+       const unsigned int parse_eol = 0x0020;
+       
+       // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default.
+       const unsigned int parse_wconv_attribute = 0x0040;
+
+       // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default.
+       const unsigned int parse_wnorm_attribute = 0x0080;
+       
+       // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default.
+       const unsigned int parse_declaration = 0x0100;
+
+       // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default.
+       const unsigned int parse_doctype = 0x0200;
+
+       // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only
+       // of whitespace is added to the DOM tree.
+       // This flag is off by default; turning it on may result in slower parsing and more memory consumption.
+       const unsigned int parse_ws_pcdata_single = 0x0400;
+
+       // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default.
+       const unsigned int parse_trim_pcdata = 0x0800;
+
+       // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document
+       // is a valid document. This flag is off by default.
+       const unsigned int parse_fragment = 0x1000;
+
+       // The default parsing mode.
+       // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded,
+       // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules.
+       const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol;
+
+       // The full parsing mode.
+       // Nodes of all types are added to the DOM tree, character/reference entities are expanded,
+       // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules.
+       const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype;
+
+       // These flags determine the encoding of input data for XML document
+       enum xml_encoding
+       {
+               encoding_auto,          // Auto-detect input encoding using BOM or < / <? detection; use UTF8 if BOM is not found
+               encoding_utf8,          // UTF8 encoding
+               encoding_utf16_le,      // Little-endian UTF16
+               encoding_utf16_be,      // Big-endian UTF16
+               encoding_utf16,         // UTF16 with native endianness
+               encoding_utf32_le,      // Little-endian UTF32
+               encoding_utf32_be,      // Big-endian UTF32
+               encoding_utf32,         // UTF32 with native endianness
+               encoding_wchar,         // The same encoding wchar_t has (either UTF16 or UTF32)
+               encoding_latin1
+       };
+
+       // Formatting flags
+       
+       // Indent the nodes that are written to output stream with as many indentation strings as deep the node is in DOM tree. This flag is on by default.
+       const unsigned int format_indent = 0x01;
+       
+       // Write encoding-specific BOM to the output stream. This flag is off by default.
+       const unsigned int format_write_bom = 0x02;
+
+       // Use raw output mode (no indentation and no line breaks are written). This flag is off by default.
+       const unsigned int format_raw = 0x04;
+       
+       // Omit default XML declaration even if there is no declaration in the document. This flag is off by default.
+       const unsigned int format_no_declaration = 0x08;
+
+       // Don't escape attribute values and PCDATA contents. This flag is off by default.
+       const unsigned int format_no_escapes = 0x10;
+
+       // Open file using text mode in xml_document::save_file. This enables special character (i.e. new-line) conversions on some systems. This flag is off by default.
+       const unsigned int format_save_file_text = 0x20;
+
+       // The default set of formatting flags.
+       // Nodes are indented depending on their depth in DOM tree, a default declaration is output if document has none.
+       const unsigned int format_default = format_indent;
+               
+       // Forward declarations
+       struct xml_attribute_struct;
+       struct xml_node_struct;
+
+       class xml_node_iterator;
+       class xml_attribute_iterator;
+       class xml_named_node_iterator;
+
+       class xml_tree_walker;
+
+       struct xml_parse_result;
+
+       class xml_node;
+
+       class xml_text;
+       
+       #ifndef PUGIXML_NO_XPATH
+       class xpath_node;
+       class xpath_node_set;
+       class xpath_query;
+       class xpath_variable_set;
+       #endif
+
+       // Range-based for loop support
+       template <typename It> class xml_object_range
+       {
+       public:
+               typedef It const_iterator;
+               typedef It iterator;
+
+               xml_object_range(It b, It e): _begin(b), _end(e)
+               {
+               }
+
+               It begin() const { return _begin; }
+               It end() const { return _end; }
+
+       private:
+               It _begin, _end;
+       };
+
+       // Writer interface for node printing (see xml_node::print)
+       class PUGIXML_CLASS xml_writer
+       {
+       public:
+               virtual ~xml_writer() {}
+
+               // Write memory chunk into stream/file/whatever
+               virtual void write(const void* data, size_t size) = 0;
+       };
+
+       // xml_writer implementation for FILE*
+       class PUGIXML_CLASS xml_writer_file: public xml_writer
+       {
+       public:
+               // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio
+               xml_writer_file(void* file);
+
+               virtual void write(const void* data, size_t size);
+
+       private:
+               void* file;
+       };
+
+       #ifndef PUGIXML_NO_STL
+       // xml_writer implementation for streams
+       class PUGIXML_CLASS xml_writer_stream: public xml_writer
+       {
+       public:
+               // Construct writer from an output stream object
+               xml_writer_stream(std::basic_ostream<char, std::char_traits<char> >& stream);
+               xml_writer_stream(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream);
+
+               virtual void write(const void* data, size_t size);
+
+       private:
+               std::basic_ostream<char, std::char_traits<char> >* narrow_stream;
+               std::basic_ostream<wchar_t, std::char_traits<wchar_t> >* wide_stream;
+       };
+       #endif
+
+       // A light-weight handle for manipulating attributes in DOM tree
+       class PUGIXML_CLASS xml_attribute
+       {
+               friend class xml_attribute_iterator;
+               friend class xml_node;
+
+       private:
+               xml_attribute_struct* _attr;
+       
+               typedef void (*unspecified_bool_type)(xml_attribute***);
+
+       public:
+               // Default constructor. Constructs an empty attribute.
+               xml_attribute();
+               
+               // Constructs attribute from internal pointer
+               explicit xml_attribute(xml_attribute_struct* attr);
+
+               // Safe bool conversion operator
+               operator unspecified_bool_type() const;
+
+               // Borland C++ workaround
+               bool operator!() const;
+
+               // Comparison operators (compares wrapped attribute pointers)
+               bool operator==(const xml_attribute& r) const;
+               bool operator!=(const xml_attribute& r) const;
+               bool operator<(const xml_attribute& r) const;
+               bool operator>(const xml_attribute& r) const;
+               bool operator<=(const xml_attribute& r) const;
+               bool operator>=(const xml_attribute& r) const;
+
+               // Check if attribute is empty
+               bool empty() const;
+
+               // Get attribute name/value, or "" if attribute is empty
+               const char_t* name() const;
+               const char_t* value() const;
+
+               // Get attribute value, or the default value if attribute is empty
+               const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const;
+
+               // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty
+               int as_int(int def = 0) const;
+               unsigned int as_uint(unsigned int def = 0) const;
+               double as_double(double def = 0) const;
+               float as_float(float def = 0) const;
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               long long as_llong(long long def = 0) const;
+               unsigned long long as_ullong(unsigned long long def = 0) const;
+       #endif
+
+               // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty
+               bool as_bool(bool def = false) const;
+
+               // Set attribute name/value (returns false if attribute is empty or there is not enough memory)
+               bool set_name(const char_t* rhs);
+               bool set_value(const char_t* rhs);
+
+               // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false")
+               bool set_value(int rhs);
+               bool set_value(unsigned int rhs);
+               bool set_value(double rhs);
+               bool set_value(bool rhs);
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               bool set_value(long long rhs);
+               bool set_value(unsigned long long rhs);
+       #endif
+
+               // Set attribute value (equivalent to set_value without error checking)
+               xml_attribute& operator=(const char_t* rhs);
+               xml_attribute& operator=(int rhs);
+               xml_attribute& operator=(unsigned int rhs);
+               xml_attribute& operator=(double rhs);
+               xml_attribute& operator=(bool rhs);
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               xml_attribute& operator=(long long rhs);
+               xml_attribute& operator=(unsigned long long rhs);
+       #endif
+
+               // Get next/previous attribute in the attribute list of the parent node
+               xml_attribute next_attribute() const;
+               xml_attribute previous_attribute() const;
+
+               // Get hash value (unique for handles to the same object)
+               size_t hash_value() const;
+
+               // Get internal pointer
+               xml_attribute_struct* internal_object() const;
+       };
+
+#ifdef __BORLANDC__
+       // Borland C++ workaround
+       bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs);
+       bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs);
+#endif
+
+       // A light-weight handle for manipulating nodes in DOM tree
+       class PUGIXML_CLASS xml_node
+       {
+               friend class xml_attribute_iterator;
+               friend class xml_node_iterator;
+               friend class xml_named_node_iterator;
+
+       protected:
+               xml_node_struct* _root;
+
+               typedef void (*unspecified_bool_type)(xml_node***);
+
+       public:
+               // Default constructor. Constructs an empty node.
+               xml_node();
+
+               // Constructs node from internal pointer
+               explicit xml_node(xml_node_struct* p);
+
+               // Safe bool conversion operator
+               operator unspecified_bool_type() const;
+
+               // Borland C++ workaround
+               bool operator!() const;
+       
+               // Comparison operators (compares wrapped node pointers)
+               bool operator==(const xml_node& r) const;
+               bool operator!=(const xml_node& r) const;
+               bool operator<(const xml_node& r) const;
+               bool operator>(const xml_node& r) const;
+               bool operator<=(const xml_node& r) const;
+               bool operator>=(const xml_node& r) const;
+
+               // Check if node is empty.
+               bool empty() const;
+
+               // Get node type
+               xml_node_type type() const;
+
+               // Get node name, or "" if node is empty or it has no name
+               const char_t* name() const;
+
+               // Get node value, or "" if node is empty or it has no value
+        // Note: For <node>text</node> node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes.
+               const char_t* value() const;
+       
+               // Get attribute list
+               xml_attribute first_attribute() const;
+               xml_attribute last_attribute() const;
+
+               // Get children list
+               xml_node first_child() const;
+               xml_node last_child() const;
+
+               // Get next/previous sibling in the children list of the parent node
+               xml_node next_sibling() const;
+               xml_node previous_sibling() const;
+               
+               // Get parent node
+               xml_node parent() const;
+
+               // Get root of DOM tree this node belongs to
+               xml_node root() const;
+
+               // Get text object for the current node
+               xml_text text() const;
+
+               // Get child, attribute or next/previous sibling with the specified name
+               xml_node child(const char_t* name) const;
+               xml_attribute attribute(const char_t* name) const;
+               xml_node next_sibling(const char_t* name) const;
+               xml_node previous_sibling(const char_t* name) const;
+
+               // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA
+               const char_t* child_value() const;
+
+               // Get child value of child with specified name. Equivalent to child(name).child_value().
+               const char_t* child_value(const char_t* name) const;
+
+               // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value)
+               bool set_name(const char_t* rhs);
+               bool set_value(const char_t* rhs);
+               
+               // Add attribute with specified name. Returns added attribute, or empty attribute on errors.
+               xml_attribute append_attribute(const char_t* name);
+               xml_attribute prepend_attribute(const char_t* name);
+               xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr);
+               xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr);
+
+               // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors.
+               xml_attribute append_copy(const xml_attribute& proto);
+               xml_attribute prepend_copy(const xml_attribute& proto);
+               xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr);
+               xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr);
+
+               // Add child node with specified type. Returns added node, or empty node on errors.
+               xml_node append_child(xml_node_type type = node_element);
+               xml_node prepend_child(xml_node_type type = node_element);
+               xml_node insert_child_after(xml_node_type type, const xml_node& node);
+               xml_node insert_child_before(xml_node_type type, const xml_node& node);
+
+               // Add child element with specified name. Returns added node, or empty node on errors.
+               xml_node append_child(const char_t* name);
+               xml_node prepend_child(const char_t* name);
+               xml_node insert_child_after(const char_t* name, const xml_node& node);
+               xml_node insert_child_before(const char_t* name, const xml_node& node);
+
+               // Add a copy of the specified node as a child. Returns added node, or empty node on errors.
+               xml_node append_copy(const xml_node& proto);
+               xml_node prepend_copy(const xml_node& proto);
+               xml_node insert_copy_after(const xml_node& proto, const xml_node& node);
+               xml_node insert_copy_before(const xml_node& proto, const xml_node& node);
+
+               // Remove specified attribute
+               bool remove_attribute(const xml_attribute& a);
+               bool remove_attribute(const char_t* name);
+
+               // Remove specified child
+               bool remove_child(const xml_node& n);
+               bool remove_child(const char_t* name);
+
+               // Parses buffer as an XML document fragment and appends all nodes as children of the current node.
+               // Copies/converts the buffer, so it may be deleted or changed after the function returns.
+               // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory.
+               xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+               // Find attribute using predicate. Returns first attribute for which predicate returned true.
+               template <typename Predicate> xml_attribute find_attribute(Predicate pred) const
+               {
+                       if (!_root) return xml_attribute();
+                       
+                       for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute())
+                               if (pred(attrib))
+                                       return attrib;
+               
+                       return xml_attribute();
+               }
+
+               // Find child node using predicate. Returns first child for which predicate returned true.
+               template <typename Predicate> xml_node find_child(Predicate pred) const
+               {
+                       if (!_root) return xml_node();
+       
+                       for (xml_node node = first_child(); node; node = node.next_sibling())
+                               if (pred(node))
+                                       return node;
+               
+                       return xml_node();
+               }
+
+               // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true.
+               template <typename Predicate> xml_node find_node(Predicate pred) const
+               {
+                       if (!_root) return xml_node();
+
+                       xml_node cur = first_child();
+                       
+                       while (cur._root && cur._root != _root)
+                       {
+                               if (pred(cur)) return cur;
+
+                               if (cur.first_child()) cur = cur.first_child();
+                               else if (cur.next_sibling()) cur = cur.next_sibling();
+                               else
+                               {
+                                       while (!cur.next_sibling() && cur._root != _root) cur = cur.parent();
+
+                                       if (cur._root != _root) cur = cur.next_sibling();
+                               }
+                       }
+
+                       return xml_node();
+               }
+
+               // Find child node by attribute name/value
+               xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const;
+               xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const;
+
+       #ifndef PUGIXML_NO_STL
+               // Get the absolute node path from root as a text string.
+               string_t path(char_t delimiter = '/') const;
+       #endif
+
+               // Search for a node by path consisting of node names and . or .. elements.
+               xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const;
+
+               // Recursively traverse subtree with xml_tree_walker
+               bool traverse(xml_tree_walker& walker);
+       
+       #ifndef PUGIXML_NO_XPATH
+               // Select single node by evaluating XPath query. Returns first node from the resulting node set.
+               xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const;
+               xpath_node select_single_node(const xpath_query& query) const;
+
+               // Select node set by evaluating XPath query
+               xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;
+               xpath_node_set select_nodes(const xpath_query& query) const;
+       #endif
+               
+               // Print subtree using a writer object
+               void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
+
+       #ifndef PUGIXML_NO_STL
+               // Print subtree to stream
+               void print(std::basic_ostream<char, std::char_traits<char> >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const;
+               void print(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const;
+       #endif
+
+               // Child nodes iterators
+               typedef xml_node_iterator iterator;
+
+               iterator begin() const;
+               iterator end() const;
+
+               // Attribute iterators
+               typedef xml_attribute_iterator attribute_iterator;
+
+               attribute_iterator attributes_begin() const;
+               attribute_iterator attributes_end() const;
+
+               // Range-based for support
+               xml_object_range<xml_node_iterator> children() const;
+               xml_object_range<xml_named_node_iterator> children(const char_t* name) const;
+               xml_object_range<xml_attribute_iterator> attributes() const;
+
+               // Get node offset in parsed file/string (in char_t units) for debugging purposes
+               ptrdiff_t offset_debug() const;
+
+               // Get hash value (unique for handles to the same object)
+               size_t hash_value() const;
+
+               // Get internal pointer
+               xml_node_struct* internal_object() const;
+       };
+
+#ifdef __BORLANDC__
+       // Borland C++ workaround
+       bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs);
+       bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs);
+#endif
+
+       // A helper for working with text inside PCDATA nodes
+       class PUGIXML_CLASS xml_text
+       {
+               friend class xml_node;
+
+               xml_node_struct* _root;
+
+               typedef void (*unspecified_bool_type)(xml_text***);
+
+               explicit xml_text(xml_node_struct* root);
+
+               xml_node_struct* _data_new();
+               xml_node_struct* _data() const;
+
+       public:
+               // Default constructor. Constructs an empty object.
+               xml_text();
+
+               // Safe bool conversion operator
+               operator unspecified_bool_type() const;
+
+               // Borland C++ workaround
+               bool operator!() const;
+
+               // Check if text object is empty
+               bool empty() const;
+
+               // Get text, or "" if object is empty
+               const char_t* get() const;
+
+               // Get text, or the default value if object is empty
+               const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const;
+
+               // Get text as a number, or the default value if conversion did not succeed or object is empty
+               int as_int(int def = 0) const;
+               unsigned int as_uint(unsigned int def = 0) const;
+               double as_double(double def = 0) const;
+               float as_float(float def = 0) const;
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               long long as_llong(long long def = 0) const;
+               unsigned long long as_ullong(unsigned long long def = 0) const;
+       #endif
+
+               // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty
+               bool as_bool(bool def = false) const;
+
+               // Set text (returns false if object is empty or there is not enough memory)
+               bool set(const char_t* rhs);
+
+               // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false")
+               bool set(int rhs);
+               bool set(unsigned int rhs);
+               bool set(double rhs);
+               bool set(bool rhs);
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               bool set(long long rhs);
+               bool set(unsigned long long rhs);
+       #endif
+
+               // Set text (equivalent to set without error checking)
+               xml_text& operator=(const char_t* rhs);
+               xml_text& operator=(int rhs);
+               xml_text& operator=(unsigned int rhs);
+               xml_text& operator=(double rhs);
+               xml_text& operator=(bool rhs);
+
+       #ifdef PUGIXML_HAS_LONG_LONG
+               xml_text& operator=(long long rhs);
+               xml_text& operator=(unsigned long long rhs);
+       #endif
+
+               // Get the data node (node_pcdata or node_cdata) for this object
+               xml_node data() const;
+       };
+
+#ifdef __BORLANDC__
+       // Borland C++ workaround
+       bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs);
+       bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs);
+#endif
+
+       // Child node iterator (a bidirectional iterator over a collection of xml_node)
+       class PUGIXML_CLASS xml_node_iterator
+       {
+               friend class xml_node;
+
+       private:
+               mutable xml_node _wrap;
+               xml_node _parent;
+
+               xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent);
+
+       public:
+               // Iterator traits
+               typedef ptrdiff_t difference_type;
+               typedef xml_node value_type;
+               typedef xml_node* pointer;
+               typedef xml_node& reference;
+
+       #ifndef PUGIXML_NO_STL
+               typedef std::bidirectional_iterator_tag iterator_category;
+       #endif
+
+               // Default constructor
+               xml_node_iterator();
+
+               // Construct an iterator which points to the specified node
+               xml_node_iterator(const xml_node& node);
+
+               // Iterator operators
+               bool operator==(const xml_node_iterator& rhs) const;
+               bool operator!=(const xml_node_iterator& rhs) const;
+
+               xml_node& operator*() const;
+               xml_node* operator->() const;
+
+               const xml_node_iterator& operator++();
+               xml_node_iterator operator++(int);
+
+               const xml_node_iterator& operator--();
+               xml_node_iterator operator--(int);
+       };
+
+       // Attribute iterator (a bidirectional iterator over a collection of xml_attribute)
+       class PUGIXML_CLASS xml_attribute_iterator
+       {
+               friend class xml_node;
+
+       private:
+               mutable xml_attribute _wrap;
+               xml_node _parent;
+
+               xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent);
+
+       public:
+               // Iterator traits
+               typedef ptrdiff_t difference_type;
+               typedef xml_attribute value_type;
+               typedef xml_attribute* pointer;
+               typedef xml_attribute& reference;
+
+       #ifndef PUGIXML_NO_STL
+               typedef std::bidirectional_iterator_tag iterator_category;
+       #endif
+
+               // Default constructor
+               xml_attribute_iterator();
+
+               // Construct an iterator which points to the specified attribute
+               xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent);
+
+               // Iterator operators
+               bool operator==(const xml_attribute_iterator& rhs) const;
+               bool operator!=(const xml_attribute_iterator& rhs) const;
+
+               xml_attribute& operator*() const;
+               xml_attribute* operator->() const;
+
+               const xml_attribute_iterator& operator++();
+               xml_attribute_iterator operator++(int);
+
+               const xml_attribute_iterator& operator--();
+               xml_attribute_iterator operator--(int);
+       };
+
+       // Named node range helper
+       class PUGIXML_CLASS xml_named_node_iterator
+       {
+               friend class xml_node;
+
+       public:
+               // Iterator traits
+               typedef ptrdiff_t difference_type;
+               typedef xml_node value_type;
+               typedef xml_node* pointer;
+               typedef xml_node& reference;
+
+       #ifndef PUGIXML_NO_STL
+               typedef std::bidirectional_iterator_tag iterator_category;
+       #endif
+
+               // Default constructor
+               xml_named_node_iterator();
+
+               // Construct an iterator which points to the specified node
+               xml_named_node_iterator(const xml_node& node, const char_t* name);
+
+               // Iterator operators
+               bool operator==(const xml_named_node_iterator& rhs) const;
+               bool operator!=(const xml_named_node_iterator& rhs) const;
+
+               xml_node& operator*() const;
+               xml_node* operator->() const;
+
+               const xml_named_node_iterator& operator++();
+               xml_named_node_iterator operator++(int);
+
+               const xml_named_node_iterator& operator--();
+               xml_named_node_iterator operator--(int);
+
+       private:
+               mutable xml_node _wrap;
+               xml_node _parent;
+               const char_t* _name;
+
+               xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name);
+       };
+
+       // Abstract tree walker class (see xml_node::traverse)
+       class PUGIXML_CLASS xml_tree_walker
+       {
+               friend class xml_node;
+
+       private:
+               int _depth;
+       
+       protected:
+               // Get current traversal depth
+               int depth() const;
+       
+       public:
+               xml_tree_walker();
+               virtual ~xml_tree_walker();
+
+               // Callback that is called when traversal begins
+               virtual bool begin(xml_node& node);
+
+               // Callback that is called for each node traversed
+               virtual bool for_each(xml_node& node) = 0;
+
+               // Callback that is called when traversal ends
+               virtual bool end(xml_node& node);
+       };
+
+       // Parsing status, returned as part of xml_parse_result object
+       enum xml_parse_status
+       {
+               status_ok = 0,                          // No error
+
+               status_file_not_found,          // File was not found during load_file()
+               status_io_error,                        // Error reading from file/stream
+               status_out_of_memory,           // Could not allocate memory
+               status_internal_error,          // Internal error occurred
+
+               status_unrecognized_tag,        // Parser could not determine tag type
+
+               status_bad_pi,                          // Parsing error occurred while parsing document declaration/processing instruction
+               status_bad_comment,                     // Parsing error occurred while parsing comment
+               status_bad_cdata,                       // Parsing error occurred while parsing CDATA section
+               status_bad_doctype,                     // Parsing error occurred while parsing document type declaration
+               status_bad_pcdata,                      // Parsing error occurred while parsing PCDATA section
+               status_bad_start_element,       // Parsing error occurred while parsing start element tag
+               status_bad_attribute,           // Parsing error occurred while parsing element attribute
+               status_bad_end_element,         // Parsing error occurred while parsing end element tag
+               status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag)
+
+               status_append_invalid_root,     // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer)
+
+               status_no_document_element      // Parsing resulted in a document without element nodes
+       };
+
+       // Parsing result
+       struct PUGIXML_CLASS xml_parse_result
+       {
+               // Parsing status (see xml_parse_status)
+               xml_parse_status status;
+
+               // Last parsed offset (in char_t units from start of input data)
+               ptrdiff_t offset;
+
+               // Source document encoding
+               xml_encoding encoding;
+
+               // Default constructor, initializes object to failed state
+               xml_parse_result();
+
+               // Cast to bool operator
+               operator bool() const;
+
+               // Get error description
+               const char* description() const;
+       };
+
+       // Document class (DOM tree root)
+       class PUGIXML_CLASS xml_document: public xml_node
+       {
+       private:
+               char_t* _buffer;
+
+               char _memory[192];
+               
+               // Non-copyable semantics
+               xml_document(const xml_document&);
+               const xml_document& operator=(const xml_document&);
+
+               void create();
+               void destroy();
+
+       public:
+               // Default constructor, makes empty document
+               xml_document();
+
+               // Destructor, invalidates all node/attribute handles to this document
+               ~xml_document();
+
+               // Removes all nodes, leaving the empty document
+               void reset();
+
+               // Removes all nodes, then copies the entire contents of the specified document
+               void reset(const xml_document& proto);
+
+       #ifndef PUGIXML_NO_STL
+               // Load document from stream.
+               xml_parse_result load(std::basic_istream<char, std::char_traits<char> >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+               xml_parse_result load(std::basic_istream<wchar_t, std::char_traits<wchar_t> >& stream, unsigned int options = parse_default);
+       #endif
+
+               // Load document from zero-terminated string. No encoding conversions are applied.
+               xml_parse_result load(const char_t* contents, unsigned int options = parse_default);
+
+               // Load document from file
+               xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+               xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+               // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns.
+               xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+               // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data).
+               // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed.
+               xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+               // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data).
+               // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore).
+               xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto);
+
+               // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details).
+               void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+
+       #ifndef PUGIXML_NO_STL
+               // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details).
+               void save(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+               void save(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const;
+       #endif
+
+               // Save XML to file
+               bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+               bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const;
+
+               // Get document element
+               xml_node document_element() const;
+       };
+
+#ifndef PUGIXML_NO_XPATH
+       // XPath query return type
+       enum xpath_value_type
+       {
+               xpath_type_none,          // Unknown type (query failed to compile)
+               xpath_type_node_set,  // Node set (xpath_node_set)
+               xpath_type_number,        // Number
+               xpath_type_string,        // String
+               xpath_type_boolean        // Boolean
+       };
+
+       // XPath parsing result
+       struct PUGIXML_CLASS xpath_parse_result
+       {
+               // Error message (0 if no error)
+               const char* error;
+
+               // Last parsed offset (in char_t units from string start)
+               ptrdiff_t offset;
+
+               // Default constructor, initializes object to failed state
+               xpath_parse_result();
+
+               // Cast to bool operator
+               operator bool() const;
+
+               // Get error description
+               const char* description() const;
+       };
+
+       // A single XPath variable
+       class PUGIXML_CLASS xpath_variable
+       {
+               friend class xpath_variable_set;
+
+       protected:
+               xpath_value_type _type;
+               xpath_variable* _next;
+
+               xpath_variable();
+
+               // Non-copyable semantics
+               xpath_variable(const xpath_variable&);
+               xpath_variable& operator=(const xpath_variable&);
+               
+       public:
+               // Get variable name
+               const char_t* name() const;
+
+               // Get variable type
+               xpath_value_type type() const;
+
+               // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error
+               bool get_boolean() const;
+               double get_number() const;
+               const char_t* get_string() const;
+               const xpath_node_set& get_node_set() const;
+
+               // Set variable value; no type conversion is performed, false is returned on type mismatch error
+               bool set(bool value);
+               bool set(double value);
+               bool set(const char_t* value);
+               bool set(const xpath_node_set& value);
+       };
+
+       // A set of XPath variables
+       class PUGIXML_CLASS xpath_variable_set
+       {
+       private:
+               xpath_variable* _data[64];
+
+               // Non-copyable semantics
+               xpath_variable_set(const xpath_variable_set&);
+               xpath_variable_set& operator=(const xpath_variable_set&);
+
+               xpath_variable* find(const char_t* name) const;
+
+       public:
+               // Default constructor/destructor
+               xpath_variable_set();
+               ~xpath_variable_set();
+
+               // Add a new variable or get the existing one, if the types match
+               xpath_variable* add(const char_t* name, xpath_value_type type);
+
+               // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch
+               bool set(const char_t* name, bool value);
+               bool set(const char_t* name, double value);
+               bool set(const char_t* name, const char_t* value);
+               bool set(const char_t* name, const xpath_node_set& value);
+
+               // Get existing variable by name
+               xpath_variable* get(const char_t* name);
+               const xpath_variable* get(const char_t* name) const;
+       };
+
+       // A compiled XPath query object
+       class PUGIXML_CLASS xpath_query
+       {
+       private:
+               void* _impl;
+               xpath_parse_result _result;
+
+               typedef void (*unspecified_bool_type)(xpath_query***);
+
+               // Non-copyable semantics
+               xpath_query(const xpath_query&);
+               xpath_query& operator=(const xpath_query&);
+
+       public:
+               // Construct a compiled object from XPath expression.
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors.
+               explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0);
+
+               // Destructor
+               ~xpath_query();
+
+               // Get query expression return type
+               xpath_value_type return_type() const;
+               
+               // Evaluate expression as boolean value in the specified context; performs type conversion if necessary.
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+               bool evaluate_boolean(const xpath_node& n) const;
+               
+               // Evaluate expression as double value in the specified context; performs type conversion if necessary.
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+               double evaluate_number(const xpath_node& n) const;
+               
+       #ifndef PUGIXML_NO_STL
+               // Evaluate expression as string value in the specified context; performs type conversion if necessary.
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+               string_t evaluate_string(const xpath_node& n) const;
+       #endif
+               
+               // Evaluate expression as string value in the specified context; performs type conversion if necessary.
+               // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero).
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors.
+               // If PUGIXML_NO_EXCEPTIONS is defined, returns empty  set instead.
+               size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const;
+
+               // Evaluate expression as node set in the specified context.
+               // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors.
+               // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead.
+               xpath_node_set evaluate_node_set(const xpath_node& n) const;
+
+               // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode)
+               const xpath_parse_result& result() const;
+
+               // Safe bool conversion operator
+               operator unspecified_bool_type() const;
+
+               // Borland C++ workaround
+               bool operator!() const;
+       };
+       
+       #ifndef PUGIXML_NO_EXCEPTIONS
+       // XPath exception class
+       class PUGIXML_CLASS xpath_exception: public std::exception
+       {
+       private:
+               xpath_parse_result _result;
+
+       public:
+               // Construct exception from parse result
+               explicit xpath_exception(const xpath_parse_result& result);
+
+               // Get error message
+               virtual const char* what() const throw();
+
+               // Get parse result
+               const xpath_parse_result& result() const;
+       };
+       #endif
+       
+       // XPath node class (either xml_node or xml_attribute)
+       class PUGIXML_CLASS xpath_node
+       {
+       private:
+               xml_node _node;
+               xml_attribute _attribute;
+       
+               typedef void (*unspecified_bool_type)(xpath_node***);
+
+       public:
+               // Default constructor; constructs empty XPath node
+               xpath_node();
+               
+               // Construct XPath node from XML node/attribute
+               xpath_node(const xml_node& node);
+               xpath_node(const xml_attribute& attribute, const xml_node& parent);
+
+               // Get node/attribute, if any
+               xml_node node() const;
+               xml_attribute attribute() const;
+               
+               // Get parent of contained node/attribute
+               xml_node parent() const;
+
+               // Safe bool conversion operator
+               operator unspecified_bool_type() const;
+               
+               // Borland C++ workaround
+               bool operator!() const;
+
+               // Comparison operators
+               bool operator==(const xpath_node& n) const;
+               bool operator!=(const xpath_node& n) const;
+       };
+
+#ifdef __BORLANDC__
+       // Borland C++ workaround
+       bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs);
+       bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs);
+#endif
+
+       // A fixed-size collection of XPath nodes
+       class PUGIXML_CLASS xpath_node_set
+       {
+       public:
+               // Collection type
+               enum type_t
+               {
+                       type_unsorted,                  // Not ordered
+                       type_sorted,                    // Sorted by document order (ascending)
+                       type_sorted_reverse             // Sorted by document order (descending)
+               };
+               
+               // Constant iterator type
+               typedef const xpath_node* const_iterator;
+       
+               // Default constructor. Constructs empty set.
+               xpath_node_set();
+
+               // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful
+               xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted);
+
+               // Destructor
+               ~xpath_node_set();
+               
+               // Copy constructor/assignment operator
+               xpath_node_set(const xpath_node_set& ns);
+               xpath_node_set& operator=(const xpath_node_set& ns);
+
+               // Get collection type
+               type_t type() const;
+               
+               // Get collection size
+               size_t size() const;
+
+               // Indexing operator
+               const xpath_node& operator[](size_t index) const;
+               
+               // Collection iterators
+               const_iterator begin() const;
+               const_iterator end() const;
+
+               // Sort the collection in ascending/descending order by document order
+               void sort(bool reverse = false);
+               
+               // Get first node in the collection by document order
+               xpath_node first() const;
+               
+               // Check if collection is empty
+               bool empty() const;
+       
+       private:
+               type_t _type;
+               
+               xpath_node _storage;
+               
+               xpath_node* _begin;
+               xpath_node* _end;
+
+               void _assign(const_iterator begin, const_iterator end);
+       };
+#endif
+
+#ifndef PUGIXML_NO_STL
+       // Convert wide string to UTF8
+       std::basic_string<char, std::char_traits<char>, std::allocator<char> > PUGIXML_FUNCTION as_utf8(const wchar_t* str);
+       std::basic_string<char, std::char_traits<char>, std::allocator<char> > PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >& str);
+       
+       // Convert UTF8 to wide string
+       std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > PUGIXML_FUNCTION as_wide(const char* str);
+       std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > PUGIXML_FUNCTION as_wide(const std::basic_string<char, std::char_traits<char>, std::allocator<char> >& str);
+#endif
+
+       // Memory allocation function interface; returns pointer to allocated memory or NULL on failure
+       typedef void* (*allocation_function)(size_t size);
+       
+       // Memory deallocation function interface
+       typedef void (*deallocation_function)(void* ptr);
+
+       // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions.
+       void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate);
+       
+       // Get current memory management functions
+       allocation_function PUGIXML_FUNCTION get_memory_allocation_function();
+       deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function();
+}
+
+#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC))
+namespace std
+{
+       // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier)
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&);
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&);
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&);
+}
+#endif
+
+#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC)
+namespace std
+{
+       // Workarounds for (non-standard) iterator category detection
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&);
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&);
+       std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&);
+}
+#endif
+
+#endif
+
+/**
+ * Copyright (c) 2006-2014 Arseny Kapoulkine
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
diff --git a/libsrc/xml.cpp b/libsrc/xml.cpp
new file mode 100644 (file)
index 0000000..4131344
--- /dev/null
@@ -0,0 +1,723 @@
+#include "ismrmrd/xml.h"
+#include "ismrmrd/version.h"
+#include "pugixml.hpp"
+#include <cstdlib>
+
+namespace ISMRMRD
+{
+  //Utility Functions for deserializing Header
+  EncodingSpace parse_encoding_space(pugi::xml_node& n, const char* child) 
+  {
+    EncodingSpace e;
+    pugi::xml_node encodingSpace = n.child(child);
+    pugi::xml_node matrixSize = encodingSpace.child("matrixSize");
+    pugi::xml_node fieldOfView_mm = encodingSpace.child("fieldOfView_mm");
+    
+    if (!matrixSize) {
+      throw std::runtime_error("matrixSize not found in encodingSpace");
+    } else {
+      e.matrixSize.x = std::atoi(matrixSize.child_value("x"));
+      e.matrixSize.y = std::atoi(matrixSize.child_value("y"));
+      e.matrixSize.z = std::atoi(matrixSize.child_value("z"));
+    }
+
+    if (!fieldOfView_mm) {
+      throw std::runtime_error("fieldOfView_mm not found in encodingSpace");
+    } else {
+      e.fieldOfView_mm.x = std::atof(fieldOfView_mm.child_value("x"));
+      e.fieldOfView_mm.y = std::atof(fieldOfView_mm.child_value("y"));
+      e.fieldOfView_mm.z = std::atof(fieldOfView_mm.child_value("z"));
+    }
+
+    return e;
+  }
+  
+  Optional<Limit> parse_encoding_limit(pugi::xml_node& n, const char* child) 
+  {
+    Optional<Limit> o;
+    pugi::xml_node nc = n.child(child);
+    
+    if (nc) {
+      Limit l;
+      l.minimum = std::atoi(nc.child_value("minimum"));
+      l.maximum = std::atoi(nc.child_value("maximum"));
+      l.center = std::atoi(nc.child_value("center"));
+      o = l;
+    }
+
+    return o;
+  }
+
+  std::string parse_string(pugi::xml_node& n, const char* child) 
+  {
+    std::string r(n.child_value(child));
+    if (r.size() == 0) throw std::runtime_error("Null length string");
+    return r;
+  }
+
+  Optional<std::string> parse_optional_string(pugi::xml_node& n, const char* child)
+  {
+    std::string s(n.child_value(child));
+    Optional<std::string> r;
+    if (s.size()) r = s;
+    return r;
+  }
+
+  Optional<float> parse_optional_float(pugi::xml_node& n, const char* child)
+  {
+    Optional<float> r;
+    pugi::xml_node nc = n.child(child);
+    if (nc) {
+      r = std::atof(nc.child_value());
+    }
+    return r;
+  }
+
+  Optional<long> parse_optional_long(pugi::xml_node& n, const char* child) {
+    Optional<long> r;
+    pugi::xml_node nc = n.child(child);
+    if (nc) {
+      r = std::atol(nc.child_value());
+    }
+    return r;
+  }
+
+  Optional<unsigned short> parse_optional_ushort(pugi::xml_node& n, const char* child) {
+    Optional<unsigned short> r;
+    pugi::xml_node nc = n.child(child);
+    if (nc) {
+      r = static_cast<unsigned short>(std::atoi(nc.child_value()));
+    }
+    return r;
+  }
+
+  std::vector<float> parse_vector_float(pugi::xml_node& n, const char* child) 
+  {
+    std::vector<float> r;
+    
+    pugi::xml_node nc = n.child(child);
+
+    while (nc) {
+      float f = std::atof(nc.child_value());
+      r.push_back(f);
+      nc = nc.next_sibling(child);
+    }
+    
+    return r;
+  }
+
+  std::vector<std::string> parse_vector_string(pugi::xml_node& n, const char* child)
+  {
+    std::vector<std::string> r;
+    pugi::xml_node nc = n.child(child);
+    while (nc) {
+      std::string s = nc.child_value();
+      r.push_back(s);
+      nc = nc.next_sibling(child);
+    }
+    return r;
+  }
+
+  std::vector<UserParameterLong> parse_user_parameter_long(pugi::xml_node& n, const char* child) 
+  {
+    std::vector<UserParameterLong> r;
+    pugi::xml_node nc = n.child(child);
+    while (nc) {
+      UserParameterLong v;
+      pugi::xml_node name = nc.child("name");
+      pugi::xml_node value = nc.child("value");
+
+      if (!name || !value) {
+       throw std::runtime_error("Malformed user parameter (long)");
+      }
+
+      v.name = std::string(name.child_value());
+      v.value = std::atoi(value.child_value());
+
+      r.push_back(v);
+
+      nc = nc.next_sibling(child);
+    }
+    return r;
+  }
+
+  std::vector<UserParameterDouble> parse_user_parameter_double(pugi::xml_node& n, const char* child) 
+  {
+    std::vector<UserParameterDouble> r;
+    pugi::xml_node nc = n.child(child);
+    while (nc) {
+      UserParameterDouble v;
+      pugi::xml_node name = nc.child("name");
+      pugi::xml_node value = nc.child("value");
+
+      if (!name || !value) {
+       throw std::runtime_error("Malformed user parameter (double)");
+      }
+
+      char buffer[10000];
+      memcpy(buffer,name.child_value(),strlen(name.child_value())+1);
+      v.name = name.child_value();
+      v.value = std::atof(value.child_value());
+
+      r.push_back(v);
+
+      nc = nc.next_sibling(child);
+    }
+   
+    return r;
+  }
+
+  std::vector<UserParameterString> parse_user_parameter_string(pugi::xml_node& n, const char* child) 
+  {
+    std::vector<UserParameterString> r;
+    pugi::xml_node nc = n.child(child);
+    while (nc) {
+      UserParameterString v;
+      pugi::xml_node name = nc.child("name");
+      pugi::xml_node value = nc.child("value");
+
+      if (!name || !value) {
+       throw std::runtime_error("Malformed user parameter (string)");
+      }
+
+      v.name = std::string(name.child_value());
+      v.value = std::string(value.child_value());
+
+      r.push_back(v);
+
+      nc = nc.next_sibling(child);
+    }
+   
+    return r;
+  }
+
+  //End of utility functions for deserializing header
+
+  void deserialize(const char* xml, IsmrmrdHeader& h) 
+  {
+    pugi::xml_document doc;
+    pugi::xml_parse_result result = doc.load(xml);
+    
+    if (!result) {
+      throw std::runtime_error("Unable to load ISMRMRD XML header");
+    }
+
+    pugi::xml_node root = doc.child("ismrmrdHeader");
+
+    if (root) {
+      pugi::xml_node subjectInformation = root.child("subjectInformation");
+      pugi::xml_node studyInformation = root.child("studyInformation");
+      pugi::xml_node measurementInformation = root.child("measurementInformation");
+      pugi::xml_node acquisitionSystemInformation = root.child("acquisitionSystemInformation");
+      pugi::xml_node experimentalConditions = root.child("experimentalConditions");
+      pugi::xml_node encoding = root.child("encoding");
+      pugi::xml_node sequenceParameters = root.child("sequenceParameters");
+      pugi::xml_node userParameters = root.child("userParameters");
+
+      // Parsing version
+      h.version = parse_optional_long(root, "version");
+      
+      //Parsing experimentalConditions
+      if (!experimentalConditions) {
+       throw std::runtime_error("experimentalConditions not defined in ismrmrdHeader");
+      } else {
+       ExperimentalConditions e;
+       e.H1resonanceFrequency_Hz = std::atol(experimentalConditions.child_value("H1resonanceFrequency_Hz"));
+       h.experimentalConditions = e;
+      }
+      
+      //Parsing encoding section
+      if (!encoding) {
+       throw std::runtime_error("encoding section not found in ismrmrdHeader");
+      } else {
+       while (encoding) {
+         Encoding e;
+         
+         try {
+           e.encodedSpace = parse_encoding_space(encoding,"encodedSpace");
+           e.reconSpace = parse_encoding_space(encoding,"reconSpace");
+         } catch (std::runtime_error& e) {
+           std::cout << "Unable to parse encoding section: " << e.what() << std::endl;
+           throw;
+         }
+
+         pugi::xml_node encodingLimits = encoding.child("encodingLimits");
+         
+         if (!encodingLimits) {
+           throw std::runtime_error("encodingLimits not found in encoding section");
+         } else {
+           e.encodingLimits.kspace_encoding_step_0 = parse_encoding_limit(encodingLimits,"kspace_encoding_step_0");
+           e.encodingLimits.kspace_encoding_step_1 = parse_encoding_limit(encodingLimits,"kspace_encoding_step_1");
+           e.encodingLimits.kspace_encoding_step_2 = parse_encoding_limit(encodingLimits,"kspace_encoding_step_2");
+           e.encodingLimits.average                = parse_encoding_limit(encodingLimits,"average");
+           e.encodingLimits.slice                  = parse_encoding_limit(encodingLimits,"slice");
+           e.encodingLimits.contrast               = parse_encoding_limit(encodingLimits,"contrast");
+           e.encodingLimits.phase                  = parse_encoding_limit(encodingLimits,"phase");
+           e.encodingLimits.repetition             = parse_encoding_limit(encodingLimits,"repetition");
+           e.encodingLimits.set                    = parse_encoding_limit(encodingLimits,"set");
+           e.encodingLimits.segment                = parse_encoding_limit(encodingLimits,"segment");
+         }
+         
+         pugi::xml_node trajectory = encoding.child("trajectory");
+         if (!trajectory) {
+           throw std::runtime_error("trajectory not found in encoding section");
+         } else {
+           e.trajectory = std::string(encoding.child_value("trajectory"));
+         }
+
+         pugi::xml_node trajectoryDescription = encoding.child("trajectoryDescription");
+         
+         if (trajectoryDescription) {
+           TrajectoryDescription traj;
+           try {
+             traj.identifier = parse_string(trajectoryDescription,"identifier");
+             traj.userParameterLong = 
+               parse_user_parameter_long(trajectoryDescription, "userParameterLong");
+             traj.userParameterDouble = 
+               parse_user_parameter_double(trajectoryDescription, "userParameterDouble");
+             traj.comment = parse_optional_string(trajectoryDescription, "comment");
+             e.trajectoryDescription = traj;
+           } catch (std::runtime_error& e) {
+             std::cout << "Error parsing trajectory description" << std::endl;
+             throw;
+           }
+           
+         }
+
+         pugi::xml_node parallelImaging = encoding.child("parallelImaging");
+         if (parallelImaging) {
+           ParallelImaging info;
+           
+           pugi::xml_node accelerationFactor = parallelImaging.child("accelerationFactor");
+           if (!accelerationFactor) {
+             throw std::runtime_error("Unable to accelerationFactor section in parallelImaging");
+           } else {
+             info.accelerationFactor.kspace_encoding_step_1 = static_cast<unsigned short>(std::atoi(accelerationFactor.child_value("kspace_encoding_step_1")));
+             info.accelerationFactor.kspace_encoding_step_2 = static_cast<unsigned short>(std::atoi(accelerationFactor.child_value("kspace_encoding_step_2")));
+           }
+           
+           info.calibrationMode = parse_optional_string(parallelImaging,"calibrationMode");
+           info.interleavingDimension = parse_optional_string(parallelImaging,"interleavingDimension");
+           e.parallelImaging = info;
+         }
+
+         e.echoTrainLength = parse_optional_long(encoding, "echoTrainLength");
+
+         h.encoding.push_back(e);
+         encoding = encoding.next_sibling("encoding");
+       }
+
+      }
+
+      if (subjectInformation) {
+       SubjectInformation info;
+       info.patientName = parse_optional_string(subjectInformation, "patientName");
+       info.patientWeight_kg = parse_optional_float(subjectInformation, "patientWeight_kg");
+       info.patientID = parse_optional_string(subjectInformation, "patientID");
+       info.patientBirthdate = parse_optional_string(subjectInformation, "patientBirthdate");
+       info.patientGender = parse_optional_string(subjectInformation, "patientGender");
+       h.subjectInformation = info;
+      }
+
+      if (studyInformation) {
+       StudyInformation info;
+       info.studyDate = parse_optional_string(studyInformation,"studyDate");
+       info.studyTime = parse_optional_string(studyInformation,"studyTime");
+       info.studyID = parse_optional_string(studyInformation,"studyID");
+       info.accessionNumber = parse_optional_long(studyInformation,"accessionNumber");
+       info.referringPhysicianName = parse_optional_string(studyInformation,"referringPhysicianName");
+       info.studyDescription = parse_optional_string(studyInformation,"studyDescription");
+       info.studyInstanceUID = parse_optional_string(studyInformation,"studyInstanceUID");
+       h.studyInformation = info;
+      }
+
+      if (measurementInformation) {
+       MeasurementInformation info;
+       info.measurementID = parse_optional_string(measurementInformation,"measurementID");
+       info.seriesDate = parse_optional_string(measurementInformation, "seriesDate");
+       info.seriesTime = parse_optional_string(measurementInformation, "seriesTime");
+       info.patientPosition = parse_string(measurementInformation, "patientPosition");
+       info.initialSeriesNumber = parse_optional_long(measurementInformation, "initialSeriesNumber");
+       info.protocolName = parse_optional_string(measurementInformation, "protocolName");
+       info.seriesDescription = parse_optional_string(measurementInformation, "seriesDescription");
+       
+       pugi::xml_node measurementDependency = measurementInformation.child("measurementDependency");
+       while (measurementDependency) {
+         try {
+           MeasurementDependency d;
+           d.measurementID = parse_string(measurementDependency,"measurementID");
+           d.dependencyType = parse_string(measurementDependency,"dependencyType");
+           info.measurementDependency.push_back(d);
+         } catch (std::runtime_error& e) {
+           std::cout << "Error parsing measurement dependency: " << e.what() << std::endl;
+           throw;
+         } 
+         measurementDependency = measurementDependency.next_sibling("measurementDependency");
+       }
+
+       info.seriesInstanceUIDRoot = parse_optional_string(measurementInformation,"seriesInstanceUIDRoot");
+       info.frameOfReferenceUID = parse_optional_string(measurementInformation,"frameOfReferenceUID");
+
+       //This part of the schema is totally messed up and needs to be fixed, but for now we will just read it. 
+       pugi::xml_node ri = measurementInformation.child("referencedImageSequence");
+       if (ri) {
+         pugi::xml_node ric = ri.child("referencedSOPInstanceUID");
+         while (ric) {
+           ReferencedImageSequence r;
+           r.referencedSOPInstanceUID = ric.child_value();
+           info.referencedImageSequence.push_back(r);
+           ric = ric.next_sibling("referencedSOPInstanceUID");
+         }
+       }
+
+       h.measurementInformation = info;
+      }
+
+      if (acquisitionSystemInformation) {
+       AcquisitionSystemInformation info;
+       info.systemVendor = parse_optional_string(acquisitionSystemInformation, "systemVendor");
+       info.systemModel = parse_optional_string(acquisitionSystemInformation, "systemModel");
+       info.systemFieldStrength_T = parse_optional_float(acquisitionSystemInformation, "systemFieldStrength_T");
+       info.relativeReceiverNoiseBandwidth = parse_optional_float(acquisitionSystemInformation, "relativeReceiverNoiseBandwidth");
+       info.receiverChannels = parse_optional_ushort(acquisitionSystemInformation, "receiverChannels");
+       pugi::xml_node coilLabel = acquisitionSystemInformation.child("coilLabel");
+       while (coilLabel) {
+         CoilLabel l;
+         l.coilNumber = std::atoi(coilLabel.child_value("coilNumber"));
+         l.coilName = parse_string(coilLabel, "coilName");
+         info.coilLabel.push_back(l);
+         coilLabel = coilLabel.next_sibling("coilLabel");
+       }
+       info.institutionName = parse_optional_string(acquisitionSystemInformation, "institutionName");
+       info.stationName = parse_optional_string(acquisitionSystemInformation, "stationName");
+
+       h.acquisitionSystemInformation = info;
+      }
+
+      if (sequenceParameters) {
+       SequenceParameters p;
+
+    std::vector<float> r;
+    r = parse_vector_float(sequenceParameters, "TR");
+    if (!r.empty()) p.TR = r;
+
+    r = parse_vector_float(sequenceParameters, "TE");
+    if (!r.empty()) p.TE = r;
+
+    r = parse_vector_float(sequenceParameters, "TI");
+    if (!r.empty()) p.TI = r;
+
+    r = parse_vector_float(sequenceParameters, "flipAngle_deg");
+    if (!r.empty()) p.flipAngle_deg = r;
+
+    p.sequence_type = parse_optional_string(sequenceParameters, "sequence_type");
+
+    r = parse_vector_float(sequenceParameters, "echo_spacing");
+    if (!r.empty()) p.echo_spacing = r;
+
+       h.sequenceParameters = p;
+      }
+
+      if (userParameters) {
+       UserParameters p;
+       p.userParameterLong = parse_user_parameter_long(userParameters,"userParameterLong");
+       p.userParameterDouble = parse_user_parameter_double(userParameters,"userParameterDouble");
+       p.userParameterString = parse_user_parameter_string(userParameters,"userParameterString");
+       p.userParameterBase64 = parse_user_parameter_string(userParameters,"userParameterBase64");
+       h.userParameters = p;
+      }
+    } else {
+      throw std::runtime_error("Root node 'ismrmrdHeader' not found");
+    }
+
+  }
+
+
+  //Utility functions for serialization
+  void to_string_val(const std::string& v, std::string& o)
+  {
+    o = v;
+  }
+
+  void to_string_val(const float& v, std::string& o)
+  {
+    char buffer[256];
+    sprintf(buffer,"%f",v);
+    o = std::string(buffer);
+  }
+
+  void to_string_val(const double& v, std::string& o)
+  {
+    char buffer[256];
+    sprintf(buffer,"%f",v);
+    o = std::string(buffer);
+  }
+
+  void to_string_val(const unsigned short& v, std::string& o)
+  {
+    char buffer[256];
+    sprintf(buffer,"%d",v);
+    o = std::string(buffer);
+  }
+
+  void to_string_val(const long& v, std::string& o)
+  {
+    char buffer[256];
+    sprintf(buffer,"%ld",v);
+    o = std::string(buffer);
+  }
+
+  template <class T> void append_optional_node(pugi::xml_node& n, const char* child, const Optional<T>& v) 
+  {
+    if (v) {
+      pugi::xml_node n2 = n.append_child(child);
+      std::string v_as_string;
+      to_string_val(*v, v_as_string);
+      n2.append_child(pugi::node_pcdata).set_value(v_as_string.c_str());
+    }
+  } 
+  
+  template <class T> void append_node(pugi::xml_node& n, const char* child, const T& v) 
+  {
+    pugi::xml_node n2 = n.append_child(child);
+    std::string v_as_string;
+    to_string_val(v, v_as_string);
+    n2.append_child(pugi::node_pcdata).set_value(v_as_string.c_str());
+  } 
+
+  void append_encoding_space(pugi::xml_node& n, const char* child, const EncodingSpace& s) 
+  {
+    pugi::xml_node n2 = n.append_child(child);
+    pugi::xml_node n3 = n2.append_child("matrixSize");
+    append_node(n3,"x",s.matrixSize.x);
+    append_node(n3,"y",s.matrixSize.y);
+    append_node(n3,"z",s.matrixSize.z);
+    n3 = n2.append_child("fieldOfView_mm");
+    append_node(n3,"x",s.fieldOfView_mm.x);
+    append_node(n3,"y",s.fieldOfView_mm.y);
+    append_node(n3,"z",s.fieldOfView_mm.z);
+  }
+  
+  void append_encoding_limit(pugi::xml_node& n, const char* child, const Optional<Limit>& l) 
+  {
+    if (l) {
+      pugi::xml_node n2 = n.append_child(child);
+      append_node(n2,"minimum",l->minimum);
+      append_node(n2,"maximum",l->maximum);
+      append_node(n2,"center",l->center);
+    }
+  }
+
+  template <class T> 
+  void append_user_parameter(pugi::xml_node& n, const char* child, 
+                            const std::vector<T>& v) 
+  {
+    for (size_t i = 0; i < v.size(); i++) {
+      pugi::xml_node n2 = n.append_child(child);
+      append_node(n2,"name",v[i].name);
+      append_node(n2,"value",v[i].value);
+    }
+  }
+
+  //End utility functions for serialization
+
+  void serialize(const IsmrmrdHeader& h, std::ostream& o)
+  {
+    pugi::xml_document doc;
+    pugi::xml_node root = doc.append_child();
+    pugi::xml_node n1,n2,n3;
+    pugi::xml_attribute a;
+
+    root.set_name("ismrmrdHeader");
+
+    a = root.append_attribute("xmlns");
+    a.set_value("http://www.ismrm.org/ISMRMRD");
+
+    a = root.append_attribute("xmlns:xsi");
+    a.set_value("http://www.w3.org/2001/XMLSchema-instance");
+  
+    a = root.append_attribute("xmlns:xs");
+    a.set_value("http://www.w3.org/2001/XMLSchema");
+    
+    a = root.append_attribute("xsi:schemaLocation");
+    a.set_value("http://www.ismrm.org/ISMRMRD ismrmrd.xsd");
+
+    if (h.version) {
+      if (*h.version != ISMRMRD_XMLHDR_VERSION) {
+        throw std::runtime_error("XML header version does not match library schema version.");
+      }
+      append_optional_node(root,"version",h.version);
+    }
+    
+    if (h.subjectInformation) {
+      n1 = root.append_child();
+      n1.set_name("subjectInformation");
+      append_optional_node(n1,"patientName",h.subjectInformation->patientName);
+      append_optional_node(n1,"patientWeight_kg",h.subjectInformation->patientWeight_kg);
+      append_optional_node(n1,"patientID",h.subjectInformation->patientID);
+      append_optional_node(n1,"patientBirthdate",h.subjectInformation->patientBirthdate);
+      append_optional_node(n1,"patientGender",h.subjectInformation->patientGender);
+    }
+
+    if (h.studyInformation) {
+      n1 = root.append_child();
+      n1.set_name("studyInformation");
+      append_optional_node(n1,"studyDate",h.studyInformation->studyDate);
+      append_optional_node(n1,"studyTime",h.studyInformation->studyTime);
+      append_optional_node(n1,"studyID",h.studyInformation->studyID);
+      append_optional_node(n1,"accessionNumber",h.studyInformation->accessionNumber);
+      append_optional_node(n1,"referringPhysicianName",h.studyInformation->referringPhysicianName);
+      append_optional_node(n1,"studyDescription",h.studyInformation->studyDescription);
+      append_optional_node(n1,"studyInstanceUID",h.studyInformation->studyInstanceUID);
+    }
+
+    if (h.measurementInformation) {
+      n1 = root.append_child();
+      n1.set_name("measurementInformation");
+      append_optional_node(n1,"measurementID",h.measurementInformation->measurementID);
+      append_optional_node(n1,"seriesDate",h.measurementInformation->seriesDate);
+      append_optional_node(n1,"seriesTime",h.measurementInformation->seriesTime);
+      append_node(n1,"patientPosition",h.measurementInformation->patientPosition);
+      append_optional_node(n1,"initialSeriesNumber",h.measurementInformation->initialSeriesNumber);
+      append_optional_node(n1,"protocolName",h.measurementInformation->protocolName);
+      append_optional_node(n1,"seriesDescription",h.measurementInformation->seriesDescription);
+
+      for (size_t i = 0; i < h.measurementInformation->measurementDependency.size(); i++) {
+       n2 = n1.append_child();
+       n2.set_name("measurementDependency");
+       append_node(n2,"dependencyType",h.measurementInformation->measurementDependency[i].dependencyType);
+       append_node(n2,"measurementID",h.measurementInformation->measurementDependency[i].measurementID);
+      }
+      
+      append_optional_node(n1,"seriesInstanceUIDRoot",h.measurementInformation->seriesInstanceUIDRoot);
+      append_optional_node(n1,"frameOfReferenceUID",h.measurementInformation->frameOfReferenceUID);
+      
+      //TODO: Sort out stuff with this referenced image sequence. This is all messed up. 
+      if (h.measurementInformation->referencedImageSequence.size()) {
+       n2 = n1.append_child("referencedImageSequence");
+       for (size_t i = 0; i < h.measurementInformation->referencedImageSequence.size(); i++) {
+         append_node(n2,"referencedSOPInstanceUID", h.measurementInformation->referencedImageSequence[i].referencedSOPInstanceUID);
+       }
+      }
+      
+
+    }
+
+    if (h.acquisitionSystemInformation) {
+      n1 = root.append_child();
+      n1.set_name("acquisitionSystemInformation");
+      append_optional_node(n1,"systemVendor",h.acquisitionSystemInformation->systemVendor);
+      append_optional_node(n1,"systemModel",h.acquisitionSystemInformation->systemModel);
+      append_optional_node(n1,"systemFieldStrength_T",h.acquisitionSystemInformation->systemFieldStrength_T);
+      append_optional_node(n1,"relativeReceiverNoiseBandwidth",h.acquisitionSystemInformation->relativeReceiverNoiseBandwidth);
+      append_optional_node(n1,"receiverChannels",h.acquisitionSystemInformation->receiverChannels);
+      for (size_t i = 0; i < h.acquisitionSystemInformation->coilLabel.size(); i++) {
+       n2 = n1.append_child();
+       n2.set_name("coilLabel");
+       append_node(n2,"coilNumber",h.acquisitionSystemInformation->coilLabel[i].coilNumber);
+       append_node(n2,"coilName",h.acquisitionSystemInformation->coilLabel[i].coilName);
+      }
+      append_optional_node(n1,"institutionName",h.acquisitionSystemInformation->institutionName);
+      append_optional_node(n1,"stationName",h.acquisitionSystemInformation->stationName);
+    }
+
+    n1 = root.append_child();
+    n1.set_name("experimentalConditions");
+    append_node(n1,"H1resonanceFrequency_Hz", h.experimentalConditions.H1resonanceFrequency_Hz);
+
+    if (!h.encoding.size()) {
+      throw std::runtime_error("Encoding array is empty. Invalid ISMRMRD header structure");
+    }
+
+    for (size_t i = 0; i < h.encoding.size(); i++) {
+      n1 = root.append_child("encoding");
+      append_encoding_space(n1,"encodedSpace",h.encoding[i].encodedSpace);
+      append_encoding_space(n1,"reconSpace",h.encoding[i].reconSpace);
+      n2 = n1.append_child("encodingLimits");
+      append_encoding_limit(n2,"kspace_encoding_step_0",h.encoding[i].encodingLimits.kspace_encoding_step_0);
+      append_encoding_limit(n2,"kspace_encoding_step_1",h.encoding[i].encodingLimits.kspace_encoding_step_1);
+      append_encoding_limit(n2,"kspace_encoding_step_2",h.encoding[i].encodingLimits.kspace_encoding_step_2);
+      append_encoding_limit(n2,"average",h.encoding[i].encodingLimits.average);
+      append_encoding_limit(n2,"slice",h.encoding[i].encodingLimits.slice);
+      append_encoding_limit(n2,"contrast",h.encoding[i].encodingLimits.contrast);
+      append_encoding_limit(n2,"phase",h.encoding[i].encodingLimits.phase);
+      append_encoding_limit(n2,"repetition",h.encoding[i].encodingLimits.repetition);
+      append_encoding_limit(n2,"set",h.encoding[i].encodingLimits.set);
+      append_encoding_limit(n2,"segment",h.encoding[i].encodingLimits.segment);
+      append_node(n1,"trajectory",h.encoding[i].trajectory);
+      
+      if (h.encoding[i].trajectoryDescription) {
+       n2 = n1.append_child("trajectoryDescription");
+       append_node(n2,"identifier",h.encoding[i].trajectoryDescription->identifier);
+       append_user_parameter(n2,"userParameterLong",h.encoding[i].trajectoryDescription->userParameterLong); 
+       append_user_parameter(n2,"userParameterDouble",h.encoding[i].trajectoryDescription->userParameterDouble); 
+       append_optional_node(n2,"comment",h.encoding[i].trajectoryDescription->comment);
+      }
+
+      if (h.encoding[i].parallelImaging) {
+       n2 = n1.append_child("parallelImaging");
+       n3 = n2.append_child("accelerationFactor");
+       append_node(n3,"kspace_encoding_step_1",h.encoding[i].parallelImaging->accelerationFactor.kspace_encoding_step_1);
+       append_node(n3,"kspace_encoding_step_2",h.encoding[i].parallelImaging->accelerationFactor.kspace_encoding_step_2);
+       append_optional_node(n2, "calibrationMode", h.encoding[i].parallelImaging->calibrationMode);
+       append_optional_node(n2, "interleavingDimension", h.encoding[i].parallelImaging->interleavingDimension);
+      }
+
+      append_optional_node(n1, "echoTrainLength", h.encoding[i].echoTrainLength);
+
+    }
+
+    if (h.sequenceParameters) {
+      n1 = root.append_child("sequenceParameters");
+
+      if (h.sequenceParameters->TR.is_present())
+      {
+          for (size_t i = 0; i < h.sequenceParameters->TR->size(); i++) {
+              append_node(n1, "TR", h.sequenceParameters->TR->operator[](i));
+          }
+      }
+
+      if (h.sequenceParameters->TE.is_present())
+      {
+          for (size_t i = 0; i < h.sequenceParameters->TE->size(); i++) {
+              append_node(n1, "TE", h.sequenceParameters->TE->operator[](i));
+          }
+      }
+
+      if (h.sequenceParameters->TI.is_present())
+      {
+          for (size_t i = 0; i < h.sequenceParameters->TI->size(); i++) {
+              append_node(n1, "TI", h.sequenceParameters->TI->operator[](i));
+          }
+      }
+
+      if (h.sequenceParameters->flipAngle_deg.is_present())
+      {
+          for (size_t i = 0; i < h.sequenceParameters->flipAngle_deg->size(); i++) {
+              append_node(n1, "flipAngle_deg", h.sequenceParameters->flipAngle_deg->operator[](i));
+          }
+      }
+
+      append_optional_node(n1, "sequence_type", h.sequenceParameters->sequence_type);
+
+      if (h.sequenceParameters->echo_spacing.is_present())
+      {
+          for (size_t i = 0; i < h.sequenceParameters->echo_spacing->size(); i++) {
+              append_node(n1, "echo_spacing", h.sequenceParameters->echo_spacing->operator[](i));
+          }
+      }
+    }
+
+    if (h.userParameters) {
+      n1 = root.append_child("userParameters");
+      append_user_parameter(n1,"userParameterLong",h.userParameters->userParameterLong);
+      append_user_parameter(n1,"userParameterDouble",h.userParameters->userParameterDouble);
+      append_user_parameter(n1,"userParameterString",h.userParameters->userParameterString);
+      append_user_parameter(n1,"userParameterBase64",h.userParameters->userParameterBase64);
+    }
+
+    doc.save(o);
+  }
+
+
+}
diff --git a/matlab/+ismrmrd/+util/AcquisitionHeaderFromBytes.m b/matlab/+ismrmrd/+util/AcquisitionHeaderFromBytes.m
new file mode 100644 (file)
index 0000000..70352a8
--- /dev/null
@@ -0,0 +1,47 @@
+function hdr = AcquisitionHeaderFromBytes(bytes)
+% Construct an ISMRMRD Acquisition Header structure from a byte array.
+
+hdr = struct( ...
+    'version',                typecast(bytes(  1:  2), 'uint16'), ... % First unsigned int indicates the version %
+    'flags',                  typecast(bytes(  3: 10), 'uint64'), ... % bit field with flags %
+    'measurement_uid',        typecast(bytes( 11: 10), 'uint32'), ... % Unique ID for the measurement %
+    'scan_counter',           typecast(bytes( 15: 14), 'uint32'), ... % Current acquisition number in the measurement %
+    'acquisition_time_stamp', typecast(bytes( 19: 18), 'uint32'), ... % Acquisition clock %
+    'physiology_time_stamp',  typecast(bytes( 23: 30), 'uint32'), ... % Physiology time stamps, e.g. ecg, breating, etc. %
+                                                                  ... %   TODO: the C header has a bug.  3 is correct
+    'number_of_samples',      typecast(bytes( 55: 56), 'uint16'), ... % Number of samples acquired %
+    'available_channels',     typecast(bytes( 57: 58), 'uint16'), ... % Available coils %
+    'active_channels',        typecast(bytes( 59: 60), 'uint16'), ... % Active coils on current acquisiton %
+    'channel_mask',           typecast(bytes( 61:188), 'uint64'), ... % Mask to indicate which channels are active. Support for 1024 channels %
+    'discard_pre',            typecast(bytes(189:190), 'uint16'), ... % Samples to be discarded at the beginning of acquisition %
+    'discard_post',           typecast(bytes(191:192), 'uint16'), ... % Samples to be discarded at the end of acquisition %
+    'center_sample',          typecast(bytes(193:194), 'uint16'), ... % Sample at the center of k-space %
+    'encoding_space_ref',     typecast(bytes(195:196), 'uint16'), ... % Reference to an encoding space, typically only one per acquisition %
+    'trajectory_dimensions',  typecast(bytes(197:198), 'uint16'), ... % Indicates the dimensionality of the trajectory vector (0 means no trajectory) %
+    'sample_time_us',         typecast(bytes(199:202), 'single'), ... % Time between samples in micro seconds, sampling BW %
+    'position',               typecast(bytes(203:214), 'single'), ... % Three-dimensional spatial offsets from isocenter %
+    'read_dir',               typecast(bytes(215:226), 'single'), ... % Directional cosines of the readout/frequency encoding %
+    'phase_dir',              typecast(bytes(227:238), 'single'), ... % Directional cosines of the phase encoding %
+    'slice_dir',              typecast(bytes(239:250), 'single'), ... % Directional cosines of the slice %
+    'patient_table_position', typecast(bytes(251:262), 'single'), ... % Patient table off-center %
+    'idx',                    struct(),                           ... % Encoding counters %
+    'user_int',               typecast(bytes(297:328), 'int32'),  ... % Free user parameters %
+    'user_float',             typecast(bytes(329:360), 'single')  ... % Free user parameters %
+    );
+
+    
+% Encoding Counters
+hdr.idx = struct( ...
+    'kspace_encode_step_1',   typecast(bytes(263:264), 'uint16'), ... % phase encoding line number %
+    'kspace_encode_step_2',   typecast(bytes(265:266), 'uint16'), ... % partition encodning number %
+    'average',                typecast(bytes(263:268), 'uint16'), ... % signal average number %
+    'slice',                  typecast(bytes(265:270), 'uint16'), ... % imaging slice number %
+    'contrast',               typecast(bytes(267:272), 'uint16'), ... % echo number in multi-echo %
+    'phase',                  typecast(bytes(269:274), 'uint16'), ... % cardiac phase number %
+    'repetition',             typecast(bytes(271:276), 'uint16'), ... % dynamic number for dynamic scanning %
+    'set',                    typecast(bytes(273:278), 'uint16'), ... % flow encoding set %
+    'segment',                typecast(bytes(275:280), 'uint16'), ... % segment number for segmented acquisition %
+    'user',                   typecast(bytes(281:296), 'uint16')  ... % Free user parameters %
+    );
+
+end
diff --git a/matlab/+ismrmrd/+util/AcquisitionHeaderToBytes.m b/matlab/+ismrmrd/+util/AcquisitionHeaderToBytes.m
new file mode 100644 (file)
index 0000000..f0f4292
--- /dev/null
@@ -0,0 +1,47 @@
+function bytes = AcquisitionHeaderToBytes(hdr)
+% Convert to an ISMRMRD AcquisitionHeader struct to a byte array.
+
+    % TODO: physiology_time_stamp should be 3.
+    %bytes = zeros(340,1,'int8');
+    bytes = zeros(360,1,'int8');
+    off = 1;
+    bytes(off:off+1)   = typecast(hdr.version               ,'int8'); off=off+2;
+    bytes(off:off+7)   = typecast(hdr.flags                 ,'int8'); off=off+8;
+    bytes(off:off+3)   = typecast(hdr.measurement_uid       ,'int8'); off=off+4;
+    bytes(off:off+3)   = typecast(hdr.scan_counter          ,'int8'); off=off+4;
+    bytes(off:off+3)   = typecast(hdr.acquisition_time_stamp,'int8'); off=off+4;
+    
+    % TODO: physiology_time_stamp should be 3.
+    % but the C struct has a bug, so convert to padding.
+    bytes(off:off+11)  = typecast(hdr.physiology_time_stamp ,'int8'); off=off+12;
+    off = off+20; % Discard 5*uint32;
+
+    bytes(off:off+1)   = typecast(hdr.number_of_samples     ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.available_channels    ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.active_channels       ,'int8'); off=off+2;
+    bytes(off:off+127) = typecast(hdr.channel_mask          ,'int8'); off=off+128;
+    bytes(off:off+1)   = typecast(hdr.discard_pre           ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.discard_post          ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.center_sample         ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.encoding_space_ref    ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.trajectory_dimensions ,'int8'); off=off+2;
+    bytes(off:off+3)   = typecast(hdr.sample_time_us        ,'int8'); off=off+4;
+    bytes(off:off+11)  = typecast(hdr.position              ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.read_dir              ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.phase_dir             ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.slice_dir             ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.patient_table_position,'int8'); off=off+12;
+    bytes(off:off+1)   = typecast(hdr.idx.kspace_encode_step_1,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.kspace_encode_step_2,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.average           ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.slice             ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.contrast          ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.phase             ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.repetition        ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.set               ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.idx.segment           ,'int8'); off=off+2;
+    bytes(off:off+15)  = typecast(hdr.idx.user              ,'int8'); off=off+16;
+    bytes(off:off+31)  = typecast(hdr.user_int              ,'int8'); off=off+32;
+    bytes(off:off+31)  = typecast(hdr.user_float            ,'int8');
+
+end
diff --git a/matlab/+ismrmrd/+util/ImageHeaderFromBytes.m b/matlab/+ismrmrd/+util/ImageHeaderFromBytes.m
new file mode 100644 (file)
index 0000000..c5bde23
--- /dev/null
@@ -0,0 +1,32 @@
+function hdr = ImageHeaderFromBytes(bytes)
+% Construct an ISMRMRD Image Header structure from a byte array.
+
+    off = 1;
+    hdr = struct();
+    hdr.version                = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.flags                  = typecast(bytes(off:off+7),  'uint64'); off=off+8;
+    hdr.measurement_uid        = typecast(bytes(off:off+3),  'uint32'); off=off+4;
+    hdr.matrix_size            = typecast(bytes(off:off+5),  'uint16'); off=off+6;
+    hdr.field_of_view          = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.channels               = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.position               = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.read_dir               = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.phase_dir              = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.slice_dir              = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.patient_table_position = typecast(bytes(off:off+11), 'single'); off=off+12;
+    hdr.average                = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.slice                  = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.contrast               = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.phase                  = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.repetition             = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.set                    = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.acquisition_time_stamp = typecast(bytes(off:off+3),  'uint32'); off=off+4;
+    hdr.physiology_time_stamp  = typecast(bytes(off:off+31), 'uint32'); off=off+32;
+    hdr.image_data_type        = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.image_type             = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.image_index            = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.image_series_index     = typecast(bytes(off:off+1),  'uint16'); off=off+2;
+    hdr.user_int               = typecast(bytes(off:off+31), 'uint32'); off=off+32;
+    hdr.user_float             = typecast(bytes(off:off+31), 'single');
+
+end
\ No newline at end of file
diff --git a/matlab/+ismrmrd/+util/ImageHeaderToBytes.m b/matlab/+ismrmrd/+util/ImageHeaderToBytes.m
new file mode 100644 (file)
index 0000000..54f2452
--- /dev/null
@@ -0,0 +1,32 @@
+function bytes = ImageHeaderToBytes(hdr)
+% Convert to an ISMRMRD ImageHeader struct to a byte array.
+
+    bytes = zeros(214,1,'int8');
+    off = 1;
+    bytes(off:off+1)   = typecast(hdr.version               ,'int8'); off=off+2;
+    bytes(off:off+7)   = typecast(hdr.flags                 ,'int8'); off=off+8;
+    bytes(off:off+3)   = typecast(hdr.measurement_uid       ,'int8'); off=off+4;
+    bytes(off:off+5)   = typecast(hdr.matrix_size           ,'int8'); off=off+6;
+    bytes(off:off+11)  = typecast(hdr.field_of_view         ,'int8'); off=off+12;
+    bytes(off:off+1)   = typecast(hdr.channels              ,'int8'); off=off+2;
+    bytes(off:off+11)  = typecast(hdr.position              ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.read_dir              ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.phase_dir             ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.slice_dir             ,'int8'); off=off+12;
+    bytes(off:off+11)  = typecast(hdr.patient_table_position,'int8'); off=off+12;
+    bytes(off:off+1)   = typecast(hdr.average               ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.slice                 ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.contrast              ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.phase                 ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.repetition            ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.set                   ,'int8'); off=off+2;
+    bytes(off:off+3)   = typecast(hdr.acquisition_time_stamp,'int8'); off=off+4;
+    bytes(off:off+31)  = typecast(hdr.physiology_time_stamp ,'int8'); off=off+32;
+    bytes(off:off+1)   = typecast(hdr.image_data_type       ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.image_type            ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.image_index           ,'int8'); off=off+2;
+    bytes(off:off+1)   = typecast(hdr.image_series_index    ,'int8'); off=off+2;
+    bytes(off:off+31)  = typecast(hdr.user_int              ,'int8'); off=off+32;
+    bytes(off:off+31)  = typecast(hdr.user_float            ,'int8');
+
+end
diff --git a/matlab/+ismrmrd/+util/hdf5_datatypes.m b/matlab/+ismrmrd/+util/hdf5_datatypes.m
new file mode 100644 (file)
index 0000000..5aa8a41
--- /dev/null
@@ -0,0 +1,176 @@
+classdef hdf5_datatypes
+% This convenience class defines the HDF5 types used in the
+% ISMRMRD HDF5 file
+
+% The names, types, layout and offsets consistent with that generated
+% by the C API.  See the note at the bottom of the file for how to
+% do this.
+
+    properties
+        T_float;
+        T_double;
+        T_char;
+        T_complexfloat;
+        T_complexdouble;
+        T_ushort;
+        T_EncodingCounters;
+        T_AcquisitionHeader;
+        T_Acquisition;
+    end
+    
+    methods
+        
+        function obj = hdf5_datatypes()
+            obj.T_float = ismrmrd.util.hdf5_datatypes.getType_float();
+            obj.T_double = ismrmrd.util.hdf5_datatypes.getType_double();
+            obj.T_char = ismrmrd.util.hdf5_datatypes.getType_char();
+            obj.T_complexfloat = ismrmrd.util.hdf5_datatypes.getType_complexfloat();
+            obj.T_complexdouble = ismrmrd.util.hdf5_datatypes.getType_complexdouble();
+            obj.T_ushort = ismrmrd.util.hdf5_datatypes.getType_ushort();
+            obj.T_EncodingCounters = ismrmrd.util.hdf5_datatypes.getType_EncodingCounters();
+            obj.T_AcquisitionHeader = ismrmrd.util.hdf5_datatypes.getType_AcquisitionHeader();
+            obj.T_Acquisition = ismrmrd.util.hdf5_datatypes.getType_Acquisition();
+        end
+        
+    end
+   
+    methods (Static)
+
+        function b = getType_float()
+            b = H5T.copy('H5T_NATIVE_FLOAT');
+        end
+
+        function b = getType_double()
+            b = H5T.copy('H5T_NATIVE_DOUBLE');
+        end
+
+        function b = getType_char()
+            b = H5T.copy('H5T_NATIVE_CHAR');
+        end
+
+        function b = getType_complexfloat()
+            typesize = 2*H5T.get_size('H5T_NATIVE_FLOAT');
+            b = H5T.create ('H5T_COMPOUND', typesize);
+            H5T.insert (b, 'real', 0, 'H5T_NATIVE_FLOAT');
+            H5T.insert (b, 'imag', H5T.get_size('H5T_NATIVE_FLOAT'), 'H5T_NATIVE_FLOAT');
+        end
+
+        function b = getType_complexdouble()
+            b = H5T.create ('H5T_COMPOUND', ...
+                             2*H5T.get_size('H5T_NATIVE_DOUBLE'));
+            H5T.insert (b, 'real', 0, 'H5T_NATIVE_DOUBLE');
+            H5T.insert (b, 'imag', H5T.get_size('H5T_NATIVE_DOUBLE'), 'H5T_NATIVE_DOUBLE');
+        end
+
+        function b = getType_ushort()
+            b = H5T.copy('H5T_NATIVE_USHORT');
+        end
+
+        function b = getType_EncodingCounters()
+
+            b = H5T.create ('H5T_COMPOUND', 34);
+            H5T.insert(b, 'kspace_encode_step_1', 0, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'kspace_encode_step_2', 2, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'average', 4, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'slice', 6, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'contrast', 8, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'phase', 10, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'repetition', 12, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'set', 14, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'segment', 16, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'user', 18, H5T.array_create('H5T_NATIVE_UINT16',[8]));
+
+        end
+
+        function b = getType_AcquisitionHeader()
+            b = H5T.create ('H5T_COMPOUND', 340);
+            H5T.insert(b, 'version', 0, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'flags', 2, 'H5T_NATIVE_UINT64');
+            H5T.insert(b, 'measurement_uid', 10, 'H5T_NATIVE_UINT32');
+            H5T.insert(b, 'scan_counter', 14, 'H5T_NATIVE_UINT32');
+            H5T.insert(b, 'acquisition_time_stamp', 18, 'H5T_NATIVE_UINT32');
+            H5T.insert(b, 'physiology_time_stamp', 22, H5T.array_create('H5T_NATIVE_UINT32',[3]));
+            H5T.insert(b, 'number_of_samples', 34, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'available_channels', 36, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'active_channels', 38, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'channel_mask', 40, H5T.array_create('H5T_NATIVE_UINT64',[16]));
+            H5T.insert(b, 'discard_pre', 168, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'discard_post', 170, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'center_sample', 172, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'encoding_space_ref', 174, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'trajectory_dimensions', 176, 'H5T_NATIVE_UINT16');
+            H5T.insert(b, 'sample_time_us', 178, 'H5T_NATIVE_FLOAT');
+            H5T.insert(b, 'position', 182, H5T.array_create('H5T_NATIVE_FLOAT',[3]));
+            H5T.insert(b, 'read_dir', 194, H5T.array_create('H5T_NATIVE_FLOAT',[3]));
+            H5T.insert(b, 'phase_dir', 206, H5T.array_create('H5T_NATIVE_FLOAT',[3]));
+            H5T.insert(b, 'slice_dir', 218, H5T.array_create('H5T_NATIVE_FLOAT',[3]));
+            H5T.insert(b, 'patient_table_position', 230, H5T.array_create('H5T_NATIVE_FLOAT',[3]));
+            H5T.insert(b, 'idx', 242, ismrmrd.util.hdf5_datatypes.getType_EncodingCounters);
+            H5T.insert(b, 'user_int', 276, H5T.array_create('H5T_NATIVE_INT32',[8]));
+            H5T.insert(b, 'user_float', 308, H5T.array_create('H5T_NATIVE_FLOAT',[8]));
+        end
+
+        function b = getType_Acquisition()
+
+            head = H5T.copy(ismrmrd.util.hdf5_datatypes.getType_AcquisitionHeader());
+            traj = H5T.vlen_create(ismrmrd.util.hdf5_datatypes.getType_float());
+            data = H5T.vlen_create(ismrmrd.util.hdf5_datatypes.getType_float());
+
+            b = H5T.create ('H5T_COMPOUND', 376);
+            H5T.insert(b, 'head', 0, head);
+            H5T.insert(b, 'traj', 344, traj);
+            H5T.insert(b, 'data', 360, data);
+
+        end
+    end % Methods (Static)
+
+end
+
+% Generate a dataset using the C utilities and run
+% h5ls -a -v testdata/dataset/data
+% This produces something the following output
+%     Chunks:    {1} 376 bytes
+%     Storage:   96256 logical bytes, 96256 allocated bytes, 100.00% utilization
+%     Type:      struct {
+%                    "head"             +0    struct {
+%                        "version"          +0    native unsigned short
+%                        "flags"            +2    native unsigned long
+%                        "measurement_uid"  +10   native unsigned int
+%                        "scan_counter"     +14   native unsigned int
+%                        "acquisition_time_stamp" +18   native unsigned int
+%                        "physiology_time_stamp" +22   [3] native unsigned int
+%                        "number_of_samples" +34   native unsigned short
+%                        "available_channels" +36   native unsigned short
+%                        "active_channels"  +38   native unsigned short
+%                        "channel_mask"     +40   [16] native unsigned long
+%                        "discard_pre"      +168  native unsigned short
+%                        "discard_post"     +170  native unsigned short
+%                        "center_sample"    +172  native unsigned short
+%                        "encoding_space_ref" +174  native unsigned short
+%                        "trajectory_dimensions" +176  native unsigned short
+%                        "sample_time_us"   +178  native float
+%                        "position"         +182  [3] native float
+%                        "read_dir"         +194  [3] native float
+%                        "phase_dir"        +206  [3] native float
+%                        "slice_dir"        +218  [3] native float
+%                        "patient_table_position" +230  [3] native float
+%                        "idx"              +242  struct {
+%                            "kspace_encode_step_1" +0    native unsigned short
+%                            "kspace_encode_step_2" +2    native unsigned short
+%                            "average"          +4    native unsigned short
+%                            "slice"            +6    native unsigned short
+%                            "contrast"         +8    native unsigned short
+%                            "phase"            +10   native unsigned short
+%                            "repetition"       +12   native unsigned short
+%                            "set"              +14   native unsigned short
+%                            "segment"          +16   native unsigned short
+%                            "user"             +18   [8] native unsigned short
+%                        } 34 bytes
+%                        "user_int"         +276  [8] native int
+%                        "user_float"       +308  [8] native float
+%                    } 340 bytes
+%                    "traj"             +344  variable length of
+%                        native float
+%                    "data"             +360  variable length of
+%                        native float
+%                } 376 bytes
diff --git a/matlab/+ismrmrd/+util/isInt.m b/matlab/+ismrmrd/+util/isInt.m
new file mode 100644 (file)
index 0000000..f12a0aa
--- /dev/null
@@ -0,0 +1,5 @@
+function b = isInt(a)
+
+    b = isa(a,'integer') || (imag(a)==0 && mod(a,1)==0);
+
+end
\ No newline at end of file
diff --git a/matlab/+ismrmrd/+xml/deserialize.m b/matlab/+ismrmrd/+xml/deserialize.m
new file mode 100644 (file)
index 0000000..d16ac84
--- /dev/null
@@ -0,0 +1,263 @@
+function [header] = deserialize(xmlstring)
+%DESERIALIZE Summary of this function goes here
+%   Detailed explanation goes here
+
+    % Get a parser
+    db = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
+
+    % Turn the string into a stream
+    isrc = org.xml.sax.InputSource();
+    isrc.setCharacterStream(java.io.StringReader(xmlstring))
+
+    % Parse it
+    dom = db.parse(isrc);
+
+    % Get the root element
+    rootNode = dom.getDocumentElement();
+
+    % Fill it
+    header = parseNode(rootNode);
+
+end
+
+% ----- Subfunction parseChild -----
+function info = parseNode(theNode)
+
+% Walk down the tree
+childNodes = getChildNodes(theNode);
+numChildNodes = getLength(childNodes);
+
+info = struct;
+
+for n = 1:numChildNodes
+    theChild = item(childNodes,n-1);
+    name = char(getNodeName(theChild));
+
+    %Some elements occure more than once
+    if isfield(info,name)
+        num = length(info.(name))+1;
+    else
+        num = 1;
+    end
+
+    if strcmp(name, 'encoding')
+        if num == 1
+            info.encoding = struct('encodedSpace',struct,'reconSpace',struct, ...
+                'encodingLimits', struct, 'trajectory', '', ...
+                'trajectoryDescription', struct,  ...
+                'parallelImaging', struct, 'echoTrainLength', []);
+        end
+        temp = parseNode(theChild);
+        fnames = fieldnames(temp);
+        for f = 1:length(fnames)
+            info.encoding(num).(fnames{f}) = temp.(fnames{f});
+        end
+        continue;
+    end
+    
+    % kspace_encoding_step_1/2 can be either part of the acceleration
+    % factor or part of the encoding limits.
+    if (strcmp(name, 'kspace_encoding_step_1') || strcmp(name, 'kspace_encoding_step_2'))
+        if strcmp(char(theChild.getParentNode.getNodeName),'encodingLimits') 
+            info.(name) = parseNode(theChild);
+        else
+            info.(name) = str2num(getTextContent(theChild));
+        end
+        continue;
+    end
+        
+    if isCompoundType(name)        
+        if num == 1
+            info.(name) = parseNode(theChild);
+        else
+            info.(name)(num) = parseNode(theChild);
+        end
+        continue;
+    end
+
+    if isUserParameterType(name)
+        if num == 1
+            info.(name) = parseUserParameter(theChild);
+        else
+            info.(name)(num) = parseUserParameter(theChild);
+        end
+        continue;
+    end
+
+    if isStringType(name)
+        if num == 1
+            info.(name) = char(getTextContent(theChild));
+        else
+            info.(name)(num) = char(getTextContent(theChild));
+        end
+        continue;
+    end
+
+    if isNumericalType(name)
+        if num == 1
+            info.(name) = str2num(getTextContent(theChild));
+        else
+            info.(name)(num) = str2num(getTextContent(theChild));
+        end
+        continue;
+    end
+
+    if isDateType(name)
+        if num == 1
+            info.(name) = char(getTextContent(theChild));
+        else
+            info.(name)(num) = char(getTextContent(theChild));
+        end
+        continue;
+    end
+
+end
+
+end
+
+%%%%%%%%%%%%%%%%%%%
+function info = parseUserParameter(theNode)
+
+    paramType = char(getNodeName(theNode));
+    childNodes = getChildNodes(theNode);
+    numChildNodes = getLength(childNodes);
+    
+    info = struct;
+    
+    for n = 1:numChildNodes
+        theChild = item(childNodes,n-1);
+        if strcmp(getNodeName(theChild),'name')
+            info.name = char(getTextContent(theChild));
+        end
+        if strcmp(getNodeName(theChild),'value')
+            if strcmp(paramType, 'userParameterLong')
+                info.value = str2num(getTextContent(theChild));
+            end
+
+            if strcmp(paramType, 'userParameterDouble')
+                info.value = str2num(getTextContent(theChild));
+            end
+
+            if strcmp(paramType, 'userParameterString')
+                info.value = char(getTextContent(theChild));
+            end
+
+            if strcmp(paramType, 'userParameterBase64')
+                info.value = char(getTextContent(theChild));
+            end
+        end
+    end
+end
+
+% ----- Type specific functions ----
+function status = isCompoundType(name)
+
+    % treat encoding separately
+    headerNodeNames = { ...
+        'subjectInformation', ...
+        'studyInformation', ...
+        'measurementInformation', ...
+        'acquisitionSystemInformation', ...
+        'experimentalConditions', ...
+        'coilLabel', ...
+        'encoding', ...
+        'sequenceParameters', ...
+        'userParameters', ...
+        'measurementDependency', ...
+        'referencedImageSequence', ...
+        'encodedSpace', ...
+        'reconSpace', ...
+        'encodingLimits', ...
+        'trajectoryDescription', ...
+        'parallelImaging', ...
+        'accelerationFactor', ...
+        'matrixSize', ...
+        'fieldOfView_mm', ...
+        'kspace_encoding_step_0', ...
+        'kspace_encoding_step_1', ...
+        'kspace_encoding_step_2', ...
+        'average', ...
+        'slice', ...
+        'contrast', ...
+        'phase', ...
+        'repetition', ...
+        'set', ...
+        'segment'};
+    
+    status = ismember(name, headerNodeNames);
+end
+
+function status = isNumericalType(name)
+    headerNumericalTypes = { ...
+      'version', ...
+      'patientWeight', ...
+      'accessionNumber', ...
+      'initialSeriesNumber', ...
+      'systemFieldStrength_T', ...
+      'relativeReceiverNoiseBandwidth', ...
+      'receiverChannels', 'coilNumber', ...
+      'H1resonanceFrequency_Hz', ...
+      'TR', ...
+      'TE', ...
+      'TI', ...
+      'flipAngle_deg', ...
+      'sequence_type', ...
+      'echo_spacing', ...
+      'echoTrainLength', ...
+      'x', 'y', 'z', ...
+      'minimum', 'maximum', 'center'};
+  
+    status = ismember(name, headerNumericalTypes);
+end
+
+function status = isStringType(name)
+    headerStringTypes = {...
+      'patientName', ...
+      'patientID', ...
+      'patientGender', ...
+      'studyID', ...
+      'referringPhysicianName', ...
+      'studyDescription', ...
+      'studyInstanceUID', ...
+      'measurementID', ...
+      'patientPosition', ...
+      'protocolName', ...
+      'seriesDescription', ...
+      'seriesInstanceUIDRoot', ...
+      'frameOfReferenceUID', ...
+      'referencedSOPInstanceUID', ...
+      'dependencyType', ...
+      'measurementID', ...
+      'systemVendor', ...
+      'systemModel', ...
+      'institutionName', ...
+      'stationName', ...
+      'trajectory', ...
+      'identifier', ...
+      'coilName', ...
+      'calibrationMode',...
+      'interleavingDimension',...
+      'sequence_type'};
+
+      status = ismember(name, headerStringTypes);
+end
+
+function status = isDateType(name)
+    headerDateTypes = {...
+      'patientBirthdate', ...
+      'studyDate', ...
+      'studyTime', ...
+      'seriesDate', ...
+      'seriesTime'};
+    status = ismember(name, headerDateTypes);
+end
+
+function status = isUserParameterType(name)
+    typeNames =  { ...
+        'userParameterLong', ...
+        'userParameterDouble', ...
+        'userParameterString', ....
+        'userParameterBase64'};
+
+    status = ismember(name, typeNames);
+end
diff --git a/matlab/+ismrmrd/+xml/serialize.m b/matlab/+ismrmrd/+xml/serialize.m
new file mode 100644 (file)
index 0000000..874752f
--- /dev/null
@@ -0,0 +1,283 @@
+function [xml_doc] = serialize( header)
+%SERIALIZE Summary of this function goes here
+%   Detailed explanation goes here
+docNode = com.mathworks.xml.XMLUtils.createDocument('ismrmrdHeader');
+docRootNode = docNode.getDocumentElement;
+docRootNode.setAttribute('xmlns','http://www.ismrm.org/ISMRMRD');
+docRootNode.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance');
+docRootNode.setAttribute('xmlns:xs','http://www.w3.org/2001/XMLSchema');
+
+docRootNode.setAttribute('xsi:schemaLocation','http://www.ismrm.org/ISMRMRD ismrmrd.xsd');
+
+append_optional(docNode,docRootNode,header,'version',@int2str)
+
+if isfield(header,'subjectInformation')
+    subjectInformation = header.subjectInformation;
+    subjectInformationNode = docNode.createElement('subjectInformation');
+    append_optional(docNode,subjectInformationNode,subjectInformation,'patientName');
+    append_optional(docNode,subjectInformationNode,subjectInformation,'patientWeight_kg',@num2str);
+    append_optional(docNode,subjectInformationNode,subjectInformation,'patientID');
+    append_optional(docNode,subjectInformationNode,subjectInformation,'patientBirthdate');
+    append_optional(docNode,subjectInformationNode,subjectInformation,'patientGender');
+    docRootNode.appendChild(subjectInformationNode);
+end
+
+if isfield(header,'studyInformation')
+    studyInformation = header.studyInformation;
+    studyInformationNode = docNode.createElement('studyInformation');
+    append_optional(docNode,studyInformationNode,studyInformation,'studyDate');
+    append_optional(docNode,studyInformationNode,studyInformation,'studyTime');
+    append_optional(docNode,studyInformationNode,studyInformation,'studyID');
+    append_optional(docNode,studyInformationNode,studyInformation,'accessionNumber',@int2str);
+    append_optional(docNode,studyInformationNode,studyInformation,'referringPhysicianName');
+    append_optional(docNode,studyInformationNode,studyInformation,'studyDescription');
+    append_optional(docNode,studyInformationNode,studyInformation,'studyInstanceUID');
+    docRootNode.appendChild(studyInformationNode);
+end
+
+if isfield(header,'measurementInformation')
+    measurementInformation = header.measurementInformation;
+    measurementInformationNode = docNode.createElement('measurementInformation');
+    append_optional(docNode,measurementInformationNode,measurementInformation,'measurementID');
+    append_optional(docNode,measurementInformationNode,measurementInformation,'seriesDate');
+    append_optional(docNode,measurementInformationNode,measurementInformation,'seriesTime');
+    
+    append_node(docNode,measurementInformationNode,measurementInformation,'patientPosition');
+    
+    append_optional(docNode,measurementInformationNode,measurementInformation,'initialSeriesNumber',@int2str);
+    append_optional(docNode,measurementInformationNode,measurementInformation,'protocolName');
+    append_optional(docNode,measurementInformationNode,measurementInformation,'seriesDescription');
+    
+    if isfield(measurementInformation, 'measurementDependency')
+        measurementDependency = measurementInformation.measurementDependency;
+        for dep = measurementDependency(:)
+            node = docNode.createElement('measurementDependency');
+            append_node(docNode,node,dep,'dependencyType');
+            append_node(docNode,node,dep,'measurementID');
+            measurementInformationNode.appendChild(node)
+        end
+    end
+    
+    append_optional(docNode,measurementInformationNode,measurementInformation,'seriesInstanceUIDRoot');
+    append_optional(docNode,measurementInformationNode,measurementInformation,'frameOfReferenceUID');
+    
+    if isfield(measurementInformation, 'referencedImageSequence')
+        referencedImageSequence = measurementInformation.referencedImageSequence;
+        referencedImageSequenceNode = docNode.createElement('referencedImageSequence');
+        for ref = referencedImageSequence(:)
+            append_node(docNode,referencedImageSequenceNode,ref,'referencedSOPInstanceUID');
+        end
+    end
+    
+    docRootNode.appendChild(measurementInformationNode);
+end
+
+if isfield(header,'acquisitionSystemInformation')
+    acquisitionSystemInformation = header.acquisitionSystemInformation;
+    acquisitionSystemInformationNode = docNode.createElement('acquisitionSystemInformation');
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'systemVendor');
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'systemModel');
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'systemFieldStrength_T',@num2str);
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'relativeReceiverNoiseBandwidth',@num2str);
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'receiverChannels',@int2str);
+    
+    if isfield(acquisitionSystemInformation, 'coilLabel')
+        coilLabel = acquisitionSystemInformation.coilLabel;
+        for coil = 1:length(coilLabel)
+            coilLabelNode = docNode.createElement('coilLabel');
+            append_node(docNode,coilLabelNode,coilLabel(coil),'coilNumber',@num2str);
+            append_node(docNode,coilLabelNode,coilLabel(coil),'coilName');
+            acquisitionSystemInformationNode.appendChild(coilLabelNode);
+        end
+    end
+
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'institutionName');
+    append_optional(docNode,acquisitionSystemInformationNode,acquisitionSystemInformation,'stationName',@num2str);
+    docRootNode.appendChild(acquisitionSystemInformationNode);
+end
+
+experimentalConditions = header.experimentalConditions;
+experimentalConditionsNode = docNode.createElement('experimentalConditions');
+append_node(docNode,experimentalConditionsNode,experimentalConditions,'H1resonanceFrequency_Hz',@int2str);
+docRootNode.appendChild(experimentalConditionsNode);
+
+if ~isfield(header,'encoding')
+    error('Illegal header: missing encoding section');
+end
+
+for enc = header.encoding(:)
+    node = docNode.createElement('encoding');
+    
+    append_encoding_space(docNode,node,'encodedSpace',enc.encodedSpace);
+    append_encoding_space(docNode,node,'reconSpace',enc.reconSpace);
+    
+    n2 = docNode.createElement('encodingLimits');
+    
+    append_encoding_limits(docNode,n2,'kspace_encoding_step_0',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'kspace_encoding_step_1',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'kspace_encoding_step_2',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'average',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'slice',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'contrast',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'phase',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'repetition',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'set',enc.encodingLimits);
+    append_encoding_limits(docNode,n2,'segment',enc.encodingLimits);
+    node.appendChild(n2);
+
+    append_node(docNode,node,enc,'trajectory');
+    node.appendChild(n2);
+    
+    % sometimes the encoding has the fields, but they are empty
+    if isfield(enc,'trajectoryDescription')
+        if ~isempty(fieldnames(enc.trajectoryDescription))
+            n2 = docNode.createElement('trajectoryDescription');
+            append_node(docNode,n2,enc.trajectoryDescription,'identifier');
+            append_user_parameter(docNode,n2,enc.trajectoryDescription,'userParameterLong',@int2str);
+            append_user_parameter(docNode,n2,enc.trajectoryDescription,'userParameterDouble',@num2str);
+            append_optional(docNode,n2,enc.trajectoryDescription,'comment');      
+            node.appendChild(n2);
+        end
+    end
+    
+    if isfield(enc,'parallelImaging')
+        if ~isempty(fieldnames(enc.parallelImaging))
+            n2 = docNode.createElement('parallelImaging');
+
+            n3 = docNode.createElement('accelerationFactor');
+            parallelImaging = enc.parallelImaging;
+            append_node(docNode,n3,parallelImaging.accelerationFactor,'kspace_encoding_step_1',@int2str);
+            append_node(docNode,n3,parallelImaging.accelerationFactor,'kspace_encoding_step_2',@int2str);
+            n2.appendChild(n3);
+
+            append_optional(docNode,n2,parallelImaging,'calibrationMode'); 
+            append_optional(docNode,n2,parallelImaging,'interleavingDimension',@int2str); 
+
+            node.appendChild(n2);
+        end
+    end
+    
+    if isfield(enc,'echoTrainLength')
+        if ~isempty(enc.echoTrainLength)
+            append_optional(docNode,node,enc,'echoTrainLength',@int2str);
+        end
+    end
+    
+    docRootNode.appendChild(node);
+    
+end
+
+if isfield(header,'sequenceParameters')
+    n1 = docNode.createElement('sequenceParameters');
+    sequenceParameters = header.sequenceParameters;
+    
+    append_optional(docNode,n1,sequenceParameters,'TR',@num2str);
+    append_optional(docNode,n1,sequenceParameters,'TE',@num2str);
+    append_optional(docNode,n1,sequenceParameters,'TI',@num2str);
+    append_optional(docNode,n1,sequenceParameters,'flipAngle_deg',@num2str);
+    append_optional(docNode,n1,sequenceParameters,'sequence_type');
+    append_optional(docNode,n1,sequenceParameters,'echo_spacing',@num2str);
+    docRootNode.appendChild(n1);
+end
+
+if isfield(header,'userParameters')
+    n1 = docNode.createElement('userParameters');
+    userParameters = header.userParameters;
+    
+    if isfield(userParameters,'userParameterLong')
+        append_user_parameter(docNode,n1,userParameters,'userParameterLong',@int2str);
+    end
+    
+    if isfield(userParameters,'userParameterDouble')
+        append_user_parameter(docNode,n1,userParameters,'userParameterDouble',@num2str);
+    end
+    if isfield(userParameters,'userParameterString')
+        append_user_parameter(docNode,n1,userParameters,'userParameterString');
+    end
+    if isfield(userParameters,'userParameterBase64')
+        append_user_parameter(docNode,n1,userParameters,'userParameterBase64');
+    end
+    
+    docRootNode.appendChild(n1);
+end
+xml_doc = xmlwrite(docNode);
+
+
+
+
+end
+
+function append_user_parameter(docNode,subNode,values,name,tostr)
+
+for v = 1:length(values.(name))
+    n2 = docNode.createElement(name);
+    
+    append_node(docNode,n2,values.(name)(v),'name');
+    
+    if nargin > 4
+        append_node(docNode,n2,values.(name)(v),'value',tostr);
+    else
+        append_node(docNode,n2,values.(name)(v),'value');
+    end
+    
+    subNode.appendChild(n2);
+end
+end
+
+    
+function append_encoding_limits(docNode,subNode,name,limit)
+    if isfield(limit,name)
+        n2 = docNode.createElement(name);    
+        append_node(docNode,n2,limit.(name),'minimum',@int2str);
+        append_node(docNode,n2,limit.(name),'maximum',@int2str);
+        append_node(docNode,n2,limit.(name),'center',@int2str);
+        subNode.appendChild(n2);
+    end
+end
+
+function append_encoding_space(docNode,subnode,name,encodedSpace)
+    n2 = docNode.createElement(name);
+    
+    n3 = docNode.createElement('matrixSize');
+    append_node(docNode,n3,encodedSpace.matrixSize,'x',@int2str);
+    append_node(docNode,n3,encodedSpace.matrixSize,'y',@int2str);
+    append_node(docNode,n3,encodedSpace.matrixSize,'z',@int2str);
+    n2.appendChild(n3);
+    
+    n3 = docNode.createElement('fieldOfView_mm');
+    append_node(docNode,n3,encodedSpace.fieldOfView_mm,'x',@num2str);
+    append_node(docNode,n3,encodedSpace.fieldOfView_mm,'y',@num2str);
+    append_node(docNode,n3,encodedSpace.fieldOfView_mm,'z',@num2str);
+    n2.appendChild(n3);
+    subnode.appendChild(n2);
+end
+    
+    
+function append_optional(docNode,subnode,subheader,name,tostr)
+    if isfield(subheader,name)
+        if nargin > 4
+            append_node(docNode,subnode,subheader,name,tostr);
+        else
+            append_node(docNode,subnode,subheader,name);
+        end
+    end       
+end
+
+function append_node(docNode,subnode,subheader,name,tostr)
+    
+    if ischar(subheader.(name))
+        n1 = docNode.createElement(name);    
+        n1.appendChild...
+        (docNode.createTextNode(subheader.(name)));
+        subnode.appendChild(n1);
+    else
+
+        for val = subheader.(name)(:)
+            n1 = docNode.createElement(name);
+            n1.appendChild...
+                (docNode.createTextNode(tostr(val)));        
+            subnode.appendChild(n1);
+        end
+    end
+end
diff --git a/matlab/+ismrmrd/+xml/validate.m b/matlab/+ismrmrd/+xml/validate.m
new file mode 100644 (file)
index 0000000..85511d1
--- /dev/null
@@ -0,0 +1,20 @@
+function validate( xml,schemaFile )
+%UNTITLED Summary of this function goes here
+%   Detailed explanation goes here
+import java.io.*;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.*;
+
+factory = SchemaFactory.newInstance('http://www.w3.org/2001/XMLSchema');
+schemaLocation = File(schemaFile);
+schema = factory.newSchema(schemaLocation);
+validator = schema.newValidator();
+sr = StringReader(xml);
+source = StreamSource(sr);
+validator.validate(source);
+
+
+
+end
+
diff --git a/matlab/+ismrmrd/Acquisition.m b/matlab/+ismrmrd/Acquisition.m
new file mode 100644 (file)
index 0000000..5b7dc23
--- /dev/null
@@ -0,0 +1,145 @@
+classdef Acquisition < handle
+
+    properties
+        head = [];
+        traj = {};
+        data = {};
+    end
+
+    methods
+
+        function obj = Acquisition(arg1, traj, data)
+            switch nargin
+                case 0
+                    % No argument constructor
+                    % initialize to a single acquisition
+                    extend(obj,1);
+                    
+                case 1
+                    % One argument constructor
+                    if ismrmrd.util.isInt(arg1)
+                        % First argument is a number
+                        M = arg1;
+                        extend(obj,M);
+                    else                        
+                        % First argument is a header (hopefully)
+                        M = length(arg1.version);
+                        obj.head = ismrmrd.AcquisitionHeader(arg1);
+                        obj.traj{M} = [];
+                        obj.data{M} = [];
+                    end
+                    
+                case 3
+                    % Three argument constructor
+                    obj.head = ismrmrd.AcquisitionHeader(arg1);
+                    M = length(arg1.version);
+                    if isempty(traj)
+                        obj.traj{M} = [];
+                    else
+                        trajFromFloat(obj,traj);
+                    end
+                    if isempty(data)
+                        obj.data{M} = [];
+                    else
+                        if isreal(data{1})
+                            dataFromFloat(obj,data);
+                        else
+                            obj.data = data;
+                        end
+                    end
+                    
+            otherwise
+                    error('ismrmrd.Acquistion constructor, wrong number of arguments.');
+            end
+        end
+        
+        function nacq = getNumber(obj)
+            nacq = obj.head.getNumber();
+        end
+        
+        function acq = select(obj, range)
+            % Return a copy of a range of acquisitions
+            
+            % create an empty acquisition
+            acq = ismrmrd.Acquisition();
+            % Fill the header
+            acq.head = obj.head.select(range);
+            % Fill the trajectory and the data
+            for p = 1:length(range)
+                acq.traj{p} = obj.traj{range(p)};
+                acq.data{p} = obj.data{range(p)};
+            end    
+        end
+        
+        function append(obj, head, traj, data)
+            Nstart = obj.getNumber + 1;
+            Nend   = obj.getNumber + length(head.version);
+            Nrange = Nstart:Nend;
+            obj.head.append(head);
+            if isempty(traj) > 0
+                obj.traj{Nrange} = traj;
+            end
+            if isempty(data) > 0
+                obj.data{Nrange} = data;
+            end
+            
+        end
+        
+        function extend(obj,N)
+            % Extend with blank head and empty traj and data.
+            if isempty(obj.head)
+                M = N;
+                obj.head = ismrmrd.AcquisitionHeader(N);
+            else
+                M = N+obj.getNumber();
+                obj.head.extend(N);
+            end
+            obj.traj{M} = [];
+            obj.data{M} = []; 
+        end
+        
+        function dataFromFloat(obj,v)
+            if (isempty(obj.head) || (length(v) ~= length(obj.head.version)))
+                error('Mismatch between size of head and data.  Please set head first.');
+            end
+            obj.data = cell(1,length(v));
+            for p = 1:length(v)
+                dims = [obj.head.number_of_samples(p), ...
+                        obj.head.active_channels(p)];
+                buff = v{p};
+                obj.data{p} = reshape(buff(1:2:end) + 1j*buff(2:2:end), dims);
+            end
+        end
+
+        function v = dataToFloat(obj)
+            v = cell(1,length(obj.data));
+            for p = 1:length(obj.data)
+                dim = size(obj.data{p});
+                buff = zeros([2*prod(dim),1],'single');
+                buff(1:2:end) = real(reshape(obj.data{p},prod(dim),1));
+                buff(2:2:end) = imag(reshape(obj.data{p},prod(dim),1));
+                v{p} = buff;
+            end
+        end
+        
+        function trajFromFloat(obj,v)
+            if (isempty(obj.head) || (length(v) ~= length(obj.head.version)))
+                error('Mismatch between size of head and trajectory.  Please set head first.');
+            end
+            obj.traj = cell(1,length(v));
+            for p = 1:length(v)
+                dims = [obj.head.trajectory_dimensions(p), ...
+                        obj.head.number_of_samples(p)];
+                obj.traj{p} = reshape(v{p}, dims);
+            end
+        end
+
+        function v = trajToFloat(obj)
+            v = cell(1,length(obj.traj));
+            for p = 1:length(obj.traj)
+                v{p} = single(obj.traj{p});
+            end
+        end
+    end
+
+end
\ No newline at end of file
diff --git a/matlab/+ismrmrd/AcquisitionHeader.m b/matlab/+ismrmrd/AcquisitionHeader.m
new file mode 100644 (file)
index 0000000..c672b11
--- /dev/null
@@ -0,0 +1,658 @@
+classdef AcquisitionHeader < handle
+    
+    properties
+        
+        version = uint16([]);                          % First unsigned int indicates the version %
+        flags = uint64([]);                            % bit field with flags %
+        measurement_uid = uint32([]);                  % Unique ID for the measurement %
+        scan_counter = uint32([]);                     % Current acquisition number in the measurement %
+        acquisition_time_stamp = uint32([]);           % Acquisition clock %
+        physiology_time_stamp = uint32([]);            % Physiology time stamps, e.g. ecg, breating, etc. %
+        number_of_samples = uint16([]);                % Number of samples acquired %
+        available_channels = uint16([]);               % Available coils %
+        active_channels = uint16([]);                  % Active coils on current acquisiton %
+        channel_mask = uint64([]);                     % Mask to indicate which channels are active. Support for 1024 channels %
+        discard_pre = uint16([]);                      % Samples to be discarded at the beginning of acquisition %
+        discard_post = uint16([]);                     % Samples to be discarded at the end of acquisition %
+        center_sample = uint16([]);                    % Sample at the center of k-space %
+        encoding_space_ref = uint16([]);               % Reference to an encoding space, typically only one per acquisition %
+        trajectory_dimensions = uint16([]);            % Indicates the dimensionality of the trajectory vector (0 means no trajectory) %
+        sample_time_us = single([]);                   % Time between samples in micro seconds, sampling BW %
+        position = single([]);                         % Three-dimensional spatial offsets from isocenter %
+        read_dir = single([]);                         % Directional cosines of the readout/frequency encoding %
+        phase_dir = single([]);                        % Directional cosines of the phase encoding %
+        slice_dir = single([]);                        % Directional cosines of the slice %
+        patient_table_position = single([]);           % Patient table off-center %
+        idx = struct(  ...                             % Encoding loop counters, see above %
+            'kspace_encode_step_1', uint16([]), ...   
+            'kspace_encode_step_2', uint16([]), ...
+            'average', uint16([]), ...
+            'slice', uint16([]), ...
+            'contrast', uint16([]), ...
+            'phase', uint16([]), ...
+            'repetition', uint16([]), ...
+            'set', uint16([]), ...
+            'segment', uint16([]), ...
+            'user', uint16([]));
+        user_int = int32([]);                 % Free user parameters %
+        user_float = single([]);              % Free user parameters %
+        
+    end
+    
+    properties(Constant)
+        FLAGS = struct( ...
+            'ACQ_FIRST_IN_ENCODE_STEP1',                1, ...
+            'ACQ_LAST_IN_ENCODE_STEP1',                 2, ...
+            'ACQ_FIRST_IN_ENCODE_STEP2',                3, ...
+            'ACQ_LAST_IN_ENCODE_STEP2',                 4, ...
+            'ACQ_FIRST_IN_AVERAGE',                     5, ...
+            'ACQ_LAST_IN_AVERAGE',                      6, ...
+            'ACQ_FIRST_IN_SLICE',                       7, ...
+            'ACQ_LAST_IN_SLICE',                        8, ...
+            'ACQ_FIRST_IN_CONTRAST',                    9, ...
+            'ACQ_LAST_IN_CONTRAST',                    10, ...
+            'ACQ_FIRST_IN_PHASE',                      11, ...
+            'ACQ_LAST_IN_PHASE',                       12, ...
+            'ACQ_FIRST_IN_REPETITION',                 13, ...
+            'ACQ_LAST_IN_REPETITION',                  14, ...
+            'ACQ_FIRST_IN_SET',                        15, ...
+            'ACQ_LAST_IN_SET',                         16, ...
+            'ACQ_FIRST_IN_SEGMENT',                    17, ...
+            'ACQ_LAST_IN_SEGMENT',                     18, ...
+            'ACQ_IS_NOISE_MEASUREMENT',                19, ...
+            'ACQ_IS_PARALLEL_CALIBRATION',             20, ...
+            'ACQ_IS_PARALLEL_CALIBRATION_AND_IMAGING', 21, ...
+            'ACQ_IS_REVERSE',                          22, ...
+            'ACQ_IS_NAVIGATION_DATA',                  23, ...
+            'ACQ_IS_PHASECORR_DATA',                   24, ...
+            'ACQ_LAST_IN_MEASUREMENT',                 25, ...
+            'ACQ_IS_HPFEEDBACK_DATA',                  26, ...
+            'ACQ_IS_DUMMYSCAN_DATA',                   27, ...
+            'ACQ_IS_RTFEEDBACK_DATA',                  28, ...
+            'ACQ_IS_SURFACECOILCORRECTIONSCAN_DATA',   29, ...
+            'ACQ_USER1',                               57, ...
+            'ACQ_USER2',                               58, ...
+            'ACQ_USER3',                               59, ...
+            'ACQ_USER4',                               60, ...
+            'ACQ_USER5',                               61, ...
+            'ACQ_USER6',                               62, ...
+            'ACQ_USER7',                               63, ...
+            'ACQ_USER8',                               64);
+    end
+    
+    methods
+        
+        function obj = AcquisitionHeader(arg)
+            % Constructor
+            
+            switch nargin
+                case 0
+                    % No argument constructor
+                    % initialize to a single acquisition header
+                    extend(obj,1);
+                    
+                case 1
+                    % One argument constructor
+                    if isstruct(arg)
+                        % plain struct
+                        fromStruct(obj,arg);
+                    elseif (length(arg)==1 && ismrmrd.util.isInt(arg)) == 1
+                        % number
+                        extend(obj,arg);
+                    elseif isa(arg,'uint8')
+                        % Byte array
+                        fromBytes(obj,arg);
+                    else
+                        % Unknown type
+                        error('Unknown argument type.')
+                    end
+                    
+                otherwise
+                    error('Wrong number of arguments.')
+                    
+            end
+        end
+        
+        function nacq = getNumber(obj)
+            % Return the number of headers
+            
+            nacq = length(obj.version);
+            
+        end
+
+        function hdr = select(obj, range)
+            % Return a copy of a range of acquisition headers
+            
+            % create an empty acquisition header
+            M = length(range);
+            hdr = ismrmrd.AcquisitionHeader(M);
+            
+            % Fill
+            hdr.version = obj.version(range);
+            hdr.flags = obj.flags(range);
+            hdr.measurement_uid = obj.measurement_uid(range);
+            hdr.scan_counter = obj.scan_counter(range);
+            hdr.acquisition_time_stamp = obj.acquisition_time_stamp(range);
+            hdr.physiology_time_stamp = obj.physiology_time_stamp(:,range);
+            hdr.number_of_samples = obj.number_of_samples(range);
+            hdr.available_channels = obj.available_channels(range);
+            hdr.active_channels = obj.active_channels(range);
+            hdr.channel_mask = obj.channel_mask(:,range);
+            hdr.discard_pre = obj.discard_pre(range);
+            hdr.discard_post = obj.discard_post(range);
+            hdr.center_sample = obj.center_sample(range);
+            hdr.encoding_space_ref = obj.encoding_space_ref(range);
+            hdr.trajectory_dimensions = obj.trajectory_dimensions(range);
+            hdr.sample_time_us = obj.sample_time_us(range);
+            hdr.position = obj.position(:,range);
+            hdr.read_dir = obj.read_dir(:,range);
+            hdr.phase_dir = obj.phase_dir(:,range);
+            hdr.slice_dir = obj.slice_dir(:,range);
+            hdr.patient_table_position = obj.patient_table_position(:,range);
+            hdr.idx.kspace_encode_step_1 = obj.idx.kspace_encode_step_1(range);
+            hdr.idx.kspace_encode_step_2 = obj.idx.kspace_encode_step_2(range);
+            hdr.idx.average = obj.idx.average(range);
+            hdr.idx.slice = obj.idx.slice(range);
+            hdr.idx.contrast = obj.idx.contrast(range);
+            hdr.idx.phase = obj.idx.phase(range);
+            hdr.idx.repetition = obj.idx.repetition(range);
+            hdr.idx.set = obj.idx.set(range);
+            hdr.idx.segment = obj.idx.segment(range);
+            hdr.idx.user = obj.idx.user(:,range);
+            hdr.user_int = obj.user_int(:,range);
+            hdr.user_float = obj.user_float(:,range);
+            
+        end        
+        
+        function extend(obj,N)
+            % Extend with blank header
+            
+            range = obj.getNumber + (1:N);
+            obj.version(1,range)                  = zeros(1,N,'uint16');
+            obj.flags(1,range)                    = zeros(1,N,'uint64');
+            obj.measurement_uid(1,range)          = zeros(1,N,'uint32');
+            obj.scan_counter(1,range)             = zeros(1,N,'uint32');
+            obj.acquisition_time_stamp(1,range)   = zeros(1,N,'uint32');
+            obj.physiology_time_stamp(1:3,range)  = zeros(3,N,'uint32');
+            obj.number_of_samples(1,range)        = zeros(1,N,'uint16');
+            obj.available_channels(1,range)       = zeros(1,N,'uint16');
+            obj.active_channels(1,range)          = zeros(1,N,'uint16');
+            obj.channel_mask(1:16,range)          = zeros(16,N,'uint64');
+            obj.discard_pre(1,range)              = zeros(1,N,'uint16');
+            obj.discard_post(1,range)             = zeros(1,N,'uint16');
+            obj.center_sample(1,range)            = zeros(1,N,'uint16');
+            obj.encoding_space_ref(1,range)       = zeros(1,N,'uint16');
+            obj.trajectory_dimensions(1,range)    = zeros(1,N,'uint16');
+            obj.sample_time_us(1,range)           = zeros(1,N,'uint16');
+            obj.position(1:3,range)               = zeros(3,N,'single');
+            obj.read_dir(1:3,range)               = zeros(3,N,'single');
+            obj.phase_dir(1:3,range)              = zeros(3,N,'single');
+            obj.slice_dir(1:3,range)              = zeros(3,N,'single');
+            obj.patient_table_position(1:3,range) = zeros(3,N,'single');
+            obj.idx.kspace_encode_step_1(1,range) = zeros(1,N,'uint16');
+            obj.idx.kspace_encode_step_2(1,range) = zeros(1,N,'uint16');
+            obj.idx.average(1,range)              = zeros(1,N,'uint16');
+            obj.idx.slice(1,range)                = zeros(1,N,'uint16');
+            obj.idx.contrast(1,range)             = zeros(1,N,'uint16');
+            obj.idx.phase(1,range)                = zeros(1,N,'uint16');
+            obj.idx.repetition(1,range)           = zeros(1,N,'uint16');
+            obj.idx.set(1,range)                  = zeros(1,N,'uint16');
+            obj.idx.segment(1,range)              = zeros(1,N,'uint16');
+            obj.idx.user(1:8,range)               = zeros(8,N,'uint16');
+            obj.user_int(1:8,range)               = zeros(8,N,'int32');
+            obj.user_float(1:8,range)             = zeros(8,N,'single');
+        end
+        
+        function append(obj, head)
+            % Append a header
+            
+            Nstart = obj.getNumber + 1;
+            Nend   = obj.getNumber + length(head.version);
+            Nrange = Nstart:Nend;
+            obj.version(1,Nrange) = hdr.version(:);
+            obj.flags(1,Nrange) = hdr.flags(:);
+            obj.measurement_uid(1,Nrange) = hdr.measurement_uid(:);
+            obj.scan_counter(1,Nrange) = hdr.scan_counter(:);
+            obj.acquisition_time_stamp(1,Nrange) = hdr.acquisition_time_stamp(:);
+            obj.physiology_time_stamp(:,Nrange) = hdr.physiology_time_stamp(:);
+            obj.number_of_samples(1,Nrange) = hdr.number_of_samples(:);
+            obj.available_channels(1,Nrange) = hdr.available_channels(:);
+            obj.active_channels(1,Nrange) = hdr.active_channels(:);
+            obj.channel_mask(:,Nrange) = hdr.channel_mask(:);
+            obj.discard_pre(1,Nrange) = hdr.discard_pre(:);
+            obj.discard_post(1,Nrange) = hdr.discard_post(:);
+            obj.center_sample(1,Nrange) = hdr.center_sample(:);
+            obj.encoding_space_ref(1,Nrange) = hdr.encoding_space_ref(:);
+            obj.trajectory_dimensions(1,Nrange) = hdr.trajectory_dimensions(:);
+            obj.sample_time_us(1,Nrange) = hdr.sample_time_us(:);
+            obj.position(:,Nrange) = hdr.position(:);
+            obj.read_dir(:,Nrange) = hdr.read_dir(:);
+            obj.phase_dir(:,Nrange) = hdr.phase_dir(:);
+            obj.slice_dir(:,Nrange) = hdr.slice_dir(:);
+            obj.patient_table_position(:,Nrange) = hdr.patient_table_position(:);
+            obj.idx.kspace_encode_step_1(1,Nrange) = hdr.idx.kspace_encode_step_1(:);
+            obj.idx.kspace_encode_step_2(1,Nrange) = hdr.idx.kspace_encode_step_2(:);
+            obj.idx.average(1,Nrange) = hdr.idx.average(:);
+            obj.idx.slice(1,Nrange) = hdr.idx.slice(:);
+            obj.idx.contrast(1,Nrange) = hdr.idx.contrast(:);
+            obj.idx.phase(1,Nrange) = hdr.idx.phase(:);
+            obj.idx.repetition(1,Nrange) = hdr.idx.repetition(:);
+            obj.idx.set(1,Nrange) = hdr.idx.set(:);
+            obj.idx.segment(1,Nrange) = hdr.idx.segment(:);
+            obj.idx.user(:,Nrange) = hdr.idx.user(:);
+            obj.user_int(:,Nrange) = hdr.user_int(:);
+            obj.user_float(:,Nrange) = hdr.user_float(:);            
+        end
+
+        function fromStruct(obj, hdr)
+            % Convert a struct to the object
+            N = length(hdr.version);
+            %warning! no error checking
+            obj.version = reshape(hdr.version,[1,N]);
+            obj.flags = reshape(hdr.flags,[1,N]);
+            obj.measurement_uid = reshape(hdr.measurement_uid,[1,N]);
+            obj.scan_counter = reshape(hdr.scan_counter,[1,N]);
+            obj.acquisition_time_stamp = reshape(hdr.acquisition_time_stamp,[1,N]);
+            obj.physiology_time_stamp = reshape(hdr.physiology_time_stamp,[3,N]);
+            obj.number_of_samples = reshape(hdr.number_of_samples,[1,N]);
+            obj.available_channels = reshape(hdr.available_channels,[1,N]);
+            obj.active_channels = reshape(hdr.active_channels,[1,N]);
+            obj.channel_mask = reshape(hdr.channel_mask,[16,N]);
+            obj.discard_pre = reshape(hdr.discard_pre,[1,N]);
+            obj.discard_post = reshape(hdr.discard_post,[1,N]);
+            obj.center_sample = reshape(hdr.center_sample,[1,N]);
+            obj.encoding_space_ref = reshape(hdr.encoding_space_ref,[1,N]);
+            obj.trajectory_dimensions = reshape(hdr.trajectory_dimensions,[1,N]);
+            obj.sample_time_us = reshape(hdr.sample_time_us,[1,N]);
+            obj.position = reshape(hdr.position,[3,N]);
+            obj.read_dir = reshape(hdr.read_dir,[3,N]);
+            obj.phase_dir = reshape(hdr.phase_dir,[3,N]);
+            obj.slice_dir = reshape(hdr.slice_dir,[3,N]);
+            obj.patient_table_position = reshape(hdr.patient_table_position,[3,N]);
+            obj.idx.kspace_encode_step_1 = reshape(hdr.idx.kspace_encode_step_1,[1,N]);
+            obj.idx.kspace_encode_step_2 = reshape(hdr.idx.kspace_encode_step_2,[1,N]);
+            obj.idx.average = reshape(hdr.idx.average,[1,N]);
+            obj.idx.slice = reshape(hdr.idx.slice,[1,N]);
+            obj.idx.contrast = reshape(hdr.idx.contrast,[1,N]);
+            obj.idx.phase = reshape(hdr.idx.phase,[1,N]);
+            obj.idx.repetition = reshape(hdr.idx.repetition,[1,N]);
+            obj.idx.set = reshape(hdr.idx.set,[1,N]);
+            obj.idx.segment = reshape(hdr.idx.segment,[1,N]);
+            obj.idx.user = reshape(hdr.idx.user,[8,N]);
+            obj.user_int = reshape(hdr.user_int,[8,N]);
+            obj.user_float = reshape(hdr.user_float,[8,N]);         
+        end
+
+        function hdr = toStruct(obj)
+            % Convert the object to a plain struct
+            
+            %warning! no error checking
+            hdr = struct();
+            hdr.version = obj.version;
+            hdr.flags = obj.flags;
+            hdr.measurement_uid = obj.measurement_uid;
+            hdr.scan_counter = obj.scan_counter;
+            hdr.acquisition_time_stamp = obj.acquisition_time_stamp;
+            hdr.physiology_time_stamp = obj.physiology_time_stamp;
+            hdr.number_of_samples = obj.number_of_samples;
+            hdr.available_channels = obj.available_channels;
+            hdr.active_channels = obj.active_channels;
+            hdr.channel_mask = obj.channel_mask;
+            hdr.discard_pre = obj.discard_pre;
+            hdr.discard_post = obj.discard_post;
+            hdr.center_sample = obj.center_sample;
+            hdr.encoding_space_ref = obj.encoding_space_ref;
+            hdr.trajectory_dimensions = obj.trajectory_dimensions;
+            hdr.sample_time_us = obj.sample_time_us;
+            hdr.position = obj.position;
+            hdr.read_dir = obj.read_dir;
+            hdr.phase_dir = obj.phase_dir;
+            hdr.slice_dir = obj.slice_dir;
+            hdr.patient_table_position = obj.patient_table_position;
+            hdr.idx.kspace_encode_step_1 = obj.idx.kspace_encode_step_1;
+            hdr.idx.kspace_encode_step_2 = obj.idx.kspace_encode_step_2;
+            hdr.idx.average = obj.idx.average;
+            hdr.idx.slice = obj.idx.slice;
+            hdr.idx.contrast = obj.idx.contrast;
+            hdr.idx.phase = obj.idx.phase;
+            hdr.idx.repetition = obj.idx.repetition;
+            hdr.idx.set = obj.idx.set;
+            hdr.idx.segment = obj.idx.segment;
+            hdr.idx.user = obj.idx.user;
+            hdr.user_int = obj.user_int;
+            hdr.user_float = obj.user_float;            
+        end
+        
+        function fromBytes(obj, bytearray)
+            % Convert from a byte array to an ISMRMRD AcquisitionHeader
+            % This conforms to the memory layout of the C-struct
+
+            if size(bytearray,1) ~= 340
+                error('Wrong number of bytes for AcquisitionHeader.')
+            end
+            N = size(bytearray,2);
+            for p = 1:N
+                obj.version(p) =                  typecast(bytearray(  1:  2,p), 'uint16'); ... % First unsigned int indicates the version %
+                obj.flags(p) =                    typecast(bytearray(  3: 10,p), 'uint64'); ... % bit field with flags %
+                obj.measurement_uid(p) =          typecast(bytearray( 11: 14,p), 'uint32'); ... % Unique ID for the measurement %
+                obj.scan_counter(p) =             typecast(bytearray( 15: 18,p), 'uint32'); ... % Current acquisition number in the measurement %
+                obj.acquisition_time_stamp(p) =   typecast(bytearray( 19: 22,p), 'uint32'); ... % Acquisition clock %
+                obj.physiology_time_stamp(:,p) =  typecast(bytearray( 23: 34,p), 'uint32'); ... % Physiology time stamps, e.g. ecg, breating, etc. %
+                obj.number_of_samples(p) =        typecast(bytearray( 35: 36,p), 'uint16'); ... % Number of samples acquired %
+                obj.available_channels(p) =       typecast(bytearray( 37: 38,p), 'uint16'); ... % Available coils %
+                obj.active_channels(p) =          typecast(bytearray( 39: 40,p), 'uint16'); ... % Active coils on current acquisiton %
+                obj.channel_mask(:,p) =           typecast(bytearray( 41:168,p), 'uint64'); ... % Mask to indicate which channels are active. Support for 1024 channels %
+                obj.discard_pre(p) =              typecast(bytearray(169:170,p), 'uint16'); ... % Samples to be discarded at the beginning of acquisition %
+                obj.discard_post(p) =             typecast(bytearray(171:172,p), 'uint16'); ... % Samples to be discarded at the end of acquisition %
+                obj.center_sample(p) =            typecast(bytearray(173:174,p), 'uint16'); ... % Sample at the center of k-space %
+                obj.encoding_space_ref(p) =       typecast(bytearray(175:176,p), 'uint16'); ... % Reference to an encoding space, typically only one per acquisition %
+                obj.trajectory_dimensions(p) =    typecast(bytearray(177:178,p), 'uint16'); ... % Indicates the dimensionality of the trajectory vector (0 means no trajectory) %
+                obj.sample_time_us(p) =           typecast(bytearray(179:182,p), 'single'); ... % Time between samples in micro seconds, sampling BW %
+                obj.position(:,p) =               typecast(bytearray(183:194,p), 'single'); ... % Three-dimensional spatial offsets from isocenter %
+                obj.read_dir(:,p) =               typecast(bytearray(195:206,p), 'single'); ... % Directional cosines of the readout/frequency encoding %
+                obj.phase_dir(:,p) =              typecast(bytearray(207:218,p), 'single'); ... % Directional cosines of the phase encoding %
+                obj.slice_dir(:,p) =              typecast(bytearray(219:230,p), 'single'); ... % Directional cosines of the slice %
+                obj.patient_table_position(:,p) = typecast(bytearray(231:242,p), 'single'); ... % Patient table off-center %
+                obj.idx.kspace_encode_step_1(p) = typecast(bytearray(243:244,p), 'uint16'); ... % phase encoding line number %
+                obj.idx.kspace_encode_step_2(p) = typecast(bytearray(245:246,p), 'uint16'); ... % partition encodning number %
+                obj.idx.average(p) =              typecast(bytearray(247:248,p), 'uint16'); ... % signal average number %
+                obj.idx.slice(p) =                typecast(bytearray(249:250,p), 'uint16'); ... % imaging slice number %
+                obj.idx.contrast(p) =             typecast(bytearray(251:252,p), 'uint16'); ... % echo number in multi-echo %
+                obj.idx.phase(p) =                typecast(bytearray(253:254,p), 'uint16'); ... % cardiac phase number %
+                obj.idx.repetition(p) =           typecast(bytearray(255:256,p), 'uint16'); ... % dynamic number for dynamic scanning %
+                obj.idx.set(p) =                  typecast(bytearray(257:258,p), 'uint16'); ... % flow encoding set %
+                obj.idx.segment(p) =              typecast(bytearray(259:260,p), 'uint16'); ... % segment number for segmented acquisition %
+                obj.idx.user(:,p) =               typecast(bytearray(261:276,p), 'uint16'); ... % Free user parameters %
+                obj.user_int(:,p) =               typecast(bytearray(277:308,p), 'int32');  ... % Free user parameters %
+                obj.user_float(:,p) =             typecast(bytearray(309:340,p), 'single'); ... % Free user parameters %
+            end              
+        end
+        
+        function bytes = toBytes(obj)
+            % Convert to an ISMRMRD AcquisitionHeader to a byte array
+            % This conforms to the memory layout of the C-struct
+
+            N = obj.getNumber;
+            bytes = zeros(340,N,'uint8');
+            for p = 1:N
+                off = 1;
+                bytes(off:off+1,p)   = typecast(obj.version(p)               ,'uint8'); off=off+2;
+                bytes(off:off+7,p)   = typecast(obj.flags(p)                 ,'uint8'); off=off+8;
+                bytes(off:off+3,p)   = typecast(obj.measurement_uid(p)       ,'uint8'); off=off+4;
+                bytes(off:off+3,p)   = typecast(obj.scan_counter(p)          ,'uint8'); off=off+4;
+                bytes(off:off+3,p)   = typecast(obj.acquisition_time_stamp(p),'uint8'); off=off+4;
+                bytes(off:off+11,p)  = typecast(obj.physiology_time_stamp(:,p) ,'uint8'); off=off+12;
+                bytes(off:off+1,p)   = typecast(obj.number_of_samples(p)     ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.available_channels(p)    ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.active_channels(p)       ,'uint8'); off=off+2;
+                bytes(off:off+127,p) = typecast(obj.channel_mask(:,p)        ,'uint8'); off=off+128;
+                bytes(off:off+1,p)   = typecast(obj.discard_pre(p)           ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.discard_post(p)          ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.center_sample(p)         ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.encoding_space_ref(p)    ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.trajectory_dimensions(p) ,'uint8'); off=off+2;
+                bytes(off:off+3,p)   = typecast(obj.sample_time_us(p)        ,'uint8'); off=off+4;
+                bytes(off:off+11,p)  = typecast(obj.position(:,p)            ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.read_dir(:,p)            ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.phase_dir(:,p)           ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.slice_dir(:,p)           ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.patient_table_position(:,p),'uint8'); off=off+12;
+                bytes(off:off+1,p)   = typecast(obj.idx.kspace_encode_step_1(p),'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.kspace_encode_step_2(p),'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.average(p)           ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.slice(p)             ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.contrast(p)          ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.phase(p)             ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.repetition(p)        ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.set(p)               ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.idx.segment(p)           ,'uint8'); off=off+2;
+                bytes(off:off+15,p)  = typecast(obj.idx.user(:,p)            ,'uint8'); off=off+16;
+                bytes(off:off+31,p)  = typecast(obj.user_int(:,p)            ,'uint8'); off=off+32;
+                bytes(off:off+31,p)  = typecast(obj.user_float(:,p)          ,'uint8');
+            end
+        end
+        
+        function obj = check(obj)
+            % Check and fix the obj types
+            
+            % Check the number of elements for each entry
+            N = obj.getNumber();
+            if (size(obj.flags) ~= N)
+                error('Size of flags is not correct.');
+            end
+            if ((size(obj.measurement_uid,1) ~= 1) || ...
+                (size(obj.measurement_uid,2) ~= N))
+                error('Size of measurement_uid is not correct.');
+            end
+            if ((size(obj.scan_counter,1) ~= 1) || ...
+                (size(obj.scan_counter,2) ~= N))
+                error('Size of scan_counter is not correct.');
+            end
+            if ((size(obj.acquisition_time_stamp,1) ~= 1) || ...
+                (size(obj.acquisition_time_stamp,2) ~= N))
+                error('Size of acquisition_time_stamp is not correct.');
+            end
+            if ((size(obj.physiology_time_stamp,1) ~= 3) || ...
+                (size(obj.physiology_time_stamp,2) ~= N))
+                error('Size of physiology_time_stamp is not correct.');
+            end
+            if ((size(obj.number_of_samples,1) ~= 1) || ...
+                (size(obj.number_of_samples,2) ~= N))
+                error('Size of number_of_samples is not correct.');
+            end
+            
+            if ((size(obj.available_channels,1) ~= 1) || ...
+                (size(obj.available_channels,2) ~= N))
+                error('Size of available_channels is not correct.');
+            end
+            if ((size(obj.active_channels,1) ~= 1) || ...
+                (size(obj.active_channels,2) ~= N))
+                error('Size of active_channels is not correct.');
+            end
+            if ((size(obj.channel_mask,1) ~= 16) || ...
+                (size(obj.channel_mask,2) ~= N))    
+                error('Size of channel_mask is not correct.');
+            end
+            if ((size(obj.discard_pre,1) ~= 1) || ...
+                (size(obj.discard_pre,2) ~= N))
+                error('Size of discard_pre is not correct.');
+            end
+            if ((size(obj.discard_post,1) ~= 1) || ...
+                (size(obj.discard_post,2) ~= N))    
+                error('Size of discard_post is not correct.');
+            end
+            if ((size(obj.center_sample,1) ~= 1) || ...
+                (size(obj.center_sample,2) ~= N))
+                error('Size of center_sample is not correct.');
+            end
+            if ((size(obj.encoding_space_ref,1) ~= 1) || ...
+                (size(obj.encoding_space_ref,2) ~= N))    
+                error('Size of encoding_space_ref is not correct.');
+            end
+            if ((size(obj.trajectory_dimensions,1) ~= 1) || ...
+                (size(obj.trajectory_dimensions,2) ~= N))
+                error('Size of trajectory_dimensions is not correct.');
+            end
+            if ((size(obj.sample_time_us,1) ~= 1) || ...
+                (size(obj.sample_time_us,2) ~= N))    
+                error('Size of sample_time_us is not correct.');
+            end
+            if ((size(obj.position,1) ~= 3) || ...
+                (size(obj.position,2) ~= N))    
+                error('Size of position is not correct.');
+            end
+            if ((size(obj.read_dir,1) ~= 3) || ...
+                (size(obj.read_dir,2) ~= N))    
+                error('Size of read_dir is not correct.');
+            end
+            if ((size(obj.phase_dir,1) ~= 3) || ...
+                (size(obj.phase_dir,2) ~= N))    
+                error('Size of phase_dir is not correct.');
+            end
+            if ((size(obj.slice_dir,1) ~= 3) || ...
+                (size(obj.slice_dir,2) ~= N))    
+                error('Size of slice_dir is not correct.');
+            end
+            if ((size(obj.patient_table_position,1) ~= 3) || ...
+                (size(obj.patient_table_position,2) ~= N))    
+                error('Size of patient_table_position is not correct.');
+            end
+            if ((size(obj.idx.kspace_encode_step_1,1) ~= 1) || ...
+                (size(obj.idx.kspace_encode_step_1,2) ~= N))
+                error('Size of kspace_encode_step_1 is not correct.');
+            end
+            if ((size(obj.idx.kspace_encode_step_2,1) ~= 1) || ...
+                (size(obj.idx.kspace_encode_step_2,2) ~= N))     
+                error('Size of kspace_encode_step_2 is not correct.');
+            end
+            if ((size(obj.idx.average,1) ~= 1) || ...
+                (size(obj.idx.average,2) ~= N))    
+                error('Size of idx.average is not correct.');
+            end
+            if ((size(obj.idx.slice,1) ~= 1) || ...
+                (size(obj.idx.slice,2) ~= N))    
+                error('Size of idx.slice is not correct.');
+            end
+            if ((size(obj.idx.contrast,1) ~= 1) || ...
+                (size(obj.idx.contrast,2) ~= N))    
+                error('Size of idx.contrast is not correct.');
+            end
+            if ((size(obj.idx.phase,1) ~= 1) || ...
+                (size(obj.idx.phase,2) ~= N))    
+                error('Size of idx.phase is not correct.');
+            end
+            if ((size(obj.idx.repetition,1) ~= 1) || ...
+                (size(obj.idx.repetition,2) ~= N))    
+                error('Size of idx.repetition is not correct.');
+            end
+            if ((size(obj.idx.set,1) ~= 1) || ...
+                (size(obj.idx.set,2) ~= N))    
+                error('Size of idx.set is not correct.');
+            end
+            if ((size(obj.idx.segment,1) ~= 1) || ...
+                (size(obj.idx.segment,2) ~= N))    
+                error('Size of idx.segment is not correct.');
+            end
+            if ((size(obj.idx.user,1) ~= 8) || ...
+                (size(obj.idx.user,2) ~= N))    
+                error('Size of idx.user is not correct.');
+            end
+            if ((size(obj.user_int,1) ~= 8) || ...
+                (size(obj.user_int,2) ~= N))    
+                error('Size of user_int is not correct.');
+            end
+            if ((size(obj.user_float,1) ~= 8) || ...
+                (size(obj.user_float,2) ~= N))    
+                error('Size of user_float is not correct.');
+            end
+            
+            % Fix the type of all the elements
+            obj.version = uint16(obj.version);
+            obj.flags = uint64(obj.flags);
+            obj.measurement_uid = uint32(obj.measurement_uid);
+            obj.scan_counter = uint32(obj.scan_counter);
+            obj.acquisition_time_stamp = uint32(obj.acquisition_time_stamp);
+            obj.physiology_time_stamp = uint32(obj.physiology_time_stamp);
+            obj.number_of_samples = uint16(obj.number_of_samples);
+            obj.available_channels = uint16(obj.available_channels);
+            obj.active_channels = uint16(obj.active_channels);
+            obj.channel_mask = uint64(obj.channel_mask);
+            obj.discard_pre = uint16(obj.discard_pre);
+            obj.discard_post = uint16(obj.discard_post);
+            obj.center_sample = uint16(obj.center_sample);
+            obj.encoding_space_ref = uint16(obj.encoding_space_ref);
+            obj.trajectory_dimensions = uint16(obj.trajectory_dimensions);
+            obj.sample_time_us = single(obj.sample_time_us);
+            obj.position = single(obj.position);
+            obj.read_dir = single(obj.read_dir);
+            obj.phase_dir = single(obj.phase_dir);
+            obj.slice_dir = single(obj.slice_dir);
+            obj.patient_table_position = single(obj.patient_table_position);
+            obj.idx.kspace_encode_step_1 = uint16(obj.idx.kspace_encode_step_1);
+            obj.idx.kspace_encode_step_2 = uint16(obj.idx.kspace_encode_step_2);
+            obj.idx.average = uint16(obj.idx.average);
+            obj.idx.slice = uint16(obj.idx.slice);
+            obj.idx.contrast = uint16(obj.idx.contrast);
+            obj.idx.phase = uint16(obj.idx.phase);
+            obj.idx.repetition = uint16(obj.idx.repetition);
+            obj.idx.set = uint16(obj.idx.set);
+            obj.idx.segment = uint16(obj.idx.segment);
+            obj.idx.user = uint16(obj.idx.user);
+            obj.user_int = int32(obj.user_int);
+            obj.user_float = single(obj.user_float);
+        end
+        
+        function ret = flagIsSet(obj, flag, range)
+            % bool = obj.flagIsSet(flag, range)
+            
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+            ret = zeros(size(range));
+            for p = 1:length(range)
+                ret(p) = (bitand(obj.flags(range(p)), bitmask)>0);
+            end
+        end
+        
+        function flagSet(obj, flag, range)
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+
+            alreadyset = obj.flagIsSet(flag,range);
+            for p = 1:length(range)
+                if ~alreadyset(p)
+                    obj.flags(range(p)) = obj.flags(range(p)) + bitmask;
+                end
+            end
+        end
+        
+        function flagClear(obj, flag, range)
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+            
+            alreadyset = obj.flagIsSet(flag,range);
+            for p = 1:length(range)
+                if alreadyset(p)
+                    obj.flags(range(p)) = obj.flags(range(p)) - bitmask;
+                end
+            end
+        end
+        
+        function flagClearAll(obj, range)
+            if nargin < 2
+                range = 1:obj.getNumber;
+            end
+            obj.flags(range) = zeros(1,length(range),'uint64');
+        end
+        
+    end
+    
+end
diff --git a/matlab/+ismrmrd/Dataset.m b/matlab/+ismrmrd/Dataset.m
new file mode 100644 (file)
index 0000000..7d2475a
--- /dev/null
@@ -0,0 +1,272 @@
+classdef Dataset
+
+    properties
+        fid = -1;
+        filename = '';
+        datapath = '';
+        xmlpath = '';
+        htypes = [];
+    end
+
+    methods
+
+        function obj = Dataset(filename,groupname)
+
+            % Set the hdf types
+            obj.htypes = ismrmrd.util.hdf5_datatypes;
+                      
+            % If the file exists, open it for read/write
+            % otherwise, create it
+            if exist(filename,'file')
+                obj.fid = H5F.open(filename,'H5F_ACC_RDWR','H5P_DEFAULT');
+            else
+                fcpl = H5P.create('H5P_FILE_CREATE');
+                obj.fid = H5F.create(filename,'H5F_ACC_TRUNC',fcpl,'H5P_DEFAULT');
+                H5P.close(fcpl);
+            end
+
+            % Set the filename
+            obj.filename = filename;
+
+            % Set the group name
+            %   default is dataset
+            if nargin == 1
+                groupname = 'dataset';
+            end
+            % Set the paths
+            grouppath = ['/' groupname];
+            obj.xmlpath   = ['/' groupname '/xml'];
+            obj.datapath  = ['/' groupname '/data'];
+
+            % Check if the group exists
+            lapl_id=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid,grouppath,lapl_id) == 0)
+                % group does not exist, create it
+                group_id = H5G.create(obj.fid, grouppath, 0);
+                H5G.close(group_id);
+            end
+            H5P.close(lapl_id);
+
+        end
+
+        function obj = close(obj)
+            % close the file
+            H5F.close(obj.fid);
+        end
+        
+        function xmlstring = readxml(obj)
+            % Check if the XML header exists
+            lapl_id=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid,obj.xmlpath,lapl_id) == 0)
+                error('No XML header found.');
+            end
+            H5P.close(lapl_id);
+
+            % Open
+            xml_id = H5D.open(obj.fid, obj.xmlpath);
+
+            % Get the type
+            xml_dtype = H5D.get_type(xml_id);
+
+            % Read the data
+            hdr = H5D.read(xml_id, xml_dtype, 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT');
+
+            % Output depends on whether or not the stored string was variale length
+            if (H5T.is_variable_str(xml_dtype))
+                xmlstring = hdr{1};
+            else
+                xmlstring = hdr';
+            end
+
+            % Close the XML
+            H5T.close(xml_dtype);
+            H5D.close (xml_id);
+        end
+
+        function writexml(obj,xmlstring)
+            % No validation is performed.  You're on your own.
+            % make sure it's a char
+            xmlstring = char(xmlstring);
+            
+            % TODO: add error checking on the write and return a status
+            % TODO: if the matlab variable length string bug is resolved
+            % then we should change this logic to just modify the length
+            % and overwrite.
+
+            % Check if the XML header exists
+            %   if it does not exist, create it
+            %   if it exists, modify the size appropriately
+            lapl_id=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid,obj.xmlpath,lapl_id) == 1)
+                % Delete it
+                H5L.delete(obj.fid, obj.xmlpath,'H5P_DEFAULT');
+            end
+            H5P.close(lapl_id);
+
+            % Set variable length string type
+            xml_dtype = H5T.copy('H5T_C_S1');
+            % Matlab is having trouble writing variable length strings
+            % that are longer that 512 characters.  Switched to fixed
+            % length.
+            H5T.set_size(xml_dtype,'H5T_VARIABLE');
+            %H5T.set_size(xml_dtype, length(xmlstring));
+            xml_space_id = H5S.create_simple (1, 1, []);
+            xml_id = H5D.create(obj.fid, obj.xmlpath, xml_dtype, ....
+                                 xml_space_id, 'H5P_DEFAULT');
+            H5S.close(xml_space_id);
+
+            % Write the data
+            H5D.write(xml_id, xml_dtype, ...
+                      'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', {xmlstring});
+
+            % Close the XML
+            H5D.close(xml_id);
+        end
+
+        function nacq = getNumberOfAcquisitions(obj)
+
+            % Check if the Data exists
+            lapl_id=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid, obj.datapath, lapl_id) == 0)
+                error([obj.datapath ' does not exist in the HDF5 dataset.']);
+            end
+            dset = H5D.open(obj.fid, obj.datapath);
+            space = H5D.get_space(dset);
+            H5S.get_simple_extent_dims(space);
+            [~,dims,~] = H5S.get_simple_extent_dims(space);
+            nacq = dims(1);
+            H5S.close(space);
+            H5D.close(dset);
+
+        end
+
+        function block = readAcquisition(obj, start, stop)
+            if nargin == 1
+                % Read all the acquisitions
+                start = 1;
+                stop = -1;
+            elseif nargin == 2
+                % Read a single acquisition
+                stop = start;
+            end
+
+            % Check if the Data exists
+            lapl=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid, obj.datapath, lapl) == 0)
+                error([obj.datapath ' does not exist in the HDF5 dataset.']);
+            end
+
+            % Open the data
+            dset = H5D.open(obj.fid, obj.datapath);
+
+            % Open the data space
+            space = H5D.get_space(dset);
+            
+            % Get the size
+            [~,dims,~] = H5S.get_simple_extent_dims(space);
+            nacq = dims(1);
+
+            % Create a mem_space for reading
+            if (stop >= start)
+                offset = [start-1];
+                dims = [stop-start+1];
+                mem_space = H5S.create_simple(1,dims,[]);
+            else
+                offset = [0];
+                dims = [nacq];
+                mem_space = H5S.create_simple(1,dims,[]);
+            end
+
+            % Read the desired acquisitions            
+            H5S.select_hyperslab(space,'H5S_SELECT_SET',offset,[1],[1],dims);
+            d = H5D.read(dset, obj.htypes.T_Acquisition, ...
+                         mem_space, space, 'H5P_DEFAULT');
+                     
+            % Pack'em
+            block = ismrmrd.Acquisition(d.head, d.traj, d.data);
+
+            % Clean up
+            H5S.close(mem_space);
+            H5S.close(space);
+            H5D.close(dset);
+        end
+
+        function appendAcquisition(obj, acq)
+            % Append an acquisition
+
+            % TODO: Check the type of the input
+            
+            % The number of acquisitions that we are going to append
+            N = acq.getNumber();
+            
+            % Check if the Data exists
+            %   if it does not exist, create it
+            %   if it does exist increase it's size
+            lapl_id=H5P.create('H5P_LINK_ACCESS');
+            if (H5L.exists(obj.fid, obj.datapath, lapl_id) == 0)
+                % Data does not exist
+                %   create with rank 1, unlimited, and set the chunk size
+                dims    = [N];
+                maxdims = [H5ML.get_constant_value('H5S_UNLIMITED')];
+                file_space_id = H5S.create_simple(1, dims, maxdims);
+
+                dcpl = H5P.create('H5P_DATASET_CREATE');
+                chunk = [1];
+                H5P.set_chunk (dcpl, chunk);
+                data_id = H5D.create(obj.fid, obj.datapath, ...
+                                     obj.htypes.T_Acquisition, ...
+                                     file_space_id, dcpl);
+                H5P.close(dcpl);
+                H5S.close(file_space_id);
+
+            else
+                % Open the data
+                data_id = H5D.open(obj.fid, obj.datapath);
+
+                % Open the data space
+                file_space_id = H5D.get_space(data_id);
+
+                % Get the size, increment by N
+                H5S.get_simple_extent_dims(file_space_id);
+                [~,dims,~] = H5S.get_simple_extent_dims(file_space_id);
+                dims = [dims(1)+N];
+                H5D.set_extent (data_id, dims);
+                H5S.close(file_space_id);
+
+            end
+            H5P.close(lapl_id);
+
+            % Get the file space
+            file_space_id = H5D.get_space(data_id);
+            [~,dims,~] = H5S.get_simple_extent_dims(file_space_id);
+
+            % Select the last N block
+            offset = [dims(1)-N];
+            H5S.select_hyperslab(file_space_id,'H5S_SELECT_SET',offset,[1],[1],[N]);
+
+            % Mem space
+            mem_space_id = H5S.create_simple(1,[N],[]);
+
+            % Check and fix the acquisition header types
+            acq.head.check();
+            % TODO: Error checking on the sizes of the data and trajectories.
+            
+            % Pack the acquisition into the correct struct for writing
+            d = struct();
+            d.head = acq.head.toStruct();
+            d.traj = acq.trajToFloat();
+            d.data = acq.dataToFloat();
+            
+            % Write
+            H5D.write(data_id, obj.htypes.T_Acquisition, ...
+                      mem_space_id, file_space_id, 'H5P_DEFAULT', d);
+
+            % Clean up
+            H5S.close(mem_space_id);
+            H5S.close(file_space_id);
+            H5D.close(data_id);
+        end
+
+    end
+
+end
diff --git a/matlab/+ismrmrd/Image.m b/matlab/+ismrmrd/Image.m
new file mode 100644 (file)
index 0000000..2f75783
--- /dev/null
@@ -0,0 +1,35 @@
+% Image
+classdef Image
+
+    % Properties
+    properties
+
+        head_ = ismrmrd.ImageHeader;
+        data_ = [];
+
+    end % Properties
+
+    % Methods
+    methods
+
+        function obj = set.head_(obj,v)
+            obj.head_ = v;
+        end
+
+        function obj = set.data_(obj,v)
+            obj.data_ = single(complex(v));
+        end
+
+        function b = isFlagSet(obj,flag)
+            bitflag = ismrmrd.FlagBit(flag);
+            b = bitflag.isSet(obj.head_.flag);
+        end
+
+        function obj = setFlag(obj,flag)
+            bitflag = ismrmrd.FlagBit(flag);
+            obj.head_.flag = bitor(obj.head_.flag, bitflag.bitmask_);
+        end
+
+    end % Methods
+
+end
diff --git a/matlab/+ismrmrd/ImageHeader.m b/matlab/+ismrmrd/ImageHeader.m
new file mode 100644 (file)
index 0000000..d245a17
--- /dev/null
@@ -0,0 +1,537 @@
+classdef ImageHeader < handle
+    
+    properties
+        
+        version = uint16([]);                % First unsigned int indicates the version %
+        data_type = uint16([]);              % e.g. unsigned short, float, complex float, etc. */
+        flags = uint64([]);                  % bit field with flags %
+        measurement_uid = uint32([]);        % Unique ID for the measurement %
+        matrix_size = uint16([]);            % Pixels in the 3 spatial dimensions
+        field_of_view = single([]);          % Size (in mm) of the 3 spatial dimensions %
+        channels = uint16([]);               % Number of receive channels %
+        position = single([]);               % Three-dimensional spatial offsets from isocenter %
+        read_dir = single([]);               % Directional cosines of the readout/frequency encoding %
+        phase_dir = single([]);              % Directional cosines of the phase encoding %
+        slice_dir = single([]);              % Directional cosines of the slice %
+        patient_table_position = single([]); % Patient table off-center %
+        average = uint16([]);                % e.g. signal average number %
+        slice = uint16([]);                  % e.g. imaging slice number %
+        contrast = uint16([]);               % e.g. echo number in multi-echo %
+        phase = uint16([]);                  % e.g. cardiac phase number %
+        repetition = uint16([]);             % e.g. dynamic number for dynamic scanning %
+        set = uint16([]);                    % e.g. flow encodning set %
+        acquisition_time_stamp = uint32([]); % Acquisition clock %
+        physiology_time_stamp = uint32([]);  % Physiology time stamps, e.g. ecg, breating, etc. %
+        image_type = uint16([]);             % e.g. magnitude, phase, complex, real, imag, etc. %
+        image_index = uint16([]);                       % e.g. image number in series of images  %
+        image_series_index = uint16([]);     % e.g. series number %
+        user_int = int32([]);                % Free user parameters %
+        user_float = single([]);             % Free user parameters %
+        attribute_string_len = uint32([]);
+        
+    end
+
+    properties(Constant)
+        FLAGS = struct( ...
+            'IMAGE_IS_NAVIGATION_DATA',  1, ...
+            'IMAGE_USER1',              57, ...
+            'IMAGE_USER2',              58, ...
+            'IMAGE_USER3',              59, ...
+            'IMAGE_USER4',              60, ...
+            'IMAGE_USER5',              61, ...
+            'IMAGE_USER6',              62, ...
+            'IMAGE_USER7',              63, ...
+            'IMAGE_USER8',              64);
+        
+        DATA_TYPE = struct( ...
+            'USHORT',   uint16(1), ...
+            'SHORT',    uint16(2), ...
+            'UINT',     uint16(3), ...
+            'INT',      uint16(4), ...
+            'FLOAT',    uint16(5), ...
+            'DOUBLE',   uint16(6), ...
+            'CXFLOAT',  uint16(7), ...
+            'CXDOUBLE', uint16(8));
+        
+        IMAGE_TYPE = struct( ...
+            'MAGNITUDE', uint16(1), ...
+            'PHASE',     uint16(2), ...
+            'REAL',      uint16(3), ...
+            'IMAG',      uint16(4), ...
+            'COMPLEX',   uint16(5));
+
+    end
+    
+    methods
+        
+        function obj = ImageHeader(arg)
+            switch nargin
+                case 0
+                    % No argument constructor
+                    % initialize to a single image header
+                    extend(obj,1);
+                    
+                case 1
+                    % One argument constructor
+                    if isstruct(arg)
+                        % plain struct
+                        fromStruct(obj,arg);
+                    elseif (length(arg)==1 && ismrmrd.util.isInt(arg)) == 1
+                        % number
+                        extend(obj,arg);
+                    elseif isa(arg,'uint8')
+                        % Byte array
+                        fromBytes(obj,arg);
+                    else
+                        % Unknown type
+                        error('Unknown argument type.')
+                    end
+                    
+                otherwise
+                    error('Wrong number of arguments.')
+                    
+            end
+        end
+        
+        function nacq = getNumber(obj)
+            nacq = length(obj.version);
+        end
+        
+        function hdr = select(obj, range)
+            % Return a copy of a range of image headers
+            
+            % create an empty image header
+            M = length(range);
+            hdr = ismrmrd.ImageHeader(M);
+            
+            % Fill
+            hdr.version = obj.version(range);
+            hdr.flags = obj.flags(range);
+            hdr.measurement_uid = obj.measurement_uid(range);
+            hdr.matrix_size = obj.matrix_size(:,range);
+            hdr.field_of_view = obj.field_of_view(:,range);
+            hdr.channels = obj.channels(:,range);
+            hdr.position = obj.position(:,range);
+            hdr.read_dir = obj.read_dir(:,range);
+            hdr.phase_dir = obj.phase_dir(:,range);
+            hdr.slice_dir = obj.slice_dir(:,range);
+            hdr.patient_table_position = obj.patient_table_position(:,range);
+            hdr.average = obj.average(range);
+            hdr.slice = obj.slice(range);
+            hdr.contrast = obj.contrast(range);
+            hdr.phase = obj.phase(range);
+            hdr.repetition = obj.repetition(range);
+            hdr.set = obj.set(range);
+            hdr.acquisition_time_stamp = obj.acquisition_time_stamp(range);
+            hdr.physiology_time_stamp = obj.physiology_time_stamp(:,range);
+            hdr.data_type = obj.data_type(range);
+            hdr.image_type = obj.image_type(range);
+            hdr.image_index = obj.image_index(range);
+            hdr.image_series_index = obj.image_series_index(range);
+            hdr.user_int = obj.user_int(:,range);
+            hdr.user_float = obj.user_float(:,range);
+            hdr.attribute_string_len = obj.attribute_string_len(:,range);
+
+        end        
+        
+        function extend(obj,N)
+            % Extend with blank header
+
+            range = obj.getNumber + (1:N);            
+            obj.version(1,range)                  = zeros(1,N,'uint16');
+            obj.flags(1,range)                    = zeros(1,N,'uint64');
+            obj.measurement_uid(1,range)          = zeros(1,N,'uint32');
+            obj.matrix_size(1:3,range)            = zeros(3,N,'uint16');
+            obj.field_of_view(1:3,range)          = zeros(3,N,'single');
+            obj.channels(1,range)                 = zeros(1,N,'uint16');
+            obj.position(1:3,range)               = zeros(3,N,'single');
+            obj.read_dir(1:3,range)               = zeros(3,N,'single');
+            obj.phase_dir(1:3,range)              = zeros(3,N,'single');
+            obj.slice_dir(1:3,range)              = zeros(3,N,'single');
+            obj.patient_table_position(1:3,range) = zeros(3,N,'single');
+            obj.average(1,range)                  = zeros(1,N,'uint16');
+            obj.slice(1,range)                    = zeros(1,N,'uint16');
+            obj.contrast(1,range)                 = zeros(1,N,'uint16');
+            obj.phase(1,range)                    = zeros(1,N,'uint16');
+            obj.repetition(1,range)               = zeros(1,N,'uint16');
+            obj.set(1,range)                      = zeros(1,N,'uint16');
+            obj.acquisition_time_stamp(1,range)   = zeros(1,N,'uint32');
+            obj.physiology_time_stamp(1:3,range)  = zeros(3,N,'uint32');
+            obj.data_type(1,range)                = zeros(1,N,'uint16');
+            obj.image_type(1,range)               = zeros(1,N,'uint16');
+            obj.image_index(1,range)              = zeros(1,N,'uint16');
+            obj.image_series_index(1,range)       = zeros(1,N,'uint16');
+            obj.user_int(1:8,range)               = zeros(8,N,'int32');
+            obj.user_float(1:8,range)             = zeros(8,N,'single');
+            obj.attribute_string_len              = zeros(1,N,'uint32');
+        end
+        
+        function append(obj, head)
+            Nstart = obj.getNumber + 1;
+            Nend   = obj.getNumber + length(head.version);
+            Nrange = Nstart:Nend;
+            obj.version(Nrange) = hdr.version;
+            obj.flags(Nrange) = hdr.flags;
+            obj.measurement_uid(Nrange) = hdr.measurement_uid;
+            obj.matrix_size(:,Nrange) = hdr.matrix_size;
+            obj.field_of_view(:,Nrange) = hdr.field_of_view;
+            obj.channels(Nrange) = hdr.channels;
+            obj.position(:,Nrange) = hdr.position;
+            obj.read_dir(:,Nrange) = hdr.read_dir;
+            obj.phase_dir(:,Nrange) = hdr.phase_dir;
+            obj.slice_dir(:,Nrange) = hdr.slice_dir;
+            obj.patient_table_position(:,Nrange) = hdr.patient_table_position;
+            obj.average(Nrange) = hdr.average;
+            obj.slice(Nrange) = hdr.slice;
+            obj.contrast(Nrange) = hdr.contrast;
+            obj.phase(Nrange) = hdr.phase;
+            obj.repetition(Nrange) = hdr.repetition;
+            obj.set(Nrange) = hdr.set;
+            obj.acquisition_time_stamp(Nrange) = hdr.acquisition_time_stamp;
+            obj.physiology_time_stamp(:,Nrange) = hdr.physiology_time_stamp;
+            obj.data_type(Nrange) = hdr.data_type;
+            obj.image_type(Nrange) = hdr.image_type;
+            obj.image_index(Nrange) = hdr.image_index;
+            obj.image_series_index(Nrange) = hdr.image_series_index;
+            obj.user_int(:,Nrange) = hdr.user_int;
+            obj.user_float(:,Nrange) = hdr.user_float;
+            obj.attribute_string_len(Nrange) = hdr.attribute_string_len;
+            
+        end
+
+        function fromStruct(obj, hdr)
+            %warning! no error checking
+            obj.version = hdr.version;
+            obj.flags = hdr.flags;
+            obj.measurement_uid = hdr.measurement_uid;
+            obj.matrix_size = hdr.matrix_size;
+            obj.field_of_view = hdr.field_of_view;
+            obj.channels = hdr.channels;
+            obj.position = hdr.position;
+            obj.read_dir = hdr.read_dir;
+            obj.phase_dir = hdr.phase_dir;
+            obj.slice_dir = hdr.slice_dir;
+            obj.patient_table_position = hdr.patient_table_position;
+            obj.average = hdr.average;
+            obj.slice = hdr.slice;
+            obj.contrast = hdr.contrast;
+            obj.phase = hdr.phase;
+            obj.repetition = hdr.repetition;
+            obj.set = hdr.set;
+            obj.acquisition_time_stamp = hdr.acquisition_time_stamp;
+            obj.physiology_time_stamp = hdr.physiology_time_stamp;
+            obj.data_type = hdr.data_type;
+            obj.image_type = hdr.image_type;
+            obj.image_index = hdr.image_index;
+            obj.image_series_index = hdr.image_series_index;
+            obj.user_int = hdr.user_int;
+            obj.user_float = hdr.user_float;
+            obj.attribute_string_len = hdr.attribute_string_len;
+        end
+
+        function hdr = toStruct(obj)
+            %warning! no error checking
+            hdr = struct();
+            hdr.version = obj.version;
+            hdr.flags = obj.flags;
+            hdr.measurement_uid = obj.measurement_uid;
+            hdr.matrix_size = obj.matrix_size;
+            hdr.field_of_view = obj.field_of_view;
+            hdr.channels = obj.channels;
+            hdr.position = obj.position;
+            hdr.read_dir = obj.read_dir;
+            hdr.phase_dir = obj.phase_dir;
+            hdr.slice_dir = obj.slice_dir;
+            hdr.patient_table_position = obj.patient_table_position;
+            hdr.average = obj.average;
+            hdr.slice = obj.slice;
+            hdr.contrast = obj.contrast;
+            hdr.phase = obj.phase;
+            hdr.repetition = obj.repetition;
+            hdr.set = obj.set;
+            hdr.acquisition_time_stamp = obj.acquisition_time_stamp;
+            hdr.physiology_time_stamp = obj.physiology_time_stamp;
+            hdr.data_type = obj.data_type;
+            hdr.image_type = obj.image_type;
+            hdr.image_index = obj.image_index;
+            hdr.image_series_index = obj.image_series_index;
+            hdr.user_int = obj.user_int;
+            hdr.user_float = obj.user_float;       
+            hdr.attribute_string_len = obj.attribute_string_len;
+        end
+        
+        function fromBytes(obj, bytearray)
+            % Convert from a byte array to an ISMRMRD ImageHeader
+           % This conforms to the memory layout of the C-struct
+            if size(bytearray,1) ~= 198
+                error('Wrong number of bytes for ImageHeader.')
+            end
+            N = size(bytearray,2);
+            for p = 1:N
+                obj.version(p)                  = typecast(bytearray(1:2,p),     'uint16');
+                obj.data_type(p)                = typecast(bytearray(3:4,p), 'uint16');
+                obj.flags(p)                    = typecast(bytearray(5:12,p),    'uint64');
+                obj.measurement_uid(p)          = typecast(bytearray(13:16,p),   'uint32');
+                obj.matrix_size(:,p)            = typecast(bytearray(17:22,p),   'uint16');
+                obj.field_of_view(:,p)          = typecast(bytearray(23:34,p),   'single');
+                obj.channels(p)                 = typecast(bytearray(35:36,p),   'uint16');
+                obj.position(:,p)               = typecast(bytearray(37:48,p),   'single');
+                obj.read_dir(:,p)               = typecast(bytearray(49:60,p),   'single');
+                obj.phase_dir(:,p)              = typecast(bytearray(61:72,p),   'single');
+                obj.slice_dir(:,p)              = typecast(bytearray(73:84,p),   'single');
+                obj.patient_table_position(:,p) = typecast(bytearray(85:96,p),   'single');
+                obj.average(p)                  = typecast(bytearray(97:98,p),   'uint16');
+                obj.slice(p)                    = typecast(bytearray(99:100,p),   'uint16');
+                obj.contrast(p)                 = typecast(bytearray(101:102,p),  'uint16');
+                obj.phase(p)                    = typecast(bytearray(103:104,p), 'uint16');
+                obj.repetition(p)               = typecast(bytearray(105:106,p), 'uint16');
+                obj.set(p)                      = typecast(bytearray(107:108,p), 'uint16');
+                obj.acquisition_time_stamp(p)   = typecast(bytearray(109:112,p), 'uint32');
+                obj.physiology_time_stamp(:,p)  = typecast(bytearray(113:124,p), 'uint32');                                                          
+                obj.image_type(p)               = typecast(bytearray(125:126,p), 'uint16');
+                obj.image_index(p)              = typecast(bytearray(127:128,p), 'uint16');
+                obj.image_series_index(p)       = typecast(bytearray(129:130,p), 'uint16');
+                obj.user_int(:,p)               = typecast(bytearray(131:162,p), 'uint32');
+                obj.user_float(:,p)             = typecast(bytearray(163:194,p), 'single');
+                obj.attribute_string_len        = typecast(bytearray(195:198,p), 'uint32');
+                
+            end              
+        end
+        
+        function bytes = toBytes(obj)
+            % Convert an ISMRMRD AcquisitionHeader to a byte array
+           % This conforms to the memory layout of the C-struct
+
+            N = obj.getNumber;
+            bytes = zeros(198,N,'uint8');
+            for p = 1:N
+                off = 1;
+                bytes(off:off+1,p)   = typecast(obj.version(p)                 ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.data_type(p)               ,'uint8'); off=off+2;
+                bytes(off:off+7,p)   = typecast(obj.flags(p)                   ,'uint8'); off=off+8;
+                bytes(off:off+3,p)   = typecast(obj.measurement_uid(p)         ,'uint8'); off=off+4;
+                bytes(off:off+5,p)   = typecast(obj.matrix_size(:,p)           ,'uint8'); off=off+6;
+                bytes(off:off+11,p)  = typecast(obj.field_of_view(:,p)         ,'uint8'); off=off+12;
+                bytes(off:off+1,p)   = typecast(obj.channels(p)                ,'uint8'); off=off+2;
+                bytes(off:off+11,p)  = typecast(obj.position(:,p)              ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.read_dir(:,p)              ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.phase_dir(:,p)             ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.slice_dir(:,p)             ,'uint8'); off=off+12;
+                bytes(off:off+11,p)  = typecast(obj.patient_table_position(:,p),'uint8'); off=off+12;
+                bytes(off:off+1,p)   = typecast(obj.average(p)                 ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.slice(p)                   ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.contrast(p)                ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.phase(p)                   ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.repetition(p)              ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.set(p)                     ,'uint8'); off=off+2;
+                bytes(off:off+3,p)   = typecast(obj.acquisition_time_stamp(p)  ,'uint8'); off=off+4;
+                
+                bytes(off:off+11,p)  = typecast(obj.physiology_time_stamp(:,p) ,'uint8'); off=off+12;
+                
+                bytes(off:off+1,p)   = typecast(obj.image_type(p)              ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.image_index(p)             ,'uint8'); off=off+2;
+                bytes(off:off+1,p)   = typecast(obj.image_series_index(p)      ,'uint8'); off=off+2;
+                bytes(off:off+31,p)  = typecast(obj.user_int(:,p)              ,'uint8'); off=off+32;
+                bytes(off:off+31,p)  = typecast(obj.user_float(:,p)            ,'uint8'); off=off+32;
+                bytes(off:off+3,p)  = typecast(obj.attribute_string_len(:,p)   ,'uint8'); 
+                
+            end
+        end
+        
+        function obj = check(obj)
+            % Check and fix the obj types
+            
+            % Check the number of elements for each entry
+            N = obj.getNumber();
+            if (size(obj.flags) ~= N)
+                error('Size of flags is not correct.');
+            end
+            if ((size(obj.measurement_uid,1) ~= 1) || ...
+                (size(obj.measurement_uid,2) ~= N))
+                error('Size of measurement_uid is not correct.');
+            end
+            if ((size(obj.matrix_size,1) ~= 3) || ...
+                (size(obj.matrix_size,2) ~= N))
+                error('Size of matrix_size is not correct.');
+            end
+            if ((size(obj.field_of_view,1) ~= 3) || ...
+                (size(obj.field_of_view,2) ~= N))
+                error('Size of field_of_view is not correct.');
+            end
+            if ((size(obj.channels,1) ~= 1) || ...
+                (size(obj.channels,2) ~= N))
+                error('Size of field_of_view is not correct.');
+            end
+            if ((size(obj.position,1) ~= 3) || ...
+                (size(obj.position,2) ~= N))    
+                error('Size of position is not correct.');
+            end
+            if ((size(obj.read_dir,1) ~= 3) || ...
+                (size(obj.read_dir,2) ~= N))    
+                error('Size of read_dir is not correct.');
+            end
+            if ((size(obj.phase_dir,1) ~= 3) || ...
+                (size(obj.phase_dir,2) ~= N))    
+                error('Size of phase_dir is not correct.');
+            end
+            if ((size(obj.slice_dir,1) ~= 3) || ...
+                (size(obj.slice_dir,2) ~= N))    
+                error('Size of slice_dir is not correct.');
+            end
+            if ((size(obj.patient_table_position,1) ~= 3) || ...
+                (size(obj.patient_table_position,2) ~= N))    
+                error('Size of patient_table_position is not correct.');
+            end
+            if ((size(obj.average,1) ~= 1) || ...
+                (size(obj.average,2) ~= N))    
+                error('Size of average is not correct.');
+            end
+            if ((size(obj.slice,1) ~= 1) || ...
+                (size(obj.slice,2) ~= N))    
+                error('Size of slice is not correct.');
+            end
+            if ((size(obj.contrast,1) ~= 1) || ...
+                (size(obj.contrast,2) ~= N))    
+                error('Size of contrast is not correct.');
+            end
+            if ((size(obj.phase,1) ~= 1) || ...
+                (size(obj.phase,2) ~= N))    
+                error('Size of phase is not correct.');
+            end
+            if ((size(obj.repetition,1) ~= 1) || ...
+                (size(obj.repetition,2) ~= N))    
+                error('Size of repetition is not correct.');
+            end
+            if ((size(obj.set,1) ~= 1) || ...
+                (size(obj.set,2) ~= N))    
+                error('Size of set is not correct.');
+            end            
+            if ((size(obj.acquisition_time_stamp,1) ~= 1) || ...
+                (size(obj.acquisition_time_stamp,2) ~= N))
+                error('Size of acquisition_time_stamp is not correct.');
+            end
+            if ((size(obj.physiology_time_stamp,1) ~= 3) || ...
+                (size(obj.physiology_time_stamp,2) ~= N))
+                error('Size of physiology_time_stamp is not correct.');
+            end
+            if ((size(obj.data_type,1) ~= 1) || ...
+                (size(obj.data_type,2) ~= N))
+                error('Size of image_data_type is not correct.');
+            end
+            if ((size(obj.image_type,1) ~= 1) || ...
+                (size(obj.image_type,2) ~= N))
+                error('Size of image_type is not correct.');
+            end
+            if ((size(obj.image_index,1) ~= 1) || ...
+                (size(obj.image_index,2) ~= N))
+                error('Size of image_index is not correct.');
+            end
+            if ((size(obj.image_series_index,1) ~= 1) || ...
+                (size(obj.image_series_index,2) ~= N))
+                error('Size of image_series_index is not correct.');
+            end
+            if ((size(obj.user_int,1) ~= 8) || ...
+                (size(obj.user_int,2) ~= N))    
+                error('Size of user_int is not correct.');
+            end
+            if ((size(obj.user_float,1) ~= 8) || ...
+                (size(obj.user_float,2) ~= N))    
+                error('Size of user_float is not correct.');
+            end
+            
+            % Fix the type of all the elements
+            obj.version = uint16(obj.version);
+            obj.flags = uint64(obj.flags);
+            obj.measurement_uid = uint32(obj.measurement_uid);
+            obj.matrix_size = uint16(obj.matrix_size);
+            obj.field_of_view = single(obj.field_of_view);
+            obj.channels = uint16(obj.channels);
+            obj.position = single(obj.position);
+            obj.read_dir = single(obj.read_dir);
+            obj.phase_dir = single(obj.phase_dir);
+            obj.slice_dir = single(obj.slice_dir);
+            obj.patient_table_position = single(obj.patient_table_position);
+            obj.average = uint16(obj.average);
+            obj.slice = uint16(obj.slice);
+            obj.contrast = uint16(obj.contrast);
+            obj.phase = uint16(obj.phase);
+            obj.repetition = uint16(obj.repetition);
+            obj.set = uint16(obj.set);
+            obj.acquisition_time_stamp = uint32(obj.acquisition_time_stamp);
+            obj.physiology_time_stamp = uint32(obj.physiology_time_stamp);            
+            obj.data_type = uint16(obj.data_type);
+            obj.image_type = uint16(obj.image_type);
+            obj.image_index = uint16(obj.image_index);
+            obj.image_series_index = uint16(obj.image_series_index);
+            obj.user_int = int32(obj.user_int);
+            obj.user_float = single(obj.user_float);
+            obj.attribute_string_len = uint32(obj.attribute_string_len);
+        end
+        
+        function ret = flagIsSet(obj, flag, range)
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+            ret = zeros(size(range));
+            for p = 1:length(range)
+                ret(p) = (bitand(obj.flags(range(p)), bitmask)>0);
+            end
+        end
+        
+        function flagSet(obj, flag, range)
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+
+            alreadyset = obj.flagIsSet(flag,range);
+            for p = 1:length(range)
+                if ~alreadyset(p)
+                    obj.flags(range(p)) = obj.flags(range(p)) + bitmask;
+                end
+            end
+        end
+        
+        function flagClear(obj, flag, range)
+            if nargin < 3
+                range = 1:obj.getNumber;
+            end
+            
+            if isa(flag, 'char')
+                b = obj.FLAGS.(flag);
+            elseif (flag>0)
+                b = uint64(flag);
+            else
+                error('Flag is of the wrong type.'); 
+            end
+            bitmask = bitshift(uint64(1),(b-1));
+            
+            alreadyset = obj.flagIsSet(flag,range);
+            for p = 1:length(range)
+                if alreadyset(p)
+                    obj.flags(range(p)) = obj.flags(range(p)) - bitmask;
+                end
+            end
+                
+            
+        end
+        
+    end
+    
+end
diff --git a/schema/ismrmrd.xsd b/schema/ismrmrd.xsd
new file mode 100644 (file)
index 0000000..02168e4
--- /dev/null
@@ -0,0 +1,277 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xs:schema xmlns="http://www.ismrm.org/ISMRMRD" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.ismrm.org/ISMRMRD" version="2">
+
+    <xs:element name="ismrmrdHeader" type="ismrmrdHeader"/>
+
+    <xs:complexType name="ismrmrdHeader">
+      <xs:sequence>
+        <xs:element maxOccurs="1" minOccurs="0" name="version" type="xs:long"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="subjectInformation" type="subjectInformationType"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="studyInformation" type="studyInformationType"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="measurementInformation" type="measurementInformationType"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="acquisitionSystemInformation" type="acquisitionSystemInformationType"/>
+        <xs:element maxOccurs="1" minOccurs="1" name="experimentalConditions" type="experimentalConditionsType"/>
+        <xs:element maxOccurs="unbounded" minOccurs="1" name="encoding" type="encoding"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="sequenceParameters" type="sequenceParametersType"/>
+        <xs:element maxOccurs="1" minOccurs="0" name="userParameters" type="userParameters"/>
+      </xs:sequence>
+    </xs:complexType>
+
+  <xs:complexType name="subjectInformationType">
+    <xs:all>
+      <xs:element minOccurs="0" name="patientName" type="xs:string"/>
+      <xs:element minOccurs="0" name="patientWeight_kg" type="xs:float"/>
+      <xs:element minOccurs="0" name="patientID" type="xs:string"/>
+      <xs:element minOccurs="0" name="patientBirthdate" type="xs:date"/>
+      <xs:element minOccurs="0" name="patientGender">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+            <xs:pattern value="[MFO]"/>
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:element>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="studyInformationType">
+    <xs:all>
+      <xs:element minOccurs="0" maxOccurs="1" name="studyDate" type="xs:date"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="studyTime" type="xs:time"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="studyID" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="accessionNumber" type="xs:long"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="referringPhysicianName" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="studyDescription" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="studyInstanceUID" type="xs:string"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="measurementInformationType">
+    <xs:sequence>
+      <xs:element minOccurs="0" name="measurementID" type="xs:string"/>
+      <xs:element minOccurs="0" name="seriesDate" type="xs:date"/>
+      <xs:element minOccurs="0" name="seriesTime" type="xs:time"/>
+      <xs:element minOccurs="1" name="patientPosition">
+        <xs:simpleType>
+          <xs:restriction base="xs:string">
+            <xs:enumeration value="HFP"/>
+            <xs:enumeration value="HFS"/>
+            <xs:enumeration value="HFDR"/>
+            <xs:enumeration value="HFDL"/>
+            <xs:enumeration value="FFP"/>
+            <xs:enumeration value="FFS"/>
+            <xs:enumeration value="FFDR"/>
+            <xs:enumeration value="FFDL"/>
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:element>
+      <xs:element minOccurs="0" name="initialSeriesNumber" type="xs:long"/>
+      <xs:element minOccurs="0" name="protocolName" type="xs:string"/>
+      <xs:element minOccurs="0" name="seriesDescription" type="xs:string"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="measurementDependency" type="measurementDependencyType"/>
+      <xs:element minOccurs="0" name="seriesInstanceUIDRoot" type="xs:string"/>
+      <xs:element minOccurs="0" name="frameOfReferenceUID" type="xs:string"/>
+      <xs:element minOccurs="0" name="referencedImageSequence" type="referencedImageSequence"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="measurementDependencyType">
+    <xs:sequence>
+        <xs:element maxOccurs="1" minOccurs="1" name="dependencyType" type="xs:string"/>
+        <xs:element maxOccurs="1" minOccurs="1" name="measurementID" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="coilLabelType">
+    <xs:sequence>
+      <xs:element maxOccurs="1" minOccurs="1" name="coilNumber" type="xs:unsignedShort" />
+      <xs:element maxOccurs="1" minOccurs="1" name="coilName" type="xs:string" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="acquisitionSystemInformationType">
+    <xs:sequence>
+      <xs:element minOccurs="0" maxOccurs="1" name="systemVendor" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="systemModel" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="systemFieldStrength_T" type="xs:float"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="relativeReceiverNoiseBandwidth" type="xs:float"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="receiverChannels" type="xs:unsignedShort"/>
+      <xs:element minOccurs="0" maxOccurs="unbounded" name="coilLabel" type="coilLabelType" />  
+      <xs:element minOccurs="0" maxOccurs="1" name="institutionName" type="xs:string"/>
+      <xs:element minOccurs="0" maxOccurs="1" name="stationName" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="experimentalConditionsType">
+    <xs:all>
+      <xs:element name="H1resonanceFrequency_Hz" type="xs:long"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="encoding">
+    <xs:all>
+      <xs:element maxOccurs="1" minOccurs="1" name="encodedSpace" type="encodingSpaceType"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="reconSpace" type="encodingSpaceType"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="encodingLimits" type="encodingLimitsType"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="trajectory" type="trajectoryType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="trajectoryDescription" type="trajectoryDescriptionType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="parallelImaging" type="parallelImagingType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="echoTrainLength" type="xs:long"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="encodingSpaceType">
+    <xs:all>
+      <xs:element maxOccurs="1" minOccurs="1" name="matrixSize" type="matrixSize"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="fieldOfView_mm" type="fieldOfView_mm"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="matrixSize">
+    <xs:sequence>
+      <xs:element default="1" maxOccurs="1" minOccurs="1" name="x" type="xs:unsignedShort"/>
+      <xs:element default="1" maxOccurs="1" minOccurs="1" name="y" type="xs:unsignedShort"/>
+      <xs:element default="1" maxOccurs="1" minOccurs="1" name="z" type="xs:unsignedShort"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="fieldOfView_mm">
+    <xs:sequence>
+      <xs:element maxOccurs="1" minOccurs="1" name="x" type="xs:float"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="y" type="xs:float"/>
+      <xs:element maxOccurs="1" minOccurs="1" name="z" type="xs:float"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="limitType">
+    <xs:all>
+      <xs:element default="0" name="minimum" type="xs:unsignedShort"/>
+      <xs:element default="0" name="maximum" type="xs:unsignedShort"/>
+      <xs:element default="0" name="center" type="xs:unsignedShort"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="encodingLimitsType">
+    <xs:all>
+      <xs:element maxOccurs="1" minOccurs="0" name="kspace_encoding_step_0" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="kspace_encoding_step_1" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="kspace_encoding_step_2" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="average" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="slice" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="contrast" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="phase" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="repetition" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="set" type="limitType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="segment" type="limitType"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:simpleType name="trajectoryType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="cartesian"/>
+      <xs:enumeration value="epi"/>
+      <xs:enumeration value="radial"/>
+      <xs:enumeration value="goldenangle"/>
+      <xs:enumeration value="spiral"/>
+      <xs:enumeration value="other"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:complexType name="trajectoryDescriptionType">
+    <xs:sequence>
+      <xs:element maxOccurs="1" minOccurs="1" name="identifier" type="xs:string"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterLong" type="userParameterLongType"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterDouble" type="userParameterDoubleType"/>
+      <xs:element maxOccurs="1" minOccurs="0" name="comment" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="sequenceParametersType">
+    <xs:sequence>
+      <xs:element minOccurs="0" maxOccurs="unbounded" type="xs:float" name="TR"/>
+      <xs:element minOccurs="0" maxOccurs="unbounded" type="xs:float" name="TE"/>
+      <xs:element minOccurs="0" maxOccurs="unbounded" type="xs:float" name="TI"/>
+      <xs:element minOccurs="0" maxOccurs="unbounded" type="xs:float" name="flipAngle_deg"/>
+      <xs:element minOccurs="0" maxOccurs="1" type="xs:string" name="sequence_type"/>
+      <xs:element minOccurs="0" maxOccurs="unbounded" type="xs:float" name="echo_spacing"/>
+
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="userParameterLongType">
+    <xs:all>
+      <xs:element name="name" type="xs:string"/>
+      <xs:element name="value" type="xs:long"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="userParameterDoubleType">
+    <xs:all>
+      <xs:element name="name" type="xs:string"/>
+      <xs:element name="value" type="xs:double"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="userParameterStringType">
+    <xs:all>
+      <xs:element name="name" type="xs:string"/>
+      <xs:element name="value" type="xs:string"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="userParameterBase64Type">
+    <xs:all>
+      <xs:element name="name" type="xs:string"/>
+      <xs:element name="value" type="xs:base64Binary"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:complexType name="referencedImageSequence">
+    <xs:sequence>
+      <xs:element minOccurs="0" maxOccurs="unbounded" name="referencedSOPInstanceUID" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
+      
+  <xs:complexType name="userParameters">
+    <xs:sequence>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterLong" type="userParameterLongType"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterDouble" type="userParameterDoubleType"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterString" type="userParameterStringType"/>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="userParameterBase64" type="userParameterBase64Type"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="accelerationFactorType">
+    <xs:all>
+      <xs:element name="kspace_encoding_step_1" type="xs:unsignedShort"/>
+      <xs:element name="kspace_encoding_step_2" type="xs:unsignedShort"/>
+    </xs:all>
+  </xs:complexType>
+
+  <xs:simpleType name="calibrationModeType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="embedded"/>
+      <xs:enumeration value="interleaved"/>
+      <xs:enumeration value="separate"/>
+      <xs:enumeration value="external"/>
+      <xs:enumeration value="other"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="interleavingDimensionType">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="phase"/>
+      <xs:enumeration value="repetition"/>
+      <xs:enumeration value="contrast"/>
+      <xs:enumeration value="average"/>
+      <xs:enumeration value="other"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:complexType name="parallelImagingType">
+       <xs:sequence>
+        <xs:element type="accelerationFactorType" name="accelerationFactor"/>
+        <xs:element maxOccurs="1" minOccurs="0" type="calibrationModeType" name="calibrationMode"/>
+        <xs:element maxOccurs="1" minOccurs="0" type="interleavingDimensionType" name="interleavingDimension"/>
+       </xs:sequence>
+  </xs:complexType>
+</xs:schema>
diff --git a/schema/ismrmrd_example.xml b/schema/ismrmrd_example.xml
new file mode 100644 (file)
index 0000000..3e5c157
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<ismrmrdHeader xmlns="http://www.ismrm.org/ISMRMRD" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.ismrm.org/ISMRMRD ismrmrd.xsd">
+  <subjectInformation>
+    <patientName>phantom</patientName>
+    <patientWeight_kg>70.3068</patientWeight_kg>
+  </subjectInformation>
+  <acquisitionSystemInformation>
+    <systemVendor>SIEMENS</systemVendor>
+    <systemModel>Avanto</systemModel>
+    <systemFieldStrength_T>1.494</systemFieldStrength_T>
+    <receiverChannels>32</receiverChannels>
+    <relativeReceiverNoiseBandwidth>0.79</relativeReceiverNoiseBandwidth>
+  </acquisitionSystemInformation>
+  <experimentalConditions>
+    <H1resonanceFrequency_Hz>63642459</H1resonanceFrequency_Hz>
+  </experimentalConditions>
+  <encoding>
+    <trajectory>cartesian</trajectory>
+    <encodedSpace>
+      <matrixSize>
+        <x>256</x>
+        <y>140</y>
+        <z>80</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>600</x>
+        <y>328.153125</y>
+        <z>160</z>
+      </fieldOfView_mm>
+    </encodedSpace>
+    <reconSpace>
+      <matrixSize>
+        <x>128</x>
+        <y>116</y>
+        <z>64</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>300</x>
+        <y>271.875</y>
+        <z>128</z>
+      </fieldOfView_mm>
+    </reconSpace>
+    <encodingLimits>
+      <kspace_encoding_step_1>
+        <minimum>0</minimum>
+        <maximum>83</maximum>
+        <center>28</center>
+      </kspace_encoding_step_1>
+      <kspace_encoding_step_2>
+        <minimum>0</minimum>
+        <maximum>45</maximum>
+        <center>20</center>
+      </kspace_encoding_step_2>
+      <slice>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </slice>
+      <set>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </set>
+    </encodingLimits>
+  </encoding>
+  <parallelImaging>
+    <accelerationFactor>
+      <kspace_encoding_step_1>1</kspace_encoding_step_1>
+      <kspace_encoding_step_2>1</kspace_encoding_step_2>
+    </accelerationFactor>
+    <calibrationMode>other</calibrationMode>
+  </parallelImaging>
+  <sequenceParameters>
+    <TR>4.6</TR>
+    <TE>2.35</TE>
+    <TI>300</TI>
+  </sequenceParameters>
+</ismrmrdHeader>
+
diff --git a/schema/ismrmrd_example_extended.xml b/schema/ismrmrd_example_extended.xml
new file mode 100644 (file)
index 0000000..deb8dbe
--- /dev/null
@@ -0,0 +1,376 @@
+<?xml version="1.0"?>
+<ismrmrdHeader xmlns="http://www.ismrm.org/ISMRMRD" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.ismrm.org/ISMRMRD ismrmrd.xsd">
+  <subjectInformation>
+    <patientName>Anonymous Patient</patientName>
+    <patientWeight_kg>70.3068</patientWeight_kg>
+    <patientID>BHHG8866789</patientID>
+    <patientBirthdate>1980-06-12</patientBirthdate>
+    <patientGender>M</patientGender>
+  </subjectInformation>
+  <studyInformation>
+    <studyDate>2012-08-13</studyDate>
+    <studyTime>09:00:00</studyTime>
+    <studyID>NNLLJHG88997</studyID>
+    <accessionNumber>7638376482</accessionNumber>
+    <referringPhysicianName>John Doe, MD</referringPhysicianName>
+    <studyDescription>This is an example study with all headers set</studyDescription>
+    <studyInstanceUID>1.34.896.2789852.787873.9999</studyInstanceUID>
+  </studyInformation>
+  <measurementInformation>
+    <measurementID>LLJGHH888986</measurementID>
+    <seriesDate>2012-08-13</seriesDate>
+    <seriesTime>09:10:12</seriesTime>
+    <patientPosition>HFS</patientPosition>
+    <initialSeriesNumber>1</initialSeriesNumber>
+    <protocolName>ExampleProt</protocolName>
+    <seriesDescription>MRIStudy1</seriesDescription>
+    <measurementDependency>
+      <dependencyType>Noise</dependencyType>
+      <measurementID>HHJJHJL000977889</measurementID>
+    </measurementDependency>
+    <measurementDependency>
+      <dependencyType>SurfaceCoilCorrection</dependencyType>
+      <measurementID>HHJJHJL000977810</measurementID>
+    </measurementDependency>
+    <seriesInstanceUIDRoot>1.34.896.2789852.787873</seriesInstanceUIDRoot>
+    <frameOfReferenceUID>1.34.896.2789852.787873.98788.78787</frameOfReferenceUID>
+    <referencedImageSequence>
+      <referencedSOPInstanceUID>1.34.896.2789852.787873.98788.78787.1</referencedSOPInstanceUID>
+      <referencedSOPInstanceUID>1.34.896.2789852.787873.98788.78787.2</referencedSOPInstanceUID>
+      <referencedSOPInstanceUID>1.34.896.2789852.787873.98788.78787.3</referencedSOPInstanceUID>
+      <referencedSOPInstanceUID>1.34.896.2789852.787873.98788.78787.4</referencedSOPInstanceUID>
+    </referencedImageSequence>
+  </measurementInformation>
+  <acquisitionSystemInformation>
+    <systemVendor>SIEMENS</systemVendor>
+    <systemModel>Avanto</systemModel>
+    <systemFieldStrength_T>1.494</systemFieldStrength_T>
+    <relativeReceiverNoiseBandwidth>0.79</relativeReceiverNoiseBandwidth>
+    <receiverChannels>32</receiverChannels>
+    <coilLabel>
+      <coilNumber>0</coilNumber>
+      <coilName>COIL_0</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>1</coilNumber>
+      <coilName>COIL_1</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>2</coilNumber>
+      <coilName>COIL_2</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>3</coilNumber>
+      <coilName>COIL_3</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>4</coilNumber>
+      <coilName>COIL_4</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>5</coilNumber>
+      <coilName>COIL_5</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>6</coilNumber>
+      <coilName>COIL_6</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>7</coilNumber>
+      <coilName>COIL_7</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>8</coilNumber>
+      <coilName>COIL_8</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>9</coilNumber>
+      <coilName>COIL_9</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>10</coilNumber>
+      <coilName>COIL_10</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>11</coilNumber>
+      <coilName>COIL_11</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>12</coilNumber>
+      <coilName>COIL_12</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>13</coilNumber>
+      <coilName>COIL_13</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>14</coilNumber>
+      <coilName>COIL_14</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>15</coilNumber>
+      <coilName>COIL_15</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>16</coilNumber>
+      <coilName>COIL_16</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>17</coilNumber>
+      <coilName>COIL_17</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>18</coilNumber>
+      <coilName>COIL_18</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>19</coilNumber>
+      <coilName>COIL_19</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>20</coilNumber>
+      <coilName>COIL_20</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>21</coilNumber>
+      <coilName>COIL_21</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>22</coilNumber>
+      <coilName>COIL_22</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>23</coilNumber>
+      <coilName>COIL_23</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>24</coilNumber>
+      <coilName>COIL_24</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>25</coilNumber>
+      <coilName>COIL_25</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>26</coilNumber>
+      <coilName>COIL_26</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>27</coilNumber>
+      <coilName>COIL_27</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>28</coilNumber>
+      <coilName>COIL_28</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>29</coilNumber>
+      <coilName>COIL_29</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>30</coilNumber>
+      <coilName>COIL_30</coilName>
+    </coilLabel>
+    <coilLabel>
+      <coilNumber>31</coilNumber>
+      <coilName>COIL_31</coilName>
+    </coilLabel>
+    <institutionName>Some University</institutionName>
+    <stationName>MRI Scanner 1</stationName>
+  </acquisitionSystemInformation>
+  <experimentalConditions>
+    <H1resonanceFrequency_Hz>63642459</H1resonanceFrequency_Hz>
+  </experimentalConditions>
+  <encoding>
+    <encodedSpace>
+      <matrixSize>
+        <x>256</x>
+        <y>140</y>
+        <z>80</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>600</x>
+        <y>328.153125</y>
+        <z>160</z>
+      </fieldOfView_mm>
+    </encodedSpace>
+    <reconSpace>
+      <matrixSize>
+        <x>128</x>
+        <y>116</y>
+        <z>64</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>300</x>
+        <y>271.875</y>
+        <z>128</z>
+      </fieldOfView_mm>
+    </reconSpace>
+    <encodingLimits>
+      <kspace_encoding_step_1>
+        <minimum>0</minimum>
+        <maximum>83</maximum>
+        <center>28</center>
+      </kspace_encoding_step_1>
+      <kspace_encoding_step_2>
+        <minimum>0</minimum>
+        <maximum>45</maximum>
+        <center>20</center>
+      </kspace_encoding_step_2>
+      <slice>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </slice>
+      <set>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </set>
+    </encodingLimits>
+    <trajectory>cartesian</trajectory>
+  </encoding>
+  <encoding>
+    <encodedSpace>
+      <matrixSize>
+        <x>64</x>
+        <y>64</y>
+        <z>64</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>300</x>
+        <y>300</y>
+        <z>300</z>
+      </fieldOfView_mm>
+    </encodedSpace>
+    <reconSpace>
+      <matrixSize>
+        <x>64</x>
+        <y>64</y>
+        <z>64</z>
+      </matrixSize>
+      <fieldOfView_mm>
+        <x>300</x>
+        <y>300</y>
+        <z>300</z>
+      </fieldOfView_mm>
+    </reconSpace>
+    <encodingLimits>
+      <kspace_encoding_step_1>
+        <minimum>0</minimum>
+        <maximum>8</maximum>
+        <center>0</center>
+      </kspace_encoding_step_1>
+      <kspace_encoding_step_2>
+        <minimum>0</minimum>
+        <maximum>16</maximum>
+        <center>0</center>
+      </kspace_encoding_step_2>
+      <average>
+        <minimum>0</minimum>
+        <maximum>16</maximum>
+        <center>0</center>     
+      </average>
+      <slice>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </slice>
+      <contrast>
+        <minimum>2</minimum>
+        <maximum>4</maximum>
+        <center>3</center>     
+      </contrast>
+      <phase>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>     
+      </phase>
+      <repetition>
+       <minimum>0</minimum>
+        <maximum>16</maximum>
+        <center>8</center>     
+      </repetition>
+      <set>
+        <minimum>0</minimum>
+        <maximum>0</maximum>
+        <center>0</center>
+      </set>
+      <segment>
+        <minimum>0</minimum>
+        <maximum>1</maximum>
+        <center>0</center>     
+      </segment>
+    </encodingLimits>
+    <trajectory>other</trajectory>
+    <trajectoryDescription>
+      <identifier>Custom Trajector</identifier>
+      <userParameterLong>
+       <name>sample_rate_ns</name>
+       <value>100</value>
+      </userParameterLong>
+      <userParameterLong>
+       <name>constant_1</name>
+       <value>300</value>
+      </userParameterLong>
+      <userParameterDouble>
+       <name>coefficient_1</name>
+       <value>2.72386</value>
+      </userParameterDouble>
+      <userParameterDouble>
+       <name>coefficient_2</name>
+       <value>2233.72</value>
+      </userParameterDouble>
+      <userParameterDouble>
+       <name>coefficient_3</name>
+       <value>112.86</value>
+      </userParameterDouble>
+      <comment>This is an example description of a custom trajectory</comment>
+    </trajectoryDescription>
+    <parallelImaging>
+      <accelerationFactor>
+       <kspace_encoding_step_1>1</kspace_encoding_step_1>
+       <kspace_encoding_step_2>2</kspace_encoding_step_2>
+      </accelerationFactor>
+      <calibrationMode>other</calibrationMode>
+    </parallelImaging>
+    <echoTrainLength>12</echoTrainLength>
+  </encoding>
+  <sequenceParameters>
+    <TR>4.6</TR>
+    <TE>2.35</TE>
+    <TE>4.35</TE>
+    <TE>6.55</TE>
+    <TE>9.22</TE>
+    <TI>300.08</TI>
+    <flipAngle_deg>90.0</flipAngle_deg>
+    <flipAngle_deg>120.0</flipAngle_deg>
+  </sequenceParameters>
+  <userParameters>
+    <userParameterLong>
+      <name>dev_long_1</name>
+      <value>789</value>
+    </userParameterLong>
+    <userParameterLong>
+      <name>dev_long_2</name>
+      <value>999</value>
+    </userParameterLong>
+    <userParameterDouble>
+      <name>dev_double1</name>
+      <value>999.999</value>
+    </userParameterDouble>
+    <userParameterDouble>
+      <name>dev_double2</name>
+      <value>9.1111</value>
+    </userParameterDouble>
+    <userParameterString>
+      <name>FILTER_SHAPE</name>
+      <value>GAUSSIAN</value>
+    </userParameterString>
+    <userParameterBase64>
+      <name>VENDOR_PROTOCOL</name>
+      <value>VGhpcyBpcyBhIGpvdXJuZXkgaW50byBzb3VuZA==</value>
+    </userParameterBase64>
+  </userParameters>
+</ismrmrdHeader>
+
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f2fc9c1
--- /dev/null
@@ -0,0 +1,21 @@
+find_package(Boost 1.43 COMPONENTS unit_test_framework)
+
+if (NOT Boost_UNIT_TEST_FRAMEWORK_FOUND)
+    message("Boost Unit Test Framework not found. Not compiling tests")
+    return()
+endif ()
+
+include_directories(${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ${Boost_INCLUDE_DIR})
+
+add_executable(test_ismrmrd
+    test_main.cpp
+    test_acquisitions.cpp
+    test_images.cpp
+    test_ndarray.cpp
+    test_flags.cpp
+    test_channels.cpp
+    test_quaternions.cpp)
+
+target_link_libraries(test_ismrmrd ismrmrd ${Boost_LIBRARIES})
+
+add_custom_target(check COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_ismrmrd DEPENDS test_ismrmrd)
diff --git a/tests/test_acquisitions.cpp b/tests/test_acquisitions.cpp
new file mode 100644 (file)
index 0000000..1fc37f2
--- /dev/null
@@ -0,0 +1,170 @@
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(AcquisitionsTest)
+
+static void check_header(ISMRMRD_AcquisitionHeader* chead);
+
+BOOST_AUTO_TEST_CASE(test_acquisition_header)
+{
+    ISMRMRD_AcquisitionHeader chead;
+
+    // Check that header is of expected size
+    size_t expected_size = 9 * sizeof(uint16_t) +
+            (3 + ISMRMRD_PHYS_STAMPS) * sizeof(uint32_t) +
+            ISMRMRD_USER_INTS * sizeof(int32_t) +
+            (1 + ISMRMRD_CHANNEL_MASKS) * sizeof(uint64_t) +
+            ((2 * ISMRMRD_POSITION_LENGTH) + (3 * ISMRMRD_DIRECTION_LENGTH) +
+                    1 + ISMRMRD_USER_FLOATS) * sizeof(float) +
+            (9 + ISMRMRD_USER_INTS) * sizeof(uint16_t);
+    BOOST_CHECK_EQUAL(sizeof(chead), expected_size);
+
+    // Check that header is initialized properly
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition_header(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition_header(&chead), ISMRMRD_NOERROR);
+    check_header(&chead);
+}
+
+BOOST_AUTO_TEST_CASE(test_acquisition_init_cleanup)
+{
+    ISMRMRD_Acquisition acq;
+
+    // Check initialization of acquisition
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition(&acq), ISMRMRD_NOERROR);
+    BOOST_CHECK(!acq.traj);
+    BOOST_CHECK(!acq.data);
+
+    // Check cleanup of acquisition
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_acquisition(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_acquisition(&acq), ISMRMRD_NOERROR);
+    BOOST_CHECK(!acq.traj);
+    BOOST_CHECK(!acq.data);
+}
+
+BOOST_AUTO_TEST_CASE(test_acquisition_create_free)
+{
+    ISMRMRD_Acquisition* cacqp = NULL;
+
+    // Check creation of new acquisition
+    BOOST_CHECK(cacqp = ismrmrd_create_acquisition());
+    // Check that it's initialized
+    check_header(&cacqp->head);
+    BOOST_CHECK(!cacqp->traj);
+    BOOST_CHECK(!cacqp->data);
+
+    // Check cleanup
+    BOOST_CHECK_EQUAL(ismrmrd_free_acquisition(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_free_acquisition(cacqp), ISMRMRD_NOERROR);
+}
+
+BOOST_AUTO_TEST_CASE(test_acquisition_copy)
+{
+    // Weak check of acquisition copying
+    ISMRMRD_Acquisition csrc, cdst;
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition(&csrc), ISMRMRD_NOERROR);
+    // TODO: it is necessary to call init_acquisition on the destination acquisition
+    // before copying, in case its traj or data are non-NULL!
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition(&cdst), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_acquisition(&cdst, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_acquisition(NULL, &csrc), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_acquisition(NULL, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_acquisition(&cdst, &csrc), ISMRMRD_NOERROR);
+    check_header(&cdst.head);
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_acquisition_data(&cdst), 0);
+    BOOST_CHECK(!cdst.traj);
+    BOOST_CHECK(!cdst.data);
+}
+
+BOOST_AUTO_TEST_CASE(test_acquisition_make_consistent)
+{
+    ISMRMRD_Acquisition acq;
+    BOOST_CHECK_EQUAL(ismrmrd_init_acquisition(&acq), ISMRMRD_NOERROR);
+
+    BOOST_CHECK_EQUAL(ismrmrd_make_consistent_acquisition(NULL), ISMRMRD_RUNTIMEERROR);
+
+    uint16_t nsamples = 512;
+    uint16_t nchannels = 8;
+    uint16_t ntrajd = 2;
+    acq.head.number_of_samples = nsamples;
+    acq.head.active_channels = nchannels;
+    acq.head.trajectory_dimensions = ntrajd;
+
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_acquisition_traj(&acq), nsamples * ntrajd * sizeof(*acq.traj));
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_acquisition_data(&acq), nsamples * nchannels * sizeof(*acq.data));
+    BOOST_CHECK_EQUAL(ismrmrd_make_consistent_acquisition(&acq), ISMRMRD_NOERROR);
+
+    BOOST_CHECK_EQUAL(acq.head.available_channels, nchannels);
+    // check that traj and data were allocated
+    BOOST_CHECK(acq.traj);
+    BOOST_CHECK(acq.data);
+
+    ismrmrd_cleanup_acquisition(&acq);
+}
+
+static void check_header(ISMRMRD_AcquisitionHeader* chead)
+{
+    BOOST_CHECK_EQUAL(chead->version, ISMRMRD_VERSION_MAJOR);
+    BOOST_CHECK_EQUAL(chead->number_of_samples, 0);
+    BOOST_CHECK_EQUAL(chead->available_channels, 1);
+    BOOST_CHECK_EQUAL(chead->active_channels, 1);
+    BOOST_CHECK_EQUAL(chead->flags, 0);
+    BOOST_CHECK_EQUAL(chead->measurement_uid, 0);
+    BOOST_CHECK_EQUAL(chead->scan_counter, 0);
+    BOOST_CHECK_EQUAL(chead->acquisition_time_stamp, 0);
+    for (int idx = 0; idx < ISMRMRD_PHYS_STAMPS; idx++) {
+        BOOST_CHECK_EQUAL(chead->physiology_time_stamp[idx], 0);
+    }
+
+    for (int idx = 0; idx < ISMRMRD_CHANNEL_MASKS; idx++) {
+        BOOST_CHECK_EQUAL(chead->channel_mask[idx], 0);
+    }
+    BOOST_CHECK_EQUAL(chead->discard_pre, 0);
+    BOOST_CHECK_EQUAL(chead->discard_post, 0);
+    BOOST_CHECK_EQUAL(chead->center_sample, 0);
+    BOOST_CHECK_EQUAL(chead->encoding_space_ref, 0);
+    BOOST_CHECK_EQUAL(chead->trajectory_dimensions, 0);
+    BOOST_CHECK_EQUAL(chead->sample_time_us, 0);
+    for (int idx = 0; idx < ISMRMRD_POSITION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->position[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->read_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->phase_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->slice_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_POSITION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->patient_table_position[idx], 0);
+    }
+
+    // EncodingCounters
+    BOOST_CHECK_EQUAL(chead->idx.kspace_encode_step_1, 0);
+    BOOST_CHECK_EQUAL(chead->idx.kspace_encode_step_2, 0);
+    BOOST_CHECK_EQUAL(chead->idx.average, 0);
+    BOOST_CHECK_EQUAL(chead->idx.slice, 0);
+    BOOST_CHECK_EQUAL(chead->idx.contrast, 0);
+    BOOST_CHECK_EQUAL(chead->idx.phase, 0);
+    BOOST_CHECK_EQUAL(chead->idx.repetition, 0);
+    BOOST_CHECK_EQUAL(chead->idx.set, 0);
+    BOOST_CHECK_EQUAL(chead->idx.segment, 0);
+
+    for (int idx = 0; idx < ISMRMRD_USER_INTS; idx++) {
+        BOOST_CHECK_EQUAL(chead->idx.user[idx], 0);
+    }
+
+    for (int idx = 0; idx < ISMRMRD_USER_INTS; idx++) {
+        BOOST_CHECK_EQUAL(chead->user_int[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_USER_FLOATS; idx++) {
+        BOOST_CHECK_EQUAL(chead->user_float[idx], 0);
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/test_channels.cpp b/tests/test_channels.cpp
new file mode 100644 (file)
index 0000000..54a1c8a
--- /dev/null
@@ -0,0 +1,76 @@
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(ChannelTest)
+
+void fill_channels(uint64_t mask[ISMRMRD_CHANNEL_MASKS])
+{
+    for (int i = 0; i < ISMRMRD_CHANNEL_MASKS; i++) {
+        mask[i] = 0xFFFFFFFFFFFFFFFF;
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_is_channel_on)
+{
+    uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS] = {0};
+
+    // TODO: this returns and ISMRMRD_RUNTIMEERROR, which casts to "true"
+    /* BOOST_CHECK_EQUAL(ismrmrd_is_channel_on(NULL, 0), false); */
+
+    for (int chan = 0; chan < 64 * ISMRMRD_CHANNEL_MASKS; chan++) {
+        BOOST_CHECK_EQUAL(ismrmrd_is_channel_on(channel_mask, chan), false);
+    }
+
+    fill_channels(channel_mask);
+    for (int chan = 0; chan < 64 * ISMRMRD_CHANNEL_MASKS; chan++) {
+        BOOST_CHECK_EQUAL(ismrmrd_is_channel_on(channel_mask, chan), true);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_set_channel_on)
+{
+    uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS] = {0};
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_channel_on(NULL, 0), ISMRMRD_RUNTIMEERROR);
+
+    for (int chan = 0; chan < 64 * ISMRMRD_CHANNEL_MASKS; chan++) {
+        BOOST_CHECK_EQUAL(ismrmrd_set_channel_on(channel_mask, chan), ISMRMRD_NOERROR);
+        uint64_t bitmask = 1 << (chan % 64);
+        size_t offset = chan / 64;
+        BOOST_REQUIRE((channel_mask[offset] & bitmask) != 0);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_set_channel_off)
+{
+    uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS] = {0};
+    fill_channels(channel_mask);
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_channel_off(NULL, 0), ISMRMRD_RUNTIMEERROR);
+
+    for (int chan = 0; chan < 64 * ISMRMRD_CHANNEL_MASKS; chan++) {
+        BOOST_CHECK_EQUAL(ismrmrd_set_channel_off(channel_mask, chan), ISMRMRD_NOERROR);
+
+        uint64_t bitmask = 1 << (chan % 64);
+        size_t offset = chan / 64;
+        BOOST_REQUIRE((channel_mask[offset] & bitmask) == 0);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_set_all_channels_off)
+{
+    uint64_t channel_mask[ISMRMRD_CHANNEL_MASKS] = {0};
+    fill_channels(channel_mask);
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_all_channels_off(NULL), ISMRMRD_RUNTIMEERROR);
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_all_channels_off(channel_mask), ISMRMRD_NOERROR);
+    for (int idx = 0; idx < ISMRMRD_CHANNEL_MASKS; idx++) {
+        BOOST_REQUIRE(channel_mask[idx] == 0);
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/test_flags.cpp b/tests/test_flags.cpp
new file mode 100644 (file)
index 0000000..6c7dc32
--- /dev/null
@@ -0,0 +1,65 @@
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(FlagTest)
+
+BOOST_AUTO_TEST_CASE(test_is_flag_set)
+{
+    uint64_t flags = 0;
+
+    for (int f = 1; f <= 64; f++) {
+        BOOST_CHECK_EQUAL(ismrmrd_is_flag_set(flags, f), false);
+    }
+
+    for (int f = 1; f <= 64; f++) {
+        flags |= ((uint64_t)1 << (f - 1));
+        BOOST_CHECK_EQUAL(ismrmrd_is_flag_set(flags, f), true);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_set_flag)
+{
+    uint64_t flags = 0;
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_flag(NULL, ISMRMRD_ACQ_USER8), ISMRMRD_RUNTIMEERROR);
+
+    for (int f = 1; f <= 64; f++) {
+        BOOST_CHECK_EQUAL(ismrmrd_set_flag(&flags, f), ISMRMRD_NOERROR);
+        BOOST_REQUIRE((flags & ((uint64_t)1 << (f - 1))) != 0);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_set_flags)
+{
+    uint64_t flags = 0;
+
+    BOOST_CHECK_EQUAL(ismrmrd_set_flags(NULL, 0xFFFFFFFFFFFFFFFF), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_set_flags(&flags, 0xFFFFFFFFFFFFFFFF), ISMRMRD_NOERROR);
+
+    BOOST_CHECK_EQUAL(flags, 0xFFFFFFFFFFFFFFFF);
+}
+
+BOOST_AUTO_TEST_CASE(test_clear_flag)
+{
+    uint64_t flags = 0xFFFFFFFFFFFFFFFF;
+
+    BOOST_CHECK_EQUAL(ismrmrd_clear_flag(NULL, ISMRMRD_IMAGE_USER8), ISMRMRD_RUNTIMEERROR);
+    for (int f = 1; f <= 64; f++) {
+        BOOST_CHECK_EQUAL(ismrmrd_clear_flag(&flags, f), ISMRMRD_NOERROR);
+        BOOST_REQUIRE((flags & ((uint64_t)1 << (f - 1))) == 0);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(test_clear_all_flags)
+{
+    uint64_t flags = 0xFFFFFFFFFFFFFFFF;
+
+    BOOST_CHECK_EQUAL(ismrmrd_clear_all_flags(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_clear_all_flags(&flags), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(flags, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/test_images.cpp b/tests/test_images.cpp
new file mode 100644 (file)
index 0000000..7c6a908
--- /dev/null
@@ -0,0 +1,167 @@
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(ImagesTest)
+
+static void check_header(ISMRMRD_ImageHeader* chead);
+
+BOOST_AUTO_TEST_CASE(test_image_header)
+{
+    ISMRMRD_ImageHeader chead;
+
+    // Check that header is of expected size
+    size_t expected_size = 15 * sizeof(uint16_t) +
+            (3 + ISMRMRD_PHYS_STAMPS) * sizeof(uint32_t) +
+            ISMRMRD_USER_INTS * sizeof(int32_t) +
+            1 * sizeof(uint64_t) +
+            ((2 * ISMRMRD_POSITION_LENGTH) + (3 * ISMRMRD_DIRECTION_LENGTH) +
+                    3 + ISMRMRD_USER_FLOATS) * sizeof(float);
+    BOOST_CHECK_EQUAL(sizeof(chead), expected_size);
+
+    // Check that header is initialized properly
+    BOOST_CHECK_EQUAL(ismrmrd_init_image_header(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_init_image_header(&chead), ISMRMRD_NOERROR);
+    check_header(&chead);
+}
+
+BOOST_AUTO_TEST_CASE(test_image_init_cleanup)
+{
+    ISMRMRD_Image cimg;
+
+    // Check initialization of image
+    BOOST_CHECK_EQUAL(ismrmrd_init_image(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_init_image(&cimg), ISMRMRD_NOERROR);
+    check_header(&cimg.head);
+    BOOST_CHECK(!cimg.attribute_string);
+    BOOST_CHECK(!cimg.data);
+
+    // Check cleanup of image
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_image(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_image(&cimg), ISMRMRD_NOERROR);
+    BOOST_CHECK(!cimg.attribute_string);
+    BOOST_CHECK(!cimg.data);
+}
+
+BOOST_AUTO_TEST_CASE(test_image_create_free)
+{
+    ISMRMRD_Image* cimgp = NULL;
+
+    // Check creation of new image
+    BOOST_CHECK(cimgp = ismrmrd_create_image());
+    // Check that it's initialized
+    check_header(&cimgp->head);
+    BOOST_CHECK(!cimgp->attribute_string);
+    BOOST_CHECK(!cimgp->data);
+
+    // Check cleanup
+    BOOST_CHECK_EQUAL(ismrmrd_free_image(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_free_image(cimgp), ISMRMRD_NOERROR);
+}
+
+BOOST_AUTO_TEST_CASE(test_image_copy)
+{
+    // Weak check of image copying
+    ISMRMRD_Image csrc, cdst;
+    BOOST_CHECK_EQUAL(ismrmrd_init_image(&csrc), ISMRMRD_NOERROR);
+    // TODO: it is necessary to call init_image on the destination image
+    // before copying, in case its attribute_string or data are non-NULL!
+    BOOST_CHECK_EQUAL(ismrmrd_init_image(&cdst), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_image(&cdst, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_image(NULL, &csrc), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_image(NULL, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_image(&cdst, &csrc), ISMRMRD_NOERROR);
+    check_header(&cdst.head);
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_image_attribute_string(&cdst), 0);
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_image_data(&cdst), 0);
+    BOOST_CHECK(!cdst.attribute_string);
+    BOOST_CHECK(!cdst.data);
+}
+
+BOOST_AUTO_TEST_CASE(test_image_make_consistent)
+{
+    ISMRMRD_Image img;
+    BOOST_CHECK_EQUAL(ismrmrd_init_image(&img), ISMRMRD_NOERROR);
+
+    BOOST_CHECK_EQUAL(ismrmrd_make_consistent_image(NULL), ISMRMRD_RUNTIMEERROR);
+
+    uint16_t matrix_size[] = {128, 128, 1};
+    uint16_t nchannels = 8;
+    uint16_t dtype = ISMRMRD_FLOAT;
+    uint16_t attrlen = 65;
+    img.head.matrix_size[0] = matrix_size[0];
+    img.head.matrix_size[1] = matrix_size[1];
+    img.head.matrix_size[2] = matrix_size[2];
+    img.head.channels = nchannels;
+    img.head.data_type = dtype;
+    img.head.attribute_string_len = attrlen;
+
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_image_data(&img),
+            matrix_size[0] * matrix_size[1] * matrix_size[2] *
+            nchannels * ismrmrd_sizeof_data_type(dtype));
+    BOOST_CHECK_EQUAL(ismrmrd_size_of_image_attribute_string(&img),
+            attrlen * sizeof(*img.attribute_string));
+    BOOST_CHECK_EQUAL(ismrmrd_make_consistent_image(&img), ISMRMRD_NOERROR);
+
+    // check that data and attribute_string were allocated
+    BOOST_CHECK(img.data);
+    BOOST_CHECK(img.attribute_string);
+
+    ismrmrd_cleanup_image(&img);
+}
+
+static void check_header(ISMRMRD_ImageHeader* chead)
+{
+    BOOST_CHECK_EQUAL(chead->version, ISMRMRD_VERSION_MAJOR);
+    BOOST_CHECK_EQUAL(chead->matrix_size[0], 0);
+    BOOST_CHECK_EQUAL(chead->matrix_size[1], 1);
+    BOOST_CHECK_EQUAL(chead->matrix_size[2], 1);
+    BOOST_CHECK_EQUAL(chead->channels, 1);
+
+    BOOST_CHECK_EQUAL(chead->data_type, 0);
+    BOOST_CHECK_EQUAL(chead->flags, 0);
+    BOOST_CHECK_EQUAL(chead->measurement_uid, 0);
+    for (int idx = 0; idx < 3; idx++) {
+        BOOST_CHECK_EQUAL(chead->field_of_view[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_POSITION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->position[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->read_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->phase_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_DIRECTION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->slice_dir[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_POSITION_LENGTH; idx++) {
+        BOOST_CHECK_EQUAL(chead->patient_table_position[idx], 0);
+    }
+    BOOST_CHECK_EQUAL(chead->average, 0);
+    BOOST_CHECK_EQUAL(chead->slice, 0);
+    BOOST_CHECK_EQUAL(chead->contrast, 0);
+    BOOST_CHECK_EQUAL(chead->phase, 0);
+    BOOST_CHECK_EQUAL(chead->repetition, 0);
+    BOOST_CHECK_EQUAL(chead->set, 0);
+    BOOST_CHECK_EQUAL(chead->acquisition_time_stamp, 0);
+    for (int idx = 0; idx < ISMRMRD_PHYS_STAMPS; idx++) {
+        BOOST_CHECK_EQUAL(chead->physiology_time_stamp[idx], 0);
+    }
+    BOOST_CHECK_EQUAL(chead->image_type, 0);
+    BOOST_CHECK_EQUAL(chead->image_index, 0);
+    BOOST_CHECK_EQUAL(chead->image_series_index, 0);
+
+    for (int idx = 0; idx < ISMRMRD_USER_INTS; idx++) {
+        BOOST_CHECK_EQUAL(chead->user_int[idx], 0);
+    }
+    for (int idx = 0; idx < ISMRMRD_USER_FLOATS; idx++) {
+        BOOST_CHECK_EQUAL(chead->user_float[idx], 0);
+    }
+    BOOST_CHECK_EQUAL(chead->attribute_string_len, 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/test_ismrmrd.h b/tests/test_ismrmrd.h
new file mode 100644 (file)
index 0000000..ece311f
--- /dev/null
@@ -0,0 +1,4 @@
+#pragma once
+
+void silent_error_handler(const char *file, int line,
+        const char *function, int code, const char *msg);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
new file mode 100644 (file)
index 0000000..4b1809d
--- /dev/null
@@ -0,0 +1,26 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE "ISMRMRD Unit Tests"
+#include <boost/test/unit_test.hpp>
+
+#include "ismrmrd/ismrmrd.h"
+using namespace ISMRMRD;
+
+void silent_error_handler(const char *file, int line,
+        const char *function, int code, const char *msg)
+{
+}
+
+struct GlobalConfig {
+    // global setup
+    GlobalConfig()
+    {
+        // enable more verbose testing output
+        boost::unit_test::unit_test_log.set_threshold_level(boost::unit_test::log_test_units);
+        // silence ISMRMRD errors on stdout
+        ismrmrd_set_error_handler(silent_error_handler);
+    }
+    // global teardown
+    ~GlobalConfig() { }
+};
+
+BOOST_GLOBAL_FIXTURE(GlobalConfig);
diff --git a/tests/test_ndarray.cpp b/tests/test_ndarray.cpp
new file mode 100644 (file)
index 0000000..abea4ab
--- /dev/null
@@ -0,0 +1,71 @@
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/version.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(NDArrayTest)
+
+BOOST_AUTO_TEST_CASE(test_ndarray_init_cleanup)
+{
+    ISMRMRD_NDArray carr;
+
+    // Check initialization of ndarray
+    BOOST_CHECK_EQUAL(ismrmrd_init_ndarray(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_init_ndarray(&carr), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(carr.version, ISMRMRD_VERSION_MAJOR);
+    BOOST_CHECK_EQUAL(carr.data_type, 0);   // TODO: enumerate ISMRMRD_NO_DATATYPE
+    BOOST_CHECK_EQUAL(carr.ndim, 0);
+    for (int idx = 0; idx < ISMRMRD_NDARRAY_MAXDIM; idx++) {
+        BOOST_CHECK_EQUAL(carr.dims[idx], 0);
+    }
+    BOOST_CHECK(!carr.data);
+
+    // Check cleanup of ndarray
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_ndarray(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_cleanup_ndarray(&carr), ISMRMRD_NOERROR);
+    BOOST_CHECK(!carr.data);
+}
+
+BOOST_AUTO_TEST_CASE(test_ndarray_create_free)
+{
+    ISMRMRD_NDArray* carrp = NULL;
+
+    // Check creation of new ndarray
+    BOOST_CHECK(carrp = ismrmrd_create_ndarray());
+    BOOST_CHECK_EQUAL(carrp->version, ISMRMRD_VERSION_MAJOR);
+    BOOST_CHECK_EQUAL(carrp->data_type, 0);   // TODO: enumerate ISMRMRD_NO_DATATYPE
+    BOOST_CHECK_EQUAL(carrp->ndim, 0);
+    for (int idx = 0; idx < ISMRMRD_NDARRAY_MAXDIM; idx++) {
+        BOOST_CHECK_EQUAL(carrp->dims[idx], 0);
+    }
+    BOOST_CHECK(!carrp->data);
+
+    // Check cleanup
+    BOOST_CHECK_EQUAL(ismrmrd_free_ndarray(NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_free_ndarray(carrp), ISMRMRD_NOERROR);
+    BOOST_CHECK(!carrp->data);
+}
+
+BOOST_AUTO_TEST_CASE(test_ndarray_copy)
+{
+    // Weak check of ndarray copying
+    ISMRMRD_NDArray csrc, cdst;
+    BOOST_CHECK_EQUAL(ismrmrd_init_ndarray(&csrc), ISMRMRD_NOERROR);
+    // NOTE: it is necessary to call init_ndarray on the destination ndarray
+    // before copying, in case its data is non-NULL!
+    BOOST_CHECK_EQUAL(ismrmrd_init_ndarray(&cdst), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_ndarray(&cdst, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_ndarray(NULL, &csrc), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_ndarray(NULL, NULL), ISMRMRD_RUNTIMEERROR);
+    BOOST_CHECK_EQUAL(ismrmrd_copy_ndarray(&cdst, &csrc), ISMRMRD_NOERROR);
+    BOOST_CHECK_EQUAL(cdst.version, ISMRMRD_VERSION_MAJOR);
+    BOOST_CHECK_EQUAL(cdst.data_type, 0);   // TODO: enumerate ISMRMRD_NO_DATATYPE
+    BOOST_CHECK_EQUAL(cdst.ndim, 0);
+    for (int idx = 0; idx < ISMRMRD_NDARRAY_MAXDIM; idx++) {
+        BOOST_CHECK_EQUAL(cdst.dims[idx], 0);
+    }
+    BOOST_CHECK(!cdst.data);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/test_quaternions.cpp b/tests/test_quaternions.cpp
new file mode 100644 (file)
index 0000000..f73ef36
--- /dev/null
@@ -0,0 +1,60 @@
+#include "ismrmrd/ismrmrd.h"
+#include <boost/test/unit_test.hpp>
+
+using namespace ISMRMRD;
+
+BOOST_AUTO_TEST_SUITE(QuaternionTest)
+
+BOOST_AUTO_TEST_CASE(test_directions_to_quaternion)
+{
+    float read_dir[3] = {1.0, 0, 0};
+    float phase_dir[3] = {0, 1.0, 0};
+    float slice_dir[3] = {0, 0, 1.0};
+    float quaternion[4];
+
+    /* convert the direction vectors to a quaternion and verify */
+    ismrmrd_directions_to_quaternion(read_dir, phase_dir, slice_dir, quaternion);
+
+    BOOST_CHECK_EQUAL(quaternion[0], 0.0);
+    BOOST_CHECK_EQUAL(quaternion[1], 0.0);
+    BOOST_CHECK_EQUAL(quaternion[2], 0.0);
+    BOOST_CHECK_EQUAL(quaternion[3], 1.0);
+}
+
+BOOST_AUTO_TEST_CASE(test_quaternion_to_directions)
+{
+    float read_dir[3];
+    float phase_dir[3];
+    float slice_dir[3];
+    float quaternion[4] = {0.0, 0.0, 0.0, 1.0};
+
+    /* convert the quaternion back to direction cosines and verify */
+    ismrmrd_quaternion_to_directions(quaternion, read_dir, phase_dir, slice_dir);
+    BOOST_CHECK_EQUAL(read_dir[0], 1.0);
+    BOOST_CHECK_EQUAL(read_dir[1], 0.0);
+    BOOST_CHECK_EQUAL(read_dir[2], 0.0);
+    BOOST_CHECK_EQUAL(phase_dir[0], 0.0);
+    BOOST_CHECK_EQUAL(phase_dir[1], 1.0);
+    BOOST_CHECK_EQUAL(phase_dir[2], 0.0);
+    BOOST_CHECK_EQUAL(slice_dir[0], 0.0);
+    BOOST_CHECK_EQUAL(slice_dir[1], 0.0);
+    BOOST_CHECK_EQUAL(slice_dir[2], 1.0);
+}
+
+BOOST_AUTO_TEST_CASE(test_sign_of_directions)
+{
+    float read_dir[3] = {1.0, 0, 0};
+    float phase_dir[3] = {0, 1.0, 0};
+    float slice_dir[3] = {0, 0, 1.0};
+
+    /* check that determinant is > 0 */
+    BOOST_REQUIRE(ismrmrd_sign_of_directions(read_dir, phase_dir, slice_dir) > 0);
+
+    /* flip sign of third column and check that determinant is < 0 */
+    slice_dir[0] = -slice_dir[0];
+    slice_dir[1] = -slice_dir[1];
+    slice_dir[2] = -slice_dir[2];
+    BOOST_REQUIRE(ismrmrd_sign_of_directions(read_dir, phase_dir, slice_dir) < 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/utilities/CMakeLists.txt b/utilities/CMakeLists.txt
new file mode 100644 (file)
index 0000000..011837c
--- /dev/null
@@ -0,0 +1,75 @@
+# Build the info application
+message("Building info application")
+include_directories(${CMAKE_BINARY_DIR}/include
+                    ${CMAKE_SOURCE_DIR}/include
+                   ${CMAKE_SOURCE_DIR}/libsrc)
+
+add_executable(ismrmrd_info ismrmrd_info.cpp)
+target_link_libraries(ismrmrd_info ismrmrd)
+install(TARGETS ismrmrd_info DESTINATION bin)
+
+if (NOT WIN32)
+  add_executable(ismrmrd_test_xml
+    ismrmrd_test_xml.cpp
+    ${CMAKE_SOURCE_DIR}/libsrc/pugixml.cpp )
+  target_link_libraries(ismrmrd_test_xml ismrmrd)
+  install(TARGETS ismrmrd_test_xml DESTINATION bin)
+endif()
+
+if (HDF5_FOUND)
+    add_executable(ismrmrd_read_timing_test read_timing_test.cpp)
+    target_link_libraries(ismrmrd_read_timing_test ismrmrd)
+    install(TARGETS ismrmrd_read_timing_test DESTINATION bin)
+
+    find_package(Boost 1.43 COMPONENTS program_options)
+    find_package(FFTW3 COMPONENTS single)
+
+    if(FFTW3_FOUND AND Boost_FOUND)
+        message("FFTW3 and Boost Found... building utilities")
+
+        if(WIN32)
+            link_directories(${Boost_LIBRARY_DIRS})
+        endif()
+
+        include_directories(
+            ${CMAKE_SOURCE_DIR/include}
+            ${Boost_INCLUDE_DIR}
+            ${FFTW3_INCLUDE_DIR})
+
+        # Shepp-Logan phantom
+        add_executable(ismrmrd_generate_cartesian_shepp_logan
+            generate_cartesian_shepp_logan.cpp
+            ismrmrd_phantom.cpp)
+        if(WIN32)
+            target_link_libraries( ismrmrd_generate_cartesian_shepp_logan
+                ismrmrd
+                ${FFTW3_LIBRARIES})
+        else()
+            target_link_libraries( ismrmrd_generate_cartesian_shepp_logan
+                ismrmrd
+                ${Boost_PROGRAM_OPTIONS_LIBRARY}
+                ${FFTW3_LIBRARIES})
+        endif()
+        install(TARGETS ismrmrd_generate_cartesian_shepp_logan DESTINATION bin)
+
+        # Shepp-Logan phantom
+        add_executable(ismrmrd_recon_cartesian_2d
+            recon_cartesian_2d.cpp)
+        if(WIN32)
+            target_link_libraries( ismrmrd_recon_cartesian_2d
+                ismrmrd
+                ${FFTW3_LIBRARIES})
+        else()
+            target_link_libraries( ismrmrd_recon_cartesian_2d
+                ismrmrd
+                ${Boost_PROGRAM_OPTIONS_LIBRARY}
+                ${FFTW3_LIBRARIES})
+        endif()
+        install(TARGETS ismrmrd_recon_cartesian_2d DESTINATION bin)
+
+    else()
+        message("FFTW3 or Boost NOT Found, cannot build utilities")
+    endif()
+else ()
+    message("HDF5 NOT Found, cannot build utilities")
+endif ()
diff --git a/utilities/generate_cartesian_shepp_logan.cpp b/utilities/generate_cartesian_shepp_logan.cpp
new file mode 100644 (file)
index 0000000..c5028f1
--- /dev/null
@@ -0,0 +1,215 @@
+
+/*
+ * generate_cartesian_shepp_logan.cpp
+ *
+ *  Created on: Apr 1, 2013
+ *      Author: Michael S. Hansen
+ *
+ */
+
+#include <iostream>
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/xml.h"
+#include "ismrmrd/dataset.h"
+#include "ismrmrd/version.h"
+#include "ismrmrd_phantom.h"
+#include "ismrmrd_fftw.h"
+
+#include <boost/program_options.hpp>
+
+using namespace ISMRMRD;
+namespace po = boost::program_options;
+
+// MAIN APPLICATION
+int main(int argc, char** argv)
+{
+       /** TODO
+        *
+        *  Noise samples
+        *  Acceleration
+        *  k-space coordinates
+        *
+        */
+
+       unsigned int matrix_size; //Matrix size
+       unsigned int ncoils;      //Number of coils
+       unsigned int ros;           //Readout ovesampling
+       unsigned int repetitions;
+       unsigned int acc_factor;
+       float noise_level;
+       std::string outfile;
+       std::string dataset;
+       bool store_coordinates = false;
+       bool noise_calibration = false;
+
+       po::options_description desc("Allowed options");
+       desc.add_options()
+           ("help,h", "produce help message")
+           ("matrix,m", po::value<unsigned int>(&matrix_size)->default_value(256), "Matrix Size")
+           ("coils,c", po::value<unsigned int>(&ncoils)->default_value(8), "Number of Coils")
+           ("oversampling,O", po::value<unsigned int>(&ros)->default_value(2), "Readout oversampling")
+           ("repetitions,r", po::value<unsigned int>(&repetitions)->default_value(1), "Repetitions")
+           ("acceleration,a", po::value<unsigned int>(&acc_factor)->default_value(1), "Acceleration factor")
+           ("noise-level,n", po::value<float>(&noise_level)->default_value(0.05f,"0.05"), "Noise Level")
+           ("output,o", po::value<std::string>(&outfile)->default_value("testdata.h5"), "Output File Name")
+           ("dataset,d", po::value<std::string>(&dataset)->default_value("dataset"), "Output Dataset Name")
+           ("noise-calibration,C", po::value<bool>(&noise_calibration)->zero_tokens(), "Add noise calibration")
+           ("k-coordinates,k",  po::value<bool>(&store_coordinates)->zero_tokens(), "Store k-space coordinates")
+       ;
+
+       po::variables_map vm;
+       po::store(po::parse_command_line(argc, argv, desc), vm);
+       po::notify(vm);
+
+       if (vm.count("help")) {
+           std::cout << desc << "\n";
+           return 1;
+       }
+
+       std::cout << "Generating Cartesian Shepp Logan Phantom!!!" << std::endl;
+       std::cout << "Acceleration: " << acc_factor << std::endl;
+
+       boost::shared_ptr<NDArray<complex_float_t> > phantom = shepp_logan_phantom(matrix_size);
+       boost::shared_ptr<NDArray<complex_float_t> > coils = generate_birdcage_sensititivies(matrix_size, ncoils, 1.5);
+
+       std::vector<size_t> dims;
+       dims.push_back(matrix_size*ros); //oversampling in the readout direction
+       dims.push_back(matrix_size);
+       dims.push_back(ncoils);
+
+       NDArray<complex_float_t> coil_images(dims);
+       memset(coil_images.getDataPtr(), 0, coil_images.getDataSize());
+
+       for (unsigned int c = 0; c < ncoils; c++) {
+            for (unsigned int y = 0; y < matrix_size; y++) {
+                for (unsigned int x = 0; x < matrix_size; x++) {
+                    uint16_t xout = x + (matrix_size*ros-matrix_size)/2;
+                    coil_images(xout,y,c) = (*phantom)(x,y) * (*coils)(x,y,c);
+                }
+            }
+       }
+
+        //Let's append the data to the file
+        //Create if needed
+       Dataset d(outfile.c_str(),dataset.c_str(), true);
+       Acquisition acq;
+        size_t readout = matrix_size*ros;
+        
+       if (noise_calibration)
+        {
+            acq.resize(readout, ncoils);
+            memset((void *)acq.getDataPtr(), 0, acq.getDataSize());
+            acq.setFlag(ISMRMRD_ACQ_IS_NOISE_MEASUREMENT);
+            add_noise(acq,noise_level);
+            acq.sample_time_us() = 5.0;
+            d.appendAcquisition(acq);
+       }
+        
+        if (store_coordinates) {
+            acq.resize(readout, ncoils, 2);
+        }
+        else {
+            acq.resize(readout, ncoils);
+        }
+        memset((void*)acq.getDataPtr(), 0, acq.getDataSize());
+        
+        acq.available_channels() = ncoils;
+       acq.center_sample() = (readout>>1);
+
+
+        for (unsigned int r = 0; r < repetitions; r++) {
+            for (unsigned int a = 0; a < acc_factor; a++) {
+                NDArray<complex_float_t> cm = coil_images;
+                fft2c(cm);
+
+                add_noise(cm,noise_level);
+                for (size_t i = a; i < matrix_size; i+=acc_factor) {
+                    acq.clearAllFlags();
+                    
+                    //Set some flags
+                    if (i == a) {
+                        acq.setFlag(ISMRMRD_ACQ_FIRST_IN_SLICE);
+                    }
+                    if (i >= (matrix_size-acc_factor)) {
+                        acq.setFlag(ISMRMRD_ACQ_LAST_IN_SLICE);
+                    }
+                    acq.idx().kspace_encode_step_1 = i;
+                    acq.idx().repetition = r*acc_factor + a;
+                    acq.sample_time_us() = 5.0;
+                    for (size_t c = 0; c < ncoils; c++) {
+                        for (size_t s = 0; s < readout; s++) {
+                            acq.data(s,c) = cm(s,i,c);
+                        }
+                    }
+                    
+                    if (store_coordinates) {
+                        float ky = (1.0*i-(matrix_size>>1))/(1.0*matrix_size);
+                        for (size_t x = 0; x < readout; x++) {
+                            float kx = (1.0*x-(readout>>1))/(1.0*readout);
+                            acq.traj(0,x) = kx;
+                            acq.traj(1,x) = ky;
+                        }
+                    }
+                    d.appendAcquisition(acq);
+                }
+            }
+       }
+
+       //Let's create a header, we will use the C++ classes in ismrmrd/xml.h
+       IsmrmrdHeader h;
+        h.version = ISMRMRD_XMLHDR_VERSION;
+       h.experimentalConditions.H1resonanceFrequency_Hz = 63500000; //~1.5T        
+
+       AcquisitionSystemInformation sys;
+       sys.institutionName = "ISMRM Synthetic Imaging Lab";
+       sys.receiverChannels = ncoils;
+       h.acquisitionSystemInformation = sys;
+
+       //Create an encoding section
+        Encoding e;
+        e.encodedSpace.matrixSize.x = readout;
+        e.encodedSpace.matrixSize.y = matrix_size;
+        e.encodedSpace.matrixSize.z = 1;
+        e.encodedSpace.fieldOfView_mm.x = 600;
+        e.encodedSpace.fieldOfView_mm.y = 300;
+        e.encodedSpace.fieldOfView_mm.z = 6;
+        e.reconSpace.matrixSize.x = readout/2;
+        e.reconSpace.matrixSize.y = matrix_size;
+        e.reconSpace.matrixSize.z = 1;
+        e.reconSpace.fieldOfView_mm.x = 300;
+        e.reconSpace.fieldOfView_mm.y = 300;
+        e.reconSpace.fieldOfView_mm.z = 6;
+        e.trajectory = "cartesian";
+        e.encodingLimits.kspace_encoding_step_1 = Limit(0, matrix_size-1,(matrix_size>>1));
+        e.encodingLimits.repetition = Limit(0, repetitions*acc_factor - 1,0);
+        
+       //e.g. parallel imaging
+       if (acc_factor > 1) {
+            ParallelImaging parallel;
+            parallel.accelerationFactor.kspace_encoding_step_1 = acc_factor;
+            parallel.accelerationFactor.kspace_encoding_step_2 = 1;
+            parallel.calibrationMode = "interleaved";
+            e.parallelImaging = parallel;
+       }
+
+       //Add the encoding section to the header
+       h.encoding.push_back(e);
+
+       //Add any additional fields that you may want would go here....
+
+       //Serialize the header
+        std::stringstream str;
+        ISMRMRD::serialize( h, str);
+        std::string xml_header = str.str();
+        //std::cout << xml_header << std::endl;
+        
+       //Write the header to the data file.
+       d.writeHeader(xml_header);
+
+        //Write out some arrays for convenience
+        d.appendNDArray("phantom", *phantom);
+        d.appendNDArray("csm", *coils);
+        d.appendNDArray("coil_images", coil_images);
+        
+       return 0;
+}
diff --git a/utilities/ismrmrd_fftw.h b/utilities/ismrmrd_fftw.h
new file mode 100644 (file)
index 0000000..bf783c0
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * ismrmrd_fftw.h
+ *
+ *  Created on: Apr 1, 2013
+ *      Author: Michael S. Hansen
+ */
+
+#include "fftw3.h"
+
+namespace ISMRMRD {
+
+template<typename TI, typename TO> void circshift(TO *out, const TI *in, int xdim, int ydim, int xshift, int yshift)
+{
+  for (int i =0; i < ydim; i++) {
+    int ii = (i + yshift) % ydim;
+    for (int j = 0; j < xdim; j++) {
+      int jj = (j + xshift) % xdim;
+      out[ii * xdim + jj] = in[i * xdim + j];
+    }
+  }
+}
+
+#define fftshift(out, in, x, y) circshift(out, in, x, y, (x/2), (y/2))
+
+
+int fft2c(NDArray<complex_float_t> &a, bool forward)
+{
+    if (a.getNDim() < 2) {
+               std::cout << "fft2c Error: input array must have at least two dimensions" << std::endl;
+               return -1;
+       }
+
+       size_t elements =  a.getDims()[0]*a.getDims()[1];
+       size_t ffts = a.getNumberOfElements()/elements;
+
+       //Array for transformation
+       fftwf_complex* tmp = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*a.getNumberOfElements());
+
+       if (!tmp) {
+            std::cout << "Error allocating temporary storage for FFTW" << std::endl;
+            return -1;
+       }
+
+       for (size_t f = 0; f < ffts; f++) {
+
+            fftshift(reinterpret_cast<std::complex<float>*>(tmp),&a(0,0,f),a.getDims()[0],a.getDims()[1]);
+
+            //Create the FFTW plan
+            fftwf_plan p;
+            if (forward) {
+                p = fftwf_plan_dft_2d(a.getDims()[1], a.getDims()[0], tmp,tmp, FFTW_FORWARD, FFTW_ESTIMATE);
+            } else {
+                p = fftwf_plan_dft_2d(a.getDims()[1], a.getDims()[0], tmp,tmp, FFTW_BACKWARD, FFTW_ESTIMATE);
+            }
+            fftwf_execute(p);
+            
+            fftshift(&a(0,0,f),reinterpret_cast<std::complex<float>*>(tmp),a.getDims()[0],a.getDims()[1]);
+            
+            //Clean up.
+            fftwf_destroy_plan(p);
+       }
+
+       std::complex<float> scale(std::sqrt(1.0f*elements),0.0);
+        for (size_t n=0; n<a.getNumberOfElements(); n++) {
+            a.getDataPtr()[n] /= scale;
+        }
+       fftwf_free(tmp);
+       return 0;
+}
+
+int fft2c(NDArray<complex_float_t> &a) {
+    return fft2c(a,true);
+}
+
+int ifft2c(NDArray<complex_float_t> &a) {
+    return fft2c(a,false);
+}
+
+};
diff --git a/utilities/ismrmrd_info.cpp b/utilities/ismrmrd_info.cpp
new file mode 100644 (file)
index 0000000..e869b8c
--- /dev/null
@@ -0,0 +1,12 @@
+#include <iostream>
+#include "ismrmrd/version.h"
+
+int main(int argc, char** argv)
+{
+  std::cout << "ISMRMRD VERSION INFO: " << std::endl;
+  std::cout << "   -- Version:         " << ISMRMRD_VERSION_MAJOR << "." <<
+        ISMRMRD_VERSION_MINOR << "." << ISMRMRD_VERSION_PATCH << std::endl;
+  std::cout << "   -- SHA1:            " << ISMRMRD_GIT_SHA1_HASH << std::endl;
+  std::cout << "   -- Dataset support: " << (ISMRMRD_DATASET_SUPPORT ? "yes" : "no") << std::endl;
+  return 0;
+}
diff --git a/utilities/ismrmrd_phantom.cpp b/utilities/ismrmrd_phantom.cpp
new file mode 100644 (file)
index 0000000..00360ac
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * ismrmrd_phanthom.cpp
+ *
+ *  Created on: Apr 1, 2013
+ *      Author: Michael S. Hansen
+ */
+
+#include "ismrmrd_phantom.h"
+#include <boost/random.hpp>
+#include <boost/random/normal_distribution.hpp>
+#include <cstring>
+
+namespace ISMRMRD {
+
+
+boost::shared_ptr<NDArray<complex_float_t> > phantom(std::vector<PhantomEllipse>& ellipses, unsigned int matrix_size)
+{
+    std::vector<size_t> dims(2,matrix_size);
+    boost::shared_ptr<NDArray<complex_float_t> > out(new NDArray<complex_float_t>(dims));
+    memset(out->getDataPtr(), 0, out->getDataSize());
+    for (std::vector<PhantomEllipse>::iterator it = ellipses.begin(); it != ellipses.end(); it++) {
+        for (unsigned int y = 0; y < matrix_size; y++) {
+            float y_co = (1.0*y-(matrix_size>>1))/(matrix_size>>1);
+            for (unsigned int x = 0; x < matrix_size; x++) {
+                float x_co = (1.0*x-(matrix_size>>1))/(matrix_size>>1);
+                if (it->isInside(x_co,y_co)) {
+                    (*out)(x,y) += std::complex<float>(it->getAmplitude(),0.0);
+                }
+            }
+        }
+    }
+    return out;
+}
+
+
+boost::shared_ptr<NDArray<complex_float_t> > shepp_logan_phantom(unsigned int matrix_size)
+{
+    boost::shared_ptr< std::vector<PhantomEllipse> > e = modified_shepp_logan_ellipses();
+    return phantom(*e, matrix_size);
+}
+
+
+boost::shared_ptr< std::vector<PhantomEllipse> > shepp_logan_ellipses()
+{
+    boost::shared_ptr< std::vector<PhantomEllipse> > out(new std::vector<PhantomEllipse>);
+    out->push_back(PhantomEllipse(1,   .69,    .92,    0,              0,              0));
+    out->push_back(PhantomEllipse(-.98, .6624,         .8740,  0,      -.0184,         0));
+    out->push_back(PhantomEllipse(-.02, .1100,         .3100,  .22,    0,         -18));
+    out->push_back(PhantomEllipse(-.02, .1600, .4100, -.22,    0,     18));
+    out->push_back(PhantomEllipse(.01, .2100, .2500,   0,    .35,    0));
+    out->push_back(PhantomEllipse(.01, .0460, .0460,   0,    .1,     0));
+    out->push_back(PhantomEllipse(.01, .0460, .0460,   0,   -.1,     0));
+    out->push_back(PhantomEllipse(.01, .0460, .0230, -.08,  -.605,   0));
+    out->push_back(PhantomEllipse(.01, .0230, .0230,   0,   -.606,   0));
+    out->push_back(PhantomEllipse( .01, .0230, .0460,  .06,  -.605,   0));
+
+    return out;
+}
+
+boost::shared_ptr< std::vector<PhantomEllipse> > modified_shepp_logan_ellipses()
+{
+    boost::shared_ptr< std::vector<PhantomEllipse> > out(new std::vector<PhantomEllipse>);
+    out->push_back(PhantomEllipse(  1,   .69,   .92,    0,     0,     0));
+    out->push_back(PhantomEllipse(-.8,  .6624, .8740,   0,  -.0184,   0));
+    out->push_back(PhantomEllipse(-.2,  .1100, .3100,  .22,    0,    -18));
+    out->push_back(PhantomEllipse(-.2,  .1600, .4100, -.22,    0,     18));
+    out->push_back(PhantomEllipse(.1,  .2100, .2500,   0,    .35,    0));
+    out->push_back(PhantomEllipse(.1,  .0460, .0460,   0,    .1,     0));
+    out->push_back(PhantomEllipse(.1,  .0460, .0460,   0,   -.1,     0));
+    out->push_back(PhantomEllipse(.1,  .0460, .0230, -.08,  -.605,   0));
+    out->push_back(PhantomEllipse(.1,  .0230, .0230,   0,  -.606,   0));
+    out->push_back(PhantomEllipse( .1,  .0230, .0460,  .06,  -.605,   0 ));
+    return out;
+}
+
+boost::shared_ptr<NDArray<complex_float_t> > generate_birdcage_sensititivies(unsigned int matrix_size, unsigned int ncoils, float relative_radius)
+{
+    //This function is heavily inspired by the mri_birdcage.m Matlab script in Jeff Fessler's IRT packake
+    //http://web.eecs.umich.edu/~fessler/code/
+
+    std::vector<size_t> dims(2,matrix_size);
+    dims.push_back(ncoils);
+    boost::shared_ptr<NDArray<complex_float_t> > out(new NDArray<complex_float_t>(dims));
+    memset(out->getDataPtr(), 0, out->getDataSize());
+
+    for (unsigned int c = 0; c < ncoils; c++) {
+        float coilx = relative_radius*std::cos(c*(2*3.14159265359/ncoils));
+        float coily = relative_radius*std::sin(c*(2*3.14159265359/ncoils));
+        float coil_phase = -c*(2*3.14159265359/ncoils);
+        for (unsigned int y = 0; y < matrix_size; y++) {
+            float y_co = (1.0*y-(matrix_size>>1))/(matrix_size>>1)-coily;
+            for (unsigned int x = 0; x < matrix_size; x++) {
+                float x_co = (1.0*x-(matrix_size>>1))/(matrix_size>>1)-coilx;
+                float rr = std::sqrt(x_co*x_co+y_co*y_co);
+                float phi = atan2(x_co, -y_co) + coil_phase;
+                (*out)(x,y,c) = std::polar(1 / rr, phi);
+            }
+        }
+    }
+
+    return out;
+}
+
+
+boost::mt19937& get_noise_seed()
+{
+    static boost::mt19937 rng;
+    return rng;
+}
+
+int add_noise(NDArray<complex_float_t> & a, float sd)
+{
+
+    boost::normal_distribution<float> nd(0.0, sd);
+    boost::variate_generator<boost::mt19937&,
+                             boost::normal_distribution<float> > var_nor(get_noise_seed(), nd);
+
+    for (size_t i = 0; i < a.getNumberOfElements(); i++) {
+        a.getDataPtr()[i] += std::complex<float>(var_nor(),var_nor());
+    }
+
+    return 0;
+}
+
+int add_noise(Acquisition& a, float sd)
+{
+
+    boost::normal_distribution<float> nd(0.0, sd);
+    boost::variate_generator<boost::mt19937&,
+                             boost::normal_distribution<float> > var_nor(get_noise_seed(), nd);
+
+    for (uint16_t c=0; c<a.active_channels(); c++) {
+        for (uint16_t s=0; s<a.number_of_samples(); s++) {
+            a.data(s,c) += std::complex<float>(var_nor(), var_nor());
+        }
+    }
+
+    return 0;
+}
+};
diff --git a/utilities/ismrmrd_phantom.h b/utilities/ismrmrd_phantom.h
new file mode 100644 (file)
index 0000000..fdae0ca
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * ismrmrd_phantom.h
+ *
+ *  Created on: Apr 1, 2013
+ *      Author: Michael S. Hansen
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <complex>
+#include "ismrmrd/ismrmrd.h"
+
+#ifndef ISMRMRD_PHANTOM_H_
+#define ISMRMRD_PHANTOM_H_
+
+namespace ISMRMRD {
+
+       class PhantomEllipse
+       {
+       public:
+               PhantomEllipse(float A, float a, float b, float x0, float y0, float phi)
+                       : A_(A)
+                       , a_(a)
+                       , b_(b)
+                       , x0_(x0)
+                       , y0_(y0)
+                       , phi_(phi)
+               {
+
+               }
+
+               bool isInside(float x, float y)
+               {
+                       float asq = a_*a_;                // a^2
+                       float bsq = b_*b_;                // b^2
+                       float phi = phi_*3.14159265359/180; // rotation angle in radians
+                       float x0  = x-x0_;                                        // x offset
+                       float y0 =  y-y0_;                     // y offset
+                       float cosp = cos(phi);
+                       float sinp = sin(phi);
+                       return (((x0*cosp + y0*sinp)*(x0*cosp + y0*sinp))/asq + ((y0*cosp - x0*sinp)*(y0*cosp - x0*sinp))/bsq <= 1);
+               }
+
+               float getAmplitude()
+               {
+                       return A_;
+               }
+
+
+       protected:
+
+               float A_;
+               float a_;
+               float b_;
+               float x0_;
+               float y0_;
+               float phi_;
+       };
+
+       boost::shared_ptr< std::vector<PhantomEllipse> > shepp_logan_ellipses();
+        boost::shared_ptr< std::vector<PhantomEllipse> > modified_shepp_logan_ellipses();
+        boost::shared_ptr<NDArray<complex_float_t> > phantom(std::vector<PhantomEllipse>& coefficients, unsigned int matrix_size);
+        boost::shared_ptr<NDArray<complex_float_t> > shepp_logan_phantom(unsigned int matrix_size);
+        boost::shared_ptr<NDArray<complex_float_t> > generate_birdcage_sensititivies(unsigned int matrix_size, unsigned int ncoils, float relative_radius);
+       int add_noise(NDArray<complex_float_t> & a, float sd);
+       int add_noise(Acquisition & a, float sd);
+
+};
+
+#endif /* ISMRMRD_PHANTOM_H_ */
diff --git a/utilities/ismrmrd_test_xml.cpp b/utilities/ismrmrd_test_xml.cpp
new file mode 100644 (file)
index 0000000..47c1541
--- /dev/null
@@ -0,0 +1,55 @@
+#include <iostream>
+#include <fstream>
+#include <streambuf>
+#include "ismrmrd/xml.h"
+#include "pugixml.hpp"
+
+int main(int argc, char** argv)
+{
+
+  if (argc < 2) {
+    std::cout << "Usage:\n\tismrmrd_test_xml <ismrmrd_xml_header.xml>\n" << std::endl;
+    return -1;
+  }
+
+  std::string filename(argv[1]);
+
+  std::cout << "ISMRMRD Header: " << filename << std::endl;
+
+  std::ifstream t(filename.c_str());
+  std::string xml((std::istreambuf_iterator<char>(t)),
+                 std::istreambuf_iterator<char>());
+
+  ISMRMRD::IsmrmrdHeader h;
+  deserialize(xml.c_str(),h);
+
+  pugi::xml_document doc;
+  pugi::xml_parse_result result = doc.load(xml.c_str());
+  if (!result) {
+    std::cout << "Unable to load XML document using pugixml parser" << std::endl;
+  }
+
+  std::ofstream raw("raw.xml");
+  std::ofstream proc("processed.xml");
+  
+
+  doc.save(raw);
+  serialize(h,proc);
+
+  /*
+  deserialize(xml->c_str(),h);
+
+  std::cout << "Resonance frequency: " << h.experimentalConditions.H1resonanceFrequency_Hz << std::endl;
+  std::cout << "TR: " << h.sequenceParameters.get().TR[0] << std::endl;
+  if (h.userParameters) {
+    std::cout << "User parameters found" << std::endl;
+    for (size_t i = 0; i < h.userParameters->userParameterLong.size(); i++) {
+      std::cout << "UserLong: " << h.userParameters->userParameterLong[i].name.c_str() << ", " 
+               << h.userParameters->userParameterLong[i].value << std::endl;
+    }
+  }
+
+  serialize(h,std::cout);
+  */
+  return 0;
+}
diff --git a/utilities/read_timing_test.cpp b/utilities/read_timing_test.cpp
new file mode 100644 (file)
index 0000000..8f387d4
--- /dev/null
@@ -0,0 +1,85 @@
+#ifdef WIN32 
+#include <windows.h>
+#else 
+#include <sys/time.h>
+#endif
+
+#include <iostream>
+#include <string>
+
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/dataset.h"
+
+
+class Timer
+{
+public:
+
+  Timer() { Timer("Timer"); }
+
+  Timer(const char* name) : name_(name) {
+    pre();
+#ifdef WIN32
+    QueryPerformanceFrequency(&frequency_);
+    QueryPerformanceCounter(&start_);
+#else
+    gettimeofday(&start_, NULL);
+#endif
+  }
+
+  virtual ~Timer() {
+    double time_in_us = 0.0;
+    post();
+#ifdef WIN32
+    QueryPerformanceCounter(&end_);
+    time_in_us = (end_.QuadPart * (1.0e6/ frequency_.QuadPart)) - start_.QuadPart * (1.0e6 / frequency_.QuadPart);
+#else
+    gettimeofday(&end_, NULL);
+    time_in_us = ((end_.tv_sec * 1e6) + end_.tv_usec) - ((start_.tv_sec * 1e6) + start_.tv_usec);
+#endif
+    std::cout << name_ << ": " << time_in_us/1000.0 << " ms" << std::endl; std::cout.flush();
+  }
+
+  virtual void pre() { }
+  virtual void post() { }
+
+protected:
+
+#ifdef WIN32
+  LARGE_INTEGER frequency_;
+  LARGE_INTEGER start_;
+  LARGE_INTEGER end_;
+#else
+  timeval start_;
+  timeval end_;
+#endif
+
+  std::string name_;
+};
+
+
+int main(int argc, char** argv)
+{
+  std::cout << "File reader timing test" << std::endl;
+
+  if (argc != 2) {
+    std::cout << "Usage: " << std::endl;
+    std::cout << "  " << argv[0] << " <FILENAME>" << std::endl;
+  } 
+
+  std::cout << "Opening file " << argv[1] << std::endl;
+
+
+  {
+    Timer t("READ TIMER");
+    ISMRMRD::Dataset d(argv[1],"dataset", false);
+    uint32_t number_of_acquisitions = d.getNumberOfAcquisitions();
+    ISMRMRD::Acquisition acq;
+    for (uint32_t i = 0; i < number_of_acquisitions; i++) {
+        d.readAcquisition(i, acq);
+        //We'll just throw the data away here. 
+    }
+  }
+  
+  return 0;
+}
diff --git a/utilities/recon_cartesian_2d.cpp b/utilities/recon_cartesian_2d.cpp
new file mode 100644 (file)
index 0000000..424296f
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * test_recon_dataset.cpp
+ *
+ *  Created on: Sep 6, 2012
+ *      Author: Michael S. Hansen (michael.hansen@nih.gov)
+ *
+ */
+
+#include <iostream>
+#include "ismrmrd/ismrmrd.h"
+#include "ismrmrd/dataset.h"
+#include "ismrmrd/xml.h"
+#include "fftw3.h"
+
+//Helper function for the FFTW library
+void circshift(complex_float_t *out, const complex_float_t *in, int xdim, int ydim, int xshift, int yshift)
+{
+    for (int i =0; i < ydim; i++) {
+        int ii = (i + yshift) % ydim;
+        for (int j = 0; j < xdim; j++) {
+            int jj = (j + xshift) % xdim;
+            out[ii * xdim + jj] = in[i * xdim + j];
+        }
+    }
+}
+
+#define fftshift(out, in, x, y) circshift(out, in, x, y, (x/2), (y/2))
+
+void print_usage(const char* application)
+{
+    std::cout << "Usage:" << std::endl;
+    std::cout << "  - " << application << " <HDF5_FILENAME> " << std::endl;
+}
+
+// MAIN APPLICATION
+int main(int argc, char** argv)
+{
+    if (argc < 2) {
+        print_usage(argv[0]);
+        return -1;
+    }
+
+    std::string datafile(argv[1]);
+
+    std::cout << "Simple ISMRMRD Reconstruction program" << std::endl;
+    std::cout << "   - filename: " << datafile << std::endl;
+
+    //Let's open the existing dataset
+    ISMRMRD::Dataset d(datafile.c_str(),"dataset", false);
+
+    std::string xml;
+    d.readHeader(xml);
+    ISMRMRD::IsmrmrdHeader hdr;
+    ISMRMRD::deserialize(xml.c_str(),hdr);
+
+    //Let's print some information from the header
+    if (hdr.version) {
+        std::cout << "XML Header version: " << hdr.version << std::endl;
+    }
+    else {
+        std::cout << "XML Header unspecified version." << std::endl;
+    }
+    
+    if (hdr.encoding.size() != 1) {
+        std::cout << "Number of encoding spaces: " << hdr.encoding.size() << std::endl;
+        std::cout << "This simple reconstruction application only supports one encoding space" << std::endl;
+        return -1;
+    }
+
+    ISMRMRD::EncodingSpace e_space = hdr.encoding[0].encodedSpace;
+    ISMRMRD::EncodingSpace r_space = hdr.encoding[0].reconSpace;
+
+    if (e_space.matrixSize.z != 1) {
+        std::cout << "This simple reconstruction application only supports 2D encoding spaces" << std::endl;
+        return -1;
+    }
+    
+    uint16_t nX = e_space.matrixSize.x;
+    uint16_t nY = e_space.matrixSize.y;
+    
+    // The number of channels is optional, so read the first line
+    ISMRMRD::Acquisition acq;
+    d.readAcquisition(0, acq);
+    uint16_t nCoils = acq.active_channels();
+    
+    std::cout << "Encoding Matrix Size        : [" << e_space.matrixSize.x << ", " << e_space.matrixSize.y << ", " << e_space.matrixSize.z << "]" << std::endl;
+    std::cout << "Reconstruction Matrix Size  : [" << r_space.matrixSize.x << ", " << r_space.matrixSize.y << ", " << r_space.matrixSize.z << "]" << std::endl;
+    std::cout << "Number of Channels          : " << nCoils << std::endl;
+    std::cout << "Number of acquisitions      : " << d.getNumberOfAcquisitions() << std::endl;
+
+    //Allocate a buffer for the data
+    std::vector<size_t> dims;
+    dims.push_back(nX);
+    dims.push_back(nY);
+    dims.push_back(nCoils);
+    ISMRMRD::NDArray<complex_float_t> buffer(dims);
+    memset(buffer.getDataPtr(), 0, sizeof(complex_float_t)*nX*nY*nCoils);
+    
+    //Now loop through and copy data
+    unsigned int number_of_acquisitions = d.getNumberOfAcquisitions();
+    for (unsigned int i = 0; i < number_of_acquisitions; i++) {
+        //Read one acquisition at a time
+        d.readAcquisition(i, acq);
+
+        //Copy data, we should probably be more careful here and do more tests....
+        for (uint16_t c=0; c<nCoils; c++) {
+            memcpy(&buffer(0,acq.idx().kspace_encode_step_1,c), &acq.data(0, c), sizeof(complex_float_t)*nX);
+        }
+    }
+
+    // Do the recon one slice at a time
+    for (uint16_t c=0; c<nCoils; c++) {
+        
+        //Let's FFT the k-space to image (in-place)
+        fftwf_complex* tmp = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*(nX*nY));
+
+        if (!tmp) {
+            std::cout << "Error allocating temporary storage for FFTW" << std::endl;
+            return -1;
+        }
+    
+        //Create the FFTW plan
+        fftwf_plan p = fftwf_plan_dft_2d(nY, nX, tmp ,tmp, FFTW_BACKWARD, FFTW_ESTIMATE);
+
+        //FFTSHIFT
+        fftshift(reinterpret_cast<complex_float_t*>(tmp), &buffer(0,0,c), nX, nY);
+        
+        //Execute the FFT
+        fftwf_execute(p);
+        
+        //FFTSHIFT
+        fftshift( &buffer(0,0,c), reinterpret_cast<std::complex<float>*>(tmp), nX, nY);
+
+        //Clean up.
+        fftwf_destroy_plan(p);
+        fftwf_free(tmp);
+
+    }
+
+    //Allocate an image
+    ISMRMRD::Image<float> img_out(r_space.matrixSize.x, r_space.matrixSize.y, 1, 1);
+    memset(img_out.getDataPtr(), 0, sizeof(float_t)*r_space.matrixSize.x*r_space.matrixSize.y);
+           
+    //f there is oversampling in the readout direction remove it
+    //Take the sqrt of the sum of squares
+    uint16_t offset = ((e_space.matrixSize.x - r_space.matrixSize.x)>>1);
+    for (uint16_t y = 0; y < r_space.matrixSize.y; y++) {
+        for (uint16_t x = 0; x < r_space.matrixSize.x; x++) {
+            for (uint16_t c=0; c<nCoils; c++) {
+                img_out(x,y) += (std::abs(buffer(x+offset, y, c)))*(std::abs(buffer(x+offset, y, c)));
+            }
+            img_out(x,y) = std::sqrt(img_out(x,y));            
+        }
+    }
+    
+    // The following are extra guidance we can put in the image header
+    img_out.setImageType(ISMRMRD::ISMRMRD_IMTYPE_MAGNITUDE);
+    img_out.setSlice(0);
+    img_out.setFieldOfView(r_space.fieldOfView_mm.x, r_space.fieldOfView_mm.y, r_space.fieldOfView_mm.z);
+    //And so on
+    
+    //Let's write the reconstructed image into the same data file
+    d.appendImage("cpp", img_out);
+
+    return 0;
+}