Parsing Collects

 Let's start looking at concrete tags by taking the collect domain.

We'll work up from the bottom by looking at the tag handling a single collect first.

class CollectTag : public BreviaryTag

{

public:

  CollectTag(): BreviaryTag(GetTagName()) { }

  ~CollectTag() override;

  Use getUse() const { return m_use; }

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

private:

  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 { return true; }

  Use m_use = Use::General;

};

The CollectTag has one allowed attribute, use, but it is not mandatory.  It translates into the Use enum, but allows the XML to be more expressinve than the internalized enum:

std::span<std::string> CollectTag::getAllowedAttributes() const

{

  static std::array<std::string, 1> rval{ "use" };

  return rval;

}

bool CollectTag::validate(std::string_view inAttribute,

                          std::string_view inValue) const

{

  return ((inValue == "Roman"sv) || (inValue == "Anglican"sv)

          || (inValue == "Sarum"sv) || (inValue == "York"sv));

}

void CollectTag::setValue(std::string_view inAttribute,

                          std::string_view inValue)

{

  if (inValue == "Roman"sv)

    m_use = Use::Roman;

  else

    m_use = Use::Anglican;

}

It can be a standalone element inside an office; if it is, it should not have a use attribute -- this is enforced if the use chosen by the user eliminates the collect available.  If you want two collects to represent Roman and Anglican use, e.g.

    <collects>

      <collect use="Anglican">Almighty God, give us grace that we may cast away the works of darkness, and put upon us the armour of light, now in the time of this mortal life, in which thy Son. Jesus Christ, came to visit us in great humility ; that in the last day, when He shall come again in His glorious Majesty to judge both tbe quick and dead, we may rise to the life immortal, through Him Who liveth and reigneth with Thee and the Holy Ghost, now and ever.</collect>

      <collect use="Roman">Raise up, we pray Thee, Lord, Thy power, and come : that from the dangers which hang over us by reason of our sins, we may be shielded by Thy protection, and delivered by Thy salvation. Who livest.</collect>

    </collects>

they need to be included in the Collects element, which manages the selected value:

class CollectsElement : public MultiElementElement

{

public:

  CollectsElement(const Use inUse, std::string_view inText);

  ~CollectsElement() override;

  const std::string& getCollect() const { return m_collect; }

private:

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

  void handleSingleCollect(const Use inUse, std::string_view& ioRest);

  NoAttributeTag m_tag;

  std::string m_collect;

};

All the logic is in the constructor:

CollectsElement::CollectsElement(const Use inUse, std::string_view inText):

    MultiElementElement(inText, "collects"), m_tag("collects")

{

  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 reference element for "

                               + getStartTagName());

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

  rest.remove_prefix(compareActualWithExpectedIndex(

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

  // Collect is the only type of tag possible


  auto index = rest.find(getEndTag());

  if (index == std::string_view::npos)

    throw OfficeParseException("Could not find a close tag for "

                               + getStartTagName());

  rest = rest.substr(0, index);

  incrementLength(rest.length());

  // No need to keep track of whitespace now -- we can use find...

  static const std::string SString("<" + CollectTag::GetTagName());

  index = rest.find(SString);

  while ((index != std::string::npos) && m_collect.empty())

    {

      rest = rest.substr(index);

      handleSingleCollect(inUse, rest);

      index = rest.find(SString);

    }

  if (m_collect.empty())

    throw OfficeParseException("There must be at least one valid collect for "

                               + getStartTagName());

  incrementLength(getEndTag().length());

}

Note that because of the compiler limitations we cannot use or_else and and_then in handling the std::expected return from set().

handleSingleCollect() sets the member if the uses match:

void CollectsElement::handleSingleCollect(const Use inUse, std::string_view& ioRest)

{

  CollectElement element(ioRest);

  if ((element.getUse() == Use::General) || (inUse == Use::General)

      || (element.getUse() == inUse))

    m_collect = element.getText();

  ioRest.remove_prefix(element.getLength());

}

The effect is that the first collect which matches a user's specification is interned, and the parse is cut short at that point -- any Collect elements after one that matches are not parsed.


Comments

Popular posts from this blog

Boundaries

State Machines

Considerations on an Optimization