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
RevLine 
[1051]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.
[1047]14
15package de.ugoe.cs.autoquest.plugin.html;
16
[1354]17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.IOException;
[1893]21import java.io.UnsupportedEncodingException;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
[1354]24import java.util.HashMap;
[1496]25import java.util.LinkedList;
[1047]26import java.util.List;
[1893]27import java.util.ListIterator;
[1047]28import java.util.Map;
[1354]29import java.util.Properties;
[1069]30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
[1047]32
[1893]33import org.apache.commons.codec.binary.Base64;
[1047]34import org.xml.sax.SAXException;
35
36import de.ugoe.cs.autoquest.eventcore.Event;
[1059]37import de.ugoe.cs.autoquest.eventcore.IEventType;
[1047]38import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
[1084]39import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
[1047]40import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
41import de.ugoe.cs.autoquest.plugin.html.eventcore.HTMLEventTypeFactory;
[1496]42import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
[1069]43import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
[1047]44import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElement;
45import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLGUIElementSpec;
[1339]46import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
[1059]47import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
48import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec;
[1047]49
50/**
51 * <p>
52 * This class provides the functionality to parse XML log files generated by the HTMLMonitor of
[1416]53 * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model.
[1047]54 * </p>
[1416]55 * <p>
[1496]56 * The parser can be configured with parsing parameters to adapt, e.g., ids or or ignore indexes of
[1416]57 * parsed GUI elements. Details can be found in the manual pages of the respective parsing commands.
58 * </p>
[1047]59 *
[1069]60 * @author Fabian Glaser, Patrick Harms
[1047]61 * @version 1.0
62 *
63 */
[1069]64public class HTMLLogParser extends AbstractDefaultLogParser {
65   
[1047]66    /**
[1232]67     * <p>
68     * the pattern used for parsing HTML GUI element paths
69     * </p>
[1047]70     */
[1069]71    private Pattern htmlElementPattern =
72        Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-]+)\\))");
[1339]73   
74    /**
75     * <p>
[1893]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>
[1496]83     * file containing parameters to influence parsing
[1339]84     * </p>
85     */
[1496]86    private String parseParamFile;
[1047]87
[1339]88    /**
89     * <p>
[1354]90     * a map containing replacement specifications for ids of GUI elements
91     * </p>
92     */
[1496]93    private Map<String, List<ReplacementSpecification>> replacementSpecifications;
[1354]94
95    /**
96     * <p>
[1496]97     * initializes the parser with the file containing parsing parameters to be considered
[1339]98     * </p>
99     *
[1496]100     * @param parseParamFile the parsing parameters to be considered
[1339]101     */
[1496]102    public HTMLLogParser(String parseParamFile) {
103        this.parseParamFile = parseParamFile;
[1339]104    }
105
[1069]106    /* (non-Javadoc)
[1232]107     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
[1047]108     */
[1069]109    @Override
110    protected boolean handleGUIElement(String id, Map<String, String> parameters)
111        throws SAXException
112    {
[1893]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       
[1496]131        ensureParsingParameters();
132       
[1069]133        HTMLGUIElementSpec specification = null;
[1893]134        String replacementId = null;
135           
[1069]136        if (parameters.containsKey("host")) {
137            // this is a server specification
138            int port = 80;
[1272]139            String portStr = parameters.get("port");
[1069]140           
141            if (portStr != null) {
142                port = Integer.parseInt(portStr);
143            }
144           
[1893]145            String host = parameters.get("host");
146            specification = new HTMLServerSpec(host, port);
147            replacementId = calculateId(host, portStr);
[1047]148        }
[1069]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");
[1059]156                }
[1069]157               
[1893]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               
[1069]182                specification = new HTMLDocumentSpec
[1893]183                    ((HTMLServerSpec) parent.getSpecification(), path, query, title);
184               
185                replacementId = calculateId(parentId, path, query, title);
[1059]186            }
[1069]187            else if (parentId == null) {
188                throw new SAXException("invalid log: a document has no parent id");
[1059]189            }
[1069]190        }
191        else if (parameters.containsKey("tagname")) {
192            String tagName = parameters.get("tagname");
193           
194            if (!tagNameMustBeConsidered(tagName)) {
195                return true;
[1059]196            }
[1047]197
[1069]198            if (parent != null) {
[1435]199                if (!childrenMustBeConsidered(parent)) {
200                    return true;
201                }
202               
[1069]203                IGUIElement document = parent;
204               
205                while ((document != null) &&
206                       (!(document.getSpecification() instanceof HTMLDocumentSpec)))
207                {
208                    document = document.getParent();
[1047]209                }
[1069]210               
211                if (document == null) {
212                    throw new SAXException
213                        ("invalid log: parent hierarchy of a page element does not contain a " +
214                         "document");
[1047]215                }
[1069]216               
217                int index = -1;
218                String indexStr = parameters.get("index");
219
220                if ((indexStr != null) && (!"".equals(indexStr))) {
221                    index = Integer.parseInt(indexStr);
[1047]222                }
[1339]223               
224                String htmlId = parameters.get("htmlid");
225               
[1496]226                String replacement = getReplacementMapping(tagName, index, htmlId, parent);
[1339]227               
[1496]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                    }
[1354]244                }
[1339]245               
246                if ((htmlId == null) && (index == -1)) {
247                    // set at least a default index, if all is to be ignored.
248                    index = 0;
249                }
[1893]250               
251                HTMLDocumentSpec documentSpec = (HTMLDocumentSpec) document.getSpecification();
[1069]252
253                specification = new HTMLPageElementSpec
[1893]254                    (documentSpec, tagName.intern(), htmlId == null ? null : htmlId.intern(), index);
[1266]255               
[1893]256                replacementId = calculateId
257                    (documentSpec.getPath(), documentSpec.getQuery(), documentSpec.getTitle(),
258                     parentId, tagName, htmlId, (htmlId == null ? Integer.toString(index) : null));
259               
[1047]260            }
[1069]261            else if (parentId == null) {
262                throw new SAXException("invalid log: a page element has no parent id");
[1047]263            }
264        }
[1059]265        else {
[1069]266            throw new SAXException("invalid log: unknown GUI element");
[1047]267        }
268
[1069]269        if (specification != null) {
[1084]270            try {
[1893]271                idMapping.put(id, replacementId);
272               
273                super.getGUIElementTree().add(replacementId, parentId, specification);
[1084]274            }
275            catch (GUIModelException e) {
276                throw new SAXException("could not handle GUI element with id " +
277                                       id + ": " + e.getMessage(), e);
278            }
[1069]279            return true;
280        }
281        else {
282            return false;
283        }
[1047]284    }
[1339]285   
286    /**
287     * <p>
[1893]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>
[1496]324     * returns the replacement mapping for the tag specified by the parameters, if a mapping exists.
[1339]325     * </p>
326     *
[1416]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     *
[1496]332     * @return the replacement mapping, if any is configured; null else
[1339]333     */
[1496]334    private String getReplacementMapping(String         tagName,
335                                         int            index,
336                                         String         htmlId,
337                                         HTMLGUIElement parent)
[1339]338    {
[1496]339        List<ReplacementSpecification> mappingCandidates = replacementSpecifications.get(tagName);
340       
[1893]341        List<ReplacementSpecification> candidates = new LinkedList<>();
342       
[1496]343        if (mappingCandidates != null) {
344            for (ReplacementSpecification replacementSpec : mappingCandidates) {
345                if (replacementSpec.matches(tagName, index, htmlId, parent)) {
[1893]346                    candidates.add(replacementSpec);
[1339]347                }
348            }
349        }
350       
[1893]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        }
[1339]363    }
[1893]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    }
[1339]414
[1893]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
[1069]452    /* (non-Javadoc)
[1232]453     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String, Map)
[1051]454     */
[1047]455    @Override
[1069]456    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
457        String targetId = parameters.get("target");
458       
459        if (targetId == null) {
[1496]460            if (replacementSpecifications.size() != 0) {
[1339]461                throw new SAXException
462                    ("old log file versions can not be parsed with parse parameters");
463            }
464           
[1069]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");
[1047]470            }
[1069]471           
472            targetId = determineTargetId(targetDocument, targetDOMPath);
473           
474            if (targetId == null) {
475                // the target id can not be determined yet
476                return false;
477            }
[1047]478        }
[1069]479       
[1893]480        targetId = idMapping.get(targetId);
[1069]481       
[1893]482        if (targetId == null) {
[1093]483            // event not processible yet
[1069]484            return false;
[1047]485        }
[1893]486       
487        IGUIElement target = super.getGUIElementTree().find(targetId);
[1059]488
[1069]489        IEventType eventType =
490            HTMLEventTypeFactory.getInstance().getEventType(type, parameters, target);
491       
[1084]492        if (eventType != null) {
493            Event event = new Event(eventType, target);
[1059]494
[1084]495            String timestampStr = parameters.get("timestamp");
[1069]496       
[1084]497            if (timestampStr != null) {
498                event.setTimestamp(Long.parseLong(timestampStr));
499            }
[1059]500
[1084]501            ((HTMLGUIElement) event.getTarget()).markUsed();
[1069]502       
[1084]503            super.addToSequence(event);
504        }
505        // else ignore unknown event type
[1059]506
[1069]507        return true;
[1047]508    }
[1059]509
[1047]510    /**
511     * <p>
[1496]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>
[1232]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.
[1047]566     * </p>
567     */
[1069]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        }
[1047]632    }
[1059]633
[1047]634    /**
635     * <p>
[1069]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.
[1047]638     * </p>
[1069]639     *
[1232]640     * @param tagName the tag name to check
641     *
642     * @return true, if the tag must be considered, false else
[1059]643     */
[1069]644    private boolean tagNameMustBeConsidered(String tagName) {
[1266]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       
[1069]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) &&
[1232]660            !"colgroup".equals(tagName) && !"col".equals(tagName) && !"hr".equals(tagName) &&
[1817]661            !"param".equals(tagName) && !"sfmsg".equals(tagName) &&
662            !"wappalyzerdata".equals(tagName);
[1059]663
[1069]664    }
[1059]665
[1435]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
[1496]684    /**
[1893]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    /**
[1496]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        /**
[1893]720         *
721         */
722        private static final String LAST_TAG_NAME_FOR_DOCUMENTS = "LAST_TAG_NAME_FOR_DOCUMENTS";
723       
724        /**
[1496]725         * <p>
726         * the pattern used for parsing parsing parameters
727         * </p>
728         */
729        private Pattern htmlElementSpecPattern = Pattern.compile
[1893]730            ("(document\\(path=([\\w/\\-#$&%]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w\\-_#]+)\\))?)");
[1496]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         */
[1893]761        private ReplacementSpecification(String tagSpec, String replacement) {
[1496]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           
[1893]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            }
[1496]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>
[1893]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>
[1496]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;
[1893]864            String currentPath = LAST_TAG_NAME_FOR_DOCUMENTS.equals(tagName) ? htmlId : null;
[1496]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();
[1893]890                    }
[1496]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     */
[1893]952    private static interface Spec {
[1496]953
[1893]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
[1496]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
[1893]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
[1496]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) {
[1893]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            }
[1496]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
[1893]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
[1496]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
[1893]1113                    if ((htmlId != null) && isPattern()) {
[1496]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    }
[1047]1175}
Note: See TracBrowser for help on using the repository browser.