001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util;
018
019import java.util.ArrayList;
020import java.util.List;
021
022/**
023 * Utility class for parsing quoted string which is intended for parameters, separated by comma.
024 */
025public final class StringQuoteHelper {
026
027    private StringQuoteHelper() {
028    }
029
030    /**
031     * Returns the text wrapped double quotes
032     */
033    public static String doubleQuote(String text) {
034        return quote(text, "\"");
035    }
036
037    /**
038     * Returns the text wrapped single quotes
039     */
040    public static String singleQuote(String text) {
041        return quote(text, "'");
042    }
043
044    /**
045     * Wraps the text in the given quote text
046     *
047     * @param  text  the text to wrap in quotes
048     * @param  quote the quote text added to the prefix and postfix of the text
049     *
050     * @return       the text wrapped in the given quotes
051     */
052    public static String quote(String text, String quote) {
053        return quote + text + quote;
054    }
055
056    /**
057     * Splits the input safely honoring if values is enclosed in quotes.
058     * <p/>
059     * Though this method does not support double quoting values. A quoted value must start with the same start and
060     * ending quote, which is either a single quote or double quote value.
061     * <p/>
062     * Will <i>trim</i> each split value by default.
063     *
064     * @param  input     the input
065     * @param  separator the separator char to split the input, for example a comma.
066     * @return           the input split, or <tt>null</tt> if the input is null.
067     */
068    public static String[] splitSafeQuote(String input, char separator) {
069        return splitSafeQuote(input, separator, true, false);
070    }
071
072    /**
073     * Splits the input safely honoring if values is enclosed in quotes.
074     * <p/>
075     * Though this method does not support double quoting values. A quoted value must start with the same start and
076     * ending quote, which is either a single quote or double quote value.
077     *
078     * @param  input     the input
079     * @param  separator the separator char to split the input, for example a comma.
080     * @param  trim      whether to trim each split value
081     * @return           the input split, or <tt>null</tt> if the input is null.
082     */
083    public static String[] splitSafeQuote(String input, char separator, boolean trim) {
084        return splitSafeQuote(input, separator, trim, false);
085    }
086
087    /**
088     * Splits the input safely honoring if values is enclosed in quotes.
089     * <p/>
090     * Though this method does not support double quoting values. A quoted value must start with the same start and
091     * ending quote, which is either a single quote or double quote value.
092     *
093     * @param  input      the input
094     * @param  separator  the separator char to split the input, for example a comma.
095     * @param  trim       whether to trim each split value
096     * @param  keepQuotes whether to keep quotes
097     * @return            the input split, or <tt>null</tt> if the input is null.
098     */
099    public static String[] splitSafeQuote(String input, char separator, boolean trim, boolean keepQuotes) {
100        if (input == null) {
101            return null;
102        }
103
104        if (input.indexOf(separator) == -1) {
105            if (input.length() > 1) {
106                char ch = input.charAt(0);
107                char ch2 = input.charAt(input.length() - 1);
108                boolean singleQuoted = ch == '\'' && ch2 == '\'';
109                boolean doubleQuoted = ch == '"' && ch2 == '"';
110                if (!keepQuotes && (singleQuoted || doubleQuoted)) {
111                    input = input.substring(1, input.length() - 1);
112                    // do not trim quoted text
113                } else if (trim) {
114                    input = input.trim();
115                }
116            }
117            // no separator in data, so return single string with input as is
118            return new String[] { input };
119        }
120
121        List<String> answer = new ArrayList<>();
122        StringBuilder sb = new StringBuilder();
123
124        boolean singleQuoted = false;
125        boolean doubleQuoted = false;
126        boolean separating = false;
127
128        for (int i = 0; i < input.length(); i++) {
129            char ch = input.charAt(i);
130            char prev = i > 0 ? input.charAt(i - 1) : 0;
131            boolean isQuoting = singleQuoted || doubleQuoted;
132            boolean last = i == input.length() - 1;
133
134            if (!doubleQuoted && ch == '\'') {
135                if (singleQuoted && prev == ch && sb.isEmpty()) {
136                    // its an empty quote so add empty text
137                    if (keepQuotes) {
138                        answer.add("''");
139                    } else {
140                        answer.add("");
141                    }
142                }
143                // special logic needed if this quote is the end
144                if (last) {
145                    if (singleQuoted && !sb.isEmpty()) {
146                        String text = sb.toString();
147                        // do not trim a quoted string
148                        if (keepQuotes) {
149                            answer.add(text + "'"); // append ending quote
150                        } else {
151                            answer.add(text);
152                        }
153                        sb.setLength(0);
154                    }
155                    break; // break out as we are finished
156                }
157                singleQuoted = !singleQuoted;
158                if (keepQuotes) {
159                    sb.append(ch);
160                }
161                continue;
162            } else if (!singleQuoted && ch == '"') {
163                if (doubleQuoted && prev == ch && sb.isEmpty()) {
164                    // its an empty quote so add empty text
165                    if (keepQuotes) {
166                        answer.add("\""); // append ending quote
167                    } else {
168                        answer.add("");
169                    }
170                }
171                // special logic needed if this quote is the end
172                if (last) {
173                    if (doubleQuoted && !sb.isEmpty()) {
174                        String text = sb.toString();
175                        // do not trim a quoted string
176                        if (keepQuotes) {
177                            answer.add(text + "\"");
178                        } else {
179                            answer.add(text);
180                        }
181                        sb.setLength(0);
182                    }
183                    break; // break out as we are finished
184                }
185                doubleQuoted = !doubleQuoted;
186                if (keepQuotes) {
187                    sb.append(ch);
188                }
189                continue;
190            } else if (!isQuoting && ch == separator) {
191                separating = true;
192                // add as answer if we are not in a quote
193                if (!sb.isEmpty()) {
194                    String text = sb.toString();
195                    if (trim) {
196                        text = text.trim();
197                    }
198                    answer.add(text);
199                    sb.setLength(0);
200                }
201                // we should avoid adding the separator
202                continue;
203            }
204
205            if (trim && !isQuoting && separating && separator != ' ' && ch == ' ') {
206                continue;
207            }
208            separating = false;
209
210            // append char
211            sb.append(ch);
212        }
213
214        // any leftover
215        if (!sb.isEmpty()) {
216            String text = sb.toString();
217            if (trim) {
218                text = text.trim();
219            }
220            answer.add(text);
221        }
222
223        return answer.toArray(new String[0]);
224    }
225
226}