/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* For further information about Alkacon Software GmbH, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.workplace.tools.content;
import org.opencms.file.CmsObject;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.util.CmsStringUtil;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import org.apache.commons.logging.Log;
import org.htmlparser.Attribute;
import org.htmlparser.NodeFactory;
import org.htmlparser.PrototypicalNodeFactory;
import org.htmlparser.Tag;
import org.htmlparser.util.ParserException;
/**
* Bean to hold the settings needed for the operation of replacing HTML Tags of xmlpage resources in
* the OpenCms VFS.
* <p>
*
* @since 6.1.7
*
*/
public final class CmsTagReplaceSettings {
/**
* Property for the tag-replace contentool to know the files that have been processed before in
* case of early terminaton in previous runs.
*/
public static final String PROPERTY_CONTENTOOLS_TAGREPLACE = "contentools.tagreplace";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsTagReplaceSettings.class);
/** Needed to verify if a path String denotes a folder in VFS. */
private final CmsObject m_cms;
/** The tags that should be deleted. */
private final Set m_deleteTags;
/** Used to create Tag instances for tags to delete of the proper type in a convenient way. */
private NodeFactory m_nodeFactory;
/**
* The value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources that
* have been processed with these settings.
*/
private String m_propertyValueTagReplaceID;
/**
* A map containing lower case tag names of tags to replace as keys and the replacement tag
* names as their corresponding values.
*/
private SortedMap m_tags2replacementTags;
/** The root of all content files to process. */
private String m_workPath;
/**
* Bean constructor with cms object for path validation.
* <p>
*
* @param cms used to test the working path for valididty.
*/
public CmsTagReplaceSettings(CmsObject cms) {
// Treemap guarantees no duplicate keys (ambiguous replacements) and the same default
// property ID's for the same replacement strings due to the ordering:
m_tags2replacementTags = new TreeMap();
m_cms = cms;
// all tags are registered for creation
m_nodeFactory = new PrototypicalNodeFactory();
m_deleteTags = new TreeSet(new Comparator() {
public int compare(Object o1, Object o2) {
return o1.getClass().getName().compareTo(o2.getClass().getName());
}
});
}
/**
* Returns the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources
* that have been processed with these settings.
* <p>
*
* @return the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources
* that have been processed with these settings.
*/
public String getPropertyValueTagReplaceID() {
return m_propertyValueTagReplaceID;
}
/**
* Returns the replacements to perform in form of a comma-separated List of "key=value" tokens.
* <p>
*
* @return the replacements to perform in form of a comma-separated List of "key=value" tokens.
*/
public SortedMap getReplacements() {
return m_tags2replacementTags;
}
/**
* Returns the path under which files will be processed recursively.
* <p>
*
* @return the path under which files will be processed recursively.
*/
public String getWorkPath() {
return m_workPath;
}
/**
* Sets the value of the shared {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources
* that have been processed with these settings.
* <p>
*
* @param propertyValueTagreplaceID the value of the shared
* {@link #PROPERTY_CONTENTOOLS_TAGREPLACE} to set on resources that have been
* processed with these settings.
*
* @throws CmsIllegalArgumentException if the argument is not valid.
*/
public void setPropertyValueTagReplaceID(String propertyValueTagreplaceID) throws CmsIllegalArgumentException {
if (CmsStringUtil.isEmptyOrWhitespaceOnly(propertyValueTagreplaceID)) {
m_propertyValueTagReplaceID = getDefaultTagReplaceID();
} else {
m_propertyValueTagReplaceID = propertyValueTagreplaceID;
}
}
/**
* Sets the replacements to perform in form of a comma-separated List of "key=value" tokens.
* <p>
*
* @param replacements the replacements to perform in form of a comma-separated List of
* "key=value" tokens.
*
* @throws CmsIllegalArgumentException if the argument is not valid.
*/
public void setReplacements(SortedMap replacements) throws CmsIllegalArgumentException {
Iterator itMappings = replacements.entrySet().iterator();
Map.Entry entry;
String key, value;
while (itMappings.hasNext()) {
entry = (Map.Entry)itMappings.next();
key = (String)entry.getKey();
value = (String)entry.getValue();
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
// removal
Tag deleteTag;
String tagName = (key).toLowerCase().trim();
try {
Vector attributeList = new Vector(1);
Attribute tagNameAttribute = new Attribute();
tagNameAttribute.setName(tagName);
attributeList.add(tagNameAttribute);
deleteTag = m_nodeFactory.createTagNode(null, 0, 0, attributeList);
m_deleteTags.add(deleteTag);
itMappings.remove();
} catch (ParserException e) {
CmsMessageContainer container = Messages.get().container(
Messages.GUI_ERR_TAGREPLACE_TAGNAME_INVALID_1,
tagName);
throw new CmsIllegalArgumentException(container, e);
}
} else {
// nop
}
m_tags2replacementTags = replacements;
}
// if setPropertyValueTagReplaceID has been invoked earlier with empty value
// due to missing user input:
if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_propertyValueTagReplaceID)) {
// trigger computation of default ID by empty value:
setPropertyValueTagReplaceID(null);
}
}
/**
* Sets the path under which files will be processed recursively.
* <p>
*
* @param workPath the path under which files will be processed recursively.
*
* @throws CmsIllegalArgumentException if the argument is not valid.
*/
public void setWorkPath(String workPath) throws CmsIllegalArgumentException {
if (CmsStringUtil.isEmptyOrWhitespaceOnly(workPath)) {
throw new CmsIllegalArgumentException(Messages.get().container(Messages.GUI_ERR_WIDGETVALUE_EMPTY_0));
}
// test if it is a valid path:
if (!m_cms.existsResource(workPath)) {
throw new CmsIllegalArgumentException(Messages.get().container(
Messages.GUI_ERR_TAGREPLACE_WORKPATH_1,
workPath));
}
m_workPath = workPath;
}
/**
* Returns the Set<{@link org.htmlparser.Tag}> to delete from transformed output.
* <p>
*
* @return the Set<{@link org.htmlparser.Tag}> to delete from transformed output.
*/
protected Set getDeleteTags() {
return m_deleteTags;
}
/**
* Transforms the given Tag into the one it has to become by changing it's name and/or
* attributes.
* <p>
*
* @param tag the tag to be transformed.
*
* @return true if the given tag was modified, false else.
*
*/
protected boolean replace(org.htmlparser.Tag tag) {
boolean result = false;
String tagName = tag.getTagName().trim().toLowerCase();
String replacementName = (String)m_tags2replacementTags.get(tagName);
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(replacementName)) {
// judge this as a bug: getTagName() returns plain name, setter needs leading '/' for
// TODO: when updating htmlparser, verify if this has changed / been fixed
// closing tags
if (tag.isEndTag()) {
replacementName = "/" + replacementName;
}
tag.setTagName(replacementName);
result = true;
// clear the attributes too:
List attributes = tag.getAttributesEx();
Iterator itAttribs = attributes.iterator();
// skip the "tagname attribute"....
itAttribs.next();
Attribute attribute;
String attName;
while (itAttribs.hasNext()) {
attribute = (Attribute)itAttribs.next();
attName = attribute.getName();
if (CmsStringUtil.isEmptyOrWhitespaceOnly(attName)) {
// this is the case for e.g. <h1 >
// -> becomes a tag with an attribute for tag name and a null name attribute
// (for the whitespace!)
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(
Messages.LOG_DEBUG_TAGREPLACE_TAG_REMOVE_ATTRIB_2,
attName, tag.getTagName()));
}
itAttribs.remove();
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(
Messages.LOG_DEBUG_TAGREPLACE_TAG_REMOVE_ATTRIB_OK_0));
}
}
}
}
return result;
}
/**
* Computes the default property value for resources that have to be marked as "processed by
* these replacement settings".
* <p>
*
* The default value will be the alphabetically sorted string for replacments or the empty
* String if the replacements have not been set before.
* <p>
*
* @return the default property value for resources that have to be marked as "processed by
* these replacement settings".
*/
private String getDefaultTagReplaceID() {
if (m_tags2replacementTags.size() == 0) {
return ""; // to know that no replacements were set before and the ID will still have
// to be computed later
} else {
StringBuffer result = new StringBuffer();
Map.Entry entry;
Iterator itEntries = m_tags2replacementTags.entrySet().iterator();
while (itEntries.hasNext()) {
entry = (Map.Entry)itEntries.next();
result.append(entry.getKey()).append('=').append(entry.getValue());
if (itEntries.hasNext()) {
result.append(',');
}
}
return result.toString();
}
}
}