Minor Hour Elements
It's now time to move up a level; all the elements common between higher-level elements have been canvassed.
We'll start, as usual, with the simplest model: the three minor hours. Their structures are isomorphic, which means that they can share a lot of logic, and their variable parts are relatively limited in number, which makes them simpler overall.
The first class to look at is the SingleAntiphonElement. It's the base class not only for the three minor hour elements but for Prime as well. This models hours where there is only, ever, one antiphon over the psalms. It also requires that the main tag have no attributes.
Single Antiphon Element
class SingleAntiphonElement : public MultiElementElement
{
public:
~SingleAntiphonElement() override;
const std::string &getStartTagName() const override
{
return m_tag.getName();
}
const std::string &getAntiphon() const { return m_antiphon; }
protected:
explicit SingleAntiphonElement(std::string_view inText,
const std::string &inName):
MultiElementElement(inText, inName),
m_tag(inName)
{ }
NoAttributeTag m_tag;
std::string m_antiphon;
void initialize(std::string_view inText);
};
The initialize() function, called during the constructors of derived classes, is very short:
void SingleAntiphonElement::initialize(std::string_view inText)
{
auto l = [&](const NoAttributeTag& inVal, const int inAttributes)
{
if (inVal.isClosed())
throw OfficeParseException("Cannot have a closed " + inVal.getName()
+ " element");
};
TagSetter().set<NoAttributeTag>(m_tag, l, inText.substr(0, getLength()));
}
Minor Hours Element
The MinorHoursElement class adds more common data members and a number of convenience functions:
class MinorHoursElement : public SingleAntiphonElement
{
protected:
explicit MinorHoursElement(std::string_view inText,
const std::string &inName):
SingleAntiphonElement(inText, inName)
{ }
public:
~MinorHoursElement() override;
const std::string &getChapterSrc() const { return m_chapterSrc; }
const std::string &getChapter() const { return m_chapter; }
std::span<std::string> getResponses() const { return m_responses; }
virtual void setInHolder(IOfficeInfoHolder &outHolder,
const ILaudsAntiphons &inAntiphons,
const bool inPassiontide) const = 0;
protected:
std::string m_chapterSrc;
std::string m_chapter;
mutable std::vector<std::string> m_responses;
std::string::size_type extractResponses(std::string_view inText,
const OfficeNames inOffice);
void handleEnd(std::string_view inText);
std::string_view::size_type extractChapter(std::string_view inText,
const OfficeNames inOffice);
};
The setInHolder() call is the point where the classes at the next level up connect to the data which is the end result of the parsing process. We've already seen the IOfficeInfoHolder interface class in looking at constructors for the hours storage classes; this is one of the points where it gets populated.
The three utility functions encapsulate logic which is common to all three subclasses, which is not affected by the class type.
std::string::size_type
MinorHoursElement::extractResponses(std::string_view inText,
const OfficeNames inOffice)
{
ChapterResponsesElement element(inText, inOffice);
m_responses = element.getValues();
return incrementLength(element.getLength());
}
void MinorHoursElement::handleEnd(std::string_view inText)
{
if (inText.starts_with(getEndTag()))
{
if (m_chapter.empty() && m_responses.empty() && m_antiphon.empty())
throw OfficeParseException("Cannot have an empty " + m_tag.getName()
+ " element");
incrementLength(getEndTag().length());
}
else
throw OfficeParseException(
"Unexpected element in " + m_tag.getName() + " element", inText);
}
std::string_view::size_type
MinorHoursElement::extractChapter(std::string_view inText,
const OfficeNames inOffice)
{
ChapterElement element(inText, inOffice);
m_chapterSrc = element.getSrc();
m_chapter = element.getText();
return incrementLength(element.getLength());
}
Sext Element
Sext and None are virtually identical in terms of their logic. Here is Sext:
class SextElement : public MinorHoursElement
{
public:
explicit SextElement(std::string_view inText);
~SextElement() override;
void setInHolder(IOfficeInfoHolder& outHolder,
const ILaudsAntiphons& inAntiphons,
const bool inPassiontide) const override;
};
The simplicity of the interface hides some considerable activity. The constructor is simple:
SextElement::SextElement(std::string_view inText):
MinorHoursElement(inText, "Sext")
{
initialize(inText);
std::string_view rest(inText.substr(getLength()));
adjustBetweenTags(rest);
if (rest.starts_with("<antiphon"))
{
AntiphonElement element(rest);
m_antiphon = element.getText();
rest.remove_prefix(incrementLength(element.getLength()));
adjustBetweenTags(rest);
}
if (rest.starts_with("<chapter"))
{
rest.remove_prefix(extractChapter(rest, OfficeNames::SEXT));
adjustBetweenTags(rest);
}
if (rest.starts_with("<ChapterResponses"))
{
rest.remove_prefix(extractResponses(rest, OfficeNames::SEXT));
adjustBetweenTags(rest);
}
handleEnd(rest);
}
Note that as these elements are frequently used for, as one might say, partial specialization of a base set of values, the three contained elements are all optional, though any that appear must do so in a fixed order.
When we come to the conversion to the permanent storage system, though, we get to more complex code.
The IOfficeInfoHolder class has the setter:
virtual void setSextInfo(std::unique_ptr<ISextInfo> &&inInfo) = 0;
And ISextInfo is defined as:
class ISextInfo
{
public:
virtual ~ISextInfo();
virtual SextChapter generateSextChapter() const = 0;
virtual SextAntiphons generateSextAntiphons() const = 0;
virtual std::unique_ptr<ISextInfo> clone() const = 0;
};
(Elements are returned by value; in context, they will be constructed in place or moved via RVO.)
setInHolder() defines a concrete implementation which carefully takes copies of everything -- no counting on any values in the elements not being ephemeral -- on construction:
void SextElement::setInHolder(IOfficeInfoHolder &outHolder,
const ILaudsAntiphons &inAntiphons,
const bool inPassiontide) const
{
class SextInfo : public ISextInfo
{
public:
SextInfo(const ILaudsAntiphons &inAntiphons,
const std::string &inChapterText, const std::string &inChapterSrc,
std::span<std::string> inResponses, const bool inPassiontide):
m_antiphon(inAntiphons.getAntiphon(OfficeNames::SEXT)),
m_chapterText(inChapterText), m_chapterSrc(inChapterSrc),
m_passiontide(inPassiontide)
{
std::ranges::move(inResponses, std::back_inserter(m_responses));
}
SextInfo(const std::string &inAntiphon, const std::string &inChapterText,
const std::string &inChapterSrc,
std::span<std::string> inResponses, const bool inPassiontide):
m_antiphon(inAntiphon),
m_chapterText(inChapterText), m_chapterSrc(inChapterSrc),
m_passiontide(inPassiontide)
{
std::ranges::move(inResponses, std::back_inserter(m_responses));
}
~SextInfo() override {}
SextChapter generateSextChapter() const override
{
return SextChapter{ m_chapterText, m_chapterSrc, m_responses,
m_passiontide };
}
SextAntiphons generateSextAntiphons() const override
{
return SextAntiphons{ m_antiphon };
}
std::unique_ptr<ISextInfo> clone() const override
{
return std::make_unique<SextInfo>(*this);
}
private:
std::string m_antiphon;
std::string m_chapterText;
std::string m_chapterSrc;
mutable std::vector<std::string> m_responses;
bool m_passiontide;
};
if (m_antiphon.empty())
outHolder.setSextInfo(
std::make_unique<SextInfo>(inAntiphons, getChapter(), getChapterSrc(),
getResponses(), inPassiontide));
else
outHolder.setSextInfo(
std::make_unique<SextInfo>(m_antiphon, getChapter(), getChapterSrc(),
getResponses(), inPassiontide));
}
Because the SextElement is always created ephemerally and the call to setInHolder() (if it occurs) is the last reference to the responses it has interned, we can move, rather than copy, the responses' contents into the SextInfo vector.
Terce Element
The Terce element can have values set from Lauds, which does not happen for Sext or None. It therefore has an additional constructor:
TerceElement::TerceElement(const ILaudsChapter &inChapter,
std::string_view inText):
MinorHoursElement(inText, "Terce")
{
initialize(inText);
std::string_view rest(inText.substr(getLength()));
adjustBetweenTags(rest);
if (rest.starts_with("<antiphon"))
{
AntiphonElement element(rest);
m_antiphon = element.getText();
rest.remove_prefix(incrementLength(element.getLength()));
adjustBetweenTags(rest);
}
if (rest.starts_with("<chapter"))
{
ChapterElement element(inChapter, rest, OfficeNames::TERCE);
m_chapterSrc = element.getSrc();
m_chapter = element.getText();
rest.remove_prefix(incrementLength(element.getLength()));
adjustBetweenTags(rest);
}
if (rest.starts_with("<ChapterResponses"))
{
rest.remove_prefix(extractResponses(rest, OfficeNames::TERCE));
adjustBetweenTags(rest);
}
handleEnd(rest);
}
Everything else is the same as Sext and None, mutatis mutandis.
Comments
Post a Comment