DayElement

Moving back to the Breviary Project...

We left off with XML element handlers for all of the offices having been described.  The next level above that is the handler for the day as a whole, which is also the man place where the rest of the office representation logic comes together with the parsed logic.

In particular, the DayElement is not only a mechanism for extracting the XML data, but its presentation of that data is determined by its being also derived from IFullDayInfo.

IFullDayInfo inherits from the two main interfaces for managing data: IHoursInfo (which presents information on the day as a whole) and IOfficeInfoSource, which presents the information for the component hours via accessors.  It also has a few extra operations defined which are used only by IFullDayInfo.

class IFullDayInfo : public IHoursInfo, public IOfficeInfoSource

{

public:

  virtual ~IFullDayInfo();

  virtual bool hasPartialParent() const = 0;

  virtual void overrideGrade(const Grades inGrade) = 0;

  virtual const std::string& getParent() const = 0;

  virtual bool isEmpty() const = 0;

};

The DayElement is not the only implementation of IFullDayInfo.  There are also FerialFullDayInfo, which models the "baseline" (ordinary time, not a feast) ferial office, and SundayFullDayInfo.  These are invariably used as bases for a specific day in combination with information which is read in from the XML source and do not have the collect defined.  They are referenced from the XML as parents of specific Sundays:

 <day grade="DOMINICA" type="major_hours_antiphons_only" name="Fourteenth Sunday after Pentecost" parent="SUNDAY">

      <collect>Almighty and merciful God, of Whose only gift it cometh that Thy faithful people do unto Thee true and laudable service; grant, we beseech Thee, that we may so faithfully serve Thee in this life, that we fail not finally to attain Thy heavenly promises; through the merits of Jesus Christ our Lord. Amen.</collect>

    <Lauds>

      <BenedictusAntiphon>A certain man went down from Jerusalem to Jericho, and fell among thieves : which stripped him of his raiment, and wounded him, and departed, leaving him half dead.</BenedictusAntiphon>

    </Lauds>

    <Vespers type="Second">

      <MagnificatAntiphon>Which now of these three, thinkest thou, was neighbour unto him that fell among the thieves? : And he said. He that shewed mercy on him. Go, and do thou likewise, alleluia.</MagnificatAntiphon>

    </Vespers>

  </day>

How Reading the Office is Structured

In saying the office with a breviary, a human will typically have markers in two to three locations:

1) The location giving the hour as it is said, at a baseline on the day of the week. (This can be the only location for Prime and Compline, which do not use the normal collect,)

2) A second location giving at minimum the collect of the day, frequently a Magnificat and Benedictus antiphons, and sometimes rather more information

3) Possibly a Common for many feasts, which is layered between the first two, as it were.

That is, on, say, Lauds of the first Monday of Lent, you would have a marker at Lauds for Monday, which gives you the psalms and canticle and the ferial responses; a marker at the First Monday of Lent, which has its own collect: the two locations give you everything you need for the office.  But for, say, the Feast of St. Thomas Aquinas, you need the office of the day (whatever day of the week it is), the office for St. Thomas, which gives you the collect and major antiphons, and the office for a Doctor of the Church, which gives you most of the rest of the office.

It is modelling these combinations which is the major structural challenge of automating the breviary.  There have to be cues and rules for combining and overriding features.

The XML Day Tag is where the variable part of this is encoded. (It is based on some specific logic for certain circumstances as well.)  It has five basic types:

  enum class Type

  {

    collect_only,

    common, // complete, but may not have collect (e.g. for apostles)

    complete,

    major_hours_antiphons_only, // can include collect

    partial

  };

"Partial" is a grab-bag: it can be any subset of "complete" which is not a full subset.

These work together with the "parent" and "partialParent" attributes.

  <day grade="DOUBLE" type="collect_only" name="St. Bernard, Abb.C.D." date="08-20" parent="Doctor">

  <day grade="FERIA" type="major_hours_antiphons_only" name="Tuesday after Advent I" partialParent="Monday after Advent I">

Parent points from a specific day record to a more general one, such as a common or a Sunday.  PartiualParent points to a parent which itself has a parent, i.e. is an indication that three levels of data have to be combined.

So a DayElement (or any IFullDayInfo) does not represent "the office of the day": it represents one level of the office of the day, which may be combined with other cases of IFullDayInfo to generate the complete office for printing.

The Day Tag

class DayTag : public BreviaryTag

{

public:

  enum class Type

  {

    collect_only,

    common, // complete, but may not have collect (e.g. for apostles)

    complete,

    major_hours_antiphons_only,

    partial

  };


  DayTag(): BreviaryLib::BreviaryTag(GetTagName()) {}

  ~DayTag() override;

  const std::string &getCommemoration() const { return m_commemoration; }

  std::chrono::year_month_day getDay(std::chrono::year inYear) const;

  const std::string &getParent() const { return m_parent; }

  Grades getGrade() const { return m_grade; }

  Type getType() const { return m_type; }

  bool hasPartialParent() const { return m_partialParent; }

  void overrideGrade(const Grades inGrade) { m_grade = inGrade; }

  static std::string GetTagName() { return "day"; }

private:

  int allowedAttributeCount() const override { return 6; }

  std::span<std::string> getAllowedAttributes() const override;

  bool validate(std::string_view inAttribute,

                std::string_view inValue) const override;

  void setValue(std::string_view inAttribute,

                std::string_view inValue) override;

  bool checkMandatoryAttributes() const override

  {

    if (!hasAttribute("grade") || !hasAttribute("type")

        || !hasAttribute("name"))

      return false;

    if (((getAttribute("type") == "collect_only")

         || (getAttribute("type") == "partial")

         || (getAttribute("type") == "major_hours_antiphons_only"))

        && !(hasAttribute("parent") || hasAttribute("partialParent")))

      return false;

    return true;

  }

  std::string m_commemoration;

  std::string m_day;

  std::string m_parent;

  Grades m_grade = Grades::FERIA;

  Type m_type = Type::partial;

  bool m_partialParent = false;

};

The Day Tag must have a parent if it is marked as partial in any way.

std::span<std::string>

DayTag::getAllowedAttributes() const

{

  static std::array<std::string, 6> rval{ "grade"s, "type"s, "name"s, "date"s,

    "parent"s, "partialParent" };

  return rval;

}

We need to know the grade associated with a day (this controls logic relating to displacement of observations, as well as determining whether festal or ferial rules are used); the type of record; the name of the record; for a saint's day or other feast in the Proper of Saints, the day of the year; and either the parent or partialParent name if this is a partial record.  The name of the parent types should either match a name attribute of another record or an in-memory type such as SUNDAY.

bool

DayTag::validate(std::string_view inAttribute, std::string_view inValue) const

{

  if (inAttribute == "type"sv)

    {

      return ((inValue == "partial"sv) || (inValue == "complete"sv)

              || (inValue == "common"sv) || (inValue == "collect_only"sv)

              || (inValue == "major_hours_antiphons_only"sv));

    }

  else if (inAttribute == "grade"sv)

    {

      return ((inValue == "FERIA"sv) || (inValue == "LESSER_OCTAVE"sv)

              || (inValue == "FESTAL_FERIA"sv) || (inValue == "SIMPLE"sv)

              || (inValue == "GREATER_SIMPLE"sv)

              || (inValue == "GREATER_OCTAVE"sv) || (inValue == "OCTAVE_DAY"sv)

              || (inValue == "DOMINICA"sv) || (inValue == "SEMIDOUBLE"sv)

      || (inValue == "GREATER_DOUBLE"sv)

              || (inValue == "DOUBLE"sv) || (inValue == "PRIVILEGED_FERIA"sv)

      || (inValue == "PRIVILEGED_VIGIL"sv)

              || (inValue == "DOUBLE_SECOND_CLASS"sv)

              || (inValue == "DOMINICA_SECOND_CLASS"sv)

              || (inValue == "DOUBLE_FIRST_CLASS"sv)

              || (inValue == "DOMINICA_FIRST_CLASS"sv));

    }

  else if ((inAttribute == "name"sv) || (inAttribute == "parent"sv) || (inAttribute == "partialParent"sv))

    {

      return !inValue.empty();

    }

  else if (inAttribute == "date"sv)

    {

      return ((inValue.length() == 5) && std::isdigit(inValue[0])

              && std::isdigit(inValue[1]) && (inValue[2] == '-')

              && std::isdigit(inValue[3]) && std::isdigit(inValue[4]));

    }

  return false;

}

Setting the value is straightforward if lengthy:

void DayTag::setValue(std::string_view inAttribute, std::string_view inValue)

{

  if (inAttribute == "type")

    {

      if (inValue == "partial"sv)

        {

          m_type = Type::partial;

        }

      else if (inValue == "complete"sv)

        {

          m_type = Type::complete;

        }

      else if (inValue == "common"sv)

        {

          m_type = Type::common;

  m_day.clear();

        }

      else if (inValue == "collect_only"sv)

        {

          m_type = Type::collect_only;

        }

      else if (inValue == "major_hours_antiphons_only"sv)

        {

          m_type = Type::major_hours_antiphons_only;

        }

    }

  else if (inAttribute == "grade"sv)

    {

      if (inValue == "FERIA"sv)

        {

          m_grade = Grades::FERIA;

        }

      else if (inValue == "LESSER_OCTAVE"sv)

        {

          m_grade = Grades::LESSER_OCTAVE;

        }

      else if (inValue == "FESTAL_FERIA"sv)

        {

          m_grade = Grades::FESTAL_FERIA;

        }

      else if (inValue == "SIMPLE"sv)

        {

          m_grade = Grades::SIMPLE;

        }

      else if (inValue == "GREATER_SIMPLE"sv)

        {

          m_grade = Grades::GREATER_SIMPLE;

        }

      else if (inValue == "GREATER_OCTAVE"sv)

        {

          m_grade = Grades::GREATER_OCTAVE;

        }

      else if (inValue == "OCTAVE_DAY"sv)

        {

          m_grade = Grades::OCTAVE_DAY;

        }

      else if (inValue == "DOMINICA"sv)

        {

          m_grade = Grades::DOMINICA;

        }

      else if (inValue == "SEMIDOUBLE"sv)

        {

          m_grade = Grades::SEMIDOUBLE;

        }

      else if (inValue == "DOUBLE"sv)

        {

          m_grade = Grades::DOUBLE;

        }

      else if (inValue == "GREATER_DOUBLE"sv)

        {

          m_grade = Grades::GREATER_DOUBLE;

        }

      else if (inValue == "PRIVILEGED_FERIA"sv)

        {

          m_grade = Grades::PRIVILEGED_FERIA;

        }

      else if (inValue == "PRIVILEGED_VIGIL"sv)

        {

          m_grade = Grades::PRIVILEGED_VIGIL;

        }

      else if (inValue == "DOUBLE_SECOND_CLASS"sv)

        {

          m_grade = Grades::DOUBLE_SECOND_CLASS;

        }

      else if (inValue == "DOMINICA_SECOND_CLASS"sv)

        {

          m_grade = Grades::DOMINICA_SECOND_CLASS;

        }

      else if (inValue == "DOUBLE_FIRST_CLASS"sv)

        {

          m_grade = Grades::DOUBLE_FIRST_CLASS;

        }

      else if (inValue == "DOMINICA_FIRST_CLASS"sv)

        {

          m_grade = Grades::DOMINICA_FIRST_CLASS;

        }

    }

  else if (inAttribute == "name"sv)

    {

      m_commemoration = inValue;

    }

  else if (inAttribute == "parent"sv)

    {

      m_parent = inValue;

    }

  else if (inAttribute == "partialParent"sv)

    {

      m_parent = inValue;

      m_partialParent = true;

    }

  else if (inAttribute == "date"sv)

    {

      if (m_type != Type::common)

m_day = inValue;

    }

}

The getDay() function is not currently needed -- previous logic uses the data to determine the name for lookup -- but the attribute is used for validating and otherwise working with the XML data and we provide a function supporting testing.

std::chrono::year_month_day

      DayTag::getDay(std::chrono::year inYear) const

{

  unsigned int month = static_cast<unsigned int>(std::atoi(m_day.substr(0, 2).c_str()));

  unsigned int day = static_cast<unsigned int>(std::atoi(m_day.substr(3).c_str()));

  return std::chrono::year_month_day{ inYear, std::chrono::month{ month },

                                      std::chrono::day{ day } };

}

The Day Element

The DayElement is, of necessity, a somewhat unwieldy class:

class DayElement : public MultiElementElement, public IFullDayInfo

{

public:

  DayElement(const IPsalter &inPsalter, const Use inUse,

             const HymnSeasons inSeason, std::string_view inText,

             const Days inDay);

  ~DayElement() override;

  const IPartialOfficeInfo &getPartialInfo() const override

  {

    return *m_partialInfo;

  }

  const StandardOfficeInfo &getStandardInfo() const override

  {

    return *m_standardInfo;

  }

  const std::string &getStartTagName() const override

  {

    return m_tag.getName();

  }

  bool isPartial() const override { return m_isPartial; }

  DayTag::Type getType() const { return m_tag.getType(); }

  const std::string &getParent() const override { return m_tag.getParent(); }

  bool hasFirstVespers() const override { return m_hasFirstVespers; }

  std::string getHymn(const Days inDay, OfficeNames inOffice) const override;

  std::string getCollect() const override

  {

    return m_standardInfo->getCollect();

  }

  Grades grade() const override { return m_tag.getGrade(); }

  bool followRomanUse() const override { return (m_use != Use::Anglican); }

  std::unique_ptr<FerialPetitions>

  generateFerialPetitions(const OfficeNames inOffice) const override

  {

    return std::make_unique<FerialPetitions>(grade(), m_standardInfo->getDay(),

                                             m_season, inOffice);

  }

  HymnSeasons getSeason() const override { return m_season; }

  bool hasPartialParent() const override { return m_tag.hasPartialParent(); }

  void overrideGrade(const Grades inGrade) override

  {

    m_tag.overrideGrade(inGrade);

    m_standardInfo->overrideGrade(inGrade);

    m_partialInfo->overrideGrade(inGrade);

  }

  bool isEmpty() const override { return m_tag.getCommemoration().empty(); }


private:

  DayTag m_tag;

  std::unique_ptr<IPartialOfficeInfo> m_partialInfo;

  bool m_isPartial;

  HymnSeasons m_season;

  Use m_use;

  std::string m_laudsHymn;

  std::string m_vespersHymn;

  std::unique_ptr<StandardOfficeInfo> m_standardInfo;

  bool m_hasFirstVespers = false;


  void throwOnMissingElement(const std::string &inName,

                             std::string_view inText);


  auto extractCollect(const Use inUse, std::string_view inText,

                      PartialOfficeInfo *inPartialInfo = nullptr)

      -> std::string_view::size_type;


  template <typename T>

  auto extractMinorHour(const std::string &inName,

                        const ILaudsAntiphons &inLaudsAnt, const T &inElement)

      -> std::string_view::size_type

  {

    incrementLength(inElement.getLength());

    inElement.setInHolder(*m_standardInfo, inLaudsAnt,

                          (getSeason() == HymnSeasons::PASSIONTIDE));

    return inElement.getLength();

  }

  void processSubstantiveHours(const IPsalter &inPsalter,

                               std::string_view inText, const Days inDay);

};

The constructor branches on the tag's type attribute to determine what to look for, how to store it, and whether to report an error if a given subelement is not found:

DayElement::DayElement(const IPsalter &inPsalter, const Use inUse,

                       const HymnSeasons inSeason, std::string_view inText,

                       const Days inDay):

    MultiElementElement(inText, DayTag::GetTagName()),

    m_partialInfo(std::make_unique<NullPartialOfficeInfo>()),

    m_isPartial(true), m_season(inSeason), m_use(inUse)

{

  auto val = m_tag.set(inText.substr(0, getLength()));

  if (!val.has_value())

    {

      throw OfficeParseException(val.error(), inText.substr(0, getLength()));

    }

  if (m_tag.isClosed())

    throw OfficeParseException("Cannot have a closed " + m_tag.getName()

                               + " element");

  m_standardInfo = std::make_unique<StandardOfficeInfo>(

      inPsalter, m_tag.getCommemoration(), m_tag.getGrade(), inDay, inSeason);

  std::string_view rest(inText.substr(getLength()));

  adjustBetweenTags(rest);

  if (m_tag.getType() == DayTag::Type::collect_only)

    {

      if (!rest.starts_with("<collect"))

        {

          throw OfficeParseException("Only legal subtags of collect_only "

                                     + m_tag.getName()

                                     + " element are collect, collects");

        }

      rest.remove_prefix(extractCollect(inUse, rest));

      adjustBetweenTags(rest);

      if (!rest.starts_with(getEndTag()))

        throw OfficeParseException(

            "Extraneous element in collect-only day element: "s, rest);

      incrementLength(getEndTag().length());

      m_partialInfo = std::make_unique<CollectOnlyPartialOfficeInfo>(

          m_tag.getCommemoration(), m_tag.getParent(), m_tag.getGrade(),

          m_standardInfo->getCollect());

    }

  else if (m_tag.getType()

           == DayTag::Type::major_hours_antiphons_only) // actually includes

                                                        // collect

    {

      if (rest.starts_with("<collect"))

        {

          rest.remove_prefix(extractCollect(inUse, rest));

          adjustBetweenTags(rest);

        }

      if (!rest.starts_with("<Lauds"))

        {

          throw OfficeParseException(

              "Lauds element mandatory in major hours antiphons day element",

              rest);

        }

      LaudsElement lauds_elem(inPsalter, inUse, m_tag.getCommemoration(), rest,

                              inSeason, inDay, m_tag.getGrade());

      auto bened = lauds_elem.getBenedictusAntiphon();

      if (bened.empty())

        throw OfficeParseException(

            "Lauds element with no canticle antiphon in major hours antiphons day element",

            rest);

      m_standardInfo->addCanticleAntiphon(OfficeNames::LAUDS, bened);

      rest.remove_prefix(incrementLength(lauds_elem.getLength()));

      adjustBetweenTags(rest);

      VespersElement vesp_elem(inUse, inDay, rest, inSeason);


      if (!vesp_elem.hasMagnificatAntiphon())

        throw OfficeParseException(

            "Vespers element with no canticle antiphon in major hours antiphons day element",

            rest);

      auto mag = vesp_elem.getPartialOfficeInfo().antiphon;

      m_standardInfo->addCanticleAntiphon(OfficeNames::VESPERS, mag, false);

      rest.remove_prefix(incrementLength(vesp_elem.getLength()));

      adjustBetweenTags(rest);

      if (!rest.starts_with(getEndTag()))

        throw OfficeParseException(

            "Extraneous element in antiphon-only day element: "s, rest);

      incrementLength(getEndTag().length());

      m_partialInfo = std::make_unique<AntiphonsOnlyPartialOfficeInfo>(

          m_tag.getCommemoration(), m_tag.getParent(), m_tag.getGrade(),

          m_standardInfo->getCollect(), bened, mag);

    }

  else if (m_tag.getType() == DayTag::Type::complete)

    {

      m_isPartial = false;

      if (!rest.starts_with("<collect"))

        {

          throw OfficeParseException("Complete " + m_tag.getName()

                                     + " element must have collect");

        }

      rest.remove_prefix(extractCollect(inUse, rest));

      rest.remove_prefix(compareActualWithExpectedIndex(

          rest, getNextTagIndex(rest), incrementOverWhitespace(rest)));

      processSubstantiveHours(inPsalter, rest, inDay);

    }

  else if (m_tag.getType() == DayTag::Type::common)

    {

      m_isPartial = false;

      if (rest.starts_with("<collect"))

        {

          rest.remove_prefix(extractCollect(inUse, rest));

          rest.remove_prefix(compareActualWithExpectedIndex(

              rest, getNextTagIndex(rest), incrementOverWhitespace(rest)));

        }

      processSubstantiveHours(inPsalter, rest, inDay);

    }

  else if (m_tag.getType() == DayTag::Type::partial)

    {

      m_partialInfo = std::make_unique<PartialOfficeInfo>(

          m_tag.getCommemoration(), m_tag.getGrade());

      PartialOfficeInfo *ptr

          = static_cast<PartialOfficeInfo *>(m_partialInfo.get());

      ptr->setBase(m_tag.getParent());

      if (rest.starts_with("<collect"))

        {

          rest.remove_prefix(extractCollect(inUse, rest, ptr));

          adjustBetweenTags(rest);

        }

      if (rest.starts_with("<Vespers"))

        {

          m_hasFirstVespers = true;

          VespersElement vesp_elem(m_use, PreviousDay(inDay), rest, m_season);

          ptr->addPartialInfoForOffice(vesp_elem.getPartialOfficeInfo(),

                                       OfficeNames::VESPERS, true);

          rest.remove_prefix(incrementLength(vesp_elem.getLength()));

          adjustBetweenTags(rest);

        }

      LaudsChapter laudsChapt;

      LaudsAntiphons laudsAnt;

      if (rest.starts_with("<Lauds"))

        {

          LaudsElement lauds_elem(inPsalter, m_use, m_tag.getCommemoration(),

                                  rest, m_season, inDay, m_tag.getGrade());

          auto info = lauds_elem.getPartialOfficeInfo();

          ptr->addPartialInfoForOffice(info, OfficeNames::LAUDS);

          rest.remove_prefix(incrementLength(lauds_elem.getLength()));

          adjustBetweenTags(rest);

          laudsChapt = LaudsChapter(info.chapter, info.chapterSrc,

                                    info.chapterResponses, info.hymnName);

          laudsAnt = LaudsAntiphons(m_tag.getCommemoration(),

                                    info.majorHourAntiphons,

                                    (info.majorHourAntiphons.size() == 5));

          if (rest.starts_with("<Prime"))

            {

              PrimeElement element(rest);

              if (!element.getAntiphon().empty())

                ptr->addHourAntiphon(OfficeNames::PRIME,

                                     element.getAntiphon());

              rest.remove_prefix(incrementLength(element.getLength()));

              adjustBetweenTags(rest);

            }

          if (rest.starts_with("<Terce"))

            {

              if (lauds_elem.hasChapter())

                {

                  rest.remove_prefix(

                      incrementLength(StorePartialMinorHoursElement(

                          *ptr, TerceElement(laudsChapt, rest),

                          OfficeNames::TERCE, laudsAnt)));

                }

              else

                {

                  rest.remove_prefix(incrementLength(

                      StorePartialMinorHoursElement(*ptr, TerceElement(rest),

                                                    OfficeNames::TERCE,

                                                    laudsAnt)));

                }

      adjustBetweenTags(rest);

            }

        }

      // We should never have terce without a preceding Lauds; we can move on

      // to Sext

      if (rest.starts_with("<Sext"))

        {

          rest.remove_prefix(incrementLength(StorePartialMinorHoursElement(

              *ptr, SextElement(rest), OfficeNames::SEXT, laudsAnt)));

  adjustBetweenTags(rest);

        }

      if (rest.starts_with("<None"))

        {

          rest.remove_prefix(incrementLength(StorePartialMinorHoursElement(

              *ptr, NoneElement(rest), OfficeNames::NONE, laudsAnt)));

  adjustBetweenTags(rest);

        }

      if (rest.starts_with("<Vespers"))

        {

          VespersElement vesp_elem(m_use, laudsChapt, laudsAnt, inDay, rest,

                                   m_season);

          ptr->addPartialInfoForOffice(vesp_elem.getPartialOfficeInfo(),

                                       OfficeNames::VESPERS, false);

          rest.remove_prefix(incrementLength(vesp_elem.getLength()));

          adjustBetweenTags(rest);

        }

      if (!rest.starts_with(getEndTag()))

        throw OfficeParseException(

            "Extraneous element in partial day element"s, rest);

      incrementLength(getEndTag().length());

    }

}

There are several assisting functions, most members:

auto DayElement::extractCollect(const Use inUse, std::string_view inText,

                                PartialOfficeInfo *inPartialInfo)

    -> std::string_view::size_type

{

  if (inText.starts_with("<collects"))

    {

      CollectsElement element(inUse, inText);

      m_standardInfo->setCollect(element.getCollect());

      if (inPartialInfo != nullptr)

        inPartialInfo->setCollect(element.getCollect());

      return incrementLength(element.getLength());

    }

  else

    {

      CollectElement element(inText);

      if ((element.getUse() != Use::General)

          && (((element.getUse() == Use::Roman) && (inUse == Use::Anglican))

              || ((element.getUse() == Use::Anglican)

                  && (inUse == Use::Roman))))

        throw OfficeParseException(

            "Only collect available is not available for use: "

            + element.getText());

      m_standardInfo->setCollect(element.getText());

      if (inPartialInfo != nullptr)

        inPartialInfo->setCollect(element.getText());

      return incrementLength(element.getLength());

    }

}

void DayElement::throwOnMissingElement(const std::string &inName,

                                       std::string_view inText)

{

  if (!inText.starts_with("<" + inName))

    {

      throw OfficeParseException(inName + " element mandatory in day element",

                                 inText);

    }

}

void DayElement::processSubstantiveHours(const IPsalter &inPsalter,

                                         std::string_view inText,

                                         const Days inDay)

{

  std::string_view rest(inText);

  if (rest.starts_with("<Vespers")) // Complete record may or may not have

    // first vespers element

    {

      m_hasFirstVespers = true;

      VespersElement vesp_elem(m_use, PreviousDay(inDay), rest, m_season);

      if (!vesp_elem.hasMagnificatAntiphon())

        throw OfficeParseException(

            "Vespers element with no canticle antiphon in complete day element",

            rest);

      m_standardInfo->setFirstVespersInfo(vesp_elem.generateVespersInfo());

      rest.remove_prefix(incrementLength(vesp_elem.getLength()));

      adjustBetweenTags(rest);

    }

  throwOnMissingElement("Lauds", rest);

  LaudsElement lauds_elem(inPsalter, m_use, m_tag.getCommemoration(), rest,

                          m_season, inDay, m_tag.getGrade());

  auto linfo(lauds_elem.generateLaudsInfo());

  auto laudsChapt = linfo->generateLaudsChapter();

  auto laudsAnt = linfo->generateLaudsAntiphons();

  m_standardInfo->setLaudsInfo(std::move(linfo));

  rest.remove_prefix(incrementLength(lauds_elem.getLength()));

  adjustBetweenTags(rest);

  if (rest.starts_with("<Prime"))

    {

      PrimeElement element(rest);

      if (!element.getAntiphon().empty())

        m_standardInfo->setPrimeAntiphon(element.getAntiphon());

      rest.remove_prefix(incrementLength(element.getLength()));

      adjustBetweenTags(rest);

    }

  throwOnMissingElement("Terce", rest);

  rest.remove_prefix(

      extractMinorHour("Terce"s, laudsAnt, TerceElement(laudsChapt, rest)));

  adjustBetweenTags(rest);


  throwOnMissingElement("Sext", rest);

  rest.remove_prefix(extractMinorHour("Sext"s, laudsAnt, SextElement(rest)));

  adjustBetweenTags(rest);

  throwOnMissingElement("None", rest);

  rest.remove_prefix(extractMinorHour("None"s, laudsAnt, NoneElement(rest)));

  adjustBetweenTags(rest);


  if (!rest.starts_with("<Vespers"))

    {

      throw OfficeParseException(

          "Second Vespers element mandatory in complete day element", rest);

    }

  VespersElement vesp_elem(m_use, laudsChapt, laudsAnt, inDay, rest, m_season);

  if (!vesp_elem.hasMagnificatAntiphon())

    throw OfficeParseException(

        "vespers element with no canticle antiphon in complete day element",

        rest);

  m_standardInfo->setSecondVespersInfo(vesp_elem.generateVespersInfo());

  rest.remove_prefix(incrementLength(vesp_elem.getLength()));

  adjustBetweenTags(rest);

  if (!rest.starts_with(getEndTag()))

    throw OfficeParseException(

        "Extraneous element in complete or common day element: "s, rest);

  incrementLength(getEndTag().length());

}

And one template function  which is a non-member helper defined in the anonymous namespace to avoid having to make the classes it uses fully defined in the header:

template <typename T>

auto StorePartialMinorHoursElement(

    BreviaryLib::PartialOfficeInfo &outInfo, const T &inElement,

    const BreviaryLib::OfficeNames inOffice,

    const BreviaryLib::ILaudsAntiphons &inLaudsAntiphons)

    -> std::string::size_type

{

  outInfo.addHourChapter(inOffice, inElement.getChapter(),

                         inElement.getChapterSrc());

  std::span<std::string> resp = inElement.getResponses();

  outInfo.addHourChapterResponses(inOffice, resp);

  if (!inElement.getAntiphon().empty())

    outInfo.addHourAntiphon(inOffice, inElement.getAntiphon());

  else if (inLaudsAntiphons.antiphons() == 5)

    outInfo.addHourAntiphon(inOffice, inLaudsAntiphons.getAntiphon(inOffice));

  return inElement.getLength();

}

The only somewhat interesting accessor is getHymn(), which handles specified lauds and vespers hymns and delegates the rest to general classes:

std::string DayElement::getHymn(const Days inDay, OfficeNames inOffice) const

{

  if (inOffice == OfficeNames::LAUDS && !m_laudsHymn.empty())

    return m_laudsHymn;

  else if (inOffice == OfficeNames::VESPERS && !m_vespersHymn.empty())

    return m_vespersHymn;

  switch (inDay)

    {

      using enum Days;

    case MONDAY:

      {

        return MondayHoursInfo(m_season, m_tag.getGrade(),

                               (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case TUESDAY:

      {

        return TuesdayHoursInfo(m_season, m_tag.getGrade(),

                                (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case WEDNESDAY:

      {

        return WednesdayHoursInfo(m_season, m_tag.getGrade(),

                                  (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case THURSDAY:

      {

        return ThursdayHoursInfo(m_season, m_tag.getGrade(),

                                 (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case FRIDAY:

      {

        return FridayHoursInfo(m_season, m_tag.getGrade(),

                               (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case SATURDAY:

      {

        return SaturdayHoursInfo(m_season, m_tag.getGrade(),

                                 (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    case SUNDAY:

      [[fallthrough]];

    default:

      {

        return SundayHoursInfo(m_season, m_tag.getGrade(),

                               (m_use != Use::Anglican))

            .getHymn(inDay, inOffice);

      }

      break;

    }

}

These were already discussed in the "Quick Interface Overview" post.

Comments

Popular posts from this blog

Boundaries

LT Project: Author

State Machines