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}