class ActiveLdap::Ldif::Parser

def attribute_spec_is_missing

def attribute_spec_is_missing
  invalid_ldif(_("attribute spec is missing"))
end

def attribute_type_is_missing

def attribute_type_is_missing
  invalid_ldif(_("attribute type is missing"))
end

def attribute_value_separator_is_missing

def attribute_value_separator_is_missing
  invalid_ldif(_("':' is missing"))
end

def change_type_is_missing

def change_type_is_missing
  invalid_ldif(_("change type is missing"))
end

def change_type_value_is_missing

def change_type_value_is_missing
  invalid_ldif(_("change type value is missing"))
end

def control_type_is_missing

def control_type_is_missing
  invalid_ldif(_("control type is missing"))
end

def criticality_is_missing

def criticality_is_missing
  invalid_ldif(_("criticality is missing"))
end

def delete_old_rdn_mark_is_missing

def delete_old_rdn_mark_is_missing
  invalid_ldif(_("'deleteoldrdn:' is missing"))
end

def delete_old_rdn_value_is_missing

def delete_old_rdn_value_is_missing
  invalid_ldif(_("delete old RDN value is missing"))
end

def dn_has_invalid_character(character)

def dn_has_invalid_character(character)
  invalid_ldif(_("DN has an invalid character: %s") % character)
end

def dn_is_missing

def dn_is_missing
  invalid_ldif(_("DN is missing"))
end

def dn_mark_is_missing

def dn_mark_is_missing
  invalid_ldif(_("'dn:' is missing"))
end

def initialize(source)

def initialize(source)
  @ldif = nil
  source = source.to_s if source.is_a?(LDIF)
  @source = source
end

def invalid_dn(dn_string, reason)

def invalid_dn(dn_string, reason)
  invalid_ldif(_("DN is invalid: %s: %s") % [dn_string, reason])
end

def invalid_ldif(reason)

def invalid_ldif(reason)
  LdifInvalid.new(@source, reason, @scanner.line, @scanner.column)
end

def invalid_uri(uri_string, message)

def invalid_uri(uri_string, message)
  invalid_ldif(_("URI is invalid: %s: %s") % [uri_string, message])
end

def modify_spec_separator_is_missing

def modify_spec_separator_is_missing
  invalid_ldif(_("'-' is missing"))
end

def new_rdn_mark_is_missing

def new_rdn_mark_is_missing
  invalid_ldif(_("'newrdn:' is missing"))
end

def new_rdn_value_is_missing

def new_rdn_value_is_missing
  invalid_ldif(_("new RDN value is missing"))
end

def new_superior_value_is_missing

def new_superior_value_is_missing
  invalid_ldif(_("new superior value is missing"))
end

def option_is_missing

def option_is_missing
  invalid_ldif(_("option is missing"))
end

def parse

def parse
  return @ldif if @ldif
  @scanner = Scanner.new(@source)
  raise version_spec_is_missing unless @scanner.scan(/version:/)
  @scanner.scan(FILL)
  version = @scanner.scan(/\d+/)
  raise version_number_is_missing if version.nil?
  version = Integer(version)
  raise unsupported_version(version) if version != 1
  raise separator_is_missing unless @scanner.scan_separators
  records = parse_records
  @ldif = LDIF.new(records)
end

def parse_attribute

def parse_attribute
  type, options = parse_attribute_description
  value = parse_attribute_value
  [type, options, value]
end

def parse_attribute_description

def parse_attribute_description
  type = @scanner.scan(ATTRIBUTE_TYPE_CHARS)
  raise attribute_type_is_missing if type.nil?
  options = parse_options
  [type, options]
end

def parse_attribute_value(accept_external_file=true)

def parse_attribute_value(accept_external_file=true)
  raise attribute_value_separator_is_missing if @scanner.scan(/:/).nil?
  if @scanner.scan(/:/)
    @scanner.scan(FILL)
    read_base64_value
  elsif accept_external_file and @scanner.scan(/</)
    @scanner.scan(FILL)
    read_external_file
  else
    @scanner.scan(FILL)
    @scanner.scan(SAFE_STRING)
  end
end

def parse_attributes(least=0, &block)

def parse_attributes(least=0, &block)
  i = 0
  attributes = {}
  block ||= Proc.new {@scanner.check_separator}
  loop do
    i += 1
    if i >= least
      break if block.call or @scanner.eos?
    end
    type, options, value = parse_attribute
    if @scanner.scan_separator.nil? and !@scanner.eos?
      raise separator_is_missing
    end
    attributes[type] ||= []
    container = attributes[type]
    options.each do |option|
      parent = container.find do |val|
        val.is_a?(Hash) and val.has_key?(option)
      end
      if parent.nil?
        parent = {option => []}
        container << parent
      end
      container = parent[option]
    end
    container << value
  end
  raise attribute_spec_is_missing if attributes.size < least
  attributes
end

def parse_change_type

def parse_change_type
  return nil unless @scanner.scan(/changetype:/)
  @scanner.scan(FILL)
  type = @scanner.check(ATTRIBUTE_TYPE_CHARS)
  raise change_type_value_is_missing if type.nil?
  unless @scanner.scan(/add|delete|modrdn|moddn|modify/)
    raise unknown_change_type(type)
  end
  raise separator_is_missing unless @scanner.scan_separator
  type
end

def parse_change_type_record(dn, controls, change_type)

def parse_change_type_record(dn, controls, change_type)
  case change_type
  when "add"
    attributes = parse_attributes(1)
    AddRecord.new(dn, controls, attributes)
  when "delete"
    DeleteRecord.new(dn, controls)
  when "moddn"
    parse_modify_name_record(ModifyDNRecord, dn, controls)
  when "modrdn"
    parse_modify_name_record(ModifyRDNRecord, dn, controls)
  when "modify"
    parse_modify_record(dn, controls)
  else
    raise unknown_change_type(change_type)
  end
end

def parse_control

def parse_control
  return nil if @scanner.scan(/control:/).nil?
  @scanner.scan(FILL)
  type = @scanner.scan(/\d+(?:\.\d+)*/)
  raise control_type_is_missing if type.nil?
  criticality = nil
  if @scanner.scan(/ +/)
    criticality = @scanner.scan(/true|false/)
    raise criticality_is_missing if criticality.nil?
  end
  value = parse_attribute_value if @scanner.check(/:/)
  raise separator_is_missing unless @scanner.scan_separator
  ChangeRecord::Control.new(type, criticality, value)
end

def parse_controls

def parse_controls
  controls = []
  loop do
    control = parse_control
    break if control.nil?
    controls << control
  end
  controls
end

def parse_dn(dn_string)

def parse_dn(dn_string)
  DN.parse(dn_string).to_s
rescue DistinguishedNameInvalid
  raise invalid_dn(dn_string, $!.reason)
end

def parse_modify_name_record(klass, dn, controls)

def parse_modify_name_record(klass, dn, controls)
  raise new_rdn_mark_is_missing unless @scanner.scan(/newrdn\b/)
  new_rdn = parse_attribute_value(false)
  raise new_rdn_value_is_missing if new_rdn.nil?
  raise separator_is_missing unless @scanner.scan_separator
  unless @scanner.scan(/deleteoldrdn:/)
    raise delete_old_rdn_mark_is_missing
  end
  @scanner.scan(FILL)
  delete_old_rdn = @scanner.scan(/[01]/)
  raise delete_old_rdn_value_is_missing if delete_old_rdn.nil?
  raise separator_is_missing unless @scanner.scan_separator
  if @scanner.scan(/newsuperior\b/)
    @scanner.scan(FILL)
    new_superior = parse_attribute_value(false)
    raise new_superior_value_is_missing if new_superior.nil?
    new_superior = parse_dn(new_superior)
    raise separator_is_missing unless @scanner.scan_separator
  end
  klass.new(dn, controls, new_rdn, delete_old_rdn, new_superior)
end

def parse_modify_record(dn, controls)

def parse_modify_record(dn, controls)
  operations = []
  loop do
    spec = parse_modify_spec
    break if spec.nil?
    type, attribute, options, attributes = spec
    case type
    when "add"
      klass = ModifyRecord::AddOperation
    when "delete"
      klass = ModifyRecord::DeleteOperation
    when "replace"
      klass = ModifyRecord::ReplaceOperation
    else
      unknown_modify_type(type)
    end
    operations << klass.new(attribute, options, attributes)
  end
  ModifyRecord.new(dn, controls, operations)
end

def parse_modify_spec

def parse_modify_spec
  return nil unless @scanner.check(/(#{ATTRIBUTE_TYPE_CHARS}):/)
  type = @scanner[1]
  unless @scanner.scan(/(?:add|delete|replace):/)
    raise unknown_modify_type(type)
  end
  @scanner.scan(FILL)
  attribute, options = parse_attribute_description
  raise separator_is_missing unless @scanner.scan_separator
  attributes = parse_attributes {@scanner.check(/-/)}
  raise modify_spec_separator_is_missing unless @scanner.scan(/-/)
  raise separator_is_missing unless @scanner.scan_separator
  [type, attribute, options, attributes]
end

def parse_options

def parse_options
  options = []
  while @scanner.scan(/;/)
    option = @scanner.scan(ATTRIBUTE_TYPE_CHARS)
    raise option_is_missing if option.nil?
    options << option
  end
  options
end

def parse_record

def parse_record
  raise dn_mark_is_missing unless @scanner.scan(/dn:/)
  if @scanner.scan(/:/)
    @scanner.scan(FILL)
    dn = read_base64_value
    raise dn_is_missing if dn.nil?
    dn = parse_dn(dn)
  else
    @scanner.scan(FILL)
    dn = @scanner.scan(/#{SAFE_STRING}$/)
    if dn.nil?
      partial_dn = @scanner.scan(SAFE_STRING)
      raise dn_has_invalid_character(@scanner.check(/./)) if partial_dn
      raise dn_is_missing
    end
    dn = parse_dn(dn)
  end
  raise separator_is_missing unless @scanner.scan_separator
  controls = parse_controls
  change_type = parse_change_type
  raise change_type_is_missing if change_type.nil? and !controls.empty?
  if change_type
    parse_change_type_record(dn, controls, change_type)
  else
    attributes = parse_attributes(1)
    ContentRecord.new(dn, attributes)
  end
end

def parse_records

def parse_records
  records = []
  loop do
    records << parse_record
    break if @scanner.eos?
    raise separator_is_missing if @scanner.scan_separator.nil?
    break if @scanner.eos?
    break if @scanner.scan_separators and @scanner.eos?
  end
  records
end

def read_base64_value

def read_base64_value
  value = @scanner.scan(/[a-zA-Z0-9\+\/=]+/)
  return nil if value.nil?
  encoding = value.encoding if value.respond_to?(:encoding)
  value = value.unpack("m")[0].chomp
  if value.respond_to?(:force_encoding)
    value.force_encoding(encoding)
    value.force_encoding("ascii-8bit") unless value.valid_encoding?
  end
  value
end

def read_external_file

def read_external_file
  uri_string = @scanner.scan(URI::ABS_URI)
  raise uri_is_missing if uri_string.nil?
  uri_string.chomp!
  uri = nil
  begin
    uri = URI.parse(uri_string)
  rescue URI::Error
    raise invalid_uri(uri_string, $!.message)
  end
  if uri.scheme == "file"
    File.open(uri.path, "rb") {|file| file.read}
  else
    uri.read
  end
end

def separator_is_missing

def separator_is_missing
  invalid_ldif(_("separator is missing"))
end

def unknown_change_type(change_type)

def unknown_change_type(change_type)
  invalid_ldif(_("unknown change type: %s") % change_type)
end

def unknown_modify_type(type)

def unknown_modify_type(type)
  invalid_ldif(_("unknown modify type: %s") % type)
end

def unsupported_version(version)

def unsupported_version(version)
  invalid_ldif(_("unsupported version: %d") % version)
end

def uri_is_missing

def uri_is_missing
  invalid_ldif(_("URI is missing"))
end

def version_number_is_missing

def version_number_is_missing
  invalid_ldif(_("version number is missing"))
end

def version_spec_is_missing

def version_spec_is_missing
  invalid_ldif(_("version spec is missing"))
end