Index: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDgenerateSeleniumReplay.java
===================================================================
--- trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDgenerateSeleniumReplay.java	(revision 2023)
+++ trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDgenerateSeleniumReplay.java	(revision 2023)
@@ -0,0 +1,371 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+/*  Selenium IDE test case template:
+ * --------------------------------------------------
+ *  <?xml version="1.0" encoding="${encoding}"?>
+ *  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
+ *	<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ *	<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ *	<meta http-equiv="Content-Type" content="text/html; charset=${encoding}" />
+ *  <link rel="selenium.base" href="${baseURL}" />
+ *	<title>${name}</title>
+ *	</head>
+ *	<body>
+ *	<table cellpadding="1" cellspacing="1" border="1">
+ *	<thead>
+ *	<tr><td rowspan="1" colspan="3">${name}</td></tr> 
+ *	</thead><tbody>
+ *	${commands}
+ *	</tbody></table>
+ *	</body>
+ *	</html>
+ * 
+ * ------------------------------------
+ * baseURL: home page
+ * name: test case name
+ * commands: Template:
+ *	<tr>\n" +
+ *	\t<td>${command.command}</td>
+ *	\t<td>${command.target}</td>
+ *	\t<td>${command.value}</td>
+ *	</tr>
+ * 
+ */
+package de.ugoe.cs.autoquest.plugin.html.commands;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import de.ugoe.cs.autoquest.CommandHelpers;
+import de.ugoe.cs.autoquest.SequenceInstanceOf;
+import de.ugoe.cs.autoquest.eventcore.Event;
+import de.ugoe.cs.autoquest.eventcore.IEventTarget;
+import de.ugoe.cs.autoquest.eventcore.gui.KeyPressed;
+import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction.Button;
+import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
+import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
+import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
+import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+
+/**
+ * create XHTML files for Selenium from sequences
+ * 
+ * @author Xiaowei Wang
+ * @version 1.0
+ */
+public class CMDgenerateSeleniumReplay implements Command {
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see de.ugoe.cs.util.console.Command#run(java.util.List)
+	 */
+	@SuppressWarnings("unchecked")
+	@Override
+	public void run(List<Object> parameters) {
+		String sequencesName = "sequences";
+		String folder = null;
+		String baseURL = null; // default
+		try {
+			if (parameters.size() == 2) {
+				folder = (String) parameters.get(0);
+				baseURL = (String) parameters.get(1);
+			} else if (parameters.size() == 3) {
+				sequencesName = (String) parameters.get(0);
+				folder = (String) parameters.get(1);
+				baseURL = (String) parameters.get(2);
+			} else {
+				throw new IllegalArgumentException(
+						"The command needs two or three parameters.");
+			}
+
+			if (!(new File(folder).isDirectory())) {
+				throw new IllegalArgumentException("HTML files folder path: "
+						+ folder + " is invalid.");
+			}
+			// validate the basic URL
+			// HttpURLConnection.setFollowRedirects(false);
+			HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(
+					baseURL).openConnection();
+			httpURLConnection.setRequestMethod("HEAD");
+			if (!(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK)) {
+				throw new IllegalArgumentException("Website basic URL: "
+						+ baseURL + " is invalid.");
+			}
+		} catch (SecurityException se) {
+			throw new IllegalArgumentException(
+					"Parameters are invalid");
+		} catch (MalformedURLException mue) {
+			throw new IllegalArgumentException("Website basic URL: "
+					+ baseURL + " is invalid.");
+		} catch (IOException e) {
+			throw new IllegalArgumentException(e);
+		}
+
+		try {
+			List<List<Event>> sequences = null;
+			Object dataObject = GlobalDataContainer.getInstance().getData(
+					sequencesName);
+			if (dataObject == null) {
+				CommandHelpers.objectNotFoundMessage(sequencesName);
+				return;
+			}
+			if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
+				CommandHelpers.objectNotType(sequencesName,
+						"Collection<List<Event<?>>>");
+				return;
+			}
+			sequences = (List<List<Event>>) dataObject;
+
+			String command = null;
+			String target = null;
+			String value = null;
+			for (int seqsIndex = 0; seqsIndex < sequences.size(); seqsIndex++) {
+				LinkedList<Map<String, String>> attributeList = new LinkedList<Map<String, String>>();
+				List<Event> sequence = sequences.get(seqsIndex);
+				Event firstEvent = sequence.get(0);
+				HTMLGUIElement htmlGUIElement = (HTMLGUIElement) firstEvent.getTarget();
+				while (!(htmlGUIElement instanceof HTMLDocument)) { // the first target must be a page
+					htmlGUIElement = (HTMLGUIElement) (htmlGUIElement.getParent());
+				}
+				command = "open";
+				target = ((HTMLDocument) htmlGUIElement).getPath();
+				value = "";
+				addToAttrList(attributeList, command, target, value);
+
+				for (int seqIndex = 1; seqIndex < sequence.size(); seqIndex++) {
+					Event event = sequence.get(seqIndex);
+					target = getXpath(event.getTarget());
+					// Login button is global, must await the last page to load
+					// otherwise, will lead to wrong page after logging in
+					if (target.equals("//ul[@id='navlist2']/li[2]/a")
+							&& (attributeList.size() > 1)
+							&& !attributeList.getLast().get("command")
+									.endsWith("AndWait")) {
+						attributeList.getLast().put(
+								"command",
+								attributeList.getLast().get("command")
+										+ "AndWait");
+					}
+
+					if (event.getType() instanceof MouseClick
+							&& (((MouseClick) event.getType()).getButton() == Button.LEFT || ((MouseClick) event
+									.getType()).getButton() == Button.MIDDLE)) {
+						command = "click";
+						String precedingCommand = "waitForElementPresent";
+						value = "";
+						addToAttrList(attributeList, precedingCommand, target,
+								value);
+					} else if (event.getType() instanceof TextInput) {
+						command = "type";
+						value = ((TextInput) event.getType()).getEnteredText();
+						// if the session contains login process,
+						// the session must log out at beginning
+						if (target
+								.equals("//form[@id='user-login']/div/div/input[@id='edit-name']")
+								&& !attributeList.get(0).get("target")
+										.equals("/user/logout")) {
+							addToAttrList(attributeList, "open",
+									"/user/logout", "", 0);
+						}
+					} else if (event.getType() instanceof KeyPressed) {
+						command = "sendKeys";
+						VirtualKey pressedKey = ((KeyPressed) event.getType())
+								.getKey();
+						switch (pressedKey) {
+						case ENTER:
+							value = "${KEY_ENTER}";
+							break;
+						case TAB:
+							value = "${KEY_TAB}";
+						}
+					} else {// Scroll or KeyboardFocusChange or else
+						continue;
+					}
+					addToAttrList(attributeList, command, target, value);
+				}
+
+				if (attributeList.size() > 0) {
+					createHTML(folder, "session-" + seqsIndex + ".html",
+							baseURL, attributeList, "UTF-8");
+				} else { // the sequence has no action
+					continue;
+				}
+			}
+		} catch (Exception e) {
+			Console.printerrln("Error on running the command.");
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * create event files in HTML with Selenium IDE template
+	 * 
+	 * @param HTMLFolder
+	 * @param fileName
+	 * @param attributeList
+	 */
+	private void createHTML(String HTMLFolder, String fileName, String baseURL,
+			List<Map<String, String>> attributeList, String encoding) {
+
+		String htmlStr = "<?xml version=\"1.0\" encoding=\"${encoding}\"?>\n"
+				+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " 
+				+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
+				+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
+				+ "<head profile=\"http://selenium-ide.openqa.org/profiles/test-case\">\n"
+				+ "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=${encoding}\" />\n"
+				+ "<link rel=\"selenium.base\" href=\"${baseURL}\" />\n"
+				+ "<title>${name}</title>\n" 
+				+ "</head>\n" 
+				+ "<body>\n"
+				+ "<table cellpadding=\"1\" cellspacing=\"1\" border=\"1\">\n"
+				+ "<thead>\n"
+				+ "<tr><td rowspan=\"1\" colspan=\"3\">${name}</td></tr>\n"
+				+ "</thead><tbody>\n";
+		htmlStr = htmlStr.replace("${encoding}", encoding);
+		htmlStr = htmlStr.replace("${baseURL}", baseURL);
+		htmlStr = htmlStr.replace("${name}", fileName.replace(".html", ""));
+		for (Map<String, String> commandMap : attributeList) {
+			htmlStr = htmlStr + "<tr>\n" 
+							  + "\t<td>" + commandMap.get("command") + "</td>\n" 
+							  + "\t<td>" + commandMap.get("target") + "</td>\n" 
+							  + "\t<td>" + commandMap.get("value") + "</td>\n" 
+							  + "</tr>\n";
+		}
+
+		String fileEnding = "</tbody></table>\n</body>\n</html>";
+		htmlStr = htmlStr + fileEnding;
+		try {
+			File newHtmlFile = new File(HTMLFolder
+					+ System.getProperty("file.separator") + fileName);
+			BufferedWriter bw = new BufferedWriter(new FileWriter(newHtmlFile));
+			bw.write(htmlStr);
+			bw.flush();
+			bw.close();
+		} catch (Exception e) {
+			Console.printerrln("Error on creating xhtml files.");
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * add an event to the event list
+	 * 
+	 * @param attributeList
+	 * @param command
+	 * @param target
+	 * @param value
+	 * 
+	 */
+	private void addToAttrList(LinkedList<Map<String, String>> attributeList,
+			String command, String target, String value) {
+		Map<String, String> ctvMap = new HashMap<String, String>();
+		ctvMap.put("command", command);
+		ctvMap.put("target", target);
+		ctvMap.put("value", value);
+		attributeList.add(ctvMap);
+	}
+
+	private void addToAttrList(LinkedList<Map<String, String>> attributeList,
+			String command, String target, String value, int position) {
+		Map<String, String> ctvMap = new HashMap<String, String>();
+		ctvMap.put("command", command);
+		ctvMap.put("target", target);
+		ctvMap.put("value", value);
+		attributeList.add(position, ctvMap);
+	}
+
+	/**
+	 * 
+	 * @param iEventTarget
+	 * @return the relative xpath
+	 */
+	private String getXpath(IEventTarget iEventTarget) {
+		HTMLGUIElement htmlGUIElement = (HTMLGUIElement) iEventTarget;
+		List<String> xpathElements = new LinkedList<String>();
+		String htmlId = null;
+		int htmlIndex = 0;
+
+		// obtain the relative xpath of the element
+		while (!(htmlGUIElement instanceof HTMLDocument)) {
+			HTMLPageElement htmlPageElement = (HTMLPageElement) htmlGUIElement;
+			StringBuffer xpathElement = new StringBuffer();
+			// the real tag is "input" without _*
+			xpathElement.append(htmlPageElement.getTagName()
+					.replace("input_text", "input")
+					.replace("input_password", "input")
+					.replace("input_submit", "input"));
+			// better not use absolute xpath,
+			// Selenium IDE specifies only the
+			// 1st tag's id, if it's not the only tag
+			if ( (htmlId = htmlPageElement.getHtmlId()) != null) {
+				xpathElement.append("[@id='");
+				xpathElement.append(htmlId); 
+				xpathElement.append("']");
+				xpathElements.add(0, xpathElement.toString());
+				// the xpath must have more then one tag with id
+				// but the Selenium IDE uses only the 1st one
+				// which is enough for finding out the element
+				if (xpathElements.size() > 1) {
+					break;
+				}
+			} else if ((htmlIndex = htmlPageElement.getIndex()) != 0) {
+				xpathElement.append("[");
+				// find element by xpath, index starts from 1, not 0
+				xpathElement.append(htmlIndex + 1);
+				xpathElement.append("]");
+				xpathElements.add(0, xpathElement.toString());
+			} else {
+				// if the index is 0, add only tag
+				xpathElements.add(0, xpathElement.toString());
+			}
+			htmlGUIElement = (HTMLGUIElement) (htmlGUIElement.getParent());
+		}
+
+		StringBuffer xPathStatement = new StringBuffer();
+		xPathStatement.append("/");
+		for (String xpathElem : xpathElements) {
+			xPathStatement.append("/");
+			xPathStatement.append(xpathElem);
+		}
+
+		return xPathStatement.toString();
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see de.ugoe.cs.util.console.Command#help()
+	 */
+	@Override
+	public String help() {
+
+		return "generateSeleniumReplay [sequenceName] <HTMLFilesFolder> <websiteBaseURL> \n "
+				+ "The default sequenceName is 'sequences' got from default sequence name of command 'parseDirHTML'";
+	}
+
+}
