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

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