1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* $Id: DocumentNavigationHandler.java 1844636 2018-10-23 11:56:21Z ssteiner $ */ 19 20 package org.apache.fop.render.intermediate.extensions; 21 22 import java.awt.Point; 23 import java.awt.Rectangle; 24 import java.util.Map; 25 import java.util.Stack; 26 27 import org.xml.sax.Attributes; 28 import org.xml.sax.SAXException; 29 import org.xml.sax.helpers.DefaultHandler; 30 31 import org.apache.commons.logging.Log; 32 import org.apache.commons.logging.LogFactory; 33 34 import org.apache.fop.accessibility.StructureTreeElement; 35 import org.apache.fop.fo.extensions.InternalElementMapping; 36 import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; 37 import org.apache.fop.render.intermediate.IFException; 38 import org.apache.fop.render.intermediate.PageIndexContext; 39 import org.apache.fop.util.XMLUtil; 40 41 /** 42 * ContentHandler that handles the IF document navigation namespace. 43 */ 44 public class DocumentNavigationHandler extends DefaultHandler 45 implements DocumentNavigationExtensionConstants { 46 47 /** Logger instance */ 48 protected static final Log log = LogFactory.getLog(DocumentNavigationHandler.class); 49 50 private StringBuffer content = new StringBuffer(); 51 private Stack objectStack = new Stack(); 52 53 private IFDocumentNavigationHandler navHandler; 54 55 private StructureTreeElement structureTreeElement; 56 57 private Map<String, StructureTreeElement> structureTreeElements; 58 59 /** 60 * Main constructor. 61 * @param navHandler the navigation handler that will receive the events 62 * @param structureTreeElements the elements representing the structure of the document 63 */ DocumentNavigationHandler(IFDocumentNavigationHandler navHandler, Map<String, StructureTreeElement> structureTreeElements)64 public DocumentNavigationHandler(IFDocumentNavigationHandler navHandler, 65 Map<String, StructureTreeElement> structureTreeElements) { 66 this.navHandler = navHandler; 67 assert structureTreeElements != null; 68 this.structureTreeElements = structureTreeElements; 69 } 70 71 /** {@inheritDoc} */ startElement(String uri, String localName, String qName, Attributes attributes)72 public void startElement(String uri, String localName, String qName, Attributes attributes) 73 throws SAXException { 74 boolean handled = false; 75 if (NAMESPACE.equals(uri)) { 76 if (BOOKMARK_TREE.getLocalName().equals(localName)) { 77 if (!objectStack.isEmpty()) { 78 throw new SAXException(localName + " must be the root element!"); 79 } 80 BookmarkTree bookmarkTree = new BookmarkTree(); 81 objectStack.push(bookmarkTree); 82 } else if (BOOKMARK.getLocalName().equals(localName)) { 83 String title = attributes.getValue("title"); 84 String s = attributes.getValue("starting-state"); 85 boolean show = !"hide".equals(s); 86 Bookmark b = new Bookmark(title, show, null); 87 Object o = objectStack.peek(); 88 if (o instanceof AbstractAction) { 89 AbstractAction action = (AbstractAction)objectStack.pop(); 90 o = objectStack.peek(); 91 ((Bookmark)o).setAction(action); 92 } 93 if (o instanceof BookmarkTree) { 94 ((BookmarkTree)o).addBookmark(b); 95 } else { 96 ((Bookmark)o).addChildBookmark(b); 97 } 98 objectStack.push(b); 99 } else if (NAMED_DESTINATION.getLocalName().equals(localName)) { 100 if (!objectStack.isEmpty()) { 101 throw new SAXException(localName + " must be the root element!"); 102 } 103 String name = attributes.getValue("name"); 104 NamedDestination dest = new NamedDestination(name, null); 105 objectStack.push(dest); 106 } else if (LINK.getLocalName().equals(localName)) { 107 if (!objectStack.isEmpty()) { 108 throw new SAXException(localName + " must be the root element!"); 109 } 110 Rectangle targetRect = XMLUtil.getAttributeAsRectangle(attributes, "rect"); 111 structureTreeElement = structureTreeElements.get(attributes.getValue( 112 InternalElementMapping.URI, InternalElementMapping.STRUCT_REF)); 113 Link link = new Link(null, targetRect); 114 objectStack.push(link); 115 } else if (GOTO_XY.getLocalName().equals(localName)) { 116 String idref = attributes.getValue("idref"); 117 GoToXYAction action; 118 if (idref != null) { 119 action = new GoToXYAction(idref); 120 } else { 121 String id = attributes.getValue("id"); 122 int pageIndex = XMLUtil.getAttributeAsInt(attributes, "page-index"); 123 int pageIndexRelative = XMLUtil.getAttributeAsInt(attributes, "page-index-relative", 0); 124 final Point location; 125 if (pageIndex < 0) { 126 location = null; 127 } else { 128 if (hasNavigation() && !inBookmark() && pageIndexRelative >= 0) { 129 int currentPageIndex = navHandler.getPageIndex(); 130 if (currentPageIndex >= 0) { 131 pageIndex = currentPageIndex; 132 } 133 } 134 final int x = XMLUtil 135 .getAttributeAsInt(attributes, "x"); 136 final int y = XMLUtil 137 .getAttributeAsInt(attributes, "y"); 138 location = new Point(x, y); 139 } 140 action = new GoToXYAction(id, pageIndex, location, 141 new PageIndexRelative(pageIndex, pageIndexRelative)); 142 } 143 if (structureTreeElement != null) { 144 action.setStructureTreeElement(structureTreeElement); 145 } 146 objectStack.push(action); 147 } else if (GOTO_URI.getLocalName().equals(localName)) { 148 String id = attributes.getValue("id"); 149 String gotoURI = attributes.getValue("uri"); 150 String showDestination = attributes.getValue("show-destination"); 151 boolean newWindow = "new".equals(showDestination); 152 URIAction action = new URIAction(gotoURI, newWindow); 153 if (id != null) { 154 action.setID(id); 155 } 156 if (structureTreeElement != null) { 157 action.setStructureTreeElement(structureTreeElement); 158 } 159 objectStack.push(action); 160 } else { 161 throw new SAXException( 162 "Invalid element '" + localName + "' in namespace: " + uri); 163 } 164 handled = true; 165 } 166 if (!handled) { 167 if (NAMESPACE.equals(uri)) { 168 throw new SAXException("Unhandled element '" + localName + "' in namespace: " 169 + uri); 170 } else { 171 log.warn("Unhandled element '" + localName + "' in namespace: " + uri); 172 } 173 } 174 } 175 176 static class PageIndexRelative implements PageIndexContext { 177 private int pageIndex; PageIndexRelative(int pageIndex, int pageIndexRelative)178 PageIndexRelative(int pageIndex, int pageIndexRelative) { 179 this.pageIndex = (pageIndexRelative * -1) + pageIndex; 180 } getPageIndex()181 public int getPageIndex() { 182 return pageIndex; 183 } 184 } 185 inBookmark()186 private boolean inBookmark() { 187 return !objectStack.empty() && objectStack.peek() instanceof Bookmark; 188 } 189 190 /** {@inheritDoc} */ endElement(String uri, String localName, String qName)191 public void endElement(String uri, String localName, String qName) throws SAXException { 192 if (NAMESPACE.equals(uri)) { 193 try { 194 if (BOOKMARK_TREE.getLocalName().equals(localName)) { 195 BookmarkTree tree = (BookmarkTree)objectStack.pop(); 196 if (hasNavigation()) { 197 this.navHandler.renderBookmarkTree(tree); 198 } 199 } else if (BOOKMARK.getLocalName().equals(localName)) { 200 if (objectStack.peek() instanceof AbstractAction) { 201 AbstractAction action = (AbstractAction)objectStack.pop(); 202 Bookmark b = (Bookmark)objectStack.pop(); 203 b.setAction(action); 204 } else { 205 objectStack.pop(); 206 } 207 } else if (NAMED_DESTINATION.getLocalName().equals(localName)) { 208 AbstractAction action = (AbstractAction)objectStack.pop(); 209 NamedDestination dest = (NamedDestination)objectStack.pop(); 210 dest.setAction(action); 211 if (hasNavigation()) { 212 this.navHandler.renderNamedDestination(dest); 213 } 214 } else if (LINK.getLocalName().equals(localName)) { 215 AbstractAction action = (AbstractAction)objectStack.pop(); 216 Link link = (Link)objectStack.pop(); 217 link.setAction(action); 218 if (hasNavigation()) { 219 this.navHandler.renderLink(link); 220 } 221 } else if (localName.startsWith("goto-")) { 222 if (objectStack.size() == 1) { 223 //Stand-alone action 224 AbstractAction action = (AbstractAction)objectStack.pop(); 225 if (hasNavigation()) { 226 this.navHandler.addResolvedAction(action); 227 } 228 } 229 } 230 } catch (IFException ife) { 231 throw new SAXException(ife); 232 } 233 } 234 content.setLength(0); // Reset text buffer (see characters()) 235 } 236 hasNavigation()237 private boolean hasNavigation() { 238 return this.navHandler != null; 239 } 240 241 /** {@inheritDoc} */ characters(char[] ch, int start, int length)242 public void characters(char[] ch, int start, int length) throws SAXException { 243 content.append(ch, start, length); 244 } 245 246 /** {@inheritDoc} */ endDocument()247 public void endDocument() throws SAXException { 248 assert objectStack.isEmpty(); 249 } 250 251 } 252