// 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. package de.ugoe.cs.autoquest.plugin.html; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.xml.sax.SAXException; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IEventType; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec; /** *

* This class provides the functionality to parse XML log files generated by the HTMLMonitor of * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model *

* * @author Fabian Glaser, Patrick Harms * @version 1.0 * */ public class HTMLLogParser extends AbstractDefaultLogParser { /** * */ private Pattern htmlElementPattern = Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))"); /* (non-Javadoc) * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(java.lang.String, java.util.Map) */ @Override protected boolean handleGUIElement(String id, Map parameters) throws SAXException { HTMLGUIElementSpec specification = null; String parentId = parameters.get("parent"); IGUIElement parent = super.getGUIElementTree().find(parentId); if (parameters.containsKey("host")) { // this is a server specification int port = 80; String portStr = parameters.get(port); if (portStr != null) { port = Integer.parseInt(portStr); } specification = new HTMLServerSpec(parameters.get("host"), port); } else if (parameters.containsKey("path")) { // this is a document specification if (parent != null) { if (!(parent.getSpecification() instanceof HTMLServerSpec)) { throw new SAXException ("invalid log: parent GUI element of a document is not of type server"); } specification = new HTMLDocumentSpec ((HTMLServerSpec) parent.getSpecification(), parameters.get("path"), parameters.get("query"), parameters.get("title")); } else if (parentId == null) { throw new SAXException("invalid log: a document has no parent id"); } } else if (parameters.containsKey("tagname")) { String tagName = parameters.get("tagname"); if (!tagNameMustBeConsidered(tagName)) { return true; } if (parent != null) { IGUIElement document = parent; while ((document != null) && (!(document.getSpecification() instanceof HTMLDocumentSpec))) { document = document.getParent(); } if (document == null) { throw new SAXException ("invalid log: parent hierarchy of a page element does not contain a " + "document"); } int index = -1; String indexStr = parameters.get("index"); if ((indexStr != null) && (!"".equals(indexStr))) { index = Integer.parseInt(indexStr); } specification = new HTMLPageElementSpec ((HTMLDocumentSpec) document.getSpecification(), tagName, parameters.get("htmlid"), index); } else if (parentId == null) { throw new SAXException("invalid log: a page element has no parent id"); } } else { throw new SAXException("invalid log: unknown GUI element"); } if (specification != null) { try { super.getGUIElementTree().add(id, parentId, specification); } catch (GUIModelException e) { throw new SAXException("could not handle GUI element with id " + id + ": " + e.getMessage(), e); } return true; } else { return false; } } /* (non-Javadoc) * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(java.lang.String, java.util.Map) */ @Override protected boolean handleEvent(String type, Map parameters) throws SAXException { String targetId = parameters.get("target"); if (targetId == null) { String targetDocument = parameters.get("targetDocument"); String targetDOMPath = parameters.get("targetDOMPath"); if ((targetDocument == null) || (targetDOMPath == null)) { throw new SAXException("event has no target defined"); } targetId = determineTargetId(targetDocument, targetDOMPath); if (targetId == null) { // the target id can not be determined yet return false; } } IGUIElement target = super.getGUIElementTree().find(targetId); if (target == null) { // event not processible yet return false; } IEventType eventType = HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target); if (eventType != null) { Event event = new Event(eventType, target); String timestampStr = parameters.get("timestamp"); if (timestampStr != null) { event.setTimestamp(Long.parseLong(timestampStr)); } ((HTMLGUIElement) event.getTarget()).markUsed(); super.addToSequence(event); } // else ignore unknown event type return true; } /** *

* TODO: comment *

* * @param targetDocument * @param targetDOMPath * @return */ private String determineTargetId(String targetDocument, String targetDOMPath) throws SAXException { IGUIElement document = super.getGUIElementTree().find(targetDocument); if (document == null) { return null; } if (!(document.getSpecification() instanceof HTMLDocumentSpec)) { throw new SAXException("an id that should refer to an HTML document refers to" + "something else"); } GUIModel model = super.getGUIElementTree().getGUIModel(); IGUIElement child = document; String[] pathElements = targetDOMPath.split("/"); int pathIndex = 0; HTMLPageElementSpec compareSpec; String tagName; int index; String htmlId; while ((pathIndex < pathElements.length) && (child != null)) { if ((pathElements[pathIndex] != null) && (!"".equals(pathElements[pathIndex]))) { Matcher matcher = htmlElementPattern.matcher(pathElements[pathIndex]); if (!matcher.matches()) { throw new SAXException ("could not parse target DOM path element " + pathElements[pathIndex]); } tagName = matcher.group(1); String indexStr = matcher.group(3); htmlId = matcher.group(4); index = -1; if ((indexStr != null) && (!"".equals(indexStr))) { index = Integer.parseInt(indexStr); } compareSpec = new HTMLPageElementSpec ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index); List children = model.getChildren(child); child = null; for (IGUIElement candidate : children) { if (compareSpec.getSimilarity(candidate.getSpecification())) { child = candidate; break; } } } pathIndex++; } if (child != null) { return super.getGUIElementTree().find(child); } else { return null; } } /** *

* checks if tags with the provided name must be handled in the GUI model. As an example, * it is not necessary to handle "head" tags and anything included in them. *

* * @param tagName * @return */ private boolean tagNameMustBeConsidered(String tagName) { return !"head".equals(tagName) && !"title".equals(tagName) && !"script".equals(tagName) && !"style".equals(tagName) && !"link".equals(tagName) && !"meta".equals(tagName) && !"iframe".equals(tagName) && !"input_hidden".equals(tagName) && !"option".equals(tagName) && !"tt".equals(tagName) && !"br".equals(tagName) && !"colgroup".equals(tagName) && !"col".equals(tagName); } }