Module: Metanorma::UnDocument::UnTextElement

Defined in:
lib/metanorma/un_document/un_text_element.rb

Overview

UN TextElement restriction, per the current UN grammar (metanorma-un, lib/metanorma/un/un.rng v1.2.1):

TextElement = text | em | eref | erefstack | strong | stem | sub |
            sup | tt | strike | smallcap | xref | br | hyperlink |
            bookmark

UN allows only a strict subset of the isodoc inline vocabulary. Enforcing this structurally would require a UN-specific variant of every block type carrying inline content; instead this module validates the parsed model graph, flagging instances of excluded inline element classes found in inline content.

Grammar-inline productions with no model class (date_inline) cannot be flagged here; the parser drops them before validation can see them.

Constant Summary collapse

ALLOWED_ELEMENTS =

Inline element names (grammar names) permitted in UN inline content. hyperlink is the grammar production for the <link> element.

%w[
  em eref erefstack strong stem sub sup tt strike smallcap
  xref br link bookmark
].freeze
EXCLUDED_ELEMENTS =

Model classes of the isodoc TextElement productions the UN grammar excludes, mapped to their grammar (XML element) names.

{
  Metanorma::Document::Components::TextElements::UnderlineElement => "underline",
  Metanorma::Document::Components::TextElements::KeywordElement => "keyword",
  Metanorma::Document::Components::TextElements::RubyElement => "ruby",
  Metanorma::Document::Components::EmptyElements::HorizontalRuleElement => "hr",
  Metanorma::Document::Components::EmptyElements::PageBreakElement => "pagebreak",
  Metanorma::Document::Components::IdElements::Image => "image",
  Metanorma::Document::Components::EmptyElements::IndexElement => "index",
  Metanorma::Document::Components::ReferenceElements::IndexXrefElement => "index-xref",
  Metanorma::Document::Components::Inline::ConceptElement => "concept",
  Metanorma::Document::Elements::Add => "add",
  Metanorma::Document::Elements::Del => "del",
  Metanorma::Document::Components::Inline::SpanElement => "span",
}.freeze
INLINE_CONTAINERS =

Model classes whose content model is inline text; an excluded instance under one of these is an inline use. Block-level occurrences (e.g. Image under FigureBlock, PageBreakElement under ClauseSection) are legal. ParagraphWithFootnote inherits from ParagraphBlock.

[
  Metanorma::Document::Components::Paragraphs::ParagraphBlock,
  Metanorma::Document::Components::Inline::TitleWithAnnotationElement,
  Metanorma::Document::Components::Inline::VariantTitleElement,
  Metanorma::Document::Components::TextElements::TextElement,
  Metanorma::Document::Components::Lists::DtElement,
  Metanorma::Document::Components::Tables::TableCell,
].freeze
FURNITURE_CLASS_PATTERN =

fmt-* classes wrap presentation-rendered copies of content, where <span> is rendering furniture rather than semantic inline markup.

/(^|::)Fmt/

Class Method Summary collapse

Class Method Details

.each_child(node) ⇒ Object

Iterates the model-valued children of a node. Attribute values are read via instance_variable_get, mirroring lutaml-model's own attribute access (Serialize#validate_attribute!) — the project's invariant specs forbid dynamic dispatch methods in model code.



91
92
93
94
95
96
97
98
99
100
# File 'lib/metanorma/un_document/un_text_element.rb', line 91

def self.each_child(node)
  node.class.attributes.each_key do |name|
    value = node.instance_variable_get(:"@#{name}")
    if value.is_a?(Enumerable) && !value.is_a?(String)
      value.each { |item| yield item if item.is_a?(Lutaml::Model::Serializable) }
    elsif value.is_a?(Lutaml::Model::Serializable)
      yield value
    end
  end
end

.excluded_name(node) ⇒ Object



102
103
104
105
# File 'lib/metanorma/un_document/un_text_element.rb', line 102

def self.excluded_name(node)
  pair = EXCLUDED_ELEMENTS.find { |klass, _| node.is_a?(klass) }
  pair&.last
end

.furniture?(node) ⇒ Boolean

Returns:

  • (Boolean)


111
112
113
# File 'lib/metanorma/un_document/un_text_element.rb', line 111

def self.furniture?(node)
  FURNITURE_CLASS_PATTERN.match?(node.class.name)
end

.inline_container?(node) ⇒ Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/metanorma/un_document/un_text_element.rb', line 107

def self.inline_container?(node)
  INLINE_CONTAINERS.any? { |klass| node.is_a?(klass) }
end

.validate(model) ⇒ Object

Returns an array of human-readable error strings for every excluded inline element instance in the given model tree; empty when valid.



64
65
66
67
68
# File 'lib/metanorma/un_document/un_text_element.rb', line 64

def self.validate(model)
  errors = []
  walk(model, false, errors, {}.compare_by_identity)
  errors
end

.walk(node, inline, errors, visited) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/metanorma/un_document/un_text_element.rb', line 70

def self.walk(node, inline, errors, visited)
  return unless node.is_a?(Lutaml::Model::Serializable)
  return if visited[node]

  visited[node] = true

  furniture = furniture?(node)
  excluded = furniture ? nil : excluded_name(node)
  if inline && excluded
    errors << "UN grammar forbids <#{excluded}> in inline " \
              "content (#{node.class.name})"
  end

  child_inline = furniture ? false : inline || inline_container?(node)
  each_child(node) { |child| walk(child, child_inline, errors, visited) }
end