001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.client.widgets;
029
030import org.opencms.util.CmsStringUtil;
031
032import java.util.ArrayList;
033import java.util.LinkedHashMap;
034import java.util.List;
035import java.util.Map;
036
037import com.google.gwt.regexp.shared.MatchResult;
038import com.google.gwt.regexp.shared.RegExp;
039
040/**
041 * Parses the configuration for various select widgets, including multi-select and combo-box.<p>
042 *
043 * It expects the canonical format of the configuration options as it is produced
044 * by {@link org.opencms.widgets.CmsSelectWidgetOption#createConfigurationString(List)}
045 */
046public class CmsSelectConfigurationParser {
047
048    /** Delimiter at the end of a value. */
049    private static final char VALUE_DELIMITER = '\'';
050
051    /** Key prefix for the 'default'. */
052    private static final String KEY_DEFAULT = "default='";
053
054    /** Key prefix for the 'help' text. */
055    private static final String KEY_HELP = "help='";
056
057    /** Key prefix for the 'option' text. */
058    private static final String KEY_OPTION = "option='";
059
060    /** Key prefix for the 'value'. */
061    private static final String KEY_VALUE = "value='";
062
063    /** The configuration to parse. */
064    private String m_configuration;
065
066    /** The default values. */
067    private List<String> m_defaultValues;
068
069    /** The help texts. */
070    private Map<String, String> m_helpTexts;
071
072    /** The options. */
073    private Map<String, String> m_options;
074
075    /**
076     * Constructor. Will parse the given configuration string.<p>
077     *
078     * @param configuration the configuration
079     */
080    public CmsSelectConfigurationParser(String configuration) {
081
082        m_configuration = configuration;
083        m_options = new LinkedHashMap<String, String>();
084        m_helpTexts = new LinkedHashMap<String, String>();
085        m_defaultValues = new ArrayList<String>();
086        parseConfiguration();
087    }
088
089    /**
090     * Splits the options string at every unescaped input delimiter, i.e., every unescaped "|".
091     * @param input the options string
092     * @return the array with the various options
093     */
094    public static int findNextUnescapedSingleQuote(String input) {
095
096        //Note that we use a regex matching all "|" characters not prefixed by "\"
097        //Since we define a regex for matching, the input delimiter "|" needs to be escaped, as well as "\",
098        //which is even double-escaped - one escaping is due to the String, one due to the regex.
099        RegExp regex = RegExp.compile("(?<!\\\\)\\" + VALUE_DELIMITER);
100        MatchResult match = regex.exec(input);
101        return match.getIndex();
102    }
103
104    /**
105     * Splits the options string at every unescaped input delimiter, i.e., every unescaped "|".
106     * @param input the options string
107     * @return the array with the various options
108     */
109    public static String[] splitOptions(String input) {
110
111        //Note that we use a regex matching all "|" characters not prefixed by "\"
112
113        //Since we define a regex for matching, the input delimiter "|" needs to be escaped, as well as "\",
114        //which is even double-escaped - one escaping is due to the String, one due to the regex.
115
116        // emulate missing lookbehinds in JS regexes by first reversing the input,
117        // then using a split with lookaheads, and finally reversing the parts resulting
118        // from the split
119        String reverse = reverse(input);
120        String[] parts = reverse.split("\\|(?!\\\\)");
121        String[] finalParts = new String[parts.length];
122        int lastIndex = parts.length - 1;
123        for (int i = 0; i < parts.length; i++) {
124            finalParts[lastIndex - i] = reverse(parts[i]);
125        }
126        return finalParts;
127
128    }
129
130    /**
131     * Reverses a string.<p>
132     *
133     * @param input the input string
134     * @return the reversed string
135     */
136    private static String reverse(String input) {
137
138        return new StringBuilder(input).reverse().toString();
139    }
140
141    /**
142     * Returns the default value.<p>
143     *
144     * @return the default value
145     */
146    public String getDefaultValue() {
147
148        String value = null;
149        if (!m_defaultValues.isEmpty()) {
150            value = m_defaultValues.get(m_defaultValues.size() - 1);
151        }
152        return value;
153    }
154
155    /**
156     * Returns the default value.<p>
157     *
158     * @return the default value
159     */
160    public List<String> getDefaultValues() {
161
162        return m_defaultValues;
163    }
164
165    /**
166     * Returns the help texts.<p>
167     *
168     * @return the help texts
169     */
170    public Map<String, String> getHelpTexts() {
171
172        return m_helpTexts;
173    }
174
175    /**
176     * Returns the options.<p>
177     *
178     * @return the options
179     */
180    public Map<String, String> getOptions() {
181
182        return m_options;
183    }
184
185    /**
186     * Parses the configuration string if provided in canonical format.<p>
187     *
188     * At the client side, the string has always to be in the canonical format "value='...' default='...' option='...' help='...'|..."
189     * where only value is mandatory, the other things are optional.
190     *
191     * The format is produced by {@link org.opencms.widgets.CmsSelectWidgetOption#createConfigurationString(List)}.
192     */
193    private void parseConfiguration() {
194
195        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_configuration)) {
196            //split the configuration in single strings to handle every string single.
197            String[] selectOptions = splitOptions(m_configuration);
198
199            for (int i = 0; i < selectOptions.length; i++) {
200                try {
201                    String value;
202                    String label = "";
203                    boolean isDefault = false;
204                    String help = "";
205                    String option = selectOptions[i];
206                    option = option.replace("\\|", "|");
207
208                    int valuePos = option.indexOf(KEY_VALUE);
209                    int defaultPos = option.indexOf(KEY_DEFAULT);
210                    int optionPos = option.indexOf(KEY_OPTION);
211                    int helpPos = option.indexOf(KEY_HELP);
212                    if (valuePos != 0) {
213                        throw new Exception("Invalid select widget configuration \"" + option + "\" is ignored.");
214                    }
215                    int end = defaultPos >= 0
216                    ? defaultPos
217                    : optionPos >= 0 ? optionPos : helpPos >= 0 ? helpPos : option.length();
218                    value = option.substring((valuePos + KEY_VALUE.length()) - 1, end).trim();
219                    value = value.substring(1, value.length() - 1);
220
221                    isDefault = (defaultPos >= 0)
222                        && option.substring(defaultPos + KEY_DEFAULT.length()).startsWith("true");
223
224                    if (optionPos >= 0) {
225                        end = helpPos >= 0 ? helpPos : option.length();
226                        label = option.substring((optionPos + KEY_OPTION.length()) - 1, end).trim();
227                        label = label.substring(1, label.length() - 1);
228                    } else {
229                        label = value;
230                    }
231                    if (helpPos >= 0) {
232                        help = option.substring((helpPos + KEY_HELP.length()) - 1, option.length()).trim();
233                        help = help.substring(1, help.length() - 1);
234                    }
235                    m_options.put(value, label);
236                    m_helpTexts.put(value, help);
237                    if (isDefault) {
238                        m_defaultValues.add(value);
239                    }
240                } catch (Exception e) {
241                    e.printStackTrace();
242                }
243            }
244        }
245    }
246}