class Polites::Parser

The parser takes XML content from a Polites file and parses it into our own abstract syntax tree built from {Node} elements. This can then be modified and formatted into the desired output.

Public Class Methods

new() click to toggle source
# File lib/polites/parser.rb, line 18
def initialize
  reset
end

Public Instance Methods

parse(source) click to toggle source

Parse a unit into our AST. This will deal with anything passed in, but to parse multiple nodes successfully or to parse and entire sheet, see {parse_fragment} and {parse_sheet}.

@param [Nokogiri::Node, String] source @raise [ParseError] when given a `source` we cannot deal with. @return [Node, Array<Node>]

# File lib/polites/parser.rb, line 79
def parse(source)
  case source
  when Nokogiri::XML::Element
    parse_element(source)
  when Nokogiri::XML::NodeSet
    source.map { |s| parse(s) }.compact
  when Nokogiri::XML::Text
    Text.new(source.text)
  when String
    doc = Nokogiri(source)
    if doc.children.any?
      parse(doc.children)
    else
      Text.new(source)
    end
  else
    raise ParseError, "unexpected #{source.inspect}"
  end
end
parse_fragment(source) click to toggle source

Parse a content fragment, which consists of multiple source elements but not necessarily an entire sheet.

@param [String] source @return [Array<Node>]

# File lib/polites/parser.rb, line 60
def parse_fragment(source)
  element = case source
            when Nokogiri::XML::Element
              source
            when String
              Nokogiri(source)
            end
  element.xpath('string/p').map do |el|
    parse(el)
  end
end
parse_markup(element) click to toggle source

Parse just the markup section of a sheet into a {Markup} container.

@param [Nokogiri::Node] element @return [Markup]

# File lib/polites/parser.rb, line 43
def parse_markup(element)
  markup = element.xpath('markup').first
  tags = markup.xpath('tag').map do |e|
    if e[:pattern]
      SimpleTag.new(e[:definition], e[:pattern])
    elsif e[:startPattern] && e[:endPattern]
      RangeTag.new(e[:definition], e[:startPattern], e[:endPattern])
    end
  end
  Markup.new(markup[:version], markup[:identifier], markup[:displayName], tags)
end
parse_sheet(source) click to toggle source

Parse an entire sheet of content, including all its structural elements.

@param [String] source @return [Sheet]

# File lib/polites/parser.rb, line 26
def parse_sheet(source)
  sheet = Nokogiri(source).xpath('/sheet').first
  Sheet.new(
    version: sheet[:version],
    app_version: sheet[:app_version],
    markup: parse_markup(sheet),
    content: parse_fragment(sheet),
    keywords: parse_keywords(sheet),
    files: parse_files(sheet),
    notes: parse_notes(sheet)
  )
end

Private Instance Methods

parse_element(source) click to toggle source
# File lib/polites/parser.rb, line 101
def parse_element(source)
  case source.name
  when 'p'
    block = Block.build(parse(source.children), **@current_block_attributes)
    reset_block
    block
  when 'element'
    span = Span.build(
      parse(source.children),
      kind: source[:kind],
      **@current_span_attributes
    )
    reset_span
    span
  when 'tag'
    @current_block_attributes[:kind] = source[:kind] if source[:kind]
    @current_block_attributes[:level] += 1 if source.text == "\t"
    nil
  when 'attribute'
    case source[:identifier]
    when 'syntax'
      @current_block_attributes[:syntax] = source.text
    when 'text'
      @current_span_attributes[:text] = parse_fragment(source)
    when 'size'
      parse(source.children)
    else
      @current_span_attributes[source[:identifier].downcase.to_sym] = source.text
    end
    nil
  when 'size'
    @current_span_attributes[:width] = source[:width].to_i
    @current_span_attributes[:height] = source[:height].to_i
    nil
  when 'tags'
    parse(source.children)
    nil
  else
    raise ParseError, "unknown element name #{source.name}"
  end
end
parse_files(element) click to toggle source
# File lib/polites/parser.rb, line 149
def parse_files(element)
  element
    .xpath('./attachment[@type="file"]')
    .map(&:text)
end
parse_keywords(element) click to toggle source
# File lib/polites/parser.rb, line 143
def parse_keywords(element)
  element
    .xpath('./attachment[@type="keywords"]')
    .flat_map { |el| el.text.split(',') }
end
parse_notes(element) click to toggle source
# File lib/polites/parser.rb, line 155
def parse_notes(element)
  element
    .xpath('./attachment[@type="note"]')
    .map { |el| parse_fragment(el) }
end
reset() click to toggle source
# File lib/polites/parser.rb, line 161
def reset
  reset_block
  reset_span
end
reset_block() click to toggle source
# File lib/polites/parser.rb, line 166
def reset_block
  @current_block_attributes = { kind: 'paragraph', level: 0 }
end
reset_span() click to toggle source
# File lib/polites/parser.rb, line 170
def reset_span
  @current_span_attributes = {}
end