diff --git a/Modules/Forms/include/mitkForm.h b/Modules/Forms/include/mitkForm.h index 2d9712779c..38644af084 100644 --- a/Modules/Forms/include/mitkForm.h +++ b/Modules/Forms/include/mitkForm.h @@ -1,93 +1,98 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkForm_h #define mitkForm_h #include #include #include #include #include #include namespace mitk::Forms { class Question; /** A form consisting of questions possibly divided into multiple sections/pages. * * A form always has at least a single section, which is also used to define the form's general * title and description. Helper methods like SetTitle() or SetDescription() can be used to * conveniently set these properties of the first section. */ class MITKFORMS_EXPORT Form { public: class MITKFORMS_EXPORT Section { public: explicit Section(const std::string& title = "", const std::string& description = ""); ~Section(); Section(Section&& other) noexcept; Section& operator=(Section&& other) noexcept; std::string GetTitle() const; void SetTitle(const std::string& title); std::string GetDescription() const; void SetDescription(const std::string& description); std::vector GetQuestions() const; void AddQuestion(Question* question); private: std::string m_Title; std::string m_Description; std::vector> m_Questions; }; explicit Form(const std::string& title = "", const std::string& description = ""); ~Form(); Form(Form&& other) noexcept; Form& operator=(Form&& other) noexcept; Section& AddSection(const std::string& title = "", const std::string& description = ""); int GetNumberOfSections() const; Section& GetSection(int index); const Section& GetSection(int index) const; std::string GetTitle() const; void SetTitle(const std::string& title); std::string GetDescription() const; void SetDescription(const std::string& description); std::vector GetQuestions() const; void AddQuestion(Question* question); + std::vector
::const_iterator begin() const; + std::vector
::const_iterator end() const; + + std::vector
::iterator begin(); + std::vector
::iterator end(); void Submit(const fs::path& csvPath) const; private: std::vector
m_Sections; }; MITKFORMS_EXPORT void from_json(const nlohmann::ordered_json& j, Form& f); MITKFORMS_EXPORT void to_json(nlohmann::ordered_json& j, const Form& f); } #endif diff --git a/Modules/Forms/src/mitkForm.cpp b/Modules/Forms/src/mitkForm.cpp index bb0b0daf73..dd0b2b4a00 100644 --- a/Modules/Forms/src/mitkForm.cpp +++ b/Modules/Forms/src/mitkForm.cpp @@ -1,286 +1,306 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include using namespace mitk::Forms; using namespace nlohmann; namespace { std::string GetCurrentISO8601DateTime() { std::timespec ts; std::timespec_get(&ts, TIME_UTC); std::array buffer{}; // YYYY-MM-DDThh:mm:ssZ std::strftime(buffer.data(), buffer.size(), "%FT%TZ", std::gmtime(&ts.tv_sec)); return buffer.data(); } } Form::Section::Section(const std::string& title, const std::string& description) : m_Title(title), m_Description(description) { } Form::Section::~Section() = default; Form::Section::Section(Section&& other) noexcept = default; Form::Section& Form::Section::operator=(Section&& other) noexcept = default; std::string Form::Section::GetTitle() const { return m_Title; } void Form::Section::SetTitle(const std::string& title) { m_Title = title; } std::string Form::Section::GetDescription() const { return m_Description; } void Form::Section::SetDescription(const std::string& description) { m_Description = description; } std::vector Form::Section::GetQuestions() const { std::vector questions; questions.reserve(m_Questions.size()); for (const auto& question : m_Questions) questions.push_back(question.get()); return questions; } void Form::Section::AddQuestion(Question* question) { m_Questions.emplace_back(question); } Form::Form(const std::string& title, const std::string& description) { m_Sections.emplace_back(title, description); } Form::~Form() = default; Form::Form(Form&& other) noexcept = default; Form& Form::operator=(Form&& other) noexcept = default; Form::Section& Form::AddSection(const std::string& title, const std::string& description) { return m_Sections.emplace_back(title, description); } int Form::GetNumberOfSections() const { return static_cast(m_Sections.size()); } Form::Section& Form::GetSection(int index) { return m_Sections.at(index); } const Form::Section& Form::GetSection(int index) const { return m_Sections.at(index); } std::string Form::GetTitle() const { return m_Sections[0].GetTitle(); } void Form::SetTitle(const std::string& title) { m_Sections[0].SetTitle(title); } std::string Form::GetDescription() const { return m_Sections[0].GetDescription(); } void Form::SetDescription(const std::string& description) { m_Sections[0].SetDescription(description); } std::vector Form::GetQuestions() const { return m_Sections[0].GetQuestions(); } void Form::AddQuestion(Question* question) { m_Sections[0].AddQuestion(question); } +std::vector::const_iterator Form::begin() const +{ + return m_Sections.begin(); +} + +std::vector::const_iterator Form::end() const +{ + return m_Sections.end(); +} + +std::vector::iterator Form::begin() +{ + return m_Sections.begin(); +} + +std::vector::iterator Form::end() +{ + return m_Sections.end(); +} + void Form::Submit(const fs::path& csvPath) const { std::ofstream csvFile; if (fs::exists(csvPath)) { csvFile.open(csvPath, std::ofstream::app); if (!csvFile.is_open()) mitkThrow() << "Could not open file \"" << csvPath << "\"!"; } else { csvFile.open(csvPath); if (!csvFile.is_open()) mitkThrow() << "Could not create file \"" << csvPath << "\"!"; csvFile << "\"Timestamp\""; for (const auto& section : m_Sections) { for (const auto* question : section.GetQuestions()) { csvFile << ",\"" << question->GetQuestionText() << '"'; } } csvFile << '\n'; } csvFile << '"' << GetCurrentISO8601DateTime() << '"'; for (const auto& section : m_Sections) { for (const auto* question : section.GetQuestions()) { csvFile << ",\""; bool isFirstResponse = true; for (const auto& response : question->GetResponsesAsStrings()) { if (!isFirstResponse) csvFile << ';'; csvFile << response; isFirstResponse = false; } csvFile << '"'; } } csvFile << std::endl; } void mitk::Forms::from_json(const nlohmann::ordered_json& j, Form& f) { if (!j.contains("FileFormat") || j["FileFormat"] != "MITK Form") mitkThrow() << "Expected \"FileFormat\" field to be \"MITK Form\"!"; if (!j.contains("Version") || j["Version"] != 1) mitkThrow() << "Expected \"Version\" field to be 1!";; const auto* questionFactory = IQuestionFactory::GetInstance(); if (j.contains("Sections")) { bool isFirstSection = true; for (const auto& jSection : j["Sections"]) { std::string title; std::string description; if (jSection.contains("Title")) title = jSection["Title"]; if (jSection.contains("Description")) description = jSection["Description"]; auto& section = isFirstSection ? f.GetSection(0) : f.AddSection(title, description); if (isFirstSection) { section.SetTitle(title); section.SetDescription(description); isFirstSection = false; } if (jSection.contains("Questions")) { for (const auto& jQuestion : jSection["Questions"]) { auto question = questionFactory->Create(jQuestion["Type"]); question->FromJSON(jQuestion); section.AddQuestion(question); } } } } } void mitk::Forms::to_json(nlohmann::ordered_json& j, const Form& f) { j["FileFormat"] = "MITK Form"; j["Version"] = 1; const int numberOfSections = f.GetNumberOfSections(); for (int index = 0; index < numberOfSections; ++index) { const auto& section = f.GetSection(index); ordered_json jSection; if (auto title = section.GetTitle(); !title.empty()) jSection["Title"] = title; if (auto description = section.GetDescription(); !description.empty()) jSection["Description"] = description; for (const auto* question : section.GetQuestions()) { ordered_json jQuestion; question->ToJSON(jQuestion); jSection["Questions"].push_back(jQuestion); } j["Sections"].push_back(jSection); } }