class Vcard::V4_0::Grammar

Attributes

errors[RW]
strict[RW]

Public Class Methods

new(strict) click to toggle source
# File lib/vobject/vcard/v4_0/grammar.rb, line 194
def initialize(strict)
  self.strict = strict
  self.errors = []
end
unfold(str) click to toggle source
# File lib/vobject/vcard/v4_0/grammar.rb, line 19
def unfold(str)
  str.gsub(/[\n\r]+[ \t]/, "")
end

Public Instance Methods

parse(vobject) click to toggle source
# File lib/vobject/vcard/v4_0/grammar.rb, line 199
def parse(vobject)
  @ctx = Rsec::ParseContext.new self.class.unfold(vobject), "source"
  ret = vobject_grammar._parse @ctx
  if !ret || Rsec::INVALID[ret]
    if strict
      raise @ctx.generate_error "source"
    else
      errors << @ctx.generate_error("source")
      ret = { VCARD: nil, errors: errors.flatten }
    end

  end
  Rsec::Fail.reset
  ret
end
rfc6868decode(x) click to toggle source

RFC 6868

# File lib/vobject/vcard/v4_0/grammar.rb, line 25
def rfc6868decode(x)
  x.gsub(/\^n/, "\n").gsub(/\^\^/, "^").gsub(/\^'/, '"')
end
vobject_grammar() click to toggle source
# File lib/vobject/vcard/v4_0/grammar.rb, line 29
def vobject_grammar
  # properties with value cardinality 1
  @cardinality1 = {}
  @cardinality1[:PARAM] = Set.new [:VALUE]
  @cardinality1[:PROP] = Set.new [:KIND, :N, :BDAY, :ANNIVERSARY, :GENDER, :PRODID, :REV, :UID, :BIRTHPLACE, :DEATHPLACE, :DEATHDATE]

  group = C::IANATOKEN
  linegroup = group <<  "."
  beginend = /BEGIN/i.r | /END/i.r

  # parameters && parameter types
  paramname          = /LANGUAGE/i.r | /VALUE/i.r | /PREF/i.r | /ALTID/i.r | /PID/i.r |
    /TYPE/i.r | /MEDIATYPE/i.r | /CALSCALE/i.r | /SORT-AS/i.r |
    /GEO/i.r | /TZ/i.r | /LABEL/i.r | /INDEX/i.r | /LEVEL/i.r
  otherparamname = C::NAME_VCARD ^ paramname
  paramvalue = C::QUOTEDSTRING_VCARD.map { |s| rfc6868decode s } | C::PTEXT_VCARD.map { |s| rfc6868decode(s).upcase }
  # tzidvalue = seq("/".r._?, C::PTEXT_VCARD).map { |_, val| val }
  calscalevalue = /GREGORIAN/i.r | C::IANATOKEN | C::XNAME_VCARD
  prefvalue = /[0-9]{1,2}/i.r | "100".r
  pidvalue = /[0-9]+(\.[0-9]+)?/.r
  pidvaluelist = seq(pidvalue, ",", lazy { pidvaluelist }) do |a, _, b|
    [a, b].flatten
  end | (pidvalue ^ ",".r).map { |z| [z] }
  typeparamtel1 = /TEXT/i.r | /VOICE/i.r | /FAX/i.r | /CELL/i.r | /VIDEO/i.r |
    /PAGER/i.r | /TEXTPHONE/i.r
  typeparamtel = typeparamtel1 | C::IANATOKEN | C::XNAME_VCARD
  typeparamrelated = /CONTACT/i.r | /ACQUAINTANCE/i.r | /FRIEND/i.r | /MET/i.r |
    /CO-WORKER/i.r | /COLLEAGUE/i.r | /CO-RESIDENT/i.r | /NEIGHBOR/i.r |
    /CHILD/i.r | /PARENT/i.r | /SIBLING/i.r | /SPOUSE/i.r | /KIN/i.r |
    /MUSE/i.r | /CRUSH/i.r | /DATE/i.r | /SWEETHEART/i.r | /ME/i.r |
    /AGENT/i.r | /EMERGENCY/i.r
  typevalue = /WORK/i.r | /HOME/i.r | typeparamtel1 | typeparamrelated | C::IANATOKEN | C::XNAME_VCARD
  typevaluelist = seq(typevalue << ",".r, lazy { typevaluelist }) do |a, b|
    [a.upcase, b].flatten
  end | typevalue.map { |t| [t.upcase] }
  typeparamtel1list = seq(typeparamtel << ",".r, lazy { typeparamtel1list }) do |a, b|
    [a.upcase, b].flatten
  end | typeparamtel.map { |t| [t.upcase] }
  geourlvalue = seq('"'.r >> C::TEXT4 << '"'.r) do |s|
    parse_err("geo value not a URI") unless s =~ URI::DEFAULT_PARSER.make_regexp
    s
  end
  tzvalue = paramvalue | geourlvalue
  valuetype = /TEXT/i.r | /URI/i.r | /TIMESTAMP/i.r | /TIME/i.r | /DATE-TIME/i.r | /DATE/i.r |
    /DATE-AND-OR-TIME/i.r | /BOOLEAN/i.r | /INTEGER/i.r | /FLOAT/i.r | /UTC-OFFSET/i.r |
    /LANGUAGE-TAG/i.r | C::IANATOKEN | C::XNAME_VCARD
  mediaattr = /[!\"#$%&'*+.^A-Z0-9a-z_`i{}|~-]+/.r
  mediavalue =      mediaattr | C::QUOTEDSTRING_VCARD
  mediatail = seq(";".r >> mediaattr << "=".r, mediavalue).map do |a, v|
    ";#{a}=#{v}"
  end
  rfc4288regname = /[A-Za-z0-9!#$&.+^+-]{1,127}/.r
  rfc4288typename = rfc4288regname
  rfc4288subtypename = rfc4288regname
  mediavalue = seq(rfc4288typename << "/".r, rfc4288subtypename, mediatail.star).map do |t, s, tail|
    ret = "#{t}/#{s}"
    ret = ret . tail[0] unless tail.empty?
    ret
  end
  pvalue_list = (seq(paramvalue << ",".r, lazy { pvalue_list }) & /[;:]/.r).map do |e, list|
    [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
  end | (paramvalue & /[;:]/.r).map do |e|
    [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
  end
  quoted_string_list = (seq(C::QUOTEDSTRING_VCARD << ",".r, lazy { quoted_string_list }) & /[;:]/.r).map do |e, list|
    [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n"), list].flatten
  end | (C::QUOTEDSTRING_VCARD & /[;:]/.r).map do |e|
    [e.sub(Regexp.new("^\"(.+)\"$"), '\1').gsub(/\\n/, "\n")]
  end

  # fmttypevalue = seq(rfc4288typename, "/", rfc4288subtypename).map(&:join)
  levelvalue = /beginner/i.r | /average/i.r | /expert/i.r | /high/i.r | /medium/i.r | /low/i.r

  param = seq(/ALTID/i.r, "=", paramvalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/LANGUAGE/i.r, "=", C::RFC5646LANGVALUE) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val.upcase }
  end | seq(/PREF/i.r, "=", prefvalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val.upcase }
  end | seq(/TYPE/i.r, "=", "\"".r >> typevaluelist << "\"".r) do |name, _, val|
    # not in spec but in examples. Errata ID 3488, "Held for Document Update": acknwoledged as error requiring an updated spec. With this included, TYPE="x,y,z" is a list of values; the proper ABNF behaviour is that "x,y,z" is interpreted as a single value
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/TYPE/i.r, "=", typevaluelist) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/MEDIATYPE/i.r, "=", mediavalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/CALSCALE/i.r, "=", calscalevalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/SORT-AS/i.r, "=", pvalue_list) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/TZ/i.r, "=", tzvalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/GEO/i.r, "=", geourlvalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/VALUE/i.r, "=", valuetype) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/PID/i.r, "=", pidvaluelist) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/INDEX/i.r, "=", prim(:int32)) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(/LEVEL/i.r, "=", levelvalue) do |name, _, val|
    { name.upcase.tr("-", "_").to_sym => val.upcase }
  end | seq(otherparamname, "=", pvalue_list) do |name, _, val|
    val = val[0] if val.length == 1
    { name.upcase.tr("-", "_").to_sym => val }
  end | seq(paramname, "=", pvalue_list) do |name, _, val|
    parse_err("Violated format of parameter value #{name} = #{val}")
  end

  params = seq(";".r >> param, lazy { params }) do |p, ps|
    p.merge(ps) do |key, old, new|
      if @cardinality1[:PARAM].include?(key)
        parse_err("Violated cardinality of parameter #{key}")
      end
      [old, new].flatten
      # deal with duplicate properties
    end
  end | seq(";".r >> param ^ ";".r).map { |e| e[0] }

  contentline = seq(linegroup._?, C::NAME_VCARD, params._? << ":".r,
                    C::VALUE, /[\r\n]/) do |l, name, p, value, _|
    key = name.upcase.tr("-", "_").to_sym
    hash = { key => {} }
    errors << Paramcheck.paramcheck(strict, key, p.empty? ? {} : p[0], @ctx)
    hash[key][:value], errors1 = Typegrammars.typematch(strict, key, p[0], :GENERIC, value)
    errors << errors1
    hash[key][:group] = l[0]  unless l.empty?
    hash[key][:params] = p[0] unless p.empty?
    hash
  end
  props = seq(contentline, lazy { props }) do |c, rest|
    c.merge(rest) do |key, old, new|
      if @cardinality1[:PROP].include?(key.upcase) &&
        !(new.is_a?(Array) &&
          new[0].key?(:params) && new[0][:params].key?(:ALTID) &&
          old.key?(:params) && old[:params].key?(:ALTID) &&
          old[:params][:ALTID] == new[0][:params][:ALTID]) &&
        !(new.is_a?(Hash) &&
          old.key?(:params) && old[:params].key?(:ALTID) &&
          new.key?(:params) && new[:params].key?(:ALTID) &&
          old[:params][:ALTID] == new[:params][:ALTID])
        parse_err("Violated cardinality of property #{key}")
      end
      [old, new].flatten
      # deal with duplicate properties
    end
  end | ("".r & beginend).map { {} }

  calpropname = /VERSION/i.r
  calprop = seq(calpropname << ":".r, C::VALUE, /[\r\n]/.r) do |key, value|
    key = key.upcase.tr("-", "_").to_sym
    hash = { key => {} }
    hash[key][:value], errors1 = Typegrammars.typematch(strict, key, nil, :VCARD, value)
    errors << errors1
    hash
  end
  vobject = seq(/BEGIN:VCARD[\r\n]/i.r >> calprop, props << /END:VCARD[\r\n]/i.r) do |v, rest|
    parse_err("Missing VERSION attribute") unless v.has_key?(:VERSION)
    parse_err("Missing FN attribute") unless rest.has_key?(:FN)
    rest.delete(:END)
    { VCARD: v.merge(rest), errors: errors.flatten }
  end
  vobject.eof
end

Private Instance Methods

parse_err(msg) click to toggle source
# File lib/vobject/vcard/v4_0/grammar.rb, line 217
def parse_err(msg)
  if strict
    raise @ctx.report_error msg, "source"
  else
    errors << @ctx.report_error(msg, "source")
  end
end