source: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java @ 1339

Last change on this file since 1339 was 1339, checked in by pharms, 10 years ago
  • added support for parameterizing the parsing of GUI models to ignore ids or indexes
  • Property svn:mime-type set to text/plain
File size: 16.8 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.html;
16
17import java.util.Arrays;
18import java.util.List;
19import java.util.Map;
20import java.util.regex.Matcher;
21import java.util.regex.Pattern;
22
23import org.xml.sax.SAXException;
24
25import de.ugoe.cs.autoquest.eventcore.Event;
26import de.ugoe.cs.autoquest.eventcore.IEventType;
27import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
28import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
29import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
30import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
31import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
32import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
33import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec;
34import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
35import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
36import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec;
37
38/**
39 * <p>
40 * This class provides the functionality to parse XML log files generated by the HTMLMonitor of
41 * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model
42 * </p>
43 *
44 * @author Fabian Glaser, Patrick Harms
45 * @version 1.0
46 *
47 */
48public class HTMLLogParser extends AbstractDefaultLogParser {
49   
50    /**
51     * <p>
52     * the pattern used for parsing HTML GUI element paths
53     * </p>
54     */
55    private Pattern htmlElementPattern =
56        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))");
57   
58    /**
59     * <p>
60     * the pattern used for parsing parsing parameters
61     * </p>
62     */
63    private Pattern htmlElementSpecPattern =
64        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))?");
65   
66    /**
67     * <p>
68     * parameters to influence parsing
69     * </p>
70     */
71    private Map<String, List<String>> parseParams;
72
73    /**
74     * <p>
75     * TODO: comment
76     * </p>
77     *
78     * @param parseParams
79     */
80    public HTMLLogParser(Map<String, List<String>> parseParams) {
81        this.parseParams = parseParams;
82       
83        for (String paramKey : parseParams.keySet()) {
84            if (!"clearId".equals(paramKey) && !"clearIndex".equals(paramKey)) {
85                throw new IllegalArgumentException("unknown parse parameter key " + paramKey);
86            }
87        }
88    }
89
90    /* (non-Javadoc)
91     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
92     */
93    @Override
94    protected boolean handleGUIElement(String id, Map<String, String> parameters)
95        throws SAXException
96    {
97        HTMLGUIElementSpec specification = null;
98       
99        String parentId = parameters.get("parent");
100        HTMLGUIElement parent = (HTMLGUIElement) super.getGUIElementTree().find(parentId);
101
102        if (parameters.containsKey("host")) {
103            // this is a server specification
104            int port = 80;
105            String portStr = parameters.get("port");
106           
107            if (portStr != null) {
108                port = Integer.parseInt(portStr);
109            }
110           
111            specification = new HTMLServerSpec(parameters.get("host"), port);
112        }
113        else if (parameters.containsKey("path")) {
114            // this is a document specification
115           
116            if (parent != null) {
117                if (!(parent.getSpecification() instanceof HTMLServerSpec)) {
118                    throw new SAXException
119                        ("invalid log: parent GUI element of a document is not of type server");
120                }
121               
122                specification = new HTMLDocumentSpec
123                    ((HTMLServerSpec) parent.getSpecification(), parameters.get("path"),
124                     parameters.get("query"), parameters.get("title"));
125            }
126            else if (parentId == null) {
127                throw new SAXException("invalid log: a document has no parent id");
128            }
129        }
130        else if (parameters.containsKey("tagname")) {
131            String tagName = parameters.get("tagname");
132           
133            if (!tagNameMustBeConsidered(tagName)) {
134                return true;
135            }
136
137            if (parent != null) {
138                IGUIElement document = parent;
139               
140                while ((document != null) &&
141                       (!(document.getSpecification() instanceof HTMLDocumentSpec)))
142                {
143                    document = document.getParent();
144                }
145               
146                if (document == null) {
147                    throw new SAXException
148                        ("invalid log: parent hierarchy of a page element does not contain a " +
149                         "document");
150                }
151               
152                int index = -1;
153                String indexStr = parameters.get("index");
154
155                if ((indexStr != null) && (!"".equals(indexStr))) {
156                    index = Integer.parseInt(indexStr);
157                }
158               
159                String htmlId = parameters.get("htmlid");
160               
161                if (clearIndex(tagName, index, htmlId, parent)) {
162                    index = -1;
163                }
164               
165                if (clearHTMLId(tagName, index, htmlId, parent)) {
166                    htmlId = null;
167                }
168               
169                if ((htmlId == null) && (index == -1)) {
170                    // set at least a default index, if all is to be ignored.
171                    index = 0;
172                }
173
174                specification = new HTMLPageElementSpec
175                    ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index);
176               
177            }
178            else if (parentId == null) {
179                throw new SAXException("invalid log: a page element has no parent id");
180            }
181        }
182        else {
183            throw new SAXException("invalid log: unknown GUI element");
184        }
185
186        if (specification != null) {
187            try {
188                super.getGUIElementTree().add(id, parentId, specification);
189            }
190            catch (GUIModelException e) {
191                throw new SAXException("could not handle GUI element with id " +
192                                       id + ": " + e.getMessage(), e);
193            }
194            return true;
195        }
196        else {
197            return false;
198        }
199    }
200
201    /**
202     * <p>
203     * TODO: comment
204     * </p>
205     *
206     * @param tagName
207     * @param parent
208     * @return
209     */
210    private boolean clearIndex(String tagName, int index, String id, HTMLGUIElement parent) {
211        return clearSomething("clearIndex", tagName, index, id, parent);
212    }
213
214    /**
215     * <p>
216     * TODO: comment
217     * </p>
218     *
219     * @param tagName
220     * @param parent
221     * @return
222     */
223    private boolean clearHTMLId(String tagName, int index, String id, HTMLGUIElement parent) {
224        return clearSomething("clearId", tagName, index, id, parent);
225    }
226   
227    /**
228     * <p>
229     * TODO: comment
230     * </p>
231     *
232     * @param tagName
233     * @param parent
234     * @return
235     */
236    private boolean clearSomething(String         parseParamId,
237                                   String         tagName,
238                                   int            index,
239                                   String         id,
240                                   HTMLGUIElement parent)
241    {
242        if (parseParams.containsKey(parseParamId)) {
243            for (String spec : parseParams.get(parseParamId)) {
244                // determine the specification parts
245                if (spec.startsWith("/")) {
246                    throw new IllegalArgumentException("can not handle absolute specifications");
247                }
248                else if (spec.endsWith("/")) {
249                    throw new IllegalArgumentException("specifications may not end with a /");
250                }
251               
252                String[] tagSpecs = spec.split("/");
253               
254                if (tagMatchesTagSpec(tagName, index, id, parent, tagSpecs)) {
255                    return true;
256                }
257            }
258        }
259       
260        return false;
261    }
262
263    /**
264     * <p>
265     * TODO: comment
266     * </p>
267     *
268     * @param tagName
269     * @param parent
270     * @param spec
271     * @return
272     */
273    private boolean tagMatchesTagSpec(String         tagName,
274                                      int            index,
275                                      String         id,
276                                      HTMLGUIElement parent,
277                                      String[]       tagSpecs)
278    {
279       
280        if (tagSpecs.length > 0) {
281            Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs[tagSpecs.length - 1]);
282           
283            if (!matcher.matches()) {
284                throw new IllegalArgumentException
285                    ("illegal tag specification " + tagSpecs[tagSpecs.length - 1]);
286            }
287           
288            if (!tagName.equals(matcher.group(1))) {
289                return false;
290            }
291           
292            String idCondition = matcher.group(4);
293           
294            if (idCondition != null) {
295                if (!idCondition.equals(id)) {
296                    return false;
297                }
298            }
299           
300            String indexCondition = matcher.group(3);
301           
302            if (indexCondition != null) {
303                try {
304                    if (index != Integer.parseInt(indexCondition)) {
305                        return false;
306                    }
307                }
308                catch (NumberFormatException e) {
309                    throw new IllegalArgumentException
310                        ("illegal tag index specification " + indexCondition, e);
311                }
312            }
313           
314            if (tagSpecs.length > 1) {
315                if (parent instanceof HTMLPageElement) {
316                    return tagMatchesTagSpec(((HTMLPageElement) parent).getTagName(),
317                                             ((HTMLPageElement) parent).getIndex(),
318                                             ((HTMLPageElement) parent).getHtmlId(),
319                                             (HTMLGUIElement) parent.getParent(),
320                                             Arrays.copyOfRange(tagSpecs, 0, tagSpecs.length - 1));
321                }
322                else {
323                    throw new IllegalArgumentException
324                        ("specification matches documents or servers. This is not supported yet.");
325                }
326            }
327            else {
328                return true;
329            }
330        }
331        else {
332            return true;
333        }
334    }
335
336    /* (non-Javadoc)
337     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
338     */
339    @Override
340    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
341        String targetId = parameters.get("target");
342       
343        if (targetId == null) {
344            if (parseParams.size() != 0) {
345                throw new SAXException
346                    ("old log file versions can not be parsed with parse parameters");
347            }
348           
349            String targetDocument = parameters.get("targetDocument");
350            String targetDOMPath = parameters.get("targetDOMPath");
351           
352            if ((targetDocument == null) || (targetDOMPath == null)) {
353                throw new SAXException("event has no target defined");
354            }
355           
356            targetId = determineTargetId(targetDocument, targetDOMPath);
357           
358            if (targetId == null) {
359                // the target id can not be determined yet
360                return false;
361            }
362        }
363       
364        IGUIElement target = super.getGUIElementTree().find(targetId);
365       
366        if (target == null) {
367            // event not processible yet
368            return false;
369        }
370
371        IEventType eventType =
372            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
373       
374        if (eventType != null) {
375            Event event = new Event(eventType, target);
376
377            String timestampStr = parameters.get("timestamp");
378       
379            if (timestampStr != null) {
380                event.setTimestamp(Long.parseLong(timestampStr));
381            }
382
383            ((HTMLGUIElement) event.getTarget()).markUsed();
384       
385            super.addToSequence(event);
386        }
387        // else ignore unknown event type
388
389        return true;
390    }
391
392    /**
393     * <p>
394     * used to determine the id of a target denoted by an event. This is only required for older
395     * document formats. The new formats use concrete ids.
396     * </p>
397     */
398    private String determineTargetId(String targetDocument, String targetDOMPath)
399        throws SAXException
400    {
401        IGUIElement document = super.getGUIElementTree().find(targetDocument);
402       
403        if (document == null) {
404            return null;
405        }
406       
407        if (!(document.getSpecification() instanceof HTMLDocumentSpec)) {
408            throw new SAXException("an id that should refer to an HTML document refers to" +
409                                   "something else");
410        }
411       
412        GUIModel model = super.getGUIElementTree().getGUIModel();
413        IGUIElement child = document;
414        String[] pathElements = targetDOMPath.split("/");
415        int pathIndex = 0;
416       
417        HTMLPageElementSpec compareSpec;
418        String tagName;
419        int index;
420        String htmlId;
421       
422        while ((pathIndex < pathElements.length) && (child != null)) {
423            if ((pathElements[pathIndex] != null) && (!"".equals(pathElements[pathIndex]))) {           
424                Matcher matcher = htmlElementPattern.matcher(pathElements[pathIndex]);
425                if (!matcher.matches()) {
426                    throw new SAXException
427                        ("could not parse target DOM path element " + pathElements[pathIndex]);
428                }
429
430                tagName = matcher.group(1);
431                String indexStr = matcher.group(3);
432                htmlId = matcher.group(4);
433
434                index = -1;
435                if ((indexStr != null) && (!"".equals(indexStr))) {
436                    index = Integer.parseInt(indexStr);
437                }
438
439                compareSpec = new HTMLPageElementSpec
440                    ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index);
441
442                List<IGUIElement> children = model.getChildren(child);
443                child = null;
444
445                for (IGUIElement candidate : children) {
446                    if (compareSpec.getSimilarity(candidate.getSpecification())) {
447                        child = candidate;
448                        break;
449                    }
450                }
451            }
452           
453            pathIndex++;
454        }
455       
456        if (child != null) {
457            return super.getGUIElementTree().find(child);
458        }
459        else {
460            return null;
461        }
462    }
463
464    /**
465     * <p>
466     * checks if tags with the provided name must be handled in the GUI model. As an example,
467     * it is not necessary to handle "head" tags and anything included in them.
468     * </p>
469     *
470     * @param tagName the tag name to check
471     *
472     * @return true, if the tag must be considered, false else
473     */
474    private boolean tagNameMustBeConsidered(String tagName) {
475        if (!tagName.startsWith("input_")) {
476            for (int i = 0; i < tagName.length(); i++) {
477                // all known HTML tags are either letters or digits, but nothing else. Any GUI model
478                // containing something different is proprietary and, therefore, ignored.
479                if (!Character.isLetterOrDigit(tagName.charAt(i))) {
480                    return false;
481                }
482            }
483        }
484       
485        return
486            !"head".equals(tagName) && !"title".equals(tagName) && !"script".equals(tagName) &&
487            !"style".equals(tagName) && !"link".equals(tagName) && !"meta".equals(tagName) &&
488            !"iframe".equals(tagName) && !"input_hidden".equals(tagName) &&
489            !"option".equals(tagName) && !"tt".equals(tagName) && !"br".equals(tagName) &&
490            !"colgroup".equals(tagName) && !"col".equals(tagName) && !"hr".equals(tagName) &&
491            !"param".equals(tagName) && !"sfmsg".equals(tagName);
492
493    }
494
495}
Note: See TracBrowser for help on using the repository browser.