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

Last change on this file since 1435 was 1435, checked in by pharms, 10 years ago
  • extended parsing of HTML log files to support further case study
  • Property svn:mime-type set to text/plain
File size: 23.7 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.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.IOException;
21import java.util.Arrays;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.Properties;
26import java.util.regex.Matcher;
27import java.util.regex.Pattern;
28
29import org.xml.sax.SAXException;
30
31import de.ugoe.cs.autoquest.eventcore.Event;
32import de.ugoe.cs.autoquest.eventcore.IEventType;
33import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
34import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
35import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
36import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
37import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
38import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
39import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec;
40import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
41import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
42import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec;
43
44/**
45 * <p>
46 * This class provides the functionality to parse XML log files generated by the HTMLMonitor of
47 * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model.
48 * </p>
49 * <p>
50 * The parser can be configured with parsing parameters to ignore, e.g., ids or indexes of
51 * parsed GUI elements. Details can be found in the manual pages of the respective parsing commands.
52 * </p>
53 *
54 * @author Fabian Glaser, Patrick Harms
55 * @version 1.0
56 *
57 */
58public class HTMLLogParser extends AbstractDefaultLogParser {
59   
60    /**
61     * <p>
62     * the pattern used for parsing HTML GUI element paths
63     * </p>
64     */
65    private Pattern htmlElementPattern =
66        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))");
67   
68    /**
69     * <p>
70     * the pattern used for parsing parsing parameters
71     * </p>
72     */
73    private Pattern htmlElementSpecPattern =
74        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-#]+)\\))?");
75   
76    /**
77     * <p>
78     * parameters to influence parsing
79     * </p>
80     */
81    private Map<String, List<String>> parseParams;
82
83    /**
84     * <p>
85     * a map containing replacement specifications for ids of GUI elements
86     * </p>
87     */
88    private Map<String, String> idReplacements;
89
90    /**
91     * <p>
92     * initializes the parser with the parsing parameters to be considered
93     * </p>
94     *
95     * @param parseParams the parsing parameters to be considered
96     */
97    public HTMLLogParser(Map<String, List<String>> parseParams) {
98        this.parseParams = parseParams;
99       
100        for (String paramKey : parseParams.keySet()) {
101            if (!"clearId".equals(paramKey) && !"clearIndex".equals(paramKey) &&
102                !"idReplacements".equals(paramKey))
103            {
104                throw new IllegalArgumentException("unknown parse parameter key " + paramKey);
105            }
106        }
107    }
108
109    /* (non-Javadoc)
110     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
111     */
112    @Override
113    protected boolean handleGUIElement(String id, Map<String, String> parameters)
114        throws SAXException
115    {
116        HTMLGUIElementSpec specification = null;
117       
118        String parentId = parameters.get("parent");
119        HTMLGUIElement parent = (HTMLGUIElement) super.getGUIElementTree().find(parentId);
120
121        if (parameters.containsKey("host")) {
122            // this is a server specification
123            int port = 80;
124            String portStr = parameters.get("port");
125           
126            if (portStr != null) {
127                port = Integer.parseInt(portStr);
128            }
129           
130            specification = new HTMLServerSpec(parameters.get("host"), port);
131        }
132        else if (parameters.containsKey("path")) {
133            // this is a document specification
134           
135            if (parent != null) {
136                if (!(parent.getSpecification() instanceof HTMLServerSpec)) {
137                    throw new SAXException
138                        ("invalid log: parent GUI element of a document is not of type server");
139                }
140               
141                specification = new HTMLDocumentSpec
142                    ((HTMLServerSpec) parent.getSpecification(), parameters.get("path"),
143                     parameters.get("query"), parameters.get("title"));
144            }
145            else if (parentId == null) {
146                throw new SAXException("invalid log: a document has no parent id");
147            }
148        }
149        else if (parameters.containsKey("tagname")) {
150            String tagName = parameters.get("tagname");
151           
152            if (!tagNameMustBeConsidered(tagName)) {
153                return true;
154            }
155
156            if (parent != null) {
157                if (!childrenMustBeConsidered(parent)) {
158                    return true;
159                }
160               
161                IGUIElement document = parent;
162               
163                while ((document != null) &&
164                       (!(document.getSpecification() instanceof HTMLDocumentSpec)))
165                {
166                    document = document.getParent();
167                }
168               
169                if (document == null) {
170                    throw new SAXException
171                        ("invalid log: parent hierarchy of a page element does not contain a " +
172                         "document");
173                }
174               
175                int index = -1;
176                String indexStr = parameters.get("index");
177
178                if ((indexStr != null) && (!"".equals(indexStr))) {
179                    index = Integer.parseInt(indexStr);
180                }
181               
182                String htmlId = parameters.get("htmlid");
183               
184                if (clearIndex(tagName, index, htmlId, parent)) {
185                    index = -1;
186                }
187               
188                String idReplacement = replaceHTMLId(tagName, index, htmlId, parent);
189                if (idReplacement != null) {
190                    htmlId = idReplacement;
191                }
192                else if (clearHTMLId(tagName, index, htmlId, parent)) {
193                    htmlId = null;
194                }
195               
196                if ((htmlId == null) && (index == -1)) {
197                    // set at least a default index, if all is to be ignored.
198                    index = 0;
199                }
200
201                specification = new HTMLPageElementSpec
202                    ((HTMLDocumentSpec) document.getSpecification(),
203                     tagName.intern(), htmlId == null ? null : htmlId.intern(), index);
204               
205            }
206            else if (parentId == null) {
207                throw new SAXException("invalid log: a page element has no parent id");
208            }
209        }
210        else {
211            throw new SAXException("invalid log: unknown GUI element");
212        }
213
214        if (specification != null) {
215            try {
216                super.getGUIElementTree().add(id, parentId, specification);
217            }
218            catch (GUIModelException e) {
219                throw new SAXException("could not handle GUI element with id " +
220                                       id + ": " + e.getMessage(), e);
221            }
222            return true;
223        }
224        else {
225            return false;
226        }
227    }
228
229    /**
230     * <p>
231     * checks if for a specific GUI element the index shall be ignored or not by considering the
232     * parsing parameters.
233     * </p>
234     *
235     * @param tagName the tag of the considered GUI element
236     * @param index   the index of the GUI element
237     * @param id      the id of the GUI element
238     * @param parent  the parent GUI element of the considered GUI element
239     *
240     * @return true if the index shall be ignored, false else.
241     */
242    private boolean clearIndex(String tagName, int index, String id, HTMLGUIElement parent) {
243        return clearSomething("clearIndex", tagName, index, id, parent);
244    }
245
246    /**
247     * <p>
248     * checks if the parsing parameters define a replacement for the id of the given GUI element
249     * and if so returns this replacement
250     * </p>
251     *
252     * @param tagName the tag of the considered GUI element
253     * @param index   the index of the GUI element
254     * @param id      the id of the GUI element
255     * @param parent  the parent GUI element of the considered GUI element
256     *
257     * @return the identified replacement
258     */
259    private String replaceHTMLId(String tagName, int index, String htmlId, HTMLGUIElement parent)
260        throws SAXException
261    {
262        if ((idReplacements == null) && (parseParams.containsKey("idReplacements"))) {
263            idReplacements = new HashMap<String, String>();
264            for (String fileName : parseParams.get("idReplacements")) {
265                Properties props = new Properties();
266                FileInputStream stream = null;
267                try {
268                    stream = new FileInputStream(new File(fileName));
269                    props.load(stream);
270                }
271                catch (FileNotFoundException e) {
272                    throw new SAXException("could not find file " + fileName, e);
273                }
274                catch (IOException e) {
275                    throw new SAXException("error reading file " + fileName, e);
276                }
277                finally {
278                    if (stream != null) {
279                        try {
280                            stream.close();
281                        }
282                        catch (IOException e) {
283                            // ignore
284                        }
285                    }
286                }
287               
288                for (Map.Entry<Object, Object> entry : props.entrySet()) {
289                    idReplacements.put((String) entry.getKey(), (String) entry.getValue());
290                }
291            }
292        }
293       
294        if (idReplacements != null) {
295            for (Map.Entry<String, String> replacementSpec : idReplacements.entrySet()) {
296                String tagSpec = replacementSpec.getKey();
297
298                if (tagSpec.startsWith("/")) {
299                    throw new IllegalArgumentException("can not handle absolute specifications");
300                }
301                else if (tagSpec.endsWith("/")) {
302                    throw new IllegalArgumentException("specifications may not end with a /");
303                }
304
305                String[] tagSpecs = tagSpec.split("/");
306
307                if (tagMatchesTagSpec(tagName, index, htmlId, parent, tagSpecs)) {
308                    return replacementSpec.getValue();
309                }
310            }
311        }
312       
313        return null;
314    }
315
316    /**
317     * <p>
318     * checks if for a specific GUI element the id shall be ignored or not by considering the
319     * parsing parameters.
320     * </p>
321     *
322     * @param tagName the tag of the considered GUI element
323     * @param index   the index of the GUI element
324     * @param id      the id of the GUI element
325     * @param parent  the parent GUI element of the considered GUI element
326     *
327     * @return true if the id shall be ignored, false else.
328     */
329    private boolean clearHTMLId(String tagName, int index, String id, HTMLGUIElement parent) {
330        return clearSomething("clearId", tagName, index, id, parent);
331    }
332   
333    /**
334     * <p>
335     * convenience method to check for the existence for specific parsing parameters for clearing
336     * ids or indexes of GUI elements.
337     * </p>
338     *
339     * @param parseParamId the id of the parsing parameter to be checked for the GUI element
340     * @param tagName      the tag of the considered GUI element
341     * @param index        the index of the GUI element
342     * @param id           the id of the GUI element
343     * @param parent       the parent GUI element of the considered GUI element
344     *
345     * @return true if the denoted parse parameter is set to ignore, false else.
346     */
347    private boolean clearSomething(String         parseParamId,
348                                   String         tagName,
349                                   int            index,
350                                   String         id,
351                                   HTMLGUIElement parent)
352    {
353        if (parseParams.containsKey(parseParamId)) {
354            for (String spec : parseParams.get(parseParamId)) {
355                // determine the specification parts
356                if (spec.startsWith("/")) {
357                    throw new IllegalArgumentException("can not handle absolute specifications");
358                }
359                else if (spec.endsWith("/")) {
360                    throw new IllegalArgumentException("specifications may not end with a /");
361                }
362               
363                String[] tagSpecs = spec.split("/");
364               
365                if (tagMatchesTagSpec(tagName, index, id, parent, tagSpecs)) {
366                    return true;
367                }
368            }
369        }
370       
371        return false;
372    }
373
374    /**
375     * <p>
376     * convenience method to check if a given GUI element matches a specification tags provided
377     * through the parsing parameters.
378     * </p>
379     *
380     * @param tagName  the tag of the considered GUI element
381     * @param index    the index of the GUI element
382     * @param id       the id of the GUI element
383     * @param parent   the parent GUI element of the considered GUI element
384     * @param tagSpecs the specification of a GUI element to match against the given GUI element
385     *
386     * @return true if the denoted parse parameter is set to ignore, false else.
387     */
388    private boolean tagMatchesTagSpec(String         tagName,
389                                      int            index,
390                                      String         id,
391                                      HTMLGUIElement parent,
392                                      String[]       tagSpecs)
393    {
394       
395        if (tagSpecs.length > 0) {
396            Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs[tagSpecs.length - 1]);
397           
398            if (!matcher.matches()) {
399                throw new IllegalArgumentException
400                    ("illegal tag specification " + tagSpecs[tagSpecs.length - 1]);
401            }
402           
403            if (!tagName.equals(matcher.group(1))) {
404                return false;
405            }
406           
407            String idCondition = matcher.group(4);
408           
409            if (idCondition != null) {
410                if (!idCondition.equals(id)) {
411                    // check if the id condition would match with ignoring specific characters
412                    if ((id != null) && (idCondition.indexOf('#') > -1)) {
413                        // first of all, the length must match
414                        if (idCondition.length() != id.length()) {
415                            return false;
416                        }
417                       
418                        for (int i = 0; i < idCondition.length(); i++) {
419                            if ((idCondition.charAt(i) != '#') &&
420                                (idCondition.charAt(i) != id.charAt(i)))
421                            {
422                                // if there is a character that is neither ignored not matches
423                                // the condition at a specific position, return "no match"
424                                return false;
425                            }
426                        }
427                       
428                    }
429                    else {
430                        // no condition ignoring specific characters
431                        return false;
432                    }
433                }
434            }
435           
436            String indexCondition = matcher.group(3);
437           
438            if (indexCondition != null) {
439                try {
440                    if (index != Integer.parseInt(indexCondition)) {
441                        return false;
442                    }
443                }
444                catch (NumberFormatException e) {
445                    throw new IllegalArgumentException
446                        ("illegal tag index specification " + indexCondition, e);
447                }
448            }
449           
450            if (tagSpecs.length > 1) {
451                if (parent instanceof HTMLPageElement) {
452                    return tagMatchesTagSpec(((HTMLPageElement) parent).getTagName(),
453                                             ((HTMLPageElement) parent).getIndex(),
454                                             ((HTMLPageElement) parent).getHtmlId(),
455                                             (HTMLGUIElement) parent.getParent(),
456                                             Arrays.copyOfRange(tagSpecs, 0, tagSpecs.length - 1));
457                }
458                else {
459                    throw new IllegalArgumentException
460                        ("specification matches documents or servers. This is not supported yet.");
461                }
462            }
463            else {
464                return true;
465            }
466        }
467        else {
468            return true;
469        }
470    }
471
472    /* (non-Javadoc)
473     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
474     */
475    @Override
476    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
477        String targetId = parameters.get("target");
478       
479        if (targetId == null) {
480            if (parseParams.size() != 0) {
481                throw new SAXException
482                    ("old log file versions can not be parsed with parse parameters");
483            }
484           
485            String targetDocument = parameters.get("targetDocument");
486            String targetDOMPath = parameters.get("targetDOMPath");
487           
488            if ((targetDocument == null) || (targetDOMPath == null)) {
489                throw new SAXException("event has no target defined");
490            }
491           
492            targetId = determineTargetId(targetDocument, targetDOMPath);
493           
494            if (targetId == null) {
495                // the target id can not be determined yet
496                return false;
497            }
498        }
499       
500        IGUIElement target = super.getGUIElementTree().find(targetId);
501       
502        if (target == null) {
503            // event not processible yet
504            return false;
505        }
506
507        IEventType eventType =
508            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
509       
510        if (eventType != null) {
511            Event event = new Event(eventType, target);
512
513            String timestampStr = parameters.get("timestamp");
514       
515            if (timestampStr != null) {
516                event.setTimestamp(Long.parseLong(timestampStr));
517            }
518
519            ((HTMLGUIElement) event.getTarget()).markUsed();
520       
521            super.addToSequence(event);
522        }
523        // else ignore unknown event type
524
525        return true;
526    }
527
528    /**
529     * <p>
530     * used to determine the id of a target denoted by an event. This is only required for older
531     * document formats. The new formats use concrete ids.
532     * </p>
533     */
534    private String determineTargetId(String targetDocument, String targetDOMPath)
535        throws SAXException
536    {
537        IGUIElement document = super.getGUIElementTree().find(targetDocument);
538       
539        if (document == null) {
540            return null;
541        }
542       
543        if (!(document.getSpecification() instanceof HTMLDocumentSpec)) {
544            throw new SAXException("an id that should refer to an HTML document refers to" +
545                                   "something else");
546        }
547       
548        GUIModel model = super.getGUIElementTree().getGUIModel();
549        IGUIElement child = document;
550        String[] pathElements = targetDOMPath.split("/");
551        int pathIndex = 0;
552       
553        HTMLPageElementSpec compareSpec;
554        String tagName;
555        int index;
556        String htmlId;
557       
558        while ((pathIndex < pathElements.length) && (child != null)) {
559            if ((pathElements[pathIndex] != null) && (!"".equals(pathElements[pathIndex]))) {           
560                Matcher matcher = htmlElementPattern.matcher(pathElements[pathIndex]);
561                if (!matcher.matches()) {
562                    throw new SAXException
563                        ("could not parse target DOM path element " + pathElements[pathIndex]);
564                }
565
566                tagName = matcher.group(1);
567                String indexStr = matcher.group(3);
568                htmlId = matcher.group(4);
569
570                index = -1;
571                if ((indexStr != null) && (!"".equals(indexStr))) {
572                    index = Integer.parseInt(indexStr);
573                }
574
575                compareSpec = new HTMLPageElementSpec
576                    ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index);
577
578                List<IGUIElement> children = model.getChildren(child);
579                child = null;
580
581                for (IGUIElement candidate : children) {
582                    if (compareSpec.getSimilarity(candidate.getSpecification())) {
583                        child = candidate;
584                        break;
585                    }
586                }
587            }
588           
589            pathIndex++;
590        }
591       
592        if (child != null) {
593            return super.getGUIElementTree().find(child);
594        }
595        else {
596            return null;
597        }
598    }
599
600    /**
601     * <p>
602     * checks if tags with the provided name must be handled in the GUI model. As an example,
603     * it is not necessary to handle "head" tags and anything included in them.
604     * </p>
605     *
606     * @param tagName the tag name to check
607     *
608     * @return true, if the tag must be considered, false else
609     */
610    private boolean tagNameMustBeConsidered(String tagName) {
611        if (!tagName.startsWith("input_")) {
612            for (int i = 0; i < tagName.length(); i++) {
613                // all known HTML tags are either letters or digits, but nothing else. Any GUI model
614                // containing something different is proprietary and, therefore, ignored.
615                if (!Character.isLetterOrDigit(tagName.charAt(i))) {
616                    return false;
617                }
618            }
619        }
620       
621        return
622            !"head".equals(tagName) && !"title".equals(tagName) && !"script".equals(tagName) &&
623            !"style".equals(tagName) && !"link".equals(tagName) && !"meta".equals(tagName) &&
624            !"iframe".equals(tagName) && !"input_hidden".equals(tagName) &&
625            !"option".equals(tagName) && !"tt".equals(tagName) && !"br".equals(tagName) &&
626            !"colgroup".equals(tagName) && !"col".equals(tagName) && !"hr".equals(tagName) &&
627            !"param".equals(tagName) && !"sfmsg".equals(tagName);
628
629    }
630
631    /**
632     * <p>
633     * checks if the children of a specified parent must be added to the GUI model or not.
634     * </p>
635     *
636     * @param parent the parent tag to check
637     *
638     * @return true, if the child of the tag must be considered, false else
639     */
640    private boolean childrenMustBeConsidered(HTMLGUIElement parent) {
641        if (parent instanceof HTMLPageElement) {
642            return !"svg".equals(((HTMLPageElement) parent).getTagName());
643        }
644        else {
645            return true;
646        }
647    }
648
649}
Note: See TracBrowser for help on using the repository browser.