private static void dump(OutputStream out) throws IOException { writeLine(out, "=== Fields ==="); for (String f : new TreeSet<String>(knownFields)) { writeLine(out, f); } for (CsvFormat fmt : new TreeSet<CsvFormat>(knownFormats)) { StringBuilder sb = new StringBuilder("=== Mapping "); sb.append(fmt.toString()).append(" ==="); writeLine(out, sb.toString()); for (CsvColumn col : fmt.columns) { if (col.field != null) { if (!knownFields.contains(col.field)) { LOG.debug("Mapping '%s' references unknown field='%s'\n", fmt.name, col.field); } } writeLine(out, col.toString()); } } }
/** * @param csv is the list of fields in a record from a CSV file * @param formats is the list of CsvFormats to be considered applicable * @return a map from field to value * @throws ParseException */ private Map<String, String> toContact(List<String> csv, CsvFormat format) throws ParseException { ContactMap contactMap = new ContactMap(); // NOTE: If there isn't a mapping for a field name defined in "format" // NOTE: a user defined attribute with that field name will be used. if (csv == null) { return contactMap.getContacts(); } if (format.allFields()) { int end = csv.size(); end = (end > fieldNames.size()) ? fieldNames.size() : end; for (int i = 0; i < end; i++) { contactMap.put(fieldNames.get(i), csv.get(i)); } } else if (format.hasNoHeaders()) { int end = csv.size(); end = (end > format.columns.size()) ? format.columns.size() : end; for (int i = 0; i < end; i++) { contactMap.put(format.columns.get(i).field, csv.get(i)); } } else { /* Many CSV formats are output in a specific order and sometimes * contain duplicate field names with mappings to different * Zimbra contact fields. */ Map<CsvColumn, Map<String, String>> pendMV = new HashMap<CsvColumn, Map<String, String>>(); List<CsvColumn> unseenColumns = new ArrayList<CsvColumn>(); unseenColumns.addAll(format.columns); for (int ndx = 0; ndx < fieldNames.size(); ndx++) { String csvFieldName = fieldNames.get(ndx); String fieldValue = (ndx >= csv.size()) ? null : csv.get(ndx); CsvColumn matchingCol = null; String matchingFieldLc = null; for (CsvColumn unseenC : unseenColumns) { matchingFieldLc = unseenC.matchingLcCsvFieldName(csvFieldName); if (matchingFieldLc == null) { continue; } if (unseenC.colType == ColType.MULTIVALUE) { Map<String, String> currMV = pendMV.get(matchingCol); if ((currMV != null) && currMV.get(matchingFieldLc) != null) { // already have field with this name that matches this column continue; } } matchingCol = unseenC; break; } if (matchingCol == null) { // unknown field - setup for adding as a user defined attribute LOG.debug( "Adding CSV contact attribute [%s=%s] - assuming is user defined.", csvFieldName, fieldValue); contactMap.put(csvFieldName, fieldValue); continue; } switch (matchingCol.colType) { case NAME: addNameField(fieldValue, matchingCol.field, contactMap); unseenColumns.remove(matchingCol); break; case DATE: addDateField(fieldValue, matchingCol.field, format.key(), contactMap); unseenColumns.remove(matchingCol); break; case TAG: contactMap.put(TAG, fieldValue); break; case MULTIVALUE: for (String cname : matchingCol.names) { if (cname.toLowerCase().equals(matchingFieldLc)) { Map<String, String> currMV = pendMV.get(matchingCol); if (currMV == null) { currMV = new HashMap<String, String>(); pendMV.put(matchingCol, currMV); } currMV.put(matchingFieldLc, fieldValue); if (currMV.size() >= matchingCol.names.size()) { addMultiValueField(matchingCol, currMV, contactMap); pendMV.remove(currMV); unseenColumns.remove(matchingCol); } } } break; default: contactMap.put(matchingCol.field, fieldValue); unseenColumns.remove(matchingCol); } } // Process multi-value fields where only some constituent fields were present for (Map.Entry<CsvColumn, Map<String, String>> entry : pendMV.entrySet()) { addMultiValueField(entry.getKey(), entry.getValue(), contactMap); } } Map<String, String> contact = contactMap.getContacts(); // Bug 50069 - Lines with single blank in them got imported as a blank contact // Initial fix idea was for parseField to return the trimmed version of the string // However, this from rfc4180 - Common Format and MIME Type for Comma-Separated // Values (CSV) Files : // "Spaces are considered part of a field and should not be ignored." // suggests that might be an invalid thing to do, so now just reject the contact // if the whole line would collapse to an empty string with trim. if (contact.size() == 1) { boolean onlyBlank = true; for (String val : contact.values()) { if (!val.trim().equals("")) { onlyBlank = false; break; } } if (onlyBlank) contact = new HashMap<String, String>(); } return contact; }