Parsing Hymn Responsories
Hymn responses are similar to the preceding elements. There is a standard, spelled-out form:
<HymnResponses>
<versicle>Drop down, ye heavens, from above.</versicle>
<response>And let the skies pour down righteousness : let the earth open, and let them bring forth a Saviour.</response>
</HymnResponses>
and there is a form which can be generated by reference to Lauds:
<HymnResponses ref="Lauds"/>
The versicle and response elements are examples of a pure text element: no possible attributes on the tag, and a simple text body. As such most of their logic -- everything except their name -- can be extracted into a parent class:
class SimpleTextElement : public TextElementBase
{
public:
SimpleTextElement(const std::string &inName, std::string_view inText);
~SimpleTextElement() override;
const std::string &getStartTagName() const override { return m_tag.getName(); }
protected:
virtual void processText(std::string_view inText)
{
std::string_view body(inText.substr(getLength()));
setTextFromBody(body);
body.remove_prefix(incrementLength(m_text.length()));
incrementLength(checkEndTag(body));
}
NoAttributeTag m_tag;
};
processText() is made virtual so that if a descendant needs to handle text content with special logic, the function can be overridden.
The constructor is extremely simple:
SimpleTextElement::SimpleTextElement(const std::string &inName, std::string_view inText):
TextElementBase(inText, inName), m_tag(inName)
{
auto l = [&](const NoAttributeTag& inVal, const int inAttributes)
{
processText(inText);
};
TagSetter().set<NoAttributeTag>(m_tag, l, inText.substr(0, getLength()));
}
Once that is in place, the VersicleElement and ResponseElement have exactly the same pattern:
class VersicleElement : public SimpleTextElement
{
public:
explicit VersicleElement(std::string_view inText):
SimpleTextElement("versicle", inText)
{ }
~VersicleElement() override;
};
HymnResponsesTag
The only special value this holds is a boolean flag if the ref attribute is set.
class HymnResponsesTag : public BreviaryTag
{
public:
HymnResponsesTag(): BreviaryTag(GetTagName()) {}
~HymnResponsesTag() override;
bool isLaudsReference() const { return m_laudsReference; }
static std::string GetTagName() { return "HymnResponses"; }
private:
bool m_laudsReference = false;
int allowedAttributeCount() const override { return 1; }
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;
};
As only one value can be used for the ref attribute, validation and setting is straightforward.
std::span<std::string>
HymnResponsesTag::getAllowedAttributes() const
{
static std::array<std::string, 1> rval{ "ref" };
return rval;
}
bool HymnResponsesTag::validate(std::string_view inAttribute,
std::string_view inValue) const
{
return (inValue == "Lauds");
}
void HymnResponsesTag::setValue(std::string_view inAttribute, std::string_view inValue)
{
m_laudsReference = true;
}
bool HymnResponsesTag::checkMandatoryAttributes() const
{
if (hasAttribute("ref") && !isClosed())
return false;
else if (!hasAttribute("ref") && isClosed())
return false;
return true;
}
HymnResponsesElement
The HymnResponsesElement class is also generally familiar. It returns the two member strings as a span.
class HymnResponsesElement : public MultiElementElement
{
public:
HymnResponsesElement(const ILaudsChapter &inChapter,
std::string_view inText);
explicit HymnResponsesElement(std::string_view inText);
const std::string &getStartTagName() const override
{
return m_nameTag.getName();
}
std::span<std::string> getValues() const { return m_vals; }
private:
HymnResponsesTag m_nameTag;
mutable std::array<std::string, 2> m_vals;
void processBody(std::string_view inText);
};
The two constructors
HymnResponsesElement::HymnResponsesElement(std::string_view inText):
MultiElementElement(inText, HymnResponsesTag::GetTagName())
{
auto l = [&](const HymnResponsesTag &inVal, const int inAttributes) {
if (m_nameTag.isLaudsReference())
throw OfficeParseException(
"Cannot have Lauds reference hymn responsory in first vespers or non-vespers office");
processBody(inText);
};
TagSetter().set<HymnResponsesTag>(m_nameTag, l, inText.substr(0, getLength()));
}
HymnResponsesElement::HymnResponsesElement(const ILaudsChapter &inChapter,
std::string_view inText):
MultiElementElement(inText, HymnResponsesTag::GetTagName())
{
auto l = [&](const HymnResponsesTag &inVal, const int inAttributes) {
if (m_nameTag.isLaudsReference())
std::ranges::copy(inChapter.getResponses(), m_vals.begin());
else
processBody(inText);
};
TagSetter().set<HymnResponsesTag>(m_nameTag, l, inText.substr(0, getLength()));
}
both delegate most of their functionality to the processBody() call:
void HymnResponsesElement::processBody(std::string_view inText)
{
std::string_view rest(inText.substr(getLength()));
rest.remove_prefix(compareActualWithExpectedIndex(
rest, getNextTagIndex(rest), incrementOverWhitespace(rest)));
{
VersicleElement e(rest);
rest.remove_prefix(incrementLength(e.getLength()));
m_vals[0] = e.getText();
}
rest.remove_prefix(compareActualWithExpectedIndex(
rest, getNextTagIndex(rest), incrementOverWhitespace(rest)));
{
ResponseElement e(rest);
rest.remove_prefix(incrementLength(e.getLength()));
m_vals[1] = e.getText();
}
rest.remove_prefix(compareActualWithExpectedIndex(
rest, getNextTagIndex(rest), incrementOverWhitespace(rest)));
if (!rest.starts_with(getEndTag()))
throw OfficeParseException(
getStartTagName() + " with unexpected element before closing tag",
rest);
incrementLength(getEndTag().length());
}
Comments
Post a Comment