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());

}

The duplication in pattern in setting the two fields, above, involves such a small range that the extra overhead would be as much of a problem as the duplicated logic it encapsulated.

Comments

Popular posts from this blog

Boundaries

State Machines

Considerations on an Optimization