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

Last change on this file since 2215 was 2215, checked in by pharms, 7 years ago
  • java doc issues removal
  • Property svn:mime-type set to text/plain
File size: 40.1 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.EventTargetModelException;
38import de.ugoe.cs.autoquest.eventcore.IEventType;
39import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
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 (EventTargetModelException 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        else {
480            targetId = idMapping.get(targetId);
481        }
482       
483        if (targetId == null) {
484            // event not processible yet
485            return false;
486        }
487       
488        IGUIElement target = super.getGUIElementTree().find(targetId);
489
490        IEventType eventType =
491            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
492       
493        if (eventType != null) {
494            Event event = new Event(eventType, target);
495
496            String timestampStr = parameters.get("timestamp");
497       
498            if (timestampStr != null) {
499                event.setTimestamp(Long.parseLong(timestampStr));
500            }
501
502            ((HTMLGUIElement) event.getTarget()).markUsed();
503       
504            super.addToSequence(event);
505        }
506        // else ignore unknown event type
507
508        return true;
509    }
510
511    /**
512     * <p>
513     * reads parsing parameters from the config file and makes them available for the parsing
514     * process
515     * </p>
516     */
517    private void ensureParsingParameters() throws SAXException {
518        if (replacementSpecifications == null) {
519            replacementSpecifications = new HashMap<String, List<ReplacementSpecification>>();
520           
521            if (parseParamFile != null) {
522                Properties props = new Properties();
523                FileInputStream stream = null;
524                try {
525                    stream = new FileInputStream(new File(parseParamFile));
526                    props.load(stream);
527                }
528                catch (FileNotFoundException e) {
529                    throw new SAXException("could not find file " + parseParamFile, e);
530                }
531                catch (IOException e) {
532                    throw new SAXException("error reading file " + parseParamFile, e);
533                }
534                finally {
535                    if (stream != null) {
536                        try {
537                            stream.close();
538                        }
539                        catch (IOException e) {
540                            // ignore
541                        }
542                    }
543                }
544
545                for (Map.Entry<Object, Object> entry : props.entrySet()) {
546                    ReplacementSpecification replSpec = new ReplacementSpecification
547                        ((String) entry.getKey(), (String) entry.getValue());
548                   
549                    List<ReplacementSpecification> similarReplSpecs =
550                        replacementSpecifications.get(replSpec.getLastTagName());
551                   
552                    if (similarReplSpecs == null) {
553                        similarReplSpecs = new LinkedList<ReplacementSpecification>();
554                        replacementSpecifications.put(replSpec.getLastTagName(), similarReplSpecs);
555                    }
556                   
557                    similarReplSpecs.add(replSpec);
558                }
559            }
560        }
561    }
562   
563    /**
564     * <p>
565     * used to determine the id of a target denoted by an event. This is only required for older
566     * document formats. The new formats use concrete ids.
567     * </p>
568     */
569    private String determineTargetId(String targetDocument, String targetDOMPath)
570        throws SAXException
571    {
572        IGUIElement document = super.getGUIElementTree().find(idMapping.get(targetDocument));
573       
574        if (document == null) {
575            return null;
576        }
577       
578        if (!(document.getSpecification() instanceof HTMLDocumentSpec)) {
579            throw new SAXException("an id that should refer to an HTML document refers to" +
580                                   "something else");
581        }
582       
583        GUIModel model = super.getGUIElementTree().getGUIModel();
584        IGUIElement child = document;
585        String[] pathElements = targetDOMPath.split("/");
586        int pathIndex = 0;
587       
588        HTMLPageElementSpec compareSpec;
589        String tagName;
590        int index;
591        String htmlId;
592       
593        while ((pathIndex < pathElements.length) && (child != null)) {
594            if ((pathElements[pathIndex] != null) && (!"".equals(pathElements[pathIndex]))) {           
595                Matcher matcher = htmlElementPattern.matcher(pathElements[pathIndex]);
596                if (!matcher.matches()) {
597                    throw new SAXException
598                        ("could not parse target DOM path element " + pathElements[pathIndex]);
599                }
600
601                tagName = matcher.group(1);
602                String indexStr = matcher.group(3);
603                htmlId = matcher.group(4);
604
605                index = -1;
606                if ((indexStr != null) && (!"".equals(indexStr))) {
607                    index = Integer.parseInt(indexStr);
608                }
609
610                compareSpec = new HTMLPageElementSpec
611                    ((HTMLDocumentSpec) document.getSpecification(), tagName, htmlId, index);
612
613                List<IGUIElement> children = model.getChildren(child);
614                child = null;
615
616                for (IGUIElement candidate : children) {
617                    if (compareSpec.getSimilarity(candidate.getSpecification())) {
618                        child = candidate;
619                        break;
620                    }
621                }
622            }
623           
624            pathIndex++;
625        }
626       
627        if (child != null) {
628            return super.getGUIElementTree().find(child);
629        }
630        else {
631            return null;
632        }
633    }
634
635    /**
636     * <p>
637     * checks if tags with the provided name must be handled in the GUI model. As an example,
638     * it is not necessary to handle "head" tags and anything included in them.
639     * </p>
640     *
641     * @param tagName the tag name to check
642     *
643     * @return true, if the tag must be considered, false else
644     */
645    private boolean tagNameMustBeConsidered(String tagName) {
646        if (!tagName.startsWith("input_")) {
647            for (int i = 0; i < tagName.length(); i++) {
648                // all known HTML tags are either letters or digits, but nothing else. Any GUI model
649                // containing something different is proprietary and, therefore, ignored.
650                if (!Character.isLetterOrDigit(tagName.charAt(i))) {
651                    return false;
652                }
653            }
654        }
655       
656        return
657            !"head".equals(tagName) && !"title".equals(tagName) && !"script".equals(tagName) &&
658            !"style".equals(tagName) && !"link".equals(tagName) && !"meta".equals(tagName) &&
659            !"iframe".equals(tagName) && !"input_hidden".equals(tagName) &&
660            !"option".equals(tagName) && !"tt".equals(tagName) && !"br".equals(tagName) &&
661            !"colgroup".equals(tagName) && !"col".equals(tagName) && !"hr".equals(tagName) &&
662            !"param".equals(tagName) && !"sfmsg".equals(tagName) &&
663            !"wappalyzerdata".equals(tagName)&& !"mytubeelement".equals(tagName);
664
665    }
666
667    /**
668     * <p>
669     * checks if the children of a specified parent must be added to the GUI model or not.
670     * </p>
671     *
672     * @param parent the parent tag to check
673     *
674     * @return true, if the child of the tag must be considered, false else
675     */
676    private boolean childrenMustBeConsidered(HTMLGUIElement parent) {
677        if (parent instanceof HTMLPageElement) {
678            return !"svg".equals(((HTMLPageElement) parent).getTagName());
679        }
680        else {
681            return true;
682        }
683    }
684
685    /**
686     * <p>
687     * calculates a unique id for the given string fragments using SHA-512 and Base64 encoding.
688     * </p>
689     *
690     * @param fragments strings to be used for calculating a unique id
691     *
692     * @return a Base64 encoded unique id for the provided fragments
693     */
694    private String calculateId(String... fragments) {
695        try {
696            MessageDigest md = MessageDigest.getInstance("SHA-512");
697           
698            for (String fragment : fragments) {
699                if (fragment != null) {
700                    md.update(fragment.getBytes("UTF-8"));
701                }
702            }
703           
704            return Base64.encodeBase64String(md.digest());
705        }
706        catch (UnsupportedEncodingException e) {
707            throw new IllegalStateException("Java VM does not support this code");
708        }
709        catch (NoSuchAlgorithmException e) {
710            throw new IllegalStateException("Java VM does not support this code");
711        }
712    }
713
714    /**
715     * <p>specification for a replacement consisting of path of tag or document specifications
716     * and the appropriate replacement.</p>
717     */
718    private static class ReplacementSpecification {
719       
720        /**
721         *
722         */
723        private static final String LAST_TAG_NAME_FOR_DOCUMENTS = "LAST_TAG_NAME_FOR_DOCUMENTS";
724       
725        /**
726         * <p>
727         * the pattern used for parsing parsing parameters
728         * </p>
729         */
730        private Pattern htmlElementSpecPattern = Pattern.compile
731            ("(document\\(path=([\\w/\\-\\^#$&%\\.]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w\\-_#]+)\\))?)");
732       
733        /**
734         * <p>
735         * the path of specifications (tags and document) specifying the tag for which this
736         * replacement is specified
737         * </p>
738         */
739        private List<Spec> specs = new LinkedList<Spec>();
740       
741        /**
742         * <p>
743         * the name of the last tag in the specification path (used for indexing purposes)
744         * </p>
745         */
746        private String lastTagName;
747       
748        /**
749         * <p>
750         * the configured replacement
751         * </p>
752         */
753        private String replacement;
754
755        /**
756         * <p>
757         * initializes the specification with the key/value strings from the config file. Parses
758         * the key to get the specification path consisting of, optionally, a document
759         * specification and one or more tag specification.
760         * </p>
761         */
762        private ReplacementSpecification(String tagSpec, String replacement) {
763            List<String> tagSpecs = split(tagSpec);
764           
765            for (int i = 0; i < tagSpecs.size(); i++) {
766                Matcher matcher = htmlElementSpecPattern.matcher(tagSpecs.get(i));
767               
768                if (!matcher.matches()) {
769                    throw new IllegalArgumentException
770                        ("illegal tag specification " + tagSpecs.get(i));
771                }
772               
773                if (matcher.group(1) != null) {
774                    this.specs.add(new DocumentSpec(matcher.group(2)));
775                }
776                else if (matcher.group(4) != null) {
777                    String indexConditionStr = matcher.group(6);
778                    Integer indexCondition = null;
779               
780                    if (indexConditionStr != null) {
781                        try {
782                            indexCondition = Integer.parseInt(indexConditionStr);
783                        }
784                        catch (NumberFormatException e) {
785                            throw new IllegalArgumentException
786                                ("illegal tag index specification " + indexConditionStr, e);
787                        }
788                    }
789               
790                    this.specs.add
791                        (new TagSpec(matcher.group(4), indexCondition, matcher.group(7)));
792                }
793            }
794           
795            if (this.specs.get(this.specs.size() - 1) instanceof TagSpec) {
796                this.lastTagName = ((TagSpec) this.specs.get(this.specs.size() - 1)).getTagName();
797            }
798            else {
799                this.lastTagName = LAST_TAG_NAME_FOR_DOCUMENTS;
800            }
801           
802            this.replacement = replacement;
803        }
804
805        /**
806         * <p>
807         * convenience method to split the key of a key/value pair from the config file into its
808         * parts
809         * </p>
810         */
811        private List<String> split(String tagSpec) {
812            List<String> specs = new LinkedList<String>();
813           
814            StringBuffer currentSpec = new StringBuffer();
815            int openBraces = 0;
816           
817            for (int i = 0; i < tagSpec.length(); i++) {
818                char curChar = tagSpec.charAt(i);
819                if ((openBraces == 0) && ('/' == curChar) && (currentSpec.length() > 0)) {
820                    specs.add(currentSpec.toString());
821                    currentSpec.setLength(0);
822                }
823                else {
824                    if ('(' == curChar) {
825                        openBraces++;
826                    }
827                    else if (')' == curChar) {
828                        openBraces--;
829                    }
830                    currentSpec.append(curChar);
831                }
832            }
833           
834            if (currentSpec.length() > 0) {
835                specs.add(currentSpec.toString());
836            }
837           
838            return specs;
839        }
840
841        /**
842         * <p>
843         * returns true, if the spec is a pattern, i.e. matches a group of similar elements
844         * </p>
845         */
846        public boolean isPattern() {
847            for (Spec spec : specs) {
848                if (spec.isPattern()) {
849                    return true;
850                }
851            }
852           
853            return false;
854        }
855
856        /**
857         * <p>
858         * checks, if the tag identified by the parameters matches this specificaiton.
859         * </p>
860         */
861        private boolean matches(String tagName, int index, String htmlId, HTMLGUIElement parent) {
862            String currentTagName = tagName;
863            int currentIndex = index;
864            String currentHtmlId = htmlId;
865            String currentPath = LAST_TAG_NAME_FOR_DOCUMENTS.equals(tagName) ? htmlId : null;
866            HTMLGUIElement currentParent = parent;
867           
868            int i = specs.size() - 1;
869           
870            while (i >= 0) {
871                if ((specs.get(i) instanceof TagSpec) &&
872                    (!((TagSpec) specs.get(i)).matches(currentTagName, currentIndex, currentHtmlId)))
873                {
874                    return false;
875                }
876                else if ((specs.get(i) instanceof DocumentSpec) &&
877                         (!((DocumentSpec) specs.get(i)).matches(currentPath)))
878                {
879                    return false;
880                }
881               
882                i--;
883               
884                if (i >= 0) {
885                    if (currentParent instanceof HTMLPageElement) {
886                        currentTagName = ((HTMLPageElement) currentParent).getTagName();
887                        currentIndex = ((HTMLPageElement) currentParent).getIndex();
888                        currentHtmlId = ((HTMLPageElement) currentParent).getHtmlId();
889                        currentPath = null;
890                        currentParent = (HTMLGUIElement) currentParent.getParent();
891                    }
892                    else if (currentParent instanceof HTMLDocument) {
893                        currentTagName = null;
894                        currentIndex = Integer.MIN_VALUE;
895                        currentHtmlId = null;
896                        currentPath = ((HTMLDocument) currentParent).getPath();
897                        currentParent = (HTMLGUIElement) currentParent.getParent();
898                    }
899                    else {
900                        throw new IllegalArgumentException
901                            ("specification matches documents or servers. This is not supported yet.");
902                    }
903                }
904            }
905           
906            return true;
907        }
908
909        /**
910         * <p>
911         * returns the specified replacement
912         * </p>
913         */
914        private String getReplacement() {
915            return replacement;
916        }
917
918        /**
919         * <p>
920         * returns the name of the last tag specified in the specification path
921         * </p>
922         */
923        private String getLastTagName() {
924            return lastTagName;
925        }
926
927        /* (non-Javadoc)
928         * @see java.lang.Object#toString()
929         */
930        @Override
931        public String toString() {
932            StringBuffer result = new StringBuffer();
933            for (Spec spec : specs) {
934                if (result.length() > 0) {
935                    result.append("/");
936                }
937                result.append(spec);
938            }
939           
940            result.append('=');
941            result.append(replacement);
942           
943            return result.toString();
944        }
945       
946    }
947
948    /**
949     * <p>
950     * parent type for document and tag specifications
951     * </p>
952     */
953    private static interface Spec {
954
955        /**
956         * <p>
957         * returns true if the spec is a pattern, i.e., matches a group of similar elements
958         * </p>
959         *
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 mustMatchBeginning = pathPart.charAt(0) == '^';
1009                boolean mustMatchAtEnd = pathPart.charAt(pathPart.length() - 1) == '$';
1010
1011                int indexInPath = 0;
1012                int indexInPathPart = mustMatchBeginning ? 1 : 0;
1013
1014                while (indexInPath < path.length()) {
1015                    if ((path.charAt(indexInPath) == pathPart.charAt(indexInPathPart)) ||
1016                        (pathPart.charAt(indexInPathPart) == '#'))
1017                    {
1018                        indexInPathPart++;
1019
1020                        if ((indexInPathPart >= pathPart.length()) ||
1021                            (mustMatchAtEnd && (indexInPathPart == pathPart.length() - 1) &&
1022                             (indexInPath == path.length() - 1)))
1023                        {
1024                            // found a match
1025                            return true;
1026                        }
1027                    }
1028                    else if (mustMatchBeginning) {
1029                        // any mismatch means, we do not match from the beginning
1030                        return false;
1031                    }
1032                    else {
1033                        indexInPathPart = 0;
1034                    }
1035
1036                    indexInPath++;
1037                }
1038               
1039                return false;
1040            }
1041            else {
1042                // no condition ignoring specific characters
1043                return false;
1044            }
1045        }
1046
1047        /* (non-Javadoc)
1048         * @see java.lang.Object#toString()
1049         */
1050        @Override
1051        public String toString() {
1052            return "document(path=" + pathPart + ")";
1053        }
1054    }
1055
1056    /**
1057     * <p>
1058     * specification for a tag containing a tag name and either an index or id condition.
1059     * </p>
1060     */
1061    private static class TagSpec implements Spec {
1062
1063        /**
1064         * <p>
1065         * the name of the tag to match
1066         * </p>
1067         */
1068        private String tagName;
1069       
1070        /**
1071         * <p>
1072         * the index of the tag to match
1073         * </p>
1074         */
1075        private Integer indexCondition;
1076       
1077        /**
1078         * <p>
1079         * the id of the tag to match
1080         * </p>
1081         */
1082        private String idCondition;
1083
1084        /**
1085         * <p>
1086         * initializes the specification with all required parameters
1087         * </p>
1088         */
1089        private TagSpec(String tagName, Integer indexCondition, String idCondition) {
1090            this.tagName = tagName;
1091            this.indexCondition = indexCondition;
1092            this.idCondition = idCondition;
1093        }
1094
1095        /* (non-Javadoc)
1096         * @see de.ugoe.cs.autoquest.plugin.html.HTMLLogParser.Spec#isPattern()
1097         */
1098        @Override
1099        public boolean isPattern() {
1100            return (idCondition != null) && (idCondition.indexOf('#') > -1);
1101        }
1102
1103        /**
1104         * <p>
1105         * returns true if the provided tag information matches this specification. The id is
1106         * checked first. If the id condition has a # at some position, the respective element
1107         * of the provided id is ignored.
1108         * </p>
1109         */
1110        private boolean matches(String tagName, int index, String htmlId) {
1111            if (!this.tagName.equals(tagName)) {
1112                return false;
1113            }
1114           
1115            if (idCondition != null) {
1116                if (!idCondition.equals(htmlId)) {
1117                    // check if the id condition would match with ignoring specific characters
1118                    if ((htmlId != null) && isPattern()) {
1119                        // first of all, the length must match
1120                        if (idCondition.length() != htmlId.length()) {
1121                            return false;
1122                        }
1123                       
1124                        for (int i = 0; i < idCondition.length(); i++) {
1125                            if ((idCondition.charAt(i) != '#') &&
1126                                (idCondition.charAt(i) != htmlId.charAt(i)))
1127                            {
1128                                // if there is a character that is neither ignored nor matches
1129                                // the condition at a specific position, return "no match"
1130                                return false;
1131                            }
1132                        }
1133                       
1134                    }
1135                    else {
1136                        // no condition ignoring specific characters
1137                        return false;
1138                    }
1139                }
1140            }
1141           
1142            if ((indexCondition != null) && (index != indexCondition)) {
1143                return false;
1144            }
1145           
1146            return true;
1147        }
1148
1149        /**
1150         * <p>
1151         * returns the name of the tags matched by this specification
1152         * </p>
1153         */
1154        private String getTagName() {
1155            return tagName;
1156        }
1157
1158        /* (non-Javadoc)
1159         * @see java.lang.Object#toString()
1160         */
1161        @Override
1162        public String toString() {
1163            StringBuffer result = new StringBuffer(tagName);
1164           
1165            if (idCondition != null) {
1166                result.append("(htmlId=");
1167                result.append(idCondition);
1168                result.append(')');
1169            }
1170            else if (indexCondition != null) {
1171                result.append('[');
1172                result.append(indexCondition);
1173                result.append(']');
1174            }
1175           
1176            return result.toString();
1177        }
1178       
1179    }
1180}
Note: See TracBrowser for help on using the repository browser.