1 /******************************************************************************* 2 * Copyright (c) 2006, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.help.internal.webapp.servlet; 15 16 import java.io.IOException; 17 import java.util.Locale; 18 import java.util.Map; 19 import java.util.WeakHashMap; 20 21 import javax.servlet.ServletException; 22 import javax.servlet.http.HttpServlet; 23 import javax.servlet.http.HttpServletRequest; 24 import javax.servlet.http.HttpServletResponse; 25 26 import org.eclipse.help.IToc; 27 import org.eclipse.help.ITopic; 28 import org.eclipse.help.base.AbstractHelpScope; 29 import org.eclipse.help.internal.Topic; 30 import org.eclipse.help.internal.base.scope.ScopeUtils; 31 import org.eclipse.help.internal.toc.Toc; 32 import org.eclipse.help.internal.webapp.WebappResources; 33 import org.eclipse.help.internal.webapp.data.IconFinder; 34 import org.eclipse.help.internal.webapp.data.RequestScope; 35 import org.eclipse.help.internal.webapp.data.TocData; 36 import org.eclipse.help.internal.webapp.data.UrlUtil; 37 38 /* 39 * Creates xml representing selected parts of one or more TOCs depending on the parameters 40 * With no parameters the head of each toc is included 41 * With parameter "href" the node and all its ancestors and siblings is included, corresponds to show in toc 42 * With parameter "toc" and optionally "path" the node, its ancestors and children are included 43 */ 44 public class TocFragmentServlet extends HttpServlet { 45 46 private static final long serialVersionUID = 1L; 47 private static Map<String, String> locale2Response = new WeakHashMap<>(); 48 private boolean isErrorSuppress; 49 50 @Override doGet(HttpServletRequest req, HttpServletResponse resp)51 protected void doGet(HttpServletRequest req, HttpServletResponse resp) 52 throws ServletException, IOException { 53 // set the character-set to UTF-8 before calling resp.getWriter() 54 resp.setContentType("application/xml; charset=UTF-8"); //$NON-NLS-1$ 55 resp.getWriter().write(processRequest(req, resp)); 56 } 57 processRequest(HttpServletRequest req, HttpServletResponse resp)58 protected String processRequest(HttpServletRequest req, HttpServletResponse resp) 59 throws ServletException, IOException { 60 String locale = UrlUtil.getLocale(req, resp); 61 req.setCharacterEncoding("UTF-8"); //$NON-NLS-1$ 62 resp.setHeader("Cache-Control","no-cache"); //$NON-NLS-1$//$NON-NLS-2$ 63 resp.setHeader("Pragma","no-cache"); //$NON-NLS-1$ //$NON-NLS-2$ 64 resp.setDateHeader ("Expires", 0); //$NON-NLS-1$ 65 TocData data = new TocData(this.getServletContext(), req, resp); 66 67 readParameters(req); 68 69 AbstractHelpScope scope = RequestScope.getScope(req, resp, false); 70 Serializer serializer = new Serializer(data, UrlUtil.getLocaleObj(req, resp), scope); 71 String response = serializer.generateTreeXml(); 72 locale2Response.put(locale, response); 73 74 return response; 75 } 76 readParameters(HttpServletRequest req)77 private void readParameters(HttpServletRequest req) { 78 String errorSuppressParam = req.getParameter("errorSuppress"); //$NON-NLS-1$ 79 isErrorSuppress = "true".equalsIgnoreCase(errorSuppressParam); //$NON-NLS-1$ 80 } 81 82 /* 83 * Class which creates the xml file based upon the request parameters 84 */ 85 private class Serializer { 86 87 private TocData tocData; 88 private StringBuilder buf; 89 private int requestKind; 90 private Locale locale; 91 private AbstractHelpScope scope; 92 private static final int REQUEST_SHOW_IN_TOC = 1; // Get the path to an element an element based on its href 93 private static final int REQUEST_SHOW_TOCS = 2; // Show all the tocs but not their children 94 private static final int REQUEST_SHOW_CHILDREN = 3; // Show the children of a node 95 private static final int REQUEST_EXPAND_PATH = 4; // Get all the nodes requires to expand a path in the tree 96 Serializer(TocData data, Locale locale, AbstractHelpScope scope)97 public Serializer(TocData data, Locale locale, AbstractHelpScope scope) { 98 tocData = data; 99 buf = new StringBuilder(); 100 this.locale = locale; 101 this.scope = scope; 102 if (tocData.isExpandPath()) { 103 requestKind = REQUEST_EXPAND_PATH; 104 } else if (tocData.getTopicHref() != null) { 105 requestKind = REQUEST_SHOW_IN_TOC; 106 } else if (tocData.getSelectedToc() == -1) { 107 requestKind = REQUEST_SHOW_TOCS; 108 } else { 109 requestKind = REQUEST_SHOW_CHILDREN; 110 } 111 } 112 generateTreeXml()113 public String generateTreeXml() { 114 buf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); //$NON-NLS-1$ 115 buf.append("<tree_data>\n"); //$NON-NLS-1$ 116 117 // Return an error for show in toc if topic was not found in toc 118 if ((requestKind == REQUEST_SHOW_IN_TOC || requestKind == REQUEST_EXPAND_PATH) && tocData.getTopicPath() == null) { 119 addError(WebappResources.getString("CannotSync", locale)); //$NON-NLS-1$ 120 } else if (requestKind == REQUEST_SHOW_IN_TOC) { 121 generateNumericPath(); 122 } else { 123 serializeTocs(); 124 } 125 buf.append("</tree_data>\n"); //$NON-NLS-1$ 126 return buf.toString(); 127 } 128 129 generateNumericPath()130 private void generateNumericPath() { 131 int selectedToc = tocData.getSelectedToc(); 132 if (selectedToc < 0) { 133 addError(WebappResources.getString("CannotSync", locale)); //$NON-NLS-1$ 134 } else { 135 // Count the number of enabled tocs 136 int enabled = 0; 137 for (int i = 0; i <= selectedToc; i++) { 138 if (ScopeUtils.showInTree(tocData.getTocs()[i], scope)) { 139 enabled++; 140 } 141 } 142 String fullNumericPath = "" + (enabled - 1); //$NON-NLS-1$ 143 String numericPath = tocData.getNumericPath(); 144 if (numericPath != null) { 145 fullNumericPath = fullNumericPath + '_' + numericPath; 146 } 147 buf.append("<numeric_path path=\"" + fullNumericPath + "\"/>\n"); //$NON-NLS-1$ //$NON-NLS-2$ 148 } 149 } 150 addError(String message)151 private void addError(String message) { 152 if (!isErrorSuppress) { 153 buf.append("<error>"); //$NON-NLS-1$ 154 buf.append(XMLGenerator.xmlEscape(message)); 155 buf.append("</error>"); //$NON-NLS-1$ 156 } 157 } 158 serializeTocs()159 private void serializeTocs() { 160 ITopic[] topicPath = tocData.getTopicPath(); 161 162 int selectedToc = tocData.getSelectedToc(); 163 // Iterate over all tocs - if there is a selected toc only generate that 164 // toc, otherwise generate the root of every toc. 165 for (int toc=0; toc< tocData.getTocCount(); toc++) { 166 boolean shouldLoad = requestKind == REQUEST_SHOW_TOCS || toc == selectedToc; 167 if (shouldLoad) { 168 boolean isSelected = false; // Should this node be selected in the tree 169 if (requestKind == REQUEST_SHOW_TOCS) { 170 isSelected = toc == 0; 171 } else if (requestKind == REQUEST_SHOW_CHILDREN) { 172 isSelected = tocData.getRootPath() == null; 173 } 174 serializeToc(tocData.getTocs()[toc], toc, topicPath, isSelected); 175 } 176 } 177 } 178 serializeToc(IToc toc, int tocIndex, ITopic[] topicPath, boolean isSelected)179 private void serializeToc(IToc toc, int tocIndex, ITopic[] topicPath, boolean isSelected) { 180 181 if (!ScopeUtils.showInTree(toc, scope)) { 182 // do not generate toc when there are no leaf topics or if it is filtered out 183 return; 184 } 185 ITopic[] topics = toc.getTopics(); 186 187 if (requestKind == REQUEST_SHOW_CHILDREN) { 188 topicPath = tocData.getTopicPathFromRootPath(toc); 189 } 190 191 buf.append("<node"); //$NON-NLS-1$ 192 if (toc.getLabel() != null) { 193 buf.append('\n' + " title=\"" + XMLGenerator.xmlEscape(toc.getLabel()) + '"'); //$NON-NLS-1$ 194 } 195 buf.append('\n' + " id=\"" + XMLGenerator.xmlEscape(toc.getHref()) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ 196 197 String href = fixupHref(toc.getTopic(null).getHref(), "" + tocIndex); //$NON-NLS-1$ 198 buf.append('\n' + " href=\"" + XMLGenerator.xmlEscape(UrlUtil.getHelpURL(href)) + "\""); //$NON-NLS-1$ //$NON-NLS-2$ 199 200 buf.append(createTocImageTag(toc)); 201 202 boolean serializeChildren = true; 203 if (requestKind == REQUEST_SHOW_TOCS) { 204 serializeChildren = false; 205 } 206 if (requestKind == REQUEST_EXPAND_PATH && topicPath.length == 0) { 207 serializeChildren = false; 208 buf.append('\n' + " is_selected=\"true\"" ); //$NON-NLS-1$ 209 buf.append('\n' + " is_highlighted=\"true\"" ); //$NON-NLS-1$ 210 } 211 buf.append(">\n"); //$NON-NLS-1$ 212 if (serializeChildren) { 213 serializeChildTopics(topics, topicPath, "", isSelected); //$NON-NLS-1$ 214 } 215 buf.append("</node>\n"); //$NON-NLS-1$ 216 217 } 218 serializeTopic(ITopic topic, ITopic[] topicPath, boolean isSelected, String parentPath)219 private void serializeTopic(ITopic topic, ITopic[] topicPath, boolean isSelected, String parentPath) { 220 ITopic[] subtopics = topic.getSubtopics(); 221 boolean isLeaf = !ScopeUtils.hasInScopeDescendent(topic, scope); 222 buf.append("<node"); //$NON-NLS-1$ 223 if (topic.getLabel() != null) { 224 buf.append('\n' + " title=\"" + XMLGenerator.xmlEscape(topic.getLabel()) + '"'); //$NON-NLS-1$ 225 } 226 227 buf.append('\n' + " id=\"" + parentPath + "\""); //$NON-NLS-1$ //$NON-NLS-2$ 228 229 String href = topic.getHref(); 230 href = fixupHref(href, "" + tocData.getSelectedToc() + '_' + parentPath); //$NON-NLS-1$ 231 buf.append('\n' + " href=\"" + XMLGenerator.xmlEscape( //$NON-NLS-1$ 232 UrlUtil.getHelpURL(href)) + '"'); 233 if (isLeaf ) { 234 buf.append('\n' + " is_leaf=\"true\"" ); //$NON-NLS-1$ 235 } 236 if (isSelected && requestKind == REQUEST_EXPAND_PATH) { 237 buf.append('\n' + " is_selected=\"true\"" ); //$NON-NLS-1$ 238 buf.append('\n' + " is_highlighted=\"true\"" ); //$NON-NLS-1$ 239 } 240 String imageTags = createTopicImageTags(topic, isLeaf); 241 buf.append(imageTags); 242 243 buf.append(">\n"); //$NON-NLS-1$ 244 serializeChildTopics(subtopics, topicPath, parentPath, isSelected); 245 buf.append("</node>\n"); //$NON-NLS-1$ 246 } 247 createTocImageTag(IToc toc)248 private String createTocImageTag(IToc toc) { 249 if (toc instanceof Toc) { 250 String icon = ((Toc) toc).getIcon(); 251 252 if (IconFinder.isIconDefined(icon)) { 253 String openIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_OPEN); 254 String closedIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_CLOSED); 255 String imageTags = '\n' + " openImage=\"/"+ openIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 256 if (!openIcon.equals(closedIcon)) { 257 imageTags += '\n' + " closedImage=\"/" + closedIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 258 } 259 String altText = IconFinder.getIconAltFromId(icon); 260 if(altText != null) { 261 imageTags += '\n' + " imageAlt=\""+ altText + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 262 } 263 return imageTags; 264 } 265 } 266 return '\n' + " image=\"toc_closed\""; //$NON-NLS-1$ 267 } 268 createTopicImageTags(ITopic topic, boolean isLeaf)269 private String createTopicImageTags(ITopic topic, boolean isLeaf) { 270 if (topic instanceof Topic) { 271 String icon = ((Topic) topic).getIcon(); 272 String altText = IconFinder.getIconAltFromId(icon); 273 274 if (IconFinder.isIconDefined(icon)) { 275 String imageTags; 276 if (isLeaf) { 277 imageTags = '\n' + " openImage=\"/" +IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_LEAF) + "\""; //$NON-NLS-1$//$NON-NLS-2$ 278 } else { 279 String openIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_OPEN); 280 String closedIcon = IconFinder.getImagePathFromId(icon, IconFinder.TYPEICON_CLOSED); 281 imageTags = '\n' + " openImage=\"/" + openIcon+ "\""; //$NON-NLS-1$ //$NON-NLS-2$ 282 if (!openIcon.equals(closedIcon)) { 283 imageTags += '\n' + " closedImage=\"/" + closedIcon + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 284 } 285 } 286 if(altText != null) { 287 imageTags += '\n' + " imageAlt=\""+ altText + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 288 } 289 return imageTags; 290 } 291 } 292 String icon; 293 if (isLeaf) { 294 icon = "topic"; //$NON-NLS-1$ 295 } else if (topic.getHref() == null) { 296 icon = "container_obj"; //$NON-NLS-1$ 297 } else { 298 icon = "container_topic"; //$NON-NLS-1$ 299 } 300 String imageTags = '\n' + " image=\"" + icon + "\""; //$NON-NLS-1$ //$NON-NLS-2$ 301 return imageTags; 302 } 303 serializeChildTopics(ITopic[] childTopics, ITopic[] topicPath, String parentPath, boolean parentIsSelected)304 private void serializeChildTopics(ITopic[] childTopics, ITopic[] topicPath, String parentPath, boolean parentIsSelected) { 305 if (parentIsSelected && requestKind == REQUEST_SHOW_CHILDREN) { 306 // Show the children of this node 307 for (int subtopic = 0; subtopic < childTopics.length; subtopic++) { 308 ITopic childTopic = childTopics[subtopic]; 309 if (ScopeUtils.showInTree(childTopic, scope)) { 310 serializeTopic(childTopic, null, false, addSuffix(parentPath, subtopic)); 311 } 312 } 313 } else if (topicPath != null) { 314 for (int subtopic = 0; subtopic < childTopics.length; subtopic++) { 315 ITopic childTopic = childTopics[subtopic]; 316 if (ScopeUtils.showInTree(childTopic, scope)) { 317 if (topicPath[0].getLabel().equals(childTopic.getLabel())) { 318 ITopic[] newPath = null; 319 if (topicPath.length > 1) { 320 newPath = new ITopic[topicPath.length - 1]; 321 System.arraycopy(topicPath, 1, newPath, 0, topicPath.length - 1); 322 } 323 serializeTopic(childTopic, newPath, topicPath.length == 1, addSuffix(parentPath, subtopic)); 324 } else { 325 serializeTopic(childTopic, null, false, addSuffix(parentPath, subtopic)); 326 } 327 } 328 } 329 } 330 } 331 addSuffix(String parentPath, int subtopic)332 private String addSuffix(String parentPath, int subtopic) { 333 if (parentPath.length() == 0) { 334 return parentPath + subtopic; 335 } 336 return parentPath + '_' + subtopic; 337 } 338 } 339 340 /* 341 * Add an extra parameter which represents the path within the tree. This enables show 342 * in Toc, print selected topic and search selected topic and all subtopics to work 343 * correctly even if the same page appears more than once in the table of contents, Bug 330868 344 * Static for testing purposes 345 */ fixupHref(String href, String path)346 public static String fixupHref(String href, String path) { 347 if (href == null) { 348 return "/../nav/" + path; //$NON-NLS-1$ 349 } 350 int aIndex = href.indexOf('#'); 351 String anchorPart = ""; //$NON-NLS-1$ 352 String hrefPart = href; 353 if (aIndex > 0) { 354 anchorPart = href.substring(aIndex); 355 hrefPart = href.substring(0, aIndex); 356 } 357 358 int questionIndex = href.indexOf('?'); 359 if (questionIndex > 0 ) { 360 return hrefPart + "&" + TocData.COMPLETE_PATH_PARAM + '=' + path + anchorPart; //$NON-NLS-1$ 361 } else { 362 return hrefPart + "?" + TocData.COMPLETE_PATH_PARAM + '=' + path + anchorPart; //$NON-NLS-1$ 363 } 364 365 } 366 367 } 368