changeset 3202:ef4d86d05503

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 06 Feb 2019 15:21:32 +0100
parents b69fe409cb4d
children 810772486249
files Core/DicomParsing/DicomWebJsonVisitor.cpp Core/DicomParsing/DicomWebJsonVisitor.h Resources/CMake/OrthancFrameworkConfiguration.cmake UnitTestsSources/DicomMapTests.cpp
diffstat 4 files changed, 739 insertions(+), 631 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomWebJsonVisitor.cpp	Wed Feb 06 15:21:32 2019 +0100
@@ -0,0 +1,573 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "DicomWebJsonVisitor.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "FromDcmtkBridge.h"
+
+#include <boost/math/special_functions/round.hpp>
+#include <boost/lexical_cast.hpp>
+
+
+static const char* const KEY_ALPHABETIC = "Alphabetic";
+static const char* const KEY_BULK_DATA_URI = "BulkDataURI";
+static const char* const KEY_INLINE_BINARY = "InlineBinary";
+static const char* const KEY_SQ = "SQ";
+static const char* const KEY_VALUE = "Value";
+static const char* const KEY_VR = "vr";
+
+
+namespace Orthanc
+{
+#if ORTHANC_ENABLE_PUGIXML == 1
+  static void ExploreXmlDataset(pugi::xml_node& target,
+                                const Json::Value& source)
+  {
+    assert(source.type() == Json::objectValue);
+
+    Json::Value::Members members = source.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]);
+      const Json::Value& content = source[members[i]];
+
+      assert(content.type() == Json::objectValue &&
+             content.isMember("vr") &&
+             content["vr"].type() == Json::stringValue);
+      const std::string vr = content["vr"].asString();
+
+      const std::string keyword = FromDcmtkBridge::GetTagName(tag, "");
+    
+      pugi::xml_node node = target.append_child("DicomAttribute");
+      node.append_attribute("tag").set_value(members[i].c_str());
+      node.append_attribute("vr").set_value(vr.c_str());
+
+      if (keyword != std::string(DcmTag_ERROR_TagName))
+      {
+        node.append_attribute("keyword").set_value(keyword.c_str());
+      }   
+
+      if (content.isMember(KEY_VALUE))
+      {
+        assert(content[KEY_VALUE].type() == Json::arrayValue);
+        
+        for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++)
+        {
+          std::string number = boost::lexical_cast<std::string>(j + 1);
+
+          if (vr == "SQ")
+          {
+            if (content[KEY_VALUE][j].type() == Json::objectValue)
+            {
+              pugi::xml_node child = node.append_child("Item");
+              child.append_attribute("number").set_value(number.c_str());
+              ExploreXmlDataset(child, content[KEY_VALUE][j]);
+            }
+          }
+          if (vr == "PN")
+          {
+            if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
+                content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue)
+            {
+              std::vector<std::string> tokens;
+              Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^');
+
+              pugi::xml_node child = node.append_child("PersonName");
+              child.append_attribute("number").set_value(number.c_str());
+            
+              pugi::xml_node name = child.append_child(KEY_ALPHABETIC);
+            
+              if (tokens.size() >= 1)
+              {
+                name.append_child("FamilyName").text() = tokens[0].c_str();
+              }
+            
+              if (tokens.size() >= 2)
+              {
+                name.append_child("GivenName").text() = tokens[1].c_str();
+              }
+            
+              if (tokens.size() >= 3)
+              {
+                name.append_child("MiddleName").text() = tokens[2].c_str();
+              }
+            
+              if (tokens.size() >= 4)
+              {
+                name.append_child("NamePrefix").text() = tokens[3].c_str();
+              }
+            
+              if (tokens.size() >= 5)
+              {
+                name.append_child("NameSuffix").text() = tokens[4].c_str();
+              }
+            }
+          }
+          else
+          {
+            pugi::xml_node child = node.append_child("Value");
+            child.append_attribute("number").set_value(number.c_str());
+
+            switch (content[KEY_VALUE][j].type())
+            {
+              case Json::stringValue:
+                child.text() = content[KEY_VALUE][j].asCString();
+                break;
+
+              case Json::realValue:
+                child.text() = content[KEY_VALUE][j].asFloat();
+                break;
+
+              case Json::intValue:
+                child.text() = content[KEY_VALUE][j].asInt();
+                break;
+
+              case Json::uintValue:
+                child.text() = content[KEY_VALUE][j].asUInt();
+                break;
+
+              default:
+                break;
+            }
+          }
+        }
+      }
+      else if (content.isMember(KEY_BULK_DATA_URI) &&
+               content[KEY_BULK_DATA_URI].type() == Json::stringValue)
+      {
+        pugi::xml_node child = node.append_child("BulkData");
+        child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString());
+      }
+      else if (content.isMember(KEY_INLINE_BINARY) &&
+               content[KEY_INLINE_BINARY].type() == Json::stringValue)
+      {
+        pugi::xml_node child = node.append_child("InlineBinary");
+        child.text() = content[KEY_INLINE_BINARY].asCString();
+      }
+    }
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+  static void DicomWebJsonToXml(pugi::xml_document& target,
+                                const Json::Value& source)
+  {
+    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");
+
+    ExploreXmlDataset(root, source);
+
+    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");
+  }
+#endif
+
+
+  std::string DicomWebJsonVisitor::FormatTag(const DicomTag& tag)
+  {
+    char buf[16];
+    sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement());
+    return std::string(buf);
+  }
+
+    
+  Json::Value& DicomWebJsonVisitor::CreateNode(const std::vector<DicomTag>& parentTags,
+                                               const std::vector<size_t>& parentIndexes,
+                                               const DicomTag& tag)
+  {
+    assert(parentTags.size() == parentIndexes.size());      
+
+    Json::Value* node = &result_;
+
+    for (size_t i = 0; i < parentTags.size(); i++)
+    {
+      std::string t = FormatTag(parentTags[i]);
+
+      if (!node->isMember(t))
+      {
+        Json::Value item = Json::objectValue;
+        item[KEY_VR] = KEY_SQ;
+        item[KEY_VALUE] = Json::arrayValue;
+        item[KEY_VALUE].append(Json::objectValue);
+        (*node) [t] = item;
+
+        node = &(*node)[t][KEY_VALUE][0];
+      }
+      else if ((*node)  [t].type() != Json::objectValue ||
+               !(*node) [t].isMember(KEY_VR) ||
+               (*node)  [t][KEY_VR].type() != Json::stringValue ||
+               (*node)  [t][KEY_VR].asString() != KEY_SQ ||
+               !(*node) [t].isMember(KEY_VALUE) ||
+               (*node)  [t][KEY_VALUE].type() != Json::arrayValue)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      else
+      {
+        size_t currentSize = (*node) [t][KEY_VALUE].size();
+
+        if (parentIndexes[i] < currentSize)
+        {
+          // The node already exists
+        }
+        else if (parentIndexes[i] == currentSize)
+        {
+          (*node) [t][KEY_VALUE].append(Json::objectValue);
+        }
+        else
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+          
+        node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])];
+      }
+    }
+
+    assert(node->type() == Json::objectValue);
+
+    std::string t = FormatTag(tag);
+    if (node->isMember(t))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      (*node) [t] = Json::objectValue;
+      return (*node) [t];
+    }
+  }
+
+    
+  Json::Value DicomWebJsonVisitor::FormatInteger(int64_t value)
+  {
+    if (value < 0)
+    {
+      return Json::Value(static_cast<int32_t>(value));
+    }
+    else
+    {
+      return Json::Value(static_cast<uint32_t>(value));
+    }
+  }
+
+    
+  Json::Value DicomWebJsonVisitor::FormatDouble(double value)
+  {
+    long long a = boost::math::llround<double>(value);
+
+    double d = fabs(value - static_cast<double>(a));
+
+    if (d <= std::numeric_limits<double>::epsilon() * 100.0)
+    {
+      return FormatInteger(a);
+    }
+    else
+    {
+      return Json::Value(value);
+    }
+  }
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+  void DicomWebJsonVisitor::FormatXml(pugi::xml_document& target) const
+  {
+    DicomWebJsonToXml(target, result_);
+  }
+#endif
+
+
+  void DicomWebJsonVisitor::VisitEmptySequence(const std::vector<DicomTag>& parentTags,
+                                               const std::vector<size_t>& parentIndexes,
+                                               const DicomTag& tag)
+  {
+    if (tag.GetElement() != 0x0000)
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence);
+    }
+  }
+  
+
+  void DicomWebJsonVisitor::VisitBinary(const std::vector<DicomTag>& parentTags,
+                                        const std::vector<size_t>& parentIndexes,
+                                        const DicomTag& tag,
+                                        ValueRepresentation vr,
+                                        const void* data,
+                                        size_t size)
+  {
+    assert(vr == ValueRepresentation_OtherByte ||
+           vr == ValueRepresentation_OtherDouble ||
+           vr == ValueRepresentation_OtherFloat ||
+           vr == ValueRepresentation_OtherLong ||
+           vr == ValueRepresentation_OtherWord ||
+           vr == ValueRepresentation_Unknown);
+
+    if (tag.GetElement() != 0x0000)
+    {
+      BinaryMode mode;
+      std::string bulkDataUri;
+        
+      if (formatter_ == NULL)
+      {
+        mode = BinaryMode_InlineBinary;
+      }
+      else
+      {
+        mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr);
+      }
+
+      if (mode != BinaryMode_Ignore)
+      {
+        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+        node[KEY_VR] = EnumerationToString(vr);
+
+        switch (mode)
+        {
+          case BinaryMode_BulkDataUri:
+            node[KEY_BULK_DATA_URI] = bulkDataUri;
+            break;
+
+          case BinaryMode_InlineBinary:
+          {
+            std::string tmp(static_cast<const char*>(data), size);
+          
+            std::string base64;
+            Toolbox::EncodeBase64(base64, tmp);
+
+            node[KEY_INLINE_BINARY] = base64;
+            break;
+          }
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+    }
+  }
+
+
+  void DicomWebJsonVisitor::VisitIntegers(const std::vector<DicomTag>& parentTags,
+                                          const std::vector<size_t>& parentIndexes,
+                                          const DicomTag& tag,
+                                          ValueRepresentation vr,
+                                          const std::vector<int64_t>& values)
+  {
+    if (tag.GetElement() != 0x0000 &&
+        vr != ValueRepresentation_NotSupported)
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      node[KEY_VR] = EnumerationToString(vr);
+
+      if (!values.empty())
+      {
+        Json::Value content = Json::arrayValue;
+        for (size_t i = 0; i < values.size(); i++)
+        {
+          content.append(FormatInteger(values[i]));
+        }
+
+        node[KEY_VALUE] = content;
+      }
+    }
+  }
+
+  void DicomWebJsonVisitor::VisitDoubles(const std::vector<DicomTag>& parentTags,
+                                         const std::vector<size_t>& parentIndexes,
+                                         const DicomTag& tag,
+                                         ValueRepresentation vr,
+                                         const std::vector<double>& values)
+  {
+    if (tag.GetElement() != 0x0000 &&
+        vr != ValueRepresentation_NotSupported)
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      node[KEY_VR] = EnumerationToString(vr);
+
+      if (!values.empty())
+      {
+        Json::Value content = Json::arrayValue;
+        for (size_t i = 0; i < values.size(); i++)
+        {
+          content.append(FormatDouble(values[i]));
+        }
+          
+        node[KEY_VALUE] = content;
+      }
+    }
+  }
+
+  
+  void DicomWebJsonVisitor::VisitAttributes(const std::vector<DicomTag>& parentTags,
+                                            const std::vector<size_t>& parentIndexes,
+                                            const DicomTag& tag,
+                                            const std::vector<DicomTag>& values)
+  {
+    if (tag.GetElement() != 0x0000)
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag);
+
+      if (!values.empty())
+      {
+        Json::Value content = Json::arrayValue;
+        for (size_t i = 0; i < values.size(); i++)
+        {
+          content.append(FormatTag(values[i]));
+        }
+          
+        node[KEY_VALUE] = content;
+      }
+    }
+  }
+
+  
+  ITagVisitor::Action
+  DicomWebJsonVisitor::VisitString(std::string& newValue,
+                                   const std::vector<DicomTag>& parentTags,
+                                   const std::vector<size_t>& parentIndexes,
+                                   const DicomTag& tag,
+                                   ValueRepresentation vr,
+                                   const std::string& value)
+  {
+    if (tag.GetElement() == 0x0000 ||
+        vr == ValueRepresentation_NotSupported)
+    {
+      return Action_None;
+    }
+    else
+    {
+      Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
+      node[KEY_VR] = EnumerationToString(vr);
+
+      if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        // TODO - The JSON file has an UTF-8 encoding, thus DCMTK
+        // replaces the specific character set with "ISO_IR 192"
+        // (UNICODE UTF-8). It is unclear whether the source
+        // character set should be kept: We thus mimic DCMTK.
+        node[KEY_VALUE].append("ISO_IR 192");
+      }
+      else
+      {
+        std::string truncated;
+        
+        if (!value.empty() &&
+            value[value.size() - 1] == '\0')
+        {
+          truncated = value.substr(0, value.size() - 1);
+        }
+        else
+        {
+          truncated = value;
+        }
+        
+        if (!truncated.empty())
+        {
+          std::vector<std::string> tokens;
+          Toolbox::TokenizeString(tokens, truncated, '\\');
+
+          node[KEY_VALUE] = Json::arrayValue;
+          for (size_t i = 0; i < tokens.size(); i++)
+          {
+            try
+            {
+              switch (vr)
+              {
+                case ValueRepresentation_PersonName:
+                {
+                  Json::Value value = Json::objectValue;
+                  if (!tokens[i].empty())
+                  {
+                    value[KEY_ALPHABETIC] = tokens[i];
+                  }
+                  node[KEY_VALUE].append(value);
+                  break;
+                }
+                  
+                case ValueRepresentation_IntegerString:
+                  if (tokens[i].empty())
+                  {
+                    node[KEY_VALUE].append(Json::nullValue);
+                  }
+                  else
+                  {
+                    int64_t value = boost::lexical_cast<int64_t>(tokens[i]);
+                    node[KEY_VALUE].append(FormatInteger(value));
+                  }
+                  
+                  break;
+              
+                case ValueRepresentation_DecimalString:
+                  if (tokens[i].empty())
+                  {
+                    node[KEY_VALUE].append(Json::nullValue);
+                  }
+                  else
+                  {
+                    double value = boost::lexical_cast<double>(tokens[i]);
+                    node[KEY_VALUE].append(FormatDouble(value));
+                  }
+                  break;
+              
+                default:
+                  if (tokens[i].empty())
+                  {
+                    node[KEY_VALUE].append(Json::nullValue);
+                  }
+                  else
+                  {
+                    node[KEY_VALUE].append(tokens[i]);
+                  }
+                  
+                  break;
+              }
+            }
+            catch (boost::bad_lexical_cast&)
+            {
+              throw OrthancException(ErrorCode_BadFileFormat);
+            }
+          }
+        }
+      }
+    }
+      
+    return Action_None;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/DicomWebJsonVisitor.h	Wed Feb 06 15:21:32 2019 +0100
@@ -0,0 +1,164 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error Macro ORTHANC_ENABLE_PUGIXML must be defined to use this file
+#endif
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include <pugixml.hpp>
+#endif
+
+#include "ITagVisitor.h"
+
+#include <json/value.h>
+
+
+namespace Orthanc
+{
+  class DicomWebJsonVisitor : public ITagVisitor
+  {
+  public:
+    enum BinaryMode
+    {
+      BinaryMode_Ignore,
+      BinaryMode_BulkDataUri,
+      BinaryMode_InlineBinary
+    };
+    
+    class IBinaryFormatter : public boost::noncopyable
+    {
+    public:
+      virtual ~IBinaryFormatter()
+      {
+      }
+
+      virtual BinaryMode Format(std::string& bulkDataUri,
+                                const std::vector<DicomTag>& parentTags,
+                                const std::vector<size_t>& parentIndexes,
+                                const DicomTag& tag,
+                                ValueRepresentation vr) = 0;
+    };
+    
+  private:
+    Json::Value        result_;
+    IBinaryFormatter  *formatter_;
+
+    static std::string FormatTag(const DicomTag& tag);
+    
+    Json::Value& CreateNode(const std::vector<DicomTag>& parentTags,
+                            const std::vector<size_t>& parentIndexes,
+                            const DicomTag& tag);
+
+    static Json::Value FormatInteger(int64_t value);
+
+    static Json::Value FormatDouble(double value);
+
+  public:
+    DicomWebJsonVisitor() :
+      formatter_(NULL)
+    {
+      Clear();
+    }
+
+    void SetFormatter(IBinaryFormatter& formatter)
+    {
+      formatter_ = &formatter;
+    }
+    
+    void Clear()
+    {
+      result_ = Json::objectValue;
+    }
+
+    const Json::Value& GetResult() const
+    {
+      return result_;
+    }
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+    void FormatXml(pugi::xml_document& target) const;
+#endif
+
+    virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags,
+                                   const std::vector<size_t>& parentIndexes,
+                                   const DicomTag& tag,
+                                   ValueRepresentation vr)
+      ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags,
+                                    const std::vector<size_t>& parentIndexes,
+                                    const DicomTag& tag)
+      ORTHANC_OVERRIDE;
+
+    virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
+                             const std::vector<size_t>& parentIndexes,
+                             const DicomTag& tag,
+                             ValueRepresentation vr,
+                             const void* data,
+                             size_t size)
+      ORTHANC_OVERRIDE;
+
+    virtual void VisitIntegers(const std::vector<DicomTag>& parentTags,
+                               const std::vector<size_t>& parentIndexes,
+                               const DicomTag& tag,
+                               ValueRepresentation vr,
+                               const std::vector<int64_t>& values)
+      ORTHANC_OVERRIDE;
+
+    virtual void VisitDoubles(const std::vector<DicomTag>& parentTags,
+                              const std::vector<size_t>& parentIndexes,
+                              const DicomTag& tag,
+                              ValueRepresentation vr,
+                              const std::vector<double>& values)
+      ORTHANC_OVERRIDE;
+
+    virtual void VisitAttributes(const std::vector<DicomTag>& parentTags,
+                                 const std::vector<size_t>& parentIndexes,
+                                 const DicomTag& tag,
+                                 const std::vector<DicomTag>& values)
+      ORTHANC_OVERRIDE;
+
+    virtual Action VisitString(std::string& newValue,
+                               const std::vector<DicomTag>& parentTags,
+                               const std::vector<size_t>& parentIndexes,
+                               const DicomTag& tag,
+                               ValueRepresentation vr,
+                               const std::string& value)
+      ORTHANC_OVERRIDE;
+  };
+}
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Feb 06 15:01:37 2019 +0100
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Feb 06 15:21:32 2019 +0100
@@ -448,6 +448,7 @@
 
   set(ORTHANC_DICOM_SOURCES_INTERNAL
     ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/DicomWebJsonVisitor.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp
--- a/UnitTestsSources/DicomMapTests.cpp	Wed Feb 06 15:01:37 2019 +0100
+++ b/UnitTestsSources/DicomMapTests.cpp	Wed Feb 06 15:21:32 2019 +0100
@@ -38,6 +38,7 @@
 #include "../Core/DicomFormat/DicomMap.h"
 #include "../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../Core/DicomParsing/ParsedDicomFile.h"
+#include "../Core/DicomParsing/DicomWebJsonVisitor.h"
 
 #include "../OrthancServer/DicomInstanceToStore.h"
 
@@ -554,633 +555,6 @@
 
 
 
-
-
-#include <boost/math/special_functions/round.hpp>
-#include <pugixml.hpp>
-
-
-static const char* const KEY_ALPHABETIC = "Alphabetic";
-static const char* const KEY_BULK_DATA_URI = "BulkDataURI";
-static const char* const KEY_INLINE_BINARY = "InlineBinary";
-static const char* const KEY_SQ = "SQ";
-static const char* const KEY_VALUE = "Value";
-static const char* const KEY_VR = "vr";
-
-namespace Orthanc
-{  
-  static void ExploreDataset(pugi::xml_node& target,
-                             const Json::Value& source)
-  {
-    assert(source.type() == Json::objectValue);
-
-    Json::Value::Members members = source.getMemberNames();
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]);
-      const Json::Value& content = source[members[i]];
-
-      assert(content.type() == Json::objectValue &&
-             content.isMember("vr") &&
-             content["vr"].type() == Json::stringValue);
-      const std::string vr = content["vr"].asString();
-
-      const std::string keyword = FromDcmtkBridge::GetTagName(tag, "");
-    
-      pugi::xml_node node = target.append_child("DicomAttribute");
-      node.append_attribute("tag").set_value(members[i].c_str());
-      node.append_attribute("vr").set_value(vr.c_str());
-
-      if (keyword != std::string(DcmTag_ERROR_TagName))
-      {
-        node.append_attribute("keyword").set_value(keyword.c_str());
-      }   
-
-      if (content.isMember(KEY_VALUE))
-      {
-        assert(content[KEY_VALUE].type() == Json::arrayValue);
-        
-        for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++)
-        {
-          std::string number = boost::lexical_cast<std::string>(j + 1);
-
-          if (vr == "SQ")
-          {
-            if (content[KEY_VALUE][j].type() == Json::objectValue)
-            {
-              pugi::xml_node child = node.append_child("Item");
-              child.append_attribute("number").set_value(number.c_str());
-              ExploreDataset(child, content[KEY_VALUE][j]);
-            }
-          }
-          if (vr == "PN")
-          {
-            if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
-                content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue)
-            {
-              std::vector<std::string> tokens;
-              Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^');
-
-              pugi::xml_node child = node.append_child("PersonName");
-              child.append_attribute("number").set_value(number.c_str());
-            
-              pugi::xml_node name = child.append_child(KEY_ALPHABETIC);
-            
-              if (tokens.size() >= 1)
-              {
-                name.append_child("FamilyName").text() = tokens[0].c_str();
-              }
-            
-              if (tokens.size() >= 2)
-              {
-                name.append_child("GivenName").text() = tokens[1].c_str();
-              }
-            
-              if (tokens.size() >= 3)
-              {
-                name.append_child("MiddleName").text() = tokens[2].c_str();
-              }
-            
-              if (tokens.size() >= 4)
-              {
-                name.append_child("NamePrefix").text() = tokens[3].c_str();
-              }
-            
-              if (tokens.size() >= 5)
-              {
-                name.append_child("NameSuffix").text() = tokens[4].c_str();
-              }
-            }
-          }
-          else
-          {
-            pugi::xml_node child = node.append_child("Value");
-            child.append_attribute("number").set_value(number.c_str());
-
-            switch (content[KEY_VALUE][j].type())
-            {
-              case Json::stringValue:
-                child.text() = content[KEY_VALUE][j].asCString();
-                break;
-
-              case Json::realValue:
-                child.text() = content[KEY_VALUE][j].asFloat();
-                break;
-
-              case Json::intValue:
-                child.text() = content[KEY_VALUE][j].asInt();
-                break;
-
-              case Json::uintValue:
-                child.text() = content[KEY_VALUE][j].asUInt();
-                break;
-
-              default:
-                break;
-            }
-          }
-        }
-      }
-      else if (content.isMember(KEY_BULK_DATA_URI) &&
-               content[KEY_BULK_DATA_URI].type() == Json::stringValue)
-      {
-        pugi::xml_node child = node.append_child("BulkData");
-        child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString());
-      }
-      else if (content.isMember(KEY_INLINE_BINARY) &&
-               content[KEY_INLINE_BINARY].type() == Json::stringValue)
-      {
-        pugi::xml_node child = node.append_child("InlineBinary");
-        child.text() = content[KEY_INLINE_BINARY].asCString();
-      }
-    }
-  }
-
-
-  static void DicomWebJsonToXml(pugi::xml_document& target,
-                                const Json::Value& source)
-  {
-    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");
-
-    ExploreDataset(root, source);
-
-    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");
-  }
-
-
-  enum DicomWebBinaryMode
-  {
-    DicomWebBinaryMode_Ignore,
-    DicomWebBinaryMode_BulkDataUri,
-    DicomWebBinaryMode_InlineBinary
-  };
-    
-  class IDicomWebBinaryFormatter : public boost::noncopyable
-  {
-  public:
-    virtual ~IDicomWebBinaryFormatter()
-    {
-    }
-
-    virtual DicomWebBinaryMode Format(std::string& bulkDataUri,
-                                      const std::vector<DicomTag>& parentTags,
-                                      const std::vector<size_t>& parentIndexes,
-                                      const DicomTag& tag,
-                                      ValueRepresentation vr) = 0;
-  };
-
-  class DicomWebJsonVisitor : public ITagVisitor
-  {
-  private:
-    Json::Value                result_;
-    IDicomWebBinaryFormatter  *formatter_;
-
-    static std::string FormatTag(const DicomTag& tag)
-    {
-      char buf[16];
-      sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement());
-      return std::string(buf);
-    }
-    
-    Json::Value& CreateNode(const std::vector<DicomTag>& parentTags,
-                            const std::vector<size_t>& parentIndexes,
-                            const DicomTag& tag)
-    {
-      assert(parentTags.size() == parentIndexes.size());      
-
-      Json::Value* node = &result_;
-
-      for (size_t i = 0; i < parentTags.size(); i++)
-      {
-        std::string t = FormatTag(parentTags[i]);
-
-        if (!node->isMember(t))
-        {
-          Json::Value item = Json::objectValue;
-          item[KEY_VR] = KEY_SQ;
-          item[KEY_VALUE] = Json::arrayValue;
-          item[KEY_VALUE].append(Json::objectValue);
-          (*node) [t] = item;
-
-          node = &(*node)[t][KEY_VALUE][0];
-        }
-        else if ((*node)  [t].type() != Json::objectValue ||
-                 !(*node) [t].isMember(KEY_VR) ||
-                 (*node)  [t][KEY_VR].type() != Json::stringValue ||
-                 (*node)  [t][KEY_VR].asString() != KEY_SQ ||
-                 !(*node) [t].isMember(KEY_VALUE) ||
-                 (*node)  [t][KEY_VALUE].type() != Json::arrayValue)
-        {
-          throw OrthancException(ErrorCode_InternalError);
-        }
-        else
-        {
-          size_t currentSize = (*node) [t][KEY_VALUE].size();
-
-          if (parentIndexes[i] < currentSize)
-          {
-            // The node already exists
-          }
-          else if (parentIndexes[i] == currentSize)
-          {
-            (*node) [t][KEY_VALUE].append(Json::objectValue);
-          }
-          else
-          {
-            throw OrthancException(ErrorCode_InternalError);
-          }
-          
-          node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])];
-        }
-      }
-
-      assert(node->type() == Json::objectValue);
-
-      std::string t = FormatTag(tag);
-      if (node->isMember(t))
-      {
-        throw OrthancException(ErrorCode_InternalError);
-      }
-      else
-      {
-        (*node) [t] = Json::objectValue;
-        return (*node) [t];
-      }
-    }
-
-    static Json::Value FormatInteger(int64_t value)
-    {
-      if (value < 0)
-      {
-        return Json::Value(static_cast<int32_t>(value));
-      }
-      else
-      {
-        return Json::Value(static_cast<uint32_t>(value));
-      }
-    }
-
-    static Json::Value FormatDouble(double value)
-    {
-      long long a = boost::math::llround<double>(value);
-
-      double d = fabs(value - static_cast<double>(a));
-
-      if (d <= std::numeric_limits<double>::epsilon() * 100.0)
-      {
-        return FormatInteger(a);
-      }
-      else
-      {
-        return Json::Value(value);
-      }
-    }
-
-  public:
-    DicomWebJsonVisitor() :
-      formatter_(NULL)
-    {
-      Clear();
-    }
-
-    void SetFormatter(IDicomWebBinaryFormatter& formatter)
-    {
-      formatter_ = &formatter;
-    }
-    
-    void Clear()
-    {
-      result_ = Json::objectValue;
-    }
-
-    const Json::Value& GetResult() const
-    {
-      return result_;
-    }
-
-    void FormatXml(pugi::xml_document& target) const
-    {
-      DicomWebJsonToXml(target, result_);
-    }
-
-    virtual void VisitNotSupported(const std::vector<DicomTag>& parentTags,
-                                   const std::vector<size_t>& parentIndexes,
-                                   const DicomTag& tag,
-                                   ValueRepresentation vr) ORTHANC_OVERRIDE
-    {
-    }
-
-    virtual void VisitEmptySequence(const std::vector<DicomTag>& parentTags,
-                                    const std::vector<size_t>& parentIndexes,
-                                    const DicomTag& tag) ORTHANC_OVERRIDE
-    {
-      if (tag.GetElement() != 0x0000)
-      {
-        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-        node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence);
-      }
-    }
-
-    virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
-                             const std::vector<size_t>& parentIndexes,
-                             const DicomTag& tag,
-                             ValueRepresentation vr,
-                             const void* data,
-                             size_t size) ORTHANC_OVERRIDE
-    {
-      assert(vr == ValueRepresentation_OtherByte ||
-             vr == ValueRepresentation_OtherDouble ||
-             vr == ValueRepresentation_OtherFloat ||
-             vr == ValueRepresentation_OtherLong ||
-             vr == ValueRepresentation_OtherWord ||
-             vr == ValueRepresentation_Unknown);
-
-      if (tag.GetElement() != 0x0000)
-      {
-        DicomWebBinaryMode mode;
-        std::string bulkDataUri;
-        
-        if (formatter_ == NULL)
-        {
-          mode = DicomWebBinaryMode_InlineBinary;
-        }
-        else
-        {
-          mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr);
-        }
-
-        /*mode = DicomWebBinaryMode_BulkDataUri;
-          bulkDataUri = "http://localhost/" + tag.Format();*/
-
-        if (mode != DicomWebBinaryMode_Ignore)
-        {
-          Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-          node[KEY_VR] = EnumerationToString(vr);
-
-          switch (mode)
-          {
-            case DicomWebBinaryMode_BulkDataUri:
-              node[KEY_BULK_DATA_URI] = bulkDataUri;
-              break;
-
-            case DicomWebBinaryMode_InlineBinary:
-            {
-              std::string tmp(static_cast<const char*>(data), size);
-          
-              std::string base64;
-              Toolbox::EncodeBase64(base64, tmp);
-
-              node[KEY_INLINE_BINARY] = base64;
-              break;
-            }
-
-            default:
-              throw OrthancException(ErrorCode_ParameterOutOfRange);
-          }
-        }
-      }
-    }
-
-    virtual void VisitIntegers(const std::vector<DicomTag>& parentTags,
-                               const std::vector<size_t>& parentIndexes,
-                               const DicomTag& tag,
-                               ValueRepresentation vr,
-                               const std::vector<int64_t>& values) ORTHANC_OVERRIDE
-    {
-      if (tag.GetElement() != 0x0000 &&
-          vr != ValueRepresentation_NotSupported)
-      {
-        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-        node[KEY_VR] = EnumerationToString(vr);
-
-        if (!values.empty())
-        {
-          Json::Value content = Json::arrayValue;
-          for (size_t i = 0; i < values.size(); i++)
-          {
-            content.append(FormatInteger(values[i]));
-          }
-
-          node[KEY_VALUE] = content;
-        }
-      }
-    }
-
-    virtual void VisitDoubles(const std::vector<DicomTag>& parentTags,
-                              const std::vector<size_t>& parentIndexes,
-                              const DicomTag& tag,
-                              ValueRepresentation vr,
-                              const std::vector<double>& values) ORTHANC_OVERRIDE
-    {
-      if (tag.GetElement() != 0x0000 &&
-          vr != ValueRepresentation_NotSupported)
-      {
-        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-        node[KEY_VR] = EnumerationToString(vr);
-
-        if (!values.empty())
-        {
-          Json::Value content = Json::arrayValue;
-          for (size_t i = 0; i < values.size(); i++)
-          {
-            content.append(FormatDouble(values[i]));
-          }
-          
-          node[KEY_VALUE] = content;
-        }
-      }
-    }
-
-    virtual void VisitAttributes(const std::vector<DicomTag>& parentTags,
-                                 const std::vector<size_t>& parentIndexes,
-                                 const DicomTag& tag,
-                                 const std::vector<DicomTag>& values) ORTHANC_OVERRIDE
-    {
-      if (tag.GetElement() != 0x0000)
-      {
-        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-        node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag);
-
-        if (!values.empty())
-        {
-          Json::Value content = Json::arrayValue;
-          for (size_t i = 0; i < values.size(); i++)
-          {
-            content.append(FormatTag(values[i]));
-          }
-          
-          node[KEY_VALUE] = content;
-        }
-      }
-    }
-
-    virtual Action VisitString(std::string& newValue,
-                               const std::vector<DicomTag>& parentTags,
-                               const std::vector<size_t>& parentIndexes,
-                               const DicomTag& tag,
-                               ValueRepresentation vr,
-                               const std::string& value) ORTHANC_OVERRIDE
-    {
-      if (tag.GetElement() == 0x0000 ||
-          vr == ValueRepresentation_NotSupported)
-      {
-        return Action_None;
-      }
-      else
-      {
-        Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
-        node[KEY_VR] = EnumerationToString(vr);
-
-        if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
-        {
-          // TODO - The JSON file has an UTF-8 encoding, thus DCMTK
-          // replaces the specific character set with "ISO_IR 192"
-          // (UNICODE UTF-8). It is unclear whether the source
-          // character set should be kept: We thus mimic DCMTK.
-          node[KEY_VALUE].append("ISO_IR 192");
-        }
-        else
-        {
-          std::string truncated;
-        
-          if (!value.empty() &&
-              value[value.size() - 1] == '\0')
-          {
-            truncated = value.substr(0, value.size() - 1);
-          }
-          else
-          {
-            truncated = value;
-          }
-        
-          if (!truncated.empty())
-          {
-            std::vector<std::string> tokens;
-            Toolbox::TokenizeString(tokens, truncated, '\\');
-
-            node[KEY_VALUE] = Json::arrayValue;
-            for (size_t i = 0; i < tokens.size(); i++)
-            {
-              try
-              {
-                switch (vr)
-                {
-                  case ValueRepresentation_PersonName:
-                  {
-                    Json::Value value = Json::objectValue;
-                    if (!tokens[i].empty())
-                    {
-                      value[KEY_ALPHABETIC] = tokens[i];
-                    }
-                    node[KEY_VALUE].append(value);
-                    break;
-                  }
-                  
-                  case ValueRepresentation_IntegerString:
-                    if (tokens[i].empty())
-                    {
-                      node[KEY_VALUE].append(Json::nullValue);
-                    }
-                    else
-                    {
-                      int64_t value = boost::lexical_cast<int64_t>(tokens[i]);
-                      node[KEY_VALUE].append(FormatInteger(value));
-                    }
-                  
-                    break;
-              
-                  case ValueRepresentation_DecimalString:
-                    if (tokens[i].empty())
-                    {
-                      node[KEY_VALUE].append(Json::nullValue);
-                    }
-                    else
-                    {
-                      double value = boost::lexical_cast<double>(tokens[i]);
-                      node[KEY_VALUE].append(FormatDouble(value));
-                    }
-                    break;
-              
-                  default:
-                    if (tokens[i].empty())
-                    {
-                      node[KEY_VALUE].append(Json::nullValue);
-                    }
-                    else
-                    {
-                      node[KEY_VALUE].append(tokens[i]);
-                    }
-                  
-                    break;
-                }
-              }
-              catch (boost::bad_lexical_cast&)
-              {
-                throw OrthancException(ErrorCode_BadFileFormat);
-              }
-            }
-          }
-        }
-      }
-      
-      return Action_None;
-    }
-  };
-}
-
-
-
-
-
-
-#include "../Core/SystemToolbox.h"
-
-
-/* 
-
-MarekLatin2.dcm 
-HierarchicalAnonymization/StructuredReports/IM0
-DummyCT.dcm
-Brainix/Epi/IM-0001-0018.dcm
-Issue22.dcm
-
-
-cat << EOF > /tmp/tutu.py
-import json
-import sys
-j = json.loads(sys.stdin.read().decode("utf-8-sig"))
-print(json.dumps(j, indent=4, sort_keys=True, ensure_ascii=False).encode('utf-8'))
-EOF
-
-DCMDICTPATH=/home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/data/dicom.dic /home/jodogne/Downloads/dcmtk-3.6.4/i/bin/dcm2json ~/Subversion/orthanc-tests/Database/DummyCT.dcm | tr -d '\0' | sed 's/\\u0000//g' | sed 's/\.0$//' | python /tmp/tutu.py > /tmp/a.json
-
-make -j4 && ./UnitTests --gtest_filter=DicomWeb* && python /tmp/tutu.py < tutu.json > /tmp/b.json && diff -i /tmp/a.json /tmp/b.json
-
-*/
-
-TEST(DicomWebJson, Basic)
-{
-  std::string content;
-  Orthanc::SystemToolbox::ReadFile(content, "/home/jodogne/Subversion/orthanc-tests/Database/DummyCT.dcm");
-
-  Orthanc::ParsedDicomFile dicom(content);
-
-  Orthanc::DicomWebJsonVisitor visitor;
-  dicom.Apply(visitor);
-
-  Orthanc::SystemToolbox::WriteFile(visitor.GetResult().toStyledString(), "tutu.json");
-
-  pugi::xml_document xml;
-  visitor.FormatXml(xml);
-  xml.print(std::cout);
-}
-
-
 TEST(DicomWebJson, Multiplicity)
 {
   // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.4.html
@@ -1214,7 +588,6 @@
 
   pugi::xml_document xml;
   visitor.FormatXml(xml);
-  xml.print(std::cout);
 }
 
 
@@ -1245,7 +618,6 @@
 
   pugi::xml_document xml;
   visitor.FormatXml(xml);
-  xml.print(std::cout);
 }
 
 
@@ -1388,7 +760,6 @@
 
   pugi::xml_document xml;
   visitor.FormatXml(xml);
-  xml.print(std::cout);  
 }
 
 
@@ -1433,5 +804,4 @@
 
   pugi::xml_document xml;
   visitor.FormatXml(xml);
-  xml.print(std::cout);
 }