Go back to Richel Bilderbeek's homepage.

Go back to Richel Bilderbeek's C++ page.

 

 

 

 

 

(C++) OpenQuestion

 

STLQt CreatorLubuntu

 

OpenQuestion is a class for an open question.

Technical facts

 

 

 

 

 

 

./CppOpenQuestion/CppOpenQuestion.pri

 

INCLUDEPATH += \
    ../../Classes/CppOpenQuestion

SOURCES += \
    ../../Classes/CppOpenQuestion/openquestion.cpp \
    ../../Classes/CppOpenQuestion/openquestionfactory.cpp

HEADERS  += \
    ../../Classes/CppOpenQuestion/openquestion.h \
    ../../Classes/CppOpenQuestion/openquestionfactory.h

OTHER_FILES += \
    ../../Classes/CppOpenQuestion/Licence.txt

 

 

 

 

 

./CppOpenQuestion/openquestion.h

 

//---------------------------------------------------------------------------
/*
OpenQuestion, class for an open question
Copyright (C) 2011-2015 Richel Bilderbeek

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.

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/>.
*/
//---------------------------------------------------------------------------
// From http://www.richelbilderbeek.nl/CppOpenQuestion
//---------------------------------------------------------------------------
#ifndef OPENQUESTION_H
#define OPENQUESTION_H

#include <string>
#include <vector>

#include "question.h"

namespace ribi {

struct OpenQuestionFactory;

///class for an open question
struct OpenQuestion : public Question
{
  ///Create a copy of the Question, depending on the derived class its type
  Question * Clone() const noexcept;

  static std::string GetVersion() noexcept;
  static std::vector<std::string> GetVersionHistory() noexcept;

  ///How to display the question as multiple lines
  std::vector<std::string> ToLines() const noexcept;

  ///Convert to std::string line, as read from file
  std::string ToStr() const noexcept;

  private:
  ///Throws nothing or std::out_of_range or std::runtime_error
  explicit OpenQuestion(const std::string& question);

  ///An open question has multiple possible answers
  ///Will throw if there is no question or if there are no answers
  OpenQuestion(
    const std::string& filename,
    const std::string& question,
    const std::vector<std::string>& answers
  );
  friend class OpenQuestionFactory;

  friend void boost::checked_delete<>(OpenQuestion *);
  friend void boost::checked_delete<>(const OpenQuestion *);
  ~OpenQuestion() noexcept {}

  ///The wrong answers are at indices 2 to SeperateString(input,',').size()
  static std::vector<std::string> ExtractAnswers(
    const std::string& input);

  #ifndef NDEBUG
  static void Test() noexcept;
  #endif
};

} //~namespace ribi

#endif // OPENQUESTION_H

 

 

 

 

 

./CppOpenQuestion/openquestion.cpp

 

//---------------------------------------------------------------------------
/*
OpenQuestion, class for an open question
Copyright (C) 2011-2015 Richel Bilderbeek

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.

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/>.
*/
//---------------------------------------------------------------------------
// From http://www.richelbilderbeek.nl/CppOpenQuestion
//---------------------------------------------------------------------------
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include "openquestion.h"

#include <cassert>
#include <string>
#include <fstream>
#include <stdexcept>

#include <boost/algorithm/string/split.hpp>
#include <boost/scoped_ptr.hpp>

#include "imagecanvas.h"
#include "openquestionfactory.h"
#include "testtimer.h"
#include "trace.h"
#pragma GCC diagnostic pop

/*
ribi::OpenQuestion::OpenQuestion(const std::string& question)
  : Question(
      SeperateString(question,',').at(0),
      SeperateString(question,',').at(1),
      ExtractAnswers(question))
{
  #ifndef NDEBUG
  Test();
  #endif

  if (question.empty())
  {
    throw std::logic_error("An open question must contain text");
  }
  if (question[0] == ',')
  {
    throw std::logic_error("An open question must not start with a comma");
  }
  if (question[question.size() - 1] == ',')
  {
    throw std::logic_error("An open question must not end with a comma");
  }
  if (question.find(",,") != std::string::npos)
  {
    throw std::logic_error("An open question cannot contain two consecutive commas");
  }

}
*/

ribi::OpenQuestion::OpenQuestion(
  const std::string& filename,
  const std::string& question,
  const std::vector<std::string>& answers)
  : Question(filename,question, answers )
{
  #ifndef NDEBUG
  Test();
  #endif
  //assert(!filename.empty() && "Filename must not be empty");
  //assert(FileExists(filename) == true && "File must exists");
  //assert(!question.empty() && "OpenQuestion must not be empty");
  //assert(!correct_answers.empty() && "Correct answer must not be empty");
}

ribi::Question * ribi::OpenQuestion::Clone() const noexcept
{
  return new OpenQuestion(
    this->GetFilename(),
    this->GetQuestion(),
    this->GetCorrectAnswers()
  );
}

//const std::vector<std::string>& ribi::OpenQuestion::GetAnswers() const noexcept
//{
//  this->GetCorrectAnswers()
//}




std::string ribi::OpenQuestion::GetVersion() noexcept
{
  return "1.3";
}

std::vector<std::string> ribi::OpenQuestion::GetVersionHistory() noexcept
{
  return {
    "2011-06-27: version 1.0: initial version",
    "2011-09-16: version 1.1: allow parsing from std::string"
    "2013-10-24: version 1.2: added tests",
    "2014-06-05: version 1.3: moved parts to OpenQuestionFactory"
  };
}

#ifndef NDEBUG
void ribi::OpenQuestion::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  {
    try { std::make_shared<ImageCanvas>("",0); } catch (std::logic_error&) { /* fine */ }
  }
  const TestTimer test_timer(__func__,__FILE__,1.0);
  //Test simple get/set with single answer
  {
    const std::string filename = "-";
    const std::string question = "1+1=";
    const std::string answer_1 = "2";
    const std::vector<std::string> answers { answer_1 };
    const auto q = OpenQuestionFactory().Create(filename,question,answers);
    assert(q->GetFilename() == filename);
    assert(q->GetQuestion() == question);
    assert(q->GetCorrectAnswers() == answers);
    assert(q->IsCorrect(answer_1));
    assert(!q->IsCorrect("3"));
    assert(!q->IsCorrect(filename));
    assert(!q->IsCorrect(question));
  }
  //Test simple get/set with two answers
  {
    const std::string filename = "-";
    const std::string question = "1+1=";
    const std::string answer_1 { "2" };
    const std::string answer_2 { "Two" };
    const std::vector<std::string> answers { answer_1, answer_2 };
    const auto q = OpenQuestionFactory().Create(filename,question,answers);
    assert(q->GetFilename() == filename);
    assert(q->GetQuestion() == question);
    assert(q->GetCorrectAnswers() == answers);
    assert(q->IsCorrect(answer_1));
    assert(q->IsCorrect(answer_2));
    assert(!q->IsCorrect( answer_1 + "/" + answer_2));
    assert(!q->IsCorrect(filename));
    assert(!q->IsCorrect(question));
  }
  //Test conversion std::string to OpenQuestion and back
  {
    const auto valid = OpenQuestionFactory().GetValidOpenQuestionStrings();
    for (const std::string& s: valid)
    {
      const auto q = OpenQuestionFactory().Create(s);
      assert(s == q->ToStr());
    }
  }
  //Test that ToLines always yields the same result
  {
    const auto valid = OpenQuestionFactory().GetValidOpenQuestionStrings();
    for (const std::string& s: valid)
    {
      const auto q = OpenQuestionFactory().Create(s);
      const auto v = q->ToLines();
      const auto w = q->ToLines();
      assert(v == w);
    }
  }
}
#endif

std::vector<std::string> ribi::OpenQuestion::ToLines() const noexcept
{
  std::vector<std::string> v;

  const int screen_rows { 23 };
  const int question_rows { 1 };
  const int n_rows { screen_rows - question_rows };
  if (!GetFilename().empty())
  {
    int n_cols = 78;

    while (1)
    {
      const boost::shared_ptr<ImageCanvas> canvas {
        new ImageCanvas(GetFilename(),n_cols)
      };
      if (canvas->GetHeight() > n_rows)
      {
        --n_cols;
      }
      else
      {
        v = canvas->ToStrings();
        break;
      }
      if (n_cols == 5) break;
    }
  }
  v.push_back(this->GetQuestion()); //The one question_row
  return v;
}

std::string ribi::OpenQuestion::ToStr() const noexcept
{
  //Concatenate the correct answer
  assert(!GetCorrectAnswers().empty());
  std::string correct_answers_str;

  for (const std::string s: this->GetCorrectAnswers()) { correct_answers_str += s + "/"; }
  assert(!correct_answers_str.empty());
  correct_answers_str.resize(correct_answers_str.size() - 1);

  std::string s
    = this->GetFilename()
    + "," + this->GetQuestion()
    + "," + correct_answers_str;
  return s;
}

 

 

 

 

 

./CppOpenQuestion/openquestionfactory.h

 

#ifndef OPENQUESTIONFACTORY_H
#define OPENQUESTIONFACTORY_H


#include <string>
#include <vector>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include <boost/shared_ptr.hpp>
#pragma GCC diagnostic pop

namespace ribi {

struct OpenQuestion;

struct OpenQuestionFactory
{
  OpenQuestionFactory();

  boost::shared_ptr<OpenQuestion> Create(const std::string& question) const;

  boost::shared_ptr<OpenQuestion>
    Create(
      const std::string& filename,
      const std::string& question,
      const std::vector<std::string>& answers
  ) const noexcept;


  std::string GetExampleOpenQuestionString() const noexcept { return "-,1+1=,2/two/Two"; }
  std::vector<std::string> GetInvalidOpenQuestionStrings() const noexcept;
  std::vector<boost::shared_ptr<OpenQuestion>> GetTestOpenQuestions() const noexcept;
  std::vector<std::string> GetValidOpenQuestionStrings() const noexcept;

  static std::string GetVersion() noexcept;
  static std::vector<std::string> GetVersionHistory() noexcept;

  private:
  #ifndef NDEBUG
  static void Test() noexcept;
  #endif
};

} //~namespace ribi

#endif // OPENQUESTIONFACTORY_H

 

 

 

 

 

./CppOpenQuestion/openquestionfactory.cpp

 

#include "openquestionfactory.h"

#include <cassert>
#include <stdexcept>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
#include <boost/algorithm/string/split.hpp>
#include <boost/make_shared.hpp>

#include "container.h"
#include "openquestion.h"
#include "testtimer.h"
#include "trace.h"
#pragma GCC diagnostic pop

ribi::OpenQuestionFactory::OpenQuestionFactory()
{
  #ifndef NDEBUG
  Test();
  #endif
}

boost::shared_ptr<ribi::OpenQuestion>
  ribi::OpenQuestionFactory::Create(const std::string& s
) const
{
  const bool verbose{false};
  if (s.empty())
  {
    throw std::logic_error("An open question must contain text");
  }
  if (s[0] == ',')
  {
    throw std::logic_error("An open question must not start with a comma");
  }
  if (s[s.size() - 1] == ',')
  {
    throw std::logic_error("An open question must not end with a comma");
  }
  if (s.find(",,") != std::string::npos)
  {
    throw std::logic_error("An open question cannot contain two consecutive commas");
  }
  const auto v = Container().SeperateString(s,',');
  if (v.size() != 3)
  {
    if (verbose) { TRACE(s); }
    throw std::logic_error("An open question has exactly three comma-seperated elements");
  }
  const auto filename = v[0];
  const auto question = v[1];
  const auto answers = Container().SeperateString(v[2],'/');
  if (answers.size() == 0)
  {
    throw std::logic_error("An open question has at least one correct answer");
  }
  return Create(filename,question,answers);

}

boost::shared_ptr<ribi::OpenQuestion>
  ribi::OpenQuestionFactory::Create(
    const std::string& filename,
    const std::string& question,
    const std::vector<std::string>& answers
) const noexcept
{
  boost::shared_ptr<OpenQuestion> openquestion(
    new OpenQuestion(
      filename,question,answers
    )
  );
  return openquestion;
  //assert(!filename.empty() && "Filename must not be empty");
  //assert(FileExists(filename) == true && "File must exists");
  //assert(!question.empty() && "OpenQuestion must not be empty");
  //assert(!correct_answers.empty() && "Correct answer must not be empty");
}

/*
std::vector<std::string> ribi::OpenQuestionFactory::ExtractAnswers(const std::string& input)
{
  const std::vector<std::string> v = SeperateString(input,',');
  if (v.size() != 3)
  {
    throw std::logic_error("An open question has exactly three comma-seperated elements");
  }

  const std::vector<std::string> w = SeperateString(v[2],'/');
  if (w.size() == 0)
  {
    throw std::logic_error("An open question has at least one correct answer");
  }
  return w;
}
*/

std::vector<std::string>
  ribi::OpenQuestionFactory::GetInvalidOpenQuestionStrings() const noexcept
{
  return {
    "-,1+1=,2,3", //Incorrect options are
    "-,1+1=",   //No answer
    "-",        //No question
    "tmp.png",  //No question
    "",         //Nothing
    ",tmp.png,1+1=,2", //Start with comma
    "tmp.png,,1+1=,2", //Two consecutive comma's
    "tmp.png,1+1=,,2", //Two consecutive comma's
    "tmp.png,1+1=,2,", //Two consecutive comma's
    "tmp.png,1+1=,2,", //End with comma
    ",tmp.png,1+1=,2,", //Start and end with comma
    ",,tmp.png,1+1=,2,",
    ",tmp.png,,1+1=,2,",
    ",tmp.png,1+1=,,2,",
    ",tmp.png,1+1=,2,,",
    ",",
    ",,",
    ",,,",
    ",,,,",
    ",,,,,",
    ",,,,,,"
  };
}

std::vector<boost::shared_ptr<ribi::OpenQuestion>>
  ribi::OpenQuestionFactory::GetTestOpenQuestions() const noexcept
{
  std::vector<boost::shared_ptr<OpenQuestion>> v;
  for (const auto& s: GetValidOpenQuestionStrings())
  {
    v.push_back(Create(s));
  }
  return v;
}

std::vector<std::string>
  ribi::OpenQuestionFactory::GetValidOpenQuestionStrings() const noexcept
{
  return {
    "-,1+1=,2",
    "tmp.png,1+1=,2/Two",
    "-,1+1=,Two/2/two"
  };
}

std::string ribi::OpenQuestionFactory::GetVersion() noexcept
{
  return "1.0";
}

std::vector<std::string> ribi::OpenQuestionFactory::GetVersionHistory() noexcept
{
  return {
    "2014-06-05: version 1.0: initial version"
  };
}

#ifndef NDEBUG
void ribi::OpenQuestionFactory::Test() noexcept
{
  {
    static bool is_tested{false};
    if (is_tested) return;
    is_tested = true;
  }
  Container();
  OpenQuestionFactory().GetTestOpenQuestions();
  const TestTimer test_timer(__func__,__FILE__,1.0);
  OpenQuestionFactory f;
  try
  {
    const auto q = f.Create(f.GetExampleOpenQuestionString());
    assert(q);
  }
  catch (std::exception& e)
  {
    assert("OpenQuestionFactory::GetExampleOpenQuestion()"
        && "must yield a valid OpenQuestion");
  }
  //Test valid multiple choice questions for validity
  {
    for (const std::string& s: f.GetValidOpenQuestionStrings())
    {
      try
      {
        const auto q = f.Create(s);
        assert(q); //To make the compiler happy
        //OK
      }
      catch (std::exception& e)
      {
        TRACE("ERROR");
        TRACE(s);
        assert(!"Valid questions must be accepted");
      }
    }
  }
  //Test invalid multiple choice questions for invalidity
  {
    for (const std::string& s: f.GetInvalidOpenQuestionStrings())
    {
      try
      {
        const auto q = f.Create(s);
        TRACE("ERROR");
        TRACE(s);
        assert(!"Invalid questions must be rejected");
        assert(q); //To make the compiler happy
      }
      catch (std::exception& e)
      {
        //OK
      }
    }
  }
}
#endif

 

 

 

 

 

Go back to Richel Bilderbeek's C++ page.

Go back to Richel Bilderbeek's homepage.

 

Valid XHTML 1.0 Strict

This page has been created by the tool CodeToHtml