| /******************************************************************************* |
| * Copyright (c) 2007 Chase Technology Ltd - http://www.chasetechnology.co.uk |
| * 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: |
| * Doug Satchwell (Chase Technology Ltd) - initial API and implementation |
| * David Carver (STAR) - bug 287499 - add XML Catalog Resolution for a file. |
| * Jesper Steen Moller - bug 304162 - support absolute files properly |
| *******************************************************************************/ |
| package org.eclipse.wst.xsl.core; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin; |
| import org.eclipse.wst.sse.core.StructuredModelManager; |
| import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; |
| import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; |
| import org.eclipse.wst.xsl.core.internal.model.StylesheetBuilder; |
| import org.eclipse.wst.xsl.core.internal.util.FileUtil; |
| import org.eclipse.wst.xsl.core.model.Stylesheet; |
| import org.eclipse.wst.xsl.core.model.StylesheetModel; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| /** |
| * The interface to all aspects of the XSL core functionality. |
| * <p> |
| * This is responsible for building and maintaining the cache of built XSL |
| * models. |
| * </p> |
| * |
| * @author Doug Satchwell |
| */ |
| public class XSLCore { |
| /** |
| * The XSL namespace URI (= http://www.w3.org/1999/XSL/Transform) |
| */ |
| public static final String XSL_NAMESPACE_URI = "http://www.w3.org/1999/XSL/Transform"; //$NON-NLS-1$ |
| |
| /** |
| * The XSL content type (= org.eclipse.wst.xml.core.xslsource) |
| */ |
| public static final String XSL_CONTENT_TYPE = "org.eclipse.wst.xml.core.xslsource"; //$NON-NLS-1$ |
| |
| private static XSLCore instance; |
| private Map<IFile, StylesheetModel> stylesheetsComposed = new HashMap<IFile, StylesheetModel>(); |
| |
| private XSLCore() { |
| } |
| |
| /** |
| * Get the cached stylesheet, or build it if it has not yet been built. |
| * |
| * @param file |
| * @return source file, or null if could not be built |
| * @since 1.0 |
| */ |
| public synchronized StylesheetModel getStylesheet(IFile file) { |
| StylesheetModel stylesheet = stylesheetsComposed.get(file); |
| if (stylesheet == null) |
| stylesheet = buildStylesheet(file); |
| return stylesheet; |
| } |
| |
| /** |
| * Completely rebuild the source file from its DOM |
| * |
| * @param file |
| * @return the stylesheet model, or null if it could not be created. |
| * @since 1.0 |
| */ |
| public synchronized StylesheetModel buildStylesheet(IFile file) { |
| Stylesheet stylesheet = StylesheetBuilder.getInstance().getStylesheet( |
| file, true); |
| if (stylesheet == null) |
| return null; |
| StylesheetModel stylesheetComposed = new StylesheetModel(stylesheet); |
| stylesheetsComposed.put(file, stylesheetComposed); |
| stylesheetComposed.fix(); |
| return stylesheetComposed; |
| } |
| |
| /** |
| * Clean all of the stylesheets from the given project. |
| * |
| * @param project |
| * the project to be cleaned |
| * @param monitor |
| * a progress monitor to track the clean progress |
| */ |
| public synchronized void clean(IProject project, IProgressMonitor monitor) { |
| for (Iterator<StylesheetModel> iter = stylesheetsComposed.values() |
| .iterator(); iter.hasNext();) { |
| StylesheetModel model = iter.next(); |
| if (project == null |
| || project.equals(model.getStylesheet().getFile() |
| .getProject())) { |
| iter.remove(); |
| } |
| } |
| } |
| |
| /** |
| * Get the singleton <code>XSLCore</code> instance. |
| * |
| * @return the <code>XSLCore</code> instance |
| */ |
| public static synchronized XSLCore getInstance() { |
| if (instance == null) |
| instance = new XSLCore(); |
| return instance; |
| } |
| |
| /** |
| * Locates a file for the given current file and URI. |
| * |
| * @param currentFile |
| * the file to resolve relative to |
| * @param uri |
| * the relative URI |
| * @return the file at the URI relative to this <code>currentFile</code> |
| */ |
| public static IFile resolveFile(IFile currentFile, String uri) { |
| if (uri == null || uri.trim().length() == 0) |
| return null; |
| IResource resource = currentFile.getParent().findMember(new Path(uri)); |
| if (resource == null) { |
| String baseURI = currentFile.getRawLocationURI().toString(); |
| String resolvedURI = URIResolverPlugin.createResolver().resolve(baseURI, "", uri); //$NON-NLS-1$ |
| |
| URI parsedUri = null; |
| boolean isAbsolute = false; |
| try { |
| parsedUri = new URI(resolvedURI); |
| if (parsedUri.isAbsolute()) isAbsolute = true; |
| } catch (URISyntaxException e) { |
| } |
| |
| if (uri != null && isAbsolute) { |
| if (resolvedURI.startsWith("platform:/resource/")) { //$NON-NLS-1$ |
| String platformPart = resolvedURI.substring(19); |
| resource = currentFile.getWorkspace().getRoot().findMember(platformPart); |
| } else { |
| resource = findBestFileForURI(parsedUri); |
| } |
| } else { |
| resource = currentFile.getParent().findMember(new Path(uri)); |
| } |
| } |
| if (resource == null || resource.getType() != IResource.FILE) |
| return null; |
| return (IFile) resource; |
| } |
| |
| /** |
| * Find an IFile as close to the root as possible matching this file URI. |
| * |
| * @param parsedUri URI from the catalog / resolver lookup |
| * @return a file resource which exists in the workspace, or null if none were found. |
| */ |
| private static IFile findBestFileForURI(URI parsedUri) { |
| IFile fileNearestRoot = null; |
| IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(parsedUri); |
| for (int i = 0; i < files.length; i++) { |
| if (files[i].exists()) { |
| if (fileNearestRoot == null |
| || |
| files[i].getFullPath().segmentCount() < fileNearestRoot.getFullPath().segmentCount()) { |
| fileNearestRoot = files[i]; |
| } |
| } |
| } |
| return fileNearestRoot; |
| } |
| |
| /** |
| * Determine whether the given file is an XML file by inspecting its content |
| * types. |
| * |
| * @param file |
| * the file to inspect |
| * @return true if this file is an XML file |
| */ |
| |
| public static boolean isXMLFile(IFile file) { |
| return FileUtil.isXMLFile(file); |
| } |
| |
| /** |
| * Determine whether the given file is an XSL file by inspecting its content |
| * types. |
| * |
| * @param file |
| * the file to inspect |
| * @return true if this file is an XSL file |
| */ |
| public static boolean isXSLFile(IFile file) { |
| return FileUtil.isXSLFile(file); |
| } |
| |
| /** |
| * Takes a given <code>Node</code> and returns whether it is part of the the |
| * XSLT Namespace. |
| * |
| * @param node |
| * The Node to be checked. |
| * @return True if part of the XSLT namespace, false otherwise. |
| * @since 1.0 |
| */ |
| public static boolean isXSLNamespace(Node node) { |
| if (hasNamespace(node)) { |
| return false; |
| } |
| return node.getNamespaceURI().equals(XSL_NAMESPACE_URI); |
| } |
| |
| /** |
| * Determine if the Node that was passed has a Namespace. If it doesn't the |
| * node is either going to be false, or the call to the getNamespace() |
| * method will return null. |
| * |
| * @param node |
| * @return |
| */ |
| private static boolean hasNamespace(Node node) { |
| return node == null || node.getNamespaceURI() == null; |
| } |
| |
| /** |
| * Returns an Attr node for the current Node if one exits at the specified |
| * offset. |
| * |
| * @param node |
| * @param offset |
| * @return A w3c.dom.Attr |
| * @since 1.0 |
| */ |
| public static Attr getCurrentAttrNode(Node node, int offset) { |
| if ((node instanceof IndexedRegion) |
| && ((IndexedRegion) node).contains(offset) |
| && (node.hasAttributes())) { |
| NamedNodeMap attrs = node.getAttributes(); |
| for (int i = 0; i < attrs.getLength(); ++i) { |
| IndexedRegion attRegion = (IndexedRegion) attrs.item(i); |
| if (attRegion.contains(offset)) { |
| return (Attr) attrs.item(i); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the current Node at the specified offset. |
| * |
| * @param document |
| * @param offset |
| * @return an w3c.dom.Node |
| * @since 1.0 |
| */ |
| public static Node getCurrentNode(IDocument document, int offset) { |
| IndexedRegion inode = null; |
| IStructuredModel sModel = null; |
| try { |
| sModel = StructuredModelManager.getModelManager() |
| .getExistingModelForRead(document); |
| inode = sModel.getIndexedRegion(offset); |
| if (inode == null) |
| inode = sModel.getIndexedRegion(offset - 1); |
| } finally { |
| if (sModel != null) |
| sModel.releaseFromRead(); |
| } |
| |
| if (inode instanceof Node) { |
| return (Node) inode; |
| } |
| return null; |
| } |
| |
| } |