LT Project: Full Record Formatting

There are several layers to the formatting logic.  In part this is to gain flexibility, and in part it is because the original formatting was all directed towards using iostreams, and the lowest level formatters use ostreams to do basic formatting.  Later work to do formatting for GTK built on this, but retained the lowest level (which, especially for bibliographic formatting. included a substantial amount of textual manipulation which could remain encapsulated in the field formatters.

IFieldFormatter and implementations

The primary interface at this lowest level is IFieldFormatter:

class IFieldFormatter

{

public:

  virtual ~IFieldFormatter();

  virtual void formatId(const int inId) const = 0;

  virtual void format(std::ostream &outStream, const ELibraryRecord inRec,

                      const std::string &inValue) const = 0;

  virtual void formatTitle(std::ostream &outStream, const ELibraryRecord inRec,

                           const std::string &inValue,

                           const std::string_view inPostTitleValue) const = 0;

  virtual void

  formatAuthor(std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

               const std::string &inValue, const std::string &inValue2,

               const ISecondaryAuthorSet &inSecondaryAuthors) const = 0;

  virtual void markAsUnderReconsideration() const = 0;

};

The general call is just format: the others are driven by (1) juggling the primary and secondary author information to generate an integrated whole; and (2) the needs of bibliographic formatting to rearrange the order of author, title, and publication data.  The under reconsideration call is used only in some GTK contexts, where it generates the title in red.

The StandardFieldFormatter produces output in a simple form, roughly in the order the fields appear in the raw record:

Title: Beautiful C++: 30 Core Guidelines for Writing Clean, Safe, and Fast Code

Author: Davidson, J. Guy

Author: Gregory, Kate

Publication: Addison-Wesley Professional (2021), Edition: 1, 352 pages

Date: 2021

Summary: Beautiful C++: 30 Core Guidelines for Writing Clean, Safe, and Fast Code by J. Guy Davidson (2021)

Media: Paperback

Tags: Programming, C++

Collections: Your library, Willowdale, To Review

Languages: English

Original Languages: English

LC Classification: QA76.C153

ISBNs: 0137647840, 9780137647842

Dewey Decimal: 005.133

Dewey Wording: Computer programming, programs, data, security > Computing and Information > General Programming Languages > Information > Languages > Programming

Source: amazon.com books

Entry Date: [2022-01-11]

From Where: Amazon.ca


The equivalent bibliographic format is

Davidson, J. Guy and Kate Gregory. _Beautiful C++: 30 Core Guidelines for Writing Clean, Safe, and Fast Code_. Addison-Wesley Professional 2021.

or, for several books by the same author:

Tolkien, J. R. R. _Beowulf: A Translation and Commentary, together with Sellic Spell_. Ed. Christopher Tolkien. HarperCollins 2015.

--------. _Beren and Luthien_. Ed. Christopher Tolkien. Harper Collins 2017.

--------. _The Book of Lost Tales, Pt. 1_. HarperCollins Canada / Trade 1985.

--------. _The Book of Lost Tales: Pt. 2_. HarperCollins 1986.

--------. _The Children of Hurin_. Harper Collins Canada 2007.

--------. _The Fall of Arthur_. Ed. Christopher Tolkien. HarperCollins 2014.

--------. _The Father Christmas Letters_. Grafton 1976.

...

You can see that the title formatting for this style defaults to ordinary logic:

class StandardFieldFormatter : public IFieldFormatter

{

public:

  ~StandardFieldFormatter() noexcept override;

  void formatId(const int inId) const override {}

  void format(std::ostream &outStream, const ELibraryRecord inRec,

              const std::string &inValue) const override;

  void formatTitle(std::ostream &outStream, const ELibraryRecord inRec,

                   const std::string &inValue,

                   const std::string_view inPostTitleValue) const override

  {

    format(outStream, inRec, inValue);

  }

  void

  formatAuthor(std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

               const std::string &inValue, const std::string &inValue2,

               const ISecondaryAuthorSet &inSecondaryAuthors) const override;


  void markAsUnderReconsideration() const override {}

};

Much of the formatting simply involves looking up the field name and printing it:

namespace

{


std::map<LtLibrary::ELibraryRecord, std::string> Headers = {

  { LtLibrary::ELibraryRecord::Book_Id, "Book Id"s },

  { LtLibrary::ELibraryRecord::Title, "Title"s },

  { LtLibrary::ELibraryRecord::Sort_Character, "Sort Charcater"s },

  { LtLibrary::ELibraryRecord::Primary_Author, "Primary Author"s },

  { LtLibrary::ELibraryRecord::Primary_Author_Role, "Primary Author Role"s },

  { LtLibrary::ELibraryRecord::Secondary_Author, "Secondary Author"s },

  { LtLibrary::ELibraryRecord::Secondary_Author_Roles,

    "Secondary Author Roles"s },

  { LtLibrary::ELibraryRecord::Publication, "Publication"s },

  { LtLibrary::ELibraryRecord::Date, "Date"s },

  { LtLibrary::ELibraryRecord::Review, "Review"s },

  { LtLibrary::ELibraryRecord::Rating, "Rating"s },

  { LtLibrary::ELibraryRecord::Comment, "Comment"s },

  { LtLibrary::ELibraryRecord::Private_Comment, "Private Comment"s },

  { LtLibrary::ELibraryRecord::Summary, "Summary"s },

  { LtLibrary::ELibraryRecord::Media, "Media"s },

  { LtLibrary::ELibraryRecord::Physical_Description, "Physical Description"s },

  { LtLibrary::ELibraryRecord::Weight, "Weight"s },

  { LtLibrary::ELibraryRecord::Height, "Height"s },

  { LtLibrary::ELibraryRecord::Thickness, "Thickness"s },

  { LtLibrary::ELibraryRecord::Length, "Length"s },

  { LtLibrary::ELibraryRecord::Dimensions, "Dimensions"s },

  { LtLibrary::ELibraryRecord::Page_Count, "Page Count"s },

  { LtLibrary::ELibraryRecord::LCCN, "LCCN"s },

  { LtLibrary::ELibraryRecord::Acquired, "Acquired"s },

  { LtLibrary::ELibraryRecord::Date_Started, "Date Started"s },

  { LtLibrary::ELibraryRecord::Date_Read, "Date Read"s },

  { LtLibrary::ELibraryRecord::Barcode, "Barcode"s },

  { LtLibrary::ELibraryRecord::BCID, "BCID"s },

  { LtLibrary::ELibraryRecord::Tags, "Tags"s },

  { LtLibrary::ELibraryRecord::Collections, "Collections"s },

  { LtLibrary::ELibraryRecord::Languages, "Languages"s },

  { LtLibrary::ELibraryRecord::Original_Languages, "Original Languages"s },

  { LtLibrary::ELibraryRecord::LC_Classification, "LC Classification"s },

  { LtLibrary::ELibraryRecord::ISBN, "ISBN"s },

  { LtLibrary::ELibraryRecord::ISBNs, "ISBNs"s },

  { LtLibrary::ELibraryRecord::Subjects, "Subjects"s },

  { LtLibrary::ELibraryRecord::Dewey_Decimal, "Dewey Decimal"s },

  { LtLibrary::ELibraryRecord::Dewey_Wording, "Dewey Wording"s },

  { LtLibrary::ELibraryRecord::Other_Call_Number, "Other Call Number"s },

  { LtLibrary::ELibraryRecord::Copies, "Copies"s },

  { LtLibrary::ELibraryRecord::Source, "Source"s },

  { LtLibrary::ELibraryRecord::Entry_Date, "Entry Date"s },

  { LtLibrary::ELibraryRecord::From_Where, "From Where"s },

  { LtLibrary::ELibraryRecord::OCLC, "OCLC"s },

  { LtLibrary::ELibraryRecord::Work_id, "Work id"s },

  { LtLibrary::ELibraryRecord::Lending_Patron, "Lending Patron"s },

  { LtLibrary::ELibraryRecord::Lending_Status, "Lending Status"s },

  { LtLibrary::ELibraryRecord::Lending_Start, "Lending Start"s },

  { LtLibrary::ELibraryRecord::Lending_End, "Lending End"s }


};


}


namespace LtLibrary

{

void StandardFieldFormatter::format(std::ostream &outStream,

                                    const ELibraryRecord inRec,

                                    const std::string &inValue) const

{

  if ((inRec == ELibraryRecord::Private_Comment) && (inValue.length() > 3)

      && (inValue.substr(0, 3) == "Box"s))

    outStream << "Archived in: " << inValue << std::endl;

  else if (inRec != ELibraryRecord::Library_Record_None)

    outStream << Headers[inRec] << ": " << inValue << std::endl;

}

The author formatting groups secondary authors immediately after the primary author:

void StandardFieldFormatter::formatAuthor(

    std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

    const std::string &inValue, const std::string &inValue2,

    const ISecondaryAuthorSet &inSecondaryAuthors) const

{

  if (inValue2.empty())

    outStream << Headers[ELibraryRecord::Primary_Author];

  else

    outStream << inValue2;

  outStream << ": " << inValue << std::endl;

  inSecondaryAuthors.process(

      [&](const std::string &inName, const std::string &inRole) -> void {

        outStream << inRole << ": " << inName << std::endl;

      });

}

The bibliographic formatter is a bit more complicated:

class BibliographicFieldFormatter : public IFieldFormatter

{

public:

  ~BibliographicFieldFormatter() override;


  void formatId(const int inId) const override {}


  void format(std::ostream &outStream, const ELibraryRecord inRec,

              const std::string &inValue) const override;


  void formatTitle(std::ostream &outStream, const ELibraryRecord inRec,

                   const std::string &inValue,

                   const std::string_view inPostTitleValue) const override;

  void

  formatAuthor(std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

               const std::string &inValue, const std::string &inValue2,

               const ISecondaryAuthorSet &inSecondaryAuthors) const override;


  static void FormatAuthorTail(std::ostream &outStream,

                               ISettableAuthorContainer &outPostTitle,

                               const std::string &inValue,

                               const ISecondaryAuthorSet &inSecondaryAuthors,

                               const bool inNameEndsWithPeriod);


  void markAsUnderReconsideration() const override {}

};

There are a number of helper functions and classes which are visible only inside the cpp file in the anonymous namespace:

Two functions for munging data:

std::string

GetAdditionalData(const std::string &inRole, const std::string &inAbbrev,

                  const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors)

{

  switch (auto sainfo = inSecondaryAuthors.getSecondaryAuthorInfo(inRole);

          ssize(sainfo))

    {

    case 0:

      return {};

    case 1:

      return inAbbrev + " "s + sainfo.back() + ". "s;

    case 2:

      return inAbbrev + " "s + sainfo[0] + " and "s + sainfo[1] + ". "s;

    default:

      return inAbbrev + " "s + sainfo[0] + " et al. "s;

    }

}

void OutputWithConvertedDate(std::ostream &outStream,

                             const std::string &inValue)

{

  auto off = inValue.find(")");

  if ((off > 10) && std::isdigit(inValue[off - 1])

      && std::isdigit(inValue[off - 2]) && std::isdigit(inValue[off - 3])

      && std::isdigit(inValue[off - 4]) && (inValue[off - 5] == '(')

      && std::isspace(inValue[off - 6]))

    outStream << inValue.substr(0, off - 6) << " "

              << inValue.substr(off - 4, 4);

  else

    outStream << inValue;

}

And a whole hierarchy of formatting assistants:

class IBibliographicAuthorsFormatter

{

public:

  virtual ~IBibliographicAuthorsFormatter() {}

  virtual void format(std::ostream &outStream,

                      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors,

                      std::string_view inType) const = 0;

  virtual bool addPeriod() const = 0;

  virtual std::string getDataForEnd(

      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const = 0;

};


class AbstractBibliographicAuthorsFormatter

    : public IBibliographicAuthorsFormatter

{

public:

  virtual ~AbstractBibliographicAuthorsFormatter() {}

  void format(std::ostream &outStream,

              const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors,

              std::string_view inType) const override

  {

    std::vector<std::string> sainfo

        = inSecondaryAuthors.getSecondaryAuthorInfo(inType);

    switch (std::size_t sz = sainfo.size(); sz)

      {

      case 0:

        addFunctionAbbreviation(outStream, sz);

        break;

      case 1:

        outStream << " and " << sainfo.back();

        addFunctionAbbreviation(outStream, sz);

        break;

      default:

        CombineMultipleAuthors(outStream, sainfo);

        addFunctionAbbreviation(outStream, sz);

        break;

      }

  }

  virtual void addFunctionAbbreviation(std::ostream &outStream,

                                       const int inCount) const = 0;

private:

  static void CombineMultipleAuthors(std::ostream &outStream,

                                     const std::vector<std::string> &inSainfo)

  {

    std::string rval;

    std::ranges::for_each(inSainfo, [&rval](const auto &inVal) {

      rval.append(", ").append(inVal);

    });

    // Replace the last , by and

    auto offset = rval.rfind(",");

    outStream << rval.substr(0, offset) << " and" << rval.substr(offset + 1);

  }

};


class BibliographicAuthorsFormatter

    : public AbstractBibliographicAuthorsFormatter

{

public:

  BibliographicAuthorsFormatter(const bool inNameEndsWithPeriod):

      m_addPeriod(true), m_nameEndsWithPeriod(inNameEndsWithPeriod)

  { }

  ~BibliographicAuthorsFormatter() override {}


  void addFunctionAbbreviation(std::ostream &outStream,

                               const int inCount) const override

  {

    if (inCount == 0)

      m_addPeriod = !m_nameEndsWithPeriod;

  }

  bool addPeriod() const override { return m_addPeriod; }

  std::string getDataForEnd(

      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const override

  {

    return GetAdditionalData("Editor"s, "Ed."s, inSecondaryAuthors)

           + GetAdditionalData("Translator"s, "Trans."s, inSecondaryAuthors);

  }

private:

  mutable bool m_addPeriod;

  bool m_nameEndsWithPeriod;

};


class BibliographicUnknownTypeFormatter : public IBibliographicAuthorsFormatter

{

public:

  BibliographicUnknownTypeFormatter(const bool inNameEndsWithPeriod):

      m_nameEndsWithPeriod(inNameEndsWithPeriod)

  { }

  ~BibliographicUnknownTypeFormatter() override {}

  void format(std::ostream &outStream,

              const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors,

              std::string_view inType) const override

  { }

  bool addPeriod() const override { return !m_nameEndsWithPeriod; }

  std::string getDataForEnd(

      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const override

  {

    return GetAdditionalData("Editor"s, "Ed."s, inSecondaryAuthors)

           + GetAdditionalData("Translator"s, "Trans."s, inSecondaryAuthors);

  }

private:

  bool m_nameEndsWithPeriod;

};


class BibliographicEditorsFormatter

    : public AbstractBibliographicAuthorsFormatter

{

public:

  ~BibliographicEditorsFormatter() override {}

  void addFunctionAbbreviation(std::ostream &outStream,

                               const int inCount) const override

  {

    if (inCount == 0)

      outStream << ", ed.";

    else

      outStream << ", eds.";

  }

  bool addPeriod() const override { return false; }

  std::string getDataForEnd(

      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const override

  {

    return GetAdditionalData("Translator"s, "Trans."s, inSecondaryAuthors);

  }

};

Note that some of these are not stateless, but can vary depending on information passed in in the constructor.

The default format() function handles only publication, plus the null record, which actually prints a message regarding "no match" to the screen.

void BibliographicFieldFormatter::format(std::ostream &outStream,

                                         const ELibraryRecord inRec,

                                         const std::string &inValue) const

{

  switch (inRec)

    {

    case ELibraryRecord::Library_Record_None:

      outStream << inValue << std::endl;

      break;

    case ELibraryRecord::Publication:

      {

        std::string clarendon{ "Oxford at the Clarendon Press"s };


        std::size_t off = inValue.find(clarendon);

        if (off != std::string::npos)

          {

            std::string s(inValue.substr(0, off) + "Oxford: Clarendon Press"s

                          + inValue.substr(off + clarendon.length()));

            OutputWithConvertedDate(outStream, s);

          }

        else

          {

            OutputWithConvertedDate(outStream, inValue);

          }

        outStream << "." << std::endl;

      }

    default:

      break;

    }

}

The bulk of the special work is in the two specialized format functions.

formatAuthor() (and the static FormatAuthorTail() function it delegates to, which is public and used in other classes) reconfigure the author information into a bibliographic format, and passes out editor/translator information (which follows the title, unless the work has only editors and not an author) for use later on.

void BibliographicFieldFormatter::formatAuthor(

    std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

    const std::string &inValue, const std::string &inValue2,

    const ISecondaryAuthorSet &inSecondaryAuthors) const

{

  outStream << inValue;

  FormatAuthorTail(outStream, outPostTitle, inValue2, inSecondaryAuthors,

                   (*(inValue.crbegin()) == '.'));

}


void BibliographicFieldFormatter::FormatAuthorTail(

    std::ostream &outStream, ISettableAuthorContainer &outPostTitle,

    const std::string &inValue, const ISecondaryAuthorSet &inSecondaryAuthors,

    const bool inNameEndsWithPeriod)

{

  std::unique_ptr<IBibliographicAuthorsFormatter> theFormatter;

  if (inValue == "Editor"s)

    theFormatter = std::make_unique<BibliographicEditorsFormatter>();

  else if (inValue == "Author"s)

    theFormatter = std::make_unique<BibliographicAuthorsFormatter>(

        inNameEndsWithPeriod);

  else

    theFormatter = std::make_unique<BibliographicUnknownTypeFormatter>(

        inNameEndsWithPeriod);

  theFormatter->format(outStream, inSecondaryAuthors, inValue);

  outPostTitle.setPostTitleContent(

      theFormatter->getDataForEnd(inSecondaryAuthors));

  if (theFormatter->addPeriod())

    outStream << ". ";

  else

    outStream << " ";

}

tormatTitle() makes use of the post-title data generates in handling the authors:

void BibliographicFieldFormatter::formatTitle(

    std::ostream &outStream, const ELibraryRecord inRec,

    const std::string &inValue, const std::string_view inPostTitleValue) const

{

  outStream << "_" << inValue << "_. " << inPostTitleValue;

}

IFormatterWrapper

One level up, we have a slightly more generic interface: IFormatterWrapper.  The calls are similar, except that you will note that they do not have streams in their interfaces:

class IFormatterWrapper

{

public:

  virtual ~IFormatterWrapper();

  virtual void formatId(const int inId) const = 0;

  virtual void format(const ELibraryRecord inRec,

                      const std::string &inValue) const = 0;

  virtual void formatTitle(const ELibraryRecord inRec,

                           const std::string &inValue,

                           const std::string_view inPostTitleValue) const = 0;

  virtual void

  formatAuthor(ISettableAuthorContainer &outPostTitle,

               const std::string &inValue1, const std::string &inValue2,

               const ISecondaryAuthorSet &inSecondaryAuthors) const = 0;

  virtual void addDelimiter(const std::string &inVal) const = 0;

  virtual void markAsUnderReconsideration() const = 0;

};

The most straightforward implementation is indeed a simple stream formatter:

class StreamFieldFormatter : public IFormatterWrapper

{

public:

  StreamFieldFormatter(std::unique_ptr<IFieldFormatter> &&inFormatter,

                       std::ostream &inStream);

  ~StreamFieldFormatter() override;

  void formatId(const int inId) const override;

  void format(const ELibraryRecord inRec,

              const std::string &inValue) const override;

  void formatTitle(const ELibraryRecord inRec, const std::string &inValue,

                   const std::string_view inPostTitleValue) const override;

  void

  formatAuthor(ISettableAuthorContainer &outPostTitle,

               const std::string &inValue, const std::string &inValue2,

               const ISecondaryAuthorSet &inSecondaryAuthors) const override;

  void addDelimiter(const std::string &inVal) const override;

  void markAsUnderReconsideration() const override {}

private:

  std::unique_ptr<IFieldFormatter> m_formatter;

  std::ostream &m_stream;

};

This essentially treats the IFieldFormatter as a simple delegate, supplying the member stream as an additional argument:

void StreamFieldFormatter::formatId(const int inId) const

{

  m_formatter->formatId(inId);

}

void StreamFieldFormatter::format(const ELibraryRecord inRec,

                                  const std::string &inValue) const

{

  m_formatter->format(m_stream, inRec, inValue);

}

void StreamFieldFormatter::formatTitle(

    const ELibraryRecord inRec, const std::string &inValue,

    const std::string_view inPostTitleValue) const

{

  m_formatter->formatTitle(m_stream, inRec, inValue, inPostTitleValue);

}

void StreamFieldFormatter::formatAuthor(

    ISettableAuthorContainer &outPostTitle, const std::string &inValue,

    const std::string &inValue2,

    const ISecondaryAuthorSet &inSecondaryAuthors) const

{

  m_formatter->formatAuthor(m_stream, outPostTitle, inValue, inValue2,

                            inSecondaryAuthors);

}

The additional function addDelimiter() can provide a marker between representations:

void StreamFieldFormatter::addDelimiter(const std::string &inVal) const

{

  m_stream << inVal << std::endl;

}

This could in principle be used for both full and bibliographic output, except for one thing: in bibliographic output the behaviour of the formatting changes when there are two or more works by the same author.  So there is also a VariableStreamFieldFormatter which ios almost like the basic class except that it can change formatting logic and can be created without an immediate formatting capability by initialization with a null object.

class VariableStreamFieldFormatter : public IVariableFormatterWrapper

{

public:

  VariableStreamFieldFormatter(std::ostream &inStream);


  VariableStreamFieldFormatter(std::ostream &inStream, IFieldFormatter *inVal);

...

  void setFormatter(IFieldFormatter *inVal) override

  {

    m_formatter.reset(inVal);

  }

  std::unique_ptr<IVariableFormatterWrapper>

  clone(IFieldFormatter *inVal) const override;

...

private:

  std::unique_ptr<IFieldFormatter> m_formatter;

  std::ostream &m_stream;

};

This inherits from the interface class IVariableFormatterWrapper:

class IVariableFormatterWrapper : public IFormatterWrapper

{

ipublic:

  virtual ~IVariableFormatterWrapper();

  virtual void setFormatter(IFieldFormatter *inVal) = 0;

  virtual std::unique_ptr<IVariableFormatterWrapper>

  clone(IFieldFormatter *inVal) const = 0;

};

IFormatter

The higher levels don't call the specific formatting functions directly.  There's a higher-level interface with a single call for that:

class IFormatter

{

public:

  virtual ~IFormatter();

  virtual void formatRecord(const ILibraryBookRecord &inRec) const = 0;

};

Let's look first at how the simplest version of formatting works: for full information being printed to the console:

class SimpleFormatter : public IFormatter

{

public:

  SimpleFormatter(std::unique_ptr<IFormatterWrapper> inFieldFormatter):

      m_formatter(std::move(inFieldFormatter)), m_delimiter("***************")

  { }

  ~SimpleFormatter() override;

  void formatRecord(const ILibraryBookRecord &inRec) const override;

private:

  std::unique_ptr<IFormatterWrapper> m_formatter;

  std::string m_delimiter;

};

void SimpleFormatter::formatRecord(const ILibraryBookRecord &inRec) const

{

  if (inRec.getId() == 0)

    return;

  if (inRec.isUnderReconsideration())

    m_formatter->markAsUnderReconsideration();

  inRec.printAll(*m_formatter);

  m_formatter->addDelimiter(m_delimiter);

}

In practice, this is created in a factory function as:

      return std::make_unique<SimpleFormatter> (

          std::make_unique<StreamFieldFormatter> (

              std::make_unique<StandardFieldFormatter>(), m_stream));

All existing applications use std::cout as the stream, although in principle they could use a file stream or some other custom stream.

However, the SimpleFormatter has other uses; there is a reason that it delegates to a field formatter.  In a GTK context, we want to do more detailed formatting of the data being produced, and in general writing to a stream is (at best) an interim step (this happens with bibliographic representations).  For full data displays there is a considerably more complicated implementation.  Right now we'll look at the general model and next post will deal with the more complicated formatting problems.

class NoStreamFieldFormatter : public LtLibrary::IFormatterWrapper

{

public:

  explicit NoStreamFieldFormatter(GtkTextBuffer *inBuffer);

  ~NoStreamFieldFormatter() override;

  void formatId(const int inId) const override;

  void format(const LtLibrary::ELibraryRecord inRec,

              const std::string &inValue) const override;

  void formatTitle(const LtLibrary::ELibraryRecord inRec,

                   const std::string &inValue,

                   const std::string_view inPostTitleValue) const override;

  void formatAuthor(

      LtLibrary::ISettableAuthorContainer &outPostTitle,

      const std::string &inValue, const std::string &inValue2,

      const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const override;

  void addDelimiter(const std::string &inVal) const override {}

  void markAsUnderReconsideration() const override;

private:

  void insertCategory(const std::string &inTitle) const;

  void insertPlainText(const std::string &inValue) const;

  void insertSmallText(const std::string &inValue) const;

  GtkTextBuffer *m_buffer;

  mutable GtkTextIter m_iter;

  mutable IBibliographicTitleInserter *m_inserter;

};

insertCategory(), insertPlainText(), and insertSmallText() are convenience wrappers around slightly ugly GTK calls.

void NoStreamFieldFormatter::insertCategory(const std::string &inTitle) const

{

  gtk_text_buffer_insert_with_tags_by_name(

      m_buffer, &m_iter, (inTitle + ": ").c_str(), -1, "bold", nullptr);

}

void NoStreamFieldFormatter::insertPlainText(const std::string &inValue) const

{

  gtk_text_buffer_insert(m_buffer, &m_iter, inValue.c_str(), -1);

}

void NoStreamFieldFormatter::insertSmallText(const std::string &inValue) const

{

  gtk_text_buffer_insert_with_tags_by_name(m_buffer, &m_iter, inValue.c_str(),

                                           inValue.length(), "small", nullptr);

}

formatAuthor() is a fairly simple example of the GTK formatting logic:

void NoStreamFieldFormatter::formatAuthor(

    LtLibrary::ISettableAuthorContainer &outPostTitle,

    const std::string &inValue, const std::string &inValue2,

    const LtLibrary::ISecondaryAuthorSet &inSecondaryAuthors) const

{

  gtk_text_buffer_get_end_iter(m_buffer, &m_iter);

  if (inValue2.empty())

    insertCategory("Author");

  else

    insertCategory(inValue2);

  insertPlainText(inValue + "\n");

  auto l = [&](const std::string &inName, const std::string &inRole) -> void {

    insertCategory(inRole);

    insertPlainText(inName + "\n");

  };

  inSecondaryAuthors.process(l);

}


as is formatTitle():


void NoStreamFieldFormatter::formatTitle(

    const LtLibrary::ELibraryRecord inRec, const std::string &inValue,

    const std::string_view inPostTitleValue) const

{

  gtk_text_buffer_get_end_iter(m_buffer, &m_iter);


  insertCategory("Title");


  m_inserter = m_inserter->insertTitle(inValue, m_buffer, &m_iter);


  insertPlainText("\n");

}

The general format() function, though has to cope with the moderately complex review field, whuiuch is marked up with a (very small) subset of HTML.  That gets put intio its own function which we will not dive into yet here:

void NoStreamFieldFormatter::format(const LtLibrary::ELibraryRecord inRec,

                                    const std::string &inValue) const

{

  if ((inRec == LtLibrary::ELibraryRecord::Sort_Character)

      || (inRec == LtLibrary::ELibraryRecord::Book_Id)

      || (inRec == LtLibrary::ELibraryRecord::Work_id)

      || ((inRec == LtLibrary::ELibraryRecord::ISBN) && inValue == "[]"s))

    return;

  gtk_text_buffer_get_end_iter(m_buffer, &m_iter);

  if ((inRec == LtLibrary::ELibraryRecord::Private_Comment)

      && (inValue.length() > 3) && (inValue.substr(0, 3) == "Box"s))

    {

      insertCategory("Archived in");

      insertPlainText(inValue + "\n");

    }

  else

    {

      insertCategory(IdToCategoryName(inRec));

      if (((inRec == LtLibrary::ELibraryRecord::Subjects)

           || (inRec == LtLibrary::ELibraryRecord::Dewey_Wording))

          && (inValue.length() > 120))

        {

  insertSmallText(inValue);

          insertPlainText("\n");

        }

      else if (inRec == LtLibrary::ELibraryRecord::Review)

        {

          formatReview(inValue);

        }

      else

        {

          insertPlainText(inValue + "\n");

        }

    }

}

In the GTK application, the SimpleFormatter uses the NoStream delegate:

  LtLibrary::SimpleFormatter formatter(

      std::make_unique<NoStreamFieldFormatter>(theTextBuffer));

  m_data->print(formatter);

The HTML parsing and the display logic involved in handling reviews will be handled in the next post.


Comments

Popular posts from this blog

Boundaries

State Machines

Considerations on an Optimization