blob: 11d273117a396173b3c4558db02d461063669fe1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.help.internal.webapp.servlet;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.help.IToc;
import org.eclipse.help.ITopic;
import org.eclipse.help.base.AbstractHelpScope;
import org.eclipse.help.internal.Topic;
import org.eclipse.help.internal.base.scope.ScopeUtils;
import org.eclipse.help.internal.toc.Toc;
import org.eclipse.help.internal.webapp.WebappResources;
import org.eclipse.help.internal.webapp.data.IconFinder;
import org.eclipse.help.internal.webapp.data.RequestScope;
import org.eclipse.help.internal.webapp.data.TocData;
import org.eclipse.help.internal.webapp.data.UrlUtil;
/*
* Creates xml representing selected parts of one or more TOCs depending on the parameters
* With no parameters the head of each toc is included
* With parameter "href" the node and all its ancestors and siblings is included, corresponds to show in toc
* With parameter "toc" and optionally "path" the node, its ancestors and children are included
*/
public class TocFragmentServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Map<String, String> locale2Response = new WeakHashMap<String, String>();
private boolean isErrorSuppress;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// set the character-set to UTF-8 before calling resp.getWriter()
resp.setContentType("application/xml; charset=UTF-8"); //$NON-NLS-1$
resp.getWriter().write(processRequest(req, resp));
}
protected String processRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String locale = UrlUtil.getLocale(req, resp);
req.setCharacterEncoding("UTF-8"); //$NON-NLS-1$
resp.setHeader("Cache-Control","no-cache"); //$NON-NLS-1$//$NON-NLS-2$
resp.setHeader("Pragma","no-cache"); //$NON-NLS-1$ //$NON-NLS-2$
resp.setDateHeader ("Expires", 0); //$NON-NLS-1$
TocData data = new TocData(this.getServletContext(), req, resp);
readParameters(req);
AbstractHelpScope scope = RequestScope.getScope(req, resp, false);
Serializer serializer = new Serializer(data, UrlUtil.getLocaleObj(req, resp), scope);
String response = serializer.generateTreeXml();
locale2Response.put(locale, response);
return response;
}
private void readParameters(HttpServletRequest req) {
String errorSuppressParam = req.getParameter("errorSuppress"); //$NON-NLS-1$
isErrorSuppress = "true".equalsIgnoreCase(errorSuppressParam); //$NON-NLS-1$
}
/*
* Class which creates the xml file based upon the request parameters
*/
private class Serializer {
private TocData tocData;
private StringBuffer buf;
private int requestKind;
private Locale locale;
private AbstractHelpScope scope;
private static final int REQUEST_SHOW_IN_TOC = 1; // Get the path to an element an element based on its href
private static final int REQUEST_SHOW_TOCS = 2; // Show all the tocs but not their children
private static final int REQUEST_SHOW_CHILDREN = 3; // Show the children of a node
private static final int REQUEST_EXPAND_PATH = 4; // Get all the nodes requires to expand a path in the tree
public Serializer(TocData data, Locale locale, AbstractHelpScope scope) {
tocData = data;
buf = new StringBuffer();
this.locale = locale;
this.scope = scope;
if (tocData.isExpandPath()) {
requestKind = REQUEST_EXPAND_PATH;
} else if (tocData.getTopicHref() != null) {
requestKind = REQUEST_SHOW_IN_TOC;
} else if (tocData.getSelectedToc() == -1) {
requestKind = REQUEST_SHOW_TOCS;
} else {
requestKind = REQUEST_SHOW_CHILDREN;
}
}
public String generateTreeXml() {
buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$
buf.append("<tree_data>\n"); //$NON-NLS-1$
// Return an error for show in toc if topic was not found in toc
if ((requestKind == REQUEST_SHOW_IN_TOC || requestKind == REQUEST_EXPAND_PATH) && tocData.getTopicPath() == null) {
addError(WebappResources.getString("CannotSync", locale)); //$NON-NLS-1$
} else if (requestKind == REQUEST_SHOW_IN_TOC) {
generateNumericPath();
} else {
serializeTocs();
}
buf.append("</tree_data>\n"); //$NON-NLS-1$
return buf.toString();
}
private void generateNumericPath() {
int selectedToc = tocData.getSelectedToc();
if (selectedToc < 0) {
addError(WebappResources.getString("CannotSync", locale)); //$NON-NLS-1$
} else {
// Count the number of enabled tocs
int enabled = 0;
for (int i = 0; i <= selectedToc; i++) {
if (ScopeUtils.showInTree(tocData.getTocs()[i], scope)) {
enabled++;
}
}
String fullNumericPath = "" + (enabled - 1); //$NON-NLS-1$
String numericPath = tocData.getNumericPath();
if (numericPath != null) {
fullNumericPath = fullNumericPath + '_' + numericPath;
}
buf.append("<numeric_path path=\"" + fullNumericPath + "\"/>\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private void addError(String message) {
if (!isErrorSuppress) {
buf.append("<error>"); //$NON-NLS-1$
buf.append(XMLGenerator.xmlEscape(message));
buf.append("</error>"); //$NON-NLS-1$
}
}
private void serializeTocs() {
ITopic[] topicPath = tocData.getTopicPath();
int selectedToc = tocData.getSelectedToc();
// Iterate over all tocs - if there is a selected toc only generate that
// toc, otherwise generate the root of every toc.
for (int toc=0; toc< tocData.getTocCount(); toc++) {
boolean shouldLoad = requestKind == REQUEST_SHOW_TOCS || toc == selectedToc;
if (shouldLoad) {
boolean isSelected = false; // Should this node be selected in the tree
if (requestKind == REQUEST_SHOW_TOCS) {
isSelected = toc == 0;
} else if (requestKind == REQUEST_SHOW_CHILDREN) {
isSelected = tocData.getRootPath() == null;
}
serializeToc(tocData.getTocs()[toc], toc, topicPath, isSelected);
}
}
}
private void serializeToc(IToc toc, int tocIndex, ITopic[] topicPath, boolean isSelected) {
if (!ScopeUtils.showInTree(toc, scope)) {
// do not generate toc when there are no leaf topics or if it is filtered out
return;
}
ITopic[] topics = toc.getTopics();
if (requestKind == REQUEST_SHOW_CHILDREN) {
topicPath = tocData.getTopicPathFromRootPath(toc);
}
buf.append("<node"); //$NON-NLS-1$
if (toc.getLabel() != null) {
buf.append('\n' + " title=\"" + XMLGenerator.xmlEscape(toc.getLabel()) + '"'); //$NON-NLS-1$
}
buf.append('\n' + " id=\"" + XMLGenerator.xmlEscape(toc.getHref()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
String href = fixupHref(toc.getTopic(null).getHref(), "" + tocIndex); //$NON-NLS-1$
buf.append('\n' + " href=\"" + XMLGenerator.xmlEscape(UrlUtil.getHelpURL(href)) + "\""); //$NON-NLS-1$ //$NON-NLS-2$
buf.append(createTocImageTag(toc));
boolean serializeChildren = true;
if (requestKind == REQUEST_SHOW_TOCS) {
serializeChildren = false;
}
if (requestKind == REQUEST_EXPAND_PATH && topicPath.length == 0) {
serializeChildren = false;
buf.append('\n' + " is_selected=\"true\"" ); //$NON-NLS-1$
buf.append('\n' + " is_highlighted=\"true\"" ); //$NON-NLS-1$
}
buf.append(">\n"); //$NON-NLS-1$
if (serializeChildren) {
serializeChildTopics(topics, topicPath, "", isSelected); //$NON-NLS-1$
}
buf.append("</node>\n"); //$NON-NLS-1$
}
private void serializeTopic(ITopic topic, ITopic[] topicPath, boolean isSelected, String parentPath) {
ITopic[] subtopics = topic.getSubtopics();
boolean isLeaf = !ScopeUtils.hasInScopeDescendent(topic, scope);
buf.append("<node"); //$NON-NLS-1$
if (topic.getLabel() != null) {
buf.append('\n' + " title=\"" + XMLGenerator.xmlEscape(topic.getLabel()) + '"'); //$NON-NLS-1$
}
buf.append('\n' + " id=\"" + parentPath + "\""); //$NON-NLS-1$ //$NON-NLS-2$
String href = topic.getHref();
href = fixupHref(href, "" + tocData.getSelectedToc() + '_' + parentPath); //$NON-NLS-1$
buf.append('\n' + " href=\"" + XMLGenerator.xmlEscape( //$NON-NLS-1$
UrlUtil.getHelpURL(href)) + '"');
if (isLeaf ) {
buf.append('\n' + " is_leaf=\"true\"" ); //$NON-NLS-1$
}
if (isSelected && requestKind == REQUEST_EXPAND_PATH) {
buf.append('\n' + " is_selected=\"true\"" ); //$NON-NLS-1$
buf.append('\n' + " is_highlighted=\"true\"" ); //$NON-NLS-1$
}
String imageTags = createTopicImageTags(topic, isLeaf);
buf.append(imageTags);
buf.append(">\n"); //$NON-NLS-1$
serializeChildTopics(subtopics, topicPath, parentPath, isSelected);
buf.append("</node>\n"); //$NON-NLS-1$
}
private String createTocImageTag(IToc toc) {
if (toc instanceof Toc) {
String icon = ((Toc) toc).getIcon();
if (IconFinder.isIconDefined(icon)) {
String openIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_OPEN);
String closedIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_CLOSED);
String imageTags = '\n' + " openImage=\"/"+ openIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$
if (!openIcon.equals(closedIcon)) {
imageTags += '\n' + " closedImage=\"/" + closedIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$
}
String altText = IconFinder.getIconAltFromId(icon);
if(altText != null) {
imageTags += '\n' + " imageAlt=\""+ altText + "\""; //$NON-NLS-1$ //$NON-NLS-2$
}
return imageTags;
}
}
return '\n' + " image=\"toc_closed\""; //$NON-NLS-1$
}
private String createTopicImageTags(ITopic topic, boolean isLeaf) {
if (topic instanceof Topic) {
String icon = ((Topic) topic).getIcon();
String altText = IconFinder.getIconAltFromId(icon);
if (IconFinder.isIconDefined(icon)) {
String imageTags;
if (isLeaf) {
imageTags = '\n' + " openImage=\"/" +IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_LEAF) + "\""; //$NON-NLS-1$//$NON-NLS-2$
} else {
String openIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_OPEN);
String closedIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_CLOSED);
imageTags = '\n' + " openImage=\"/" + openIcon+ "\""; //$NON-NLS-1$ //$NON-NLS-2$
if (!openIcon.equals(closedIcon)) {
imageTags += '\n' + " closedImage=\"/" + closedIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$
}
}
if(altText != null) {
imageTags += '\n' + " imageAlt=\""+ altText + "\""; //$NON-NLS-1$ //$NON-NLS-2$
}
return imageTags;
}
}
String icon;
if (isLeaf) {
icon = "topic"; //$NON-NLS-1$
} else if (topic.getHref() == null) {
icon = "container_obj"; //$NON-NLS-1$
} else {
icon = "container_topic"; //$NON-NLS-1$
}
String imageTags = '\n' + " image=\"" + icon + "\""; //$NON-NLS-1$ //$NON-NLS-2$
return imageTags;
}
private void serializeChildTopics(ITopic[] childTopics, ITopic[] topicPath, String parentPath, boolean parentIsSelected) {
if (parentIsSelected && requestKind == REQUEST_SHOW_CHILDREN) {
// Show the children of this node
for (int subtopic = 0; subtopic < childTopics.length; subtopic++) {
ITopic childTopic = childTopics[subtopic];
if (ScopeUtils.showInTree(childTopic, scope)) {
serializeTopic(childTopic, null, false, addSuffix(parentPath, subtopic));
}
}
} else if (topicPath != null) {
for (int subtopic = 0; subtopic < childTopics.length; subtopic++) {
ITopic childTopic = childTopics[subtopic];
if (ScopeUtils.showInTree(childTopic, scope)) {
if (topicPath[0].getLabel().equals(childTopic.getLabel())) {
ITopic[] newPath = null;
if (topicPath.length > 1) {
newPath = new ITopic[topicPath.length - 1];
System.arraycopy(topicPath, 1, newPath, 0, topicPath.length - 1);
}
serializeTopic(childTopic, newPath, topicPath.length == 1, addSuffix(parentPath, subtopic));
} else {
serializeTopic(childTopic, null, false, addSuffix(parentPath, subtopic));
}
}
}
}
}
private String addSuffix(String parentPath, int subtopic) {
if (parentPath.length() == 0) {
return parentPath + subtopic;
}
return parentPath + '_' + subtopic;
}
}
/*
* Add an extra parameter which represents the path within the tree. This enables show
* in Toc, print selected topic and search selected topic and all subtopics to work
* correctly even if the same page appears more than once in the table of contents, Bug 330868
* Static for testing purposes
*/
public static String fixupHref(String href, String path) {
if (href == null) {
return "/../nav/" + path; //$NON-NLS-1$
}
int aIndex = href.indexOf('#');
String anchorPart = ""; //$NON-NLS-1$
String hrefPart = href;
if (aIndex > 0) {
anchorPart = href.substring(aIndex);
hrefPart = href.substring(0, aIndex);
}
int questionIndex = href.indexOf('?');
if (questionIndex > 0 ) {
return hrefPart + "&" + TocData.COMPLETE_PATH_PARAM + '=' + path + anchorPart; //$NON-NLS-1$
} else {
return hrefPart + "?" + TocData.COMPLETE_PATH_PARAM + '=' + path + anchorPart; //$NON-NLS-1$
}
}
}