lib/vobject.rb in vobject-0.1.0 vs lib/vobject.rb in vobject-1.0.2

- old
+ new

@@ -1,182 +1,25 @@ -require "vobject/version" - module Vobject - - module Rules - - module ABNF - IANAToken = '[a-zA-Z\d\-]+?' - Cr = "\u000d" - Lf = "\u000a" - Crlf = "(#{Cr}|#{Lf})" - Utf8_tail = '[\u0080-\u00bf]' - Utf8_2 = '([\u00c2-\u00df]|' + "#{Utf8_tail})" - Utf8_3 = '([\u00e0\u00a0-\u00bf\u00e1-\u00ec\u00ed\u0080-\u009f\u00ee-\u00ef]|' + "#{Utf8_tail})" - Utf8_4 = '([\u00f0\u0090-\u00bf\u00f1-\u00f3\u00f4\u0080-\u008f]|' + "#{Utf8_tail})" - Wsp = '[ \t]' - VChar = '[\u0021-\u007e]' - NonASCII = "(#{Utf8_2}|#{Utf8_3}|#{Utf8_4})" - QSafeChar = "(#{Wsp}|" + '[!\u0023-\u007e]' + "|#{NonASCII})" - SafeChar = "(#{Wsp}|" + '[!\u0023-\u0039\u003c-\u007e]' + "|#{NonASCII})" - ValueChar = "(#{Wsp}|#{VChar}|#{NonASCII})" - DQuote = '"' - PText = "#{SafeChar}*?" - QuotedString = "#{DQuote}(#{QSafeChar}*?)#{DQuote}" - XName = "[xX]-#{IANAToken}" - Group = IANAToken - Name = "(#{XName}|#{IANAToken})" - ParamName = "(#{XName}|#{IANAToken})" - ParamValue = "(#{PText}|#{QuotedString})" - PValueList = "(?<head>#{ParamValue})(?<tail>(,#{ParamValue})*)" - Pid = '\d+(\.\d+)*' - PidList = "(?<head>#{Pid})(?<tail>(,#{Pid})*)" - Param = "(?<pname>#{ParamName})=(?<pvalue>#{PValueList})" - Params = "(;(?<phead>#{Param}))(?<ptail>(;#{Param})*)" - Value = "#{ValueChar}*?" - LineGroup = "((?<group>#{Group})" + '\.' + ")?" - Contentline = "#{LineGroup}(?<key>#{Name})(?<params>(#{Params})?):(?<value>#{Value})#{Crlf}" - BeginLine = "BEGIN:#{IANAToken}#{Crlf}" - VersionLine = "VERSION:#{Value}#{Crlf}" - EndLine = "END:#{IANAToken}#{Crlf}" - Vobject = "#{BeginLine}#{VersionLine}(#{Contentline})+#{EndLine}" - end - - end - class << self - - def parse(vobject) - vobject = unfold(vobject) - lines = [] - rule = "(?<line>#{Rules::ABNF::Contentline})(?<remainder>(#{Rules::ABNF::Contentline})*)" - - parse_for_rule(Rules::ABNF::Vobject, vobject) do |parsed| - - remainder = vobject - - while !remainder.empty? - parse_for_rule(rule, remainder) do |remainder_parsed| - lines << remainder_parsed[:line] - remainder = remainder_parsed[:remainder] - end - end - end - - parse_lines lines - end - - private - + MAX_LINE_WIDTH = 75 def unfold(str) - str.gsub(/#{Rules::ABNF::Crlf}#{Rules::ABNF::Wsp}/, '') + str.gsub(/(\r|\n|\r\n)[ \t]/, "") end - def parse_lines lines - lines.each_with_index.reduce([]) do |hash_stack, (line, i)| - prop = parse_line(line) + # This implements the line folding as specified in + # http://tools.ietf.org/html/rfc6350#section-3.2 + # NOTE: the "line" here is not including the trailing \n + def fold_line(line) + folded_line = line[0, MAX_LINE_WIDTH] + remainder_line = line[MAX_LINE_WIDTH, line.length - MAX_LINE_WIDTH] || "" - if prop.has_key?(:BEGIN) - comp = prop[:BEGIN][:value].to_sym - hash = { comp => [] } - next hash_stack << hash - end + max_width = MAX_LINE_WIDTH - 1 - if prop.has_key?(:END) - hash = hash_stack.pop - comp = hash.keys.first - - raise_invalid_parsing if comp != prop[:END][:value].to_sym - - prev_hash = hash_stack.last - - raise_invalid_parsing if !prev_hash && i != lines.length - 1 - - return hash unless prev_hash - - prev_hash[prev_hash.keys.first] << hash - - next hash_stack - end - - prev_hash = hash_stack.last - - prev_hash[prev_hash.keys.first] << prop - hash_stack + (0..((remainder_line.length - 1) / max_width)).each do |i| + folded_line << "\n " + folded_line << remainder_line[i * max_width, max_width] end - end - def parse_line(line) - parse_for_rule(Rules::ABNF::Contentline, unfold(line)) do |parsed| - key = parsed[:key].to_sym - - group = parsed[:group] - params = parse_params(parsed[:params]) - value = parsed[:value] - - hash = { key => {} } - hash[key][:group] = group if group - hash[key][:params] = params if !params.empty? - hash[key][:value] = value - - hash - end + folded_line end - - def parse_params(params_str) - params = {} - - while !params_str.empty? - parse_for_rule(Rules::ABNF::Params, params_str) do |parsed| - - parse_for_rule(Rules::ABNF::Param, parsed[:phead]) do |param_parsed| - pname = param_parsed[:pname].to_sym - pvalue = param_parsed[:pvalue].sub( - Regexp.new("^#{Rules::ABNF::QuotedString}$"), - '\1' - ) - - pvalue.gsub!(/\\n/, "\n") - - params[pname] = if params[pname] - "#{params[pname]},#{pvalue}" - else - pvalue - end - end - - params_str = parsed[:ptail] - end - end - - params - end - - #Method: parse_for_rule - #Parameter: String containing the regular expression, String to be parsed - #and optional block to indicate whether to yield the resulting hash - #Return: a hash with keys indicating their regex names - def parse_for_rule(rule, str, &block) - matched = /\A#{rule}\Z/.match(str) - - raise_invalid_parsing unless matched - - parsed = matched.names.reduce({}) do |parsed_hash, name| - #can we reduce memory consumption by only creating Keys whose value is not nil? - parsed_hash[name.to_sym] = matched[name.to_sym] if matched[name.to_sym] - #parsed_hash[name.to_sym] = matched[name.to_sym] - parsed_hash - end - - return yield(parsed) if block - - parsed - end - - def raise_invalid_parsing - raise "VObject parse failed" - end - end - end -