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

Last change on this file since 1893 was 1893, checked in by pharms, 9 years ago
  • added support for mapping document paths and removing their queries
  • Property svn:mime-type set to text/plain
File size: 39.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.io.UnsupportedEncodingException;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.HashMap;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.ListIterator;
28import java.util.Map;
29import java.util.Properties;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32
33import org.apache.commons.codec.binary.Base64;
34import org.xml.sax.SAXException;
35
36import de.ugoe.cs.autoquest.eventcore.Event;
37import de.ugoe.cs.autoquest.eventcore.IEventType;
38import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
39import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
40import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
41import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
42import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
43import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
44import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
45import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec;
46import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
47import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
48import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec;
49
50/**
51 * <p>
52 * This class provides the functionality to parse XML log files generated by the HTMLMonitor of
53 * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model.
54 * </p>
55 * <p>
56 * The parser can be configured with parsing parameters to adapt, e.g., ids or or ignore indexes of
57 * parsed GUI elements. Details can be found in the manual pages of the respective parsing commands.
58 * </p>
59 *
60 * @author Fabian Glaser, Patrick Harms
61 * @version 1.0
62 *
63 */
64public class HTMLLogParser extends AbstractDefaultLogParser {
65   
66    /**
67     * <p>
68     * the pattern used for parsing HTML GUI element paths
69     * </p>
70     */
71    private Pattern htmlElementPattern =
72        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))");
73   
74    /**
75     * <p>
76     * the mapping between ids and their replacements due to merging while parsing
77     * </p>
78     */
79    private Map<String, String> idMapping = new HashMap<>();
80   
81    /**
82     * <p>
83     * file containing parameters to influence parsing
84     * </p>
85     */
86    private String parseParamFile;
87
88    /**
89     * <p>
90     * a map containing replacement specifications for ids of GUI elements
91     * </p>
92     */
93    private Map<String, List<ReplacementSpecification>> replacementSpecifications;
94
95    /**
96     * <p>
97     * initializes the parser with the file containing parsing parameters to be considered
98     * </p>
99     *
100     * @param parseParamFile the parsing parameters to be considered
101     */
102    public HTMLLogParser(String parseParamFile) {
103        this.parseParamFile = parseParamFile;
104    }
105
106    /* (non-Javadoc)
107     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
108     */
109    @Override
110    protected boolean handleGUIElement(String id, Map<String, String> parameters)
111        throws SAXException
112    {
113        if (idMapping.containsKey(id)) {
114            // the element is already existing. Return, that it was processed
115            return true;
116        }
117       
118        String parentId = parameters.get("parent");
119       
120        if (parentId != null) {
121            parentId = idMapping.get(parentId);
122
123            if (parentId == null) {
124                // parent not yet handled, return that this elements can not be handled yet
125                return false;
126            }
127        }
128       
129        HTMLGUIElement parent = (HTMLGUIElement) super.getGUIElementTree().find(parentId);
130       
131        ensureParsingParameters();
132       
133        HTMLGUIElementSpec specification = null;
134        String replacementId = null;
135           
136        if (parameters.containsKey("host")) {
137            // this is a server specification
138            int port = 80;
139            String portStr = parameters.get("port");
140           
141            if (portStr != null) {
142                port = Integer.parseInt(portStr);
143            }
144           
145            String host = parameters.get("host");
146            specification = new HTMLServerSpec(host, port);
147            replacementId = calculateId(host, portStr);
148        }
149        else if (parameters.containsKey("path")) {
150            // this is a document specification
151           
152            if (parent != null) {
153                if (!(parent.getSpecification() instanceof HTMLServerSpec)) {
154                    throw new SAXException
155                        ("invalid log: parent GUI element of a document is not of type server");
156                }
157               
158                String path = parameters.get("path");
159                String query = parameters.get("query");
160                String title = parameters.get("title");
161               
162                String replacement = getReplacementMapping(path, parent);
163               
164                if (replacement != null) {
165                    if (replacement.startsWith("CLEAR_QUERY,")) {
166                        query = null;
167                        replacement = replacement.substring("CLEAR_QUERY,".length());
168                    }
169                    else if ("CLEAR_QUERY".equals(replacement)) {
170                        query = null;
171                        replacement = path;
172                    }
173                   
174                    if ("".equals(replacement)) {
175                        path = null;
176                    }
177                    else {
178                        path = replacement;
179                    }
180                }
181               
182                specification = new HTMLDocumentSpec
183                    ((HTMLServerSpec) parent.getSpecification(), path, query, title);
184               
185                replacementId = calculateId(parentId, path, query, title);
186            }
187            else if (parentId == null) {
188                throw new SAXException("invalid log: a document has no parent id");
189            }
190        }
191        else if (parameters.containsKey("tagname")) {
192            String tagName = parameters.get("tagname");
193           
194            if (!tagNameMustBeConsidered(tagName)) {
195                return true;
196            }
197
198            if (parent != null) {
199                if (!childrenMustBeConsidered(parent)) {
200                    return true;
201                }
202               
203                IGUIElement document = parent;
204               
205                while ((document != null) &&
206                       (!(document.getSpecification() instanceof HTMLDocumentSpec)))
207                {
208                    document = document.getParent();
209                }
210               
211                if (document == null) {
212                    throw new SAXException
213                        ("invalid log: parent hierarchy of a page element does not contain a " +
214                         "document");
215                }
216               
217                int index = -1;
218                String indexStr = parameters.get("index");
219
220                if ((indexStr != null) && (!"".equals(indexStr))) {
221                    index = Integer.parseInt(indexStr);
222                }
223               
224                String htmlId = parameters.get("htmlid");
225               
226                String replacement = getReplacementMapping(tagName, index, htmlId, parent);
227               
228                if (replacement != null) {
229                    if (replacement.startsWith("CLEAR_INDEX,")) {
230                        index = -1;
231                        replacement = replacement.substring("CLEAR_INDEX,".length());
232                    }
233                    else if ("CLEAR_INDEX".equals(replacement)) {
234                        index = -1;
235                        replacement = htmlId;
236                    }
237                   
238                    if ("".equals(replacement)) {
239                        htmlId = null;
240                    }
241                    else {
242                        htmlId = replacement;
243                    }
244                }
245               
246                if ((htmlId == null) && (index == -1)) {
247                    // set at least a default index, if all is to be ignored.
248                    index = 0;
249                }
250               
251                HTMLDocumentSpec documentSpec = (HTMLDocumentSpec) document.getSpecification();
252
253                specification = new HTMLPageElementSpec
254                    (documentSpec, tagName.intern(), htmlId == null ? null : htmlId.intern(), index);
255               
256                replacementId = calculateId
257                    (documentSpec.getPath(), documentSpec.getQuery(), documentSpec.getTitle(),
258                     parentId, tagName, htmlId, (htmlId == null ? Integer.toString(index) : null));
259               
260            }
261            else if (parentId == null) {
262                throw new SAXException("invalid log: a page element has no parent id");
263            }
264        }
265        else {
266            throw new SAXException("invalid log: unknown GUI element");
267        }
268
269        if (specification != null) {
270            try {
271                idMapping.put(id, replacementId);
272               
273                super.getGUIElementTree().add(replacementId, parentId, specification);
274            }
275            catch (GUIModelException e) {
276                throw new SAXException("could not handle GUI element with id " +
277                                       id + ": " + e.getMessage(), e);
278            }
279            return true;
280        }
281        else {
282            return false;
283        }
284    }
285   
286    /**
287     * <p>
288     * returns the replacement mapping for the document path specified by the parameter, if a
289     * mapping exists.
290     * </p>
291     *
292     * @param path the path of the document
293     *
294     * @return the replacement mapping, if any is configured; null else
295     */
296    private String getReplacementMapping(String path, HTMLGUIElement parent) {
297        List<ReplacementSpecification> mappingCandidates =
298            replacementSpecifications.get(ReplacementSpecification.LAST_TAG_NAME_FOR_DOCUMENTS);
299       
300        List<ReplacementSpecification> candidates = new LinkedList<>();
301       
302        if (mappingCandidates != null) {
303            for (ReplacementSpecification replacementSpec : mappingCandidates) {
304                if (replacementSpec.matches(ReplacementSpecification.LAST_TAG_NAME_FOR_DOCUMENTS,
305                                            -1, path, parent))
306                {
307                    candidates.add(replacementSpec);
308                }
309            }
310        }
311       
312        prioritizeReplacementSpecs(candidates, "document " + path);
313       
314        if (candidates.size() == 1) {
315            return candidates.get(0).getReplacement();
316        }
317        else {
318            return null;
319        }
320    }
321
322    /**
323     * <p>
324     * returns the replacement mapping for the tag specified by the parameters, if a mapping exists.
325     * </p>
326     *
327     * @param tagName      the tag of the considered GUI element
328     * @param index        the index of the GUI element
329     * @param id           the id of the GUI element
330     * @param parent       the parent GUI element of the considered GUI element
331     *
332     * @return the replacement mapping, if any is configured; null else
333     */
334    private String getReplacementMapping(String         tagName,
335                                         int            index,
336                                         String         htmlId,
337                                         HTMLGUIElement parent)
338    {
339        List<ReplacementSpecification> mappingCandidates = replacementSpecifications.get(tagName);
340       
341        List<ReplacementSpecification> candidates = new LinkedList<>();
342       
343        if (mappingCandidates != null) {
344            for (ReplacementSpecification replacementSpec : mappingCandidates) {
345                if (replacementSpec.matches(tagName, index, htmlId, parent)) {
346                    candidates.add(replacementSpec);
347                }
348            }
349        }
350       
351        StringBuffer forWhat = new StringBuffer();
352        forWhat.append("tag ");
353        toString(tagName, index, htmlId, parent, forWhat);
354       
355        prioritizeReplacementSpecs(candidates, forWhat.toString());
356       
357        if (candidates.size() == 1) {
358            return candidates.get(0).getReplacement();
359        }
360        else {
361            return null;
362        }
363    }
364   
365    /**
366     * <p>
367     * decides, which replacement specification is to be preferred, if several match
368     * </p>
369     */
370    private void prioritizeReplacementSpecs(List<ReplacementSpecification> candidates,
371                                            String                         forWhat)
372    {
373        boolean hasNonPattern = false;
374       
375        for (ReplacementSpecification spec : candidates) {
376            if (!spec.isPattern()) {
377                hasNonPattern = true;
378                break;
379            }
380        }
381       
382        if (hasNonPattern) {
383            ListIterator<ReplacementSpecification> it = candidates.listIterator();
384           
385            while (it.hasNext()) {
386                if (it.next().isPattern()) {
387                    it.remove();
388                }
389            }
390        }
391       
392        if (candidates.size() > 1) {
393            StringBuffer message = new StringBuffer();
394            message.append("parse parameter file is ambigious for ");
395            message.append(forWhat);
396            message.append(". Can be mapped using ");
397           
398            int counter = 0;
399            for (ReplacementSpecification spec : candidates) {
400                message.append(spec);
401                counter++;
402               
403                if (counter < (candidates.size() - 1)) {
404                    message.append(", ");
405                }
406                else if (counter < candidates.size()) {
407                    message.append(", and ");
408                }
409            }
410                       
411            throw new IllegalArgumentException(message.toString());
412        }
413    }
414
415    /**
416     *
417     */
418    private void toString(String         tagName,
419                          int            index,
420                          String         htmlId,
421                          HTMLGUIElement parent,
422                          StringBuffer   message)
423    {
424        LinkedList<HTMLGUIElementSpec> specs = new LinkedList<>();
425       
426        HTMLGUIElement currentParent = parent;
427       
428        while (currentParent != null) {
429            specs.addFirst((HTMLGUIElementSpec) currentParent.getSpecification());
430            currentParent = (HTMLGUIElement) currentParent.getParent();
431        }
432       
433        for (HTMLGUIElementSpec spec : specs) {
434            message.append(spec.toString());
435            message.append("/");
436        }
437       
438        message.append(tagName);
439       
440        if (htmlId != null) {
441            message.append("(htmlId=");
442            message.append(htmlId);
443            message.append(')');
444        }
445        else {
446            message.append('[');
447            message.append(index);
448            message.append(']');
449        }
450    }
451
452    /* (non-Javadoc)
453     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
454     */
455    @Override
456    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
457        String targetId = parameters.get("target");
458       
459        if (targetId == null) {
460            if (replacementSpecifications.size() != 0) {
461                throw new SAXException
462                    ("old log file versions can not be parsed with parse parameters");
463            }
464           
465            String targetDocument = parameters.get("targetDocument");
466            String targetDOMPath = parameters.get("targetDOMPath");
467           
468            if ((targetDocument == null) || (targetDOMPath == null)) {
469                throw new SAXException("event has no target defined");
470            }
471           
472            targetId = determineTargetId(targetDocument, targetDOMPath);
473           
474            if (targetId == null) {
475                // the target id can not be determined yet
476                return false;
477            }
478        }
479       
480        targetId = idMapping.get(targetId);
481       
482        if (targetId == null) {
483            // event not processible yet
484            return false;
485        }
486       
487        IGUIElement target = super.getGUIElementTree().find(targetId);
488
489        IEventType eventType =
490            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
491       
492        if (eventType != null) {
493            Event event = new Event(eventType, target);
494
495            String timestampStr = parameters.get("timestamp");
496       
497            if (timestampStr != null) {
498                event.setTimestamp(Long.parseLong(timestampStr));
499            }
500
501            ((HTMLGUIElement) event.getTarget()).markUsed();
502       
503            super.addToSequence(event);
504        }
505        // else ignore unknown event type
506
507        return true;
508    }
509
510    /**
511     * <p>
512     * reads parsing parameters from the config file and makes them available for the parsing
513     * process
514     * </p>
515     */
516    private void ensureParsingParameters() throws SAXException {
517        if (replacementSpecifications == null) {
518            replacementSpecifications = new HashMap<String, List<ReplacementSpecification>>();
519           
520            if (parseParamFile != null) {
521                Properties props = new Properties();
522                FileInputStream stream = null;
523                try {
524                    stream = new FileInputStream(new File(parseParamFile));
525                    props.load(stream);
526                }
527                catch (FileNotFoundException e) {
528                    throw new SAXException("could not find file " + parseParamFile, e);
529                }
530                catch (IOException e) {
531                    throw new SAXException("error reading file " + parseParamFile, e);
532                }
533                finally {
534                    if (stream != null) {
535                        try {
536                            stream.close();
537                        }
538                        catch (IOException e) {
539                            // ignore
540                        }
541                    }
542                }
543
544                for (Map.Entry<Object, Object> entry : props.entrySet()) {
545                    ReplacementSpecification replSpec = new ReplacementSpecification
546                        ((String) entry.getKey(), (String) entry.getValue());
547                   
548                    List<ReplacementSpecification> similarReplSpecs =
549                        replacementSpecifications.get(replSpec.getLastTagName());
550                   
551                    if (similarReplSpecs == null) {
552                        similarReplSpecs = new LinkedList<ReplacementSpecification>();
553                        replacementSpecifications.put(replSpec.getLastTagName(), similarReplSpecs);
554                    }
555                   
556                    similarReplSpecs.add(replSpec);
557                }
558            }
559        }
560    }
561   
562    /**
563     * <p>
564     * used to determine the id of a target denoted by an event. This is only required for older
565     * document formats. The new formats use concrete ids.
566     * </p>
567     */
568    private String determineTargetId(String targetDocument, String targetDOMPath)
569        throws SAXException
570    {
571        IGUIElement document = super.getGUIElementTree().find(targetDocument);
572       
573        if (document == null) {
574            return null;
575        }
576       
577        if (!(document.getSpecification() instanceof HTMLDocumentSpec)) {
578            throw new SAXException("an id that should refer to an HTML document refers to" +
579                                   "something else");
580        }
581       
582        GUIModel model = super.getGUIElementTree().getGUIModel();
583        IGUIElement child = document;
584        String[] pathElements = targetDOMPath.split("/");
585        int pathIndex = 0;
586       
587        HTMLPageElementSpec compareSpec;
588        String tagName;
589        int index;
590        String htmlId;
591       
592        while ((pathIndex < pathElements.length) && (child != null)) {
593            if ((pathElements[pathIndex] != null) && (!"".equals(pathElements[pathIndex]))) {           
594                Matcher matcher = htmlElementPattern.matcher(pathElements[pathIndex]);
595                if (!matcher.matches()) {
596                    throw new SAXException
597                        ("could not parse target DOM path element " + pathElements[pathIndex]);
598                }
599
600                tagName = matcher.group(1);
601                String indexStr = matcher.group(3);
602                htmlId = matcher.group(4);
603
604                index = -1;
605                if ((indexStr != null) && (!"".equals(indexStr))) {
606                    index = Integer.parseInt(indexStr);
607                }
608
609                compareSpec = new HTMLPageElementSpec
610                    ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index);
611
612                List<IGUIElement> children = model.getChildren(child);
613                child = null;
614
615                for (IGUIElement candidate : children) {
616                    if (compareSpec.getSimilarity(candidate.getSpecification())) {
617                        child = candidate;
618                        break;
619                    }
620                }
621            }
622           
623            pathIndex++;
624        }
625       
626        if (child != null) {
627            return super.getGUIElementTree().find(child);
628        }
629        else {
630            return null;
631        }
632    }
633
634    /**
635     * <p>
636     * checks if tags with the provided name must be handled in the GUI model. As an example,
637     * it is not necessary to handle "head" tags and anything included in them.
638     * </p>
639     *
640     * @param tagName the tag name to check
641     *
642     * @return true, if the tag must be considered, false else
643     */
644    private boolean tagNameMustBeConsidered(String tagName) {
645        if (!tagName.startsWith("input_")) {
646            for (int i = 0; i < tagName.length(); i++) {
647                // all known HTML tags are either letters or digits, but nothing else. Any GUI model
648                // containing something different is proprietary and, therefore, ignored.
649                if (!Character.isLetterOrDigit(tagName.charAt(i))) {
650                    return false;
651                }
652            }
653        }
654       
655        return
656            !"head".equals(tagName) && !"title".equals(tagName) && !"script".equals(tagName) &&
657            !"style".equals(tagName) && !"link".equals(tagName) && !"meta".equals(tagName) &&
658            !"iframe".equals(tagName) && !"input_hidden".equals(tagName) &&
659            !"option".equals(tagName) && !"tt".equals(tagName) && !"br".equals(tagName) &&
660            !"colgroup".equals(tagName) && !"col".equals(tagName) && !"hr".equals(tagName) &&
661            !"param".equals(tagName) && !"sfmsg".equals(tagName) &&
662            !"wappalyzerdata".equals(tagName);
663
664    }
665
666    /**
667     * <p>
668     * checks if the children of a specified parent must be added to the GUI model or not.
669     * </p>
670     *
671     * @param parent the parent tag to check
672     *
673     * @return true, if the child of the tag must be considered, false else
674     */
675    private boolean childrenMustBeConsidered(HTMLGUIElement parent) {
676        if (parent instanceof HTMLPageElement) {
677            return !"svg".equals(((HTMLPageElement) parent).getTagName());
678        }
679        else {
680            return true;
681        }
682    }
683
684    /**
685     * <p>
686     * calculates a unique id for the given string fragments using SHA-512 and Base64 encoding.
687     * </p>
688     *
689     * @param fragments strings to be used for calculating a unique id
690     *
691     * @return a Base64 encoded unique id for the provided fragments
692     */
693    private String calculateId(String... fragments) {
694        try {
695            MessageDigest md = MessageDigest.getInstance("SHA-512");
696           
697            for (String fragment : fragments) {
698                if (fragment != null) {
699                    md.update(fragment.getBytes("UTF-8"));
700                }
701            }
702           
703            return Base64.encodeBase64String(md.digest());
704        }
705        catch (UnsupportedEncodingException e) {
706            throw new IllegalStateException("Java VM does not support this code");
707        }
708        catch (NoSuchAlgorithmException e) {
709            throw new IllegalStateException("Java VM does not support this code");
710        }
711    }
712
713    /**
714     * <p>specification for a replacement consisting of path of tag or document specifications
715     * and the appropriate replacement.</p>
716     */
717    private static class ReplacementSpecification {
718       
719        /**
720         *
721         */
722        private static final String LAST_TAG_NAME_FOR_DOCUMENTS = "LAST_TAG_NAME_FOR_DOCUMENTS";
723       
724        /**
725         * <p>
726         * the pattern used for parsing parsing parameters
727         * </p>
728         */
729        private Pattern htmlElementSpecPattern = Pattern.compile
730            ("(document\\(path=([\\w/\\-#$&%]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w\\-_#]+)\\))?)");
731       
732        /**
733         * <p>
734         * the path of specifications (tags and document) specifying the tag for which this
735         * replacement is specified
736         * </p>
737         */
738        private List<Spec> specs = new LinkedList<Spec>();
739       
740        /**
741         * <p>
742         * the name of the last tag in the specification path (used for indexing purposes)
743         * </p>
744         */
745        private String lastTagName;
746       
747        /**
748         * <p>
749         * the configured replacement
750         * </p>
751         */
752        private String replacement;
753
754        /**
755         * <p>
756         * initializes the specification with the key/value strings from the config file. Parses
757         * the key to get the specification path consisting of, optionally, a document
758         * specification and one or more tag specification.
759         * </p>
760         */
761        private ReplacementSpecification(String tagSpec, String replacement) {
762            List<String> tagSpecs = split(tagSpec);
763           
764            for (int i = 0; i < tagSpecs.size(); i++) {
765                Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs.get(i));
766               
767                if (!matcher.matches()) {
768                    throw new IllegalArgumentException
769                        ("illegal tag specification " + tagSpecs.get(i));
770                }
771               
772                if (matcher.group(1) != null) {
773                    this.specs.add(new DocumentSpec(matcher.group(2)));
774                }
775                else if (matcher.group(4) != null) {
776                    String indexConditionStr = matcher.group(6);
777                    Integer indexCondition = null;
778               
779                    if (indexConditionStr != null) {
780                        try {
781                            indexCondition = Integer.parseInt(indexConditionStr);
782                        }
783                        catch (NumberFormatException e) {
784                            throw new IllegalArgumentException
785                                ("illegal tag index specification " + indexConditionStr, e);
786                        }
787                    }
788               
789                    this.specs.add
790                        (new TagSpec(matcher.group(4), indexCondition, matcher.group(7)));
791                }
792            }
793           
794            if (this.specs.get(this.specs.size() - 1) instanceof TagSpec) {
795                this.lastTagName = ((TagSpec) this.specs.get(this.specs.size() - 1)).getTagName();
796            }
797            else {
798                this.lastTagName = LAST_TAG_NAME_FOR_DOCUMENTS;
799            }
800           
801            this.replacement = replacement;
802        }
803
804        /**
805         * <p>
806         * convenience method to split the key of a key/value pair from the config file into its
807         * parts
808         * </p>
809         */
810        private List<String> split(String tagSpec) {
811            List<String> specs = new LinkedList<String>();
812           
813            StringBuffer currentSpec = new StringBuffer();
814            int openBraces = 0;
815           
816            for (int i = 0; i < tagSpec.length(); i++) {
817                char curChar = tagSpec.charAt(i);
818                if ((openBraces == 0) && ('/' == curChar) && (currentSpec.length() > 0)) {
819                    specs.add(currentSpec.toString());
820                    currentSpec.setLength(0);
821                }
822                else {
823                    if ('(' == curChar) {
824                        openBraces++;
825                    }
826                    else if (')' == curChar) {
827                        openBraces--;
828                    }
829                    currentSpec.append(curChar);
830                }
831            }
832           
833            if (currentSpec.length() > 0) {
834                specs.add(currentSpec.toString());
835            }
836           
837            return specs;
838        }
839
840        /**
841         * <p>
842         * returns true, if the spec is a pattern, i.e. matches a group of similar elements
843         * </p>
844         */
845        public boolean isPattern() {
846            for (Spec spec : specs) {
847                if (spec.isPattern()) {
848                    return true;
849                }
850            }
851           
852            return false;
853        }
854
855        /**
856         * <p>
857         * checks, if the tag identified by the parameters matches this specificaiton.
858         * </p>
859         */
860        private boolean matches(String tagName, int index, String htmlId, HTMLGUIElement parent) {
861            String currentTagName = tagName;
862            int currentIndex = index;
863            String currentHtmlId = htmlId;
864            String currentPath = LAST_TAG_NAME_FOR_DOCUMENTS.equals(tagName) ? htmlId : null;
865            HTMLGUIElement currentParent = parent;
866           
867            int i = specs.size() - 1;
868           
869            while (i >= 0) {
870                if ((specs.get(i) instanceof TagSpec) &&
871                    (!((TagSpec) specs.get(i)).matches(currentTagName, currentIndex, currentHtmlId)))
872                {
873                    return false;
874                }
875                else if ((specs.get(i) instanceof DocumentSpec) &&
876                         (!((DocumentSpec) specs.get(i)).matches(currentPath)))
877                {
878                    return false;
879                }
880               
881                i--;
882               
883                if (i >= 0) {
884                    if (currentParent instanceof HTMLPageElement) {
885                        currentTagName = ((HTMLPageElement) currentParent).getTagName();
886                        currentIndex = ((HTMLPageElement) currentParent).getIndex();
887                        currentHtmlId = ((HTMLPageElement) currentParent).getHtmlId();
888                        currentPath = null;
889                        currentParent = (HTMLGUIElement) currentParent.getParent();
890                    }
891                    else if (currentParent instanceof HTMLDocument) {
892                        currentTagName = null;
893                        currentIndex = Integer.MIN_VALUE;
894                        currentHtmlId = null;
895                        currentPath = ((HTMLDocument) currentParent).getPath();
896                        currentParent = (HTMLGUIElement) currentParent.getParent();
897                    }
898                    else {
899                        throw new IllegalArgumentException
900                            ("specification matches documents or servers. This is not supported yet.");
901                    }
902                }
903            }
904           
905            return true;
906        }
907
908        /**
909         * <p>
910         * returns the specified replacement
911         * </p>
912         */
913        private String getReplacement() {
914            return replacement;
915        }
916
917        /**
918         * <p>
919         * returns the name of the last tag specified in the specification path
920         * </p>
921         */
922        private String getLastTagName() {
923            return lastTagName;
924        }
925
926        /* (non-Javadoc)
927         * @see java.lang.Object#toString()
928         */
929        @Override
930        public String toString() {
931            StringBuffer result = new StringBuffer();
932            for (Spec spec : specs) {
933                if (result.length() > 0) {
934                    result.append("/");
935                }
936                result.append(spec);
937            }
938           
939            result.append('=');
940            result.append(replacement);
941           
942            return result.toString();
943        }
944       
945    }
946
947    /**
948     * <p>
949     * parent type for document and tag specifications
950     * </p>
951     */
952    private static interface Spec {
953
954        /**
955         * <p>
956         * returns true if the spec is a pattern, i.e., matches a group of similar elements
957         * </p>
958         *
959         * @return
960         */
961        boolean isPattern();
962       
963    }
964
965    /**
966     * <p>
967     * specification of a document
968     * </p>
969     */
970    private static class DocumentSpec implements Spec {
971       
972        /**
973         * <p>
974         * the part of the path the document path must have to match this specification
975         * </p>
976         */
977        private String pathPart;
978
979        /**
980         * <p>
981         * initializes the document specification with the path part
982         * </p>
983         */
984        private DocumentSpec(String pathPart) {
985            this.pathPart = pathPart;
986        }
987
988        /* (non-Javadoc)
989         * @see de.ugoe.cs.autoquest.plugin.html.HTMLLogParser.Spec#isPattern()
990         */
991        @Override
992        public boolean isPattern() {
993            return (pathPart != null) && (pathPart.indexOf('#') > -1);
994        }
995
996        /**
997         * <p>
998         * returns true if the provided path contains the path part provided to the parameter
999         * </p>
1000         */
1001        private boolean matches(String path) {
1002            if ((path != null) && (path.contains(pathPart))) {
1003                return true;
1004            }
1005            else if ((path != null) && (isPattern() || (pathPart.indexOf('$') > -1))) {
1006                // check if the path condition would match with ignoring specific characters
1007               
1008                boolean mustMatchAtEnd = pathPart.charAt(pathPart.length() - 1) == '$';
1009
1010                int indexInPath = 0;
1011                int indexInPathPart = 0;
1012
1013                while (indexInPath < path.length()) {
1014                    if ((path.charAt(indexInPath) == pathPart.charAt(indexInPathPart)) ||
1015                        (pathPart.charAt(indexInPathPart) == '#'))
1016                    {
1017                        indexInPathPart++;
1018
1019                        if ((indexInPathPart >= pathPart.length()) ||
1020                            (mustMatchAtEnd && (indexInPathPart == pathPart.length() - 1) &&
1021                             (indexInPath == path.length() - 1)))
1022                        {
1023                            // found a match
1024                            return true;
1025                        }
1026                    }
1027                    else {
1028                        indexInPathPart = 0;
1029                    }
1030
1031                    indexInPath++;
1032                }
1033               
1034                return false;
1035            }
1036            else {
1037                // no condition ignoring specific characters
1038                return false;
1039            }
1040        }
1041
1042        /* (non-Javadoc)
1043         * @see java.lang.Object#toString()
1044         */
1045        @Override
1046        public String toString() {
1047            return "document(path=" + pathPart + ")";
1048        }
1049    }
1050
1051    /**
1052     * <p>
1053     * specification for a tag containing a tag name and either an index or id condition.
1054     * </p>
1055     */
1056    private static class TagSpec implements Spec {
1057
1058        /**
1059         * <p>
1060         * the name of the tag to match
1061         * </p>
1062         */
1063        private String tagName;
1064       
1065        /**
1066         * <p>
1067         * the index of the tag to match
1068         * </p>
1069         */
1070        private Integer indexCondition;
1071       
1072        /**
1073         * <p>
1074         * the id of the tag to match
1075         * </p>
1076         */
1077        private String idCondition;
1078
1079        /**
1080         * <p>
1081         * initializes the specification with all required parameters
1082         * </p>
1083         */
1084        private TagSpec(String tagName, Integer indexCondition, String idCondition) {
1085            this.tagName = tagName;
1086            this.indexCondition = indexCondition;
1087            this.idCondition = idCondition;
1088        }
1089
1090        /* (non-Javadoc)
1091         * @see de.ugoe.cs.autoquest.plugin.html.HTMLLogParser.Spec#isPattern()
1092         */
1093        @Override
1094        public boolean isPattern() {
1095            return (idCondition != null) && (idCondition.indexOf('#') > -1);
1096        }
1097
1098        /**
1099         * <p>
1100         * returns true if the provided tag information matches this specification. The id is
1101         * checked first. If the id condition has a # at some position, the respective element
1102         * of the provided id is ignored.
1103         * </p>
1104         */
1105        private boolean matches(String tagName, int index, String htmlId) {
1106            if (!this.tagName.equals(tagName)) {
1107                return false;
1108            }
1109           
1110            if (idCondition != null) {
1111                if (!idCondition.equals(htmlId)) {
1112                    // check if the id condition would match with ignoring specific characters
1113                    if ((htmlId != null) && isPattern()) {
1114                        // first of all, the length must match
1115                        if (idCondition.length() != htmlId.length()) {
1116                            return false;
1117                        }
1118                       
1119                        for (int i = 0; i < idCondition.length(); i++) {
1120                            if ((idCondition.charAt(i) != '#') &&
1121                                (idCondition.charAt(i) != htmlId.charAt(i)))
1122                            {
1123                                // if there is a character that is neither ignored nor matches
1124                                // the condition at a specific position, return "no match"
1125                                return false;
1126                            }
1127                        }
1128                       
1129                    }
1130                    else {
1131                        // no condition ignoring specific characters
1132                        return false;
1133                    }
1134                }
1135            }
1136           
1137            if ((indexCondition != null) && (index != indexCondition)) {
1138                return false;
1139            }
1140           
1141            return true;
1142        }
1143
1144        /**
1145         * <p>
1146         * returns the name of the tags matched by this specification
1147         * </p>
1148         */
1149        private String getTagName() {
1150            return tagName;
1151        }
1152
1153        /* (non-Javadoc)
1154         * @see java.lang.Object#toString()
1155         */
1156        @Override
1157        public String toString() {
1158            StringBuffer result = new StringBuffer(tagName);
1159           
1160            if (idCondition != null) {
1161                result.append("(htmlId=");
1162                result.append(idCondition);
1163                result.append(')');
1164            }
1165            else if (indexCondition != null) {
1166                result.append('[');
1167                result.append(indexCondition);
1168                result.append(']');
1169            }
1170           
1171            return result.toString();
1172        }
1173       
1174    }
1175}
Note: See TracBrowser for help on using the repository browser.