changeset 41:9e7f9134f237

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 01 Aug 2015 17:18:21 +0200
parents 43085eac99ff
children 3a2040a79007
files CMakeLists.txt Core/ChunkedBuffer.cpp Core/ChunkedBuffer.h Core/Configuration.cpp Core/Configuration.h Core/Dicom.cpp Core/Dicom.h Core/DicomResults.cpp Core/DicomResults.h Core/Toolbox.cpp Core/Toolbox.h Plugin/ChunkedBuffer.cpp Plugin/ChunkedBuffer.h Plugin/Configuration.cpp Plugin/Configuration.h Plugin/Dicom.cpp Plugin/Dicom.h Plugin/DicomResults.cpp Plugin/DicomResults.h Plugin/Plugin.cpp Plugin/QidoRs.cpp Plugin/StowRs.cpp Plugin/Toolbox.cpp Plugin/Toolbox.h Plugin/WadoRs.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 26 files changed, 1709 insertions(+), 1709 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri Jul 31 13:36:02 2015 +0200
+++ b/CMakeLists.txt	Sat Aug 01 17:18:21 2015 +0200
@@ -98,11 +98,11 @@
   ${BOOST_SOURCES}
   ${JSONCPP_SOURCES}
   ${PUGIXML_SOURCES}
-  Core/ChunkedBuffer.cpp
-  Core/Configuration.cpp
-  Core/Dicom.cpp
-  Core/DicomResults.cpp
-  Core/Toolbox.cpp
+  Plugin/ChunkedBuffer.cpp
+  Plugin/Configuration.cpp
+  Plugin/Dicom.cpp
+  Plugin/DicomResults.cpp
+  Plugin/Toolbox.cpp
   )
 
 add_library(OrthancDicomWeb SHARED ${CORE_SOURCES}
--- a/Core/ChunkedBuffer.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "ChunkedBuffer.h"
-
-#include <cassert>
-#include <string.h>
-
-
-namespace OrthancPlugins
-{
-  void ChunkedBuffer::Clear()
-  {
-    numBytes_ = 0;
-
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      delete *it;
-    }
-  }
-
-
-  void ChunkedBuffer::AddChunk(const char* chunkData,
-                               size_t chunkSize)
-  {
-    if (chunkSize == 0)
-    {
-      return;
-    }
-
-    assert(chunkData != NULL);
-    chunks_.push_back(new std::string(chunkData, chunkSize));
-    numBytes_ += chunkSize;
-  }
-
-
-  void ChunkedBuffer::AddChunk(const std::string& chunk)
-  {
-    if (chunk.size() > 0)
-    {
-      AddChunk(&chunk[0], chunk.size());
-    }
-  }
-
-
-  void ChunkedBuffer::Flatten(std::string& result)
-  {
-    result.resize(numBytes_);
-
-    size_t pos = 0;
-    for (Chunks::iterator it = chunks_.begin(); 
-         it != chunks_.end(); ++it)
-    {
-      assert(*it != NULL);
-
-      size_t s = (*it)->size();
-      if (s != 0)
-      {
-        memcpy(&result[pos], (*it)->c_str(), s);
-        pos += s;
-      }
-
-      delete *it;
-    }
-
-    chunks_.clear();
-  }
-}
--- a/Core/ChunkedBuffer.h	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <list>
-#include <string>
-
-namespace OrthancPlugins
-{
-  class ChunkedBuffer
-  {
-  private:
-    typedef std::list<std::string*>  Chunks;
-    size_t numBytes_;
-    Chunks chunks_;
-  
-    void Clear();
-
-  public:
-    ChunkedBuffer() : numBytes_(0)
-    {
-    }
-
-    ~ChunkedBuffer()
-    {
-      Clear();
-    }
-
-    size_t GetNumBytes() const
-    {
-      return numBytes_;
-    }
-
-    void AddChunk(const char* chunkData,
-                  size_t chunkSize);
-
-    void AddChunk(const std::string& chunk);
-
-    void Flatten(std::string& result);
-  };
-}
--- a/Core/Configuration.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Configuration.h"
-
-#include "Toolbox.h"
-
-#include <fstream>
-#include <json/reader.h>
-
-namespace OrthancPlugins
-{
-  namespace Configuration
-  {
-    bool Read(Json::Value& configuration,
-              OrthancPluginContext* context)
-    {
-      std::string s;
-
-      {
-        char* tmp = OrthancPluginGetConfiguration(context);
-        if (tmp == NULL)
-        {
-          OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc");
-          return false;
-        }
-
-        s.assign(tmp);
-        OrthancPluginFreeString(context, tmp);      
-      }
-
-      Json::Reader reader;
-      if (reader.parse(s, configuration))
-      {
-        return true;
-      }
-      else
-      {
-        OrthancPluginLogError(context, "Unable to parse the configuration");
-        return false;
-      }
-    }
-
-
-    std::string GetStringValue(const Json::Value& configuration,
-                               const std::string& key,
-                               const std::string& defaultValue)
-    {
-      if (configuration.type() != Json::objectValue ||
-          !configuration.isMember(key) ||
-          configuration[key].type() != Json::stringValue)
-      {
-        return defaultValue;
-      }
-      else
-      {
-        return configuration[key].asString();
-      }
-    }
-
-
-    bool GetBoolValue(const Json::Value& configuration,
-                      const std::string& key,
-                      bool defaultValue)
-    {
-      if (configuration.type() != Json::objectValue ||
-          !configuration.isMember(key) ||
-          configuration[key].type() != Json::booleanValue)
-      {
-        return defaultValue;
-      }
-      else
-      {
-        return configuration[key].asBool();
-      }
-    }
-
-
-    std::string GetRoot(const Json::Value& configuration)
-    {
-      std::string root;
-
-      if (configuration.isMember("DicomWeb"))
-      {
-        root = GetStringValue(configuration["DicomWeb"], "Root", "");
-      }
-
-      if (root.empty())
-      {
-        root = "/dicom-web/";
-      }
-
-      // Make sure the root URI starts and ends with a slash
-      if (root[0] != '/')
-      {
-        root = "/" + root;
-      }
-    
-      if (root[root.length() - 1] != '/')
-      {
-        root += "/";
-      }
-
-      return root;
-    }
-
-
-    std::string  GetBaseUrl(const Json::Value& configuration,
-                            const OrthancPluginHttpRequest* request)
-    {
-      std::string host;
-      bool ssl = false;
-
-      if (configuration.isMember("DicomWeb"))
-      {
-        host = GetStringValue(configuration["DicomWeb"], "Host", "");
-        ssl = GetBoolValue(configuration["DicomWeb"], "Ssl", false);
-      }
-
-      if (host.empty() &&
-          !LookupHttpHeader(host, request, "host"))
-      {
-        // Should never happen: The "host" header should always be present
-        // in HTTP requests. Provide a default value anyway.
-        host = "localhost:8042";
-      }
-
-      return (ssl ? "https://" : "http://") + host + GetRoot(configuration);
-    }
-  }
-}
--- a/Core/Configuration.h	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <orthanc/OrthancCPlugin.h>
-#include <json/value.h>
-
-namespace OrthancPlugins
-{
-  namespace Configuration
-  {
-    bool Read(Json::Value& configuration,
-              OrthancPluginContext* context);
-
-    std::string GetStringValue(const Json::Value& configuration,
-                               const std::string& key,
-                               const std::string& defaultValue);
-    
-    bool GetBoolValue(const Json::Value& configuration,
-                      const std::string& key,
-                      bool defaultValue);
-
-    std::string GetRoot(const Json::Value& configuration);
-
-    std::string GetBaseUrl(const Json::Value& configuration,
-                           const OrthancPluginHttpRequest* request);
-  }
-}
--- a/Core/Dicom.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,563 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Dicom.h"
-
-#include "ChunkedBuffer.h"
-
-#include <gdcmDictEntry.h>
-#include <gdcmStringFilter.h>
-#include <boost/lexical_cast.hpp>
-#include <json/writer.h>
-
-namespace OrthancPlugins
-{
-  namespace
-  {
-    class ChunkedBufferWriter : public pugi::xml_writer
-    {
-    private:
-      ChunkedBuffer buffer_;
-
-    public:
-      virtual void write(const void *data, size_t size)
-      {
-        if (size > 0)
-        {
-          buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
-        }
-      }
-
-      void Flatten(std::string& s)
-      {
-        buffer_.Flatten(s);
-      }
-    };
-  }
-
-
-
-  void ParsedDicomFile::Setup(const std::string& dicom)
-  {
-    // Prepare a memory stream over the DICOM instance
-    std::stringstream stream(dicom);
-
-    // Parse the DICOM instance using GDCM
-    reader_.SetStream(stream);
-    if (!reader_.Read())
-    {
-      throw std::runtime_error("GDCM cannot read this DICOM instance of length " + 
-                               boost::lexical_cast<std::string>(dicom.size()));
-    }
-  }
-
-
-  ParsedDicomFile::ParsedDicomFile(const OrthancPlugins::MultipartItem& item)
-  {
-    std::string dicom(item.data_, item.data_ + item.size_);
-    Setup(dicom);
-  }
-
-
-  static bool GetTag(std::string& result,
-                     const gdcm::DataSet& dataset,
-                     const gdcm::Tag& tag,
-                     bool stripSpaces)
-  {
-    if (dataset.FindDataElement(tag))
-    {
-      const gdcm::ByteValue* value = dataset.GetDataElement(tag).GetByteValue();
-      if (value)
-      {
-        result = std::string(value->GetPointer(), value->GetLength());
-
-        if (stripSpaces)
-        {
-          result = OrthancPlugins::StripSpaces(result);
-        }
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-  static std::string GetTagWithDefault(const gdcm::DataSet& dataset,
-                                       const gdcm::Tag& tag,
-                                       const std::string& defaultValue,
-                                       bool stripSpaces)
-  {
-    std::string result;
-    if (!GetTag(result, dataset, tag, false))
-    {
-      result = defaultValue;
-    }
-
-    if (stripSpaces)
-    {
-      result = StripSpaces(result);
-    }
-
-    return result;
-  }
-
-
-  bool ParsedDicomFile::GetTag(std::string& result,
-                               const gdcm::Tag& tag,
-                               bool stripSpaces) const
-  {
-    return OrthancPlugins::GetTag(result, GetDataSet(), tag, stripSpaces);
-  }
-
-
-  std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag,
-                                                 const std::string& defaultValue,
-                                                 bool stripSpaces) const
-  {
-    return OrthancPlugins::GetTagWithDefault(GetDataSet(), tag, defaultValue, stripSpaces);
-  }
-
-
-  static std::string FormatTag(const gdcm::Tag& tag)
-  {
-    char tmp[16];
-    sprintf(tmp, "%04X%04X", tag.GetGroup(), tag.GetElement());
-    return std::string(tmp);
-  }
-
-
-  static const char* GetKeyword(const gdcm::Dict& dictionary,
-                                const gdcm::Tag& tag)
-  {
-    const gdcm::DictEntry &entry = dictionary.GetDictEntry(tag);
-    const char* keyword = entry.GetKeyword();
-
-    if (strlen(keyword) != 0)
-    {
-      return keyword;
-    }
-
-    if (tag == DICOM_TAG_RETRIEVE_URL)
-    {
-      return "RetrieveURL";
-    }
-
-    //throw std::runtime_error("Unknown keyword for tag: " + FormatTag(tag));
-    return NULL;
-  }
-
-
-
-  static const char* GetVRName(bool& isSequence,
-                               const gdcm::Dict& dictionary,
-                               const gdcm::DataElement& element)
-  {
-    gdcm::VR vr = element.GetVR();
-    if (vr == gdcm::VR::INVALID)
-    {
-      const gdcm::DictEntry &entry = dictionary.GetDictEntry(element.GetTag());
-      vr = entry.GetVR();
-
-      if (vr == gdcm::VR::OB_OW)
-      {
-        vr = gdcm::VR::OB;
-      }
-    }
-
-    isSequence = (vr == gdcm::VR::SQ);
-
-    const char* str = gdcm::VR::GetVRString(vr);
-    if (isSequence)
-    {
-      return str;
-    }
-
-    if (str == NULL ||
-        strlen(str) != 2 ||
-        !(str[0] >= 'A' && str[0] <= 'Z') ||
-        !(str[1] >= 'A' && str[1] <= 'Z'))
-    {
-      return "UN";
-    }
-    else
-    {
-      return str;
-    }
-  }
-
-
-  static bool IsBulkData(const std::string& vr)
-  {
-    /**
-     * Full list of VR (Value Representations) that are admissible for
-     * being retrieved as bulk data. We commented out some of them, as
-     * they correspond to strings and not to binary data.
-     **/
-    return (//vr == "FL" ||
-            //vr == "FD" ||
-            //vr == "IS" ||
-            vr == "LT" ||
-            vr == "OB" ||
-            vr == "OD" ||
-            vr == "OF" ||
-            vr == "OW" ||
-            //vr == "SL" ||
-            //vr == "SS" ||
-            //vr == "ST" ||
-            //vr == "UL" ||
-            vr == "UN" ||
-            //vr == "US" ||
-            vr == "UT");
-  }
-
-
-  static std::string GetBulkUriRoot(const std::string& wadoBase,
-                                    const gdcm::DataSet& dicom)
-  {
-    std::string study, series, instance;
-
-    if (!GetTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) ||
-        !GetTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) ||
-        !GetTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true))
-    {
-      return "";
-    }
-    else
-    {
-      return wadoBase + "studies/" + study + "/series/" + series + "/instances/" + instance + "/bulk/";
-    }
-  }
-
-
-  static Encoding DetectEncoding(const gdcm::DataSet& dicom)
-  {
-    if (!dicom.FindDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET))
-    {
-      return Encoding_Ascii;
-    }
-
-    const gdcm::DataElement& element = 
-      dicom.GetDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET);
-
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      return Encoding_Unknown;
-    }
-
-    std::string tmp(data->GetPointer(), data->GetLength());
-    tmp = StripSpaces(tmp);
-
-    return GetDicomEncoding(tmp.c_str());
-  }
-
-
-  static bool ConvertDicomStringToUf8(std::string& result,
-                                      const gdcm::Dict& dictionary,
-                                      const gdcm::File* file,
-                                      const gdcm::DataElement& element,
-                                      const Encoding sourceEncoding)
-  {
-    const gdcm::ByteValue* data = element.GetByteValue();
-    if (!data)
-    {
-      return false;
-    }
-
-    if (file != NULL)
-    {
-      bool isSequence;
-      std::string vr = GetVRName(isSequence, dictionary, element);
-      if (!isSequence && (
-            vr == "FL" ||
-            vr == "FD" ||
-            vr == "SL" ||
-            vr == "SS" ||
-            vr == "UL" ||
-            vr == "US"
-            ))
-      {      
-        gdcm::StringFilter f;
-        f.SetFile(*file);
-        result = f.ToString(element.GetTag());
-        return true;
-      }
-    }
-
-    if (sourceEncoding == Encoding_Utf8)
-    {
-      result.assign(data->GetPointer(), data->GetLength());
-    }
-    else
-    {
-      std::string tmp(data->GetPointer(), data->GetLength());
-      result = ConvertToUtf8(tmp, sourceEncoding);
-    }
-
-    result = StripSpaces(result);
-    return true;
-  }
-
-
-  static void DicomToXmlInternal(pugi::xml_node& target,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,
-                                 const gdcm::DataSet& dicom,
-                                 const Encoding sourceEncoding,
-                                 const std::string& bulkUri)
-  {
-    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
-         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
-    {
-      char path[16];
-      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
-
-      pugi::xml_node node = target.append_child("DicomAttribute");
-      node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str());
-
-      const char* keyword = GetKeyword(dictionary, it->GetTag());
-      if (keyword != NULL)
-      {
-        node.append_attribute("keyword").set_value(keyword);
-      }
-
-      bool isSequence = false;
-      std::string vr;
-      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
-      {
-        // The VR of this attribute has changed from UT to UR.
-        vr = "UR";
-      }
-      else
-      {
-        vr = GetVRName(isSequence, dictionary, *it);
-      }
-
-      node.append_attribute("vr").set_value(vr.c_str());
-
-      if (isSequence)
-      {
-        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
-        if (seq.GetPointer() != NULL)
-        {
-          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
-          {
-            pugi::xml_node item = node.append_child("Item");
-            std::string number = boost::lexical_cast<std::string>(i);
-            item.append_attribute("number").set_value(number.c_str());
-
-            std::string childUri;
-            if (!bulkUri.empty())
-            {
-              childUri = bulkUri + std::string(path) + "/" + number + "/";
-            }
-
-            DicomToXmlInternal(item, dictionary, file, seq->GetItem(i).GetNestedDataSet(), sourceEncoding, childUri);
-          }
-        }
-      }
-      else if (IsBulkData(vr))
-      {
-        // Bulk data
-        if (!bulkUri.empty())
-        {
-          pugi::xml_node value = node.append_child("BulkData");
-          std::string uri = bulkUri + std::string(path);
-          value.append_attribute("uri").set_value(uri.c_str());
-        }
-      }
-      else
-      {
-        // Deal with other value representations
-        pugi::xml_node value = node.append_child("Value");
-        value.append_attribute("number").set_value("1");
-
-        std::string tmp;
-        if (ConvertDicomStringToUf8(tmp, dictionary, file, *it, sourceEncoding)) 
-        {
-          value.append_child(pugi::node_pcdata).set_value(tmp.c_str());
-        }
-      }
-    }
-  }
-
-
-  static void DicomToXml(pugi::xml_document& target,
-                         const gdcm::Dict& dictionary,
-                         const gdcm::File* file,
-                         const gdcm::DataSet& dicom,
-                         const std::string& bulkUriRoot)
-  {
-    pugi::xml_node root = target.append_child("NativeDicomModel");
-    root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
-    root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
-    root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");
-
-    Encoding encoding = DetectEncoding(dicom);
-    DicomToXmlInternal(root, dictionary, file, dicom, encoding, bulkUriRoot);
-
-    pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
-    decl.append_attribute("version").set_value("1.0");
-    decl.append_attribute("encoding").set_value("utf-8");
-  }
-
-
-  static void DicomToJsonInternal(Json::Value& target,
-                                  const gdcm::Dict& dictionary,
-                                  const gdcm::File* file,
-                                  const gdcm::DataSet& dicom,
-                                  const std::string& bulkUri,
-                                  Encoding sourceEncoding)
-  {
-    target = Json::objectValue;
-
-    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
-         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
-    {
-      char path[16];
-      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
-
-      Json::Value node = Json::objectValue;
-
-      bool isSequence = false;
-      std::string vr;
-      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
-      {
-        // The VR of this attribute has changed from UT to UR.
-        vr = "UR";
-      }
-      else
-      {
-        vr = GetVRName(isSequence, dictionary, *it);
-      }
-
-      node["vr"] = vr.c_str();
-
-      if (isSequence)
-      {
-        // Deal with sequences
-        node["Value"] = Json::arrayValue;
-
-        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
-        if (seq.GetPointer() != NULL)
-        {
-          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
-          {
-            Json::Value child;
-
-            std::string childUri;
-            if (!bulkUri.empty())
-            {
-              std::string number = boost::lexical_cast<std::string>(i);
-              childUri = bulkUri + std::string(path) + "/" + number + "/";
-            }
-
-            DicomToJsonInternal(child, dictionary, file, seq->GetItem(i).GetNestedDataSet(), childUri, sourceEncoding);
-            node["Value"].append(child);
-          }
-        }
-      }
-      else if (IsBulkData(vr))
-      {
-        // Bulk data
-        if (!bulkUri.empty())
-        {
-          node["BulkDataURI"] = bulkUri + std::string(path);
-        }
-      }
-      else
-      {
-        // Deal with other value representations
-        node["Value"] = Json::arrayValue;
-
-        std::string value;
-        if (ConvertDicomStringToUf8(value, dictionary, file, *it, sourceEncoding)) 
-        {
-          node["Value"].append(value.c_str());
-        }
-      }
-
-      target[FormatTag(it->GetTag())] = node;
-    }
-  }
-
-
-  static void DicomToJson(Json::Value& target,
-                          const gdcm::Dict& dictionary,
-                          const gdcm::File* file,
-                          const gdcm::DataSet& dicom,
-                          const std::string& bulkUriRoot)
-  {
-    Encoding encoding = DetectEncoding(dicom);
-    DicomToJsonInternal(target, dictionary, file, dicom, bulkUriRoot, encoding);
-  }
-
-
-  void GenerateSingleDicomAnswer(std::string& result,
-                                 const std::string& wadoBase,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,  // Can be NULL
-                                 const gdcm::DataSet& dicom,
-                                 bool isXml,
-                                 bool isBulkAccessible)
-  {
-    std::string bulkUriRoot;
-    if (isBulkAccessible)
-    {
-      bulkUriRoot = GetBulkUriRoot(wadoBase, dicom);
-    }
-
-    if (isXml)
-    {
-      pugi::xml_document doc;
-      DicomToXml(doc, dictionary, file, dicom, bulkUriRoot);
-    
-      ChunkedBufferWriter writer;
-      doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
-
-      writer.Flatten(result);
-    }
-    else
-    {
-      Json::Value v;
-      DicomToJson(v, dictionary, file, dicom, bulkUriRoot);
-
-      Json::FastWriter writer;
-      result = writer.write(v); 
-    }
-  }
-
-
-  void AnswerDicom(OrthancPluginContext* context,
-                   OrthancPluginRestOutput* output,
-                   const std::string& wadoBase,
-                   const gdcm::Dict& dictionary,
-                   const gdcm::DataSet& dicom,
-                   bool isXml,
-                   bool isBulkAccessible)
-  {
-    std::string answer;
-    GenerateSingleDicomAnswer(answer, wadoBase, dictionary, NULL, dicom, isXml, isBulkAccessible);
-    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), 
-                              isXml ? "application/dicom+xml" : "application/json");
-  }
-}
--- a/Core/Dicom.h	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "Toolbox.h"
-
-#include <gdcmReader.h>
-#include <gdcmDataSet.h>
-#include <pugixml.hpp>
-#include <gdcmDict.h>
-#include <list>
-
-
-namespace OrthancPlugins
-{
-  static const gdcm::Tag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
-  static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
-  static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
-  static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
-  static const gdcm::Tag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
-  static const gdcm::Tag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198);
-  static const gdcm::Tag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197);
-  static const gdcm::Tag DICOM_TAG_WARNING_REASON(0x0008, 0x1196);
-  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199);
-  static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
-  static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
-
-  class ParsedDicomFile
-  {
-  private:
-    gdcm::Reader reader_;
-
-    void Setup(const std::string& dicom);
-
-  public:
-    ParsedDicomFile(const OrthancPlugins::MultipartItem& item);
-
-    ParsedDicomFile(const std::string& dicom)
-    {
-      Setup(dicom);
-    }
-
-    const gdcm::File& GetFile() const
-    {
-      return reader_.GetFile();
-    }
-
-    const gdcm::DataSet& GetDataSet() const
-    {
-      return reader_.GetFile().GetDataSet();
-    }
-
-    bool GetTag(std::string& result,
-                const gdcm::Tag& tag,
-                bool stripSpaces) const;
-
-    std::string GetTagWithDefault(const gdcm::Tag& tag,
-                                  const std::string& defaultValue,
-                                  bool stripSpaces) const;
-  };
-
-
-  void GenerateSingleDicomAnswer(std::string& result,
-                                 const std::string& wadoBase,
-                                 const gdcm::Dict& dictionary,
-                                 const gdcm::File* file,  // Can be NULL
-                                 const gdcm::DataSet& dicom,
-                                 bool isXml,
-                                 bool isBulkAccessible);
-
-  void AnswerDicom(OrthancPluginContext* context,
-                   OrthancPluginRestOutput* output,
-                   const std::string& wadoBase,
-                   const gdcm::Dict& dictionary,
-                   const gdcm::DataSet& dicom,
-                   bool isXml,
-                   bool isBulkAccessible);
-}
--- a/Core/DicomResults.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "DicomResults.h"
-
-#include "Dicom.h"
-
-namespace OrthancPlugins
-{
-  DicomResults::DicomResults(OrthancPluginContext* context,
-                             OrthancPluginRestOutput* output,
-                             const std::string& wadoBase,
-                             const gdcm::Dict& dictionary,
-                             bool isXml,
-                             bool isBulkAccessible) :
-    context_(context),
-    output_(output),
-    wadoBase_(wadoBase),
-    dictionary_(dictionary),
-    isFirst_(true),
-    isXml_(isXml),
-    isBulkAccessible_(isBulkAccessible)
-  {
-    if (isXml_ &&
-        OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0)
-    {
-      throw std::runtime_error("Unable to create a multipart stream of DICOM+XML answers");
-    }
-
-    jsonWriter_.AddChunk("[\n");
-  }
-
-
-  void DicomResults::AddInternal(const gdcm::File* file,
-                                 const gdcm::DataSet& dicom)
-  {
-    if (isXml_)
-    {
-      std::string answer;
-      GenerateSingleDicomAnswer(answer, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_);
-
-      if (OrthancPluginSendMultipartItem(context_, output_, answer.c_str(), answer.size()) != 0)
-      {
-        throw std::runtime_error("Unable to write an item to a multipart stream of DICOM+XML answers");
-      }
-    }
-    else
-    {
-      if (!isFirst_)
-      {
-        jsonWriter_.AddChunk(",\n");
-      }
-
-      std::string item;
-      GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_);
-      jsonWriter_.AddChunk(item);
-    }
-
-    isFirst_ = false;
-  }
-
-  void DicomResults::Answer()
-  {
-    if (isXml_)
-    {
-      // Nothing to do in this case
-    }
-    else
-    {
-      jsonWriter_.AddChunk("]\n");
-
-      std::string answer;
-      jsonWriter_.Flatten(answer);
-      OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/json");
-    }
-  }
-}
--- a/Core/DicomResults.h	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include "ChunkedBuffer.h"
-
-#include <orthanc/OrthancCPlugin.h>
-#include <gdcmDataSet.h>
-#include <gdcmDict.h>
-#include <gdcmFile.h>
-
-namespace OrthancPlugins
-{
-  class DicomResults
-  {
-  private:
-    OrthancPluginContext*     context_;
-    OrthancPluginRestOutput*  output_;
-    std::string               wadoBase_;
-    const gdcm::Dict&         dictionary_;
-    ChunkedBuffer             jsonWriter_;  // Used for JSON output
-    bool                      isFirst_; 
-    bool                      isXml_;
-    bool                      isBulkAccessible_;
-
-    void AddInternal(const gdcm::File* file,
-                     const gdcm::DataSet& dicom);
-
-  public:
-    DicomResults(OrthancPluginContext* context,
-                 OrthancPluginRestOutput* output,
-                 const std::string& wadoBase,
-                 const gdcm::Dict& dictionary,
-                 bool isXml,
-                 bool isBulkAccessible);
-
-    void Add(const gdcm::File& file)
-    {
-      AddInternal(&file, file.GetDataSet());
-    }
-
-    void Add(const gdcm::File& file,
-             const gdcm::DataSet& subset)
-    {
-      AddInternal(&file, subset);
-    }
-
-    void Answer();
-  };
-}
--- a/Core/Toolbox.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,433 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "Toolbox.h"
-
-#include <string>
-#include <algorithm>
-#include <boost/regex.hpp>
-#include <boost/locale.hpp>
-#include <json/reader.h>
-
-
-namespace OrthancPlugins
-{
-  void ToLowerCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
-  }
-
-
-  void ToUpperCase(std::string& s)
-  {
-    std::transform(s.begin(), s.end(), s.begin(), ::toupper);
-  }
-
-
-  std::string StripSpaces(const std::string& source)
-  {
-    size_t first = 0;
-
-    while (first < source.length() &&
-           isspace(source[first]))
-    {
-      first++;
-    }
-
-    if (first == source.length())
-    {
-      // String containing only spaces
-      return "";
-    }
-
-    size_t last = source.length();
-    while (last > first &&
-           (isspace(source[last - 1]) ||
-            source[last - 1] == '\0'))
-    {
-      last--;
-    }          
-    
-    assert(first <= last);
-    return source.substr(first, last - first);
-  }
-
-  
-
-  void TokenizeString(std::vector<std::string>& result,
-                      const std::string& value,
-                      char separator)
-  {
-    result.clear();
-
-    std::string currentItem;
-
-    for (size_t i = 0; i < value.size(); i++)
-    {
-      if (value[i] == separator)
-      {
-        result.push_back(currentItem);
-        currentItem.clear();
-      }
-      else
-      {
-        currentItem.push_back(value[i]);
-      }
-    }
-
-    result.push_back(currentItem);
-  }
-
-
-
-  void ParseContentType(std::string& application,
-                        std::map<std::string, std::string>& attributes,
-                        const std::string& header)
-  {
-    application.clear();
-    attributes.clear();
-
-    std::vector<std::string> tokens;
-    TokenizeString(tokens, header, ';');
-
-    assert(tokens.size() > 0);
-    application = tokens[0];
-    StripSpaces(application);
-    ToLowerCase(application);
-
-    boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*");
-
-    for (size_t i = 1; i < tokens.size(); i++)
-    {
-      boost::cmatch what;
-      if (boost::regex_match(tokens[i].c_str(), what, pattern))
-      {
-        std::string key(what[1]);
-        std::string value(what[2]);
-        ToLowerCase(key);
-        attributes[key] = value;
-      }
-    }
-  }
-
-
-  bool LookupHttpHeader(std::string& value,
-                        const OrthancPluginHttpRequest* request,
-                        const std::string& header)
-  {
-    value.clear();
-
-    for (uint32_t i = 0; i < request->headersCount; i++)
-    {
-      std::string s = request->headersKeys[i];
-      ToLowerCase(s);
-      if (s == header)
-      {
-        value = request->headersValues[i];
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-
-
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary)
-  {
-    result.clear();
-
-    boost::regex header("\r?(\n?)--" + boundary + "(--|.*\r?\n\r?\n)");
-    boost::regex pattern(".*^Content-Type\\s*:\\s*([^\\s]*).*",
-                         boost::regex::icase /* case insensitive */);
-    
-    boost::cmatch what;
-    boost::match_flag_type flags = (boost::match_perl | 
-                                    boost::match_not_dot_null);
-    const char* start = body;
-    const char* end = body + bodySize;
-    std::string currentType;
-
-    while (boost::regex_search(start, end, what, header, flags))   
-    {
-      if (start != body)
-      {
-        MultipartItem item;
-        item.data_ = start;
-        item.size_ = what[0].first - start;
-        item.contentType_ = currentType;
-
-        result.push_back(item);
-      }
-
-      boost::cmatch contentType;
-      if (boost::regex_match(what[0].first, what[0].second, contentType, pattern))
-      {
-        currentType = contentType[1];
-      }
-      else
-      {
-        currentType.clear();
-      }
-    
-      start = what[0].second;
-      flags |= boost::match_prev_avail;
-    }
-  }
-
-
-  bool RestApiGetString(std::string& result,
-                        OrthancPluginContext* context,
-                        const std::string& uri)
-  {
-    OrthancPluginMemoryBuffer buffer;
-    int code = OrthancPluginRestApiGet(context, &buffer, uri.c_str());
-    if (code)
-    {
-      // Error
-      return false;
-    }
-
-    bool ok = true;
-
-    try
-    {
-      if (buffer.size)
-      {
-        result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size);
-      }
-      else
-      {
-        result.clear();
-      }
-    }
-    catch (std::bad_alloc&)
-    {
-      ok = false;
-    }
-
-    OrthancPluginFreeMemoryBuffer(context, &buffer);
-
-    return ok;
-  }
-
-
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri)
-  {
-    std::string content;
-    RestApiGetString(content, context, uri);
-    
-    Json::Reader reader;
-    return reader.parse(content, result);
-  }
-
-
-  std::string ConvertToAscii(const std::string& source)
-  {
-    std::string result;
-
-    result.reserve(source.size() + 1);
-    for (size_t i = 0; i < source.size(); i++)
-    {
-      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
-      {
-        result.push_back(source[i]);
-      }
-    }
-
-    return result;
-  }
-
-  
-  std::string ConvertToUtf8(const std::string& source,
-                            const Encoding sourceEncoding)
-  {
-    const char* encoding;
-
-    // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html
-    switch (sourceEncoding)
-    {
-      case Encoding_Utf8:
-        // Already in UTF-8: No conversion is required
-        return source;
-
-      case Encoding_Unknown:
-      case Encoding_Ascii:
-        return ConvertToAscii(source);
-
-      case Encoding_Latin1:
-        encoding = "ISO-8859-1";
-        break;
-
-      case Encoding_Latin2:
-        encoding = "ISO-8859-2";
-        break;
-
-      case Encoding_Latin3:
-        encoding = "ISO-8859-3";
-        break;
-
-      case Encoding_Latin4:
-        encoding = "ISO-8859-4";
-        break;
-
-      case Encoding_Latin5:
-        encoding = "ISO-8859-9";
-        break;
-
-      case Encoding_Cyrillic:
-        encoding = "ISO-8859-5";
-        break;
-
-      case Encoding_Arabic:
-        encoding = "ISO-8859-6";
-        break;
-
-      case Encoding_Greek:
-        encoding = "ISO-8859-7";
-        break;
-
-      case Encoding_Hebrew:
-        encoding = "ISO-8859-8";
-        break;
-        
-      case Encoding_Japanese:
-        encoding = "SHIFT-JIS";
-        break;
-
-      case Encoding_Chinese:
-        encoding = "GB18030";
-        break;
-
-      case Encoding_Thai:
-        encoding = "TIS620.2533-0";
-        break;
-
-      default:
-        throw std::runtime_error("Unsupported encoding");
-    }
-
-    try
-    {
-      return boost::locale::conv::to_utf<char>(source, encoding);
-    }
-    catch (std::runtime_error&)
-    {
-      // Bad input string or bad encoding
-      return ConvertToAscii(source);
-    }
-  }
-
-
-  Encoding GetDicomEncoding(const char* specificCharacterSet)
-  {
-    std::string s = specificCharacterSet;
-    ToUpperCase(s);
-
-    // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
-    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
-    if (s == "ISO_IR 6" ||
-        s == "ISO_IR 192" ||
-        s == "ISO 2022 IR 6")
-    {
-      return Encoding_Utf8;
-    }
-    else if (s == "ISO_IR 100" ||
-             s == "ISO 2022 IR 100")
-    {
-      return Encoding_Latin1;
-    }
-    else if (s == "ISO_IR 101" ||
-             s == "ISO 2022 IR 101")
-    {
-      return Encoding_Latin2;
-    }
-    else if (s == "ISO_IR 109" ||
-             s == "ISO 2022 IR 109")
-    {
-      return Encoding_Latin3;
-    }
-    else if (s == "ISO_IR 110" ||
-             s == "ISO 2022 IR 110")
-    {
-      return Encoding_Latin4;
-    }
-    else if (s == "ISO_IR 148" ||
-             s == "ISO 2022 IR 148")
-    {
-      return Encoding_Latin5;
-    }
-    else if (s == "ISO_IR 144" ||
-             s == "ISO 2022 IR 144")
-    {
-      return Encoding_Cyrillic;
-    }
-    else if (s == "ISO_IR 127" ||
-             s == "ISO 2022 IR 127")
-    {
-      return Encoding_Arabic;
-    }
-    else if (s == "ISO_IR 126" ||
-             s == "ISO 2022 IR 126")
-    {
-      return Encoding_Greek;
-    }
-    else if (s == "ISO_IR 138" ||
-             s == "ISO 2022 IR 138")
-    {
-      return Encoding_Hebrew;
-    }
-    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
-    {
-      return Encoding_Thai;
-    }
-    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
-    {
-      return Encoding_Japanese;
-    }
-    else if (s == "GB18030")
-    {
-      return Encoding_Chinese;
-    }
-    /*
-      else if (s == "ISO 2022 IR 149")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 159")
-      {
-      TODO
-      }
-      else if (s == "ISO 2022 IR 87")
-      {
-      TODO
-      }
-    */
-    else
-    {
-      return Encoding_Unknown;
-    }
-  }
-}
--- a/Core/Toolbox.h	Fri Jul 31 13:36:02 2015 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#include <orthanc/OrthancCPlugin.h>
-#include <string>
-#include <vector>
-#include <json/value.h>
-#include <map>
-
-namespace OrthancPlugins
-{
-  struct MultipartItem
-  {
-    const char*   data_;
-    size_t        size_;
-    std::string   contentType_;
-  };
-
-  // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
-  enum Encoding
-  {
-    Encoding_Unknown,
-    Encoding_Ascii,
-    Encoding_Utf8,
-    Encoding_Latin1,
-    Encoding_Latin2,
-    Encoding_Latin3,
-    Encoding_Latin4,
-    Encoding_Latin5,                        // Turkish
-    Encoding_Cyrillic,
-    Encoding_Arabic,
-    Encoding_Greek,
-    Encoding_Hebrew,
-    Encoding_Thai,                          // TIS 620-2533
-    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
-    Encoding_Chinese                        // GB18030 - Chinese simplified
-    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
-    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
-    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
-  };
-
-  void ToLowerCase(std::string& s);
-
-  void ToUpperCase(std::string& s);
-
-  std::string StripSpaces(const std::string& source);
-
-  void TokenizeString(std::vector<std::string>& result,
-                      const std::string& source,
-                      char separator);
-
-  void ParseContentType(std::string& application,
-                        std::map<std::string, std::string>& attributes,
-                        const std::string& header);
-
-  bool LookupHttpHeader(std::string& value,
-                        const OrthancPluginHttpRequest* request,
-                        const std::string& header);
-
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary);
-
-  bool RestApiGetString(std::string& result,
-                        OrthancPluginContext* context,
-                        const std::string& uri);
-
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri);
-
-  std::string ConvertToAscii(const std::string& source);
-
-  std::string ConvertToUtf8(const std::string& source,
-                            const Encoding sourceEncoding);
-
-  Encoding GetDicomEncoding(const char* specificCharacterSet);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ChunkedBuffer.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "ChunkedBuffer.h"
+
+#include <cassert>
+#include <string.h>
+
+
+namespace OrthancPlugins
+{
+  void ChunkedBuffer::Clear()
+  {
+    numBytes_ = 0;
+
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const char* chunkData,
+                               size_t chunkSize)
+  {
+    if (chunkSize == 0)
+    {
+      return;
+    }
+
+    assert(chunkData != NULL);
+    chunks_.push_back(new std::string(chunkData, chunkSize));
+    numBytes_ += chunkSize;
+  }
+
+
+  void ChunkedBuffer::AddChunk(const std::string& chunk)
+  {
+    if (chunk.size() > 0)
+    {
+      AddChunk(&chunk[0], chunk.size());
+    }
+  }
+
+
+  void ChunkedBuffer::Flatten(std::string& result)
+  {
+    result.resize(numBytes_);
+
+    size_t pos = 0;
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      assert(*it != NULL);
+
+      size_t s = (*it)->size();
+      if (s != 0)
+      {
+        memcpy(&result[pos], (*it)->c_str(), s);
+        pos += s;
+      }
+
+      delete *it;
+    }
+
+    chunks_.clear();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ChunkedBuffer.h	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <list>
+#include <string>
+
+namespace OrthancPlugins
+{
+  class ChunkedBuffer
+  {
+  private:
+    typedef std::list<std::string*>  Chunks;
+    size_t numBytes_;
+    Chunks chunks_;
+  
+    void Clear();
+
+  public:
+    ChunkedBuffer() : numBytes_(0)
+    {
+    }
+
+    ~ChunkedBuffer()
+    {
+      Clear();
+    }
+
+    size_t GetNumBytes() const
+    {
+      return numBytes_;
+    }
+
+    void AddChunk(const char* chunkData,
+                  size_t chunkSize);
+
+    void AddChunk(const std::string& chunk);
+
+    void Flatten(std::string& result);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Configuration.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,148 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Configuration.h"
+
+#include "Toolbox.h"
+
+#include <fstream>
+#include <json/reader.h>
+
+namespace OrthancPlugins
+{
+  namespace Configuration
+  {
+    bool Read(Json::Value& configuration,
+              OrthancPluginContext* context)
+    {
+      std::string s;
+
+      {
+        char* tmp = OrthancPluginGetConfiguration(context);
+        if (tmp == NULL)
+        {
+          OrthancPluginLogError(context, "Error while retrieving the configuration from Orthanc");
+          return false;
+        }
+
+        s.assign(tmp);
+        OrthancPluginFreeString(context, tmp);      
+      }
+
+      Json::Reader reader;
+      if (reader.parse(s, configuration))
+      {
+        return true;
+      }
+      else
+      {
+        OrthancPluginLogError(context, "Unable to parse the configuration");
+        return false;
+      }
+    }
+
+
+    std::string GetStringValue(const Json::Value& configuration,
+                               const std::string& key,
+                               const std::string& defaultValue)
+    {
+      if (configuration.type() != Json::objectValue ||
+          !configuration.isMember(key) ||
+          configuration[key].type() != Json::stringValue)
+      {
+        return defaultValue;
+      }
+      else
+      {
+        return configuration[key].asString();
+      }
+    }
+
+
+    bool GetBoolValue(const Json::Value& configuration,
+                      const std::string& key,
+                      bool defaultValue)
+    {
+      if (configuration.type() != Json::objectValue ||
+          !configuration.isMember(key) ||
+          configuration[key].type() != Json::booleanValue)
+      {
+        return defaultValue;
+      }
+      else
+      {
+        return configuration[key].asBool();
+      }
+    }
+
+
+    std::string GetRoot(const Json::Value& configuration)
+    {
+      std::string root;
+
+      if (configuration.isMember("DicomWeb"))
+      {
+        root = GetStringValue(configuration["DicomWeb"], "Root", "");
+      }
+
+      if (root.empty())
+      {
+        root = "/dicom-web/";
+      }
+
+      // Make sure the root URI starts and ends with a slash
+      if (root[0] != '/')
+      {
+        root = "/" + root;
+      }
+    
+      if (root[root.length() - 1] != '/')
+      {
+        root += "/";
+      }
+
+      return root;
+    }
+
+
+    std::string  GetBaseUrl(const Json::Value& configuration,
+                            const OrthancPluginHttpRequest* request)
+    {
+      std::string host;
+      bool ssl = false;
+
+      if (configuration.isMember("DicomWeb"))
+      {
+        host = GetStringValue(configuration["DicomWeb"], "Host", "");
+        ssl = GetBoolValue(configuration["DicomWeb"], "Ssl", false);
+      }
+
+      if (host.empty() &&
+          !LookupHttpHeader(host, request, "host"))
+      {
+        // Should never happen: The "host" header should always be present
+        // in HTTP requests. Provide a default value anyway.
+        host = "localhost:8042";
+      }
+
+      return (ssl ? "https://" : "http://") + host + GetRoot(configuration);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Configuration.h	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,46 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+#include <json/value.h>
+
+namespace OrthancPlugins
+{
+  namespace Configuration
+  {
+    bool Read(Json::Value& configuration,
+              OrthancPluginContext* context);
+
+    std::string GetStringValue(const Json::Value& configuration,
+                               const std::string& key,
+                               const std::string& defaultValue);
+    
+    bool GetBoolValue(const Json::Value& configuration,
+                      const std::string& key,
+                      bool defaultValue);
+
+    std::string GetRoot(const Json::Value& configuration);
+
+    std::string GetBaseUrl(const Json::Value& configuration,
+                           const OrthancPluginHttpRequest* request);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Dicom.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,563 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Dicom.h"
+
+#include "ChunkedBuffer.h"
+
+#include <gdcmDictEntry.h>
+#include <gdcmStringFilter.h>
+#include <boost/lexical_cast.hpp>
+#include <json/writer.h>
+
+namespace OrthancPlugins
+{
+  namespace
+  {
+    class ChunkedBufferWriter : public pugi::xml_writer
+    {
+    private:
+      ChunkedBuffer buffer_;
+
+    public:
+      virtual void write(const void *data, size_t size)
+      {
+        if (size > 0)
+        {
+          buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
+        }
+      }
+
+      void Flatten(std::string& s)
+      {
+        buffer_.Flatten(s);
+      }
+    };
+  }
+
+
+
+  void ParsedDicomFile::Setup(const std::string& dicom)
+  {
+    // Prepare a memory stream over the DICOM instance
+    std::stringstream stream(dicom);
+
+    // Parse the DICOM instance using GDCM
+    reader_.SetStream(stream);
+    if (!reader_.Read())
+    {
+      throw std::runtime_error("GDCM cannot read this DICOM instance of length " + 
+                               boost::lexical_cast<std::string>(dicom.size()));
+    }
+  }
+
+
+  ParsedDicomFile::ParsedDicomFile(const OrthancPlugins::MultipartItem& item)
+  {
+    std::string dicom(item.data_, item.data_ + item.size_);
+    Setup(dicom);
+  }
+
+
+  static bool GetTag(std::string& result,
+                     const gdcm::DataSet& dataset,
+                     const gdcm::Tag& tag,
+                     bool stripSpaces)
+  {
+    if (dataset.FindDataElement(tag))
+    {
+      const gdcm::ByteValue* value = dataset.GetDataElement(tag).GetByteValue();
+      if (value)
+      {
+        result = std::string(value->GetPointer(), value->GetLength());
+
+        if (stripSpaces)
+        {
+          result = OrthancPlugins::StripSpaces(result);
+        }
+
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  static std::string GetTagWithDefault(const gdcm::DataSet& dataset,
+                                       const gdcm::Tag& tag,
+                                       const std::string& defaultValue,
+                                       bool stripSpaces)
+  {
+    std::string result;
+    if (!GetTag(result, dataset, tag, false))
+    {
+      result = defaultValue;
+    }
+
+    if (stripSpaces)
+    {
+      result = StripSpaces(result);
+    }
+
+    return result;
+  }
+
+
+  bool ParsedDicomFile::GetTag(std::string& result,
+                               const gdcm::Tag& tag,
+                               bool stripSpaces) const
+  {
+    return OrthancPlugins::GetTag(result, GetDataSet(), tag, stripSpaces);
+  }
+
+
+  std::string ParsedDicomFile::GetTagWithDefault(const gdcm::Tag& tag,
+                                                 const std::string& defaultValue,
+                                                 bool stripSpaces) const
+  {
+    return OrthancPlugins::GetTagWithDefault(GetDataSet(), tag, defaultValue, stripSpaces);
+  }
+
+
+  static std::string FormatTag(const gdcm::Tag& tag)
+  {
+    char tmp[16];
+    sprintf(tmp, "%04X%04X", tag.GetGroup(), tag.GetElement());
+    return std::string(tmp);
+  }
+
+
+  static const char* GetKeyword(const gdcm::Dict& dictionary,
+                                const gdcm::Tag& tag)
+  {
+    const gdcm::DictEntry &entry = dictionary.GetDictEntry(tag);
+    const char* keyword = entry.GetKeyword();
+
+    if (strlen(keyword) != 0)
+    {
+      return keyword;
+    }
+
+    if (tag == DICOM_TAG_RETRIEVE_URL)
+    {
+      return "RetrieveURL";
+    }
+
+    //throw std::runtime_error("Unknown keyword for tag: " + FormatTag(tag));
+    return NULL;
+  }
+
+
+
+  static const char* GetVRName(bool& isSequence,
+                               const gdcm::Dict& dictionary,
+                               const gdcm::DataElement& element)
+  {
+    gdcm::VR vr = element.GetVR();
+    if (vr == gdcm::VR::INVALID)
+    {
+      const gdcm::DictEntry &entry = dictionary.GetDictEntry(element.GetTag());
+      vr = entry.GetVR();
+
+      if (vr == gdcm::VR::OB_OW)
+      {
+        vr = gdcm::VR::OB;
+      }
+    }
+
+    isSequence = (vr == gdcm::VR::SQ);
+
+    const char* str = gdcm::VR::GetVRString(vr);
+    if (isSequence)
+    {
+      return str;
+    }
+
+    if (str == NULL ||
+        strlen(str) != 2 ||
+        !(str[0] >= 'A' && str[0] <= 'Z') ||
+        !(str[1] >= 'A' && str[1] <= 'Z'))
+    {
+      return "UN";
+    }
+    else
+    {
+      return str;
+    }
+  }
+
+
+  static bool IsBulkData(const std::string& vr)
+  {
+    /**
+     * Full list of VR (Value Representations) that are admissible for
+     * being retrieved as bulk data. We commented out some of them, as
+     * they correspond to strings and not to binary data.
+     **/
+    return (//vr == "FL" ||
+            //vr == "FD" ||
+            //vr == "IS" ||
+            vr == "LT" ||
+            vr == "OB" ||
+            vr == "OD" ||
+            vr == "OF" ||
+            vr == "OW" ||
+            //vr == "SL" ||
+            //vr == "SS" ||
+            //vr == "ST" ||
+            //vr == "UL" ||
+            vr == "UN" ||
+            //vr == "US" ||
+            vr == "UT");
+  }
+
+
+  static std::string GetBulkUriRoot(const std::string& wadoBase,
+                                    const gdcm::DataSet& dicom)
+  {
+    std::string study, series, instance;
+
+    if (!GetTag(study, dicom, DICOM_TAG_STUDY_INSTANCE_UID, true) ||
+        !GetTag(series, dicom, DICOM_TAG_SERIES_INSTANCE_UID, true) ||
+        !GetTag(instance, dicom, DICOM_TAG_SOP_INSTANCE_UID, true))
+    {
+      return "";
+    }
+    else
+    {
+      return wadoBase + "studies/" + study + "/series/" + series + "/instances/" + instance + "/bulk/";
+    }
+  }
+
+
+  static Encoding DetectEncoding(const gdcm::DataSet& dicom)
+  {
+    if (!dicom.FindDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET))
+    {
+      return Encoding_Ascii;
+    }
+
+    const gdcm::DataElement& element = 
+      dicom.GetDataElement(DICOM_TAG_SPECIFIC_CHARACTER_SET);
+
+    const gdcm::ByteValue* data = element.GetByteValue();
+    if (!data)
+    {
+      return Encoding_Unknown;
+    }
+
+    std::string tmp(data->GetPointer(), data->GetLength());
+    tmp = StripSpaces(tmp);
+
+    return GetDicomEncoding(tmp.c_str());
+  }
+
+
+  static bool ConvertDicomStringToUf8(std::string& result,
+                                      const gdcm::Dict& dictionary,
+                                      const gdcm::File* file,
+                                      const gdcm::DataElement& element,
+                                      const Encoding sourceEncoding)
+  {
+    const gdcm::ByteValue* data = element.GetByteValue();
+    if (!data)
+    {
+      return false;
+    }
+
+    if (file != NULL)
+    {
+      bool isSequence;
+      std::string vr = GetVRName(isSequence, dictionary, element);
+      if (!isSequence && (
+            vr == "FL" ||
+            vr == "FD" ||
+            vr == "SL" ||
+            vr == "SS" ||
+            vr == "UL" ||
+            vr == "US"
+            ))
+      {      
+        gdcm::StringFilter f;
+        f.SetFile(*file);
+        result = f.ToString(element.GetTag());
+        return true;
+      }
+    }
+
+    if (sourceEncoding == Encoding_Utf8)
+    {
+      result.assign(data->GetPointer(), data->GetLength());
+    }
+    else
+    {
+      std::string tmp(data->GetPointer(), data->GetLength());
+      result = ConvertToUtf8(tmp, sourceEncoding);
+    }
+
+    result = StripSpaces(result);
+    return true;
+  }
+
+
+  static void DicomToXmlInternal(pugi::xml_node& target,
+                                 const gdcm::Dict& dictionary,
+                                 const gdcm::File* file,
+                                 const gdcm::DataSet& dicom,
+                                 const Encoding sourceEncoding,
+                                 const std::string& bulkUri)
+  {
+    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
+         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
+    {
+      char path[16];
+      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
+
+      pugi::xml_node node = target.append_child("DicomAttribute");
+      node.append_attribute("tag").set_value(FormatTag(it->GetTag()).c_str());
+
+      const char* keyword = GetKeyword(dictionary, it->GetTag());
+      if (keyword != NULL)
+      {
+        node.append_attribute("keyword").set_value(keyword);
+      }
+
+      bool isSequence = false;
+      std::string vr;
+      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
+      {
+        // The VR of this attribute has changed from UT to UR.
+        vr = "UR";
+      }
+      else
+      {
+        vr = GetVRName(isSequence, dictionary, *it);
+      }
+
+      node.append_attribute("vr").set_value(vr.c_str());
+
+      if (isSequence)
+      {
+        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
+        if (seq.GetPointer() != NULL)
+        {
+          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
+          {
+            pugi::xml_node item = node.append_child("Item");
+            std::string number = boost::lexical_cast<std::string>(i);
+            item.append_attribute("number").set_value(number.c_str());
+
+            std::string childUri;
+            if (!bulkUri.empty())
+            {
+              childUri = bulkUri + std::string(path) + "/" + number + "/";
+            }
+
+            DicomToXmlInternal(item, dictionary, file, seq->GetItem(i).GetNestedDataSet(), sourceEncoding, childUri);
+          }
+        }
+      }
+      else if (IsBulkData(vr))
+      {
+        // Bulk data
+        if (!bulkUri.empty())
+        {
+          pugi::xml_node value = node.append_child("BulkData");
+          std::string uri = bulkUri + std::string(path);
+          value.append_attribute("uri").set_value(uri.c_str());
+        }
+      }
+      else
+      {
+        // Deal with other value representations
+        pugi::xml_node value = node.append_child("Value");
+        value.append_attribute("number").set_value("1");
+
+        std::string tmp;
+        if (ConvertDicomStringToUf8(tmp, dictionary, file, *it, sourceEncoding)) 
+        {
+          value.append_child(pugi::node_pcdata).set_value(tmp.c_str());
+        }
+      }
+    }
+  }
+
+
+  static void DicomToXml(pugi::xml_document& target,
+                         const gdcm::Dict& dictionary,
+                         const gdcm::File* file,
+                         const gdcm::DataSet& dicom,
+                         const std::string& bulkUriRoot)
+  {
+    pugi::xml_node root = target.append_child("NativeDicomModel");
+    root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
+    root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM");
+    root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance");
+
+    Encoding encoding = DetectEncoding(dicom);
+    DicomToXmlInternal(root, dictionary, file, dicom, encoding, bulkUriRoot);
+
+    pugi::xml_node decl = target.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("utf-8");
+  }
+
+
+  static void DicomToJsonInternal(Json::Value& target,
+                                  const gdcm::Dict& dictionary,
+                                  const gdcm::File* file,
+                                  const gdcm::DataSet& dicom,
+                                  const std::string& bulkUri,
+                                  Encoding sourceEncoding)
+  {
+    target = Json::objectValue;
+
+    for (gdcm::DataSet::ConstIterator it = dicom.Begin();
+         it != dicom.End(); ++it)  // "*it" represents a "gdcm::DataElement"
+    {
+      char path[16];
+      sprintf(path, "%04x%04x", it->GetTag().GetGroup(), it->GetTag().GetElement());
+
+      Json::Value node = Json::objectValue;
+
+      bool isSequence = false;
+      std::string vr;
+      if (it->GetTag() == DICOM_TAG_RETRIEVE_URL)
+      {
+        // The VR of this attribute has changed from UT to UR.
+        vr = "UR";
+      }
+      else
+      {
+        vr = GetVRName(isSequence, dictionary, *it);
+      }
+
+      node["vr"] = vr.c_str();
+
+      if (isSequence)
+      {
+        // Deal with sequences
+        node["Value"] = Json::arrayValue;
+
+        gdcm::SmartPointer<gdcm::SequenceOfItems> seq = it->GetValueAsSQ();
+        if (seq.GetPointer() != NULL)
+        {
+          for (gdcm::SequenceOfItems::SizeType i = 1; i <= seq->GetNumberOfItems(); i++)
+          {
+            Json::Value child;
+
+            std::string childUri;
+            if (!bulkUri.empty())
+            {
+              std::string number = boost::lexical_cast<std::string>(i);
+              childUri = bulkUri + std::string(path) + "/" + number + "/";
+            }
+
+            DicomToJsonInternal(child, dictionary, file, seq->GetItem(i).GetNestedDataSet(), childUri, sourceEncoding);
+            node["Value"].append(child);
+          }
+        }
+      }
+      else if (IsBulkData(vr))
+      {
+        // Bulk data
+        if (!bulkUri.empty())
+        {
+          node["BulkDataURI"] = bulkUri + std::string(path);
+        }
+      }
+      else
+      {
+        // Deal with other value representations
+        node["Value"] = Json::arrayValue;
+
+        std::string value;
+        if (ConvertDicomStringToUf8(value, dictionary, file, *it, sourceEncoding)) 
+        {
+          node["Value"].append(value.c_str());
+        }
+      }
+
+      target[FormatTag(it->GetTag())] = node;
+    }
+  }
+
+
+  static void DicomToJson(Json::Value& target,
+                          const gdcm::Dict& dictionary,
+                          const gdcm::File* file,
+                          const gdcm::DataSet& dicom,
+                          const std::string& bulkUriRoot)
+  {
+    Encoding encoding = DetectEncoding(dicom);
+    DicomToJsonInternal(target, dictionary, file, dicom, bulkUriRoot, encoding);
+  }
+
+
+  void GenerateSingleDicomAnswer(std::string& result,
+                                 const std::string& wadoBase,
+                                 const gdcm::Dict& dictionary,
+                                 const gdcm::File* file,  // Can be NULL
+                                 const gdcm::DataSet& dicom,
+                                 bool isXml,
+                                 bool isBulkAccessible)
+  {
+    std::string bulkUriRoot;
+    if (isBulkAccessible)
+    {
+      bulkUriRoot = GetBulkUriRoot(wadoBase, dicom);
+    }
+
+    if (isXml)
+    {
+      pugi::xml_document doc;
+      DicomToXml(doc, dictionary, file, dicom, bulkUriRoot);
+    
+      ChunkedBufferWriter writer;
+      doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
+
+      writer.Flatten(result);
+    }
+    else
+    {
+      Json::Value v;
+      DicomToJson(v, dictionary, file, dicom, bulkUriRoot);
+
+      Json::FastWriter writer;
+      result = writer.write(v); 
+    }
+  }
+
+
+  void AnswerDicom(OrthancPluginContext* context,
+                   OrthancPluginRestOutput* output,
+                   const std::string& wadoBase,
+                   const gdcm::Dict& dictionary,
+                   const gdcm::DataSet& dicom,
+                   bool isXml,
+                   bool isBulkAccessible)
+  {
+    std::string answer;
+    GenerateSingleDicomAnswer(answer, wadoBase, dictionary, NULL, dicom, isXml, isBulkAccessible);
+    OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), 
+                              isXml ? "application/dicom+xml" : "application/json");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Dicom.h	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,98 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "Toolbox.h"
+
+#include <gdcmReader.h>
+#include <gdcmDataSet.h>
+#include <pugixml.hpp>
+#include <gdcmDict.h>
+#include <list>
+
+
+namespace OrthancPlugins
+{
+  static const gdcm::Tag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const gdcm::Tag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const gdcm::Tag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+  static const gdcm::Tag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
+  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
+  static const gdcm::Tag DICOM_TAG_RETRIEVE_URL(0x0008, 0x1190);
+  static const gdcm::Tag DICOM_TAG_FAILED_SOP_SEQUENCE(0x0008, 0x1198);
+  static const gdcm::Tag DICOM_TAG_FAILURE_REASON(0x0008, 0x1197);
+  static const gdcm::Tag DICOM_TAG_WARNING_REASON(0x0008, 0x1196);
+  static const gdcm::Tag DICOM_TAG_REFERENCED_SOP_SEQUENCE(0x0008, 0x1199);
+  static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
+  static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+
+  class ParsedDicomFile
+  {
+  private:
+    gdcm::Reader reader_;
+
+    void Setup(const std::string& dicom);
+
+  public:
+    ParsedDicomFile(const OrthancPlugins::MultipartItem& item);
+
+    ParsedDicomFile(const std::string& dicom)
+    {
+      Setup(dicom);
+    }
+
+    const gdcm::File& GetFile() const
+    {
+      return reader_.GetFile();
+    }
+
+    const gdcm::DataSet& GetDataSet() const
+    {
+      return reader_.GetFile().GetDataSet();
+    }
+
+    bool GetTag(std::string& result,
+                const gdcm::Tag& tag,
+                bool stripSpaces) const;
+
+    std::string GetTagWithDefault(const gdcm::Tag& tag,
+                                  const std::string& defaultValue,
+                                  bool stripSpaces) const;
+  };
+
+
+  void GenerateSingleDicomAnswer(std::string& result,
+                                 const std::string& wadoBase,
+                                 const gdcm::Dict& dictionary,
+                                 const gdcm::File* file,  // Can be NULL
+                                 const gdcm::DataSet& dicom,
+                                 bool isXml,
+                                 bool isBulkAccessible);
+
+  void AnswerDicom(OrthancPluginContext* context,
+                   OrthancPluginRestOutput* output,
+                   const std::string& wadoBase,
+                   const gdcm::Dict& dictionary,
+                   const gdcm::DataSet& dicom,
+                   bool isXml,
+                   bool isBulkAccessible);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomResults.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "DicomResults.h"
+
+#include "Dicom.h"
+
+namespace OrthancPlugins
+{
+  DicomResults::DicomResults(OrthancPluginContext* context,
+                             OrthancPluginRestOutput* output,
+                             const std::string& wadoBase,
+                             const gdcm::Dict& dictionary,
+                             bool isXml,
+                             bool isBulkAccessible) :
+    context_(context),
+    output_(output),
+    wadoBase_(wadoBase),
+    dictionary_(dictionary),
+    isFirst_(true),
+    isXml_(isXml),
+    isBulkAccessible_(isBulkAccessible)
+  {
+    if (isXml_ &&
+        OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0)
+    {
+      throw std::runtime_error("Unable to create a multipart stream of DICOM+XML answers");
+    }
+
+    jsonWriter_.AddChunk("[\n");
+  }
+
+
+  void DicomResults::AddInternal(const gdcm::File* file,
+                                 const gdcm::DataSet& dicom)
+  {
+    if (isXml_)
+    {
+      std::string answer;
+      GenerateSingleDicomAnswer(answer, wadoBase_, dictionary_, file, dicom, true, isBulkAccessible_);
+
+      if (OrthancPluginSendMultipartItem(context_, output_, answer.c_str(), answer.size()) != 0)
+      {
+        throw std::runtime_error("Unable to write an item to a multipart stream of DICOM+XML answers");
+      }
+    }
+    else
+    {
+      if (!isFirst_)
+      {
+        jsonWriter_.AddChunk(",\n");
+      }
+
+      std::string item;
+      GenerateSingleDicomAnswer(item, wadoBase_, dictionary_, file, dicom, false, isBulkAccessible_);
+      jsonWriter_.AddChunk(item);
+    }
+
+    isFirst_ = false;
+  }
+
+  void DicomResults::Answer()
+  {
+    if (isXml_)
+    {
+      // Nothing to do in this case
+    }
+    else
+    {
+      jsonWriter_.AddChunk("]\n");
+
+      std::string answer;
+      jsonWriter_.Flatten(answer);
+      OrthancPluginAnswerBuffer(context_, output_, answer.c_str(), answer.size(), "application/json");
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DicomResults.h	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ChunkedBuffer.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <gdcmDataSet.h>
+#include <gdcmDict.h>
+#include <gdcmFile.h>
+
+namespace OrthancPlugins
+{
+  class DicomResults
+  {
+  private:
+    OrthancPluginContext*     context_;
+    OrthancPluginRestOutput*  output_;
+    std::string               wadoBase_;
+    const gdcm::Dict&         dictionary_;
+    ChunkedBuffer             jsonWriter_;  // Used for JSON output
+    bool                      isFirst_; 
+    bool                      isXml_;
+    bool                      isBulkAccessible_;
+
+    void AddInternal(const gdcm::File* file,
+                     const gdcm::DataSet& dicom);
+
+  public:
+    DicomResults(OrthancPluginContext* context,
+                 OrthancPluginRestOutput* output,
+                 const std::string& wadoBase,
+                 const gdcm::Dict& dictionary,
+                 bool isXml,
+                 bool isBulkAccessible);
+
+    void Add(const gdcm::File& file)
+    {
+      AddInternal(&file, file.GetDataSet());
+    }
+
+    void Add(const gdcm::File& file,
+             const gdcm::DataSet& subset)
+    {
+      AddInternal(&file, subset);
+    }
+
+    void Answer();
+  };
+}
--- a/Plugin/Plugin.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ b/Plugin/Plugin.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -23,7 +23,7 @@
 #include "QidoRs.h"
 #include "StowRs.h"
 #include "WadoRs.h"
-#include "../Core/Configuration.h"
+#include "Configuration.h"
 
 
 #include <gdcmDictEntry.h>
--- a/Plugin/QidoRs.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ b/Plugin/QidoRs.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -22,10 +22,10 @@
 
 #include "Plugin.h"
 #include "StowRs.h"  // For IsXmlExpected()
-#include "../Core/Dicom.h"
-#include "../Core/DicomResults.h"
-#include "../Core/Toolbox.h"
-#include "../Core/Configuration.h"
+#include "Dicom.h"
+#include "DicomResults.h"
+#include "Toolbox.h"
+#include "Configuration.h"
 
 #include <gdcmTag.h>
 #include <list>
--- a/Plugin/StowRs.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ b/Plugin/StowRs.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -21,8 +21,8 @@
 #include "StowRs.h"
 #include "Plugin.h"
 
-#include "../Core/Configuration.h"
-#include "../Core/Dicom.h"
+#include "Configuration.h"
+#include "Dicom.h"
 
 
 static void SetTag(gdcm::DataSet& dataset,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Toolbox.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,433 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Toolbox.h"
+
+#include <string>
+#include <algorithm>
+#include <boost/regex.hpp>
+#include <boost/locale.hpp>
+#include <json/reader.h>
+
+
+namespace OrthancPlugins
+{
+  void ToLowerCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), ::tolower);
+  }
+
+
+  void ToUpperCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), ::toupper);
+  }
+
+
+  std::string StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           (isspace(source[last - 1]) ||
+            source[last - 1] == '\0'))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+  
+
+  void TokenizeString(std::vector<std::string>& result,
+                      const std::string& value,
+                      char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
+
+
+  void ParseContentType(std::string& application,
+                        std::map<std::string, std::string>& attributes,
+                        const std::string& header)
+  {
+    application.clear();
+    attributes.clear();
+
+    std::vector<std::string> tokens;
+    TokenizeString(tokens, header, ';');
+
+    assert(tokens.size() > 0);
+    application = tokens[0];
+    StripSpaces(application);
+    ToLowerCase(application);
+
+    boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*");
+
+    for (size_t i = 1; i < tokens.size(); i++)
+    {
+      boost::cmatch what;
+      if (boost::regex_match(tokens[i].c_str(), what, pattern))
+      {
+        std::string key(what[1]);
+        std::string value(what[2]);
+        ToLowerCase(key);
+        attributes[key] = value;
+      }
+    }
+  }
+
+
+  bool LookupHttpHeader(std::string& value,
+                        const OrthancPluginHttpRequest* request,
+                        const std::string& header)
+  {
+    value.clear();
+
+    for (uint32_t i = 0; i < request->headersCount; i++)
+    {
+      std::string s = request->headersKeys[i];
+      ToLowerCase(s);
+      if (s == header)
+      {
+        value = request->headersValues[i];
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+
+  void ParseMultipartBody(std::vector<MultipartItem>& result,
+                          const char* body,
+                          const uint64_t bodySize,
+                          const std::string& boundary)
+  {
+    result.clear();
+
+    boost::regex header("\r?(\n?)--" + boundary + "(--|.*\r?\n\r?\n)");
+    boost::regex pattern(".*^Content-Type\\s*:\\s*([^\\s]*).*",
+                         boost::regex::icase /* case insensitive */);
+    
+    boost::cmatch what;
+    boost::match_flag_type flags = (boost::match_perl | 
+                                    boost::match_not_dot_null);
+    const char* start = body;
+    const char* end = body + bodySize;
+    std::string currentType;
+
+    while (boost::regex_search(start, end, what, header, flags))   
+    {
+      if (start != body)
+      {
+        MultipartItem item;
+        item.data_ = start;
+        item.size_ = what[0].first - start;
+        item.contentType_ = currentType;
+
+        result.push_back(item);
+      }
+
+      boost::cmatch contentType;
+      if (boost::regex_match(what[0].first, what[0].second, contentType, pattern))
+      {
+        currentType = contentType[1];
+      }
+      else
+      {
+        currentType.clear();
+      }
+    
+      start = what[0].second;
+      flags |= boost::match_prev_avail;
+    }
+  }
+
+
+  bool RestApiGetString(std::string& result,
+                        OrthancPluginContext* context,
+                        const std::string& uri)
+  {
+    OrthancPluginMemoryBuffer buffer;
+    int code = OrthancPluginRestApiGet(context, &buffer, uri.c_str());
+    if (code)
+    {
+      // Error
+      return false;
+    }
+
+    bool ok = true;
+
+    try
+    {
+      if (buffer.size)
+      {
+        result.assign(reinterpret_cast<const char*>(buffer.data), buffer.size);
+      }
+      else
+      {
+        result.clear();
+      }
+    }
+    catch (std::bad_alloc&)
+    {
+      ok = false;
+    }
+
+    OrthancPluginFreeMemoryBuffer(context, &buffer);
+
+    return ok;
+  }
+
+
+  bool RestApiGetJson(Json::Value& result,
+                      OrthancPluginContext* context,
+                      const std::string& uri)
+  {
+    std::string content;
+    RestApiGetString(content, context, uri);
+    
+    Json::Reader reader;
+    return reader.parse(content, result);
+  }
+
+
+  std::string ConvertToAscii(const std::string& source)
+  {
+    std::string result;
+
+    result.reserve(source.size() + 1);
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
+      {
+        result.push_back(source[i]);
+      }
+    }
+
+    return result;
+  }
+
+  
+  std::string ConvertToUtf8(const std::string& source,
+                            const Encoding sourceEncoding)
+  {
+    const char* encoding;
+
+    // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html
+    switch (sourceEncoding)
+    {
+      case Encoding_Utf8:
+        // Already in UTF-8: No conversion is required
+        return source;
+
+      case Encoding_Unknown:
+      case Encoding_Ascii:
+        return ConvertToAscii(source);
+
+      case Encoding_Latin1:
+        encoding = "ISO-8859-1";
+        break;
+
+      case Encoding_Latin2:
+        encoding = "ISO-8859-2";
+        break;
+
+      case Encoding_Latin3:
+        encoding = "ISO-8859-3";
+        break;
+
+      case Encoding_Latin4:
+        encoding = "ISO-8859-4";
+        break;
+
+      case Encoding_Latin5:
+        encoding = "ISO-8859-9";
+        break;
+
+      case Encoding_Cyrillic:
+        encoding = "ISO-8859-5";
+        break;
+
+      case Encoding_Arabic:
+        encoding = "ISO-8859-6";
+        break;
+
+      case Encoding_Greek:
+        encoding = "ISO-8859-7";
+        break;
+
+      case Encoding_Hebrew:
+        encoding = "ISO-8859-8";
+        break;
+        
+      case Encoding_Japanese:
+        encoding = "SHIFT-JIS";
+        break;
+
+      case Encoding_Chinese:
+        encoding = "GB18030";
+        break;
+
+      case Encoding_Thai:
+        encoding = "TIS620.2533-0";
+        break;
+
+      default:
+        throw std::runtime_error("Unsupported encoding");
+    }
+
+    try
+    {
+      return boost::locale::conv::to_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  Encoding GetDicomEncoding(const char* specificCharacterSet)
+  {
+    std::string s = specificCharacterSet;
+    ToUpperCase(s);
+
+    // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
+    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
+    if (s == "ISO_IR 6" ||
+        s == "ISO_IR 192" ||
+        s == "ISO 2022 IR 6")
+    {
+      return Encoding_Utf8;
+    }
+    else if (s == "ISO_IR 100" ||
+             s == "ISO 2022 IR 100")
+    {
+      return Encoding_Latin1;
+    }
+    else if (s == "ISO_IR 101" ||
+             s == "ISO 2022 IR 101")
+    {
+      return Encoding_Latin2;
+    }
+    else if (s == "ISO_IR 109" ||
+             s == "ISO 2022 IR 109")
+    {
+      return Encoding_Latin3;
+    }
+    else if (s == "ISO_IR 110" ||
+             s == "ISO 2022 IR 110")
+    {
+      return Encoding_Latin4;
+    }
+    else if (s == "ISO_IR 148" ||
+             s == "ISO 2022 IR 148")
+    {
+      return Encoding_Latin5;
+    }
+    else if (s == "ISO_IR 144" ||
+             s == "ISO 2022 IR 144")
+    {
+      return Encoding_Cyrillic;
+    }
+    else if (s == "ISO_IR 127" ||
+             s == "ISO 2022 IR 127")
+    {
+      return Encoding_Arabic;
+    }
+    else if (s == "ISO_IR 126" ||
+             s == "ISO 2022 IR 126")
+    {
+      return Encoding_Greek;
+    }
+    else if (s == "ISO_IR 138" ||
+             s == "ISO 2022 IR 138")
+    {
+      return Encoding_Hebrew;
+    }
+    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
+    {
+      return Encoding_Thai;
+    }
+    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
+    {
+      return Encoding_Japanese;
+    }
+    else if (s == "GB18030")
+    {
+      return Encoding_Chinese;
+    }
+    /*
+      else if (s == "ISO 2022 IR 149")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 159")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 87")
+      {
+      TODO
+      }
+    */
+    else
+    {
+      return Encoding_Unknown;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Toolbox.h	Sat Aug 01 17:18:21 2015 +0200
@@ -0,0 +1,98 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <orthanc/OrthancCPlugin.h>
+#include <string>
+#include <vector>
+#include <json/value.h>
+#include <map>
+
+namespace OrthancPlugins
+{
+  struct MultipartItem
+  {
+    const char*   data_;
+    size_t        size_;
+    std::string   contentType_;
+  };
+
+  // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
+  enum Encoding
+  {
+    Encoding_Unknown,
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1,
+    Encoding_Latin2,
+    Encoding_Latin3,
+    Encoding_Latin4,
+    Encoding_Latin5,                        // Turkish
+    Encoding_Cyrillic,
+    Encoding_Arabic,
+    Encoding_Greek,
+    Encoding_Hebrew,
+    Encoding_Thai,                          // TIS 620-2533
+    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
+    Encoding_Chinese                        // GB18030 - Chinese simplified
+    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
+    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
+    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
+  };
+
+  void ToLowerCase(std::string& s);
+
+  void ToUpperCase(std::string& s);
+
+  std::string StripSpaces(const std::string& source);
+
+  void TokenizeString(std::vector<std::string>& result,
+                      const std::string& source,
+                      char separator);
+
+  void ParseContentType(std::string& application,
+                        std::map<std::string, std::string>& attributes,
+                        const std::string& header);
+
+  bool LookupHttpHeader(std::string& value,
+                        const OrthancPluginHttpRequest* request,
+                        const std::string& header);
+
+  void ParseMultipartBody(std::vector<MultipartItem>& result,
+                          const char* body,
+                          const uint64_t bodySize,
+                          const std::string& boundary);
+
+  bool RestApiGetString(std::string& result,
+                        OrthancPluginContext* context,
+                        const std::string& uri);
+
+  bool RestApiGetJson(Json::Value& result,
+                      OrthancPluginContext* context,
+                      const std::string& uri);
+
+  std::string ConvertToAscii(const std::string& source);
+
+  std::string ConvertToUtf8(const std::string& source,
+                            const Encoding sourceEncoding);
+
+  Encoding GetDicomEncoding(const char* specificCharacterSet);
+}
--- a/Plugin/WadoRs.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ b/Plugin/WadoRs.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -22,9 +22,9 @@
 
 #include <boost/lexical_cast.hpp>
 
-#include "../Core/Configuration.h"
-#include "../Core/Dicom.h"
-#include "../Core/DicomResults.h"
+#include "Configuration.h"
+#include "Dicom.h"
+#include "DicomResults.h"
 
 static bool AcceptMultipartDicom(const OrthancPluginHttpRequest* request)
 {
--- a/UnitTestsSources/UnitTestsMain.cpp	Fri Jul 31 13:36:02 2015 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Sat Aug 01 17:18:21 2015 +0200
@@ -21,7 +21,7 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "../Core/Toolbox.h"
+#include "../Plugin/Toolbox.h"
 #include "../Plugin/Plugin.h"
 
 using namespace OrthancPlugins;