Bug 404956: [xsl][launch] Launching an XML file as 'XSL Transformation'
doesn't transform anything (fix copyrights)
diff --git a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/Messages.java b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/Messages.java
index 653a397..64d8611 100644
--- a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/Messages.java
+++ b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/Messages.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 Chase Technology Ltd - http://www.chasetechnology.co.uk
+ * Copyright (c) 2007, 2013 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
@@ -9,6 +9,7 @@
  *     Doug Satchwell (Chase Technology Ltd) - initial API and implementation
  *     David Carver (STAR) - updated to meet Galileo requirements
  *     Stuart Harper - added "open files" selector
+ *     Jesper Steen Moller - Bug 404956: Launching an XML file as 'XSL Transformation' doesn't transform anything
  *******************************************************************************/
 package org.eclipse.wst.xsl.internal.debug.ui;
 
@@ -242,6 +243,7 @@
 	public static String XSLLaunchShortcut_1;
 	public static String XSLLaunchShortcut_2;
 	public static String XSLLaunchShortcut_6;
+	public static String XSLLaunchShortcut_7;
 	public static String XSLSelectExisting;
 
 	private Messages() {
diff --git a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XMLProcessingInstructionSniffer.java b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XMLProcessingInstructionSniffer.java
new file mode 100644
index 0000000..3e182a0
--- /dev/null
+++ b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XMLProcessingInstructionSniffer.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Jesper Steen Moller 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:
+ *     Jesper Steen Moller - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.wst.xsl.internal.debug.ui;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * @since 3.5
+ */
+public final class XMLProcessingInstructionSniffer extends DefaultHandler {
+	/**
+	 * An exception indicating that the parsing should stop. This is usually
+	 * triggered when the top-level element has been found.
+	 */
+	private class StopParsingException extends SAXException {
+		/**
+		 * All serializable objects should have a stable serialVersionUID
+		 */
+		private static final long serialVersionUID = 1L;
+
+		/**
+		 * Constructs an instance of <code>StopParsingException</code> with a
+		 * <code>null</code> detail message.
+		 */
+		public StopParsingException() {
+			super((String) null);
+		}
+	}
+
+	private Map<String, List<Map<String,String>>> processingInstructions = new TreeMap<String, List<Map<String,String>>>();
+
+	void addInstruction(String key, Map<String,String> attributes) {
+		List<Map<String, String>> list = processingInstructions.get(key);
+		if (list == null) {
+			list = new LinkedList<Map<String,String>>();
+			processingInstructions.put(key, list);
+		}
+		list.add(attributes);
+	}
+
+	public List<Map<String,String>> getProcessingInstructions(String key) {
+		return processingInstructions.get(key);
+	}
+
+	Pattern PSEUDO_ATTRIBUTES_REGEX = Pattern.compile("\\s*(\\w+)\\s*=\\s*['\"]([^'\"]+)['\"]\\s*"); //$NON-NLS-1$
+	
+	private Map<String,String> parsePseudoAttributes(String pseudoAttributes) {
+		Map<String, String> attrs = new TreeMap<String, String>();
+		Matcher matcher = PSEUDO_ATTRIBUTES_REGEX.matcher(pseudoAttributes);
+		while (matcher.find()) {
+			String value = matcher.group(2);
+			// TODO: Unescape character references and predefined entity references: "&amp;" | "&lt;" | "&gt;" | "&quot;" | "&apos;"
+			attrs.put(matcher.group(1), value);
+		}
+		return attrs;
+	}
+	
+	public XMLProcessingInstructionSniffer() {
+	}
+
+	/**
+	 * Creates a new SAX parser for use within this instance.
+	 * 
+	 * @return The newly created parser.
+	 * 
+	 * @throws ParserConfigurationException
+	 *             If a parser of the given configuration cannot be created.
+	 * @throws SAXException
+	 *             If something in general goes wrong when creating the parser.
+	 * @throws SAXNotRecognizedException
+	 *             If the <code>XMLReader</code> does not recognize the
+	 *             lexical handler configuration option.
+	 * @throws SAXNotSupportedException
+	 *             If the <code>XMLReader</code> does not support the lexical
+	 *             handler configuration option.
+	 */
+	private final SAXParser createParser(SAXParserFactory parserFactory) throws ParserConfigurationException, SAXException, SAXNotRecognizedException, SAXNotSupportedException {
+		// Initialize the parser.
+		final SAXParser parser = parserFactory.newSAXParser();
+		final XMLReader reader = parser.getXMLReader();
+//		reader.setProperty("http://xml.org/sax/properties/lexical-handler", this); //$NON-NLS-1$
+		// disable DTD validation (bug 63625)
+		try {
+			//	be sure validation is "off" or the feature to ignore DTD's will not apply
+			reader.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$
+			reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$
+		} catch (SAXNotRecognizedException e) {
+			// not a big deal if the parser does not recognize the features
+		} catch (SAXNotSupportedException e) {
+			// not a big deal if the parser does not support the features
+		}
+		return parser;
+	}
+
+	public boolean parseContents(InputSource contents) throws IOException, ParserConfigurationException, SAXException {
+		// Parse the file into we have what we need (or an error occurs).
+	
+		try {
+			final SAXParser parser = createParser(SAXParserFactory.newInstance());
+			// to support external entities specified as relative URIs (see bug 63298)
+			contents.setSystemId("/"); //$NON-NLS-1$
+			parser.parse(contents, this);
+		} catch (StopParsingException e) {
+			// Abort the parsing normally. Fall through...
+		}
+		return true;
+	}
+
+	/*
+	 * Resolve external entity definitions to an empty string.  This is to speed
+	 * up processing of files with external DTDs.  Not resolving the contents 
+	 * of the DTD is ok, as only the System ID of the DTD declaration is used.
+	 * @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String, java.lang.String)
+	 */
+	public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
+		return new InputSource(new StringReader("")); //$NON-NLS-1$
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
+	 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
+	 */
+	public final void startElement(final String uri, final String elementName, final String qualifiedName, final Attributes attributes) throws SAXException {
+		throw new StopParsingException();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
+	 */
+	public final void startEntity(final String name) {
+		// Not interested.
+	}
+	
+	@Override
+	public void processingInstruction(String target, String data)
+			throws SAXException {
+		addInstruction(target, parsePseudoAttributes(data));
+	}
+
+//	public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException {
+//		String xml = "<?xml-stylesheet href='where.xsl'?><root><element/></root>";
+//		XMLProcessingInstructionSniffer sniffer = new XMLProcessingInstructionSniffer();
+//		sniffer.parseContents(new InputSource(new StringReader(xml)));
+//		List<Map<String, String>> instructions = sniffer.getProcessingInstructions("xml-stylesheet");
+//		System.out.println(instructions);
+//	}
+}
diff --git a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XSLLaunchShortcut.java b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XSLLaunchShortcut.java
index 3c0dd50..0ddb436 100644
--- a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XSLLaunchShortcut.java
+++ b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/XSLLaunchShortcut.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 Chase Technology Ltd - http://www.chasetechnology.co.uk
+ * Copyright (c) 2007, 2013 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
@@ -7,12 +7,20 @@
  *
  * Contributors:
  *     Doug Satchwell (Chase Technology Ltd) - initial API and implementation
+ *     Jesper Steen Moller - Bug 404956: Launching an XML file as 'XSL Transformation' doesn't transform anything
  *******************************************************************************/
 package org.eclipse.wst.xsl.internal.debug.ui;
 
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IResource;
@@ -46,10 +54,13 @@
 import org.eclipse.ui.dialogs.ElementListSelectionDialog;
 import org.eclipse.wst.xsl.core.XSLCore;
 import org.eclipse.wst.xsl.internal.debug.ui.tabs.main.InputFileBlock;
+import org.eclipse.wst.xsl.internal.debug.ui.tabs.main.TransformsBlock;
 import org.eclipse.wst.xsl.launching.XSLLaunchConfigurationConstants;
 import org.eclipse.wst.xsl.launching.config.BaseLaunchHelper;
 import org.eclipse.wst.xsl.launching.config.LaunchPipeline;
 import org.eclipse.wst.xsl.launching.config.LaunchTransform;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 
 /**
  * <table border=1>
@@ -92,9 +103,12 @@
  * @since 1.0
  */
 public class XSLLaunchShortcut implements ILaunchShortcut {
+	private static final String XML_STYLESHEET_PI = "xml-stylesheet"; //$NON-NLS-1$
 	private IFile xmlFile;
 	private IPath xmlFilePath;
 	private IFile[] xslFiles;
+	private String xslFilePath; // always an external path
+	private LaunchPipeline pipeline; 
 
 	public void launch(ISelection selection, String mode) {
 		if (selection instanceof IStructuredSelection) {
@@ -115,8 +129,12 @@
 	private void searchAndLaunch(Object[] objects, String mode) {
 		if (fillFiles(objects)) {
 			// ensure we have an input file
-			if (xmlFile == null)
+			if (xmlFile == null) {
 				promptForInput();
+			}
+			if (xslFiles == null || xslFiles.length == 0 && xslFilePath == null) {
+				promptForStylesheet();
+			}
 			if (xmlFile != null || xmlFilePath != null)
 				launch(mode);
 		}
@@ -149,6 +167,11 @@
 
 			@Override
 			protected void okPressed() {
+				saveSelectedXmlFile();
+				super.okPressed();
+			}
+
+			private void saveSelectedXmlFile() {
 				IResource res = inputFileBlock.getResource();
 				if (res == null)
 					xmlFilePath = new Path(inputFileBlock.getText());
@@ -156,9 +179,54 @@
 						res.getFullPath())
 						&& res.getType() == IResource.FILE)
 					xmlFile = (IFile) res;
+			}
+		};
+		dialog.setHelpAvailable(false);
+		dialog.setStatusLineAboveButtons(true);
+		dialog.setTitle(Messages.XSLLaunchShortcut_1);
+		dialog.open();
+	}
+
+	private void promptForStylesheet() {
+		// prompt for input xml file
+		final LaunchPipeline promptedPipeline = new LaunchPipeline();
+		
+		StatusDialog dialog = new StatusDialog(getShell()) {
+			private TransformsBlock transformsBlock = new TransformsBlock();
+
+			@Override
+			protected Control createDialogArea(Composite parent) {
+				Composite comp = (Composite) super.createDialogArea(parent);
+				comp.setFont(parent.getFont());
+				GridLayout layout = new GridLayout(1, false);
+				comp.setLayout(layout);
+
+				Label label = new Label(comp, SWT.NONE);
+				label.setFont(comp.getFont());
+				GridData gd = new GridData();
+				gd.horizontalIndent = 5;
+				gd.verticalIndent = 5;
+				gd.widthHint = 380;
+				label.setLayoutData(gd);
+				label.setText(Messages.XSLLaunchShortcut_7);
+
+				promptedPipeline.setTransformDefs(new ArrayList<LaunchTransform>());
+				transformsBlock.setPipeline(promptedPipeline);
+				transformsBlock.createControl(comp);
+				transformsBlock.initializeFrom(null);
+				return comp;
+			}
+
+			@Override
+			protected void okPressed() {
+				savePipeline();
 				super.okPressed();
 			}
 
+			private void savePipeline() {
+				pipeline = promptedPipeline;
+			}
+
 		};
 		dialog.setHelpAvailable(false);
 		dialog.setStatusLineAboveButtons(true);
@@ -170,6 +238,7 @@
 		xmlFile = null;
 		xmlFilePath = null;
 		List<IFile> xslFileList = new ArrayList<IFile>();
+		xslFilePath = null;
 		for (Object object : selections) {
 			IResource resource = (IResource) object;
 			if (resource.getType() == IResource.FILE) {
@@ -185,6 +254,42 @@
 				}
 			}
 		}
+		if (xslFileList.isEmpty() && xmlFile != null) {
+			// Could it be we have a directive in the file, near the top
+			// <?xml-stylesheet type="text/xsl" href="test1.xsl"?>
+			XMLProcessingInstructionSniffer sniffer = new XMLProcessingInstructionSniffer();
+			try {
+				sniffer.parseContents(new InputSource(xmlFile.getContents()));
+				List<Map<String, String>> instructions = sniffer.getProcessingInstructions(XML_STYLESHEET_PI);
+				if (instructions != null) {
+					for (Map<String, String> attrs : instructions) {
+						String alternative = attrs.get("alternative"); //$NON-NLS-1$
+						if (alternative != null && alternative.equalsIgnoreCase("yes")) continue; //$NON-NLS-1$
+						
+						String href = attrs.get("href"); //$NON-NLS-1$
+						if (href == null) continue;
+						
+						// This is the one, now compute the path
+						if (new URI(href).isAbsolute()) {
+							xslFilePath = href;
+						} else {
+							xslFileList.add(xmlFile.getProject().getFile(xmlFile.getParent().getProjectRelativePath().append(href)));
+						}
+					}
+				}
+			} catch (IOException e) {
+				// ignored
+			} catch (ParserConfigurationException e) {
+				// ignored
+			} catch (SAXException e) {
+				// ignored
+			} catch (CoreException e) {
+				// ignored
+			} catch (URISyntaxException e) {
+				// ignored
+			}
+		}
+		
 		xslFiles = xslFileList.toArray(new IFile[0]);
 		return true;
 	}
@@ -308,12 +413,16 @@
 			wc.setAttribute(XSLLaunchConfigurationConstants.ATTR_OPEN_FILE,
 					true);
 
-			LaunchPipeline pipeline = new LaunchPipeline();
+			if (pipeline == null) pipeline = new LaunchPipeline();
 			for (IFile element : xslFiles) {
 				pipeline.addTransformDef(new LaunchTransform(element
 						.getFullPath().toPortableString(),
 						LaunchTransform.RESOURCE_TYPE));
 			}
+			if (xslFilePath != null) {
+				pipeline.addTransformDef(new LaunchTransform(xslFilePath,
+						LaunchTransform.EXTERNAL_TYPE));
+			}
 			wc.setAttribute(XSLLaunchConfigurationConstants.ATTR_PIPELINE,
 					pipeline.toXML());
 			if (xmlFile != null)
diff --git a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/messages.properties b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/messages.properties
index fc15a2f..1a03282 100644
--- a/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/messages.properties
+++ b/bundles/org.eclipse.wst.xsl.debug.ui/src/org/eclipse/wst/xsl/internal/debug/ui/messages.properties
@@ -3,6 +3,7 @@
 XSLLaunchShortcut_1=Input File
 XSLLaunchShortcut_2=Select a Launch Configuration
 XSLLaunchShortcut_6=Error
+XSLLaunchShortcut_7=Select an XSLT file for the transformation
 XSLSelectExisting=&Select existing configuration:
 #Action Messages
 RemoveAction_Text=Remove
diff --git a/bundles/org.eclipse.wst.xsl.jaxp.debug/src/org/eclipse/wst/xsl/jaxp/debug/invoker/internal/JAXPSAXProcessorInvoker.java b/bundles/org.eclipse.wst.xsl.jaxp.debug/src/org/eclipse/wst/xsl/jaxp/debug/invoker/internal/JAXPSAXProcessorInvoker.java
index 28ec4cd..6fcb337 100644
--- a/bundles/org.eclipse.wst.xsl.jaxp.debug/src/org/eclipse/wst/xsl/jaxp/debug/invoker/internal/JAXPSAXProcessorInvoker.java
+++ b/bundles/org.eclipse.wst.xsl.jaxp.debug/src/org/eclipse/wst/xsl/jaxp/debug/invoker/internal/JAXPSAXProcessorInvoker.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 Chase Technology Ltd - http://www.chasetechnology.co.uk
+ * Copyright (c) 2007, 2013 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
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     Doug Satchwell (Chase Technology Ltd) - initial API and implementation
+ *     Jesper Steen Moller - Bug 404956: Launching an XML file as 'XSL Transformation' doesn't transform anything
  *******************************************************************************/
 package org.eclipse.wst.xsl.jaxp.debug.invoker.internal;
 
@@ -202,17 +203,14 @@
 				SAXSource saxSource = new SAXSource(inputsource);
 				Source src = saxSource;
 				String media = null, title = null, charset = null;
-				while (true)
+				src = tFactory.getAssociatedStylesheet(src, media, title, charset);
+				if (src != null)
 				{
-					src = tFactory.getAssociatedStylesheet(src, media, title, charset);
-					if (src != null)
-					{
-						addStylesheet(saxSource, null, Collections.EMPTY_MAP, new Properties());
-					}
-					else
-					{
-						throw new TransformationException(Messages.getString("JAXPSAXProcessorInvoker.7") + inputsource.getSystemId()); //$NON-NLS-1$
-					}
+					addStylesheet(src, null, Collections.EMPTY_MAP, new Properties());
+				}
+				else
+				{
+					throw new TransformationException(Messages.getString("JAXPSAXProcessorInvoker.7") + inputsource.getSystemId()); //$NON-NLS-1$
 				}
 			}
 			th.setResult(res);
diff --git a/bundles/org.eclipse.wst.xsl.jaxp.launching/src/org/eclipse/wst/xsl/jaxp/launching/internal/PluginProcessorJar.java b/bundles/org.eclipse.wst.xsl.jaxp.launching/src/org/eclipse/wst/xsl/jaxp/launching/internal/PluginProcessorJar.java
index f6ce3ff..8377dcd 100644
--- a/bundles/org.eclipse.wst.xsl.jaxp.launching/src/org/eclipse/wst/xsl/jaxp/launching/internal/PluginProcessorJar.java
+++ b/bundles/org.eclipse.wst.xsl.jaxp.launching/src/org/eclipse/wst/xsl/jaxp/launching/internal/PluginProcessorJar.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 Chase Technology Ltd - http://www.chasetechnology.co.uk
+ * Copyright (c) 2007, 2013 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
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *     Doug Satchwell (Chase Technology Ltd) - initial API and implementation
+ *     Jesper Steen Moller - Bug 404956: Launching an XML file as 'XSL Transformation' doesn't transform anything
  *******************************************************************************/
 package org.eclipse.wst.xsl.jaxp.launching.internal;
 
@@ -40,7 +41,9 @@
 			// There is surely a better way, but I can'd find it.
 			if (path == null)
 			{
-				url = Platform.getBundle(pluginId).getEntry("/"); //$NON-NLS-1$
+				Bundle bundle = Platform.getBundle(pluginId);
+				if (bundle == null) return null;
+				url = bundle.getEntry("/"); //$NON-NLS-1$
 				url = FileLocator.resolve(url);
 				String s = url.getPath();
 				if (s.endsWith("!/")) //$NON-NLS-1$