/******************************************************************************* * Copyright (c) 2006, 2016 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<>(); 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$ } } }