/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package info.ineighborhood.cardme.util;
import info.ineighborhood.cardme.io.FoldingScheme;
import java.text.NumberFormat;
/**
* Copyright (c) 2004, Neighborhood Technologies
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Neighborhood Technologies nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
* @author George El-Haddad
* <br/>
* Sep 21, 2006
*
* <p>Utility methods to help with VCard parsing and reading.</p>
*/
public final class VCardUtils {
/**
* <p>Carriage Return character. CR</p>
*/
public static final String CR = "\r";
/**
* <p>Line Feed character. LF</p>
*/
public static final String LF = "\n";
/**
* <p>Horizontal Tab character. HT</p>
*/
public static final String HT = "\t";
/**
* <p>Space character. SP</p>
*/
public static final String SP = " ";
/**
* <p>Internet Standard "END OF LINE" mark. CRLF</p>
*/
public static final String CRLF = CR + LF;
/**
* <p>The "END OF LINE" delimiter used for folding labels</p>
*/
public static final String LABEL_DELIMETER = "=";
/**
* <p>List of characters that need escaping.</p>
*/
private static final char[] NEED_ESCAPING = new char[] {
',',
';',
':',
'\\',
'\n'
};
/**
* <p>List of characters that need un-escaping.</p>
*/
private static final String[] NEED_UNESCAPING = new String[] {
"\\n",
"\\N",
"\\\\\\",
"\\,",
"\\;",
"\\:"
};
private static final NumberFormat GEO_NUM_FORMATTER = NumberFormat.getNumberInstance();
/**
* <p>Private constructor.</p>
*/
private VCardUtils() {
GEO_NUM_FORMATTER.setMinimumFractionDigits(6);
GEO_NUM_FORMATTER.setMaximumFractionDigits(6);
GEO_NUM_FORMATTER.setMaximumIntegerDigits(6);
GEO_NUM_FORMATTER.setMinimumIntegerDigits(1);
GEO_NUM_FORMATTER.setGroupingUsed(false);
}
/**
* <p>Returns true if the specified textual string contains any
* one of the following characters defined in the constant char[]
* of {@link #NEED_ESCAPING}.</p>
*
* @param text
* @return boolean
*/
public static boolean needsEscaping(String text)
{
boolean needs = false;
for(int i = 0; i < NEED_ESCAPING.length; i++) {
if(text.indexOf(NEED_ESCAPING[i]) != -1) {
needs = true;
break;
}
}
return needs;
}
/**
* <p>Returns true if the specified textual string contains any
* one of the following characters defined in the constant String[]
* of {@link #NEED_UNESCAPING}.</p>
*
* @param text
* @return boolean
*/
public static boolean needsUnEscaping(String text)
{
boolean needs = false;
for(int i = 0; i < NEED_UNESCAPING.length; i++) {
if(text.indexOf(NEED_UNESCAPING[i]) != -1) {
needs = true;
break;
}
}
return needs;
}
/**
* <p>Effectively un-escapes a string, performs the reverse of {@link #escapeString(String)}.</p>
*
* @param text
* @return {@link String}
*/
public static String unescapeString(String text)
{
String unescaped = text.replaceAll("\\\\n", "\n");
unescaped = unescaped.replaceAll("\\\\N", "\n");
unescaped = unescaped.replaceAll("\\\\\\\\", "\\\\");
unescaped = unescaped.replaceAll("\\\\,", ",");
unescaped = unescaped.replaceAll("\\\\;", ";");
unescaped = unescaped.replaceAll("\\\\:", ":");
return unescaped;
}
/**
* <p>Escapes special characters in a string to be suitable for SQL.
* Characters that are escaped are the following:
* <ul>
* <li><b>EOL</b> -> \n or \N</li>
* <li><b>\</b> -> \\</li>
* <li><b>,</b> -> \,</li>
* <li><b>;</b> -> \;</li>
* </p>
*
* @param text
* @return {@link String}
*/
public static String escapeString(String text)
{
if(!needsEscaping(text)) {
return text;
}
StringBuilder sb = new StringBuilder(text.length());
for(int i = 0; i < text.length(); ++i) {
char c = text.charAt(i);
switch (c)
{
case '\n':
{
sb.append('\\');
sb.append('n');
break;
}
case '\\':
{
sb.append('\\');
sb.append('\\');
break;
}
case ',':
{
sb.append('\\');
sb.append(',');
break;
}
case ';':
{
sb.append('\\');
sb.append(';');
break;
}
case ':':
{
sb.append('\\');
sb.append(':');
break;
}
default:
{
sb.append(c);
}
}
}
return sb.toString();
}
/**
* <p>Returns true if the given String <code>line</code> is greater than the maximum allowed
* characters needed for folding (75 chars per line excluding CRLF.)</p>
*
* @param line
* @return boolean
*/
public static boolean needsFolding(String line)
{
return needsFolding(line, FoldingScheme.MIME_DIR);
}
/**
* <p>Returns true if the given String is greater than the maximum allowed
* characters needed for folding as specified by <code>foldingScheme</code> excluding
* the CRLF at the end.</p>
*
* @param line
* @param foldingScheme
* @return boolean
*/
public static boolean needsFolding(String line, FoldingScheme foldingScheme)
{
if(foldingScheme.getMaxChars() < 0) {
return false;
}
else {
return line.length() > foldingScheme.getMaxChars();
}
}
/**
* <p>First line unfolds all the labels
* Second line unfolds all the lines (if needed.)</p>
*
* <p>This unfolding technique is according to the MIME-DIR specifications
* as described in section 5.8.1 "Line delimiting and folding."</p>
*
* @param vcardString
* @return {@link String}
*/
public static String unfoldVCard(String vcardString)
{
String unfold1 = vcardString.replaceAll("=\n\\p{Blank}+", "");
String unfold2 = unfold1.replaceAll("\n\\p{Blank}+", "");
return unfold2;
}
/**
* <p>Takes a single line and folds it according to the MIME-DIR specification.
* A line is folded its length exceeds 75 characters. For every 75
* characters a CRLF is appended to the end, the following line will begin
* with a SP character and then followed by the usual next 75 chars + CRLF
* until the end.</p>
*
* <p>This method will not check the length of the String before folding, use
* the VCardUtils.needsFolding(String) method before invoking this one. This
* method will start the folding procedure right away. Running a line that
* doesn't need folding through here won't affect the outcome, but creates
* extra over head.</p>
*
* @param thisLine
* @return {@link String}
*/
public static String foldLine(String thisLine)
{
return foldLine(thisLine, FoldingScheme.MIME_DIR);
}
/**
* <p>Takes a single line and folds it according to the MIME-DIR specification.
* The line is folded if its length exceeds <code>maxChars</code> characters.
* For every <code>maxChars</code> characters a CRLF is appended to the end,
* the following line will begin with a SP character and then followed by the
* usual next <code>maxChars</code> chars + CRLF until the end.</p>
*
* <p>Strings coming out of here will always terminate with CRLF.</p>
*
* <p>This method will not check the length of the String before folding, use
* the VCardUtils.needsFolding(String) method before invoking this one. This
* method will start the folding procedure right away. Running a line that
* doesn't need folding through here won't affect the outcome, but creates
* extra over head.</p>
*
* @param thisLine
* @param foldingScheme
* @return {@link String}
*/
public static String foldLine(String thisLine, FoldingScheme foldingScheme)
{
return foldLine(thisLine, VCardUtils.CRLF, foldingScheme);
}
/**
* <p>Does the same job as {@link #foldLine(String)} but allows you
* to specify a String to be append when the line folds. By default
* it is CRLF. Here you can specify what you want. Useful when folding
* Quoted-Printable labels. Though a trailing delimiter may appear.</p>
*
* @param thisLine
* @param eolDelimeter
* @param foldingScheme
* @return {@link String}
*/
public static String foldLine(String thisLine, String eolDelimeter, FoldingScheme foldingScheme)
{
if(!needsFolding(thisLine, foldingScheme)) {
return thisLine;
}
boolean loop = true;
boolean first = true;
int crnt = 0;
int prev = 0;
StringBuilder builder = new StringBuilder();
while (loop) {
prev = crnt;
crnt = crnt + foldingScheme.getMaxChars();
if (crnt > thisLine.length()) {
// Append any extra characters at the end
if (prev < thisLine.length()) {
if (!first) {
builder.append(foldingScheme.getIndent()); // on some rare occasions
}
builder.append(thisLine.substring(prev).trim());
if(eolDelimeter != null)
builder.append(eolDelimeter);
}
loop = false;
}
else {
if (!first) {
builder.append(foldingScheme.getIndent());
}
else {
first = false;
}
builder.append(thisLine.substring(prev, crnt).trim());
if(eolDelimeter != null) {
builder.append(eolDelimeter);
}
}
}
return builder.toString().trim(); // removes the extra CRLF at the end
}
/**
* <p>Returns the number formatter for the geographical position feature.
* This will format the floating value to the 6th decimal place.</p>
*
* @return {@link NumberFormat}
*/
public static NumberFormat getGeographicPositionFormatter()
{
return GEO_NUM_FORMATTER;
}
}