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 GmbH & Co. KG, 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.workplace.tools.content; 029 030import org.opencms.file.CmsObject; 031import org.opencms.i18n.CmsMessageContainer; 032import org.opencms.main.CmsIllegalArgumentException; 033import org.opencms.main.CmsLog; 034import org.opencms.util.CmsStringUtil; 035 036import java.util.Comparator; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041import java.util.SortedMap; 042import java.util.TreeMap; 043import java.util.TreeSet; 044import java.util.Vector; 045 046import org.apache.commons.logging.Log; 047 048import org.htmlparser.Attribute; 049import org.htmlparser.NodeFactory; 050import org.htmlparser.PrototypicalNodeFactory; 051import org.htmlparser.Tag; 052import org.htmlparser.util.ParserException; 053 054/** 055 * Bean to hold the settings needed for the operation of replacing HTML Tags of xmlpage resources in 056 * the OpenCms VFS. 057 * <p> 058 * 059 * @since 6.1.7 060 * 061 */ 062public final class CmsTagReplaceSettings { 063 064 /** 065 * Property for the tag-replace contentool to know the files that have been processed before in 066 * case of early terminaton in previous runs. 067 */ 068 public static final String PROPERTY_CONTENTOOLS_TAGREPLACE = "contentools.tagreplace"; 069 070 /** The log object for this class. */ 071 private static final Log LOG = CmsLog.getLog(CmsTagReplaceSettings.class); 072 073 /** Needed to verify if a path String denotes a folder in VFS. */ 074 private final CmsObject m_cms; 075 076 /** The tags that should be deleted. */ 077 private final Set m_deleteTags; 078 079 /** Used to create Tag instances for tags to delete of the proper type in a convenient way. */ 080 private NodeFactory m_nodeFactory; 081 082 /** 083 * The value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources that 084 * have been processed with these settings. 085 */ 086 private String m_propertyValueTagReplaceID; 087 088 /** 089 * A map containing lower case tag names of tags to replace as keys and the replacement tag 090 * names as their corresponding values. 091 */ 092 private SortedMap m_tags2replacementTags; 093 094 /** The root of all content files to process. */ 095 private String m_workPath; 096 097 /** 098 * Bean constructor with cms object for path validation. 099 * <p> 100 * 101 * @param cms used to test the working path for valididty. 102 */ 103 public CmsTagReplaceSettings(CmsObject cms) { 104 105 // Treemap guarantees no duplicate keys (ambiguous replacements) and the same default 106 // property ID's for the same replacement strings due to the ordering: 107 m_tags2replacementTags = new TreeMap(); 108 m_cms = cms; 109 // all tags are registered for creation 110 m_nodeFactory = new PrototypicalNodeFactory(); 111 m_deleteTags = new TreeSet(new Comparator() { 112 113 public int compare(Object o1, Object o2) { 114 115 return o1.getClass().getName().compareTo(o2.getClass().getName()); 116 } 117 }); 118 } 119 120 /** 121 * Returns the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources 122 * that have been processed with these settings. 123 * <p> 124 * 125 * @return the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources 126 * that have been processed with these settings. 127 */ 128 public String getPropertyValueTagReplaceID() { 129 130 return m_propertyValueTagReplaceID; 131 } 132 133 /** 134 * Returns the replacements to perform in form of a comma-separated List of "key=value" tokens. 135 * <p> 136 * 137 * @return the replacements to perform in form of a comma-separated List of "key=value" tokens. 138 */ 139 public SortedMap getReplacements() { 140 141 return m_tags2replacementTags; 142 } 143 144 /** 145 * Returns the path under which files will be processed recursively. 146 * <p> 147 * 148 * @return the path under which files will be processed recursively. 149 */ 150 public String getWorkPath() { 151 152 return m_workPath; 153 } 154 155 /** 156 * Sets the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources 157 * that have been processed with these settings. 158 * <p> 159 * 160 * @param propertyValueTagreplaceID the value of the shared 161 * {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources that have been 162 * processed with these settings. 163 * 164 * @throws CmsIllegalArgumentException if the argument is not valid. 165 */ 166 public void setPropertyValueTagReplaceID(String propertyValueTagreplaceID) throws CmsIllegalArgumentException { 167 168 if (CmsStringUtil.isEmptyOrWhitespaceOnly(propertyValueTagreplaceID)) { 169 m_propertyValueTagReplaceID = getDefaultTagReplaceID(); 170 } else { 171 m_propertyValueTagReplaceID = propertyValueTagreplaceID; 172 } 173 } 174 175 /** 176 * Sets the replacements to perform in form of a comma-separated List of "key=value" tokens. 177 * <p> 178 * 179 * @param replacements the replacements to perform in form of a comma-separated List of 180 * "key=value" tokens. 181 * 182 * @throws CmsIllegalArgumentException if the argument is not valid. 183 */ 184 public void setReplacements(SortedMap replacements) throws CmsIllegalArgumentException { 185 186 Iterator itMappings = replacements.entrySet().iterator(); 187 Map.Entry entry; 188 String key, value; 189 while (itMappings.hasNext()) { 190 entry = (Map.Entry)itMappings.next(); 191 key = (String)entry.getKey(); 192 value = (String)entry.getValue(); 193 if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) { 194 // removal 195 Tag deleteTag; 196 String tagName = (key).toLowerCase().trim(); 197 try { 198 Vector attributeList = new Vector(1); 199 Attribute tagNameAttribute = new Attribute(); 200 tagNameAttribute.setName(tagName); 201 attributeList.add(tagNameAttribute); 202 deleteTag = m_nodeFactory.createTagNode(null, 0, 0, attributeList); 203 m_deleteTags.add(deleteTag); 204 itMappings.remove(); 205 } catch (ParserException e) { 206 CmsMessageContainer container = Messages.get().container( 207 Messages.GUI_ERR_TAGREPLACE_TAGNAME_INVALID_1, 208 tagName); 209 throw new CmsIllegalArgumentException(container, e); 210 } 211 } else { 212 // nop 213 } 214 m_tags2replacementTags = replacements; 215 } 216 // if setPropertyValueTagReplaceID has been invoked earlier with empty value 217 // due to missing user input: 218 if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_propertyValueTagReplaceID)) { 219 // trigger computation of default ID by empty value: 220 setPropertyValueTagReplaceID(null); 221 } 222 } 223 224 /** 225 * Sets the path under which files will be processed recursively. 226 * <p> 227 * 228 * @param workPath the path under which files will be processed recursively. 229 * 230 * @throws CmsIllegalArgumentException if the argument is not valid. 231 */ 232 public void setWorkPath(String workPath) throws CmsIllegalArgumentException { 233 234 if (CmsStringUtil.isEmptyOrWhitespaceOnly(workPath)) { 235 throw new CmsIllegalArgumentException(Messages.get().container(Messages.GUI_ERR_WIDGETVALUE_EMPTY_0)); 236 } 237 // test if it is a valid path: 238 if (!m_cms.existsResource(workPath)) { 239 throw new CmsIllegalArgumentException( 240 Messages.get().container(Messages.GUI_ERR_TAGREPLACE_WORKPATH_1, workPath)); 241 } 242 m_workPath = workPath; 243 244 } 245 246 /** 247 * Returns the Set<{@link org.htmlparser.Tag}> to delete from transformed output. 248 * <p> 249 * 250 * @return the Set<{@link org.htmlparser.Tag}> to delete from transformed output. 251 */ 252 protected Set getDeleteTags() { 253 254 return m_deleteTags; 255 } 256 257 /** 258 * Transforms the given Tag into the one it has to become by changing it's name and/or 259 * attributes. 260 * <p> 261 * 262 * @param tag the tag to be transformed. 263 * 264 * @return true if the given tag was modified, false else. 265 * 266 */ 267 protected boolean replace(org.htmlparser.Tag tag) { 268 269 boolean result = false; 270 String tagName = tag.getTagName().trim().toLowerCase(); 271 String replacementName = (String)m_tags2replacementTags.get(tagName); 272 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(replacementName)) { 273 // judge this as a bug: getTagName() returns plain name, setter needs leading '/' for 274 // TODO: when updating htmlparser, verify if this has changed / been fixed 275 // closing tags 276 if (tag.isEndTag()) { 277 replacementName = "/" + replacementName; 278 } 279 tag.setTagName(replacementName); 280 result = true; 281 // clear the attributes too: 282 List attributes = tag.getAttributesEx(); 283 Iterator itAttribs = attributes.iterator(); 284 // skip the "tagname attribute".... 285 itAttribs.next(); 286 Attribute attribute; 287 String attName; 288 while (itAttribs.hasNext()) { 289 attribute = (Attribute)itAttribs.next(); 290 attName = attribute.getName(); 291 if (CmsStringUtil.isEmptyOrWhitespaceOnly(attName)) { 292 // this is the case for e.g. <h1 > 293 // -> becomes a tag with an attribute for tag name and a null name attribute 294 // (for the whitespace!) 295 } else { 296 if (LOG.isDebugEnabled()) { 297 LOG.debug( 298 Messages.get().getBundle().key( 299 Messages.LOG_DEBUG_TAGREPLACE_TAG_REMOVE_ATTRIB_2, 300 attName, 301 tag.getTagName())); 302 303 } 304 itAttribs.remove(); 305 if (LOG.isDebugEnabled()) { 306 LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEBUG_TAGREPLACE_TAG_REMOVE_ATTRIB_OK_0)); 307 } 308 } 309 } 310 } 311 return result; 312 } 313 314 /** 315 * Computes the default property value for resources that have to be marked as "processed by 316 * these replacement settings". 317 * <p> 318 * 319 * The default value will be the alphabetically sorted string for replacments or the empty 320 * String if the replacements have not been set before. 321 * <p> 322 * 323 * @return the default property value for resources that have to be marked as "processed by 324 * these replacement settings". 325 */ 326 private String getDefaultTagReplaceID() { 327 328 if (m_tags2replacementTags.size() == 0) { 329 return ""; // to know that no replacements were set before and the ID will still have 330 // to be computed later 331 } else { 332 StringBuffer result = new StringBuffer(); 333 Map.Entry entry; 334 Iterator itEntries = m_tags2replacementTags.entrySet().iterator(); 335 while (itEntries.hasNext()) { 336 entry = (Map.Entry)itEntries.next(); 337 result.append(entry.getKey()).append('=').append(entry.getValue()); 338 if (itEntries.hasNext()) { 339 result.append(','); 340 } 341 } 342 return result.toString(); 343 344 } 345 } 346}