Index: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java
===================================================================
--- trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java	(revision 1817)
+++ trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java	(revision 1893)
@@ -19,7 +19,11 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Properties;
@@ -27,4 +31,5 @@
 import java.util.regex.Pattern;
 
+import org.apache.commons.codec.binary.Base64;
 import org.xml.sax.SAXException;
 
@@ -69,4 +74,11 @@
     /**
      * <p>
+     * the mapping between ids and their replacements due to merging while parsing
+     * </p>
+     */
+    private Map<String, String> idMapping = new HashMap<>();
+    
+    /**
+     * <p>
      * file containing parameters to influence parsing
      * </p>
@@ -99,11 +111,27 @@
         throws SAXException
     {
+        if (idMapping.containsKey(id)) {
+            // the element is already existing. Return, that it was processed
+            return true;
+        }
+        
+        String parentId = parameters.get("parent");
+        
+        if (parentId != null) {
+            parentId = idMapping.get(parentId);
+
+            if (parentId == null) {
+                // parent not yet handled, return that this elements can not be handled yet
+                return false;
+            }
+        }
+        
+        HTMLGUIElement parent = (HTMLGUIElement) super.getGUIElementTree().find(parentId);
+        
         ensureParsingParameters();
         
         HTMLGUIElementSpec specification = null;
-        
-        String parentId = parameters.get("parent");
-        HTMLGUIElement parent = (HTMLGUIElement) super.getGUIElementTree().find(parentId);
-
+        String replacementId = null;
+            
         if (parameters.containsKey("host")) {
             // this is a server specification
@@ -115,5 +143,7 @@
             }
             
-            specification = new HTMLServerSpec(parameters.get("host"), port);
+            String host = parameters.get("host");
+            specification = new HTMLServerSpec(host, port);
+            replacementId = calculateId(host, portStr);
         }
         else if (parameters.containsKey("path")) {
@@ -126,7 +156,32 @@
                 }
                 
+                String path = parameters.get("path");
+                String query = parameters.get("query");
+                String title = parameters.get("title");
+                
+                String replacement = getReplacementMapping(path, parent);
+                
+                if (replacement != null) {
+                    if (replacement.startsWith("CLEAR_QUERY,")) {
+                        query = null;
+                        replacement = replacement.substring("CLEAR_QUERY,".length());
+                    }
+                    else if ("CLEAR_QUERY".equals(replacement)) {
+                        query = null;
+                        replacement = path;
+                    }
+                    
+                    if ("".equals(replacement)) {
+                        path = null;
+                    }
+                    else {
+                        path = replacement;
+                    }
+                }
+                
                 specification = new HTMLDocumentSpec
-                    ((HTMLServerSpec) parent.getSpecification(), parameters.get("path"),
-                     parameters.get("query"), parameters.get("title"));
+                    ((HTMLServerSpec) parent.getSpecification(), path, query, title);
+                
+                replacementId = calculateId(parentId, path, query, title);
             }
             else if (parentId == null) {
@@ -193,8 +248,13 @@
                     index = 0;
                 }
+                
+                HTMLDocumentSpec documentSpec = (HTMLDocumentSpec) document.getSpecification();
 
                 specification = new HTMLPageElementSpec
-                    ((HTMLDocumentSpec) document.getSpecification(),
-                     tagName.intern(), htmlId == null ? null : htmlId.intern(), index);
+                    (documentSpec, tagName.intern(), htmlId == null ? null : htmlId.intern(), index);
+                
+                replacementId = calculateId
+                    (documentSpec.getPath(), documentSpec.getQuery(), documentSpec.getTitle(),
+                     parentId, tagName, htmlId, (htmlId == null ? Integer.toString(index) : null));
                 
             }
@@ -209,5 +269,7 @@
         if (specification != null) {
             try {
-                super.getGUIElementTree().add(id, parentId, specification);
+                idMapping.put(id, replacementId);
+                
+                super.getGUIElementTree().add(replacementId, parentId, specification);
             }
             catch (GUIModelException e) {
@@ -222,4 +284,40 @@
     }
     
+    /**
+     * <p>
+     * returns the replacement mapping for the document path specified by the parameter, if a
+     * mapping exists.
+     * </p>
+     *
+     * @param path the path of the document
+     * 
+     * @return the replacement mapping, if any is configured; null else
+     */
+    private String getReplacementMapping(String path, HTMLGUIElement parent) {
+        List<ReplacementSpecification> mappingCandidates =
+            replacementSpecifications.get(ReplacementSpecification.LAST_TAG_NAME_FOR_DOCUMENTS);
+        
+        List<ReplacementSpecification> candidates = new LinkedList<>();
+        
+        if (mappingCandidates != null) {
+            for (ReplacementSpecification replacementSpec : mappingCandidates) {
+                if (replacementSpec.matches(ReplacementSpecification.LAST_TAG_NAME_FOR_DOCUMENTS,
+                                            -1, path, parent))
+                {
+                    candidates.add(replacementSpec);
+                }
+            }
+        }
+        
+        prioritizeReplacementSpecs(candidates, "document " + path);
+        
+        if (candidates.size() == 1) {
+            return candidates.get(0).getReplacement();
+        }
+        else {
+            return null;
+        }
+    }
+
     /**
      * <p>
@@ -241,13 +339,113 @@
         List<ReplacementSpecification> mappingCandidates = replacementSpecifications.get(tagName);
         
+        List<ReplacementSpecification> candidates = new LinkedList<>();
+        
         if (mappingCandidates != null) {
             for (ReplacementSpecification replacementSpec : mappingCandidates) {
                 if (replacementSpec.matches(tagName, index, htmlId, parent)) {
-                    return replacementSpec.getReplacement();
-                }
-            }
-        }
-        
-        return null;
+                    candidates.add(replacementSpec);
+                }
+            }
+        }
+        
+        StringBuffer forWhat = new StringBuffer();
+        forWhat.append("tag ");
+        toString(tagName, index, htmlId, parent, forWhat);
+        
+        prioritizeReplacementSpecs(candidates, forWhat.toString());
+        
+        if (candidates.size() == 1) {
+            return candidates.get(0).getReplacement();
+        }
+        else {
+            return null;
+        }
+    }
+    
+    /**
+     * <p>
+     * decides, which replacement specification is to be preferred, if several match
+     * </p>
+     */
+    private void prioritizeReplacementSpecs(List<ReplacementSpecification> candidates,
+                                            String                         forWhat)
+    {
+        boolean hasNonPattern = false;
+        
+        for (ReplacementSpecification spec : candidates) {
+            if (!spec.isPattern()) {
+                hasNonPattern = true;
+                break;
+            }
+        }
+        
+        if (hasNonPattern) {
+            ListIterator<ReplacementSpecification> it = candidates.listIterator();
+            
+            while (it.hasNext()) {
+                if (it.next().isPattern()) {
+                    it.remove();
+                }
+            }
+        }
+        
+        if (candidates.size() > 1) {
+            StringBuffer message = new StringBuffer();
+            message.append("parse parameter file is ambigious for ");
+            message.append(forWhat);
+            message.append(". Can be mapped using ");
+            
+            int counter = 0;
+            for (ReplacementSpecification spec : candidates) {
+                message.append(spec);
+                counter++;
+                
+                if (counter < (candidates.size() - 1)) {
+                    message.append(", ");
+                }
+                else if (counter < candidates.size()) {
+                    message.append(", and ");
+                }
+            }
+                        
+            throw new IllegalArgumentException(message.toString());
+        }
+    }
+
+    /**
+     *
+     */
+    private void toString(String         tagName,
+                          int            index,
+                          String         htmlId,
+                          HTMLGUIElement parent,
+                          StringBuffer   message)
+    {
+        LinkedList<HTMLGUIElementSpec> specs = new LinkedList<>();
+        
+        HTMLGUIElement currentParent = parent;
+        
+        while (currentParent != null) {
+            specs.addFirst((HTMLGUIElementSpec) currentParent.getSpecification());
+            currentParent = (HTMLGUIElement) currentParent.getParent();
+        }
+        
+        for (HTMLGUIElementSpec spec : specs) {
+            message.append(spec.toString());
+            message.append("/");
+        }
+        
+        message.append(tagName);
+        
+        if (htmlId != null) {
+            message.append("(htmlId=");
+            message.append(htmlId);
+            message.append(')');
+        }
+        else {
+            message.append('[');
+            message.append(index);
+            message.append(']');
+        }
     }
 
@@ -280,10 +478,12 @@
         }
         
-        IGUIElement target = super.getGUIElementTree().find(targetId);
-        
-        if (target == null) {
+        targetId = idMapping.get(targetId);
+        
+        if (targetId == null) {
             // event not processible yet
             return false;
         }
+        
+        IGUIElement target = super.getGUIElementTree().find(targetId);
 
         IEventType eventType =
@@ -483,4 +683,33 @@
 
     /**
+     * <p>
+     * calculates a unique id for the given string fragments using SHA-512 and Base64 encoding.
+     * </p>
+     *
+     * @param fragments strings to be used for calculating a unique id
+     * 
+     * @return a Base64 encoded unique id for the provided fragments
+     */
+    private String calculateId(String... fragments) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("SHA-512");
+            
+            for (String fragment : fragments) {
+                if (fragment != null) {
+                    md.update(fragment.getBytes("UTF-8"));
+                }
+            }
+            
+            return Base64.encodeBase64String(md.digest());
+        }
+        catch (UnsupportedEncodingException e) {
+            throw new IllegalStateException("Java VM does not support this code");
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("Java VM does not support this code");
+        }
+    }
+
+    /**
      * <p>specification for a replacement consisting of path of tag or document specifications
      * and the appropriate replacement.</p>
@@ -489,4 +718,9 @@
         
         /**
+         * 
+         */
+        private static final String LAST_TAG_NAME_FOR_DOCUMENTS = "LAST_TAG_NAME_FOR_DOCUMENTS";
+        
+        /**
          * <p>
          * the pattern used for parsing parsing parameters
@@ -494,5 +728,5 @@
          */
         private Pattern htmlElementSpecPattern = Pattern.compile
-            ("(document\\(path=([\\w/-]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w-_#]+)\\))?)");
+            ("(document\\(path=([\\w/\\-#$&%]+)\\))|((\\w+)(\\[(\\d+)\\]|\\(htmlId=([\\w\\-_#]+)\\))?)");
         
         /**
@@ -525,5 +759,5 @@
          * </p>
          */
-        public ReplacementSpecification(String tagSpec, String replacement) {
+        private ReplacementSpecification(String tagSpec, String replacement) {
             List<String> tagSpecs = split(tagSpec);
             
@@ -558,5 +792,10 @@
             }
             
-            this.lastTagName = ((TagSpec) this.specs.get(this.specs.size() - 1)).getTagName();
+            if (this.specs.get(this.specs.size() - 1) instanceof TagSpec) {
+                this.lastTagName = ((TagSpec) this.specs.get(this.specs.size() - 1)).getTagName();
+            }
+            else {
+                this.lastTagName = LAST_TAG_NAME_FOR_DOCUMENTS;
+            }
             
             this.replacement = replacement;
@@ -601,4 +840,19 @@
         /**
          * <p>
+         * returns true, if the spec is a pattern, i.e. matches a group of similar elements
+         * </p>
+         */
+        public boolean isPattern() {
+            for (Spec spec : specs) {
+                if (spec.isPattern()) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+
+        /**
+         * <p>
          * checks, if the tag identified by the parameters matches this specificaiton.
          * </p>
@@ -608,5 +862,5 @@
             int currentIndex = index;
             String currentHtmlId = htmlId;
-            String currentPath = null;
+            String currentPath = LAST_TAG_NAME_FOR_DOCUMENTS.equals(tagName) ? htmlId : null;
             HTMLGUIElement currentParent = parent;
             
@@ -634,5 +888,5 @@
                         currentPath = null;
                         currentParent = (HTMLGUIElement) currentParent.getParent();
-                     }
+                    }
                     else if (currentParent instanceof HTMLDocument) {
                         currentTagName = null;
@@ -696,5 +950,16 @@
      * </p>
      */
-    private static interface Spec { }
+    private static interface Spec {
+
+        /**
+         * <p>
+         * returns true if the spec is a pattern, i.e., matches a group of similar elements
+         * </p>
+         *
+         * @return
+         */
+        boolean isPattern();
+        
+    }
 
     /**
@@ -721,4 +986,12 @@
         }
 
+        /* (non-Javadoc)
+         * @see de.ugoe.cs.autoquest.plugin.html.HTMLLogParser.Spec#isPattern()
+         */
+        @Override
+        public boolean isPattern() {
+            return (pathPart != null) && (pathPart.indexOf('#') > -1);
+        }
+
         /**
          * <p>
@@ -727,5 +1000,42 @@
          */
         private boolean matches(String path) {
-            return path.contains(pathPart);
+            if ((path != null) && (path.contains(pathPart))) {
+                return true;
+            }
+            else if ((path != null) && (isPattern() || (pathPart.indexOf('$') > -1))) {
+                // check if the path condition would match with ignoring specific characters
+                
+                boolean mustMatchAtEnd = pathPart.charAt(pathPart.length() - 1) == '$';
+
+                int indexInPath = 0;
+                int indexInPathPart = 0;
+
+                while (indexInPath < path.length()) {
+                    if ((path.charAt(indexInPath) == pathPart.charAt(indexInPathPart)) ||
+                        (pathPart.charAt(indexInPathPart) == '#'))
+                    {
+                        indexInPathPart++;
+
+                        if ((indexInPathPart >= pathPart.length()) ||
+                            (mustMatchAtEnd && (indexInPathPart == pathPart.length() - 1) &&
+                             (indexInPath == path.length() - 1)))
+                        {
+                            // found a match
+                            return true;
+                        }
+                    }
+                    else {
+                        indexInPathPart = 0;
+                    }
+
+                    indexInPath++;
+                }
+                
+                return false;
+            }
+            else {
+                // no condition ignoring specific characters
+                return false;
+            }
         }
 
@@ -778,4 +1088,12 @@
         }
 
+        /* (non-Javadoc)
+         * @see de.ugoe.cs.autoquest.plugin.html.HTMLLogParser.Spec#isPattern()
+         */
+        @Override
+        public boolean isPattern() {
+            return (idCondition != null) && (idCondition.indexOf('#') > -1);
+        }
+
         /**
          * <p>
@@ -793,5 +1111,5 @@
                 if (!idCondition.equals(htmlId)) {
                     // check if the id condition would match with ignoring specific characters
-                    if ((htmlId != null) && (idCondition.indexOf('#') > -1)) {
+                    if ((htmlId != null) && isPattern()) {
                         // first of all, the length must match
                         if (idCondition.length() != htmlId.length()) {
